From e82b4c0ec20d0539c82c102eb138115834cd7b7c Mon Sep 17 00:00:00 2001 From: Eric Dahlvang Date: Fri, 13 Dec 2019 17:03:30 -0800 Subject: [PATCH 1/3] add crude action based fetch task scenario --- .../app.py | 89 +++++++++ .../bots/__init__.py | 6 + ...ased_messaging_extension_fetch_task_bot.py | 184 ++++++++++++++++++ .../config.py | 13 ++ .../requirements.txt | 2 + .../teams_app_manifest/icon-color.png | Bin 0 -> 1229 bytes .../teams_app_manifest/icon-outline.png | Bin 0 -> 383 bytes .../teams_app_manifest/manifest.json | 67 +++++++ 8 files changed, 361 insertions(+) create mode 100644 scenarios/action-based-messaging-extension-fetch-task/app.py create mode 100644 scenarios/action-based-messaging-extension-fetch-task/bots/__init__.py create mode 100644 scenarios/action-based-messaging-extension-fetch-task/bots/action_based_messaging_extension_fetch_task_bot.py create mode 100644 scenarios/action-based-messaging-extension-fetch-task/config.py create mode 100644 scenarios/action-based-messaging-extension-fetch-task/requirements.txt create mode 100644 scenarios/action-based-messaging-extension-fetch-task/teams_app_manifest/icon-color.png create mode 100644 scenarios/action-based-messaging-extension-fetch-task/teams_app_manifest/icon-outline.png create mode 100644 scenarios/action-based-messaging-extension-fetch-task/teams_app_manifest/manifest.json diff --git a/scenarios/action-based-messaging-extension-fetch-task/app.py b/scenarios/action-based-messaging-extension-fetch-task/app.py new file mode 100644 index 000000000..57206edac --- /dev/null +++ b/scenarios/action-based-messaging-extension-fetch-task/app.py @@ -0,0 +1,89 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +import json +import sys +from datetime import datetime + +from aiohttp import web +from aiohttp.web import Request, Response, json_response +from botbuilder.core import ( + BotFrameworkAdapterSettings, + TurnContext, + BotFrameworkAdapter, +) +from botbuilder.schema import Activity, ActivityTypes +from bots import ActionBasedMessagingExtensionFetchTaskBot +from config import DefaultConfig + +CONFIG = DefaultConfig() + +# Create adapter. +# See https://aka.ms/about-bot-adapter to learn more about how bots work. +SETTINGS = BotFrameworkAdapterSettings(CONFIG.APP_ID, CONFIG.APP_PASSWORD) +ADAPTER = BotFrameworkAdapter(SETTINGS) + + +# Catch-all for errors. +async def on_error(context: TurnContext, error: Exception): + # This check writes out errors to console log .vs. app insights. + # NOTE: In production environment, you should consider logging this to Azure + # application insights. + print(f"\n [on_turn_error] unhandled error: {error}", file=sys.stderr) + + # Send a message to the user + await context.send_activity("The bot encountered an error or bug.") + await context.send_activity( + "To continue to run this bot, please fix the bot source code." + ) + # Send a trace activity if we're talking to the Bot Framework Emulator + if context.activity.channel_id == "emulator": + # Create a trace activity that contains the error object + trace_activity = Activity( + label="TurnError", + name="on_turn_error Trace", + timestamp=datetime.utcnow(), + type=ActivityTypes.trace, + value=f"{error}", + value_type="https://www.botframework.com/schemas/error", + ) + # Send a trace activity, which will be displayed in Bot Framework Emulator + await context.send_activity(trace_activity) + + +ADAPTER.on_turn_error = on_error + +# Create the Bot +BOT = ActionBasedMessagingExtensionFetchTaskBot() + + +# Listen for incoming requests on /api/messages +async def messages(req: Request) -> Response: + # Main bot message handler. + if "application/json" in req.headers["Content-Type"]: + body = await req.json() + else: + return Response(status=415) + + activity = Activity().deserialize(body) + auth_header = req.headers["Authorization"] if "Authorization" in req.headers else "" + + try: + invoke_response = await ADAPTER.process_activity(activity, auth_header, BOT.on_turn) + if invoke_response: + return json_response(data=invoke_response.body, status=invoke_response.status) + return Response(status=201) + except PermissionError: + return Response(status=401) + except Exception: + return Response(status=500) + + +APP = web.Application() +APP.router.add_post("/api/messages", messages) + +if __name__ == "__main__": + try: + web.run_app(APP, host="localhost", port=CONFIG.PORT) + except Exception as error: + raise error diff --git a/scenarios/action-based-messaging-extension-fetch-task/bots/__init__.py b/scenarios/action-based-messaging-extension-fetch-task/bots/__init__.py new file mode 100644 index 000000000..41a6a9781 --- /dev/null +++ b/scenarios/action-based-messaging-extension-fetch-task/bots/__init__.py @@ -0,0 +1,6 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +from .action_based_messaging_extension_fetch_task_bot import ActionBasedMessagingExtensionFetchTaskBot + +__all__ = ["ActionBasedMessagingExtensionFetchTaskBot"] diff --git a/scenarios/action-based-messaging-extension-fetch-task/bots/action_based_messaging_extension_fetch_task_bot.py b/scenarios/action-based-messaging-extension-fetch-task/bots/action_based_messaging_extension_fetch_task_bot.py new file mode 100644 index 000000000..425211e81 --- /dev/null +++ b/scenarios/action-based-messaging-extension-fetch-task/bots/action_based_messaging_extension_fetch_task_bot.py @@ -0,0 +1,184 @@ +# Copyright (c) Microsoft Corp. All rights reserved. +# Licensed under the MIT License. + +from typing import List +import random +from botbuilder.core import ( + CardFactory, + MessageFactory, + TurnContext, + UserState, + ConversationState, + PrivateConversationState, +) +from botbuilder.schema import ChannelAccount, HeroCard, CardAction, CardImage, Attachment +from botbuilder.schema.teams import ( + MessagingExtensionAction, + MessagingExtensionActionResponse, + TaskModuleContinueResponse, + MessagingExtensionAttachment, + MessagingExtensionResult, + TaskModuleTaskInfo +) +from botbuilder.core.teams import TeamsActivityHandler, TeamsInfo +from botbuilder.azure import CosmosDbPartitionedStorage + + +class ActionBasedMessagingExtensionFetchTaskBot(TeamsActivityHandler): + async def on_message_activity(self, turn_context: TurnContext): + value = turn_context.activity.value + if value is not None: + # This was a message from the card. + answer = value["Answer"] + choices = value["Choices"] + reply = MessageFactory.text(f"{turn_context.activity.from_property.name} answered '{answer}' and chose '{choices}'.") + await turn_context.send_activity(reply) + else: + # This is a regular text message. + reply = MessageFactory.text("Hello from ActionBasedMessagingExtensionFetchTaskBot.") + await turn_context.send_activity(reply) + + async def on_teams_messaging_extension_fetch_task( + self, turn_context: TurnContext, action: MessagingExtensionAction + ) -> MessagingExtensionActionResponse: + card = self._create_adaptive_card_editor() + task_info = TaskModuleTaskInfo(card=card, height=450, title="Task Module Fetch Example", width=500) + continue_response = TaskModuleContinueResponse(type="continue", value=task_info) + return MessagingExtensionActionResponse(task=continue_response) + + async def on_teams_messaging_extension_submit_action( # pylint: disable=unused-argument + self, turn_context: TurnContext, action: MessagingExtensionAction + ) -> MessagingExtensionActionResponse: + question = action.data["Question"] + multi_select = action.data["MultiSelect"] + option1 = action.data["Option1"] + option2 = action.data["Option2"] + option3 = action.data["Option3"] + preview_card = self._create_adaptive_card_preview(user_text=question, is_multi_select=multi_select, option1=option1, option2=option2, option3=option3) + + extension_result = MessagingExtensionResult( + type="botMessagePreview", activity_preview=MessageFactory.attachment(preview_card) + ) + return MessagingExtensionActionResponse(compose_extension=extension_result) + + async def on_teams_messaging_extension_bot_message_preview_edit( # pylint: disable=unused-argument + self, turn_context: TurnContext, action: MessagingExtensionAction + ) -> MessagingExtensionActionResponse: + activity_preview = action.bot_activity_preview[0] + content = activity_preview.attachments[0].content + body = content["body"] + question = body[1]["text"] + choice_set = body[3] + multi_select = "isMultiSelect" in choice_set + option1 = choice_set["choices"][0]["value"] + option2 = choice_set["choices"][1]["value"] + option3 = choice_set["choices"][2]["value"] + card = self._create_adaptive_card_editor(question, multi_select, option1, option2, option3) + task_info = TaskModuleTaskInfo(card=card, height=450, title="Task Module Fetch Example", width=500) + continue_response = TaskModuleContinueResponse(type="continue", value=task_info) + return MessagingExtensionActionResponse(task=continue_response) + + + + async def on_teams_messaging_extension_bot_message_preview_send( # pylint: disable=unused-argument + self, turn_context: TurnContext, action: MessagingExtensionAction + ) -> MessagingExtensionActionResponse: + activity_preview = action.bot_activity_preview[0] + content = activity_preview.attachments[0].content + body = content["body"] + question = body[1]["text"] + choice_set = body[3] + multi_select = "isMultiSelect" in choice_set + option1 = choice_set["choices"][0]["value"] + option2 = choice_set["choices"][1]["value"] + option3 = choice_set["choices"][2]["value"] + card = self._create_adaptive_card_preview(question, multi_select, option1, option2, option3) + message = MessageFactory.attachment(card) + await turn_context.send_activity(message) + + def _create_adaptive_card_editor( + self, user_text: str = None, is_multi_select: bool = False, option1: str = None, option2: str = None, option3: str = None + ) -> Attachment: + return CardFactory.adaptive_card({ + "actions": [ + { + "data": { + "submitLocation": "messagingExtensionFetchTask" + }, + "title": "Submit", + "type": "Action.Submit" + } + ], + "body": [ + { + "text": "This is an Adaptive Card within a Task Module", + "type": "TextBlock", + "weight": "bolder" + }, + { "type": "TextBlock", "text": "Enter text for Question:" }, + { + "id": "Question", + "placeholder": "Question text here", + "type": "Input.Text", + "value": user_text + }, + { "type": "TextBlock", "text": "Options for Question:" }, + { "type": "TextBlock", "text": "Is Multi-Select:" }, + { + "choices": [{"title": "True", "value": "true"}, {"title": "False", "value": "false"}], + "id": "MultiSelect", + "isMultiSelect": "false", + "style": "expanded", + "type": "Input.ChoiceSet", + "value": "true" if is_multi_select else "false" + }, + { + "id": "Option1", + "placeholder": "Option 1 here", + "type": "Input.Text", + "value": option1 + }, + { + "id": "Option2", + "placeholder": "Option 2 here", + "type": "Input.Text", + "value": option2 + }, + { + "id": "Option3", + "placeholder": "Option 3 here", + "type": "Input.Text", + "value": option3 + } + ], + "type": "AdaptiveCard", + "version" : "1.0" + }) + + + def _create_adaptive_card_preview( + self, user_text: str = None, is_multi_select: bool = False, option1: str = None, option2: str = None, option3: str = None + ) -> Attachment: + return CardFactory.adaptive_card({ + "actions": [ + { "type": "Action.Submit", "title": "Submit", "data": { "submitLocation": "messagingExtensionSubmit"} } + ], + "body": [ + { "text": "Adaptive Card from Task Module", "type": "TextBlock", "weight": "bolder" }, + { "text": user_text, "type": "TextBlock", "id": "Question" }, + { "id": "Answer", "placeholder": "Answer here...", "type": "Input.Text" }, + { + "choices": [ + {"title": option1, "value": option1}, + {"title": option2, "value": option2}, + {"title": option3, "value": option3} + ], + "id": "Choices", + "isMultiSelect": is_multi_select, + "style": "expanded", + "type": "Input.ChoiceSet" + } + ], + "type": "AdaptiveCard", + "version": "1.0" + }) \ No newline at end of file diff --git a/scenarios/action-based-messaging-extension-fetch-task/config.py b/scenarios/action-based-messaging-extension-fetch-task/config.py new file mode 100644 index 000000000..6b5116fba --- /dev/null +++ b/scenarios/action-based-messaging-extension-fetch-task/config.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python3 +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +import os + + +class DefaultConfig: + """ Bot Configuration """ + + PORT = 3978 + APP_ID = os.environ.get("MicrosoftAppId", "") + APP_PASSWORD = os.environ.get("MicrosoftAppPassword", "") diff --git a/scenarios/action-based-messaging-extension-fetch-task/requirements.txt b/scenarios/action-based-messaging-extension-fetch-task/requirements.txt new file mode 100644 index 000000000..7e54b62ec --- /dev/null +++ b/scenarios/action-based-messaging-extension-fetch-task/requirements.txt @@ -0,0 +1,2 @@ +botbuilder-core>=4.4.0b1 +flask>=1.0.3 diff --git a/scenarios/action-based-messaging-extension-fetch-task/teams_app_manifest/icon-color.png b/scenarios/action-based-messaging-extension-fetch-task/teams_app_manifest/icon-color.png new file mode 100644 index 0000000000000000000000000000000000000000..48a2de13303e1e8a25f76391f4a34c7c4700fd3d GIT binary patch literal 1229 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD1|%QND7OGojKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WCe1|JzX3_D&pSWuFnWfl{x;g|9jrEYf8Vqrkk2Ba|%ol3OT){=#|7ID~|e{ zODQ{kU&ME#@`*-tm%Tukt_gFr+`F?$dx9wg-jad`^gsMn2_%Kh%WH91&SjKq5 zgkdI|!exdOVgw@>>=!Tjnk6q)zV*T8$FdgRFYC{kQ7``NOcl@R(_%_8e5e0E;>v0G zEM9kb)2itgOTSfH7M=b3-S61B?PiazMdwXZwrS)^5UUS#HQjaoua5h_{Gx*_Zz|XK z$tf0mZ&=tpf2!!Q)!A_l&o_$g*|JM$VZa~F^0{x1T{=QFu*x$`=V%~jUW=G`iqqp=lquB-`P{Qjw`=zEu3cMc_x7m2f#9m}uoFBMMQ^+%cOL)F_)N@JZ}Axoxi1y= zeebq`y==e!nl+?cK-PhOec!3%|IupShHrcjW8sSt)F1>NW*{ zW%ljk2)nk%-}+F&?gi=7^$L#VeX3@kp%f{n}fR z`}uZPx$IY~r8R5%gMlrc`jP!L3IloKFoq~sFFH5|cdklX=R08T)}71BhaN8$`AsNf0_ zq>WNhAtCd|-nBlTU=y5zl_vXlXZ~bkuaYENMp>3QSQ_#zuYZ+eQh*OIHRxP~s(}ic zN2J4$u=AQcPt)|>F3zZLsjtP;Tajkugx;NcYED2~JVBlVO>{`uAY?Q4O|AA z=16}CJieK^5P_TKnou!zGR`$!PUC)DqtkO;?!`p!+9v3lP_mu=%Vt3BkoWsq%;FN1sp58w*zfr-z^7tIb*q>!yncCjrzLuOk3N+d&~^Cxd| z>", + "packageName": "com.microsoft.teams.samples", + "developer": { + "name": "Microsoft", + "websiteUrl": "https://dev.botframework.com", + "privacyUrl": "https://privacy.microsoft.com", + "termsOfUseUrl": "https://www.microsoft.com/en-us/legal/intellectualproperty/copyright/default.aspx" + }, + "icons": { + "color": "icon-color.png", + "outline": "icon-outline.png" + }, + "name": { + "short": "Preview Messaging Extension", + "full": "Microsoft Teams Action Based Messaging Extension with Preview" + }, + "description": { + "short": "Sample demonstrating an Action Based Messaging Extension with Preview", + "full": "Sample Action Messaging Extension built with the Bot Builder SDK demonstrating Preview" + }, + "accentColor": "#FFFFFF", + "bots": [ + { + "botId": "<>", + "scopes": [ + "team" + ] + } + ], + "composeExtensions": [ + { + "botId": "<>", + "canUpdateConfiguration": false, + "commands": [ + { + "id": "createWithPreview", + "type": "action", + "title": "Create Card", + "description": "Example of creating a Card", + "initialRun": false, + "fetchTask": true, + "context": [ + "commandBox", + "compose", + "message" + ], + "parameters": [ + { + "name": "param", + "title": "param", + "description": "" + } + ] + } + ] + } + ], + "permissions": [ + "identity", + "messageTeamMembers" + ], + "validDomains": [] +} \ No newline at end of file From 7577bf9fb978884e0a7443391ea8517c342e40a0 Mon Sep 17 00:00:00 2001 From: Eric Dahlvang Date: Mon, 16 Dec 2019 12:33:59 -0800 Subject: [PATCH 2/3] add ExampleData and some cleanup of preview extension --- ...ased_messaging_extension_fetch_task_bot.py | 36 ++++++++----------- .../example_data.py | 11 ++++++ 2 files changed, 26 insertions(+), 21 deletions(-) create mode 100644 scenarios/action-based-messaging-extension-fetch-task/example_data.py diff --git a/scenarios/action-based-messaging-extension-fetch-task/bots/action_based_messaging_extension_fetch_task_bot.py b/scenarios/action-based-messaging-extension-fetch-task/bots/action_based_messaging_extension_fetch_task_bot.py index 425211e81..9cd687f24 100644 --- a/scenarios/action-based-messaging-extension-fetch-task/bots/action_based_messaging_extension_fetch_task_bot.py +++ b/scenarios/action-based-messaging-extension-fetch-task/bots/action_based_messaging_extension_fetch_task_bot.py @@ -7,22 +7,20 @@ CardFactory, MessageFactory, TurnContext, - UserState, - ConversationState, - PrivateConversationState, ) -from botbuilder.schema import ChannelAccount, HeroCard, CardAction, CardImage, Attachment +from botbuilder.schema import Attachment from botbuilder.schema.teams import ( MessagingExtensionAction, MessagingExtensionActionResponse, TaskModuleContinueResponse, - MessagingExtensionAttachment, MessagingExtensionResult, TaskModuleTaskInfo ) -from botbuilder.core.teams import TeamsActivityHandler, TeamsInfo -from botbuilder.azure import CosmosDbPartitionedStorage +from botbuilder.core.teams import TeamsActivityHandler +import sys +sys.path.append("..") # Adds higher directory to python modules path. +from example_data import ExampleData class ActionBasedMessagingExtensionFetchTaskBot(TeamsActivityHandler): async def on_message_activity(self, turn_context: TurnContext): @@ -66,25 +64,23 @@ async def on_teams_messaging_extension_bot_message_preview_edit( # pylint: disa ) -> MessagingExtensionActionResponse: activity_preview = action.bot_activity_preview[0] content = activity_preview.attachments[0].content - body = content["body"] - question = body[1]["text"] - choice_set = body[3] - multi_select = "isMultiSelect" in choice_set - option1 = choice_set["choices"][0]["value"] - option2 = choice_set["choices"][1]["value"] - option3 = choice_set["choices"][2]["value"] - card = self._create_adaptive_card_editor(question, multi_select, option1, option2, option3) + data = self._get_example_data(content) + card = self._create_adaptive_card_editor(data.question, data.is_multi_select, data.option1, data.option2, data.option3) task_info = TaskModuleTaskInfo(card=card, height=450, title="Task Module Fetch Example", width=500) continue_response = TaskModuleContinueResponse(type="continue", value=task_info) return MessagingExtensionActionResponse(task=continue_response) - - async def on_teams_messaging_extension_bot_message_preview_send( # pylint: disable=unused-argument self, turn_context: TurnContext, action: MessagingExtensionAction ) -> MessagingExtensionActionResponse: activity_preview = action.bot_activity_preview[0] content = activity_preview.attachments[0].content + data = self._get_example_data(content) + card = self._create_adaptive_card_preview(data.question, data.is_multi_select, data.option1, data.option2, data.option3) + message = MessageFactory.attachment(card) + await turn_context.send_activity(message) + + def _get_example_data(self, content: dict) -> ExampleData: body = content["body"] question = body[1]["text"] choice_set = body[3] @@ -92,9 +88,7 @@ async def on_teams_messaging_extension_bot_message_preview_send( # pylint: disa option1 = choice_set["choices"][0]["value"] option2 = choice_set["choices"][1]["value"] option3 = choice_set["choices"][2]["value"] - card = self._create_adaptive_card_preview(question, multi_select, option1, option2, option3) - message = MessageFactory.attachment(card) - await turn_context.send_activity(message) + return ExampleData(question, multi_select, option1, option2, option3) def _create_adaptive_card_editor( self, user_text: str = None, is_multi_select: bool = False, option1: str = None, option2: str = None, option3: str = None @@ -181,4 +175,4 @@ def _create_adaptive_card_preview( ], "type": "AdaptiveCard", "version": "1.0" - }) \ No newline at end of file + }) diff --git a/scenarios/action-based-messaging-extension-fetch-task/example_data.py b/scenarios/action-based-messaging-extension-fetch-task/example_data.py new file mode 100644 index 000000000..983d815cf --- /dev/null +++ b/scenarios/action-based-messaging-extension-fetch-task/example_data.py @@ -0,0 +1,11 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +class ExampleData(object): + def __init__(self, question: str = None, is_multi_select: bool = False, option1: str = None, option2: str = None, option3: str = None): + self.question = question + self.is_multi_select = is_multi_select + self.option1 = option1 + self.option2 = option2 + self.option3 = option3 + \ No newline at end of file From c191726a2cd36c9387e12f5c26162fdb8ff97c62 Mon Sep 17 00:00:00 2001 From: Eric Dahlvang Date: Mon, 16 Dec 2019 12:47:47 -0800 Subject: [PATCH 3/3] black cleanup --- .../app.py | 8 +- .../bots/__init__.py | 4 +- ...ased_messaging_extension_fetch_task_bot.py | 237 +++++++++++------- .../example_data.py | 11 +- 4 files changed, 162 insertions(+), 98 deletions(-) diff --git a/scenarios/action-based-messaging-extension-fetch-task/app.py b/scenarios/action-based-messaging-extension-fetch-task/app.py index 57206edac..103c5f31a 100644 --- a/scenarios/action-based-messaging-extension-fetch-task/app.py +++ b/scenarios/action-based-messaging-extension-fetch-task/app.py @@ -69,9 +69,13 @@ async def messages(req: Request) -> Response: auth_header = req.headers["Authorization"] if "Authorization" in req.headers else "" try: - invoke_response = await ADAPTER.process_activity(activity, auth_header, BOT.on_turn) + invoke_response = await ADAPTER.process_activity( + activity, auth_header, BOT.on_turn + ) if invoke_response: - return json_response(data=invoke_response.body, status=invoke_response.status) + return json_response( + data=invoke_response.body, status=invoke_response.status + ) return Response(status=201) except PermissionError: return Response(status=401) diff --git a/scenarios/action-based-messaging-extension-fetch-task/bots/__init__.py b/scenarios/action-based-messaging-extension-fetch-task/bots/__init__.py index 41a6a9781..fe9caf948 100644 --- a/scenarios/action-based-messaging-extension-fetch-task/bots/__init__.py +++ b/scenarios/action-based-messaging-extension-fetch-task/bots/__init__.py @@ -1,6 +1,8 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. -from .action_based_messaging_extension_fetch_task_bot import ActionBasedMessagingExtensionFetchTaskBot +from .action_based_messaging_extension_fetch_task_bot import ( + ActionBasedMessagingExtensionFetchTaskBot, +) __all__ = ["ActionBasedMessagingExtensionFetchTaskBot"] diff --git a/scenarios/action-based-messaging-extension-fetch-task/bots/action_based_messaging_extension_fetch_task_bot.py b/scenarios/action-based-messaging-extension-fetch-task/bots/action_based_messaging_extension_fetch_task_bot.py index 9cd687f24..9e9c13fa9 100644 --- a/scenarios/action-based-messaging-extension-fetch-task/bots/action_based_messaging_extension_fetch_task_bot.py +++ b/scenarios/action-based-messaging-extension-fetch-task/bots/action_based_messaging_extension_fetch_task_bot.py @@ -14,14 +14,12 @@ MessagingExtensionActionResponse, TaskModuleContinueResponse, MessagingExtensionResult, - TaskModuleTaskInfo + TaskModuleTaskInfo, ) from botbuilder.core.teams import TeamsActivityHandler - -import sys -sys.path.append("..") # Adds higher directory to python modules path. from example_data import ExampleData + class ActionBasedMessagingExtensionFetchTaskBot(TeamsActivityHandler): async def on_message_activity(self, turn_context: TurnContext): value = turn_context.activity.value @@ -29,18 +27,24 @@ async def on_message_activity(self, turn_context: TurnContext): # This was a message from the card. answer = value["Answer"] choices = value["Choices"] - reply = MessageFactory.text(f"{turn_context.activity.from_property.name} answered '{answer}' and chose '{choices}'.") + reply = MessageFactory.text( + f"{turn_context.activity.from_property.name} answered '{answer}' and chose '{choices}'." + ) await turn_context.send_activity(reply) else: # This is a regular text message. - reply = MessageFactory.text("Hello from ActionBasedMessagingExtensionFetchTaskBot.") + reply = MessageFactory.text( + "Hello from ActionBasedMessagingExtensionFetchTaskBot." + ) await turn_context.send_activity(reply) async def on_teams_messaging_extension_fetch_task( self, turn_context: TurnContext, action: MessagingExtensionAction ) -> MessagingExtensionActionResponse: card = self._create_adaptive_card_editor() - task_info = TaskModuleTaskInfo(card=card, height=450, title="Task Module Fetch Example", width=500) + task_info = TaskModuleTaskInfo( + card=card, height=450, title="Task Module Fetch Example", width=500 + ) continue_response = TaskModuleContinueResponse(type="continue", value=task_info) return MessagingExtensionActionResponse(task=continue_response) @@ -52,10 +56,17 @@ async def on_teams_messaging_extension_submit_action( # pylint: disable=unused- option1 = action.data["Option1"] option2 = action.data["Option2"] option3 = action.data["Option3"] - preview_card = self._create_adaptive_card_preview(user_text=question, is_multi_select=multi_select, option1=option1, option2=option2, option3=option3) + preview_card = self._create_adaptive_card_preview( + user_text=question, + is_multi_select=multi_select, + option1=option1, + option2=option2, + option3=option3, + ) extension_result = MessagingExtensionResult( - type="botMessagePreview", activity_preview=MessageFactory.attachment(preview_card) + type="botMessagePreview", + activity_preview=MessageFactory.attachment(preview_card), ) return MessagingExtensionActionResponse(compose_extension=extension_result) @@ -65,8 +76,16 @@ async def on_teams_messaging_extension_bot_message_preview_edit( # pylint: disa activity_preview = action.bot_activity_preview[0] content = activity_preview.attachments[0].content data = self._get_example_data(content) - card = self._create_adaptive_card_editor(data.question, data.is_multi_select, data.option1, data.option2, data.option3) - task_info = TaskModuleTaskInfo(card=card, height=450, title="Task Module Fetch Example", width=500) + card = self._create_adaptive_card_editor( + data.question, + data.is_multi_select, + data.option1, + data.option2, + data.option3, + ) + task_info = TaskModuleTaskInfo( + card=card, height=450, title="Task Module Fetch Example", width=500 + ) continue_response = TaskModuleContinueResponse(type="continue", value=task_info) return MessagingExtensionActionResponse(task=continue_response) @@ -76,7 +95,13 @@ async def on_teams_messaging_extension_bot_message_preview_send( # pylint: disa activity_preview = action.bot_activity_preview[0] content = activity_preview.attachments[0].content data = self._get_example_data(content) - card = self._create_adaptive_card_preview(data.question, data.is_multi_select, data.option1, data.option2, data.option3) + card = self._create_adaptive_card_preview( + data.question, + data.is_multi_select, + data.option1, + data.option2, + data.option3, + ) message = MessageFactory.attachment(card) await turn_context.send_activity(message) @@ -88,91 +113,117 @@ def _get_example_data(self, content: dict) -> ExampleData: option1 = choice_set["choices"][0]["value"] option2 = choice_set["choices"][1]["value"] option3 = choice_set["choices"][2]["value"] - return ExampleData(question, multi_select, option1, option2, option3) + return ExampleData(question, multi_select, option1, option2, option3) def _create_adaptive_card_editor( - self, user_text: str = None, is_multi_select: bool = False, option1: str = None, option2: str = None, option3: str = None + self, + user_text: str = None, + is_multi_select: bool = False, + option1: str = None, + option2: str = None, + option3: str = None, ) -> Attachment: - return CardFactory.adaptive_card({ - "actions": [ - { - "data": { - "submitLocation": "messagingExtensionFetchTask" + return CardFactory.adaptive_card( + { + "actions": [ + { + "data": {"submitLocation": "messagingExtensionFetchTask"}, + "title": "Submit", + "type": "Action.Submit", + } + ], + "body": [ + { + "text": "This is an Adaptive Card within a Task Module", + "type": "TextBlock", + "weight": "bolder", + }, + {"type": "TextBlock", "text": "Enter text for Question:"}, + { + "id": "Question", + "placeholder": "Question text here", + "type": "Input.Text", + "value": user_text, + }, + {"type": "TextBlock", "text": "Options for Question:"}, + {"type": "TextBlock", "text": "Is Multi-Select:"}, + { + "choices": [ + {"title": "True", "value": "true"}, + {"title": "False", "value": "false"}, + ], + "id": "MultiSelect", + "isMultiSelect": "false", + "style": "expanded", + "type": "Input.ChoiceSet", + "value": "true" if is_multi_select else "false", }, - "title": "Submit", - "type": "Action.Submit" - } - ], - "body": [ - { - "text": "This is an Adaptive Card within a Task Module", - "type": "TextBlock", - "weight": "bolder" - }, - { "type": "TextBlock", "text": "Enter text for Question:" }, - { - "id": "Question", - "placeholder": "Question text here", - "type": "Input.Text", - "value": user_text - }, - { "type": "TextBlock", "text": "Options for Question:" }, - { "type": "TextBlock", "text": "Is Multi-Select:" }, - { - "choices": [{"title": "True", "value": "true"}, {"title": "False", "value": "false"}], - "id": "MultiSelect", - "isMultiSelect": "false", - "style": "expanded", - "type": "Input.ChoiceSet", - "value": "true" if is_multi_select else "false" - }, - { - "id": "Option1", - "placeholder": "Option 1 here", - "type": "Input.Text", - "value": option1 - }, - { - "id": "Option2", - "placeholder": "Option 2 here", - "type": "Input.Text", - "value": option2 - }, - { - "id": "Option3", - "placeholder": "Option 3 here", - "type": "Input.Text", - "value": option3 - } - ], - "type": "AdaptiveCard", - "version" : "1.0" - }) - + { + "id": "Option1", + "placeholder": "Option 1 here", + "type": "Input.Text", + "value": option1, + }, + { + "id": "Option2", + "placeholder": "Option 2 here", + "type": "Input.Text", + "value": option2, + }, + { + "id": "Option3", + "placeholder": "Option 3 here", + "type": "Input.Text", + "value": option3, + }, + ], + "type": "AdaptiveCard", + "version": "1.0", + } + ) def _create_adaptive_card_preview( - self, user_text: str = None, is_multi_select: bool = False, option1: str = None, option2: str = None, option3: str = None + self, + user_text: str = None, + is_multi_select: bool = False, + option1: str = None, + option2: str = None, + option3: str = None, ) -> Attachment: - return CardFactory.adaptive_card({ - "actions": [ - { "type": "Action.Submit", "title": "Submit", "data": { "submitLocation": "messagingExtensionSubmit"} } - ], - "body": [ - { "text": "Adaptive Card from Task Module", "type": "TextBlock", "weight": "bolder" }, - { "text": user_text, "type": "TextBlock", "id": "Question" }, - { "id": "Answer", "placeholder": "Answer here...", "type": "Input.Text" }, - { - "choices": [ - {"title": option1, "value": option1}, - {"title": option2, "value": option2}, - {"title": option3, "value": option3} - ], - "id": "Choices", - "isMultiSelect": is_multi_select, - "style": "expanded", - "type": "Input.ChoiceSet" - } - ], - "type": "AdaptiveCard", - "version": "1.0" - }) + return CardFactory.adaptive_card( + { + "actions": [ + { + "type": "Action.Submit", + "title": "Submit", + "data": {"submitLocation": "messagingExtensionSubmit"}, + } + ], + "body": [ + { + "text": "Adaptive Card from Task Module", + "type": "TextBlock", + "weight": "bolder", + }, + {"text": user_text, "type": "TextBlock", "id": "Question"}, + { + "id": "Answer", + "placeholder": "Answer here...", + "type": "Input.Text", + }, + { + "choices": [ + {"title": option1, "value": option1}, + {"title": option2, "value": option2}, + {"title": option3, "value": option3}, + ], + "id": "Choices", + "isMultiSelect": is_multi_select, + "style": "expanded", + "type": "Input.ChoiceSet", + }, + ], + "type": "AdaptiveCard", + "version": "1.0", + } + ) diff --git a/scenarios/action-based-messaging-extension-fetch-task/example_data.py b/scenarios/action-based-messaging-extension-fetch-task/example_data.py index 983d815cf..79dede038 100644 --- a/scenarios/action-based-messaging-extension-fetch-task/example_data.py +++ b/scenarios/action-based-messaging-extension-fetch-task/example_data.py @@ -1,11 +1,18 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. + class ExampleData(object): - def __init__(self, question: str = None, is_multi_select: bool = False, option1: str = None, option2: str = None, option3: str = None): + def __init__( + self, + question: str = None, + is_multi_select: bool = False, + option1: str = None, + option2: str = None, + option3: str = None, + ): self.question = question self.is_multi_select = is_multi_select self.option1 = option1 self.option2 = option2 self.option3 = option3 - \ No newline at end of file