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..5be8bb376 --- /dev/null +++ b/scenarios/link-unfurling/app.py @@ -0,0 +1,83 @@ +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 LinkUnfurlingBot +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 = LinkUnfurlingBot() + +# 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: + 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: + web.run_app(APP, host="localhost", port=CONFIG.PORT) + except Exception as error: + raise error 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 000000000..48a2de133 Binary files /dev/null and b/scenarios/link-unfurling/teams_app_manifest/color.png differ diff --git a/scenarios/link-unfurling/teams_app_manifest/manifest.json b/scenarios/link-unfurling/teams_app_manifest/manifest.json new file mode 100644 index 000000000..ad1d3c3a6 --- /dev/null +++ b/scenarios/link-unfurling/teams_app_manifest/manifest.json @@ -0,0 +1,67 @@ +{ + "$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.5/MicrosoftTeams.schema.json", + "manifestVersion": "1.5", + "version": "1.0", + "id": "<>", + "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 000000000..aaedf42c4 Binary files /dev/null and b/scenarios/link-unfurling/teams_app_manifest/manifest.zip differ 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 000000000..dbfa92772 Binary files /dev/null and b/scenarios/link-unfurling/teams_app_manifest/outline.png differ