[AITuberの作り方] YouTubeとTwitchで視聴者からのコメントを取得する方法
こんにちは、私は普段AITuber OnAirというAITuber配信のためのWebアプリを日々開発しています。
最近AITuber OnAirでは新機能として、Twitch配信でのコメント取得機能を新たにサポートしました。これによりTwitchでの配信についてもサポートしたことになります。
今回の記事では今回の開発で学んだことを、私と同じようにAITuberを開発されている方向けに還元できたらと思い、Twitchでのコメント取得方法、それからYouTubeでのコメント取得方法についても記載していきます。
私と同じようにAITuberを開発されている方の参考になれば幸いです。
1. YouTube Live のコメント取得
まずはYouTube Live中のコメント取得方法についてです。
処理の流れ
YouTube Liveでのチャット内容を取得する際の流れは以下のようになります。
[Live Video ID]
│ videos?part=liveStreamingDetails
▼
[activeLiveChatId]
│ liveChat/messages?part=snippet,authorDetails → nextPageToken
▼
[Chat Messages]
このように2つのエンドポイントを利用してコメントを取得する流れとなります。
-
Live Video ID … URL の
v=
パラメータなどで取得 -
activeLiveChatId …
videos
エンドポイントのliveStreamingDetails
に含まれる -
liveChat/messages …
nextPageToken
を使ってループ [1] [2]
YouTube API KeyとLive IDの取得方法
コメントを取得するにはこれら2つのキーを取得する必要があります。それぞれの取得方法については以下のnote記事内で紹介しているので併せてご覧ください。
YouTubeライブでのコメント取得サンプルコード
// youtube-chat.ts
import 'dotenv/config';
import fetch from 'node-fetch';
const API_KEY = process.env.YT_API_KEY!; // Data API v3 キー
const VIDEO_ID = process.env.YT_VIDEO_ID!; // 配信中の動画 ID
/** 配信中かつチャット有効なら activeLiveChatId を返す */
async function getLiveChatId(videoId: string) {
const url =
`https://youtube.googleapis.com/youtube/v3/videos` +
`?part=liveStreamingDetails&id=${videoId}&key=${API_KEY}`;
const res = await fetch(url);
const json = await res.json();
return json.items?.[0]?.liveStreamingDetails?.activeLiveChatId ?? null;
}
/** チャットを永続ポーリングしてコンソールに流す */
async function pollChat(liveChatId: string, pageToken = '') {
const url =
`https://youtube.googleapis.com/youtube/v3/liveChat/messages` +
`?liveChatId=${liveChatId}&part=snippet,authorDetails&key=${API_KEY}` +
(pageToken ? `&pageToken=${pageToken}` : '');
const res = await fetch(url);
const json = await res.json();
// メッセージを整形して出力
for (const m of json.items ?? []) {
const author = m.authorDetails.displayName;
const text = m.snippet.textMessageDetails?.messageText ?? '';
if (text) console.log(`[YT] ${author}: ${text}`);
}
// 1〜2 秒待って再帰呼び出し(API 推奨間隔 ≧ 1 s)
const waitMs = json.pollingIntervalMillis ?? 1000;
await new Promise(r => setTimeout(r, waitMs));
await pollChat(liveChatId, json.nextPageToken);
}
(async () => {
const id = await getLiveChatId(VIDEO_ID);
if (!id) { console.error('配信が見つからない、またはチャット無効'); return; }
await pollChat(id);
})();
実行方法
export YT_API_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxx
export YT_VIDEO_ID=YOUR_LIVE_VIDEO_ID
node youtube-chat.ts
2. Twitch のコメント取得(EventSub WebSocket 版)
Twitchのコメント取得はIRCではなく、EventSub
実は最初にコメント取得方法を調べた際にIRCを用いたコメント取得方法が出てきました。
IRCを使うのはシブいと思いましたが、よくよく調べてみたところ、2024年以降、Twitch は チャット読み取り用途に IRC ではなく EventSub の channel.chat.message
を推奨しています。
OAuth トークンに user:read:chat (旧 chat:read)
スコープを付ければ、WebSocket 1本で購読できます。なお session_reconnect イベントを受け取ったら自分でソケットを張り替える必要があります。 [3], [4]
処理の流れ(Twitch)
[User Access Token] + [Channel Login]
│ ① GET /users?login=<channel> (Helix Users API)
▼
[broadcaster_user_id]
│ ② GET /oauth2/validate (Validate API)
▼
[user_id (token owner)]
│ ③ Connect wss://eventsub.wss.twitch.tv/ws
▼ (session_welcome)
[session_id]
│ ④ POST /eventsub/subscriptions
│ type=channel.chat.message
▼
[WebSocket PUSH] (notification)
│
[Chat Messages]
参考:
Helix Users API(Twitch API Reference)
Validating Tokens
トークン取得手順
Twitchではコメントを取得するためにDeveloper Console上でアプリケーション登録をしたうえでClient IDを取得する必要があります。
そちらの取得手順については以下のnoteに記載していますので、そちらをご参照ください。
Twitch 側の「クライアントタイプ」は Public(公開) を想定しています
AITuber OnAir はブラウザだけで完結する SPA(Single-Page Application)のため、クライアントシークレットを安全に保持できません。Twitch 公式ドキュメントでも サーバーを持たないクライアントサイド JavaScript アプリは Implicit Grant Flow か Device Code Flowを用い、Public クライアントとして登録する ことが推奨されています。[3:1]
- Public クライアントの特徴
ちなみに、Getting OAuth Access Tokensでは、Implicit Grant Flow をはじめ、SPA 向けに使える 4 つのフローを公式が比較しています。Public クライアントでどのフローを選ぶべきか判断する際の一次情報として重宝するので、併せてご覧になってみてください。
Twitch配信中のコメントを取得するサンプルコード
import 'dotenv/config';
import fetch from 'node-fetch';
import WebSocket from 'ws';
const TOKEN = process.env.TWITCH_TOKEN!; // user access (user:read:chat)
const CLIENT_ID = process.env.TWITCH_CLIENT_ID!; // アプリのクライアント ID
const CHANNEL = process.env.TWITCH_CHANNEL!; // 配信者 login 名
(async () => {
/* 1) broadcaster_user_id を取得 */
const uRes = await fetch(
`https://api.twitch.tv/helix/users?login=${encodeURIComponent(CHANNEL)}`,
{ headers: { Authorization: `Bearer ${TOKEN}`, 'Client-Id': CLIENT_ID } }
);
const broadcasterId = (await uRes.json()).data?.[0]?.id;
if (!broadcasterId) throw new Error('invalid channel');
/* 2) 自分の user_id (token owner) を取得 */
const vRes = await fetch('https://id.twitch.tv/oauth2/validate', {
headers: { Authorization: `OAuth ${TOKEN}` }
});
const userId = (await vRes.json()).user_id;
/* 3) WebSocket へ接続 */
const ws = new WebSocket('wss://eventsub.wss.twitch.tv/ws');
ws.on('open', () => console.log('🟢 connected'));
ws.on('close', () => console.log('🔴 closed'));
ws.on('message', async raw => {
const msg = JSON.parse(raw.toString());
const type = msg.metadata?.message_type;
if (type === 'session_welcome') {
// 4) 接続直後にサブスクライブ
const sessionId = msg.payload.session.id;
await fetch('https://api.twitch.tv/helix/eventsub/subscriptions', {
method : 'POST',
headers: {
Authorization : `Bearer ${TOKEN}`,
'Client-Id' : CLIENT_ID,
'Content-Type': 'application/json'
},
body: JSON.stringify({
type : 'channel.chat.message',
version : '1',
condition : { broadcaster_user_id: broadcasterId, user_id: userId },
transport: { method: 'websocket', session_id: sessionId }
})
});
return;
}
if (type === 'notification'
&& msg.payload.subscription.type === 'channel.chat.message') {
const txt = msg.payload.event.message.text;
const usr = msg.payload.event.chatter_user_name;
console.log(`[TW] ${usr}: ${txt}`);
}
if (type === 'session_reconnect') {
// 5) 再接続 URL 指定時はワンライナーで乗り換え
ws.removeAllListeners(); // 古いハンドラ破棄
const newWs = new WebSocket(msg.payload.session.reconnect_url);
newWs.on('message', ws.listeners('message')[0]!);
}
});
})();
実行方法
export TWITCH_TOKEN=oauth_xxxxxxxxxxxxxxxxxxxx
export TWITCH_CLIENT_ID=yyyyyyyyyyyyyyyyyyyyy
export TWITCH_CHANNEL=mychannel
node twitch-chat.ts
まとめ
-
YouTube は
videos ➜ liveChat/messages
の二段クエリと定期ポーリング。 -
Twitch は EventSub WebSocket 一択で、
channel.chat.message
へ Subscribe すると PUSH で受信。 - どちらも 100 行未満のスクリプトでリアルタイム取得が可能。
このサンプルを起点に、バッファリングや NG ワードフィルター、AI要約などを組み込めば 独自 AITuber のコメントワークフローをすぐに構築できます。
今回の記事が参考になれば幸いです!
Discussion