diff --git a/samples/msgext-link-unfurling/python/Images/1.Install.png b/samples/msgext-link-unfurling/python/Images/1.Install.png index 4d14c0f426..dec01252d3 100644 Binary files a/samples/msgext-link-unfurling/python/Images/1.Install.png and b/samples/msgext-link-unfurling/python/Images/1.Install.png differ diff --git a/samples/msgext-link-unfurling/python/Images/10.Outlook_Selcetd_Card.png b/samples/msgext-link-unfurling/python/Images/10.Outlook_Selcetd_Card.png new file mode 100644 index 0000000000..0024f4e12c Binary files /dev/null and b/samples/msgext-link-unfurling/python/Images/10.Outlook_Selcetd_Card.png differ diff --git a/samples/msgext-link-unfurling/python/Images/2.Installed.png b/samples/msgext-link-unfurling/python/Images/2.Installed.png deleted file mode 100644 index f8a18fb43e..0000000000 Binary files a/samples/msgext-link-unfurling/python/Images/2.Installed.png and /dev/null differ diff --git a/samples/msgext-link-unfurling/python/Images/2.LinkUnfurling_For_Paste_URL_Collapse_View.png b/samples/msgext-link-unfurling/python/Images/2.LinkUnfurling_For_Paste_URL_Collapse_View.png new file mode 100644 index 0000000000..9cbf221622 Binary files /dev/null and b/samples/msgext-link-unfurling/python/Images/2.LinkUnfurling_For_Paste_URL_Collapse_View.png differ diff --git a/samples/msgext-link-unfurling/python/Images/3.LinkUnfurling_For_Paste_URL_Expand_View.png b/samples/msgext-link-unfurling/python/Images/3.LinkUnfurling_For_Paste_URL_Expand_View.png new file mode 100644 index 0000000000..4b012364ad Binary files /dev/null and b/samples/msgext-link-unfurling/python/Images/3.LinkUnfurling_For_Paste_URL_Expand_View.png differ diff --git a/samples/msgext-link-unfurling/python/Images/3.Link_Unfurling_In_Compose_Box.png b/samples/msgext-link-unfurling/python/Images/3.Link_Unfurling_In_Compose_Box.png deleted file mode 100644 index e24dc9eb80..0000000000 Binary files a/samples/msgext-link-unfurling/python/Images/3.Link_Unfurling_In_Compose_Box.png and /dev/null differ diff --git a/samples/msgext-link-unfurling/python/Images/4.LinkUnfurling_Both_The_Views.png b/samples/msgext-link-unfurling/python/Images/4.LinkUnfurling_Both_The_Views.png new file mode 100644 index 0000000000..b6a97d23a9 Binary files /dev/null and b/samples/msgext-link-unfurling/python/Images/4.LinkUnfurling_Both_The_Views.png differ diff --git a/samples/msgext-link-unfurling/python/Images/4.Link_Unfurling_Chat.png b/samples/msgext-link-unfurling/python/Images/4.Link_Unfurling_Chat.png deleted file mode 100644 index ac5506ca26..0000000000 Binary files a/samples/msgext-link-unfurling/python/Images/4.Link_Unfurling_Chat.png and /dev/null differ diff --git a/samples/msgext-link-unfurling/python/Images/5.Link_Unfurling_Search.png b/samples/msgext-link-unfurling/python/Images/5.Link_Unfurling_Search.png new file mode 100644 index 0000000000..0fefc32fd7 Binary files /dev/null and b/samples/msgext-link-unfurling/python/Images/5.Link_Unfurling_Search.png differ diff --git a/samples/msgext-link-unfurling/python/Images/6.Link_Unfurling_Search_Card.png b/samples/msgext-link-unfurling/python/Images/6.Link_Unfurling_Search_Card.png new file mode 100644 index 0000000000..34d5584790 Binary files /dev/null and b/samples/msgext-link-unfurling/python/Images/6.Link_Unfurling_Search_Card.png differ diff --git a/samples/msgext-link-unfurling/python/Images/7.To_Work_In_Outlook_Enable_Outlook_Channel.png b/samples/msgext-link-unfurling/python/Images/7.To_Work_In_Outlook_Enable_Outlook_Channel.png new file mode 100644 index 0000000000..9215f9f696 Binary files /dev/null and b/samples/msgext-link-unfurling/python/Images/7.To_Work_In_Outlook_Enable_Outlook_Channel.png differ diff --git a/samples/msgext-link-unfurling/python/Images/8.Not_Available_In_M365Copilot.png b/samples/msgext-link-unfurling/python/Images/8.Not_Available_In_M365Copilot.png new file mode 100644 index 0000000000..8b22a9e957 Binary files /dev/null and b/samples/msgext-link-unfurling/python/Images/8.Not_Available_In_M365Copilot.png differ diff --git a/samples/msgext-link-unfurling/python/Images/9.Outlook_MsgExt_Search.png b/samples/msgext-link-unfurling/python/Images/9.Outlook_MsgExt_Search.png new file mode 100644 index 0000000000..91e3402de5 Binary files /dev/null and b/samples/msgext-link-unfurling/python/Images/9.Outlook_MsgExt_Search.png differ diff --git a/samples/msgext-link-unfurling/python/Images/LinkUnfurling.gif b/samples/msgext-link-unfurling/python/Images/LinkUnfurling.gif new file mode 100644 index 0000000000..9fda2fc123 Binary files /dev/null and b/samples/msgext-link-unfurling/python/Images/LinkUnfurling.gif differ diff --git a/samples/msgext-link-unfurling/python/Images/Preview.gif b/samples/msgext-link-unfurling/python/Images/Preview.gif deleted file mode 100644 index efe6cf19ea..0000000000 Binary files a/samples/msgext-link-unfurling/python/Images/Preview.gif and /dev/null differ diff --git a/samples/msgext-link-unfurling/python/Images/SearchText.PNG b/samples/msgext-link-unfurling/python/Images/SearchText.PNG deleted file mode 100644 index 1d39698c11..0000000000 Binary files a/samples/msgext-link-unfurling/python/Images/SearchText.PNG and /dev/null differ diff --git a/samples/msgext-link-unfurling/python/README.md b/samples/msgext-link-unfurling/python/README.md index 5a9d9d4385..0d828837a5 100644 --- a/samples/msgext-link-unfurling/python/README.md +++ b/samples/msgext-link-unfurling/python/README.md @@ -17,14 +17,14 @@ urlFragment: officedev-microsoft-teams-samples-bot-msgext-link-unfurling-python This sample application illustrates a Python bot that enhances Microsoft Teams by performing [link unfurling](https://docs.microsoft.com/microsoftteams/platform/messaging-extensions/how-to/link-unfurling?tabs=json) in messaging extensions. By integrating with Azure, the bot facilitates seamless interactions when users share links, improving overall communication. - -This bot has been created using [Bot Framework](https://dev.botframework.com), it shows how to create a simple bot that performs link unfurling in Teams. +This bot has been created using [Microsoft Agents SDK](https://learn.microsoft.com/en-us/microsoft-365-copilot/extensibility/agents-sdk-overview), it shows how to create a simple bot that performs link unfurling in Teams. ## Included Features * Bots * Message Extensions * Search Commands * Link Unfurling +* Agent SDK ## Interaction with Messaging Extension Link Unfurling ![MsgExtLink](Images/LinkUnfurling.gif) @@ -94,10 +94,10 @@ the Teams service needs to call into the bot. * `User.Read` (enabled by default) * Click on Add permissions. Please make sure to grant the admin consent for the required permissions. -3) Create [Bot Framework registration resource](https://docs.microsoft.com/azure/bot-service/bot-service-quickstart-registration) in Azure +3) Create [Azure Bot Service registration resource](https://docs.microsoft.com/azure/bot-service/bot-service-quickstart-registration) in Azure - Use the current `https` URL you were given by running the tunnelling application. Append with the path `/api/messages` used by this sample - Ensure that you've [enabled the Teams Channel](https://docs.microsoft.com/azure/bot-service/channel-connect-teams?view=azure-bot-service-4.0) - - __*If you don't have an Azure account*__ you can use this [Bot Framework registration](https://docs.microsoft.com/microsoftteams/platform/bots/how-to/create-a-bot-for-teams#register-your-web-service-with-the-bot-framework) + - __*If you don't have an Azure account*__ you can use this [Azure Bot registration guide](https://docs.microsoft.com/microsoftteams/platform/bots/how-to/create-a-bot-for-teams#register-your-web-service-with-the-bot-framework) 1) Bring up a terminal, navigate to `Microsoft-Teams-Samples\samples\msgext-link-unfurling\python` folder @@ -105,7 +105,7 @@ the Teams service needs to call into the bot. 1) Install dependencies by running ```pip install -r requirements.txt``` in the project folder. -1) Update the `config.py` configuration for the bot to use the Microsoft App Id and App Password from the Bot Framework registration. (Note the App Password is referred to as the "client secret" in the azure portal and you can always create a new client secret anytime.) +1) Update the `config.py` configuration for the bot to use the Microsoft App Id and App Password from the Azure Bot registration. (Note the App Password is referred to as the "client secret" in the azure portal and you can always create a new client secret anytime.) 1) __*This step is specific to Teams.*__ - **Edit** the `manifest.json` contained in the `appManifest` folder to replace your Microsoft App Id (that was created when you registered your bot earlier) *everywhere* you see the place holder string `{{AAD_APP_CLIENT_ID}}` (depending on the scenario the Microsoft App Id may occur multiple times in the `manifest.json`) @@ -126,18 +126,51 @@ To learn more about deploying a bot to Azure, see [Deploy your bot to Azure](htt ## Running the sample -![msgext-search ](Images/1.Install.png) +**Install the app in Teams:** + +![Msgext-LinkUnfurling](Images/1.Install.png) + +**Link unfurling in collapsed view when you paste a URL:** + +![Msgext-LinkUnfurling](Images/2.LinkUnfurling_For_Paste_URL_Collapse_View.png) + +**Link unfurling in expanded view showing the rich preview card:** + +![Msgext-LinkUnfurling](Images/3.LinkUnfurling_For_Paste_URL_Expand_View.png) + +**Both collapsed and expanded views of the unfurled link:** + +![Msgext-LinkUnfurling](Images/4.LinkUnfurling_Both_The_Views.png) + +**Messaging extension search command in action:** + +![Msgext-LinkUnfurling](Images/5.Link_Unfurling_Search.png) + +**Search results displaying adaptive cards:** + +![Msgext-LinkUnfurling](Images/6.Link_Unfurling_Search_Card.png) + +**Enable Outlook channel in Azure Bot Service for Outlook support (Note: Link unfurling is not currently supported in Outlook with Agent SDK):** + +![Msgext-LinkUnfurling](Images/7.To_Work_In_Outlook_Enable_Outlook_Channel.png) + +**Link unfurling is not available in M365 Copilot:** + +![Msgext-LinkUnfurling](Images/8.Not_Available_In_M365Copilot.png) + +**Messaging extension search working in Outlook:** -![msgext-search ](Images/2.Installed.png) +![Msgext-LinkUnfurling](Images/9.Outlook_MsgExt_Search.png) -![msgext-search ](Images/3.Link_Unfurling_In_Compose_Box.png) +**Selected card inserted into Outlook email:** -![msgext-search ](Images/4.Link_Unfurling_Chat.png) +![Msgext-LinkUnfurling](Images/10.Outlook_Selcetd_Card.png) ## Further reading - [Link unfurling](https://learn.microsoft.com/microsoftteams/platform/messaging-extensions/how-to/link-unfurling?tabs=dotnet%2Cadvantages) -- [Bot Framework Documentation](https://docs.botframework.com) +- [Microsoft Agents SDK](https://learn.microsoft.com/en-us/microsoft-365-copilot/extensibility/agents-sdk-overview) +- [Microsoft Agents SDK for Python](https://pypi.org/project/microsoft-agents/) - [Bot Basics](https://docs.microsoft.com/azure/bot-service/bot-builder-basics?view=azure-bot-service-4.0) - [Azure Bot Service Introduction](https://docs.microsoft.com/azure/bot-service/bot-service-overview-introduction?view=azure-bot-service-4.0) - [Azure Bot Service Documentation](https://docs.microsoft.com/azure/bot-service/?view=azure-bot-service-4.0) diff --git a/samples/msgext-link-unfurling/python/app.py b/samples/msgext-link-unfurling/python/app.py index 2e9939ebe6..8313c364b3 100644 --- a/samples/msgext-link-unfurling/python/app.py +++ b/samples/msgext-link-unfurling/python/app.py @@ -3,28 +3,32 @@ import sys import traceback -from datetime import datetime -from http import HTTPStatus - from aiohttp import web -from aiohttp.web import Request, Response, json_response -from botbuilder.core import ( - - TurnContext -) -from botbuilder.integration.aiohttp import CloudAdapter, ConfigurationBotFrameworkAuthentication -from botbuilder.core.integration import aiohttp_error_middleware -from botbuilder.schema import Activity, ActivityTypes - +from aiohttp.web import Request, Response +from microsoft_agents.hosting.core import TurnContext +from microsoft_agents.hosting.aiohttp import CloudAdapter +from microsoft_agents.authentication.msal import MsalConnectionManager +from microsoft_agents.activity import load_configuration_from_env +from os import environ 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. -ADAPTER = CloudAdapter(ConfigurationBotFrameworkAuthentication(CONFIG)) +# Set up environment variables for Agent SDK +# The Agent SDK expects specific environment variable format for service connection +environ.setdefault("CONNECTIONS__SERVICE_CONNECTION__SETTINGS__CLIENTID", CONFIG.APP_ID) +environ.setdefault("CONNECTIONS__SERVICE_CONNECTION__SETTINGS__CLIENTSECRET", CONFIG.APP_PASSWORD) +environ.setdefault("CONNECTIONS__SERVICE_CONNECTION__SETTINGS__TENANTID", CONFIG.APP_TENANTID) +# Load configuration from environment using Agent SDK helper +agents_sdk_config = load_configuration_from_env(environ) + +# Create connection manager for authentication +CONNECTION_MANAGER = MsalConnectionManager(**agents_sdk_config) + +# Create adapter with connection manager +ADAPTER = CloudAdapter(connection_manager=CONNECTION_MANAGER) # Catch-all for errors. async def on_error(context: TurnContext, error: Exception): @@ -39,20 +43,6 @@ async def on_error(context: TurnContext, error: Exception): 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 @@ -60,13 +50,13 @@ async def on_error(context: TurnContext, error: Exception): BOT = LinkUnfurlingBot() -# Listen for incoming requests on /api/messages.s +# Listen for incoming requests on /api/messages async def messages(req: Request) -> Response: # Main bot message handler. - return await ADAPTER.process(req, BOT) + return await ADAPTER.process(req, BOT) -APP = web.Application(middlewares=[aiohttp_error_middleware]) +APP = web.Application() APP.router.add_post("/api/messages", messages) if __name__ == "__main__": diff --git a/samples/msgext-link-unfurling/python/appManifest/manifest.json b/samples/msgext-link-unfurling/python/appManifest/manifest.json index 26cc9e0e5f..02d97d2fb2 100644 --- a/samples/msgext-link-unfurling/python/appManifest/manifest.json +++ b/samples/msgext-link-unfurling/python/appManifest/manifest.json @@ -52,10 +52,11 @@ "value": { "domains": [ "${{BOT_DOMAIN}}", + "*.devtunnels.ms", + "*.inc1.devtunnels.ms", "*.botframework.com" - ], - "supportsAnonymizedPayloads": true + "supportsAnonymizedPayloads": false } } ] @@ -69,5 +70,15 @@ ], "validDomains": [ "${{BOT_DOMAIN}}" - ] + ], + "authorization": { + "permissions": { + "resourceSpecific": [ + { + "name": "MailboxItem.ReadWrite.User", + "type": "Delegated" + } + ] + } + } } \ No newline at end of file diff --git a/samples/msgext-link-unfurling/python/assets/sample.json b/samples/msgext-link-unfurling/python/assets/sample.json index b65afef4f5..f015cb133e 100644 --- a/samples/msgext-link-unfurling/python/assets/sample.json +++ b/samples/msgext-link-unfurling/python/assets/sample.json @@ -9,7 +9,7 @@ "This sample app demonstrates a Python bot designed for Microsoft Teams, showcasing the link unfurling feature within messaging extensions. It includes functionalities such as search commands and can be easily deployed using Azure." ], "creationDateTime": "2019-10-17", - "updateDateTime": "2024-10-15", + "updateDateTime": "2025-12-02", "products": [ "Teams" ], diff --git a/samples/msgext-link-unfurling/python/bots/link_unfurling_bot.py b/samples/msgext-link-unfurling/python/bots/link_unfurling_bot.py index 2ca43bef6f..e5cd6af765 100644 --- a/samples/msgext-link-unfurling/python/bots/link_unfurling_bot.py +++ b/samples/msgext-link-unfurling/python/bots/link_unfurling_bot.py @@ -1,35 +1,38 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. -from botbuilder.core import TurnContext, CardFactory -from botbuilder.core.card_factory import ContentTypes -from botbuilder.core.teams import TeamsActivityHandler -from botbuilder.schema import ThumbnailCard, CardImage, HeroCard -from botbuilder.schema.teams import ( - AppBasedLinkQuery, - MessagingExtensionQuery, +from microsoft_agents.hosting.core import TurnContext +from microsoft_agents.hosting.teams import TeamsActivityHandler +from microsoft_agents.activity.teams import ( + MessagingExtensionResponse, MessagingExtensionAttachment, MessagingExtensionResult, - MessagingExtensionResponse, ) - class LinkUnfurlingBot(TeamsActivityHandler): async def on_teams_app_based_link_query( - self, turn_context: TurnContext, query: AppBasedLinkQuery + self, turn_context: TurnContext, query ): - hero_card = ThumbnailCard( - title="Thumbnail Card", - text=query.url, - images=[ - CardImage( - url="https://raw.githubusercontent.com/microsoft/botframework-sdk/master/icon.png" - ) - ], - ) + # query is a dict in Agent SDK + url = query.get("url", "No URL provided") + + thumbnail_card = { + "contentType": "application/vnd.microsoft.card.thumbnail", + "content": { + "title": "Thumbnail Card", + "text": url, + "images": [ + { + "url": "https://raw.githubusercontent.com/microsoft/botframework-sdk/master/icon.png" + } + ], + }, + } attachments = MessagingExtensionAttachment( - content_type=ContentTypes.hero_card, content=hero_card + content_type="application/vnd.microsoft.card.thumbnail", + content=thumbnail_card["content"], + content_url="" # Required field in Agent SDK ) result = MessagingExtensionResult( @@ -39,18 +42,22 @@ async def on_teams_app_based_link_query( return MessagingExtensionResponse(compose_extension=result) async def on_teams_messaging_extension_query( - self, turn_context: TurnContext, query: MessagingExtensionQuery + self, turn_context: TurnContext, query ): # These commandIds are defined in the Teams App Manifest. - if not query.command_id == "searchQuery": - raise NotImplementedError(f"Invalid CommandId: {query.command_id}") + command_id = query.get("commandId") if isinstance(query, dict) else getattr(query, "command_id", None) + + if command_id != "searchQuery": + raise NotImplementedError(f"Invalid CommandId: {command_id}") - 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. ", - ) + hero_card = { + "contentType": "application/vnd.microsoft.card.hero", + "content": { + "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.", + }, + } return MessagingExtensionResponse( compose_extension=MessagingExtensionResult( @@ -58,9 +65,10 @@ async def on_teams_messaging_extension_query( type="result", attachments=[ MessagingExtensionAttachment( - content=card, - content_type=ContentTypes.hero_card, - preview=CardFactory.hero_card(card), + content=hero_card["content"], + content_type="application/vnd.microsoft.card.hero", + content_url="", # Required field in Agent SDK + preview=hero_card, ) ], ) diff --git a/samples/msgext-link-unfurling/python/config.py b/samples/msgext-link-unfurling/python/config.py index 45c20e6eb1..90f851305c 100644 --- a/samples/msgext-link-unfurling/python/config.py +++ b/samples/msgext-link-unfurling/python/config.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python3 # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. @@ -9,7 +8,7 @@ class DefaultConfig: """ Bot Configuration """ PORT = 3978 - APP_TYPE = os.environ.get("MicrosoftAppType", "") + APP_TYPE = os.environ.get("MicrosoftAppType", "MultiTenant") APP_ID = os.environ.get("MicrosoftAppId", "") APP_PASSWORD = os.environ.get("MicrosoftAppPassword", "") APP_TENANTID = os.environ.get("MicrosoftAppTenantId", "") diff --git a/samples/msgext-link-unfurling/python/requirements.txt b/samples/msgext-link-unfurling/python/requirements.txt index 71f6391e4a..89969d3ad8 100644 --- a/samples/msgext-link-unfurling/python/requirements.txt +++ b/samples/msgext-link-unfurling/python/requirements.txt @@ -1,3 +1,6 @@ -requests>=2.25.0 -botbuilder-integration-aiohttp>=4.14.0 -urllib3==1.26.18 \ No newline at end of file +microsoft-agents-activity>=0.6.0 +microsoft-agents-hosting-core>=0.6.0 +microsoft-agents-hosting-aiohttp>=0.6.0 +microsoft-agents-hosting-teams>=0.6.0 +microsoft-agents-authentication-msal>=0.6.0 +faker \ No newline at end of file diff --git a/samples/msgext-search-quickstart/python/Images/1.Install_App.png b/samples/msgext-search-quickstart/python/Images/1.Install_App.png index 6b26b5321a..faff02c4e0 100644 Binary files a/samples/msgext-search-quickstart/python/Images/1.Install_App.png and b/samples/msgext-search-quickstart/python/Images/1.Install_App.png differ diff --git a/samples/msgext-search-quickstart/python/Images/10.Outlook_Search_Results.png b/samples/msgext-search-quickstart/python/Images/10.Outlook_Search_Results.png new file mode 100644 index 0000000000..18aca3d203 Binary files /dev/null and b/samples/msgext-search-quickstart/python/Images/10.Outlook_Search_Results.png differ diff --git a/samples/msgext-search-quickstart/python/Images/11.Outlook_Selected_Card.png b/samples/msgext-search-quickstart/python/Images/11.Outlook_Selected_Card.png new file mode 100644 index 0000000000..123418a15b Binary files /dev/null and b/samples/msgext-search-quickstart/python/Images/11.Outlook_Selected_Card.png differ diff --git a/samples/msgext-search-quickstart/python/Images/2.Open_App.png b/samples/msgext-search-quickstart/python/Images/2.Open_App.png new file mode 100644 index 0000000000..144387dfe9 Binary files /dev/null and b/samples/msgext-search-quickstart/python/Images/2.Open_App.png differ diff --git a/samples/msgext-search-quickstart/python/Images/2.Select_App.png b/samples/msgext-search-quickstart/python/Images/2.Select_App.png deleted file mode 100644 index ca70b132c9..0000000000 Binary files a/samples/msgext-search-quickstart/python/Images/2.Select_App.png and /dev/null differ diff --git a/samples/msgext-search-quickstart/python/Images/3.Search_Query_With_Results.png b/samples/msgext-search-quickstart/python/Images/3.Search_Query_With_Results.png deleted file mode 100644 index 1ee96792a9..0000000000 Binary files a/samples/msgext-search-quickstart/python/Images/3.Search_Query_With_Results.png and /dev/null differ diff --git a/samples/msgext-search-quickstart/python/Images/3.Select_MsgExt_Search_Python.png b/samples/msgext-search-quickstart/python/Images/3.Select_MsgExt_Search_Python.png new file mode 100644 index 0000000000..c975a8d6ab Binary files /dev/null and b/samples/msgext-search-quickstart/python/Images/3.Select_MsgExt_Search_Python.png differ diff --git a/samples/msgext-search-quickstart/python/Images/4.Msgext_Search_Results.png b/samples/msgext-search-quickstart/python/Images/4.Msgext_Search_Results.png new file mode 100644 index 0000000000..7d7567ed18 Binary files /dev/null and b/samples/msgext-search-quickstart/python/Images/4.Msgext_Search_Results.png differ diff --git a/samples/msgext-search-quickstart/python/Images/4.Selected_Result_Card.png b/samples/msgext-search-quickstart/python/Images/4.Selected_Result_Card.png deleted file mode 100644 index e2b55644f6..0000000000 Binary files a/samples/msgext-search-quickstart/python/Images/4.Selected_Result_Card.png and /dev/null differ diff --git a/samples/msgext-search-quickstart/python/Images/5.Compose_Card.png b/samples/msgext-search-quickstart/python/Images/5.Compose_Card.png deleted file mode 100644 index 554bf9e588..0000000000 Binary files a/samples/msgext-search-quickstart/python/Images/5.Compose_Card.png and /dev/null differ diff --git a/samples/msgext-search-quickstart/python/Images/5.Selected_Result.png b/samples/msgext-search-quickstart/python/Images/5.Selected_Result.png new file mode 100644 index 0000000000..1dc453bce6 Binary files /dev/null and b/samples/msgext-search-quickstart/python/Images/5.Selected_Result.png differ diff --git a/samples/msgext-search-quickstart/python/Images/6.MsgExt_Search_Results_2.png b/samples/msgext-search-quickstart/python/Images/6.MsgExt_Search_Results_2.png new file mode 100644 index 0000000000..1dd5e74313 Binary files /dev/null and b/samples/msgext-search-quickstart/python/Images/6.MsgExt_Search_Results_2.png differ diff --git a/samples/msgext-search-quickstart/python/Images/6.Search_Results_2.png b/samples/msgext-search-quickstart/python/Images/6.Search_Results_2.png deleted file mode 100644 index 0bb10c08ec..0000000000 Binary files a/samples/msgext-search-quickstart/python/Images/6.Search_Results_2.png and /dev/null differ diff --git a/samples/msgext-search-quickstart/python/Images/7.Outlook_App.png b/samples/msgext-search-quickstart/python/Images/7.Outlook_App.png deleted file mode 100644 index f59f47b43b..0000000000 Binary files a/samples/msgext-search-quickstart/python/Images/7.Outlook_App.png and /dev/null differ diff --git a/samples/msgext-search-quickstart/python/Images/7.Selected_NPM_Packages.png b/samples/msgext-search-quickstart/python/Images/7.Selected_NPM_Packages.png new file mode 100644 index 0000000000..8816bd08c9 Binary files /dev/null and b/samples/msgext-search-quickstart/python/Images/7.Selected_NPM_Packages.png differ diff --git a/samples/msgext-search-quickstart/python/Images/8.Enable_Channel_On_Azure_Bot.png b/samples/msgext-search-quickstart/python/Images/8.Enable_Channel_On_Azure_Bot.png new file mode 100644 index 0000000000..1edc25e711 Binary files /dev/null and b/samples/msgext-search-quickstart/python/Images/8.Enable_Channel_On_Azure_Bot.png differ diff --git a/samples/msgext-search-quickstart/python/Images/8.Outlook_Click_On_NewMail.png b/samples/msgext-search-quickstart/python/Images/8.Outlook_Click_On_NewMail.png new file mode 100644 index 0000000000..7ddfd19f37 Binary files /dev/null and b/samples/msgext-search-quickstart/python/Images/8.Outlook_Click_On_NewMail.png differ diff --git a/samples/msgext-search-quickstart/python/Images/9.Outlook_Select_App_From_here.png b/samples/msgext-search-quickstart/python/Images/9.Outlook_Select_App_From_here.png new file mode 100644 index 0000000000..f29d3a068a Binary files /dev/null and b/samples/msgext-search-quickstart/python/Images/9.Outlook_Select_App_From_here.png differ diff --git a/samples/msgext-search-quickstart/python/Images/MsgExtQuickStart.gif b/samples/msgext-search-quickstart/python/Images/MsgExtQuickStart.gif new file mode 100644 index 0000000000..f73a02414e Binary files /dev/null and b/samples/msgext-search-quickstart/python/Images/MsgExtQuickStart.gif differ diff --git a/samples/msgext-search-quickstart/python/Images/msgextsearchquickstart.gif b/samples/msgext-search-quickstart/python/Images/msgextsearchquickstart.gif deleted file mode 100644 index fe74593cbd..0000000000 Binary files a/samples/msgext-search-quickstart/python/Images/msgextsearchquickstart.gif and /dev/null differ diff --git a/samples/msgext-search-quickstart/python/README.md b/samples/msgext-search-quickstart/python/README.md index 2b4ed8c141..7880b8a31f 100644 --- a/samples/msgext-search-quickstart/python/README.md +++ b/samples/msgext-search-quickstart/python/README.md @@ -23,10 +23,11 @@ This comprehensive python quick start sample illustrates the creation of a Messa * Bots * Message Extensions * Search Commands +* Agent SDK ## Interaction with app -![Sample Module](Images/msgextsearchquickstart.gif) +![Sample Module](Images/MsgExtQuickStart.gif) ## Try it yourself - experience the App in your Microsoft Teams client Please find below demo manifest which is deployed on Microsoft Azure and you can try it yourself by uploading the app package (.zip file link below) to your teams and/or as a personal app. (Uploading must be enabled for your tenant, [see steps here](https://docs.microsoft.com/microsoftteams/platform/concepts/build-and-test/prepare-your-o365-tenant#enable-custom-teams-apps-and-turn-on-custom-app-uploading)). @@ -124,25 +125,55 @@ The simplest way to run this sample in Teams is to use Microsoft 365 Agents Tool ## Running the sample -![MsgExt Search](Images/1.Install_App.png) +**Install the app in Teams:** -![MsgExt Search](Images/2.Select_App.png) +![Install App](Images/1.Install_App.png) -![MsgExt Search](Images/3.Search_Query_With_Results.png) +**Open the installed app:** -![MsgExt Search](Images/4.Selected_Result_Card.png) +![Open App](Images/2.Open_App.png) -![MsgExt Search](Images/5.Compose_Card.png) +**Select messaging extension search:** -![MsgExt Search](Images/6.Search_Results_2.png) +![Select MsgExt Search](Images/3.Select_MsgExt_Search_Python.png) -![MsgExt Search](Images/7.Outlook_App.png) +**Search results displayed:** + +![Msgext Search Results](Images/4.Msgext_Search_Results.png) + +**Selected search result:** + +![Selected Result](Images/5.Selected_Result.png) + +**Additional search results:** + +![MsgExt Search Results 2](Images/6.MsgExt_Search_Results_2.png) + +**Selected NPM package details:** + +![Selected NPM Packages](Images/7.Selected_NPM_Packages.png) + +**Using the app in Outlook - Click on New Mail:** + +![Outlook Click On New Mail](Images/8.Outlook_Click_On_NewMail.png) + +**Select the app from Outlook:** + +![Outlook Select App](Images/9.Outlook_Select_App_From_here.png) + +**Search results in Outlook:** + +![Outlook Search Results](Images/10.Outlook_Search_Results.png) + +**Selected card in Outlook:** + +![Outlook Selected Card](Images/11.Outlook_Selected_Card.png) ## 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) +- [Microsoft 365 Agent SDK Documentation](https://learn.microsoft.com/microsoft-365-copilot/extensibility/agents-are-apps) +- [Agent SDK Python Package](https://pypi.org/project/microsoft-agents/) - [Azure Bot Service Introduction](https://docs.microsoft.com/azure/bot-service/bot-service-overview-introduction?view=azure-bot-service-4.0) - [Azure Bot Service Documentation](https://docs.microsoft.com/azure/bot-service/?view=azure-bot-service-4.0) - [Search based messaging extension](https://learn.microsoft.com/microsoftteams/platform/messaging-extensions/how-to/search-commands/define-search-command) \ No newline at end of file diff --git a/samples/msgext-search-quickstart/python/app.py b/samples/msgext-search-quickstart/python/app.py index ed6b82f372..851df0843f 100644 --- a/samples/msgext-search-quickstart/python/app.py +++ b/samples/msgext-search-quickstart/python/app.py @@ -3,66 +3,54 @@ import sys import traceback -from datetime import datetime -from http import HTTPStatus +from os import environ from aiohttp import web from aiohttp.web import Request, Response -from botbuilder.core import TurnContext -from botbuilder.integration.aiohttp import ( - CloudAdapter, - ConfigurationBotFrameworkAuthentication, -) -from botbuilder.core.integration import aiohttp_error_middleware -from botbuilder.schema import Activity, ActivityTypes +from microsoft_agents.hosting.core import TurnContext +from microsoft_agents.hosting.aiohttp import CloudAdapter +from microsoft_agents.authentication.msal import MsalConnectionManager +from microsoft_agents.activity import load_configuration_from_env from bots import BotActivityHandler from config import DefaultConfig CONFIG = DefaultConfig() -# Create adapter. -ADAPTER = CloudAdapter(ConfigurationBotFrameworkAuthentication(CONFIG)) +# Set up environment variables for Agent SDK authentication +environ.setdefault("CONNECTIONS__SERVICE_CONNECTION__SETTINGS__CLIENTID", CONFIG.APP_ID) +environ.setdefault("CONNECTIONS__SERVICE_CONNECTION__SETTINGS__CLIENTSECRET", CONFIG.APP_PASSWORD) +environ.setdefault("CONNECTIONS__SERVICE_CONNECTION__SETTINGS__TENANTID", CONFIG.APP_TENANTID) +# Load configuration from environment +agents_sdk_config = load_configuration_from_env(environ) + +# Create MSAL connection manager for authentication +CONNECTION_MANAGER = MsalConnectionManager(**agents_sdk_config) + +# Create cloud adapter with connection manager +ADAPTER = CloudAdapter(connection_manager=CONNECTION_MANAGER) -# Catch-all for errors. async def on_error(context: TurnContext, error: Exception): + """Catch-all error handler for the adapter.""" print(f"\n [on_turn_error] unhandled error: {error}", file=sys.stderr) traceback.print_exc() - 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." - ) - - if context.activity.channel_id == "emulator": - 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", - ) - await context.send_activity(trace_activity) - + # Only send error messages for regular conversations, not for invokes (messaging extensions) + if context.activity.type != "invoke": + 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.") +# Set error handler for the adapter ADAPTER.on_turn_error = on_error -# Create the Bot +# Create bot instance BOT = BotActivityHandler() - -# Listen for incoming requests on /api/messages. async def messages(req: Request) -> Response: - try: - return await ADAPTER.process(req, BOT) - except Exception as e: - print(f"Error processing activity: {e}") - traceback.print_exc() - return Response(status=HTTPStatus.INTERNAL_SERVER_ERROR) - + """Main bot message handler.""" + return await ADAPTER.process(req, BOT) -# Create the main application and routes -APP = web.Application(middlewares=[aiohttp_error_middleware]) +# Create web application and configure routes +APP = web.Application() APP.router.add_post("/api/messages", messages) if __name__ == "__main__": diff --git a/samples/msgext-search-quickstart/python/appManifest/manifest.json b/samples/msgext-search-quickstart/python/appManifest/manifest.json index 3befef08ef..aa9f46edc5 100644 --- a/samples/msgext-search-quickstart/python/appManifest/manifest.json +++ b/samples/msgext-search-quickstart/python/appManifest/manifest.json @@ -56,5 +56,15 @@ ], "validDomains": [ "${{BOT_DOMAIN}}" - ] + ], + "authorization": { + "permissions": { + "resourceSpecific": [ + { + "name": "MailboxItem.ReadWrite.User", + "type": "Delegated" + } + ] + } + } } \ No newline at end of file diff --git a/samples/msgext-search-quickstart/python/assets/sample.json b/samples/msgext-search-quickstart/python/assets/sample.json index b253f79ed7..2048f32a6b 100644 --- a/samples/msgext-search-quickstart/python/assets/sample.json +++ b/samples/msgext-search-quickstart/python/assets/sample.json @@ -9,7 +9,7 @@ "This python quick start sample showcases how to create a Messaging Extension in Microsoft Teams that handles user search queries. It allows users to interact with your web service through the Teams client, providing seamless search functionality." ], "creationDateTime": "2024-12-26", - "updateDateTime": "2024-12-26", + "updateDateTime": "2025-12-04", "products": [ "Teams" ], @@ -35,7 +35,7 @@ { "type": "image", "order": 100, - "url": "https://raw.githubusercontent.com/OfficeDev/Microsoft-Teams-Samples/main/samples/msgext-search-quickstart/python/Images/msgext-search-quickstart.gif", + "url": "https://raw.githubusercontent.com/OfficeDev/Microsoft-Teams-Samples/main/samples/msgext-search-quickstart/python/Images/MsgExtQuickStart.gif", "alt": "Solution UX showing search messaging extension- Microsoft 365 Agents Toolkit" } ], diff --git a/samples/msgext-search-quickstart/python/bots/botActivityHandler.py b/samples/msgext-search-quickstart/python/bots/botActivityHandler.py index b3edb62936..c0de54e0fa 100644 --- a/samples/msgext-search-quickstart/python/bots/botActivityHandler.py +++ b/samples/msgext-search-quickstart/python/bots/botActivityHandler.py @@ -2,104 +2,130 @@ # Licensed under the MIT License. import requests -from botbuilder.core import TurnContext, CardFactory -from botbuilder.schema import ThumbnailCard, CardAction, CardImage, InvokeResponse -from botbuilder.core.teams import TeamsActivityHandler -from botbuilder.schema.teams import ( +from microsoft_agents.hosting.core import TurnContext +from microsoft_agents.hosting.teams import TeamsActivityHandler +from microsoft_agents.activity.teams import ( MessagingExtensionResponse, - MessagingExtensionAttachment, MessagingExtensionResult, + MessagingExtensionAttachment, ) -# A bot activity handler that processes Teams messaging extensions and app link queries class BotActivityHandler(TeamsActivityHandler): + """Bot activity handler for Teams messaging extensions and app link queries.""" + def __init__(self): super().__init__() - # Handle search-based messaging extension queries async def on_teams_messaging_extension_query(self, context, query): - # Get the search query parameter - search_query = query.parameters[0].value - - # Call the npm registry search API - response = requests.get( - "http://registry.npmjs.com/-/v1/search", - params={"text": search_query, "size": 8}, - ) - response.raise_for_status() - data = response.json() + """Handle search-based messaging extension queries.""" + # Extract search query from parameters (supports both dict and object formats) + search_query = "" + if isinstance(query, dict): + parameters = query.get("parameters", []) + search_query = parameters[0].get("value", "") if parameters else "" + else: + search_query = query.parameters[0].value if hasattr(query, "parameters") and query.parameters else "" + + # Use default search term if not provided or too short (npm API requires min 2 chars) + if not search_query or len(search_query) < 2: + search_query = "react" + + try: + # Call the npm registry search API + response = requests.get( + "http://registry.npmjs.com/-/v1/search", + params={"text": search_query, "size": 8}, + timeout=10, + ) + response.raise_for_status() + data = response.json() + except Exception as e: + # Return error card on API failure + error_card = { + "contentType": "application/vnd.microsoft.card.hero", + "content": { + "title": "Search Error", + "text": "Unable to search npm packages. Please try again with a different query (minimum 2 characters).", + }, + } + return MessagingExtensionResponse( + compose_extension=MessagingExtensionResult( + type="result", + attachment_layout="list", + attachments=[ + MessagingExtensionAttachment( + content=error_card["content"], + content_type="application/vnd.microsoft.card.hero", + content_url="", + preview=error_card, + ) + ], + ) + ) attachments = [] - # Build a list of cards for the search results + # Build result cards for search results for obj in data["objects"][:8]: package = obj.get("package", {}) package_name = package.get("name", "Unknown Package") description = package.get("description", "No description available") homepage = package.get("links", {}).get("homepage", "https://www.npmjs.com") - # Main card (full view when selected/inserted into conversation) - thumbnail_card = ThumbnailCard( - title=package_name, - text=description, - buttons=[ - CardAction( - type="openUrl", - title="View on NPM", - value=f"https://www.npmjs.com/package/{package_name}", - ), - CardAction( - type="openUrl", - title="Homepage", - value=homepage, - ), - ], - ) - card_attachment = CardFactory.thumbnail_card(thumbnail_card) - - # Preview card (shown in search result list) - preview_card = ThumbnailCard(title=package_name, text=description) - preview_attachment = CardFactory.thumbnail_card(preview_card) - preview_attachment.content.tap = CardAction( - type="invoke", - value={"title": package_name, "description": description}, - ) - - # Messaging Extension attachment + # Create hero card (better Outlook compatibility) + hero_card = { + "contentType": "application/vnd.microsoft.card.hero", + "content": { + "title": package_name, + "text": description, + "buttons": [ + { + "type": "openUrl", + "title": "View on NPM", + "value": f"https://www.npmjs.com/package/{package_name}", + }, + { + "type": "openUrl", + "title": "Homepage", + "value": homepage, + }, + ], + }, + } + + # Create messaging extension attachment attachment = MessagingExtensionAttachment( - content=card_attachment.content, - content_type=card_attachment.content_type, - preview=preview_attachment, + content=hero_card["content"], + content_type="application/vnd.microsoft.card.hero", + content_url="", # Required field in Agent SDK + preview=hero_card, ) attachments.append(attachment) - # Wrap the results in a MessagingExtensionResult - result = MessagingExtensionResult( - type="result", - attachment_layout="list", - attachments=attachments, - ) - - # Return InvokeResponse with the MessagingExtensionResponse - return InvokeResponse( - status=200, - body=MessagingExtensionResponse(compose_extension=result), + # Return messaging extension response + return MessagingExtensionResponse( + compose_extension=MessagingExtensionResult( + type="result", + attachment_layout="list", + attachments=attachments, + ) ) - # Handle the user's selection of an item from the search results - async def on_teams_messaging_extension_select_item( - self, turn_context: TurnContext, query - ) -> InvokeResponse: - # Build a card with the selected package details - selected_card = ThumbnailCard( - title=query.get("title"), - text=query.get("description"), - ) - selected_attachment = CardFactory.thumbnail_card(selected_card) + async def on_teams_messaging_extension_select_item(self, turn_context: TurnContext, query) -> MessagingExtensionResponse: + """Handle user's selection of an item from search results.""" + # Create card with selected package details + selected_card = { + "contentType": "application/vnd.microsoft.card.thumbnail", + "content": { + "title": query.get("title"), + "text": query.get("description"), + }, + } me_attachment = MessagingExtensionAttachment( - content=selected_attachment.content, - content_type=selected_attachment.content_type, + content=selected_card["content"], + content_type="application/vnd.microsoft.card.thumbnail", + content_url="", # Required field in Agent SDK ) result = MessagingExtensionResult( @@ -108,30 +134,31 @@ async def on_teams_messaging_extension_select_item( attachments=[me_attachment], ) - return InvokeResponse( - status=200, - body=MessagingExtensionResponse(compose_extension=result), - ) - - # Handle app-based link unfurling (e.g., when a user pastes a link) - async def on_teams_app_based_link_query( - self, turn_context: TurnContext, query - ) -> InvokeResponse: - # Create a card with the unfurled link details - link_card = ThumbnailCard( - title="Thumbnail Card", - text=query.url, - images=[ - CardImage( - url="https://raw.githubusercontent.com/microsoft/botframework-sdk/master/icon.png" - ) - ], - ) - link_attachment = CardFactory.thumbnail_card(link_card) + return MessagingExtensionResponse(compose_extension=result) + + async def on_teams_app_based_link_query(self, turn_context: TurnContext, query) -> MessagingExtensionResponse: + """Handle app-based link unfurling when user pastes a link.""" + # Extract URL from query (supports both dict and object formats) + url = query.get("url", "No URL provided") if isinstance(query, dict) else getattr(query, "url", "No URL provided") + + # Create card with unfurled link details + link_card = { + "contentType": "application/vnd.microsoft.card.thumbnail", + "content": { + "title": "Thumbnail Card", + "text": url, + "images": [ + { + "url": "https://raw.githubusercontent.com/microsoft/botframework-sdk/master/icon.png" + } + ], + }, + } me_attachment = MessagingExtensionAttachment( - content=link_attachment.content, - content_type=link_attachment.content_type, + content=link_card["content"], + content_type="application/vnd.microsoft.card.thumbnail", + content_url="", # Required field in Agent SDK ) result = MessagingExtensionResult( @@ -140,7 +167,4 @@ async def on_teams_app_based_link_query( attachments=[me_attachment], ) - return InvokeResponse( - status=200, - body=MessagingExtensionResponse(compose_extension=result), - ) + return MessagingExtensionResponse(compose_extension=result) diff --git a/samples/msgext-search-quickstart/python/config.py b/samples/msgext-search-quickstart/python/config.py index 9d7d12aca1..41fe268e41 100644 --- a/samples/msgext-search-quickstart/python/config.py +++ b/samples/msgext-search-quickstart/python/config.py @@ -4,14 +4,12 @@ import os -""" Bot Configuration """ - class DefaultConfig: - """ Bot Configuration """ + """Agent configuration settings.""" PORT = 3978 - APP_ID = os.environ.get("MicrosoftAppId", "<>") - APP_PASSWORD = os.environ.get("MicrosoftAppPassword", "<>") - APP_TYPE = os.environ.get("MicrosoftAppType", "") + APP_ID = os.environ.get("MicrosoftAppId", "") + APP_PASSWORD = os.environ.get("MicrosoftAppPassword", "") + APP_TYPE = os.environ.get("MicrosoftAppType", "MultiTenant") APP_TENANTID = os.environ.get("MicrosoftAppTenantId", "") \ No newline at end of file diff --git a/samples/msgext-search-quickstart/python/env/.env.local b/samples/msgext-search-quickstart/python/env/.env.local index 22987ef6bb..31471d3a95 100644 --- a/samples/msgext-search-quickstart/python/env/.env.local +++ b/samples/msgext-search-quickstart/python/env/.env.local @@ -13,9 +13,6 @@ AAD_APP_OAUTH_AUTHORITY= AAD_APP_OAUTH_AUTHORITY_HOST= TEAMS_APP_ID= TEAMS_APP_TENANT_ID= -MICROSOFT_APP_TYPE= -MICROSOFT_APP_TENANT_ID= RESOURCE_SUFFIX= AZURE_SUBSCRIPTION_ID= -AZURE_RESOURCE_GROUP_NAME= -APP_NAME_SUFFIX= \ No newline at end of file +AZURE_RESOURCE_GROUP_NAME= \ No newline at end of file diff --git a/samples/msgext-search-quickstart/python/requirements.txt b/samples/msgext-search-quickstart/python/requirements.txt index 385d723f9c..807dc65e1a 100644 --- a/samples/msgext-search-quickstart/python/requirements.txt +++ b/samples/msgext-search-quickstart/python/requirements.txt @@ -1,2 +1,6 @@ requests==2.32.3 -botbuilder-integration-aiohttp>=4.16.2 +microsoft-agents-activity>=0.6.0 +microsoft-agents-hosting-core>=0.6.0 +microsoft-agents-hosting-aiohttp>=0.6.0 +microsoft-agents-hosting-teams>=0.6.0 +microsoft-agents-authentication-msal>=0.6.0 diff --git a/samples/tab-deeplink/python/Images/M365Copilot_AdaptiveCard_info.png b/samples/tab-deeplink/python/Images/M365Copilot_AdaptiveCard_info.png new file mode 100644 index 0000000000..eac407fc57 Binary files /dev/null and b/samples/tab-deeplink/python/Images/M365Copilot_AdaptiveCard_info.png differ diff --git a/samples/tab-deeplink/python/Images/M365Copilot_Bot_Info.png b/samples/tab-deeplink/python/Images/M365Copilot_Bot_Info.png new file mode 100644 index 0000000000..d671352f9c Binary files /dev/null and b/samples/tab-deeplink/python/Images/M365Copilot_Bot_Info.png differ diff --git a/samples/tab-deeplink/python/Images/M365Copilot_Click_Continue.png b/samples/tab-deeplink/python/Images/M365Copilot_Click_Continue.png new file mode 100644 index 0000000000..8256f5b5ec Binary files /dev/null and b/samples/tab-deeplink/python/Images/M365Copilot_Click_Continue.png differ diff --git a/samples/tab-deeplink/python/Images/M365Copilot_Select_App.png b/samples/tab-deeplink/python/Images/M365Copilot_Select_App.png new file mode 100644 index 0000000000..8453d4659f Binary files /dev/null and b/samples/tab-deeplink/python/Images/M365Copilot_Select_App.png differ diff --git a/samples/tab-deeplink/python/Images/M365_Copilot_Tab_Loaded_Successfully.png b/samples/tab-deeplink/python/Images/M365_Copilot_Tab_Loaded_Successfully.png new file mode 100644 index 0000000000..eaf27c655e Binary files /dev/null and b/samples/tab-deeplink/python/Images/M365_Copilot_Tab_Loaded_Successfully.png differ diff --git a/samples/tab-deeplink/python/Images/Outlook_Adaptive_Card_Info.png b/samples/tab-deeplink/python/Images/Outlook_Adaptive_Card_Info.png new file mode 100644 index 0000000000..bd419a1348 Binary files /dev/null and b/samples/tab-deeplink/python/Images/Outlook_Adaptive_Card_Info.png differ diff --git a/samples/tab-deeplink/python/Images/Outlook_Bot_Info.png b/samples/tab-deeplink/python/Images/Outlook_Bot_Info.png new file mode 100644 index 0000000000..a739c72f54 Binary files /dev/null and b/samples/tab-deeplink/python/Images/Outlook_Bot_Info.png differ diff --git a/samples/tab-deeplink/python/Images/Outlook_Click_Continue.png b/samples/tab-deeplink/python/Images/Outlook_Click_Continue.png new file mode 100644 index 0000000000..c83f37c87d Binary files /dev/null and b/samples/tab-deeplink/python/Images/Outlook_Click_Continue.png differ diff --git a/samples/tab-deeplink/python/Images/Outlook_ME_Info.png b/samples/tab-deeplink/python/Images/Outlook_ME_Info.png new file mode 100644 index 0000000000..77196242fd Binary files /dev/null and b/samples/tab-deeplink/python/Images/Outlook_ME_Info.png differ diff --git a/samples/tab-deeplink/python/Images/Outlook_Tab_2.png b/samples/tab-deeplink/python/Images/Outlook_Tab_2.png new file mode 100644 index 0000000000..a687f8b68c Binary files /dev/null and b/samples/tab-deeplink/python/Images/Outlook_Tab_2.png differ diff --git a/samples/tab-deeplink/python/Images/Outlook_Tab_Loaded_Successfully.png b/samples/tab-deeplink/python/Images/Outlook_Tab_Loaded_Successfully.png new file mode 100644 index 0000000000..aeb7033ce7 Binary files /dev/null and b/samples/tab-deeplink/python/Images/Outlook_Tab_Loaded_Successfully.png differ diff --git a/samples/tab-deeplink/python/README.md b/samples/tab-deeplink/python/README.md index c27ac587b8..f91ffe54ac 100644 --- a/samples/tab-deeplink/python/README.md +++ b/samples/tab-deeplink/python/README.md @@ -15,12 +15,13 @@ urlFragment: officedev-microsoft-teams-samples-tab-deeplink-python # DeepLink -Explore this Microsoft Teams sample app designed to demonstrate the use of deeplinks for seamless interactions, including calls, chats, and navigation across tabs and applications. Featuring bot integration and comprehensive setup guidance, this app empowers developers to create engaging and efficient communication experiences within Teams.[DeepLink](https://learn.microsoft.com/microsoftteams/platform/concepts/build-and-test/deep-links) +Explore this Microsoft Teams sample app designed to demonstrate the use of deeplinks for seamless interactions, including calls, chats, and navigation across tabs and applications. Built with Microsoft Agents SDK and featuring comprehensive setup guidance, this app empowers developers to create engaging and efficient communication experiences within Teams.[DeepLink](https://learn.microsoft.com/microsoftteams/platform/concepts/build-and-test/deep-links) ## Included Features * Tabs * Bots * Deep Links +* Agent SDK ## Interaction with app. @@ -87,7 +88,7 @@ the Teams service needs to call into the bot. * `User.Read` (enabled by default) * Click on Add permissions. Please make sure to grant the admin consent for the required permissions. -4) Create [Azure Bot resource resource](https://docs.microsoft.com/azure/bot-service/bot-service-quickstart-registration) in Azure +4) Create [Azure Bot Service registration resource](https://docs.microsoft.com/azure/bot-service/bot-service-quickstart-registration) in Azure - Use the current `https` URL you were given by running the tunneling application. Append with the path `/api/messages` used by this sample - Ensure that you've [enabled the Teams Channel](https://docs.microsoft.com/azure/bot-service/channel-connect-teams?view=azure-bot-service-4.0) - __*If you don't have an Azure account*__ you can use this [Azure free account here](https://azure.microsoft.com/free/) @@ -98,7 +99,7 @@ the Teams service needs to call into the bot. 7) Install dependencies by running ```pip install -r requirements.txt``` in the project folder. -8) Update the `config.py` configuration for the bot to use the Microsoft App Id and App Password from the Bot Framework registration. (Note the App Password is referred to as the "client secret" in the azure portal and you can always create a new client secret anytime.) +8) Update the `config.py` configuration for the bot to use the Microsoft App Id and App Password from the Azure Bot Service registration. (Note the App Password is referred to as the "client secret" in the azure portal and you can always create a new client secret anytime.) 9) - navigate to `Deeplink.html` page at line number `58` Update the `data-app-id` attribute with your application id. - Navigate to `env.js` file and update your AppId at placeholder `<>` (You can get it manually from [teams admin portal](https://admin.teams.microsoft.com/). @@ -112,9 +113,8 @@ the Teams service needs to call into the bot. 11) Run your bot with `python app.py` -## Interacting with the bot +## Running the sample -Enter text in the emulator. The text will be echoed back by the bot. 1. Interact with DeepLink bot by pinging it in personal or channel scope. ![Deep link card](Images/BotCard.png) @@ -202,6 +202,30 @@ Click on `Side Panel Deeplink` which will redirect to the meeting side panel. ![AppOutlook](Images/AppOutlook.png) +**Click Continue to interact with the bot in Outlook:** + +![Outlook Click Continue](Images/Outlook_Click_Continue.png) + +**Bot information displayed in Outlook:** + +![Outlook Bot Info](Images/Outlook_Bot_Info.png) + +**Adaptive card displayed in Outlook:** + +![Outlook Adaptive Card Info](Images/Outlook_Adaptive_Card_Info.png) + +**Tab loaded successfully in Outlook:** + +![Outlook Tab Loaded Successfully](Images/Outlook_Tab_Loaded_Successfully.png) + +**Additional tab view in Outlook:** + +![Outlook Tab 2](Images/Outlook_Tab_2.png) + +**Messaging extension in Outlook:** + +![Outlook ME Info](Images/Outlook_ME_Info.png) + **Note:** Similarly, you can test your application in the Outlook desktop app as well. ## Office on the web @@ -220,10 +244,38 @@ Click on `Side Panel Deeplink` which will redirect to the meeting side panel. **Note:** Similarly, you can test your application in the Office 365 desktop app as well. +## M365 Copilot + +- To preview your app running in M365 Copilot. + +**Select the app from M365 Copilot:** + +![M365 Copilot Select App](Images/M365Copilot_Select_App.png) + +**Click Continue to interact with the bot:** + +![M365 Copilot Click Continue](Images/M365Copilot_Click_Continue.png) + +**Bot information displayed in M365 Copilot:** + +![M365 Copilot Bot Info](Images/M365Copilot_Bot_Info.png) + +**Adaptive card displayed in M365 Copilot:** + +![M365 Copilot Adaptive Card Info](Images/M365Copilot_AdaptiveCard_info.png) + +**Tab loaded successfully in M365 Copilot:** + +![M365 Copilot Tab Loaded Successfully](Images/M365_Copilot_Tab_Loaded_Successfully.png) + ## Further reading +- [Microsoft Agents SDK Documentation](https://learn.microsoft.com/microsoft-365-copilot/extensibility/agents-are-apps) +- [Microsoft Agents SDK for Python](https://pypi.org/project/microsoft-agents/) +- [Bot Basics](https://docs.microsoft.com/azure/bot-service/bot-builder-basics?view=azure-bot-service-4.0) +- [Azure Bot Service Introduction](https://docs.microsoft.com/azure/bot-service/bot-service-overview-introduction?view=azure-bot-service-4.0) +- [Azure Bot Service Documentation](https://docs.microsoft.com/azure/bot-service/?view=azure-bot-service-4.0) - [Extend Teams apps across Microsoft 365](https://learn.microsoft.com/en-us/microsoftteams/platform/m365-apps/overview) - - [Share to teams web apps](https://learn.microsoft.com/en-us/microsoftteams/platform/concepts/build-and-test/share-to-teams-from-web-apps?branch=pr-en-us-10824&tabs=method1) \ No newline at end of file diff --git a/samples/tab-deeplink/python/app.py b/samples/tab-deeplink/python/app.py index 37ef5fff06..fbeaed427c 100644 --- a/samples/tab-deeplink/python/app.py +++ b/samples/tab-deeplink/python/app.py @@ -1,66 +1,68 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. +import sys +import traceback import os -import logging -import asyncio -from flask import Flask, request, jsonify, send_from_directory, Response -from botbuilder.core import TurnContext -from botbuilder.schema import Activity +from aiohttp import web +from aiohttp.web import Request, Response +from microsoft_agents.hosting.core import TurnContext +from microsoft_agents.hosting.aiohttp import CloudAdapter +from microsoft_agents.authentication.msal import MsalConnectionManager +from microsoft_agents.activity import load_configuration_from_env +from os import environ from bots import DeepLinkTabsBot -from botbuilder.integration.aiohttp import ( - CloudAdapter, - ConfigurationBotFrameworkAuthentication -) from config import DefaultConfig CONFIG = DefaultConfig() -app = Flask(__name__) +# Configure Agent SDK authentication using environment variables +environ.setdefault("CONNECTIONS__SERVICE_CONNECTION__SETTINGS__CLIENTID", CONFIG.APP_ID) +environ.setdefault("CONNECTIONS__SERVICE_CONNECTION__SETTINGS__CLIENTSECRET", CONFIG.APP_PASSWORD) +environ.setdefault("CONNECTIONS__SERVICE_CONNECTION__SETTINGS__TENANTID", CONFIG.APP_TENANTID) -# Load environment variables -PORT = int(os.getenv("PORT", 3978)) -MICROSOFT_APP_ID = os.getenv("MicrosoftAppId", "") -MICROSOFT_APP_PASSWORD = os.getenv("MicrosoftAppPassword", "") +# Load Agent SDK configuration from environment +agents_sdk_config = load_configuration_from_env(environ) -# Configure logging -logging.basicConfig(level=logging.INFO) +# Create MSAL connection manager for bot authentication +CONNECTION_MANAGER = MsalConnectionManager(**agents_sdk_config) -# Configure Bot Adapter -adapter = CloudAdapter(ConfigurationBotFrameworkAuthentication(CONFIG)) +# Create CloudAdapter with Agent SDK (replaces Bot Framework CloudAdapter) +ADAPTER = CloudAdapter(connection_manager=CONNECTION_MANAGER) -# Bot instance -bot = DeepLinkTabsBot() +# Initialize bot instance +BOT = DeepLinkTabsBot() -# Error handling -async def on_turn_error(context: TurnContext, error: Exception): - logging.error(f"\n [on_turn_error] unhandled error: {error}") - await context.send_activity("Sorry, it looks like something went wrong.") +# Error handler for bot turn errors +async def on_error(context: TurnContext, error: Exception): + print(f"\n [on_turn_error] unhandled error: {error}", file=sys.stderr) + traceback.print_exc() + 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.") -adapter.on_turn_error = on_turn_error +ADAPTER.on_turn_error = on_error -@app.route("/api/messages", methods=["POST"]) -def messages(): - body = request.json - activity = Activity().deserialize(body) - auth_header = request.headers.get("Authorization", "") +# Bot message endpoint - processes incoming Teams messages +async def messages(req: Request) -> Response: + """Main bot message handler.""" + return await ADAPTER.process(req, BOT) - async def aux_func(turn_context: TurnContext): - await bot.on_turn(turn_context) # Correct method name +# API endpoint to retrieve Microsoft App ID +async def get_app_id(req: Request) -> Response: + """Return the Microsoft App ID.""" + return web.json_response({"microsoftAppId": CONFIG.APP_ID}) - loop = asyncio.new_event_loop() - asyncio.set_event_loop(loop) - loop.run_until_complete(adapter.process_activity(activity, auth_header, aux_func)) +# Create aiohttp web application (Agent SDK uses aiohttp, not Flask) +APP = web.Application() +APP.router.add_post("/api/messages", messages) +APP.router.add_get("/api/getAppId", get_app_id) - return Response(status=200) # Explicitly return an empty response with status 200 - -@app.route("/api/getAppId", methods=["GET"]) -def get_app_id(): - return jsonify({"microsoftAppId": MICROSOFT_APP_ID}) - -@app.route("/") -def redirect_static(filename): - return send_from_directory("static", filename) +# Serve static files (HTML, CSS, JS) for Teams tabs +STATIC_DIR = os.path.join(os.path.dirname(__file__), 'static') +APP.router.add_static('/', STATIC_DIR, name='static') if __name__ == "__main__": - app.run(host="0.0.0.0", port=PORT) \ No newline at end of file + try: + web.run_app(APP, host="0.0.0.0", port=CONFIG.PORT) + except Exception as error: + raise error \ No newline at end of file diff --git a/samples/tab-deeplink/python/assets/sample.json b/samples/tab-deeplink/python/assets/sample.json index 472d711f4b..adfc147c7b 100644 --- a/samples/tab-deeplink/python/assets/sample.json +++ b/samples/tab-deeplink/python/assets/sample.json @@ -9,7 +9,7 @@ "This Teams sample application illustrates how to use deeplinks for initiating calls, video chats, and navigating within various app tabs. It includes detailed setup instructions and supports interactions with bots and tabs to enhance user experience." ], "creationDateTime": "2025-03-05", - "updateDateTime": "2025-03-05", + "updateDateTime": "2025-12-05", "products": [ "Teams" ], @@ -35,7 +35,7 @@ { "type": "image", "order": 100, - "url": "https://raw.githubusercontent.com/OfficeDev/Microsoft-Teams-Samples/main/samples/tab-deeplink/python/Images/Preview.gif", + "url": "https://raw.githubusercontent.com/OfficeDev/Microsoft-Teams-Samples/main/samples/tab-deeplink/python/Images/TabDeepLink.gif", "alt": "Solution UX showing deeplink" } ], diff --git a/samples/tab-deeplink/python/bots/bot_activity_handler.py b/samples/tab-deeplink/python/bots/bot_activity_handler.py index b0937cb99e..a5125babd2 100644 --- a/samples/tab-deeplink/python/bots/bot_activity_handler.py +++ b/samples/tab-deeplink/python/bots/bot_activity_handler.py @@ -1,10 +1,14 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + import os import json -from botbuilder.core import ActivityHandler, TurnContext, MessageFactory, CardFactory -from botbuilder.schema import Attachment +from microsoft_agents.hosting.core import TurnContext, MessageFactory, CardFactory +from microsoft_agents.hosting.teams import TeamsActivityHandler from .deep_link_tab_helper import DeepLinkTabHelper -class DeepLinkTabsBot(ActivityHandler): +# Bot class using Agent SDK TeamsActivityHandler +class DeepLinkTabsBot(TeamsActivityHandler): def __init__(self): super().__init__() @@ -13,6 +17,7 @@ async def on_message_activity(self, turn_context: TurnContext): extended_deep_link = "" side_panel_link = "" + # Generate deeplinks based on conversation type (channel vs personal/group chat) if conversation_type == "channel": bots_deep_link = DeepLinkTabHelper.get_deep_link_tab_channel( "topic1", 1, "Bots", turn_context.activity.channel_data["teamsChannelId"], @@ -40,6 +45,7 @@ async def on_message_activity(self, turn_context: TurnContext): turn_context.activity.conversation.id, "chat" ) + # Create and send adaptive card with all deeplinks adaptive_card = self.create_adaptive_card( conversation_type, bots_deep_link, messaging_deep_link, adaptive_card_deep_link, extended_deep_link, side_panel_link ) @@ -47,12 +53,14 @@ async def on_message_activity(self, turn_context: TurnContext): await turn_context.send_activity(MessageFactory.attachment(adaptive_card)) async def on_members_added_activity(self, members_added, turn_context: TurnContext): + # Send welcome message when new members join welcome_text = "Hello and welcome!" for member in members_added: if member.id != turn_context.activity.recipient.id: await turn_context.send_activity(MessageFactory.text(welcome_text)) - def create_adaptive_card(self, conversation_type, bots_deep_link, messaging_deep_link, adaptive_card_deep_link, extended_deep_link, side_panel_link) -> Attachment: + def create_adaptive_card(self, conversation_type, bots_deep_link, messaging_deep_link, adaptive_card_deep_link, extended_deep_link, side_panel_link): + # Load adaptive card template from JSON file with open("resources/AdaptiveCard.json", "r", encoding="utf-8") as json_file: template_payload = json.load(json_file) @@ -70,7 +78,7 @@ def create_adaptive_card(self, conversation_type, bots_deep_link, messaging_deep "sidePanelLinkTitle": side_panel_link["TaskText"] if side_panel_link else "" } - # Convert template to string for manual replacement + # Replace template placeholders with actual deeplink values adaptive_card_json = json.dumps(template_payload) # Replace placeholders manually @@ -80,4 +88,5 @@ def create_adaptive_card(self, conversation_type, bots_deep_link, messaging_deep # Convert back to dictionary adaptive_card = json.loads(adaptive_card_json) + # Return adaptive card using Agent SDK CardFactory return CardFactory.adaptive_card(adaptive_card) \ No newline at end of file diff --git a/samples/tab-deeplink/python/bots/deep_link_tab_helper.py b/samples/tab-deeplink/python/bots/deep_link_tab_helper.py index 4c478a0b0a..ea0dca092d 100644 --- a/samples/tab-deeplink/python/bots/deep_link_tab_helper.py +++ b/samples/tab-deeplink/python/bots/deep_link_tab_helper.py @@ -1,9 +1,14 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + import os import urllib.parse +# Helper class to generate Teams deeplinks for various scenarios class DeepLinkTabHelper: @staticmethod def get_deep_link_tab_channel(sub_entity_id, ID, desc, channel_id, app_id, entity_id): + # Generate deeplink for channel tab with context task_context = urllib.parse.quote(f'{{"subEntityId": "{sub_entity_id}","channelId":"{channel_id}"}}') encoded_web_url = urllib.parse.quote(f'{os.getenv("Base_URL")}/ChannelDeepLink.html&label=DeepLink') @@ -15,6 +20,7 @@ def get_deep_link_tab_channel(sub_entity_id, ID, desc, channel_id, app_id, entit @staticmethod def get_deep_link_tab_static(sub_entity_id, ID, desc, app_id): + # Generate deeplink for static/personal tab task_context = urllib.parse.quote(f'{{"subEntityId": "{sub_entity_id}"}}') return { @@ -25,6 +31,7 @@ def get_deep_link_tab_static(sub_entity_id, ID, desc, app_id): @staticmethod def get_deep_link_to_meeting_side_panel(ID, desc, app_id, base_url, chat_id, context_type): + # Generate deeplink to open meeting side panel json_context = f'{{"chatId": "{chat_id}", "contextType": "{context_type}"}}' task_context = urllib.parse.quote(json_context) encoded_url = urllib.parse.quote(f"{base_url}/ChannelDeepLink.html") diff --git a/samples/tab-deeplink/python/config.py b/samples/tab-deeplink/python/config.py index 5528d4bcd1..4a19e7e0ad 100644 --- a/samples/tab-deeplink/python/config.py +++ b/samples/tab-deeplink/python/config.py @@ -1,21 +1,23 @@ -#!/usr/bin/env python3 # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. import os -""" Bot Configuration """ +""" Agent Configuration """ class DefaultConfig: - """ Bot Configuration """ + """ Configuration for Agent SDK bot """ PORT = 3978 + # Microsoft App credentials from Azure Bot Service registration APP_ID = os.environ.get("MicrosoftAppId", "<>") APP_PASSWORD = os.environ.get("MicrosoftAppPassword", "<>") BOT_ENDPOINT = os.environ.get("BaseUrl", "<>") + # Teams app configuration TEAMS_APP_ID = os.environ.get("TeamsAppId", "<>") Tab_Entity_Id = os.environ.get("Tab_Entity_Id", "<>") Channel_Entity_Id = os.environ.get("Channel_Entity_Id", "<>") - APP_TYPE = os.environ.get("MicrosoftAppType", "") + # App type must be MultiTenant for Agent SDK + APP_TYPE = os.environ.get("MicrosoftAppType", "MultiTenant") APP_TENANTID = os.environ.get("MicrosoftAppTenantId", "") \ No newline at end of file diff --git a/samples/tab-deeplink/python/requirements.txt b/samples/tab-deeplink/python/requirements.txt index d0c0dcc518..438ebbe91d 100644 --- a/samples/tab-deeplink/python/requirements.txt +++ b/samples/tab-deeplink/python/requirements.txt @@ -1,3 +1,6 @@ -requests==2.31.0 -botbuilder-integration-aiohttp>=4.16.2 -flask \ No newline at end of file +requests==2.32.3 +microsoft-agents-activity>=0.6.0 +microsoft-agents-hosting-core>=0.6.0 +microsoft-agents-hosting-aiohttp>=0.6.0 +microsoft-agents-hosting-teams>=0.6.0 +microsoft-agents-authentication-msal>=0.6.0 \ No newline at end of file diff --git a/samples/tab-deeplink/python/static/ChannelDeepLink.html b/samples/tab-deeplink/python/static/ChannelDeepLink.html index d6f7cd50fc..7368476270 100644 --- a/samples/tab-deeplink/python/static/ChannelDeepLink.html +++ b/samples/tab-deeplink/python/static/ChannelDeepLink.html @@ -1,3 +1,6 @@ + + diff --git a/samples/tab-deeplink/python/static/ChannelDetails.html b/samples/tab-deeplink/python/static/ChannelDetails.html index a879b3762d..ed6966a942 100644 --- a/samples/tab-deeplink/python/static/ChannelDetails.html +++ b/samples/tab-deeplink/python/static/ChannelDetails.html @@ -1,3 +1,6 @@ + + diff --git a/samples/tab-deeplink/python/static/ChannelTasklist.html b/samples/tab-deeplink/python/static/ChannelTasklist.html index f5b812286b..7389f1f842 100644 --- a/samples/tab-deeplink/python/static/ChannelTasklist.html +++ b/samples/tab-deeplink/python/static/ChannelTasklist.html @@ -1,3 +1,6 @@ + + diff --git a/samples/tab-deeplink/python/static/Configure.html b/samples/tab-deeplink/python/static/Configure.html index 090df1dec6..2de01ee888 100644 --- a/samples/tab-deeplink/python/static/Configure.html +++ b/samples/tab-deeplink/python/static/Configure.html @@ -1,3 +1,6 @@ + + diff --git a/samples/tab-deeplink/python/static/DeepLink.html b/samples/tab-deeplink/python/static/DeepLink.html index ce13d2cb5e..c076d11f16 100644 --- a/samples/tab-deeplink/python/static/DeepLink.html +++ b/samples/tab-deeplink/python/static/DeepLink.html @@ -1,3 +1,6 @@ + + diff --git a/samples/tab-deeplink/python/static/DeepLinkFeatures.js b/samples/tab-deeplink/python/static/DeepLinkFeatures.js index 625e9a7466..d0dd03cc40 100644 --- a/samples/tab-deeplink/python/static/DeepLinkFeatures.js +++ b/samples/tab-deeplink/python/static/DeepLinkFeatures.js @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + // Initialize the library. microsoftTeams.app.initialize().then(() => { }); diff --git a/samples/tab-deeplink/python/static/DeepLinkModel.js b/samples/tab-deeplink/python/static/DeepLinkModel.js index c2e81f8720..6181a0c948 100644 --- a/samples/tab-deeplink/python/static/DeepLinkModel.js +++ b/samples/tab-deeplink/python/static/DeepLinkModel.js @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + var Task1Link ={ ID:1, Desc: "Bots"} var Task2Link ={ ID:2, Desc:"Messaging Extension"} var Task3Link ={ ID:3, Desc: "Adaptive Card"} diff --git a/samples/tab-deeplink/python/static/Details.html b/samples/tab-deeplink/python/static/Details.html index 6b1afc8cc3..cc55141fec 100644 --- a/samples/tab-deeplink/python/static/Details.html +++ b/samples/tab-deeplink/python/static/Details.html @@ -1,3 +1,6 @@ + + diff --git a/samples/tab-deeplink/python/static/LinkDescription.js b/samples/tab-deeplink/python/static/LinkDescription.js index 580794a523..75412b649e 100644 --- a/samples/tab-deeplink/python/static/LinkDescription.js +++ b/samples/tab-deeplink/python/static/LinkDescription.js @@ -1,5 +1,10 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +// Initialize Teams SDK and handle different page contexts microsoftTeams.app.initialize().then(() => { microsoftTeams.app.getContext().then((context) => { + // Display content when opened in meeting side panel if (context.page.frameContext === "sidePanel") { document.getElementById("side-panel-content").style.display = "block"; document.getElementById('list-content').style.display = "none"; @@ -7,9 +12,11 @@ microsoftTeams.app.initialize().then(() => { document.getElementById("extended-deeplink").style.display = "none"; document.getElementById("side-panel-deeplink").style.display = "none"; } + // Display deeplink when opened in meeting stage else if (context.page.frameContext === "meetingStage") { fetch(`${window.location.origin}/api/getAppId`).then(response => response.json()).then(data => { + // Generate deeplink to open meeting side panel deepLinkString = `https://teams.microsoft.com/l/entity/${data.microsoftAppId}/DeepLinkApp?context={"chatId": "${context.chat.id}","contextType":"chat"}`; document.getElementById("side-panel-deeplink").style.display = "block"; @@ -18,20 +25,22 @@ microsoftTeams.app.initialize().then(() => { document.getElementById("extended-deeplink").style.display = "none"; }); } + // Display Bots information when topic1 is selected else if (context.page.subPageId === "topic1") { document.getElementById("taskDiv").innerHTML = "Bots"; document.getElementById("taskContent").innerHTML = "A bot also referred to as a chatbot or conversational bot is an app that runs simple and repetitive automated tasks performed by the users, such as customer service or support staff. Examples of bots in everyday use include, bots that provide information about the weather, make dinner reservations, or provide travel information. A bot interaction can be a quick question and answer, or it can be a complex conversation that provides access to services.For more details Click here"; } - + // Display Messaging Extension information when topic2 is selected else if (context.page.subPageId === "topic2") { document.getElementById("taskDiv").innerHTML = "Messaging Extension"; document.getElementById("taskContent").innerHTML = "Messaging extensions allow the users to interact with your web service through buttons and forms in the Microsoft Teams client. They can search or initiate actions in an external system from the compose message area, the command box, or directly from a message. You can send back the results of that interaction to the Microsoft Teams client in the form of a richly formatted card. This document gives an overview of the messaging extension, tasks performed under different scenarios, working of messaging extension, action and search commands, and link unfurling.For more details Click here"; } - + // Display Adaptive Card information when topic3 is selected else if (context.page.subPageId === "topic3") { document.getElementById("taskDiv").innerHTML = "Adaptive Card"; document.getElementById("taskContent").innerHTML ="Adaptive cards are a new cross product specification for cards in Microsoft products including Bots, Cortana, Outlook, and Windows. They are the recommended card type for new Teams development. For general information from the Adaptive cards team see Adaptive Cards Overview. You can use adaptive cards anywhere you can use existing Hero cards, Office365 cards, and Thumbnail cards.For more details Click here"; } + // Default: Display list of all deeplink options else{ var s=DeepLinkModel; var html =''; diff --git a/samples/tab-deeplink/python/static/TaskList.html b/samples/tab-deeplink/python/static/TaskList.html index 3d91a8499c..2f175dd217 100644 --- a/samples/tab-deeplink/python/static/TaskList.html +++ b/samples/tab-deeplink/python/static/TaskList.html @@ -1,3 +1,6 @@ + +