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
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.
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.
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.
Binary file not shown.
Binary file not shown.
60 changes: 43 additions & 17 deletions samples/msgext-search-quickstart/js/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,11 @@ This comprehensive JavaScript quick start sample illustrates the creation of a M
* Bots
* Message Extensions
* Search Commands
* Agent SDK

## Interaction with app

![Sample Module](Images/msgextsearchquickstart.gif)
![Sample Module](Images/MsgExtSearchQuickStart.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)).
Expand All @@ -36,7 +37,6 @@ Please find below demo manifest which is deployed on Microsoft Azure and you can

## Prerequisites

**Dependencies**
- [NodeJS](https://nodejs.org/en/)
- [dev tunnel](https://learn.microsoft.com/en-us/azure/developer/dev-tunnels/get-started?tabs=windows) or [ngrok](https://ngrok.com/download) latest version or equivalent tunneling solution
- [M365 developer account](https://docs.microsoft.com/microsoftteams/platform/concepts/build-and-test/prepare-your-o365-tenant) or access to a Teams account with the appropriate permissions to install an app.
Expand Down Expand Up @@ -65,7 +65,7 @@ The simplest way to run this sample in Teams is to use Microsoft 365 Agents Tool

- For the Messaging endpoint URL, use the current `https` URL you were given by running the tunnelling application and append it with the path `/api/messages`. It should like something work `https://{subdomain}.ngrok-free.app/api/messages`.

- Click on the `Bots` menu item from the toolkit and select the bot you are using for this project. Update the messaging endpoint and press enter to save the value in the Bot Framework.
- Click on the `Bots` menu item from the toolkit and select the bot you are using for this project. Update the messaging endpoint and press enter to save the value in Azure Bot Service.

- Ensure that you've [enabled the Teams Channel](https://docs.microsoft.com/azure/bot-service/channel-connect-teams?view=azure-bot-service-4.0)

Expand Down Expand Up @@ -130,37 +130,63 @@ The simplest way to run this sample in Teams is to use Microsoft 365 Agents Tool

## Running the sample

![Search](Images/search.png)
**Install the app in Teams:**

![Result](Images/result.png)
![Install App](Images/1.Install_App.png)

## Outlook on the web
**Open the installed app:**

![Open App](Images/2.Open_App.png)

**Select messaging extension search:**

![Select MsgExt Search](Images/3.Select_MsgExt_Search.png)

**Search results displayed:**

![Results On Search](Images/4.Results_On_Search.png)

**Searched NPM package results:**

![Searched NPM Results](Images/5.Searched_NPM_Results.png)

- To view your app in Outlook on the web.
**Using the app in Outlook - Click on New Mail:**

- Go to [Outlook on the web](https://outlook.office.com/mail/)and sign in using your dev tenant account.
![Outlook Click New Mail](Images/6.Outlook_Click_New_Main.png)

**After opening Outlook web, click the "New mail" button.**
**Select the app from Outlook:**

![Open New Mail](Images/OpenNewMail.png)
![Outlook Select App](Images/7.Outlook_Select_App.png)

**on the tool bar on top,select Apps icon. Your uploaded app title appears among your installed apps**
**Enable Outlook channel in Azure Bot Service:**

![OpenAppIcon](Images/OpenAppIcon.png)
![Enable Channel In Azure Bot](Images/8.Enable_Channel_In_Azure_Bot.png)

**Select your app icon to launch your app in Office on the web**
**Search results in Outlook:**

![Outlook Search Results](Images/9.Outlook_Search_Results.png)

**Selected NPM package in Outlook:**

![Outlook Selected NPM Package](Images/10.Outlook_Selected_NPM_Package.png)

**Multiple NPM packages displayed:**

![Multiple NPM Packages](Images/11.Multiple_NPM_Packages.png)

## Outlook on the web

![Search in Extension](Images/SearchInExtension.png)
- To view your app in Outlook on the web, go to [Outlook on the web](https://outlook.office.com/mail/) and sign in using your dev tenant account.

![Output in Outlook](Images/OutputInOutlook.png)
**Note:** Please refer to the images in the "Running the sample" section above for detailed screenshots of the Outlook integration.

## Deploy to Teams
Start debugging the project by hitting the `F5` key or click the debug icon in Visual Studio Code and click the `Start Debugging` green arrow button.

## 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 Agents SDK](https://learn.microsoft.com/en-us/microsoft-365-copilot/extensibility/agents-sdk-overview)
- [Microsoft Agents SDK for JavaScript](https://www.npmjs.com/package/@microsoft/agents-hosting)
- [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)
14 changes: 12 additions & 2 deletions samples/msgext-search-quickstart/js/appManifest/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
},
"name": {
"short": "Messaging Extension Search",
"full": ""
"full": "Messaging Extension Search"
},
"description": {
"short": "Messaging Extension quick start for handling search requests.",
Expand Down Expand Up @@ -56,5 +56,15 @@
],
"validDomains": [
"${{BOT_DOMAIN}}"
]
],
"authorization": {
"permissions": {
"resourceSpecific": [
{
"name": "MailboxItem.ReadWrite.User",
"type": "Delegated"
}
]
}
}
}
4 changes: 2 additions & 2 deletions samples/msgext-search-quickstart/js/assets/sample.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"This JavaScript 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": "2021-07-07",
"updateDateTime": "2024-10-15",
"updateDateTime": "2025-12-04",
"products": [
"Teams"
],
Expand All @@ -35,7 +35,7 @@
{
"type": "image",
"order": 100,
"url": "https://raw.githubusercontent.com/OfficeDev/Microsoft-Teams-Samples/main/samples/msgext-search-quickstart/js/Images/msgext-search-quickstart.gif",
"url": "https://raw.githubusercontent.com/OfficeDev/Microsoft-Teams-Samples/main/samples/msgext-search-quickstart/js/Images/MsgExtSearchQuickStart.gif",
"alt": "Solution UX showing search messaging extension"
}
],
Expand Down
149 changes: 104 additions & 45 deletions samples/msgext-search-quickstart/js/botActivityHandler.js
Original file line number Diff line number Diff line change
@@ -1,70 +1,131 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

const {
TurnContext,
MessageFactory,
TeamsActivityHandler,
CardFactory,
ActionTypes
} = require('botbuilder');
const { CardFactory } = require('@microsoft/agents-hosting');
const { TeamsActivityHandler } = require('@microsoft/agents-hosting-extensions-teams');
const axios = require('axios');
const querystring = require('querystring');

/* Building a messaging extension search command is a two step process.
(1) Define how the messaging extension will look and be invoked in the client.
This can be done from the Configuration tab, or the Manifest Editor.
Learn more: https://aka.ms/teams-me-design-search.
(2) Define how the bot service will respond to incoming search commands.
Learn more: https://aka.ms/teams-me-respond-search.

NOTE: Ensure the bot endpoint that services incoming messaging extension queries is
registered with Bot Framework.
Learn more: https://aka.ms/teams-register-bot.
*/

class BotActivityHandler extends TeamsActivityHandler {
constructor() {
super();
}

/* Building a messaging extension search command is a two step process.
(1) Define how the messaging extension will look and be invoked in the client.
This can be done from the Configuration tab, or the Manifest Editor.
Learn more: https://aka.ms/teams-me-design-search.
(2) Define how the bot service will respond to incoming search commands.
Learn more: https://aka.ms/teams-me-respond-search.
// handleTeamsMessagingExtensionQuery: search command -> help (empty), sample results, or error card
async handleTeamsMessagingExtensionQuery(context, query) {
// Extract search query from parameters with validation
let searchQuery = '';
try {
if (query && query.parameters && Array.isArray(query.parameters) && query.parameters.length > 0) {
const param = query.parameters[0];
if (param && param.value) {
searchQuery = param.value.trim();
}
}
} catch (err) {
console.error('Error extracting search query:', err);
}

NOTE: Ensure the bot endpoint that services incoming messaging extension queries is
registered with Bot Framework.
Learn more: https://aka.ms/teams-register-bot.
*/
// Use default search term if not provided or too short (npm API requires min 2 chars)
if (!searchQuery || searchQuery.length < 2) {
searchQuery = 'react';
}

// Invoked when the service receives an incoming search query.
async handleTeamsMessagingExtensionQuery(context, query) {
const axios = require('axios');
const querystring = require('querystring');
try {
const response = await axios.get(`http://registry.npmjs.com/-/v1/search?${querystring.stringify({ text: searchQuery, size: 8 })}`, {
timeout: 10000
});

const searchQuery = query.parameters[0].value;
const response = await axios.get(`http://registry.npmjs.com/-/v1/search?${ querystring.stringify({ text: searchQuery, size: 8 }) }`);
const attachments = [];
response.data.objects.forEach(obj => {
const packageName = obj.package.name;
const description = obj.package.description || 'No description available';
const homepage = obj.package.links.homepage || 'https://www.npmjs.com';

const attachments = [];
response.data.objects.forEach(obj => {
const heroCard = CardFactory.heroCard(obj.package.name);
const preview = CardFactory.heroCard(obj.package.name); // Preview cards are optional for Hero card. You need them for Adaptive Cards.
preview.content.tap = { type: 'invoke', value: { description: obj.package.description } };
const attachment = { ...heroCard, preview };
attachments.push(attachment);
});
// Create Hero Card (better Outlook compatibility than Adaptive Cards)
const heroCard = CardFactory.heroCard(
packageName,
description,
null,
[
{
type: 'openUrl',
title: 'View on NPM',
value: `https://www.npmjs.com/package/${packageName}`
},
{
type: 'openUrl',
title: 'Homepage',
value: homepage
}
]
);

return {
composeExtension: {
type: 'result',
attachmentLayout: 'list',
attachments: attachments
}
};
// Create preview card
const preview = CardFactory.heroCard(packageName, description);
preview.content.tap = { type: 'invoke', value: { title: packageName, description: description } };

const attachment = { ...heroCard, preview };
attachments.push(attachment);
});

return {
composeExtension: {
type: 'result',
attachmentLayout: 'list',
attachments: attachments
}
};
} catch (error) {
console.error('Error searching npm packages:', error);

// Return error card on API failure
const errorCard = CardFactory.heroCard(
'Search Error',
'Unable to search npm packages. Please try again with a different query (minimum 2 characters).'
);

return {
composeExtension: {
type: 'result',
attachmentLayout: 'list',
attachments: [errorCard]
}
};
}
}

// Invoked when the user selects an item from the search result list returned above.
async handleTeamsMessagingExtensionSelectItem(context, obj) {
// Create thumbnail card with selected item details
const thumbnailCard = CardFactory.thumbnailCard(
obj.title || 'Package Details',
obj.description || 'No description available'
);

return {
composeExtension: {
type: 'result',
attachmentLayout: 'list',
attachments: [CardFactory.thumbnailCard(obj.description)]
attachments: [thumbnailCard]
}
};
}

/* Messaging Extension - Unfurling Link */
handleTeamsAppBasedLinkQuery(context, query) {
// handleTeamsAppBasedLinkQuery: link unfurl -> adaptive card + preview (fallback to "Unknown URL")
async handleTeamsAppBasedLinkQuery(context, query) {
const attachment = CardFactory.thumbnailCard('Thumbnail Card',
query.url,
['https://raw.githubusercontent.com/microsoft/botframework-sdk/master/icon.png']);
Expand All @@ -75,12 +136,10 @@ class BotActivityHandler extends TeamsActivityHandler {
attachments: [attachment]
};

const response = {
return {
composeExtension: result
};
return response;
}
/* Messaging Extension - Unfurling Link */
}
}

module.exports.BotActivityHandler = BotActivityHandler;
Expand Down
29 changes: 16 additions & 13 deletions samples/msgext-search-quickstart/js/index.js
Original file line number Diff line number Diff line change
@@ -1,30 +1,32 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

// index.js is used to setup and configure your bot
// index.js is used to setup and configure your agent

// Import required packages
const path = require('path');
const express = require('express');

// Import required bot services.
// See https://aka.ms/bot-services to learn more about the different parts of a bot.
// Import required agent services.
// See https://aka.ms/agents-hosting to learn more about the Agent SDK.
const {
CloudAdapter,
ConfigurationBotFrameworkAuthentication
} = require('botbuilder')
loadAuthConfigFromEnv
} = require('@microsoft/agents-hosting');

// Import bot definitions
// Import agent application
const { BotActivityHandler } = require('./botActivityHandler');

// Read botFilePath and botFileSecret from .env file.
// Read environment variables from .env file.
const ENV_FILE = path.join(__dirname, '.env');
require('dotenv').config({ path: ENV_FILE });

// Load authentication configuration from environment variables
const authConfig = loadAuthConfigFromEnv();

// Create adapter.
// See https://aka.ms/about-bot-adapter to learn more about adapters.
const botFrameworkAuthentication = new ConfigurationBotFrameworkAuthentication(process.env);
const adapter = new CloudAdapter(botFrameworkAuthentication);
// See https://aka.ms/agents-hosting to learn more about adapters.
const adapter = new CloudAdapter(authConfig);

adapter.onTurnError = async (context, error) => {
// This check writes out errors to console log .vs. app insights.
Expand All @@ -40,9 +42,10 @@ adapter.onTurnError = async (context, error) => {
'TurnError'
);

// Uncomment below commented line for local debugging.
// await context.sendActivity(`Sorry, it looks like something went wrong. Exception Caught: ${error}`);

// Only send error messages for regular conversations, not for invokes (messaging extensions)
if (context.activity.type !== 'invoke') {
await context.sendActivity(`Sorry, it looks like something went wrong. Exception Caught: ${error}`);
}
};

// Create bot handlers
Expand Down
Loading