PythonでTeamsにWebhook投稿する方法~Adaptive Cardを使った動的通知

Pythonで生成したJSONを使ってMicrosoft TeamsのチャネルにAdaptive Cardを投稿すると、プロジェクトの進捗やリリース情報をリアルタイムに共有できるため、とても便利です。しかし、いざWebhookを使ってJSONをPOSTしたらカード内のテキストが空になってしまう、というトラブルに直面することがあります。ここでは、そんなトラブルの原因と、動的に生成したデータをしっかりとAdaptive Card上に反映させるためのポイントを解説していきます。

TeamsにAdaptive Cardを投稿する仕組み

TeamsにWebhookを使ってAdaptive Cardを投稿するためには、まずTeamsのチャネルごとに「Incoming Webhook」を設定する必要があります。Incoming Webhookは、外部のシステムから特定のURLにPOSTリクエストを送るだけでメッセージをチャネルに表示できる仕組みです。メッセージとして単純なテキストを送るだけでなく、リッチなUIを持つカードを利用できるため、状況の可視化に役立ちます。

さらに、Adaptive CardはJSONフォーマットで定義され、TeamsなどさまざまなMicrosoftのサービスで利用できるのが特徴です。Adaptive Cardを使うことで、見栄えの良いレイアウトや画像、アクションボタンなどを含んだリッチな情報を表示できるようになります。

Webhook設定の流れ

Webhook設定は、Teamsの「チーム名」→「チャンネル名」→「コネクタ」→「Incoming Webhook」の順番で行い、Webhook用のURLを取得します。取得したURLは大切に保管しておき、PythonなどからPOSTを行う際に利用します。Webhook設定自体は数クリックで完了できるほどシンプルなので、一度慣れてしまえばすぐに使いこなせるでしょう。

Adaptive Card投稿のメリット

  • 視覚的に伝わりやすい
    Adaptive Cardはテキストや画像、ボタンなどを自由に配置できるため、単なるテキスト通知以上に情報をわかりやすくまとめられます。
  • 多様な用途で活用可能
    リリース情報、障害アラート、顧客サポートの進捗通知、タスク管理の更新など、あらゆる業務シーンで使えます。
  • 動的な値を簡単に埋め込める
    JSONに変数を入れておけばPythonなどで値を差し替えられるため、最新の状況を常にTeamsに配信するシステムがつくりやすいです。

PythonでJSONを生成してTeamsに投稿する基本手順

PythonからTeamsにAdaptive Cardを送る大まかな手順は以下の通りです。

  1. JSONテンプレートを用意する
    Adaptive Cardのレイアウトなどを定義したJSONをあらかじめ準備します。
  2. 変数を埋め込む
    Pythonの文字列操作やテンプレートエンジン(例:jinja2など)を用いてJSON内に動的に値を埋め込みます。
  3. ヘッダーを付与してWebhook URLにPOSTする
    requestsライブラリなどを使って、適切なHTTPヘッダーとともにWebhookにJSONを送信します。
  4. Teams上で表示を確認
    送ったJSON構造が正しければ、Teamsのチャネル上に設定した内容どおりにAdaptive Cardが表示されます。

このフローを踏むだけで基本的には問題なく表示されるはずなのですが、なぜかテキストが表示されなかったり、空欄になってしまう現象に悩まされることがあります。

よくある問題点:Adaptive CardのJSON構造とエスケープ

Adaptive CardのJSONが正しく書かれていないと、Teamsが受け取っても正しい表示を行ってくれません。特に次のような点に注意しましょう。

  • typecontentType の指定が不足している
    Teamsでは、Adaptive Cardを表現する際にattachments内でcontentType: "application/vnd.microsoft.card.adaptive"を必ず指定する必要があります。これが省略されていると、ただのJSONデータとして扱われてカードがうまく表示されない可能性が高いです。
  • Adaptive Cardのバージョンが一致していない
    "version": "1.5"などの指定を間違えると、思わぬ表示崩れやエラーが起きることがあります。記述は最新バージョンだけではなく、Teamsで対応しているバージョンかを確認してください。
  • $schema のURLなどの基本的な構造が不足している
    公式ドキュメントにあるサンプル構造を参考に、$schema"type": "AdaptiveCard" など必要な要素をきちんと揃えましょう。
  • 変数のエスケープミス
    JSONをPythonの文字列として定義する際、ダブルクォーテーションや特殊文字(\nなど)をどう扱うかでミスが起こりやすいです。また、ドル記号$を使う場面では、テンプレートエンジンとの兼ね合いで二重にエスケープが必要なケースがあります。

