Skip to the content.

Teams Toolkit 之 Incoming Webhook

上一篇文章介绍了如何使用 teams toolkit 来快速创建一个最最简单的 teams app:incoming webhook。这篇文章我们就来深入看一下 incoming webhook的代码,我自己的学习方法就是花时间把一个新东西理解透彻后,再学另一个,因为一旦深入理解一个东西后,会发现很多知识是相通的。

我们先来看一下teams toolkit 生成的文件。

TeamsTookkit

  • .fx 文件夹,这里面放置的是一些 teams toolkit 会用到的配置文件,如果如何配置 azure 等等,我们以后会详细的讲这块
  • .vscode 文件夹,这里面是vscode用到的一些配置文件,对于我们这个简单的teams app来说,它很标准化,如果是一个复杂的 teams app,比如 bot,这里面还是挺有学问的,我们讲到 bot app后再做展开
  • images。这里面的图片主要是给 README 使用的,可以忽略,我们也可以删掉它
  • incoming-webhook 文件夹,这里面是我们真正的代码,下面前三个文件比较重要,我们后面展开讲
    • adaptiveCards 里面是 adaptive card 的模板文件
    • index.ts 我们程序的入口
    • webhookTarget.ts 辅助类似的代码,用来给 Teams 发送请求
    • tsconfig.json 因为sample使用typescript开发的,所以这个文件里是typescript的一些设置,比如使用ES的标准之类的东西。
    • package.json 标准的 node应用的项目文件,可以看到里面已经为我们定义好了几个命令,我们可以用来编译,运行,调试
      {
      "scripts": {
          "dev": "nodemon -e ts --exec node --inspect=9239 --signal SIGINT -r ts-node/register ./src/index.ts",
          "build": "tsc --build",
          "start": "node ./lib/src/index.js",
          "watch": "nodemon --watch ./src -e ts --exec \"npm run start\""
      }
      }
      
  • templates 文件夹,里面是 teams app 需要的 manifest.json 文件,由于我们这个sample是最最简单的 incoming webhook,所以不需要 manifest。manifest.template.json 文件是空的

了解了上面的文件结构后,我们来看一下整个应用的入口代码 index.ts

const webhookUrl: string = "https://teetee365.webhook.office.com/.......";
const webhookTarget = new WebhookTarget(new URL(webhookUrl));

webhookTarget.sendAdaptiveCard(
    AdaptiveCards.declare(template).render(
    {
        "title": "New Event Occurred!",
        "appName": "Contoso App",
        "description": "Detailed description of what happened so the user knows what's going on.",
        "notificationUrl" : "https://www.adaptivecards.io/"
    }))
.then(() => console.log("Send adaptive card successfully."))
.catch(e => console.log(`Failed to send adaptive card. ${e}`));

代码非常简单,基本就是一句话,webhookTarget.sendAdaptiveCard(),往 webhook 上发送一个 adaptive card,如果大家对 adaptive card 不太了解的话,可以参考这个网站 https://www.adaptivecards.io/,它是一种界面的描述性的代码,打开 notification-default.json 文件,我们看到下面的json。

{
  "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
  "type": "AdaptiveCard",
  "version": "1.4",
  "body": [
    {
      "type": "TextBlock",
      "text": "${title}",
      "size": "Large",
      "weight": "Bolder",
      "wrap": true
    },
    {
      "type": "TextBlock",
      "text": "${appName}",
      "isSubtle": true,
      "color": "Accent",
      "weight": "Bolder",
      "size": "Small",
      "spacing": "None"
    },
    {
      "type": "TextBlock",
      "text": "${description}",
      "isSubtle": true,
      "wrap": true
    }
  ],
  "actions": [
    {
      "type": "Action.OpenUrl",
      "title": "View Details",
      "url": "${notificationUrl}"
    }
  ]
}

上面这个配置我们一看就能想象到界面的样子,有三个文字块 (TextBlock),每块里面有一个文件的 placeholder,用 ${blablabla} 表示,最后有一个可以交互的动作 (action),用来打开一个网页url。我们对照一下最终在teams 里面的效果,就一目了然了。

TeamsTookkit

那我们来修改一下这个 adaptive card 的 json,加一张图片试试看。我们把上面的json改成如下内容。

{
  "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
  "type": "AdaptiveCard",
  "version": "1.4",
  "body": [
    {
      "type": "TextBlock",
      "text": "Tony's Cat",
      "size": "Large",
      "weight": "Bolder",
      "wrap": true
    },
    {
      "type": "Image",
      "url": "https://adaptivecards.io/content/cats/1.png",
      "altText": "Tony's Cat"
    }
  ],
  "actions": [
    {
      "type": "Action.OpenUrl",
      "title": "Go to Microsoft",
      "url": "https://www.microsoft.com"
    }
  ]
}

然后运行 npm run build,再运行 npm run start,我们在 teams 里就可以看到效果了。

TeamsTookkit

到这里我想各位基本知道是怎么回事了,我们最后再来看一下 webhookTarget.ts,看看代码里到底是怎么发送消息给 teams 的。

    public sendAdaptiveCard(card: unknown): Promise<void> {
      return axios.post(
        this.webhook.toString(),
        {
          type: "message",
          attachments: [
            {
              contentType: "application/vnd.microsoft.card.adaptive",
              contentUrl: null,
              content: card,
            },
          ],
        },
        {
          headers: { "content-type": "application/json" },
        }
      );
    }

看到这里,我相信各位已经已经明白了,实际上 incoming webhook 就是发送了一个简单的 http request 给到 webhook url,这个http request 的内容是一个特殊结构的 json,仅此而已,

看到这里,如果是有 node 开发经验的读者可能在想,实际上一句话就可以完成这些功能。我们另起一个 node 程序,在 ts 的入口文件里,写这么几行:

import axios from "axios";

axios.post(
    "https://teetee365.webhook.office.com/......",
    {
      type: "message",
      attachments: [
        {
          contentType: "application/vnd.microsoft.card.adaptive",
          contentUrl: null,
          content: {
            "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
            "type": "AdaptiveCard",
            "version": "1.4",
            "body": [
              { "type": "TextBlock", "text": "Tony's Cat (new)", "size": "Large", "weight": "Bolder", "wrap": true },
              { "type": "Image", "url": "https://adaptivecards.io/content/cats/1.png", "altText": "Tony's Cat" }
            ],
            "actions": [
              { "type": "Action.OpenUrl", "title": "Go to Microsoft", "url":  "https://www.microsoft.com" }
            ]
          },
        },
      ],
    },
    {
      headers: { "content-type": "application/json" },
    }
  );

看到这里是不是有一种恍然大悟的感觉?希望通过这种简化,抛开sample code里的各种干扰,能让大家更好的理解 incoming webhook 的本质。

Written on September 19, 2022