CastCraftのWebhookの正体を探ってみた【Next.jsでAPI構築＆MongoDB】

# CastCraftってなに
CastCraftは、YouTube/Twitchなどでの配信をサポートするアプリケーションです。例えばコメントの管理、コメントの読み上げ、オーバーレイ、映像エフェクトなど配信を魅力的にできる機能が盛りだくさんです。

https://castcraft.live/

私もYoutubeでゲーム配信をしており、お世話になることが多いアプリケーションです。めちゃくちゃ助かってます。

# CastCraftのWebhook機能
この間設定をちらほら見ていると、超マイナーな欄に**Webhook(Experimental)**と書かれた欄がありました。Webhookとは、あるWebアプリケーションで特定のイベントが発生した際に、他のWebアプリケーションへ自動的に通知を送る仕組みですが、「**指定されたWebhook URLに対してチャットデータをJSON形式でPOSTします**」とだけ書かれており、どのhookで送信されるものなのか明確に書いていません。詳細な情報が載ってるドキュメントを探しましたが、どこにも見つからず...

![{ADE067C4-09B9-4572-AB3F-6846889ED91F}.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3092163/4123092a-962f-4b2a-82cf-4fc914f17571.png)

というわけで、hookを受け取るAPIを作成し、NoSQLに格納してデータの中身を見てみることにしました。

# API構築における概要
今回はhookを受け取るだけの簡単なものなのでスピード重視でやります。
とりあえずAPIを作ってみたい初心者の方におすすめです。

### 技術スタック
* Next.js (v14。 AppRouter)
* TypeScript
* MongoDB Atlas

# API開発
### MongoDBまわり
Mongodbをインストールし、
```terminal:terminal
$ npm i mongodb
```

clientインスタンスを作ります

```typescript:client.ts
import { MongoClient, ServerApiVersion } from "mongodb";

export default class MongoDBClient extends MongoClient {
    private db_name: string;

    constructor(db_name: string, uri: string) {
        super(uri,
            {   
                serverApi: ServerApiVersion.v1,
            }
        );
        this.db_name = db_name;
    }

    collection(collection_name: string) {
        return this.db(this.db_name).collection(collection_name);
    }
}
```

MongoDB Atlas側でデータベースNameとURIを控えておきましょう
```.env
MONGODB_NAME = "*****"
MONGODB_URI = "mongodb+srv://*****:[db_password]@..."
```

### API本体
`/app/api`に、`route.ts`を置き、`POST`ルートを作ります。
さっき作った`client`もインポートしてつかいます

```typescript:route.ts
import MongoDBClient from "clients/mongodb";

const mongodb = new MongoDBClient(
    process.env.MONGODB_DB_NAME || '',
    process.env.MONGODB_URI || ''
);

export async function POST(req: Request) {
    try {
        const request = await req.json();
        const collection = mongodb.collection('collection-name');
        const result = await collection.insertOne(request);
        
        const response = {
            message: 'Webhook received successfully',
            request: request,
            response: result
        };
        return Response.json(response);
    } catch (error) {
        return Response.json({ error: String(error) }, { status: 500 });
    }
}
```

とりあえずこれで、任意の`JSON`形式のリクエストに対して、`JSON`を指定のMongoDB Clusterに`insert`するAPIがかけました！

# 検証結果
これをVercelでデプロイし、さっきのWebhook送信先リンクにURLを貼り付けて配信してみました

レスポンスからおおまかに以下のようなリクエストデータが得られました。
どうやらチャットを取得することにhookが送信されるようです。
```typescript
type response = {
    videoId: string,
    chatMessages: {
        chatMessageId: string,
        platform: string,
        youtubeExtension: Object[], // 深すぎるので省略
        displayMessage: string,
        displayName: string,
        photoUrl: string,
        platformAudienceId: string
        createdAt: {
            seconds: number,
            nanoseconds: number
        }
    }[]
}
```

それぞれ意味をまとめるとこうです
```typescript
type response = {
    videoId: "配信のvideoId",
    chatMessages: {
        chatMessageId: "チャットメッセージのId",
        platform: "youtubeExtension", // ?
        youtubeExtension: Object[], // 深すぎるので省略
        displayMessage: "チャットメッセージのテキスト",
        displayName: "チャットした人のアカウント名テキスト",
        photoUrl: "チャットした人のアイコンソース",
        platformAudienceId: "?"
        createdAt: {
            seconds: number,
            nanoseconds: number
        } // チャットされた時間
    }[] // ロードされたときに取得したコメント一覧
}

```


# まとめ
今回はCastcraftのマイナーな機能の実態について探ってみました。
実際のところ、Youtubeのライブチャット取得には`Youtube Data API v3`でも可能ですが、リアルタイム取得では短期間に数回のリクエストを送らなければ取得できません。
これにはネックな点があり、1日の取得回数に上限があります。上限解放もできますが長期間にわたってYoutubeと申請を送り合う必要があり、かなり面倒です。
<br>
今回発見したこのwebhook機能は、その問題を解決するに至りました。
<br>
これだけのデータがとれていれば、自分の配信に限られてしまいますが、チャットリアルタイム取得をノーコストで実現できる可能性があります。
<br>
次の記事では、Youtube Data APIなしでライブチャットをリアルタイム取得してみようと考えてます。