使用邮件扩展进行搜索

重要

本部分中的文章基于 v3 Bot Framework SDK。 如果要查找当前文档 (4.6 或更高版本的 SDK) ,请参阅 与消息扩展的面向任务的交互 部分。

基于搜索的消息扩展允许查询服务,并将该信息以卡片的形式直接发布到邮件中。

屏幕截图显示了消息扩展卡的示例。

以下部分介绍如何执行此操作:

向应用添加消息扩展

消息扩展是一种云托管服务,用于侦听用户请求并使用结构化数据(如 卡片)进行响应。 通过 Bot Framework Activity 对象将服务与 Microsoft Teams 集成。 Bot Builder SDK 的 .NET 和 Node.js 扩展可以帮助你将消息扩展功能添加到应用。

显示 Teams 中基于操作的消息扩展的屏幕截图。

在 Bot Framework 中注册

必须先向 Microsoft Bot Framework 注册机器人。 机器人的Microsoft应用 ID 和回调终结点(如其中定义)在消息扩展中使用来接收和响应用户请求。 请记住为机器人启用 Microsoft Teams 频道。

记下机器人应用 ID 和应用密码,需要在应用清单中提供应用 ID。

更新应用清单

与机器人和选项卡一样,更新应用的 清单 以包含消息扩展属性。 这些属性控制消息扩展在 Microsoft Teams 客户端中的显示方式和行为方式。 从清单 v1.0 开始支持消息扩展。

声明消息扩展

若要添加消息扩展,请使用 属性在清单 composeExtensions 中包含新的顶级 JSON 结构。 只能为应用创建单个消息扩展。

注意

清单将消息扩展称为 composeExtensions。 这是为了保持向后兼容性。

扩展定义是具有以下结构的对象:

属性名称 用途 是否必需?
botId 使用 Bot Framework 注册的自动程序的唯一 Microsoft 应用 ID。 这通常应与整个 Teams 应用的 ID 相同。
scopes 声明此扩展是否可以添加到 personalteam 范围 (或两者) 的数组。
canUpdateConfiguration 启用 “设置” 菜单项。
commands 此消息扩展支持的命令数组。 限制为 10 个命令。

注意

如果在应用清单中将 属性设置为 canUpdateConfigurationtrue ,则可以显示消息扩展的 “设置” 菜单项。 若要启用 “设置”,还必须处理 onQuerySettingsUrlonSettingsUpdate

定义命令

消息扩展应声明一个命令,当用户从撰写框中的 “更多选项 (” “) ”按钮中选择你的应用时,将显示该命令。

屏幕截图是一个示例,其中显示了 Teams 中的消息扩展列表。

在应用清单中,命令项是具有以下结构的对象:

属性名称 用途 是否必需? 最低清单版本
id 分配给此命令的唯一 ID。 用户请求包含此 ID。 1.0
title 命令名称。 此值显示在 UI 中。 1.0
description 指示此命令执行的操作的帮助文本。 此值显示在 UI 中。 1.0
type 设置命令的类型。 可取值包括 queryaction。 如果不存在,则默认值设置为 query 1.4
initialRun 可选参数,与命令一 query 起使用。 如果设置为 true,则指示应在用户在 UI 中选择此命令后立即执行此命令。 1.0
fetchTask 可选参数,与命令一 action 起使用。 设置为 true 可提取自适应卡片或 Web URL 以在 任务模块中显示。 当命令的输入是动态的 action ,而不是一组静态参数时,会使用此方法。 请注意,如果设置为 true,将忽略命令的静态参数列表。 1.4
parameters 命令的参数的静态列表。 1.0
parameter.name 参数的名称。 这会在用户请求中发送到服务。 1.0
parameter.description 描述此参数的用途和应提供的值的示例。 此值显示在 UI 中。 1.0
parameter.title 简短的用户友好型参数标题或标签。 1.0
parameter.inputType 设置为所需的输入类型。 可能的值包括 text、、textareanumberdatetimetoggle。 默认值设置为 text 1.4
context 可选的值数组,用于定义消息操作可用的上下文。 可能的值为 messagecomposecommandBox。 默认值为“["compose", "commandBox"]”。 1.5

