Teamsのアダプティブカードで画像やアイコンを設定するメモ

前回試した新手順でアダプティブカードという形式でメッセージ送信をしていました。

https://qiita.com/n0bisuke/items/3bb150967fb84a85eebe

Webhookの投稿者を指定することができなさそう？な雰囲気だったのですが、アイコンの設定などをある程度自由にやりたいなと思ってのメモです。

> ![CleanShot 2025-06-04 at 23.15.56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35387/b5b74c7f-6423-4589-a1ab-e18a54e1f96d.png)
> 
> こんな雰囲気の投稿を作ります。

## Adaptive Cardの設定

この辺りを参考にしつつ

https://learn.microsoft.com/en-us/adaptive-cards/authoring-cards/universal-action-model

### 作ったJSONオブジェクト

```js
const payload = {
	"attachments": [
		{
			"contentType": "application/vnd.microsoft.card.adaptive",
			"content": {
				"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
				"type": "AdaptiveCard",
				"version": "1.5",
				"body": [
					{
						"type": "ColumnSet",
						"columns": [
							{
								"type": "Column",
								"items": [
									{
										"type": "Image",
										"style": "Person",
										"url": "https://avatars.githubusercontent.com/u/2968926?v=4",
										"size": "Small"
									}
								],
								"width": "auto"
							},
							{
								"type": "Column",
								"spacing": "medium",
								"verticalContentAlignment": "center",
								"items": [
									{
										"type": "TextBlock",
										"weight": "Bolder",
										"text": "のびすけ",
										"wrap": true
									}
								],
								"width": "auto"
							},
							{
								"type": "Column",
								"spacing": "medium",
								"items": [
									{
										"type": "TextBlock",
										"text": "xxxxの質問",
										"isSubtle": true,
										"wrap": true
									}
								],
								"width": "stretch",
								"verticalContentAlignment": "center"
							}
						]
					},
					{
						"type": "TextBlock",
						"text": "## Workflow経由のTeamsへの通知テストです。\n\n* 通知の詳細1です。\n* 通知の詳細2です。",
						"wrap": true,
						"markdown": true
					}
				],
				"actions": [
					{
						"type": "Action.OpenUrl",
						"title": "質問ページを見てみる",
						"url": "https://github.com/your-repo/issues/1",
						"iconUrl": "icon:People,filled"
					}
				]
			}
		}
	]
};
```

### 使ってるツール

- 画像

画像はtypeにImageを指定してurlに画像URLを指定します。

```json
{
    "type": "Image",
    "style": "Person",
    "url": "https://hogehoge.com/hoge.png",
    "size": "Small"
}
```

- レイアウト

CulumnSetを指定して使います。何もしないと下の段（行）に要素が描画されますが、このCulumnSetを使うと横に並べることができます。

> ![CleanShot 2025-06-04 at 23.23.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35387/88f8fab1-a33c-47b6-9cf8-7292ea6b47bf.png)
> 
> "(画像)"、"n0bisukeさんのコメント"、"PEO/PEH/PMV/PERクラス質問場所コメント25件"の3つの要素が横に並んでます。

```json
{
    "type": "ColumnSet",
    "columns": [
		{
        1列目の要素
        },
        {
        2列目の要素
        },
        {
        3列目の要素
        }
    ]
}
```

- アクション

先ほどの画像で下の段に"質問ページを見てみる"というボタンがありますがこれの作り方です。

```json 
"actions": [
    {
        "type": "Action.OpenUrl",
        "title": "質問ページを見てみる",
        "url": target_url,
        "iconUrl": "icon:People,filled"
    }
]
```

ユーザーに何かしらアクションさせるボタンを作る機能みたいですね。

https://learn.microsoft.com/ja-jp/adaptive-cards/rendering-cards/actions

## 旧バージョンからのマイグレーション

これらを踏まえて旧バージョンとの比較です。

