Skip to content
This repository was archived by the owner on Jun 26, 2025. It is now read-only.

Commit c9067a6

Browse files
authored
Merge pull request #141 from treethought/platform-responses
Initial support for dialogflow messenger integration
2 parents d27c988 + aa3c1a9 commit c9067a6

File tree

12 files changed

+231
-69
lines changed

12 files changed

+231
-69
lines changed

Pipfile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,12 @@ idna = "==2.8"
1818
itsdangerous = "==1.1.0"
1919
requests = "==2.21.0"
2020
urllib3 = "==1.24.2"
21-
Flask = "==1.0.2"
22-
Jinja2 = "==2.10"
21+
Flask = "==1.1.1"
2322
MarkupSafe = "==1.1.0"
2423
"ruamel.yaml" = "==0.15.81"
2524
Werkzeug = "==0.15.3"
2625
google-auth = "*"
26+
dialogflow = "*"
2727

2828
[requires]
2929
python_version = "3.7"

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,14 @@ from flask import Flask
3636
from flask_assistant import Assistant, ask
3737

3838
app = Flask(__name__)
39-
assist = Assistant(app, project_id='GOOGLE_CLOUD_PROJECT_ID')
39+
assist = Assistant(app, project_id="GOOGLE_CLOUD_PROJECT_ID")
4040

41-
@assist.action('Demo')
41+
@assist.action("Demo")
4242
def hello_world():
43-
speech = 'Microphone check 1, 2 what is this?'
43+
speech = "Microphone check 1, 2 what is this?"
4444
return ask(speech)
4545