搜索类型消息扩展

对于基于搜索的消息扩展,请将 type 参数设置为 query。 下面是具有单个搜索命令的清单示例。 单个消息扩展最多可以有 10 个不同的命令与之关联。 这可以包括多个搜索和多个基于操作的命令。

完整的应用清单示例

{
  "$schema": "https://developer.microsoft.com/json-schemas/teams/v1.8/MicrosoftTeams.schema.json",
  "manifestVersion": "1.5",
  "version": "1.0",
  "id": "57a3c29f-1fc5-4d97-a142-35bb662b7b23",
  "developer": {
    "name": "John Developer",
    "websiteUrl": "http://bingbotservice.azurewebsites.net/",
    "privacyUrl": "http://bingbotservice.azurewebsites.net/privacy",
    "termsOfUseUrl": "http://bingbotservice.azurewebsites.net/termsofuse"
  },
  "name": {
    "short": "Bing",
    "full": "Bing"
  },
  "description": {
    "short": "Find Bing search results",
    "full": "Find Bing search results and share them with your team members."
  },
  "icons": {
    "outline": "bing-outline.jpg",
    "color": "bing-color.jpg"
  },
  "accentColor": "#ff6a00",
  "composeExtensions": [
    {
      "botId": "57a3c29f-1fc5-4d97-a142-35bb662b7b23",
      "canUpdateConfiguration": true,
      "commands": [{
          "id": "searchCmd",
          "description": "Search Bing for information on the web",
          "title": "Search",
          "initialRun": true,
          "parameters": [{
            "name": "searchKeyword",
            "description": "Enter your search keywords",
            "title": "Keywords"
          }]
        }
      ]
    }
  ],
  "permissions": [
    "identity",
    "messageTeamMembers"
  ],
  "validDomains": [
    "bingbotservice.azurewebsites.net",
    "*.bingbotservice.azurewebsites.net"
  ]
}

通过上传进行测试

可以通过上传应用来测试消息扩展。

若要打开消息扩展,请转到任何聊天或频道。 选择撰写框中的“ 更多选项 () ”按钮,然后选择邮件扩展。

添加事件处理程序

大部分工作都涉及 事件, onQuery 该事件处理消息扩展窗口中的所有交互。

如果在清单中将 设置为 canUpdateConfigurationtrue ,则为邮件扩展启用“设置”菜单项,并且还必须处理 onQuerySettingsUrlonSettingsUpdate

处理 onQuery 事件

消息扩展在 onQuery 消息扩展窗口中发生任何事件或发送到窗口时接收事件。

如果消息扩展使用配置页,则 处理程序 onQuery 应首先检查存储的配置信息;如果未配置消息扩展,则返回 config 包含配置页链接的响应。 配置页的响应也由 onQuery处理。 唯一的例外是处理程序调用 onQuerySettingsUrl配置页时;请参阅以下部分:

如果消息扩展需要身份验证,请检查用户状态信息。 如果用户未登录,请按照本文后面的 身份验证 部分中的说明进行操作。

接下来,检查是否已 initialRun 设置;如果是,请采取适当的操作,例如提供说明或响应列表。

处理程序 onQuery 的其余部分提示用户输入信息,显示预览卡片列表,并返回用户选择的卡片。

处理 onQuerySettingsUrl 和 onSettingsUpdate 事件

和 事件协同工作以启用“设置”菜单项。onSettingsUpdateonQuerySettingsUrl

屏幕截图显示了“设置”菜单项的位置。

