数多くいる推しVtuberの配信を自動収集する

# 前置き

唐突ですが、みなさん推しのVTuberはいますか？
そして推しのVを追いかけようにも、たくさんいて配信を見逃してしまうなんてことはありませんか？

そういった難民を減らすため、大手の事務所には配信情報の専用ページがあります。
有名どころでは[ここ](https://schedule.hololive.tv)などでしょう。

では個人Vを追いかけたいときはどうするか？チャンネル登録と通知の組み合わせが自然だろうけど、その数が増えると通知に埋もれてしまって追いかけられない……。あるいは他のVも追いかけたくなるけど、情報が手に入らない……。

そう個人的にも関わりが深い将棋系Vもそういった悩みを配信者側も視聴者側も抱えていました。

それなら配信情報のまとめサイトを作ろう！

この記事はそこから始まる物語。

なおこの記事を応用することで、個人レベルでも専用の情報収集プログラムなどができます。

# まずは宣伝

この記事のタイトルを見て将棋系Vに少しでも関心を持たれた方、あるいは完成版を見たい方は次のリンクをポチッとしてください。

[将棋V総合情報サイト](https://v-shogi.site/index.html)

# 本題

このサイトは大雑把に以下の構成でできています。

1. HPに掲載希望するVを募集
1. YouTube Data APIで1時間毎にライブ配信予定の情報を取得しリスト化する
1. リストファイルを元として、5分ごとにライブ配信中かどうかを確認する

ということで、まずは情報収集するプログラムをJavaScriptで書いてしまいましょう。

## JavaScript部

ライブ状態の取得には[こちら](https://coolish.hatenablog.com/entry/2019/01/24/040125)を参考にさせていただきました。

実行環境は`Node v17.0.1`になります。

情報は2021年12月～2022年1月。

またレンタルサーバーは[Conoha](https://www.conoha.jp/conoha/)を利用しています。

~~そういえばキャラの「美雲このは」ちゃん可愛いよね。~~

```liveCollect.js
"use strict";

require('date-utils');
const schedule = require('node-schedule');
const fs = require('fs');
const fetch = require('cross-fetch');
const { type } = require('os');

// APIキー取得＆代入
const jsonKeys = <YOUR KEY with JSON FILE>;
const key = jsonKeys.collectKeys;

// 定期的に実行
schedule.scheduleJob('51 * * * *', () => {collectingUpcomingLive(key)});

// ブレイクタイム1秒
function waitTimeDiff(time){
    const waitTime = 1000;
    while(true) {
        const nowTime = Date.now();
        if (nowTime - time > waitTime){
            return;
        }
    }
}

// 同期的にデータ取得
async function collectingUpcomingLive(key){
    let videoIDs = [];
    let videoInfo = [];
    // JSONでメンバーデータ読み込み
    const jsonMembers = JSON.parse(fs.readFileSync('./members.json', 'utf-8'));
    for (let i = 0; i < Object.keys(jsonMembers["member"]).length; i++){
        // 投げるurl指定
        const youtubeURL = jsonMembers.member[i].youtube;
        // アドレスがあるかをチェック
        if (youtubeURL === undefined || youtubeURL === ""){
            continue;
        }
        const urlUpcoming = `https://www.googleapis.com/youtube/v3/search?part=snippet&type=video&channelId=${youtubeURL}&eventType=upcoming&maxResults=5&order=date&key=${key}`;
        await fetch(urlUpcoming).then((responseVideos) => {
            if (!responseVideos.ok) {
                console.log(`HTTP Error! Status: ${responseVideos.status} Part: ${jsonMembers.member[i].name}`);
            } else {
                // returnしないと情報を取りに行かない
                return responseVideos.json();
            }
        }).then(async (data) => {
            const items = data.items;
            for (let j = 0; j < items.length; j++){
                const videoId = items[j].id.videoId;
                videoIDs.push(videoId);
                const urlVideoInfo = `https://www.googleapis.com/youtube/v3/videos?part=snippet%2CliveStreamingDetails&id=${videoId}&key=${key}`;
                await fetch(urlVideoInfo).then((responseVideo) => {
                    if (!responseVideo.ok){
                        console.log(`HTTP Error! Status: ${responseVideo.status} Part: ${jsonMembers.member[i].name} at ${videoId}`);
                    } else {
                        // returnしないと情報を取りに行かない
                        return responseVideo.json();
                    }
                }).then((responseVideo) => {
                    const dataVideo = responseVideo.items[0];
                    const snippet = dataVideo.snippet;
                    const videoThumbnailURL = dataVideo.snippet.thumbnails.high.url;
                    const streamStartTime = dataVideo.liveStreamingDetails.scheduledStartTime;
                    const streamDate = new Date(streamStartTime).toLocaleString("ja");
                    if (Date.parse(streamDate) < Date.now().valueOf()){
                        return;
                    }
                    const showTime = new Date(streamDate).toFormat('YYYY年M月D日 HH24時MI分');
                    // 時刻の数値化
                    const streamingPage = "https://www.youtube.com/watch?v=" + videoId;
                    videoInfo.push({"streamer": snippet.channelTitle, "title": snippet.title, "streamID": videoId, "startAtDateStyle": streamDate, "startAt": showTime, "streamPage": streamingPage, "thumbnailLink": videoThumbnailURL});
                }).then(() => {
                    // クールタイムを設ける
                    waitTimeDiff(Date.now());
                });
            }
        }).then(() => {
            // クールタイムを設ける
            waitTimeDiff(Date.now());
        });
    }

    // sorting
    videoInfo.sort(function(a, b) {
        return (a.startAt < b.startAt) ? 1 : -1;
    });

    const writeJsonVideosInfo = JSON.stringify(videoInfo);
    fs.writeFileSync(<Target Directory>, writeJsonVideosInfo);
}
```

定期実行の時刻が毎時51分なのは、別に走っている5分ごとに情報取得するプログラムとのバッティングを避けるためです。
必要に応じて変更してください。

またメンバー情報は同じディレクトリの`members.json`に格納されています。

なおこの記事を書いている当人はJavaScriptに関してほぼ素人に近いので~~（非同期処理にめっちゃ苦労して嫌気がさした）~~、その点は優しく見守っていただきたいです。いろいろコメントお待ちしています。

## YouTube Data APIの割り当てQuotaの増加

この記事のメインはどちらかというとこのお話。YouTube Data APIを使う場合、そのリソースは10 000クオータ/日しかありません。なのに動画検索にはなんと100クオータ/回も使ってしまうのです。

追いかける人数が数人程度ならどうにかやりくりできそうですが、数十人を超えるレベルになると絶対的に足りません。てことで、Quotaの増加を申請することにしました。が、そこでのやりとりに苦労したので、この記事がQuota増加の申請をする人の参考になれば幸いです。

### やってはいけないこと

同じ種類だが複数の異なるAPIキーを使うことは**規約違反**です。考える人がいるかもしれないので、先に注意をしておきます。

~~天下のGoogleはあなたの行動をきっちり見ていますよ。~~

## 何をすべきか

1ヶ月ほど開発陣とやりとりして、申請を通すために大事なのは特に以下のようなところでした。

1. 日本語申請ページもあるが、**返答は英語**なので最初から英語で書くべし。
1. 申請時に添付するファイルは、**動画と概略図の二本立て**にしよう。
1. 表に見える形で提供（今回のようにHPを使うなど）する場合は、注意書きや**規約へのリンク**を確実に張るようにしよう。
1. **ロゴ**を使うときもルールがあるのできっちり守ること。

上から順に理由などを述べていこう。

* 日本語で送っても結局は翻訳ツールを使ってのやりとりになるので、誤解を招きたくないならできるだけこちらの意図した文面で送る方が無難である。
* ここが一番の難関。サーバーサイドなので表面的にはAPIの挙動が見えない、でも使ってることは確か。だけどどうやって示すか？

ということで、最終的に話を通すためにしたことは次の二点である。

1. 「`node`で実際に動かしているところ＋取得データを`cat`で表示する」までを動画にする。
1. スプレッドシートで良いので、一枚の概略図描く。このとき、どの処理がサーバー側で行われ、どの段階でAPIを利用し、どのようなデータを取得して、どう処理するか、までを順序立てて描く。最後にこの流れ図をPDF化する。

* 注意書きの状態なども当然ながら厳密に指摘される。トップページを見ていただいたら分かることも多いが、以下の点をきっちりおさえよう。

1. YouTube APIを利用していることの明記
1. YouTube API規約へのリンク
1. Googleプライバシーポリシーへのリンク
1. どのようにAPIが利用されるか？（対象に**される**側の明記
1. 収集データに関して外部利用の有無
1. 第三者への広告などに利用するかの有無
1. 訪問者のデータを利用するかどうか

* ロゴの利用方法については[このページ](https://www.youtube.com/howyoutubeworks/resources/brand-resources/#logos-icons-and-colors)を見て調整するのが一番早いです。

# 最後に

実はAPIのやりとりだけで1ヶ月くらいかかり、途中では意思疎通がうまくいかずに~~喧嘩一歩手前の高等な煽り合いかと~~苦労しました。

レビューに入る前に上記の指摘事項はPDFでまとめられるので、その通知が来れば状況はかなり進んでいるといえる。

そして審査そのものは2日ほどで結論がやってくる。

もしトライするならば諦めずにやり抜いてほしい。そして快適な視聴者ライフを送ってほしい。

あと再三になるが、将棋Vの応援もしてほしい。

[将棋V総合情報サイト](https://v-shogi.site/index.html)