From 9a29fbdfd5d05a45856017d25f0bb8bf9357f1a6 Mon Sep 17 00:00:00 2001 From: virtual-josh Date: Thu, 12 Dec 2019 12:29:56 -0800 Subject: [PATCH 1/3] adding link unfurling bot --- scenarios/link-unfurling/README.md | 30 ++++++ scenarios/link-unfurling/app.py | 92 ++++++++++++++++++ scenarios/link-unfurling/bots/__init__.py | 6 ++ .../link-unfurling/bots/link_unfurling_bot.py | 57 +++++++++++ scenarios/link-unfurling/config.py | 13 +++ scenarios/link-unfurling/requirements.txt | 2 + .../teams_app_manifest/color.png | Bin 0 -> 1229 bytes .../teams_app_manifest/manifest.json | 67 +++++++++++++ .../teams_app_manifest/manifest.zip | Bin 0 -> 2461 bytes .../teams_app_manifest/outline.png | Bin 0 -> 383 bytes 10 files changed, 267 insertions(+) create mode 100644 scenarios/link-unfurling/README.md create mode 100644 scenarios/link-unfurling/app.py create mode 100644 scenarios/link-unfurling/bots/__init__.py create mode 100644 scenarios/link-unfurling/bots/link_unfurling_bot.py create mode 100644 scenarios/link-unfurling/config.py create mode 100644 scenarios/link-unfurling/requirements.txt create mode 100644 scenarios/link-unfurling/teams_app_manifest/color.png create mode 100644 scenarios/link-unfurling/teams_app_manifest/manifest.json create mode 100644 scenarios/link-unfurling/teams_app_manifest/manifest.zip create mode 100644 scenarios/link-unfurling/teams_app_manifest/outline.png diff --git a/scenarios/link-unfurling/README.md b/scenarios/link-unfurling/README.md new file mode 100644 index 000000000..39f77916c --- /dev/null +++ b/scenarios/link-unfurling/README.md @@ -0,0 +1,30 @@ +# RosterBot + +Bot Framework v4 teams roster bot sample. + +This bot has been created using [Bot Framework](https://dev.botframework.com), it shows how to create a simple bot that accepts input from the user and echoes it back. + +## Running the sample +- Clone the repository +```bash +git clone https://github.com/Microsoft/botbuilder-python.git +``` +- Activate your desired virtual environment +- Bring up a terminal, navigate to `botbuilder-python\samples\roster` folder +- In the terminal, type `pip install -r requirements.txt` +- In the terminal, type `python app.py` + +## Testing the bot using Bot Framework Emulator +[Microsoft Bot Framework Emulator](https://github.com/microsoft/botframework-emulator) is a desktop application that allows bot developers to test and debug their bots on localhost or running remotely through a tunnel. + +- Install the Bot Framework emulator from [here](https://github.com/Microsoft/BotFramework-Emulator/releases) + +### Connect to bot using Bot Framework Emulator +- Launch Bot Framework Emulator +- Paste this URL in the emulator window - http://localhost:3978/api/messages + +## Further reading + +- [Bot Framework Documentation](https://docs.botframework.com) +- [Bot Basics](https://docs.microsoft.com/azure/bot-service/bot-builder-basics?view=azure-bot-service-4.0) +- [Activity processing](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-concept-activity-processing?view=azure-bot-service-4.0) diff --git a/scenarios/link-unfurling/app.py b/scenarios/link-unfurling/app.py new file mode 100644 index 000000000..608452d8f --- /dev/null +++ b/scenarios/link-unfurling/app.py @@ -0,0 +1,92 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +import asyncio +import sys +from datetime import datetime +from types import MethodType + +from flask import Flask, request, Response +from botbuilder.core import ( + BotFrameworkAdapterSettings, + TurnContext, + BotFrameworkAdapter, +) +from botbuilder.schema import Activity, ActivityTypes + +from bots import LinkUnfurlingBot + +# Create the loop and Flask app +LOOP = asyncio.get_event_loop() +APP = Flask(__name__, instance_relative_config=True) +APP.config.from_object("config.DefaultConfig") + +# Create adapter. +# See https://aka.ms/about-bot-adapter to learn more about how bots work. +SETTINGS = BotFrameworkAdapterSettings(APP.config["APP_ID"], APP.config["APP_PASSWORD"]) +ADAPTER = BotFrameworkAdapter(SETTINGS) + + +# Catch-all for errors. +async def on_error( # pylint: disable=unused-argument + self, 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 = MethodType(on_error, ADAPTER) + +# Create the Bot +BOT = LinkUnfurlingBot() + +# Listen for incoming requests on /api/messages.s +@APP.route("/api/messages", methods=["POST"]) +def messages(): + # Main bot message handler. + if "application/json" in request.headers["Content-Type"]: + body = request.json + else: + return Response(status=415) + + activity = Activity().deserialize(body) + auth_header = ( + request.headers["Authorization"] if "Authorization" in request.headers else "" + ) + + try: + task = LOOP.create_task( + ADAPTER.process_activity(activity, auth_header, BOT.on_turn) + ) + LOOP.run_until_complete(task) + return Response(status=201) + except Exception as exception: + raise exception + + +if __name__ == "__main__": + try: + APP.run(debug=False, port=APP.config["PORT"]) # nosec debug + except Exception as exception: + raise exception diff --git a/scenarios/link-unfurling/bots/__init__.py b/scenarios/link-unfurling/bots/__init__.py new file mode 100644 index 000000000..7dc2c44a9 --- /dev/null +++ b/scenarios/link-unfurling/bots/__init__.py @@ -0,0 +1,6 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +from .link_unfurling_bot import LinkUnfurlingBot + +__all__ = ["LinkUnfurlingBot"] diff --git a/scenarios/link-unfurling/bots/link_unfurling_bot.py b/scenarios/link-unfurling/bots/link_unfurling_bot.py new file mode 100644 index 000000000..1c1888375 --- /dev/null +++ b/scenarios/link-unfurling/bots/link_unfurling_bot.py @@ -0,0 +1,57 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +from botbuilder.core import CardFactory, MessageFactory, TurnContext +from botbuilder.schema import ChannelAccount, ThumbnailCard, CardImage, HeroCard, Attachment +from botbuilder.schema.teams import AppBasedLinkQuery, MessagingExtensionAttachment, MessagingExtensionQuery, MessagingExtensionResult, MessagingExtensionResponse +from botbuilder.core.teams import TeamsActivityHandler, TeamsInfo + +class LinkUnfurlingBot(TeamsActivityHandler): + async def on_teams_app_based_link_query(self, turn_context: TurnContext, query: AppBasedLinkQuery): + hero_card = ThumbnailCard( + title="Thumnnail card", + text=query.url, + images=[ + CardImage( + url="https://raw.githubusercontent.com/microsoft/botframework-sdk/master/icon.png" + ) + ] + ) + attachments = MessagingExtensionAttachment( + content_type=CardFactory.content_types.hero_card, + content=hero_card) + result = MessagingExtensionResult( + attachment_layout="list", + type="result", + attachments=[attachments] + ) + return MessagingExtensionResponse(compose_extension=result) + + async def on_teams_messaging_extension_query(self, turn_context: TurnContext, query: MessagingExtensionQuery): + if query.command_id == "searchQuery": + card = HeroCard( + title="This is a Link Unfurling Sample", + subtitle="It will unfurl links from *.botframework.com", + text="This sample demonstrates how to handle link unfurling in Teams. Please review the readme for more information." + ) + attachment = Attachment( + content_type=CardFactory.content_types.hero_card, + content=card + ) + msg_ext_atc = MessagingExtensionAttachment( + content=card, + content_type=CardFactory.content_types.hero_card, + preview=attachment + ) + msg_ext_res = MessagingExtensionResult( + attachment_layout="list", + type="result", + attachments=[msg_ext_atc] + ) + response = MessagingExtensionResponse( + compose_extension=msg_ext_res + ) + + return response + + raise NotImplementedError(f"Invalid command: {query.command_id}") \ No newline at end of file diff --git a/scenarios/link-unfurling/config.py b/scenarios/link-unfurling/config.py new file mode 100644 index 000000000..6b5116fba --- /dev/null +++ b/scenarios/link-unfurling/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/link-unfurling/requirements.txt b/scenarios/link-unfurling/requirements.txt new file mode 100644 index 000000000..7e54b62ec --- /dev/null +++ b/scenarios/link-unfurling/requirements.txt @@ -0,0 +1,2 @@ +botbuilder-core>=4.4.0b1 +flask>=1.0.3 diff --git a/scenarios/link-unfurling/teams_app_manifest/color.png b/scenarios/link-unfurling/teams_app_manifest/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`}uZ>", + "packageName": "com.teams.sample.linkunfurling", + "developer": { + "name": "Link Unfurling", + "websiteUrl": "https://www.microsoft.com", + "privacyUrl": "https://www.teams.com/privacy", + "termsOfUseUrl": "https://www.teams.com/termsofuser" + }, + "icons": { + "color": "color.png", + "outline": "outline.png" + }, + "name": { + "short": "Link Unfurling", + "full": "Link Unfurling" + }, + "description": { + "short": "Link Unfurling", + "full": "Link Unfurling" + }, + "accentColor": "#FFFFFF", + "bots": [ + { + "botId": "<>", + "scopes": [ "personal", "team" ] + } + ], + "composeExtensions": [ + { + "botId": "<>", + "commands": [ + { + "id": "searchQuery", + "context": [ "compose", "commandBox" ], + "description": "Test command to run query", + "title": "Search", + "type": "query", + "parameters": [ + { + "name": "searchQuery", + "title": "Search Query", + "description": "Your search query", + "inputType": "text" + } + ] + } + ], + "messageHandlers": [ + { + "type": "link", + "value": { + "domains": [ + "microsoft.com", + "github.com", + "linkedin.com", + "bing.com" + ] + } + } + ] + } + ] +} diff --git a/scenarios/link-unfurling/teams_app_manifest/manifest.zip b/scenarios/link-unfurling/teams_app_manifest/manifest.zip new file mode 100644 index 0000000000000000000000000000000000000000..aaedf42c4f052fa16794c7dd224681d5c9c3a4e6 GIT binary patch literal 2461 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 literal 0 HcmV?d00001 diff --git a/scenarios/link-unfurling/teams_app_manifest/outline.png b/scenarios/link-unfurling/teams_app_manifest/outline.png new file mode 100644 index 0000000000000000000000000000000000000000..dbfa9277299d36542af02499e06e3340bc538fe7 GIT binary patch literal 383 zcmV-_0f7FAP)Px$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 Date: Thu, 12 Dec 2019 12:29:56 -0800 Subject: [PATCH 2/3] adding link unfurling bot --- scenarios/link-unfurling/README.md | 30 ++++++ scenarios/link-unfurling/app.py | 92 ++++++++++++++++++ scenarios/link-unfurling/bots/__init__.py | 6 ++ .../link-unfurling/bots/link_unfurling_bot.py | 57 +++++++++++ scenarios/link-unfurling/config.py | 13 +++ scenarios/link-unfurling/requirements.txt | 2 + .../teams_app_manifest/color.png | Bin 0 -> 1229 bytes .../teams_app_manifest/manifest.json | 67 +++++++++++++ .../teams_app_manifest/manifest.zip | Bin 0 -> 2461 bytes .../teams_app_manifest/outline.png | Bin 0 -> 383 bytes 10 files changed, 267 insertions(+) create mode 100644 scenarios/link-unfurling/README.md create mode 100644 scenarios/link-unfurling/app.py create mode 100644 scenarios/link-unfurling/bots/__init__.py create mode 100644 scenarios/link-unfurling/bots/link_unfurling_bot.py create mode 100644 scenarios/link-unfurling/config.py create mode 100644 scenarios/link-unfurling/requirements.txt create mode 100644 scenarios/link-unfurling/teams_app_manifest/color.png create mode 100644 scenarios/link-unfurling/teams_app_manifest/manifest.json create mode 100644 scenarios/link-unfurling/teams_app_manifest/manifest.zip create mode 100644 scenarios/link-unfurling/teams_app_manifest/outline.png diff --git a/scenarios/link-unfurling/README.md b/scenarios/link-unfurling/README.md new file mode 100644 index 000000000..39f77916c --- /dev/null +++ b/scenarios/link-unfurling/README.md @@ -0,0 +1,30 @@ +# RosterBot + +Bot Framework v4 teams roster bot sample. + +This bot has been created using [Bot Framework](https://dev.botframework.com), it shows how to create a simple bot that accepts input from the user and echoes it back. + +## Running the sample +- Clone the repository +```bash +git clone https://github.com/Microsoft/botbuilder-python.git +``` +- Activate your desired virtual environment +- Bring up a terminal, navigate to `botbuilder-python\samples\roster` folder +- In the terminal, type `pip install -r requirements.txt` +- In the terminal, type `python app.py` + +## Testing the bot using Bot Framework Emulator +[Microsoft Bot Framework Emulator](https://github.com/microsoft/botframework-emulator) is a desktop application that allows bot developers to test and debug their bots on localhost or running remotely through a tunnel. + +- Install the Bot Framework emulator from [here](https://github.com/Microsoft/BotFramework-Emulator/releases) + +### Connect to bot using Bot Framework Emulator +- Launch Bot Framework Emulator +- Paste this URL in the emulator window - http://localhost:3978/api/messages + +## Further reading + +- [Bot Framework Documentation](https://docs.botframework.com) +- [Bot Basics](https://docs.microsoft.com/azure/bot-service/bot-builder-basics?view=azure-bot-service-4.0) +- [Activity processing](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-concept-activity-processing?view=azure-bot-service-4.0) diff --git a/scenarios/link-unfurling/app.py b/scenarios/link-unfurling/app.py new file mode 100644 index 000000000..608452d8f --- /dev/null +++ b/scenarios/link-unfurling/app.py @@ -0,0 +1,92 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +import asyncio +import sys +from datetime import datetime +from types import MethodType + +from flask import Flask, request, Response +from botbuilder.core import ( + BotFrameworkAdapterSettings, + TurnContext, + BotFrameworkAdapter, +) +from botbuilder.schema import Activity, ActivityTypes + +from bots import LinkUnfurlingBot + +# Create the loop and Flask app +LOOP = asyncio.get_event_loop() +APP = Flask(__name__, instance_relative_config=True) +APP.config.from_object("config.DefaultConfig") + +# Create adapter. +# See https://aka.ms/about-bot-adapter to learn more about how bots work. +SETTINGS = BotFrameworkAdapterSettings(APP.config["APP_ID"], APP.config["APP_PASSWORD"]) +ADAPTER = BotFrameworkAdapter(SETTINGS) + + +# Catch-all for errors. +async def on_error( # pylint: disable=unused-argument + self, 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 = MethodType(on_error, ADAPTER) + +# Create the Bot +BOT = LinkUnfurlingBot() + +# Listen for incoming requests on /api/messages.s +@APP.route("/api/messages", methods=["POST"]) +def messages(): + # Main bot message handler. + if "application/json" in request.headers["Content-Type"]: + body = request.json + else: + return Response(status=415) + + activity = Activity().deserialize(body) + auth_header = ( + request.headers["Authorization"] if "Authorization" in request.headers else "" + ) + + try: + task = LOOP.create_task( + ADAPTER.process_activity(activity, auth_header, BOT.on_turn) + ) + LOOP.run_until_complete(task) + return Response(status=201) + except Exception as exception: + raise exception + + +if __name__ == "__main__": + try: + APP.run(debug=False, port=APP.config["PORT"]) # nosec debug + except Exception as exception: + raise exception diff --git a/scenarios/link-unfurling/bots/__init__.py b/scenarios/link-unfurling/bots/__init__.py new file mode 100644 index 000000000..7dc2c44a9 --- /dev/null +++ b/scenarios/link-unfurling/bots/__init__.py @@ -0,0 +1,6 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +from .link_unfurling_bot import LinkUnfurlingBot + +__all__ = ["LinkUnfurlingBot"] diff --git a/scenarios/link-unfurling/bots/link_unfurling_bot.py b/scenarios/link-unfurling/bots/link_unfurling_bot.py new file mode 100644 index 000000000..1c1888375 --- /dev/null +++ b/scenarios/link-unfurling/bots/link_unfurling_bot.py @@ -0,0 +1,57 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +from botbuilder.core import CardFactory, MessageFactory, TurnContext +from botbuilder.schema import ChannelAccount, ThumbnailCard, CardImage, HeroCard, Attachment +from botbuilder.schema.teams import AppBasedLinkQuery, MessagingExtensionAttachment, MessagingExtensionQuery, MessagingExtensionResult, MessagingExtensionResponse +from botbuilder.core.teams import TeamsActivityHandler, TeamsInfo + +class LinkUnfurlingBot(TeamsActivityHandler): + async def on_teams_app_based_link_query(self, turn_context: TurnContext, query: AppBasedLinkQuery): + hero_card = ThumbnailCard( + title="Thumnnail card", + text=query.url, + images=[ + CardImage( + url="https://raw.githubusercontent.com/microsoft/botframework-sdk/master/icon.png" + ) + ] + ) + attachments = MessagingExtensionAttachment( + content_type=CardFactory.content_types.hero_card, + content=hero_card) + result = MessagingExtensionResult( + attachment_layout="list", + type="result", + attachments=[attachments] + ) + return MessagingExtensionResponse(compose_extension=result) + + async def on_teams_messaging_extension_query(self, turn_context: TurnContext, query: MessagingExtensionQuery): + if query.command_id == "searchQuery": + card = HeroCard( + title="This is a Link Unfurling Sample", + subtitle="It will unfurl links from *.botframework.com", + text="This sample demonstrates how to handle link unfurling in Teams. Please review the readme for more information." + ) + attachment = Attachment( + content_type=CardFactory.content_types.hero_card, + content=card + ) + msg_ext_atc = MessagingExtensionAttachment( + content=card, + content_type=CardFactory.content_types.hero_card, + preview=attachment + ) + msg_ext_res = MessagingExtensionResult( + attachment_layout="list", + type="result", + attachments=[msg_ext_atc] + ) + response = MessagingExtensionResponse( + compose_extension=msg_ext_res + ) + + return response + + raise NotImplementedError(f"Invalid command: {query.command_id}") \ No newline at end of file diff --git a/scenarios/link-unfurling/config.py b/scenarios/link-unfurling/config.py new file mode 100644 index 000000000..6b5116fba --- /dev/null +++ b/scenarios/link-unfurling/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/link-unfurling/requirements.txt b/scenarios/link-unfurling/requirements.txt new file mode 100644 index 000000000..7e54b62ec --- /dev/null +++ b/scenarios/link-unfurling/requirements.txt @@ -0,0 +1,2 @@ +botbuilder-core>=4.4.0b1 +flask>=1.0.3 diff --git a/scenarios/link-unfurling/teams_app_manifest/color.png b/scenarios/link-unfurling/teams_app_manifest/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`}uZ>", + "packageName": "com.teams.sample.linkunfurling", + "developer": { + "name": "Link Unfurling", + "websiteUrl": "https://www.microsoft.com", + "privacyUrl": "https://www.teams.com/privacy", + "termsOfUseUrl": "https://www.teams.com/termsofuser" + }, + "icons": { + "color": "color.png", + "outline": "outline.png" + }, + "name": { + "short": "Link Unfurling", + "full": "Link Unfurling" + }, + "description": { + "short": "Link Unfurling", + "full": "Link Unfurling" + }, + "accentColor": "#FFFFFF", + "bots": [ + { + "botId": "<>", + "scopes": [ "personal", "team" ] + } + ], + "composeExtensions": [ + { + "botId": "<>", + "commands": [ + { + "id": "searchQuery", + "context": [ "compose", "commandBox" ], + "description": "Test command to run query", + "title": "Search", + "type": "query", + "parameters": [ + { + "name": "searchQuery", + "title": "Search Query", + "description": "Your search query", + "inputType": "text" + } + ] + } + ], + "messageHandlers": [ + { + "type": "link", + "value": { + "domains": [ + "microsoft.com", + "github.com", + "linkedin.com", + "bing.com" + ] + } + } + ] + } + ] +} diff --git a/scenarios/link-unfurling/teams_app_manifest/manifest.zip b/scenarios/link-unfurling/teams_app_manifest/manifest.zip new file mode 100644 index 0000000000000000000000000000000000000000..aaedf42c4f052fa16794c7dd224681d5c9c3a4e6 GIT binary patch literal 2461 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 literal 0 HcmV?d00001 diff --git a/scenarios/link-unfurling/teams_app_manifest/outline.png b/scenarios/link-unfurling/teams_app_manifest/outline.png new file mode 100644 index 0000000000000000000000000000000000000000..dbfa9277299d36542af02499e06e3340bc538fe7 GIT binary patch literal 383 zcmV-_0f7FAP)Px$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 Date: Fri, 13 Dec 2019 13:02:15 -0800 Subject: [PATCH 3/3] updating app.py --- scenarios/link-unfurling/app.py | 57 ++++++++++++++------------------- 1 file changed, 24 insertions(+), 33 deletions(-) diff --git a/scenarios/link-unfurling/app.py b/scenarios/link-unfurling/app.py index 608452d8f..5be8bb376 100644 --- a/scenarios/link-unfurling/app.py +++ b/scenarios/link-unfurling/app.py @@ -1,36 +1,29 @@ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. - -import asyncio +import json import sys from datetime import datetime -from types import MethodType -from flask import Flask, request, Response +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 botbuilder.schema import Activity, ActivityTypes from bots import LinkUnfurlingBot +from config import DefaultConfig -# Create the loop and Flask app -LOOP = asyncio.get_event_loop() -APP = Flask(__name__, instance_relative_config=True) -APP.config.from_object("config.DefaultConfig") +CONFIG = DefaultConfig() # Create adapter. # See https://aka.ms/about-bot-adapter to learn more about how bots work. -SETTINGS = BotFrameworkAdapterSettings(APP.config["APP_ID"], APP.config["APP_PASSWORD"]) +SETTINGS = BotFrameworkAdapterSettings(CONFIG.APP_ID, CONFIG.APP_PASSWORD) ADAPTER = BotFrameworkAdapter(SETTINGS) - # Catch-all for errors. -async def on_error( # pylint: disable=unused-argument - self, context: TurnContext, error: Exception -): +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. @@ -52,41 +45,39 @@ async def on_error( # pylint: disable=unused-argument 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 = MethodType(on_error, ADAPTER) +ADAPTER.on_turn_error = on_error # Create the Bot BOT = LinkUnfurlingBot() -# Listen for incoming requests on /api/messages.s -@APP.route("/api/messages", methods=["POST"]) -def messages(): +# Listen for incoming requests on /api/messages +async def messages(req: Request) -> Response: # Main bot message handler. - if "application/json" in request.headers["Content-Type"]: - body = request.json + if "application/json" in req.headers["Content-Type"]: + body = await req.json() else: return Response(status=415) activity = Activity().deserialize(body) - auth_header = ( - request.headers["Authorization"] if "Authorization" in request.headers else "" - ) + auth_header = req.headers["Authorization"] if "Authorization" in req.headers else "" try: - task = LOOP.create_task( - ADAPTER.process_activity(activity, auth_header, BOT.on_turn) - ) - LOOP.run_until_complete(task) + response = await ADAPTER.process_activity(activity, auth_header, BOT.on_turn) + if response: + return json_response(data=response.body, status=response.status) return Response(status=201) except Exception as exception: raise exception +APP = web.Application() +APP.router.add_post("/api/messages", messages) if __name__ == "__main__": try: - APP.run(debug=False, port=APP.config["PORT"]) # nosec debug - except Exception as exception: - raise exception + web.run_app(APP, host="localhost", port=CONFIG.PORT) + except Exception as error: + raise error