onQuerySettingsUrl 处理程序返回配置页的 URL;在配置页关闭后,用于 onSettingsUpdate 的处理程序将接受并保存返回的状态。 这是一种无法从配置页接收响应的情况onQuery

接收和响应查询

对消息扩展的每个请求都是通过 Activity 发布到回调 URL 的对象完成的。 请求包含有关用户命令的信息,例如 ID 和参数值。 请求还提供有关调用扩展的上下文的元数据,包括用户和租户 ID,以及聊天 ID 或频道和团队 ID。

接收用户请求

当用户执行查询时,Microsoft Teams 会向服务发送标准 Bot Framework Activity 对象。 服务应为 Activity 设置为 typeinvokename 设置为受支持的 composeExtensions 类型的 执行其逻辑,如下表所示。

除了标准机器人活动属性外,有效负载还包含以下请求元数据:

属性名称 用途
type 请求类型;必须为 invoke
name 颁发给服务的命令类型。 支持以下类型:
composeExtension/query
composeExtension/querySettingUrl
composeExtension/setting
composeExtension/selectItem
composeExtension/queryLink
from.id 发送请求的用户 ID。
from.name 发送请求的用户名。
from.aadObjectId Microsoft发送请求的用户的 Entra 对象 ID。
channelData.tenant.id Microsoft Entra 租户 ID。
channelData.channel.id 频道 ID(如果请求是在频道中发出)。
channelData.team.id 团队 ID(如果请求是在频道中发出)。
clientInfo 有关用于发送用户消息的客户端软件的可选元数据。 实体可以包含两个属性:
字段 country 包含用户的检测到的位置。
字段 platform 描述消息客户端平台。
有关详细信息, 请参阅非 IRI 实体类型 - clientInfo

请求参数位于 value 对象中,其中包括以下属性:

属性名称 用途
commandId 用户调用的命令的名称,与应用清单中声明的命令之一相匹配。
parameters 参数数组:每个参数对象都包含参数名称以及用户提供的参数值。
queryOptions 分页参数:
skip:跳过此查询的计数
count:要返回的元素数

请求示例

{
  "name": "composeExtension/query",
  "value": {
    "commandId": "searchCmd",
    "parameters": [
      {
        "name": "searchKeywords",
        "value": "Toronto"
      }
    ],
    "queryOptions": {
      "skip": 0,
      "count": 25
    }
  },
  "type": "invoke",
  "timestamp": "2017-05-01T15:45:51.876Z",
  "localTimestamp": "2017-05-01T08:45:51.876-07:00",
  "id": "f:622749630322482883",
  "channelId": "msteams",
  "serviceUrl": "https://smba.trafficmanager.net/amer-client-ss.msg/",
  "from": {
    "id": "29:1C7dbRrC_5yzN1RGtZIrcWT0xz88KPGP9sxdpVpV8sODlgPHeQE9RqQ02hnpuKzy6zZ-AaZx6swUOMj_Dsdse3TQ4sIaeebbFBF-VgjJy_nY",
    "name": "Larry Jin",
    "aadObjectId": "cd723fa0-0591-416a-9290-e93ecf3a9b92"
  },
  "conversation": {
    "id": "19:skypespaces_8198cfe0dd2647ae91930f0974768a40@thread.skype"
  },
  "recipient": {
    "id": "28:b4922ea1-5315-4fd0-9b21-d941ab06e39f",
    "name": "TheComposeExtensionDev"
  },
  "entities": [
    {
    "type": "clientInfo",
      "country": "US",
      "platform": "Windows"
    }
  ]
}

作为另一种 (,或者除了搜索外部服务) 之外,还可以使用插入撰写消息框中的 URL 来查询服务并返回卡片。 在下面的屏幕截图中,用户粘贴了 Azure DevOps 中工作项的 URL,消息扩展已将其解析为卡片。

屏幕截图显示了链接展开的示例。

若要使消息扩展以这种方式与链接交互,首先需要将 messageHandlers 数组添加到应用清单,如示例中所示:

