当サイトにはアフィリエイト広告が含まれます。なおレビューは私の感想を書いており、内容を指示するご依頼はお断りしています

GASでDiscordボット開発奮闘記:Webhookは動いた!…でもボット本体は?

今回は、GAS(Google Apps Script)を使ってDiscordにメッセージを送信するボットの作成に挑戦した話です。LINEで実現していた定期投稿機能をDiscordでも実現したい、というモチベーションで始めました。

成功体験:Discord Webhookでメッセージ送信!

まず最初に試したのは、Discordの「Webhook」機能を使ったメッセージ送信です。DiscordのWebhookは、特定のチャンネルに外部からメッセージを「流し込む」ためのURLを提供してくれます。ボットがオンラインである必要もなく、単にURLにHTTP POSTリクエストを送るだけでメッセージが投稿される、というシンプルな仕組みです。

GASのUrlFetchApp.fetchを使えば、簡単にHTTPリクエストを送ることができます。

Discord Webhook URLの取得方法:

  1. メッセージを送りたいDiscordチャンネルを右クリックし、「チャンネルを編集」を選択します。
  2. 左側のメニューから「連携サービス(Integrations)」を選びます。
  3. 「ウェブフックを作成」をクリックし、新しいウェブフックを作成します。
  4. 作成されたウェブフックの「ウェブフックURLをコピー」をクリックしてURLを取得します。

GASでの実装(抜粋):

// DiscordのウェブフックURLを設定
// ここにDiscordからコピーしたウェブフックURLを貼り付けます
const DISCORD_WEBHOOK_URL = "ここにコピーしたウェブフックURLを貼り付ける"; 

function testWebhookMessage() {
  sendMessageByWebhook('first message from GAS Webhook!'); 
}

function sendMessageByWebhook(message) {
  const url = DISCORD_WEBHOOK_URL;
  const payload = {
    content: message,
    // username: "お掃除ボット", // 表示名を上書きしたい場合
    // avatar_url: "[https://example.com/bot_icon.png](https://example.com/bot_icon.png)" // アイコンを変えたい場合
  };

  const options = {
    method: "post",
    contentType: "application/json",
    payload: JSON.stringify(payload),
    muteHttpExceptions: true
  };

  try {
    const response = UrlFetchApp.fetch(url, options);
    Logger.log("DiscordへのWebhookメッセージ送信結果: " + response.getContentText());
    if (response.getResponseCode() !== 204) { // Webhookの成功レスポンスは通常204 No Content
      Logger.log("DiscordへのWebhookメッセージ送信エラー: " + response.getContentText());
    }
  } catch (e) {
    Logger.log("Discord Webhookメッセージ送信エラー: " + e.message);
  }
}

この方法で実装したスクリプトを実行したところ、無事にDiscordチャンネルにメッセージが投稿されました!

ただし、注意点があります。この方法で投稿されたメッセージは、「ボット自身の名前」ではなく、ウェブフック作成時に設定した名前(またはAPIリクエストで上書きした名前)とアイコンで表示されます。いわゆる「ボット本体」としての投稿とは異なります。

直面した課題:ボット本体からの投稿ができない!

Webhookでの送信は成功しましたが、目標はDiscord Developer Portalで作成した「ボット本体」としてメッセージを投稿することでした。ボットトークンを使って、より柔軟な操作ができるようにしたかったのです。

そこで、ボットトークンを使ったメッセージ送信を試みました。GASのスクリプトプロパティにボットトークンとターゲットチャンネルIDを設定し、Discord APIのエンドポイントに直接リクエストを送るコードを記述しました。

しかし、このコードを実行すると、{"message": "internal network error", "code": 40333}というエラーが返ってきてしまいました。

AIに相談しながら、様々な原因を検証しました。

  • DISCORD_BOT_TOKENDISCORD_TARGET_CHANNEL_IDの設定確認。
  • Discord Developer Portalでのボットの権限設定(Send MessagesView Channelsなど)確認。
  • 特に重要だったのが「MESSAGE CONTENT INTENT」のON/OFFの確認。画像で示してくれたように、最初はOFFになっていたためONにしましたが、残念ながらエラーは解消されませんでした。

なぜボット本体が動かせなかったのか?:GASの制約

最終的に判明したのは、今回のエラーが「認証が通らない」というよりは、GASの性質とDiscordボットの仕組みの根本的な違いによるものだ、ということでした。

Discordの本格的なボットは、DiscordのAPIとの間に「WebSocket接続」という常時接続を維持する必要があるのです。これは、ボットがリアルタイムでユーザーのメッセージやリアクションなどのイベントを受け取り、それに応答するために不可欠な仕組みです。ボットトークンは、この常時接続を確立するための認証情報として機能します。

しかし、Google Apps Scriptは、基本的にHTTPリクエストを処理する「サーバーレス」な環境です。スクリプトは、外部からのHTTPリクエスト(Webhookなど)が来た時や、タイマーで指定された時にだけ起動し、処理が終われば終了します。常時接続を維持するような設計にはなっていないため、Discordボットがオンライン状態を保ち続けることが困難なのです。

私たちが直面した40333 internal network errorは、ボットトークン自体の認証が失敗したというよりは、ボットがオンライン状態(WebSocket接続が確立されている状態)でなかったために、メッセージ送信のリクエストが正常に処理されなかった、という可能性が高いという結論に至りました。

Webhookが動いたのは、Webhookが単なるHTTP POSTリクエストで完結するため、常時接続が不要だったためです。

今後の展望

今回の挑戦で、GASを使ってDiscordにメッセージを送ることはWebhookを使えば可能であることが分かりました。しかし、「Discordボット本体」としてユーザーからのメッセージを受け取って応答するような、よりインタラクティブな機能を実現するには、GASだけでは限界があることが明らかになりました。

今後は、以下のいずれかの方法を検討していくことになりそうです。

  1. Discordのスラッシュコマンドを使う: GASのWebアプリ機能を利用し、ユーザーがDiscordでスラッシュコマンド(例:/chat こんにちは)を打った際に、そのイベントをGASで受け取り処理する方法です。これなら常時接続は不要ですが、Discordからのリクエスト署名検証など、追加の実装が必要になります。
  2. Node.jsやPythonなど、他のプログラミング言語でボットを開発する: Discordボット開発の主流な方法です。これらの言語にはDiscord APIを扱うためのライブラリが豊富にあり、HerokuやVercel、Railwayといった常時稼働が可能なプラットフォームにデプロイすることで、本格的なDiscordボットを運用できます。ただし、GASで書いたロジック(スプレッドシート連携など)を別の言語で書き直す手間が発生します。

今回の経験は、GASの得意分野と限界を学ぶ良い機会となりました。次のステップで、Discordでの目標を実現できるよう、引き続き頑張っていきたいと思います!