JSON構造のサンプル

以下のようなJSONを想定します。例として、リリースタイトルと説明文を動的に差し込みたいケースです。実際の要素やバージョンは適宜変更してください。

{
  "type": "message",
  "attachments": [
    {
      "contentType": "application/vnd.microsoft.card.adaptive",
      "content": {
        "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
        "type": "AdaptiveCard",
        "version": "1.5",
        "body": [
          {
            "type": "TextBlock",
            "text": "${releaseTitle}",
            "weight": "bolder",
            "size": "large"
          },
          {
            "type": "TextBlock",
            "text": "${description}",
            "wrap": true
          }
        ]
      }
    }
  ]
}

このとき、JSONファイルをPythonコードで読み込み、${releaseTitle}${description}の部分に任意の文字列を挿入してPOSTすると期待どおりの結果が得られます。ただし、Adaptive Cardのテンプレート記法はPower Automateなど一部サービスでの利用を想定しているため、TeamsのIncoming Webhookでは直接この記法が解釈されない場合があります。

Teamsに対しては、テンプレート変数${...}の部分を単純に置換して、最終的な文字列を完成させた状態で送る必要があります。たとえば以下のようなPythonコードをイメージするとわかりやすいでしょう。

import json
import requests

def post_adaptive_card(webhook_url, release_title, description):
    # JSONテンプレートを読み込む(あるいは文字列として定義)
    with open("adaptive_template.json", "r", encoding="utf-8") as f:
        card_template = f.read()

    # テンプレート内の変数を置換
    card_filled = card_template.replace("${releaseTitle}", release_title)
    card_filled = card_filled.replace("${description}", description)

    # 文字列をJSONにパース
    card_json = json.loads(card_filled)

    # WebhookにPOSTする
    headers = {"Content-Type": "application/json"}
    response = requests.post(webhook_url, headers=headers, json=card_json)

    if response.status_code != 200:
        print(f"Error: {response.status_code} - {response.text}")
    else:
        print("Adaptive Card posted successfully!")

上記のサンプルのように、最終的にTeamsに送信する時点で${releaseTitle}${description}が実際の文字列に置き換えられているかが非常に大切です。単にJSONをそのまま送っているとTeams側では置き換え処理をしてくれないため、空のまま表示されたり、文字列ごと消えてしまったりします。

変数展開におけるPythonとテンプレートエンジンの活用

実際の開発現場では、文字列の置換をreplace()メソッドでゴリゴリ書くよりも、jinja2やf-stringなどを使ってテンプレート処理を行うほうが保守性が高まります。

jinja2の例

jinja2を使うと、以下のようにテンプレートファイルで変数の埋め込み構文が使えます。

import json
import requests
from jinja2 import Template

def post_adaptive_card(webhook_url, release_title, description):
    with open("adaptive_template.json", "r", encoding="utf-8") as f:
        template_str = f.read()

    template = Template(template_str)
    # jinja2では {{ release_title }} のような形でテンプレートに記述する
    filled_str = template.render(release_title=release_title, description=description)

    card_json = json.loads(filled_str)

    headers = {"Content-Type": "application/json"}
    response = requests.post(webhook_url, headers=headers, json=card_json)
    if response.status_code != 200:
        print(f"Error: {response.status_code} - {response.text}")
    else:
        print("Adaptive Card posted successfully!")

この場合、テンプレートファイル(例:adaptive_template.json)を以下のように書き換えます。

{
  "type": "message",
  "attachments": [
    {
      "contentType": "application/vnd.microsoft.card.adaptive",
      "content": {
        "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
        "type": "AdaptiveCard",
        "version": "1.5",
        "body": [
          {
            "type": "TextBlock",
            "text": "{{ release_title }}",
            "weight": "bolder",
            "size": "large"
          },
          {
            "type": "TextBlock",
            "text": "{{ description }}",
            "wrap": true
          }
        ]
      }
    }
  ]
}

こうすると、テンプレート側の変数{{ release_title }}や{{ description }}にPython側で渡した引数が挿入されるため、余計な置換処理を挟まずスマートに実装できます。

f-stringの例

ごくシンプルなケースであれば、f-stringを使う方法もあります。テンプレートを単なるPython文字列として定義し、変数を埋め込んでも構いません。