"composeExtensions": [
  {
    "botId": "abc123456-ab12-ab12-ab12-abcdef123456",
    "messageHandlers": [
      {
        "type": "link",
        "value": {
          "domains": [
            "*.trackeddomain.com"
          ]
        }
      }
    ]
  }
]

添加域以侦听应用清单后,需要更改机器人代码以 响应 以下调用请求。

{
  "type": "invoke",
  "name": "composeExtension/queryLink",
  "value": {
    "url": "https://theurlsubmittedbyyouruser.trackeddomain.com/id/1234"
  }
}

如果应用返回多个项,则仅使用第一个项。

响应用户请求

当用户执行查询时,Teams 会向服务发出同步 HTTP 请求。 在此期间,代码有 5 秒的时间向请求提供 HTTP 响应。 在此期间,服务可以执行另一个查找,或者执行处理请求所需的任何其他业务逻辑。

服务应使用与用户查询匹配的结果进行响应。 响应必须使用以下正文指示 的 200 OK HTTP 状态代码和有效的 application/json 对象:

属性名称 用途
composeExtension 顶级响应信封。
composeExtension.type 响应类型。 支持以下类型:
result:显示搜索结果的列表
auth:提示用户进行身份验证
config:提示用户设置消息扩展
message: 显示纯文本邮件
composeExtension.attachmentLayout 指定附件的布局。 用于 类型的 result响应。
支持以下类型:
list:包含缩略图、标题和文本字段的卡片对象列表
grid:缩略图图像网格
composeExtension.attachments 有效附件对象的数组。 用于 类型的 result响应。
支持以下类型:
application/vnd.microsoft.card.thumbnail
application/vnd.microsoft.card.hero
application/vnd.microsoft.teams.card.o365connector
application/vnd.microsoft.card.adaptive
composeExtension.suggestedActions 建议的操作。 用于 类型 auth 为 或 config的响应。
composeExtension.text 要显示的消息。 用于 类型的 message响应。

响应卡类型和预览

我们支持以下附件类型:

有关详细信息,请参阅 卡片 了解概述。

若要了解如何使用缩略图和主图卡类型,请参阅 添加卡片和卡片操作

有关 Microsoft 365 组的连接器卡的详细信息,请参阅 将连接器卡用于 Microsoft 365 组

结果列表显示在Microsoft Teams UI 中,每个项目的预览。 预览是通过以下两种方式之一生成的:

  • preview在 对象中使用 attachment 属性。 附件 preview 只能是“英雄”或“缩略图”卡片。
  • 从附件的基本 titletextimage 属性中提取。 仅当未设置属性且这些属性可用时 preview ,才使用这些属性。

只需设置其预览属性,即可在结果列表中显示 Microsoft 365 组的自适应或连接器卡的预览。 如果结果已经是英雄卡或缩略图卡,则不需要这样做。 如果使用预览附件,它必须是“Hero”或“缩略图”卡片。 如果未指定预览属性,则卡片的预览将失败,并且不会显示任何内容。

响应示例

此示例显示了包含两个结果的响应,其中混合了不同的卡片格式:Microsoft 365 组连接器和自适应。 虽然你可能希望在响应中坚持使用一种卡片格式,但它显示了集合中attachments每个元素的 属性必须如何preview显式定义一个以主图或缩略图格式(如上所述)的预览。

