Twitchで特定の配信者が配信を開始したら自動でコメントしたい

# はじめに
Twitchで推しが配信を開始したのに見に行けなくてもどかしいこと、ありますよね？
見に行ってコメントを残したかったのに〜ということ、ありますよね？

そんな時に、推しが配信を開始したら自動でコメントできると便利だな() ということで作ってみました。

# 配信開始の検出
## 方法
### Twitch EventSubを利用する方法
Twitchが提供するイベントサブスクリプション機能「EventSub」を使う方法です。これにより、配信開始イベントをリアルタイムで受信できます。

### Twitch Helix APIをポーリングする方法
TwitchのHelix APIを定期的に呼び出して配信状況を確認する方法です。
注意点:
ポーリング間隔を短くするとTwitch APIのレート制限に引っかかる可能性があるため注意が必要です（通常1分程度の間隔が推奨）。

配信開始ごとにリアルタイムで1度だけコメントをしたいので、今回は前者のTwitch EventSubを利用する方法で進めます。

## 具体的な手順
偉大な先人がまとめてくれているのでそれを参考にしましょう。

https://qiita.com/azumag/items/4575c8147585c329fdf5
https://qiita.com/syakesalmon/items/dabb90a3bf66fb548c81

公式ドキュメント
https://dev.twitch.tv/docs/eventsub/

# コメント方法
## Twitch IRCの利用
今回はコメント先の配信者が決まっているので、Twitch IRCの仕組みを使うだけで簡易に実現可能です。
https://tmijs.com/#guide

### コメント権限・トークンの取得
コメントしたいアカウントの、コメント権限を取得します。
「配信開始の検出」の際に作成したTwitchアプリの`client_id`を用いて、同様の方法でトークンを取得します。
今回はコメントをしたいので、scopeに`chat:edit`と`chat:read`を指定します。

認証URLの例
```
https://id.twitch.tv/oauth2/authorize?client_id={client_id}&redirect_uri=http://localhost:3000&response_type=token&scope=chat:edit+chat:read
```

トークンの権限の種類については以下を参照ください
https://dev.twitch.tv/docs/authentication/scopes/#chat-and-pubsub-scopes

### Twitch IRCを利用したコメント送信
Cloud Functionsを利用する場合の実装例が以下になります。
```
const tmi = require('tmi.js');
const express = require('express');
const bodyParser = require('body-parser');

const app = express();
app.use(bodyParser.json());

// Twitch Bot設定
const TWITCH_BOT_USERNAME = 'your_user_name'; // コメント送信元のアカウント名
const TWITCH_OAUTH_TOKEN = 'oauth:your_oauth_token'; // OAuthトークン
const TARGET_CHANNELS = ["target_channel_name"]; // 配信者のチャンネル名
const COMMENT_CONTENT = "こんにちは！"; // コメントしたい内容

// Botクライアント設定
const client = new tmi.Client({
  identity: {
    username: TWITCH_BOT_USERNAME,
    password: TWITCH_OAUTH_TOKEN,
  },
  channels: TARGET_CHANNELS.concat([]), // 配列の要素の接頭に#が付与されるので複製して使用する
});

async function sendComment(broadcaster_user_login) {
  if ( TARGET_CHANNELS.includes(broadcaster_user_login) ) {
    // コメント送信
    try {
      await client.connect();
      await client.say(broadcaster_user_login, COMMENT_CONTENT);
      console.log('Comment sent successfully');
      } catch (error) {
      console.error('Error sending comment:', error);
      } finally {
      client.disconnect();
    }
  }
}

// 配信開始イベントのハンドリング
app.post('/', async (req, res) => {
  // Twitchからの検証リクエストを処理
  if (req.headers['twitch-eventsub-message-type'] === 'webhook_callback_verification') {
    res.status(200).send(req.body.challenge);
    console.log('Webhook verified');
    return;
  }

  // 配信開始イベントを処理
  if (
    req.headers['twitch-eventsub-message-type'] === 'notification' &&
    req.body.subscription.type === 'stream.online'
  ) {
    console.log('Stream started for channel:', req.body.event.broadcaster_user_login);
  //     {
  //         "subscription": {
  //             "id": "abcdefg123456",
  //             "type": "stream.online",
  //             ...
  //         },
  //         "event": {
  //             "broadcaster_user_id": "123456789",
  //             "broadcaster_user_login": "streamer_name", ← target_channel_name
  //             "broadcaster_user_name": "StreamerName",
  //             "started_at": "2024-12-11T08:00:00Z"
  //         }
  //      }
  //
    await sendComment(req.body.event.broadcaster_user_login);
    res.status(200).send('OK');
  } else {
    res.status(200).send('Unhandled event');
  }
});

exports.twitchStreamStartHandler = app;
```

# 終わりに
本当は自動で投げ銭までしたかったけど、IRC経由のコメントでは投げ銭が不可、その他APIを利用しても不可能とのことで残念でした。