From 805a383c838e6f9328d8519cd4708ccd0272ab1e Mon Sep 17 00:00:00 2001 From: Eric Dahlvang Date: Fri, 13 Dec 2019 14:45:05 -0800 Subject: [PATCH] add task module fetch scenario --- scenarios/task-module/app.py | 93 ++++++++++++++++++ scenarios/task-module/bots/__init__.py | 6 ++ .../task-module/bots/teams_task_module_bot.py | 90 +++++++++++++++++ scenarios/task-module/config.py | 15 +++ scenarios/task-module/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 | 42 ++++++++ 8 files changed, 248 insertions(+) create mode 100644 scenarios/task-module/app.py create mode 100644 scenarios/task-module/bots/__init__.py create mode 100644 scenarios/task-module/bots/teams_task_module_bot.py create mode 100644 scenarios/task-module/config.py create mode 100644 scenarios/task-module/requirements.txt create mode 100644 scenarios/task-module/teams_app_manifest/icon-color.png create mode 100644 scenarios/task-module/teams_app_manifest/icon-outline.png create mode 100644 scenarios/task-module/teams_app_manifest/manifest.json diff --git a/scenarios/task-module/app.py b/scenarios/task-module/app.py new file mode 100644 index 000000000..4fa136703 --- /dev/null +++ b/scenarios/task-module/app.py @@ -0,0 +1,93 @@ +# 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 TaskModuleBot +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 = TaskModuleBot() + + +# 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/task-module/bots/__init__.py b/scenarios/task-module/bots/__init__.py new file mode 100644 index 000000000..464ebfcd1 --- /dev/null +++ b/scenarios/task-module/bots/__init__.py @@ -0,0 +1,6 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +from .teams_task_module_bot import TaskModuleBot + +__all__ = ["TaskModuleBot"] diff --git a/scenarios/task-module/bots/teams_task_module_bot.py b/scenarios/task-module/bots/teams_task_module_bot.py new file mode 100644 index 000000000..be0e8bf08 --- /dev/null +++ b/scenarios/task-module/bots/teams_task_module_bot.py @@ -0,0 +1,90 @@ +# Copyright (c) Microsoft Corp. All rights reserved. +# Licensed under the MIT License. + +import json +from typing import List +import random +from botbuilder.core import CardFactory, MessageFactory, TurnContext +from botbuilder.schema import ( + ChannelAccount, + HeroCard, + CardAction, + CardImage, + Attachment, +) +from botbuilder.schema.teams import ( + MessagingExtensionAction, + MessagingExtensionActionResponse, + MessagingExtensionAttachment, + MessagingExtensionResult, + TaskModuleResponse, + TaskModuleResponseBase, + TaskModuleContinueResponse, + TaskModuleMessageResponse, + TaskModuleTaskInfo, + TaskModuleRequest, +) +from botbuilder.core.teams import TeamsActivityHandler, TeamsInfo +from botbuilder.azure import CosmosDbPartitionedStorage +from botbuilder.core.teams.teams_helper import deserializer_helper + +class TaskModuleBot(TeamsActivityHandler): + async def on_message_activity(self, turn_context: TurnContext): + reply = MessageFactory.attachment(self._get_task_module_hero_card()) + await turn_context.send_activity(reply) + + def _get_task_module_hero_card(self) -> Attachment: + task_module_action = CardAction( + type="invoke", + title="Adaptive Card", + value={"type": "task/fetch", "data": "adaptivecard"}, + ) + card = HeroCard( + title="Task Module Invocation from Hero Card", + subtitle="This is a hero card with a Task Module Action button. Click the button to show an Adaptive Card within a Task Module.", + buttons=[task_module_action], + ) + return CardFactory.hero_card(card) + + async def on_teams_task_module_fetch( + self, turn_context: TurnContext, task_module_request: TaskModuleRequest + ) -> TaskModuleResponse: + reply = MessageFactory.text( + f"OnTeamsTaskModuleFetchAsync TaskModuleRequest: {json.dumps(task_module_request.data)}" + ) + await turn_context.send_activity(reply) + + # base_response = TaskModuleResponseBase(type='continue') + card = CardFactory.adaptive_card( + { + "version": "1.0.0", + "type": "AdaptiveCard", + "body": [ + {"type": "TextBlock", "text": "Enter Text Here",}, + { + "type": "Input.Text", + "id": "usertext", + "placeholder": "add some text and submit", + "IsMultiline": "true", + }, + ], + "actions": [{"type": "Action.Submit", "title": "Submit",}], + } + ) + + task_info = TaskModuleTaskInfo( + card=card, title="Adaptive Card: Inputs", height=200, width=400 + ) + continue_response = TaskModuleContinueResponse(type="continue", value=task_info) + return TaskModuleResponse(task=continue_response) + + async def on_teams_task_module_submit( + self, turn_context: TurnContext, task_module_request: TaskModuleRequest + ) -> TaskModuleResponse: + reply = MessageFactory.text( + f"on_teams_messaging_extension_submit_action_activity MessagingExtensionAction: {json.dumps(task_module_request.data)}" + ) + await turn_context.send_activity(reply) + + message_response = TaskModuleMessageResponse(type="message", value="Thanks!") + return TaskModuleResponse(task=message_response) diff --git a/scenarios/task-module/config.py b/scenarios/task-module/config.py new file mode 100644 index 000000000..9496963d3 --- /dev/null +++ b/scenarios/task-module/config.py @@ -0,0 +1,15 @@ +#!/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/task-module/requirements.txt b/scenarios/task-module/requirements.txt new file mode 100644 index 000000000..7e54b62ec --- /dev/null +++ b/scenarios/task-module/requirements.txt @@ -0,0 +1,2 @@ +botbuilder-core>=4.4.0b1 +flask>=1.0.3 diff --git a/scenarios/task-module/teams_app_manifest/icon-color.png b/scenarios/task-module/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://example.azurewebsites.net", + "privacyUrl": "https://example.azurewebsites.net/privacy", + "termsOfUseUrl": "https://example.azurewebsites.net/termsofuse" + }, + "icons": { + "color": "color.png", + "outline": "outline.png" + }, + "name": { + "short": "Task Module", + "full": "Simple Task Module" + }, + "description": { + "short": "Test Task Module Scenario", + "full": "Simple Task Module Scenario Test" + }, + "accentColor": "#FFFFFF", + "bots": [ + { + "botId": "<>", + "scopes": [ + "personal", + "team", + "groupchat" + ], + "supportsFiles": false, + "isNotificationOnly": false + } + ], + "permissions": [ + "identity", + "messageTeamMembers" + ] +} \ No newline at end of file