{
  "composeExtension": {
    "type": "result",
    "attachmentLayout": "list",
    "attachments": [
      {
        "contentType": "application/vnd.microsoft.teams.card.o365connector",
        "content": {
          "sections": [
            {
              "activityTitle": "[85069]: Create a cool app",
              "activityImage": "https://placekitten.com/200/200"
            },
            {
              "title": "Details",
              "facts": [
                {
                  "name": "Assigned to:",
                  "value": "[Larry Brown](mailto:larryb@example.com)"
                },
                {
                  "name": "State:",
                  "value": "Active"
                }
              ]
            }
          ]
        },
        "preview": {
          "contentType": "application/vnd.microsoft.card.thumbnail",
          "content": {
            "title": "85069: Create a cool app",
            "images": [
              {
                "url": "https://placekitten.com/200/200"
              }
            ]
          }
        }
      },
      {
        "contentType": "application/vnd.microsoft.card.adaptive",
        "content": {
          "type": "AdaptiveCard",
          "body": [
            {
              "type": "Container",
              "items": [
                {
                  "type": "TextBlock",
                  "text": "Microsoft Corp (NASDAQ: MSFT)",
                  "size": "medium",
                  "isSubtle": true
                },
                {
                  "type": "TextBlock",
                  "text": "September 19, 4:00 PM EST",
                  "isSubtle": true
                }
              ]
            },
            {
              "type": "Container",
              "spacing": "none",
              "items": [
                {
                  "type": "ColumnSet",
                  "columns": [
                    {
                      "type": "Column",
                      "width": "stretch",
                      "items": [
                        {
                          "type": "TextBlock",
                          "text": "75.30",
                          "size": "extraLarge"
                        },
                        {
                          "type": "TextBlock",
                          "text": "▼ 0.20 (0.32%)",
                          "size": "small",
                          "color": "attention",
                          "spacing": "none"
                        }
                      ]
                    },
                    {
                      "type": "Column",
                      "width": "auto",
                      "items": [
                        {
                          "type": "FactSet",
                          "facts": [
                            {
                              "title": "Open",
                              "value": "62.24"
                            },
                            {
                              "title": "High",
                              "value": "62.98"
                            },
                            {
                              "title": "Low",
                              "value": "62.20"
                            }
                          ]
                        }
                      ]
                    }
                  ]
                }
              ]
            }
          ],
          "version": "1.0"
        },
        "preview": {
          "contentType": "application/vnd.microsoft.card.thumbnail",
          "content": {
            "title": "Microsoft Corp (NASDAQ: MSFT)",
            "text": "75.30 ▼ 0.20 (0.32%)"
          }
        }
      }
    ]
  }
}

默认查询

如果在清单中将 true 设置为 initialRun ,Microsoft Teams 会在用户首次打开邮件扩展时发出“默认”查询。 服务可以使用一组预填充的结果来响应此查询。 例如,这对于显示最近查看的项目、收藏夹或任何其他不依赖于用户输入的信息非常有用。

默认查询的结构与任何常规用户查询相同,但 initialRun 字符串值为 的参数除外 true

默认查询的请求示例

{
  "type": "invoke",
  "name": "composeExtension/query",
  "value": {
    "commandId": "searchCmd",
    "parameters": [
      {
        "name": "initialRun",
        "value": "true"
      }
    ],
    "queryOptions": {
      "skip": 0,
      "count": 25
    }
  },
  ⋮
}

标识用户

对服务的每个请求都包括执行请求的用户的模糊处理 ID,以及用户的显示名称和Microsoft Entra 对象 ID。

"from": {
  "id": "29:1C7dbRrC_5yzN1RGtZIrcWT0xz88KPGP9sxdpVpV8sODlgPHeQE9RqQ02hnpuKzy6zZ-AaZx6swUOMj_Dsdse3TQ4sIaeebbFBF-VgjJy_nY",
  "name": "Larry Jin",
  "aadObjectId": "cd723fa0-0591-416a-9290-e93ecf3a9b92"
},

idaadObjectId 值保证为经过身份验证的 Teams 用户的和值。 它们可用作在服务中查找凭据或任何缓存状态的密钥。 此外,每个请求都包含用户的 Microsoft Entra 租户 ID,可用于标识用户的组织。 如果适用,请求还包含发起请求的团队和频道 ID。

身份验证