46-
if __name__ == '__main__':
46+
if __name__ == "__main__":
4747
app.run(debug=True)
4848
```
4949

flask_assistant/__init__.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,17 @@
2323
storage,
2424
session_id,
2525
context_in,
26-
profile
26+
profile,
2727
)
2828

2929
from flask_assistant.response import (
30-
ask, tell, event, build_item, permission, sign_in, build_button
30+
ask,
31+
tell,
32+
event,
33+
build_item,
34+
permission,
35+
sign_in,
36+
build_button,
3137
)
3238

3339
from flask_assistant.manager import Context

flask_assistant/core.py

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -100,19 +100,13 @@ def __init__(
100100

101101
self.api = ApiAi(dev_token, client_token)
102102

103-
if route is None and app is not None:
104-
self._route = "/"
105-
106103
if app is not None:
107104
self.init_app(app)
105+
108106
elif blueprint is not None:
109107
self.init_blueprint(blueprint)
110-
else:
111-
raise ValueError(
112-
"Assistant object must be intialized with either an app or blueprint"
113-
)
114108

115-
if self.client_id is None:
109+
if self.client_id is None and self.app is not None:
116110
self.client_id = self.app.config.get("AOG_CLIENT_ID")
117111

118112
if project_id is None:
@@ -126,14 +120,17 @@ def __init__(
126120
)
127121

128122
def init_app(self, app):
123+
self.app = app
129124

130125
if self._route is None:
131-
raise TypeError("route is a required argument when app is not None")
126+
self._route = "/"
132127

133128
app.assist = self
134129
app.add_url_rule(
135130
self._route, view_func=self._flask_assitant_view_func, methods=["POST"]
136131
)
132+
if self.client_id is None and self.app is not None:
133+
self.client_id = self.app.config.get("AOG_CLIENT_ID")
137134

138135
# Taken from Flask-ask courtesy of @voutilad
139136
def init_blueprint(self, blueprint, path="templates.yaml"):
@@ -160,7 +157,10 @@ def init_blueprint(self, blueprint, path="templates.yaml"):
160157
blueprint.add_url_rule(
161158
"", view_func=self._flask_assitant_view_func, methods=["POST"]
162159
)
160+
163161
# blueprint.jinja_loader = ChoiceLoader([YamlLoader(blueprint, path)])
162+
if self.client_id is None and self.app is not None:
163+
self.client_id = self.app.config.get("AOG_CLIENT_ID")
164164

165165
@property
166166
def request(self):
@@ -396,18 +396,15 @@ def _set_user_profile(self):
396396

397397
token = self.user["idToken"]
398398
decode_resp = decode_token(token, self.client_id)
399-
if decode_resp["status"]=="BAD":
399+
if decode_resp["status"] == "BAD":
400400
return
401-
else: #decode_resp["status"]=="OK"
401+
else: # decode_resp["status"]=="OK"
402402
profile_payload = decode_resp["output"]
403403
for k in ["sub", "iss", "aud", "iat", "exp"]:
404404
profile_payload.pop(k)
405405

406406
self.profile = profile_payload
407407

408-
409-
410-
411408
def _flask_assitant_view_func(self, nlp_result=None, *args, **kwargs):
412409
if nlp_result: # pass API query result directly
413410
self.request = nlp_result

flask_assistant/manager.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ def parse_context_name(context_obj):
77

88

99
class Context(dict):
10-
"""docstring for _Context"""
10+
"""This is a docstring for _Context"""
1111

1212
def __init__(self, name, parameters={}, lifespan=5):
1313

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
from .base import _Response, _ListSelector, ask, tell, event, permission
2+
from .base import *
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
def build_card(
2+
text,
3+
title,
4+
img_url=None,
5+
img_alt=None,
6+
subtitle=None,
7+
link=None,
8+
link_title=None,
9+
buttons=None,
10+
):
11+
12+
card_payload = {"title": title, "subtitle": subtitle, "formattedText": text}
13+
14+
if buttons:
15+
card_payload["buttons"] = buttons
16+
17+
elif link and link_title:
18+
btn_payload = [{"title": link_title, "openUriAction": {"uri": link}}]
19+
card_payload["buttons"] = btn_payload
20+
21+
if img_url:
22+
img_payload = {"imageUri": img_url, "accessibilityText": img_alt or img_url}
23+
card_payload["image"] = img_payload
24+
25+
return {"platform": "ACTIONS_ON_GOOGLE", "basicCard": card_payload}
Lines changed: 71 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from flask import json, make_response, current_app
2-
from . import logger
2+
from flask_assistant import logger
3+
from flask_assistant.response import actions, dialogflow, hangouts, df_messenger
34

45

56
class _Response(object):
@@ -11,6 +12,8 @@ def __init__(self, speech, display_text=None, is_ssml=False):
1112
self._display_text = display_text
1213
self._integrations = current_app.config.get("INTEGRATIONS", [])
1314
self._messages = [{"text": {"text": [speech]}}]
15+
self._platform_messages = {}
16+
self._render_func = None
1417
self._is_ssml = is_ssml
1518
self._response = {
1619
"fulfillmentText": speech,
@@ -27,12 +30,16 @@ def __init__(self, speech, display_text=None, is_ssml=False):
2730
"followupEventInput": None, # TODO
2831
}
2932

33+
for i in self._integrations:
34+
self._platform_messages[i] = []
35+
3036
if "ACTIONS_ON_GOOGLE" in self._integrations:
3137
self._set_user_storage()
3238
self._integrate_with_actions(self._speech, self._display_text, is_ssml)
3339

3440
def add_msg(self, speech, display_text=None, is_ssml=False):
3541
self._messages.append({"text": {"text": [speech]}})
42+
3643
if "ACTIONS_ON_GOOGLE" in self._integrations:
3744
self._integrate_with_actions(speech, display_text, is_ssml)
3845

@@ -55,9 +62,32 @@ def _set_user_storage(self):
5562

5663
self._response["payload"]["google"]["userStorage"] = user_storage
5764

65+
def _integrate_with_df_messenger(self, speech=None, display_text=None):
66+
67+
logger.debug("Integrating with dialogflow messenger")
68+
69+
content = {"richContent": [[]]}
70+
for m in self._platform_messages.get("DIALOGFLOW_MESSENGER", []):
71+
content["richContent"][0].append(m)
72+
73+
payload = {"payload": content}
74+
75+
self._messages.append(payload)
76+
77+
def _integrate_with_hangouts(self, speech=None, display_text=None, is_ssml=False):
78+
if display_text is None:
79+
display_text = speech
80+
81+
self._messages.append(
82+
{"platform": "GOOGLE_HANGOUTS", "text": {"text": [display_text]},}
83+
)
84+
for m in self._platform_messages.get("GOOGLE_HANGOUTS", []):
85+
self._messages.append(m)
86+
5887
def _integrate_with_actions(self, speech=None, display_text=None, is_ssml=False):
5988
if display_text is None:
6089
display_text = speech
90+
6191
if is_ssml:
6292
ssml_speech = "<speak>" + speech + "</speak>"
6393
self._messages.append(
@@ -90,6 +120,11 @@ def _include_contexts(self):
90120

91121
def render_response(self):
92122
self._include_contexts()
123+
if self._render_func:
124+
self._render_func()
125+
126+
self._integrate_with_df_messenger()
127+
self._integrate_with_hangouts(self._speech, self._display_text)
93128
logger.debug(json.dumps(self._response, indent=2))
94129
resp = make_response(json.dumps(self._response))
95130
resp.headers["Content-Type"] = "application/json"
@@ -102,21 +137,14 @@ def suggest(self, *replies):
102137
for r in replies:
103138
chips.append({"title": r})
104139

105-
# NOTE: both of these formats work in the dialogflow console,
106-
# but only the first (suggestions) appears in actual Google Assistant
107-
108140
# native chips for GA
109141
self._messages.append(
110142
{"platform": "ACTIONS_ON_GOOGLE", "suggestions": {"suggestions": chips}}
111143
)
112144

113-
# # quick replies for other platforms
114-
# self._messages.append(
115-
# {
116-
# "platform": "ACTIONS_ON_GOOGLE",
117-
# "quickReplies": {"title": None, "quickReplies": replies},
118-
# }
119-
# )
145+
if "DIALOGFLOW_MESSENGER" in self._integrations:
146+
chip_resp = df_messenger._build_suggestions(*replies)
147+
self._platform_messages["DIALOGFLOW_MESSENGER"].append(chip_resp)
120148

121149
return self
122150

@@ -142,32 +170,33 @@ def card(
142170
link_title=None,
143171
buttons=None,
144172
):
145-
"""
146-
:param: link and :param: link_title supports only one button per card
147-
in future versions should be deleted
148-
and from now deprecated
173+
df_card = dialogflow.build_card(
174+
text, title, img_url, img_alt, subtitle, link, link_title
175+
)
176+
self._messages.append(df_card)
149177

150-
:param link:
151-
:param link_title:
152-
:return:
153-
"""
178+
# df_messengar car is a combo of description + button
179+
if "DIALOGFLOW_MESSENGER" in self._integrations:
154180

155-
card_payload = {"title": title, "subtitle": subtitle, "formattedText": text}
181+
description = df_messenger._build_description_response(text, title)
182+
self._platform_messages["DIALOGFLOW_MESSENGER"].append(description)
156183

157-
if buttons:
158-
card_payload["buttons"] = buttons
159-
elif link and link_title:
160-
logger.info('use button parameter instead of link and link_title')
161-
buttons = [build_button(title=link_title, link=link)]
162-
card_payload["buttons"] = buttons
184+
if link:
185+
btn = df_messenger._build_button(link, link_title)
186+
self._platform_messages["DIALOGFLOW_MESSENGER"].append(btn)
163187

164-
if img_url:
165-
img_payload = {"imageUri": img_url, "accessibilityText": img_alt or img_url}
166-
card_payload["image"] = img_payload
188+
if "GOOGLE_HANGOUTS" in self._integrations:
189+
hangouts_card = hangouts.build_card(
190+
text, title, img_url, img_alt, subtitle, link, link_title
191+
)
192+
self._platform_messages["GOOGLE_HANGOUTS"].append(hangouts_card)
167193

168-
self._messages.append(
169-
{"platform": "ACTIONS_ON_GOOGLE", "basicCard": card_payload}
170-
)
194+
if "ACTIONS_ON_GOOGLE" in self._integrations:
195+
actions_card = actions.build_card(
196+
text, title, img_url, img_alt, subtitle, link, link_title, buttons
197+
)
198+
199+
self._messages.append(actions_card)
171200

172201
return self
173202

@@ -255,10 +284,7 @@ def add_media(self, url, name, description=None, icon_url=None, icon_alt=None):
255284

256285

257286
def build_button(title, link):
258-
return {
259-
"title": title,
260-
"openUriAction": {"uri": link}
261-
}
287+
return {"title": title, "openUriAction": {"uri": link}}
262288

263289

264290
def build_item(
@@ -290,7 +316,7 @@ class _CardWithItems(_Response):
290316
def __init__(self, speech, display_text=None, items=None):
291317
super(_CardWithItems, self).__init__(speech, display_text)
292318
self._items = items or list()
293-
self._add_message() # possibly call this later?
319+
self._render_func = self._add_message
294320

295321
def _add_message(self):
296322
raise NotImplementedError
@@ -332,12 +358,20 @@ def __init__(self, speech, display_text=None, title=None, items=None):
332358
super(_ListSelector, self).__init__(speech, display_text, items)
333359

334360
def _add_message(self):
361+
335362
self._messages.append(
336363
{
337364
"platform": "ACTIONS_ON_GOOGLE",
338365
"listSelect": {"title": self._title, "items": self._items},
339366
}
340367
)
368+
self._add_platform_msgs()
369+
370+
def _add_platform_msgs(self):
371+
372+
if "DIALOGFLOW_MESSENGER" in self._integrations:
373+
list_resp = df_messenger._build_list(self._title, self._items)
374+
self._platform_messages["DIALOGFLOW_MESSENGER"].extend(list_resp)
341375

342376

343377
class _CarouselCard(_ListSelector):
@@ -453,5 +487,3 @@ def __init__(self, reason=None):
453487
}
454488
}
455489
}
456-
457-

0 commit comments

Comments
 (0)