今回は、GAS(Google Apps Script)を使ってDiscordにメッセージを送信するボットの作成に挑戦した話です。LINEで実現していた定期投稿機能をDiscordでも実現したい、というモチベーションで始めました。
成功体験:Discord Webhookでメッセージ送信!
まず最初に試したのは、Discordの「Webhook」機能を使ったメッセージ送信です。DiscordのWebhookは、特定のチャンネルに外部からメッセージを「流し込む」ためのURLを提供してくれます。ボットがオンラインである必要もなく、単にURLにHTTP POSTリクエストを送るだけでメッセージが投稿される、というシンプルな仕組みです。
GASのUrlFetchApp.fetchを使えば、簡単にHTTPリクエストを送ることができます。
Discord Webhook URLの取得方法:
- メッセージを送りたいDiscordチャンネルを右クリックし、「チャンネルを編集」を選択します。
- 左側のメニューから「連携サービス(Integrations)」を選びます。
- 「ウェブフックを作成」をクリックし、新しいウェブフックを作成します。
- 作成されたウェブフックの「ウェブフック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_TOKENやDISCORD_TARGET_CHANNEL_IDの設定確認。- Discord Developer Portalでのボットの権限設定(
Send Messages、View 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だけでは限界があることが明らかになりました。
今後は、以下のいずれかの方法を検討していくことになりそうです。
- Discordのスラッシュコマンドを使う: GASのWebアプリ機能を利用し、ユーザーがDiscordでスラッシュコマンド(例:
/chat こんにちは)を打った際に、そのイベントをGASで受け取り処理する方法です。これなら常時接続は不要ですが、Discordからのリクエスト署名検証など、追加の実装が必要になります。 - Node.jsやPythonなど、他のプログラミング言語でボットを開発する: Discordボット開発の主流な方法です。これらの言語にはDiscord APIを扱うためのライブラリが豊富にあり、HerokuやVercel、Railwayといった常時稼働が可能なプラットフォームにデプロイすることで、本格的なDiscordボットを運用できます。ただし、GASで書いたロジック(スプレッドシート連携など)を別の言語で書き直す手間が発生します。
今回の経験は、GASの得意分野と限界を学ぶ良い機会となりました。次のステップで、Discordでの目標を実現できるよう、引き続き頑張っていきたいと思います!