如果服务需要用户身份验证,则需要先将用户登录,然后用户才能使用消息扩展。 如果你编写了一个机器人或一个登录用户的选项卡,本部分应该很熟悉。

顺序如下:

  1. 用户发出查询,或者默认查询会自动发送到服务。
  2. 服务通过检查 Teams 用户 ID 来检查用户是否已首次进行身份验证。
  3. 如果用户尚未进行身份验证,请使用包括身份验证 URL 在内的建议操作发送回 auth 响应 openUrl
  4. Microsoft Teams 客户端使用给定的身份验证 URL 启动托管网页的弹出窗口。
  5. 用户登录后,应关闭窗口,并向 Teams 客户端发送“身份验证代码”。
  6. 然后,Teams 客户端向服务重新发出查询,其中包括步骤 5 中传递的身份验证代码。 你的服务必须验证在步骤 6 中收到的身份验证代码是否与步骤 5 中的身份验证代码匹配,这可确保恶意用户不会尝试欺骗或破坏登录流。 这可以有效地“关闭循环”以完成安全身份验证序列。

使用登录操作进行响应

若要提示未经身份验证的用户登录,请使用包含身份验证 URL 的类型为 openUrl 的建议操作进行响应。

登录操作的响应示例

{
  "composeExtension":{
    "type":"auth",
    "suggestedActions":{
      "actions":[
        {
          "type": "openUrl",
          "value": "https://example.com/auth",
          "title": "Sign in to this app"
        }
      ]
    }
  }
}

注意

若要在 Teams 弹出窗口中托管登录体验,URL 的域部分必须位于应用的有效域列表中。 有关详细信息,请参阅清单架构中的 validDomains

启动登录流

登录必须响应并适合弹出窗口。 它应与使用消息传递的 Microsoft Teams JavaScript 客户端 SDK 集成。

与 Teams 中运行的其他嵌入式体验一样,窗口中的代码需要首先调用 microsoftTeams.initialize()。 如果代码执行 OAuth 流,则可以将 Teams 用户 ID 传递到窗口中,然后该窗口可以将其传递到 OAuth 登录 URL 的 URL。

完成登录流

登录请求完成并重定向回页面时,应执行以下步骤:

  1. 生成安全代码。 (可以是随机数。) 需要在服务上缓存此代码,以及通过登录获取的凭据,例如 OAuth 2.0 令牌。
  2. 调用 microsoftTeams.authentication.notifySuccess 并传递安全代码。

此时,窗口将关闭,控件将传递给 Teams 客户端。 客户端现在可以重新发出原始用户查询以及 属性中的 state 安全代码。 你的代码可以使用安全代码来查找之前存储的凭据以完成身份验证序列,然后完成用户请求。

重新发出的请求示例

{
    "name": "composeExtension/query",
    "value": {
        "commandId": "insertWiki",
        "parameters": [{
            "name": "searchKeyword",
            "value": "lakers"
        }],
        "state": "12345",
        "queryOptions": {
            "skip": 0,
            "count": 25
        }
    },
    "type": "invoke",
    "timestamp": "2017-04-26T05:18:25.629Z",
    "localTimestamp": "2017-04-25T22:18:25.629-07:00",
    "entities": [{
        "type": "clientInfo",
        "country": "US",
        "platform": "Web",
        
    }],
    "text": "",
    "attachments": [],
    "address": {
        "id": "f:7638210432489287768",
        "channelId": "msteams",
        "user": {
            "id": "29:1A5TJWHkbOwSyu_L9Ktk9QFI1d_kBOEPeNEeO1INscpKHzHTvWfiau5AX_6y3SuiOby-r73dzHJ17HipUWqGPgw",
            "aadObjectId": "fc8ca1c0-d043-4af6-b09f-141536207403"
        },
        "conversation": {
            "id": "19:7705841b240044b297123ad7f9c99217@thread.skype"
        },
        "bot": {
            "id": "28:c073afa8-7e77-4f92-b3e7-aa589e952a3e",
            "name": "maotestbot2"
        },
        "serviceUrl": "https://smba.trafficmanager.net/amer-client-ss.msg/",
        "useAuth": true
    },
    "source": "msteams"
}

