From 23535c7357901e8d3af1b91d589a4913eaa75ac2 Mon Sep 17 00:00:00 2001 From: virtual-josh Date: Fri, 13 Dec 2019 15:19:03 -0800 Subject: [PATCH] adding search extension --- .../README.md | 30 +++ .../search-based-messaging-extension/app.py | 83 +++++++++ .../bots/__init__.py | 6 + .../bots/search_based_messaging_extension.py | 175 ++++++++++++++++++ .../config.py | 13 ++ .../requirements.txt | 2 + .../teams_app_manifest/color.png | Bin 0 -> 1229 bytes .../teams_app_manifest/manifest.json | 49 +++++ .../teams_app_manifest/outline.png | Bin 0 -> 383 bytes 9 files changed, 358 insertions(+) create mode 100644 scenarios/search-based-messaging-extension/README.md create mode 100644 scenarios/search-based-messaging-extension/app.py create mode 100644 scenarios/search-based-messaging-extension/bots/__init__.py create mode 100644 scenarios/search-based-messaging-extension/bots/search_based_messaging_extension.py create mode 100644 scenarios/search-based-messaging-extension/config.py create mode 100644 scenarios/search-based-messaging-extension/requirements.txt create mode 100644 scenarios/search-based-messaging-extension/teams_app_manifest/color.png create mode 100644 scenarios/search-based-messaging-extension/teams_app_manifest/manifest.json create mode 100644 scenarios/search-based-messaging-extension/teams_app_manifest/outline.png diff --git a/scenarios/search-based-messaging-extension/README.md b/scenarios/search-based-messaging-extension/README.md new file mode 100644 index 000000000..39f77916c --- /dev/null +++ b/scenarios/search-based-messaging-extension/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/search-based-messaging-extension/app.py b/scenarios/search-based-messaging-extension/app.py new file mode 100644 index 000000000..4b0440729 --- /dev/null +++ b/scenarios/search-based-messaging-extension/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 SearchBasedMessagingExtension +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 = SearchBasedMessagingExtension() + +# 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/search-based-messaging-extension/bots/__init__.py b/scenarios/search-based-messaging-extension/bots/__init__.py new file mode 100644 index 000000000..d35ade2a7 --- /dev/null +++ b/scenarios/search-based-messaging-extension/bots/__init__.py @@ -0,0 +1,6 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +from .search_based_messaging_extension import SearchBasedMessagingExtension + +__all__ = ["SearchBasedMessagingExtension"] diff --git a/scenarios/search-based-messaging-extension/bots/search_based_messaging_extension.py b/scenarios/search-based-messaging-extension/bots/search_based_messaging_extension.py new file mode 100644 index 000000000..ff576fd85 --- /dev/null +++ b/scenarios/search-based-messaging-extension/bots/search_based_messaging_extension.py @@ -0,0 +1,175 @@ +# 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, CardAction +from botbuilder.schema.teams import AppBasedLinkQuery, MessagingExtensionAttachment, MessagingExtensionQuery, MessagingExtensionResult, MessagingExtensionResponse +from botbuilder.core.teams import TeamsActivityHandler, TeamsInfo + +from typing import List +import requests + +class SearchBasedMessagingExtension(TeamsActivityHandler): + async def on_message_activity(self, turn_context: TurnContext): + await turn_context.send_activities(MessageFactory.text(f"Echo: {turn_context.activity.text}")) + + async def on_teams_messaging_extension_query(self, turn_context: TurnContext, query: MessagingExtensionQuery): + search_query = str(query.parameters[0].value) + response = requests.get(f"http://registry.npmjs.com/-/v1/search",params={"text":search_query}) + data = response.json() + + attachments = [] + + for obj in data["objects"]: + hero_card = HeroCard( + title=obj["package"]["name"], + tap=CardAction( + type="invoke", + value=obj["package"] + ), + preview=[CardImage(url=obj["package"]["links"]["npm"])] + ) + + attachment = MessagingExtensionAttachment( + content_type=CardFactory.content_types.hero_card, + content=HeroCard(title=obj["package"]["name"]), + preview=CardFactory.hero_card(hero_card) + ) + attachments.append(attachment) + return MessagingExtensionResponse( + compose_extension=MessagingExtensionResult( + type="result", + attachment_layout="list", + attachments=attachments + ) + ) + + + + async def on_teams_messaging_extension_select_item(self, turn_context: TurnContext, query) -> MessagingExtensionResponse: + hero_card = HeroCard( + title=query["name"], + subtitle=query["description"], + buttons=[ + CardAction( + type="openUrl", + value=query["links"]["npm"] + ) + ] + ) + attachment = MessagingExtensionAttachment( + content_type=CardFactory.content_types.hero_card, + content=hero_card + ) + + return MessagingExtensionResponse( + compose_extension=MessagingExtensionResult( + type="result", + attachment_layout="list", + attachments=[attachment] + ) + ) + + def _create_messaging_extension_result(self, attachments: List[MessagingExtensionAttachment]) -> MessagingExtensionResult: + return MessagingExtensionResult( + type="result", + attachment_layout="list", + attachments=attachments + ) + + def _create_search_result_attachment(self, search_query: str) -> MessagingExtensionAttachment: + card_text = f"You said {search_query}" + bf_logo = "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQtB3AwMUeNoq4gUBGe6Ocj8kyh3bXa9ZbV7u1fVKQoyKFHdkqU" + + button = CardAction( + type="openUrl", + title="Click for more Information", + value="https://docs.microsoft.com/en-us/microsoftteams/platform/concepts/bots/bots-overview" + ) + + images = [CardImage(url=bf_logo)] + buttons = [button] + + hero_card = HeroCard( + title="You searched for:", + text=card_text, + images=images, + buttons=buttons + ) + + return MessagingExtensionAttachment( + content_type=CardFactory.content_types.hero_card, + content=hero_card, + preview=CardFactory.hero_card(hero_card) + ) + + def _create_dummy_search_result_attachment(self) -> MessagingExtensionAttachment: + card_text = "https://docs.microsoft.com/en-us/microsoftteams/platform/concepts/bots/bots-overview" + bf_logo = "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQtB3AwMUeNoq4gUBGe6Ocj8kyh3bXa9ZbV7u1fVKQoyKFHdkqU" + + button = CardAction( + type = "openUrl", + title = "Click for more Information", + value = "https://docs.microsoft.com/en-us/microsoftteams/platform/concepts/bots/bots-overview" + ) + + images = [CardImage(url=bf_logo)] + + buttons = [button] + + + hero_card = HeroCard( + title="Learn more about Teams:", + text=card_text, images=images, + buttons=buttons + ) + + preview = HeroCard( + title="Learn more about Teams:", + text=card_text, + images=images + ) + + return MessagingExtensionAttachment( + content_type = CardFactory.content_types.hero_card, + content = hero_card, + preview = CardFactory.hero_card(preview) + ) + + def _create_select_items_result_attachment(self, search_query: str) -> MessagingExtensionAttachment: + card_text = f"You said {search_query}" + bf_logo = "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQtB3AwMUeNoq4gUBGe6Ocj8kyh3bXa9ZbV7u1fVKQoyKFHdkqU" + + buttons = CardAction( + type="openUrl", + title="Click for more Information", + value="https://docs.microsoft.com/en-us/microsoftteams/platform/concepts/bots/bots-overview" + ) + + images = [CardImage(url=bf_logo)] + buttons = [buttons] + + select_item_tap = CardAction( + type="invoke", + value={"query": search_query} + ) + + hero_card = HeroCard( + title="You searched for:", + text=card_text, + images=images, + buttons=buttons + ) + + preview = HeroCard( + title=card_text, + text=card_text, + images=images, + tap=select_item_tap + ) + + return MessagingExtensionAttachment( + content_type=CardFactory.content_types.hero_card, + content=hero_card, + preview=CardFactory.hero_card(preview) + ) \ No newline at end of file diff --git a/scenarios/search-based-messaging-extension/config.py b/scenarios/search-based-messaging-extension/config.py new file mode 100644 index 000000000..6b5116fba --- /dev/null +++ b/scenarios/search-based-messaging-extension/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/search-based-messaging-extension/requirements.txt b/scenarios/search-based-messaging-extension/requirements.txt new file mode 100644 index 000000000..7e54b62ec --- /dev/null +++ b/scenarios/search-based-messaging-extension/requirements.txt @@ -0,0 +1,2 @@ +botbuilder-core>=4.4.0b1 +flask>=1.0.3 diff --git a/scenarios/search-based-messaging-extension/teams_app_manifest/color.png b/scenarios/search-based-messaging-extension/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.microsoft.teams.samples.searchExtension", + "developer": { + "name": "Microsoft Corp", + "websiteUrl": "https://example.azurewebsites.net", + "privacyUrl": "https://example.azurewebsites.net/privacy", + "termsOfUseUrl": "https://example.azurewebsites.net/termsofuse" + }, + "name": { + "short": "search-extension-settings", + "full": "Microsoft Teams V4 Search Messaging Extension Bot and settings" + }, + "description": { + "short": "Microsoft Teams V4 Search Messaging Extension Bot and settings", + "full": "Sample Search Messaging Extension Bot using V4 Bot Builder SDK and V4 Microsoft Teams Extension SDK" + }, + "icons": { + "outline": "icon-outline.png", + "color": "icon-color.png" + }, + "accentColor": "#abcdef", + "composeExtensions": [ + { + "botId": "<>", + "canUpdateConfiguration": true, + "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" + } + ] + } + ] + } + ] + } \ No newline at end of file diff --git a/scenarios/search-based-messaging-extension/teams_app_manifest/outline.png b/scenarios/search-based-messaging-extension/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