```js
    const postData = {
        '@type': 'MessageCard',
        themeColor: '0076D7',
        summary: 'ディスカッション',
        sections: [{
            activityTitle: title,
            activitySubtitle: discussion.title,
            facts: [{
                name: '投稿者',
                value: sender.login
            }, {
                name: 'URL',
                value: target_url
            }, {
                name: '本文',
                value: description
            }],

            markdown: true
        }]
    }
```

従来はfactsって要素の配列に色々入れてた模様です。

### アダプティブカード形式にしてみる

以前書いた記事にある記載がこちらです。

https://qiita.com/n0bisuke/items/3bb150967fb84a85eebe

```js
const payload = {
  attachments: [
    {
      contentType: 'application/vnd.microsoft.card.adaptive',
      content: {
        $schema: 'http://adaptivecards.io/schemas/adaptive-card.json',
        type: 'AdaptiveCard',
        version: '1.2',
        body: [
          {
            type: 'TextBlock',
            text: '## Workflow経由のTeamsへの通知テストです。\n\n* 通知の詳細1です。\n* 通知の詳細2です。',
            wrap: true,
            markdown: true
          }
        ]
      }
    }
  ]
};
```

従来は`{}`の中にキーを色々書いてましたが、`{attachments:[]}`に全て入れ込むっぽいですね。

### 真似してみる

```js
    const postData = {
	    "attachments": [
		{
			"contentType": "application/vnd.microsoft.card.adaptive",
			"content": {
				"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
				"type": "AdaptiveCard",
				"version": "1.5",
				"body": [
					{
						"type": "ColumnSet",
						"columns": [
							{
								"type": "Column",
								"items": [
									{
										"type": "Image",
										"style": "Person",
										"url": sender.avatar_url,
										"size": "Small"
									}
								],
								"width": "auto"
							},
							{
								"type": "Column",
								"spacing": "medium",
								"verticalContentAlignment": "center",
								"items": [
									{
										"type": "TextBlock",
										"weight": "Bolder",
										"text": `${sender.login}さんのコメント`,
										"wrap": true
									}
								],
								"width": "auto"
							},
							{
								"type": "Column",
								"spacing": "medium",
								"items": [
									{
										"type": "TextBlock",
										"text": `${categoryName} ${commentCount}`,
										"isSubtle": true,
										"wrap": true
									}
								],
								"width": "stretch",
								"verticalContentAlignment": "center"
							}
						]
					},
					{
						"type": "TextBlock",
						"text": `[${firstLabel}] ${description}`,
						"wrap": true,
						"markdown": true
					}
				],
				"actions": [
					{
						"type": "Action.OpenUrl",
						"title": "質問ページを見てみる",
						"url": target_url,
						"iconUrl": "icon:People,filled"
					}
				]
			}
		}
	]
    };
```

こんな感じでうまくいきました。

## おまけ: Fetchで出たエラー

昔のコードがaxiosを使ってましたが、Fetchに切り替えた際エラーが発生していました。

```
Error: Error: SyntaxError: Unexpected end of JSON input
    at module.exports (/Users/n0bisuke/ds/3_project/protobot/server/github/discussion/qa.js:51:19)
    at process.processTicksAndRejections (node:internal/process/task_queues:105:5)
    at async module.exports (/Users/n0bisuke/ds/3_project/protobot/server/github/discussion/index.js:70:13)
```

以下のように書いていたのですが、TeamsのAPIはレスポンスが何もないっぽい雰囲気で実行後の`response.json()`でパースエラーが発生してしまう模様でした。

```js
    try {
        const response = await fetch(webhook_url, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(postData)
        });
        if (!response.ok) {
            throw new Error(`HTTP error! status: ${response.status}`);
        }
        return await response.json();
    } catch (error) {
        throw new Error(error);
    }
```

`response.json()`を`response.text()`に書き換えたらエラー解消されました。

```js
    try {
        const response = await fetch(webhook_url, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(postData)
        });
        if (!response.ok) {
            throw new Error(`HTTP error! status: ${response.status}`);
        }
        const text = await response.text();
        console.log(`Teamsへの送信に成功しました: ${text}`);
        return text;
    } catch (error) {
        throw new Error(error);
    }
```