SDK 支持

.NET

若要使用 Bot Builder SDK for .NET 接收和处理查询,可以检查 invoke 传入活动的操作类型,然后使用 NuGet 包 Microsoft.Bot.Connector.Teams 中的帮助程序方法来确定它是否是消息扩展活动。

.NET 中的示例代码

public async Task<HttpResponseMessage> Post([FromBody]Activity activity)
{
    if (activity.Type == ActivityTypes.Invoke) // Received an invoke
    {
        if (activity.IsComposeExtensionQuery())
        {
            // This is the response object that will get sent back to the messaging extension request.
            ComposeExtensionResponse invokeResponse = null;

            // This helper method gets the query as an object.
            var query = activity.GetComposeExtensionQueryData();

            if (query.CommandId != null && query.Parameters != null && query.Parameters.Count > 0)
            {
                // query.Parameters has the parameters sent by client
                var results = new ComposeExtensionResult()
                {
                    AttachmentLayout = "list",
                    Type = "result",
                    Attachments = new List<ComposeExtensionAttachment>(),
                };
                invokeResponse.ComposeExtension = results;
            }

            // Return the response
            return Request.CreateResponse<ComposeExtensionResponse>(HttpStatusCode.OK, invokeResponse);
        } else
        {
            // Handle other types of Invoke activities here.
        }
    } else {
      // Failure case catch-all.
      var response = Request.CreateResponse(HttpStatusCode.BadRequest);
      response.Content = new StringContent("Invalid request! This API supports only messaging extension requests. Check your query and try again");
      return response;
    }
}

Node.js

Node.js 中的示例代码

require('dotenv').config();

import * as restify from 'restify';
import * as builder from 'botbuilder';
import * as teamBuilder from 'botbuilder-teams';

class App {
    run() {
        const server = restify.createServer();
        let teamChatConnector = new teamBuilder.TeamsChatConnector({
            appId: process.env.MICROSOFT_APP_ID,
            appPassword: process.env.MICROSOFT_APP_PASSWORD
        });

        // Command ID must match what's defined in manifest
        teamChatConnector.onQuery('<%= commandId %>',
            (event: builder.IEvent,
            query: teamBuilder.ComposeExtensionQuery,
            callback: (err: Error, result: teamBuilder.IComposeExtensionResponse, statusCode: number) => void) => {
                // Check for initialRun; i.e., when you should return default results
                // if (query.parameters[0].name === 'initialRun') {}

                // Check query.queryOptions.count and query.queryOptions.skip for paging

                // Return auth response
                // let response = teamBuilder.ComposeExtensionResponse.auth().actions([
                //     builder.CardAction.openUrl(null, 'https://authUrl', 'Please sign in')
                // ]).toResponse();

                // Return config response
                // let response = teamBuilder.ComposeExtensionResponse.config().actions([
                //     builder.CardAction.openUrl(null, 'https://configUrl', 'Please sign in')
                // ]).toResponse();

                // Return result response
                let response = teamBuilder.ComposeExtensionResponse.result('list').attachments([
                    new builder.ThumbnailCard()
                        .title('Test thumbnail card')
                        .text('This is a test thumbnail card')
                        .images([new builder.CardImage().url('https://bot-framework.azureedge.net/bot-icons-v1/bot-framework-default-9.png')])
                        .toAttachment()
                ]).toResponse();
                callback(null, response, 200);
            });
        server.post('/api/composeExtension', teamChatConnector.listen());
        server.listen(process.env.PORT, () => console.log(`listening to port:` + process.env.PORT));
    }
}

const app = new App();
app.run();

另请参阅

Bot Framework 示例