Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified samples/msgext-link-unfurling/python/Images/1.Install.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Binary file not shown.
53 changes: 43 additions & 10 deletions samples/msgext-link-unfurling/python/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -94,18 +94,18 @@ 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

1) Activate your desired virtual environment

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`)
Expand All @@ -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)
Expand Down
54 changes: 22 additions & 32 deletions samples/msgext-link-unfurling/python/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -39,34 +43,20 @@ 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

# Create the Bot
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__":
Expand Down
17 changes: 14 additions & 3 deletions samples/msgext-link-unfurling/python/appManifest/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,11 @@
"value": {
"domains": [
"${{BOT_DOMAIN}}",
"*.devtunnels.ms",
"*.inc1.devtunnels.ms",
"*.botframework.com"

],
"supportsAnonymizedPayloads": true
"supportsAnonymizedPayloads": false
}
}
]
Expand All @@ -69,5 +70,15 @@
],
"validDomains": [
"${{BOT_DOMAIN}}"
]
],
"authorization": {
"permissions": {
"resourceSpecific": [
{
"name": "MailboxItem.ReadWrite.User",
"type": "Delegated"
}
]
}
}
}
2 changes: 1 addition & 1 deletion samples/msgext-link-unfurling/python/assets/sample.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
],
Expand Down
72 changes: 40 additions & 32 deletions samples/msgext-link-unfurling/python/bots/link_unfurling_bot.py
Original file line number Diff line number Diff line change
@@ -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(
Expand All @@ -39,28 +42,33 @@ 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(
attachment_layout="list",
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,
)
],
)
Expand Down
3 changes: 1 addition & 2 deletions samples/msgext-link-unfurling/python/config.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
#!/usr/bin/env python3
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.

Expand All @@ -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", "")
9 changes: 6 additions & 3 deletions samples/msgext-link-unfurling/python/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
requests>=2.25.0
botbuilder-integration-aiohttp>=4.14.0
urllib3==1.26.18
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
Binary file modified samples/msgext-search-quickstart/python/Images/1.Install_App.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Loading