card_template = f"""
{{
  "type": "message",
  "attachments": [
    {{
      "contentType": "application/vnd.microsoft.card.adaptive",
      "content": {{
        "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
        "type": "AdaptiveCard",
        "version": "1.5",
        "body": [
          {{
            "type": "TextBlock",
            "text": "{release_title}",
            "weight": "bolder",
            "size": "large"
          }},
          {{
            "type": "TextBlock",
            "text": "{description}",
            "wrap": true
          }}
        ]
      }}
    }}
  ]
}}
"""

上記のように文字列の中にPythonの変数を直接埋め込み、json.loads()で辞書に変換してPOSTします。f-stringは可読性が高く、簡単なケースでは非常に便利です。ただし、複雑なカード構造や複数箇所に同じ値を埋め込む場合は、jinja2のように専用のテンプレートエンジンを使ったほうが管理しやすいでしょう。

Adaptive Cardが正しく表示されないときのトラブルシューティング

実際に運用し始めると、表示が崩れたり、うまく表示されないケースが出てくるかもしれません。そんなときには以下の点をチェックしましょう。

1. Teamsへの投稿形式が正しいか

  • attachments[0].contentType"application/vnd.microsoft.card.adaptive"を指定しているか
  • type"message"になっているか
  • version$schemaのURLにミスがないか

このあたりのキーが誤っていると、受け取る側のTeamsがカードとして解釈してくれず、ただのテキストやエラーメッセージになってしまいます。

2. JSONが正しいフォーマットになっているか

Pythonでテンプレートに値を埋め込む際、JSON文法エラーが混入しやすいです。特に以下のような問題がありがちです。

  • ダブルクォーテーションとシングルクォーテーションの混在
  • 余計なカンマや波括弧{}の対応ミス
  • 文字列に改行が含まれていてエラーになる

送信前にjson.loads()でJSONとしてパースできるかを必ず確認する習慣をつけましょう。

3. テンプレートの変数記法を正しく使っているか

Adaptive Cardで提供されるテンプレート機能(${variable})は、Power Automateなどの機能と連動するときに特に便利ですが、TeamsのIncoming Webhookはそれを自動解釈してくれるわけではありません。Pythonでそれを都度置換してやるか、jinja2やf-stringを活用して最終的に完成したJSONを送信しないと、${...}のまま何も表示されないという結果になりがちです。

4. パラメータが正しい値を持っているか

リリースタイトルや説明文を生成するロジック側で空文字が渡されていないか、JSONとして送る前にprintデバッグなどで内容を確かめるのも大切です。Pythonで変数がNoneになっている可能性、置換処理の対象の文字列が全く合致していない可能性など、コードロジック上のミスが潜んでいるかもしれません。

Power AutomateやLogic Appsを使う方法も検討しよう

単純にWebhookへJSONを投げ込むだけでも十分に運用できますが、プロセスが複雑になってくるとPower Automate(旧Microsoft Flow)やAzure Logic Appsなどを活用すると管理しやすくなる場合があります。これらのサービスでは、GUIベースでフローを設計し、Adaptive Cardのテンプレートを活用しやすい仕組みがあります。

  • Power Automateの場合
    事前にテンプレートをアップロードし、動的コンテンツを差し込む形でAdaptive Cardを生成し、Teamsに投稿するフローを組むことが可能です。コードを書かずにフローを作れるメリットがある一方で、細かな制御が必要になるとやや工夫が必要です。
  • Logic Appsの場合
    Azureのポータルから設定を行い、継続的なワークフローをクラウド上で実行できます。TeamsへのAdaptive Card投稿を大規模なシステムの一部として組み込むのに向いています。

これらを利用するメリットとして、ノンコーディングで管理できるので保守性が高く、担当者が変わってもフローを引き継ぎやすいという点が挙げられます。一方で、細かなロジックを実装したい場合はPythonなどのコードを書くほうが柔軟性が高いかもしれません。

まとめ

Teamsに動的に生成したAdaptive Cardを投稿する際は、まずWebhookとJSONの構造を正しく把握することが重要です。変数の埋め込みがうまくいかない場合は、テンプレートエンジンの使い方や、Adaptive Cardのテンプレート機能の挙動をしっかり理解しておきましょう。また、Power AutomateやLogic Appsなどのサービスと組み合わせれば、より大規模で保守性の高いシステムを構築できます。開発者としては、コードによる管理かノーコードツールか、それぞれの長所を活かして最適な方法を選ぶのがコツです。

最終的には、「Teamsに送る最終的なJSONが正しいフォーマットで、適切に変数が埋め込まれているか」を確認すれば、多くの問題は解決されるでしょう。実運用にあたっては、デバッグログや例外処理を細かく仕込んでおくと、トラブルシュートもスムーズになります。

コメント

コメントする