diff --git a/samples/msgext-search-auth-config/python/Images/1-add_app.png b/samples/msgext-search-auth-config/python/Images/1-add_app.png index 0231c3f6e8..6211402fd1 100644 Binary files a/samples/msgext-search-auth-config/python/Images/1-add_app.png and b/samples/msgext-search-auth-config/python/Images/1-add_app.png differ diff --git a/samples/msgext-search-auth-config/python/Images/10.signout_success.png b/samples/msgext-search-auth-config/python/Images/10.signout_success.png new file mode 100644 index 0000000000..7e1c6c6cf5 Binary files /dev/null and b/samples/msgext-search-auth-config/python/Images/10.signout_success.png differ diff --git a/samples/msgext-search-auth-config/python/Images/2.added_Successfully.PNG b/samples/msgext-search-auth-config/python/Images/2.added_Successfully.PNG index f99e52b124..6dcddbf89c 100644 Binary files a/samples/msgext-search-auth-config/python/Images/2.added_Successfully.PNG and b/samples/msgext-search-auth-config/python/Images/2.added_Successfully.PNG differ diff --git a/samples/msgext-search-auth-config/python/Images/3.search.PNG b/samples/msgext-search-auth-config/python/Images/3.search.PNG index 49c41a41ad..6f1b62e1f9 100644 Binary files a/samples/msgext-search-auth-config/python/Images/3.search.PNG and b/samples/msgext-search-auth-config/python/Images/3.search.PNG differ diff --git a/samples/msgext-search-auth-config/python/Images/6.search.PNG b/samples/msgext-search-auth-config/python/Images/6.search.PNG index 2913976ae1..b71fd7a458 100644 Binary files a/samples/msgext-search-auth-config/python/Images/6.search.PNG and b/samples/msgext-search-auth-config/python/Images/6.search.PNG differ diff --git a/samples/msgext-search-auth-config/python/Images/7.search.PNG b/samples/msgext-search-auth-config/python/Images/7.search.PNG index f0c66139af..978aace4da 100644 Binary files a/samples/msgext-search-auth-config/python/Images/7.search.PNG and b/samples/msgext-search-auth-config/python/Images/7.search.PNG differ diff --git a/samples/msgext-search-auth-config/python/Images/8.zero_install.png b/samples/msgext-search-auth-config/python/Images/8.zero_install.png new file mode 100644 index 0000000000..cef77666d3 Binary files /dev/null and b/samples/msgext-search-auth-config/python/Images/8.zero_install.png differ diff --git a/samples/msgext-search-auth-config/python/Images/9.signout.png b/samples/msgext-search-auth-config/python/Images/9.signout.png new file mode 100644 index 0000000000..97bd671f20 Binary files /dev/null and b/samples/msgext-search-auth-config/python/Images/9.signout.png differ diff --git a/samples/msgext-search-auth-config/python/Images/msgext-auth-config-gif.gif b/samples/msgext-search-auth-config/python/Images/msgext-auth-config-gif.gif index 81fefb8a30..c3011323a7 100644 Binary files a/samples/msgext-search-auth-config/python/Images/msgext-auth-config-gif.gif and b/samples/msgext-search-auth-config/python/Images/msgext-auth-config-gif.gif differ diff --git a/samples/msgext-search-auth-config/python/README.md b/samples/msgext-search-auth-config/python/README.md index a95476f254..7390f85d74 100644 --- a/samples/msgext-search-auth-config/python/README.md +++ b/samples/msgext-search-auth-config/python/README.md @@ -118,12 +118,11 @@ Once the Messaging Extension is installed, click the icon for **Config Auth Sear ![search ](Images/3.search.PNG) -![search ](Images/4.search.PNG) - -![search ](Images/5.search.PNG) - ![search ](Images/6.search.PNG) +![search ](Images/7.search.PNG) + +![search ](Images/8.zero_install.PNG) ## Deploy the bot to Azure diff --git a/samples/msgext-search-auth-config/python/app.py b/samples/msgext-search-auth-config/python/app.py index 9c0d33790d..86c423171e 100644 --- a/samples/msgext-search-auth-config/python/app.py +++ b/samples/msgext-search-auth-config/python/app.py @@ -14,7 +14,7 @@ TurnContext, BotFrameworkAdapter, MemoryStorage, - UserState, + UserState ) from botbuilder.core.integration import aiohttp_error_middleware diff --git a/samples/msgext-search-auth-config/python/appManifest/manifest.json b/samples/msgext-search-auth-config/python/appManifest/manifest.json index f76a58aecf..32d9c3067a 100644 --- a/samples/msgext-search-auth-config/python/appManifest/manifest.json +++ b/samples/msgext-search-auth-config/python/appManifest/manifest.json @@ -3,7 +3,6 @@ "manifestVersion": "1.19", "version": "1.0.0", "id": "${{TEAMS_APP_ID}}", - "packageName": "com.microsoft.teams.sample", "developer": { "name": "Microsoft", "websiteUrl": "https://dev.botframework.com", @@ -15,8 +14,8 @@ "outline": "icon-outline.png" }, "name": { - "short": "Config Auth Search", - "full": "Config Auth Search" + "short": "Messaging Extension Auth", + "full": "ME Auth for Search, Action and link unfurling" }, "description": { "short": "Python Messaging Extension with authentication and configuration page.", @@ -37,7 +36,8 @@ "fetchTask": false, "context": [ "commandBox", - "compose" + "compose", + "message" ], "parameters": [ { @@ -67,6 +67,18 @@ } ] } + ], + "messageHandlers": [ + { + "type": "link", + "value": { + "domains": [ + "*.botframework.com", + "${{BOT_DOMAIN}}" + ], + "supportsAnonymizedPayloads": true + } + } ] } ], diff --git a/samples/msgext-search-auth-config/python/bots/teams_messaging_extensions_search_auth_config_bot.py b/samples/msgext-search-auth-config/python/bots/teams_messaging_extensions_search_auth_config_bot.py index ca3fb9a41e..ecc27498d9 100644 --- a/samples/msgext-search-auth-config/python/bots/teams_messaging_extensions_search_auth_config_bot.py +++ b/samples/msgext-search-auth-config/python/bots/teams_messaging_extensions_search_auth_config_bot.py @@ -2,14 +2,16 @@ # Licensed under the MIT License. import urllib.parse -import xmlrpc.client from botbuilder.core import CardFactory, MessageFactory, TurnContext, UserState from botbuilder.schema import ( ThumbnailCard, CardImage, HeroCard, CardAction, + ActionTypes, + InvokeResponse ) +from bs4 import BeautifulSoup from botbuilder.schema.teams import ( MessagingExtensionAction, MessagingExtensionAttachment, @@ -21,6 +23,8 @@ TaskModuleContinueResponse, TaskModuleTaskInfo, ) +import requests +from http import HTTPStatus from botbuilder.core.teams import TeamsActivityHandler from simple_graph_client import SimpleGraphClient @@ -77,6 +81,67 @@ async def on_teams_messaging_extension_configuration_query_settings_url( ) ) + async def on_invoke_activity(self, turn_context: TurnContext): + if turn_context.activity.name == "composeExtension/anonymousQueryLink": + # Handle the anonymous query link + response = await self.handle_teams_app_based_anonymous_link_query( + turn_context, turn_context.activity.value + ) + return InvokeResponse(status=HTTPStatus.OK, body=self.convert_to_dict(response)) + else: + return await super().on_invoke_activity(turn_context) + + async def handle_teams_app_based_anonymous_link_query(self, turn_context: TurnContext, value): + # Create the Adaptive Card JSON directly + adaptive_card = { + "type": "AdaptiveCard", + "version": "1.5", + "body": [ + { + "type": "TextBlock", + "text": "Zero Installation Link Unfurling Card", + "size": "ExtraLarge", + }, + { + "type": "TextBlock", + "text": "Install the app or sign in to view full content of the card.", + "size": "Medium", + }, + ], + } + # Create a MessagingExtensionAttachment for the card + attachment = MessagingExtensionAttachment( + content_type="application/vnd.microsoft.card.adaptive", + content=adaptive_card, + ) + # Create a MessagingExtensionResult + result = MessagingExtensionResult( + attachment_layout="list", + type="auth", + attachments=[attachment], + ) + # Return the MessagingExtensionResponse + return MessagingExtensionResponse(compose_extension=result) + + def convert_to_dict(self, response: MessagingExtensionResponse) -> dict: + """Manually convert MessagingExtensionResponse and MessagingExtensionResult to a JSON-serializable dictionary.""" + compose_extension = response.compose_extension + if compose_extension is None: return {} + # Manually serialize the MessagingExtensionResult + result_dict = { + "attachmentLayout": compose_extension.attachment_layout, + "type": compose_extension.type, + "attachments": [ + { + "contentType": attachment.content_type, + "content": attachment.content, + } + for attachment in compose_extension.attachments + ], + } + # Return the dictionary in the expected format + return {"composeExtension": result_dict} + async def on_teams_messaging_extension_configuration_setting( self, turn_context: TurnContext, settings ): @@ -118,14 +183,14 @@ async def on_teams_messaging_extension_query( content_type=CardFactory.content_types.hero_card, content=HeroCard(title=obj["name"]), preview=CardFactory.hero_card(hero_card), - ) + ) attachments.append(attachment) return MessagingExtensionResponse( compose_extension=MessagingExtensionResult( type="result", attachment_layout="list", attachments=attachments ) ) - + async def _get_auth_or_search_result( self, turn_context: TurnContext, @@ -246,10 +311,10 @@ async def on_teams_messaging_extension_fetch_task( card=card, height=200, title="Adaptive Card Example", width=400 ) continue_response = TaskModuleContinueResponse( - type="continue", value=task_info + value=task_info ) return MessagingExtensionActionResponse(task=continue_response) - + return None async def on_teams_messaging_extension_select_item( @@ -275,6 +340,40 @@ async def on_teams_messaging_extension_select_item( ) def _get_search_results(self, query: str): - client = xmlrpc.client.ServerProxy("https://pypi.org/pypi") - search_results = client.search({"name": query}) - return search_results[:10] if len(search_results) > 10 else search_results + """ + Fetch the top 10 search results from PyPI using HTML scraping. + + :param query: The package name or search query + :return: List of search results + """ + search_url = f"https://pypi.org/search/?q={query}&page=1" + search_results = [] + + try: + # Send GET request to the PyPI search page + response = requests.get(search_url, headers={"User-Agent": "TeamsBot/1.0"}) + response.raise_for_status() # Raise an exception for HTTP errors + + # Parse the HTML response to extract package information + soup = BeautifulSoup(response.text, "html.parser") + for package in soup.find_all("a", class_="package-snippet", limit=10): # Limit to 10 results + name_tag = package.find("span", class_="package-snippet__name") + version_tag = package.find("span", class_="package-snippet__version") + summary_tag = package.find("p", class_="package-snippet__description") + + # Safely extract text from tags, ensuring no NoneType error + name = name_tag.text if name_tag else "Unknown" + version = version_tag.text if version_tag else "N/A" + summary = summary_tag.text if summary_tag else "No description provided." + + search_results.append({ + "name": name, + "version": version, + "summary": summary + }) + except requests.exceptions.RequestException as e: + print(f"Error fetching search results: {e}") + except Exception as e: + print(f"An unexpected error occurred: {e}") + + return search_results \ No newline at end of file diff --git a/samples/msgext-search-auth-config/python/requirements.txt b/samples/msgext-search-auth-config/python/requirements.txt index 9fdbe819f3..594b7c4eb6 100644 --- a/samples/msgext-search-auth-config/python/requirements.txt +++ b/samples/msgext-search-auth-config/python/requirements.txt @@ -1,2 +1,2 @@ -botbuilder-integration-aiohttp>=4.14.0 +botbuilder-integration-aiohttp>=4.16.2 requests_oauthlib>=1.3.0 \ No newline at end of file