技術メモ

プログラミングとか電子工作とか

YouTubeのSuperChatをGoogle Spredsheetに読み込む(SuperChatEvents: list使用編)

YouTube Live Streaming APIYouTube Data API v3)を利用してスーパーチャット情報をGoogle Spredsheetに出力してみました。
が、結論から言えば実用性が無かったので供養エントリです。なんでダメだったか知りたい人など対象です。
(あとOAuthの勉強の題材探してる人とか)

内容を要約すれば

  • SuperChatEvents: listを利用してGoogle Spredsheetにデータを抽出する方針、実際にココまではできた
  • 上記APIOAuth認証で承認したアカウントのスーパーチャットを抽出できる(第三者のスーパーチャットは取得できない)
  • 最新50件のみ取得可 ← コレが問題

という感じです。
実用的なのは検証中(クォータ割り当て上限の引き上げは現在やりとり中・・・)

前提

HTMLのパースをしないワケ

結構スーパーチャットを抽出するツールは有志の方々によって開発されています。
ただし、大体YouTubeのチャット欄のHTMLをパースして抽出する方針のものが散見されます。
(これはおそらくなのですが、どっかのバージョンアップまでチャット情報を取得するAPIが無かったのにも起因している可能性はあります)
僕個人としてはYouTube側がしっかりしたツールを用意していない方が問題だと思っているのでいちいち指摘したくないんですが、これはYouTubeの規約に抵触する可能性があります。

利用規約に以下の行為は禁止するという下記のような記載があります。

本サービスの利用には制限があり、以下の行為が禁止されています。
[省略]
3. 自動化された手段(ロボット、ボットネット、スクレーパなど)を使用して本サービスにアクセスすること。ただし、(a)公開されている検索エンジンYouTuberobots.txt ファイルに従って使用する場合、または(b)YouTube が事前に書面で許可している場合を除きます。

www.youtube.com

じゃあ実行タイミングが手動ならええんか?という規約の穴を突くような発想もしたくなりますが、意図としては「負荷増えるからAPI以外でのスクレイピングすんな」だと思うので

の3択が考えられます。
2番目は過去に盥回しにされた人を観測していた経験があり、3番目はアレなので1番目の方針で考えましょう。

なんでスプレッドシート+JS?

非エンジニアの方でも比較的使いやすいスプレッドーシートの拡張機能として実装した方が役に立つかなぁと思った次第です。
TypeScriptに脳を侵された人間なので純JavaScriptみたいな言語は非常に辛いのですが、この規模ならどんな言語でもヨシとします。

実装

流れ

  1. OAuth認証の前準備をしておく(一番最初に書くけど別タイミングにする)
  2. スプレッドシートを作成する
  3. スクリプトを追加する
  4. スクリプトを書く
  5. OAuth認証を行う
  6. 実行する

OAuth認証の前準備をしておく

基本的にこの辺を参照して下さい。もしかしたら古くなってるかも。

www.tech-note.info

ただし、上記の準備では不十分です。不十分な部分は後ほど解説します。

スプレッドシートを作成する

とりあえずスプレッドシートを作成しないとなにも始まらないので作成します。
スプレッドシートのコンソールから新規作成します。

https://docs.google.com/spreadsheets/?usp=mkt_sheets

とりあえず「無題のスプレッドシート」という箇所に何かファイル名を入れておいて保存しておきます。
名前を適当に入れたら勝手に保存されます。

f:id:ysmn_deus:20201019115600p:plain

f:id:ysmn_deus:20201019115904p:plain

スクリプトを追加する

「ツール」→「スクリプトエディタ」を選択するとスプレッドシートに紐付いたスクリプトが作成されます。

f:id:ysmn_deus:20201019120201p:plain

先ほどと同様に適当に名前は付けておきましょう。

スクリプトを書く

main.gstoken.gsの二種類に分けてますが、1ファイルでも別に動くと思います。

f:id:ysmn_deus:20201019130233p:plain

ファイルを追加するには、左上の「ファイル」→「New」→「スクリプトファイル」で名前を適当に入れます。
main.gsを追加したい場合は「Enter new file name」と書かれた箇所にmainと入れると作成されます。

main.gs

token.gs

OAuth認証を行う

OAuth認証の準備をする(続編)

OAuth認証の前準備をしておくの手順では不十分な箇所をここに来てようやくすすめて行きます。
token.gsにはdoGetという関数が含まれているのですが、これはOAuth認証のリダイレクトで飛ばされた先のウェブアプリケーションになっています。
('ω')。o(????????????)という人は、とりあえず無視して読み進めて下さい。たぶん分かります

OAuth認証を承認するには

  1. Googleの承認画面にアクセスする
  2. 自分の作ったスクリプトGoogleアカウントの機能を利用する(今回はYouTube Data API v3の読み取りのみ)
  3. 戻ってきてアクセストークンを取得する為のいろんなidとかを取得する

という流れがあります。

トークン情報をメモっておくシートを作成する

スプレッドシートトークン情報を保存しておくシートを作成します。
まだ何も書いてない「シート1」の名前を変更するか、新しくシートを作るかして、「token」というシートを作成してください。
そこに、OAuth認証の前準備をしておくで取得した「クライアントID」と「クライアントシークレット」を保存しておきます。

f:id:ysmn_deus:20201019141904p:plain

分かる方は適当な位置で大丈夫ですが、よく分からない人は画像の通りA1~A8に名称(無くても良いんですが分かりやすいので)、B1~B8に該当する情報を入力していきます。
今回はB1に「クライアントID」を、B2に「クライアントシークレット」を入力して下さい。

Googleの承認画面にアクセスする

本来であればこの辺はWebアプリケーションに組み込んで運用するものだと思いますが、いかんせんめんどくさ過ぎるのでURLを生成する関数を実行してURLを取得します。
そしてコレをブラウザにコピペして承認してしまおうという魂胆です。

token.gsを開き、メニューから「公開」→「ウェブアプリケーションとして導入」を選択します。

f:id:ysmn_deus:20201019140050p:plain

すると「Deploy as web app」という表示がでてくるので、特に何も考えずに下にある「Deploy」というボタンを押します。

f:id:ysmn_deus:20201019140234p:plain

初めて実行する際には「このスクリプトスプレッドシートの編集や外部サービスへのアクセス(YouTubeからデータを取ってくる操作)」を許可する必要があります。
一度許可していれば後からは何も言われませんが、上記の「Deploy」を押すと下記の警告が表示されるかと思います。

f:id:ysmn_deus:20201019140455p:plain

「許可を確認」を押します。
自分のアカウントを選択して次に進みます。

f:id:ysmn_deus:20201019174837p:plain

(ここでアプリ名が「test01」になってますが、これはスクリプトの名前(エディタが表示されてるところの左上にあるやつ)です。)

f:id:ysmn_deus:20201019140837p:plain

「このアプリは確認されていません」とヤバそうな表示が出ますがヤバくないです。自分の作ったばっかりのアプリケーションなのでGoogle様による確認が済んでないのは当然です。
進む為に左下の詳細をクリックして進みます。

f:id:ysmn_deus:20201019140847p:plain

f:id:ysmn_deus:20201019141047p:plain

順調にいけば最後にURLが表示されます。このURLをtoken.gsの13行目にあるdeployURLとして保存します。

f:id:ysmn_deus:20201019141217p:plain

例えばURLがhttps://script.google.com/macros/s/hogefuga/execと表示されていれば、token.gsの13行目を下記のように修正します。

const deployURL = "https://script.google.com/macros/s/hogefuga/exec"

ここまで来たら、ツールバーの「関数を選択」という箇所で「makeAccessTokenURL」を選択し、三角アイコンのボタンを押します。

f:id:ysmn_deus:20201019142255p:plain

問題無く実行できれば、準備の段階に作成した「token」というシートの一番下にURLが追記されています。

f:id:ysmn_deus:20201019142409p:plain

早々にアクセスしたくなるのですが今アクセスするとはじかれます。
OAuth認証の前準備をしておくでOAuth画面の作成など行ったと思いますが、同じ画面で先ほどのdeployURLへのリダイレクトを許可します。

f:id:ysmn_deus:20201019142703p:plain

このクライアント名のところをクリックすると変種画面に移動します。

f:id:ysmn_deus:20201019142810p:plain

「承認済みの JavaScript 生成元」という箇所にhttps://script.google.comを、「承認済みのリダイレクト URI」にdeployURLに設定したURLを入力します。
画像では2個URLが入ってますが、1個です。
「保存」を押して完了です。

OAuth認証を行う(トークンを取得する為のcodeの取得)

ここまで来たら、先ほど「makeAccessTokenURL」で作成したURLにアクセスします。

f:id:ysmn_deus:20201019142409p:plain

アクセスすると、先ほど「ウェブアプリケーションとして導入」の時に見たような画面が表示されます。

f:id:ysmn_deus:20201019164028p:plain
国会の黒塗り資料みたいになってるのは許して

OAuthクライアント作成時の名前が表示されており、その下にYouTubeのチャンネル(アカウント)が表示されていると思います。
該当するアカウント(スーパーチャット情報が取得したいアカウント)をクリックして次に進みます。

また見覚えのある画面だと思いますので、同様に処理していきます。

f:id:ysmn_deus:20201019164317p:plain

f:id:ysmn_deus:20201019164359p:plain

ここの承認の際に「YouTubeアカウントの表示」となっている事を確認して下さい。
僕が悪意のある人間で、なにがしか余計なことをするのであれば情報の表示のみならずコンテンツの編集やアカウントの情報変更など全てできる権限を承認させます。
ここでは「表示」とあるので、最悪情報が取られるだけで済みます。それはそれで困りますが、ソースも全部出してるのでどういう処理してるのかは確認して下さい。

f:id:ysmn_deus:20201019164715p:plain

しつこいぐらい承認のステップがありますが、OAuthは簡単にできる反面結構権限が強いです。
このぐらい「ほんまに大丈夫なんか?」と聞かれる行為であるということを肝に据えて開発をしましょう。(自戒を込めて)
「許可」を押して処理完了です。

{"status":"ok"}

と表示されれば処理完了です。

上記の処理の最後でエラーページが表示される

幾つかアカウントを所持してログインしていると、上記の処理の最後でエラーになる事があります。
その場合はそのブラウザを閉じずに、エラーページが表示されているブラウザのURLを確認して下さい。
おそらくパラメータにcodeというものが含まれていると思います。

https://script.google.com/macros/s/hogefuga/exec?code=XXXXXXXXXXXXXXXX&hoge=YYYYYYYYYYYYYYYYYY...

上記のcode=XXXXXXXXXXXXXXXXにあたるXXXXXXXXXXXXXXXXの情報さえあれば問題無いのでもしエラーが表示されてしまった場合はそちらをコピーして下さい。
そして、スプレッドシートで作成した「token」というシートのB3にあたるcodeという箇所に貼り付けて置いて下さい。

OAuth認証を行う(新規トークンの発行)

上記までうまくいっていればあとはそこまで難しい処理では無い筈です。
token.gsgetNewAccessTokenを実行します。

f:id:ysmn_deus:20201019170106p:plain

問題が起こらなければ何事も無かったかの用に処理が終わります。
(codeを生成して時間がたっているとエラーが発生するかも知れません)
スプレッドシートtokenのシートにrefresh_tokenなどが生成されているのを確認しておきましょう。

f:id:ysmn_deus:20201019170507p:plain

トークンが生成されていれば、後はこのtoken.gsを直接触ることは無いかと思います。

実行する

それではとりあえず実行してみます。
取得する関数はgetSuperChatEventsとして宣言しています。getSuperChatEventsを実行して下さい。

f:id:ysmn_deus:20201019171230p:plain

問題無ければtokenを作成していたスプレッドシートに実行時間の名前がついたシートが追加されていると思います。

f:id:ysmn_deus:20201019171429p:plain
収益化もされてないアカウントで実行してるのでなにも表示されていない

スーパーチャットが有効化されていて何件か取得できる場合はこのようになります。

f:id:ysmn_deus:20201019172140p:plain

問題点

上記まで実行できれば「おっ、これでスプレッドシートにデータが集約できて便利やな!」という感じなんですが、様々な問題点がありました。
もしかしたら今後APIが改善されるかもしれないので、さしあたり今回用意したスクリプトの意図などを列挙しておきます。

  • SuperChatはアカウント(チャンネル)に対して発生するイベントであり、どのライブで発生したものかは現段階では判別できない
  • SuperChatEvents: listは最新のSuperChatを取得するAPIなので特定期限~現在まで、という縛りでシートに書き出す仕様にした
    • 全部書き出したいって人はmain.gsの1行目にあるafterDateに格納されている日付をかなり古い日付にすればたぶん大丈夫
  • 一番上でも書いたが最新50件のみ取得可、50件以上古いデータは取得できない。これは実際に観測しましたし、海外のフォーラムなどでも言及がありました
  • 上記の制約込みで考えると、GASの最短実行間隔である1分の中でスーパーチャットが50件未満であれば、ライブ時に1分間隔で実行するトリガーを作成して毎分getSuperChatEventsを実行するというのはできそう
    • ただし、人気ライバーさんの記念放送なんてのは1分間に50件以上なんて余裕で来てそうなのでむり
    • 仮に非同期処理で5秒間隔などでgetSuperChatEventsを実行しまくる策も無くは無さそうだが、先にGASのなにがしかの実行制限に到達してしまいそう
    • GCPの有料アカウントで実行することも考えられるが金払うならインスタンス立ち上げて実行しまくった方が確実

今後の課題

たぶんYouTube Live Streaming APIのチャットを取得するAPIを利用してスーパーチャットのみを抽出するのが確実と思われます。
ただし、このAPIライブ配信時に取得し続ける方針は同時接続人数5000~6000ぐらいの状況下で1分あたりのクオータコストが60ぐらいでした。
1アカウントに割り当てられた日にちの上限は10000なので、この規模のライバーさんの放送だと2.7時間が限度という事になります。 実用的で無いこともないんですが、コメント数が増えればクオータコストが上がる可能性がある(こちらは機会があれば別記事で紹介します)ので心許ない気がします。

ちなみに、SuperChatEvents: listのコストは直接分からないんですがたぶん1か2です。チャットを全部取得するよりはコストが低いので可能性としてはこちらを3秒に1回実行する(16件/秒程度のスパチャ速度まで捌ける、一日MAX8時間程度)のが現実的かもしれません。

Storybookでwebpackの設定に手を加えずにCSS Moduleを適応する

Next.jsでもデフォルトで使える様になったのでCSS Moduleを積極的に採用していこうかと思っていたのですが、webpackを通したあとでないとスタイルが適応されないので、デフォルトの設定だとStorybookで確認できません。

css-loaderに読み込ませる設定をwebpackの設定ファイルに追記する方法もあるみたいなのですが、色々探していたところstorybook-css-modules-presetというパッケージを見つけました。

github.com

パッケージをインストールした後にStorybookのアドオン設定をするだけでcssがロードされる様になります。
yarn add -D storybook-css-modules-presetした後に.storybook/main.jsを下記の通りにするだけで適応されました。

module.exports = {
  stories: ['../src/**/*.stories.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'],
  addons: ['@storybook/addon-links', '@storybook/addon-essentials', 'storybook-css-modules-preset'],
}

めっちゃ楽。

Next.js (TypeScript) + Storybook + Amplify の初期構築(@2020-10-03)

f:id:ysmn_deus:20201003173613j:plain

前回からの続きです。
Amplifyを導入して嬉しいのは主に

  • AppSyncによるGlaphQL APIの利用
  • Cognitoによる認証

だと思います。
今回はAmplify DocsにあるようにGraphQL APIを利用することを持って初期構築としてみます。

まず最初に

必要最低限の環境構築

  • AWSアカウント(当たり前ですが)

Amplify CLIのセットアップ

インストール

AmplifyはCLI経由で色々することが多いです(というか、CLIは必須?)。
公式ドキュメント通りnpmでインストールします。

npm install -g @aws-amplify/cli

インストールが完了するとamplifyコマンドが利用できます。

初期設定

Amplify CLI経由でAWSサービスを触るユーザー設定を行います。

amplify configure

コマンドを実行するとAWSコンソールのログイン画面に飛ばされるので、ログインします。
その後、幾つか質問されるので回答していきます。

Specify the AWS Region
? region:  # 任意のリージョン、日本ならたぶんap-northeast-1
Specify the username of the new IAM user:
? user name:  # 分かりやすい名前付けといた方が後々困らない
Complete the user creation using the AWS console

アカウント作成の為にブラウザに飛ばされます。
うまくアカウント作成画面に移動できなかった場合はコンソールに表示されているURLに直接アクセスしましょう。
基本的に「次へ」を連打で良いと思います。好みがある場合は都度修正して下さい。

アカウントが作成できたら、コンソールへ戻ります。
accessKeyIdとsecretAccessKeyが問われますので、それぞれアクセスキーIDとシークレットアクセスキーを入力します。
両方を入力すると、プロファイル名を問われます。基本的にはdefaultで良いと思いますが、アカウントを使い分けたりしている人は名前を変えておくと良いかもしれません。
(一応この設定はC:\Users\ユーザー名\.aws内にあるcredentialsの中に記載されています。)

以上でAmplify CLIの初期設定は完了です。

Storybookのセットアップ

必須では無いんですが、Storybookを利用したいのでこのタイミングでセットアップします。
基本的には下記の記事を参考にさせていただきました。

Storybookのインストール

超絶簡単です。Storybookの公式にあるとおり

npx sb init

で完了です。
サンプルのコンポーネントsrc/storiesに生成されるようで、これらがtsxファイルで用意されているところを鑑みるとtypescriptのプロジェクトかどうかもインストール時にチェックしているようです。
試しに起動するには

yarn storybook

でStorybookが起動します。

Web上には、どうもトラブルがあったりする影響なのかnpm/yarnでパッケージをインストールしている方が多いように見受けられます。
もし今後触っていく内にトラブルが多いようでしたら手動でパッケージをインストールする方法を試してみたいと思います。

一応react-docgen-typescriptも便利そうだったのですが、基本的にデフォルトでTypeScript使っていれば型を表示してくれるのと、プロパティに?を付与した場合の挙動がめんどくさそうだったので導入しませんでした。
参考までに比較画像を掲載しておきます。

f:id:ysmn_deus:20200929173054j:plain
左が標準で、右が`react-docgen-typescript`を導入した画面

プロジェクトでのAmplifyのセットアップ

初期化

まずはプロジェクトのルートディレクトリでAmplify CLIを用いて初期化する必要があります。

ampllify init

いくつか質問されるので答えていきます。

? Enter a name for the project nextamplified
? Enter a name for the environment dev
? Choose your default editor: IntelliJ IDEA #(ここはお好きなIDEなどをお選び下さい)
? Choose the type of app that you're building javascript
Please tell us about your project
? What javascript framework are you using react
? Source Directory Path:  src
? Distribution Directory Path: build
? Build Command:  npm.cmd run-script build
? Start Command: npm.cmd run-script start
? Do you want to use an AWS profile? Yes
? Please choose the profile you want to use default

大体デフォルトです。
問題無くAmplifyの初期化が終われば、Amplifyをアプリケーションで利用する為にライブラリを入れておきます。

yarn add aws-amplify @aws-amplify/ui-react

GraphQL APIとデータベースの作成

準備

GraphQLのAPIを有効にするとAWS側でAppSyncの準備がなされます。
本来であればDynamoDBなどの設定も必要ですが、その辺を全てCloudFormationで処理してくれるので、基本的にウィザードに沿って進めるだけで大丈夫です。

amplify add api

APIを追加していきます。

? Please select from one of the below mentioned services: GraphQL
? Provide API name: nextamplified
? Choose the default authorization type for the API API key
? Enter a description for the API key:
? After how many days from now the API key should expire (1-365): 7
? Do you want to configure advanced settings for the GraphQL API Yes, I want to make some additional changes.
? Configure additional auth types? Yes
? Choose the additional authorization types you want to configure for the API Amazon Cognito User Pool
Cognito UserPool configuration
Use a Cognito user pool configured as a part of this project.
? Configure conflict detection? No
? Do you have an annotated GraphQL schema? No
? Choose a schema template: Single object with fields (e.g., “Todo” with ID, name, description)

The following types do not have '@auth' enabled. Consider using @auth with @model
         - Todo
Learn more about @auth here: https://docs.amplify.aws/cli/graphql-transformer/directives#auth


GraphQL schema compiled successfully.

Edit your schema at [プロジェクトパス]\amplify\backend\api\nextamplified\schema.graphql or place .graphql files in a directory at [プロジェクトパス]\amplify\backend\api\nextamplified\schema
? Do you want to edit the schema now? Yes
Please edit the file in your editor: [プロジェクトパス]\amplify\backend\api\nextamplified\schema.graphql
Successfully added resource nextamplified locally

基本的にはドキュメント通りやってますが、試しにやってみる分にはCognitoの認証などは不要だと思います。
とりあえずスキーマを見てみます。Choose a schema templateSingle object with fieldsを選択している場合はamplify/backend/api/nextamplified/schema.graphqlに下記の通りのファイルが生成されています。

type Todo @model {
  id: ID!
  name: String!
  description: String
}

はてなシンタックスにgraphql追加してくれ

このまま試しても良さそうですが、一応ドキュメントの通りに修正します。

type Post
@model
@auth(rules: [{ allow: owner }, { allow: public, operations: [read] }]) {
  id: ID!
  title: String!
  content: String!
}

@authディレクティブで認証処理を入れています。この辺の挙動はドキュメントを参照して下さい。
(個人的にこの辺の柔軟さはAppSyncを採用する理由になっても良いと思っています)
編集が完了したらAPIAWS上に生成します。

AWS上にバックエンドを生成(ローカルでモックを作る場合はとりあえず不要)

以降の操作は課金対象になる(DynamoDBやCongnito)と思いますので、ご留意下さい。ほぼ無料枠に収まると思いますが。
最初に現在の状況を確認しておきます。

amplify status

おそらく2個のリソース(AuthとApi)が表示されていると思います。
これらのリソースを下記の操作でAWSに生成します。

amplify push

AppSyncのGraphQLスキーマコンパイルがまず走ると思います。

? Do you want to generate code for your newly created GraphQL API Yes
? Choose the code generation language target typescript
? Enter the file name pattern of graphql queries, mutations and subscriptions src\graphql\**\*.ts
? Do you want to generate/update all possible GraphQL operations - queries, mutations and subscriptions Yes
? Enter maximum statement depth [increase from default if your schema is deeply nested] 2
? Enter the file name for the generated code src\API.ts

とりあえず基本デフォルトです。
データ構造が複雑になった際にはmaximum statement depthあたりをもう少し深くしてもいいかもしれません。
このウィザードが完了するとリソースを生成します。少々時間がかかると思います。

完了すると、ターミナルにGraphQLのエンドポイントとAPIキーが表示されると思います。

GraphQL endpoint: https://
GraphQL API KEY: 

この状態まで行けばAWS上にはリソースが生成されています。
GraphQLのウェブコンソールを確認する場合は

amplify console api

で該当URLに飛ばされるようです。(AWSアカウントのログインが必要です)

ローカルでモックを生成する

AppSyncはローカルでモックを走らせて開発することができます。
下記のコマンドを打てばローカルでAppSyncのサーバーが起動します。

amplify mock api

料金が発生しないという安心感もありますが、何よりAWSにリソースを適応するのに結構時間がかかるので、基本こちらでの開発がメインになると思います。

GraphQLツールバーでデータを挿入する

前述のAWSリソースでもローカルモックでもいいのですが、とりあえずデータを入れて見ます。
(基本的にローカル環境での想定で話を進めます)
GraphQLのツールバー(ウェブコンソール)から下記のミューテーションを走らせます。

mutation CreatePost {
  createPost(input: {title: "Test Post", content: "post content"}) {
    id
    owner
    title
    updatedAt
    createdAt
    content
  }
}

たぶんなにがしかの文句を言われる筈です。
これは@authディレクティブがAPIキー認証はreadのみ許容するというスキーマな為です。
Use: API Keyという箇所をクリックしてUse: User Pool(Cognito認証)にし、Update Authをクリックして適当な認証トークンを設定します(基本は何も変更せずにGenerate Tokenで問題無いです)。
その後にもう一度上記のMutationを実行すると右側に適当なレスポンスが表示されると思います。

右側にレスポンスが表示されていれば大丈夫ですが、一応Queryも実行してみます。

query ListPosts {
  listPosts {
    items {
      content
      createdAt
      id
      owner
      title
    }
  }
}

問題無くPostのリスト(とはいえ1個ですが)が取得できていると思います。

SSRでのAPI利用

ようやっとコンポーネントを触ります。
とりあえずチュートリアルということで、src/pages/index.tsxに全て記載していきます。
index.tsxcssを参照しているので、csssrcディレクトリに移動しています。

これでトップページは表示されます。
AmplifyAuthenticatorといれるだけで、簡易的ではありますが認証保護+アカウント生成の機能までやってくれるのは嬉しいところです。
とりあえず起動してみます。

yarn dev

ローカルで開発するときはあわせて

amplify mock api

も実行しておきます。
問題無く表示されていればAmplifyAuthenticatorのところからユーザーを作成して適当に記事を投稿してみます。
たぶん404に飛ばされますが、これは作成後に記事のページに飛ぶ処理を書いておきながら飛んだ先のコンポーネントが無いためです。

SSGでのAPI利用

Next.jsの特徴としてSSRとSSGの両方を使い分けられます。
ここではSSGを利用して記事ページを作成します。src/pages/posts/[id].tsxに作成していきます。

上記のyarn devamplify mock apiで確認できると思います。

デプロイ

AWS上へのデプロイはServerless Frameworkを利用することができるようです。
ビルドしたデータをamplify publishする方法もありそうですが、Next.jsを利用するならServerless Frameworkを利用しておくのが無難だと思います。
ルートディレクトリにserverless.ymlを追加します。

# serverless.yml
nextamplified:
  component: "@sls-next/serverless-component@1.17.0"

ドキュメントではバージョンは1.16.0ですが、1.16.0だとThe parameter MinTTL is required.と怒られてしまいます。
あとこの辺で躓いたのですが、tsconfigでincrementalをtrueにしていると文句いわれるかもしれません。
とりあえずtsBuildInfoFileを書けば良さそうだったのでtsconfig.jsontsBuildInfoFile"./.tsbuildinfo"に設定する記述を追記しました。

たぶん以上の設定で準備は完了です。下記のコマンドでデプロイします。

npx serverless

ターミナル上にcloudfrontのURLなどの情報が出てきたら完了です。
ただし、上記の投稿した先のURL(ルートURL/posts/uuid)はSSGを利用しているので、ビルド時にデータが無ければ表示されないと思います。

Serverless Frameworkを利用してデプロイするのが非常に簡単ですが、CI/CDを考えるとAmplify Consoleを利用してGitリポジトリからデプロイされる方式を採る方がいいかもしれません。

Next.js (+TypeScript) の初期構築(@2020-09-25)

f:id:ysmn_deus:20200925185828p:plain

去年にNext.jsの学習をしていたのですが、Amplifyとの相性が悪くて(Cognitoの認証周りでたいへん苦労した)使うのを辞めていました。
ただ、久しぶりに色々調べてみると、どうやら公式ドキュメントにもNext.js用のチュートリアルがあるみたいなのでちょっとチャレンジしてみます。
ついでに、ESLint + Prettierまわりも何やら推奨設定が変わってきてるそうなのでその辺にも対応した防備録的な記事を目指します。
(また来年新しい記事を書いてそう・・・)
下記は全てWindows環境で行っています。

必要最低限の環境構築

まず最初にこの記事を見てる方は大丈夫だと思うんですが、下記の環境は整えて下さい
章タイトルから察する方はプロジェクト構築へ飛ばして読んで下さい。

  • Node.js(v10.x 以上)
  • npm(v5.x 以上)、後々yarnを使いますが、自分はWindows環境なので一番最初にnpxを使います。
    この記事を参考にする人はついでにyarnも入れといて下さい。
  • git(v2.14.1以上)

上記が整っていれば最初のセットアップは進める事が出来ると思います。

プロジェクト構築

Next.jsのプロジェクトを作成する

Next.jsのプロジェクトを作成します。
LinuxMac環境では

yarn create next-app プロジェクト名

でも作成可能だと思いますが、WindowsはNodeのパスに半角スペースが入ってる人が多いと思いますのでnpxでプロジェクトを作成します。

npx create-next-app next-amplified

プロジェクト名はAmplifyのドキュメントに習ってnext-amplifiedにしてますが、なんでもいいです。
とりあえず問題無く動くか確認しておきたいので、Next.jsプロジェクトを実行してみます。

cd next-amplified
yarn dev

http://localhost:3000にアクセスして問題無く表示されれば大丈夫です。

TypeScriptのセットアップ

tscのインストール

もしTypeScriptのコンパイラがまだグローバルにインストールされてない場合はインストールして置いて下さい。

npm install -g typescript

tsconfig.jsonの作成

プロジェクトのルートディレクトリ(この場合はnext-amplified直下)にtsconfig.jsonを作成します。

tsc --init

上記のコマンドでtsconfig.jsonが作成されます。
最初は別に空ファイルでも良いんですが、この辺は好みです。

開発に必要なライブラリの追加

あとは必要な依存ライブラリを入れておきます。

yarn add -D typescript @types/react @types/node

試しに動かしてみる

上記までで多分動きます。
試しにpagesに入ってるファイルをTypeScriptのファイルに変更してみます。
めんどくさいのでpages/index.jsのみをpages/index.tsxに名前を変更します。
特に他の設定無しで、yarn devを実行すればhttp://localhost:3000に先ほどと同様のページが表示されているかと思います。
(TypeScriptの設定がおかしければ、ここでコンパイルされずに表示されないので)

整理する

とりあえずコンパイルまでは良さそうなので一旦ファイルを整理します。
pagesディレクトリはsrcディレクトリを作成してその直下に配置することにします。
(Next.jsのv9からデフォルトでsrc直下でも認識するようになったようです。)
ついでにstylesは削除しています。
やっぱり後で必要だったので残します

mkdir src
mv pages src

あと、api_app.jsも今は不要なのでいったん消します。

Remove-Item .\src\pages\api\ -Recurse
Remove-Item .\src\pages\_app.js 

色々消してコンパイルが通らなくなるので、index.tsxも編集しておきます。

import React from 'react'

export default function Home() {
  return (
    <div>hoge</div>
  )
}

とりあえずここまでで問題無いか実行してみます。

yarn dev

問題無ければ、Linterの設定などに行きましょう。

ESLint + Prettierの設定

構成

以前はESLintのプラグインでPrettierを走らせていたのですが、下記の記事でも紹介されているとおり公式の推奨が明示されました。

Prettier と ESLint の組み合わせの公式推奨が変わり plugin が不要になった

よって、この設定に従いprettier-eslintを利用してPrettier → ESLintと走るような環境を構築します。
何はともあれESLintとPrettierをセットアップします。

(余談ですが、この辺からIDEを利用した方が色々補完が効いて楽かもしれません)

ESLint

ESLintのインストール

ESLintをTypeScriptで走るようにセットアップしていきます。
この辺は昔と変わらず、素直にインストールします。

yarn add -D eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin eslint-plugin-react

eslintをグローバル環境でセットアップしてる人は、適宜って感じです。

ESLintの設定

ESLintの設定ファイル.eslintrc.jsをプロジェクトのルートディレクトリに置いておきます。
こちらも、以前の設定と変更無しです。

module.exports = {
  env: {
    browser: true,
    node: true,
    es6: true
  },
  parser: '@typescript-eslint/parser',
  parserOptions: {
    ecmaFeatures: {
      jsx: true
    },
    sourceType: "module"
  },
  plugins: [
    "react",
    '@typescript-eslint'
  ],
  extends: [
    "eslint:recommended",
    "plugin:react/recommended",
    'plugin:@typescript-eslint/recommended',
  ],
  rules: {
  },
  overrides: [
    {
      'files': ["**/*.tsx"],
      'rules': {
        'react/prop-types': 'off'
      }
    }
  ]
};

ESLintが走る用のコマンドをpackage.jsonに記載しておきます。

  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "eslint --ext .ts,.js,.tsx,.jsx src/**/*"
  },

チェックする対象は全てsrcディレクトリ内に収まると思うのでこうしておきます。
試しにsrc/pages/index.tsxにわざとセミコロンを付けてESLintでチェックしてみます。

import React from 'react'

export default function Home() {
  return (
    <div>hoge</div>
  )
};
yarn lint

error Unnecessary semicolon @typescript-eslint/no-extra-semiが出てる筈です。
良さそうなのでついでにfixも試してみます。

yarn lint --fix

おそらくセミコロンが消滅したと思います。
ESLintのセットアップは大丈夫そうです。

Prettier

ESLintとPrettierの競合を解消する方法は数種類ありますが、今回は最近TLで見かけたPrettier公式の推奨設定を参考にします。
(なにはともあれ、公式の推奨に従っておくのが賢い筈です)

prettier.io

Prettierのインストール

とりあえずPrettier、eslint-config-prettierをインストールします。

yarn add -D prettier eslint-config-prettier

とりあえずPrettierが動作するか試してみます。index.tsxの最後に改行を入れまくるなどしてフォーマッタで修正されるであろう形にしてみて下さい。
その後

prettier --write "src/**/*.{js,jsx,ts,tsx}"

で修正されるかチェックします。問題無ければPrettierの設定は完了しているはずです。

eslint-config-prettierの設定

PrettierとESLintの競合を解消するために.eslintrc.jsを編集します。

~
  extends: [
    "eslint:recommended",
    "plugin:react/recommended",
    'plugin:@typescript-eslint/recommended',
    "prettier",
    "prettier/@typescript-eslint",
    "prettier/react",
  ],
~

ESLintのextendsの設定は上から順に適応され、後ろの方で上書きされていきます。
なので

~
  extends: [
    [prettier以外のプラグインの設定],
    "prettier",
    "prettier/[対応されていプラグイン]",
  ],
~

という順番で記載することにより、競合が解消されるようです。
(ただし、この辺は理解が曖昧なので、もし挙動が異なっていたら後々修正します。)

ESLint + Prettier

たぶん上記まで対応はOKの筈なので、ESLintとPrettierが両方走るスクリプトを追加します。
ESLintとPrettierを両方実行するために、npm-run-allをインストールします。

yarn add -D npm-run-all

このライブラリでpackage.jsonに記載のあるscriptsを順次/並列実行できるようになります。

~
    "fix": "run-s fix:eslint fix:prettier",
    "fix:eslint": "eslint --ext .ts,.js,.tsx,.jsx src/**/* --fix",
    "fix:prettier": "prettier --check --write \"src/**/*.{js,jsx,ts,tsx}\""
~

これでyarn fixでeslintとprettierが走るようになりました。

こまかいこと

Prettier→ESLintの順番で実行すれば.prettierrc.js要らないんじゃね?とも思いましたが、細かいフォーマット規則も後々付け加えられるので.prettierrc.jsも準備します。
prettierrcjsonで作られてることが多そうなイメージですが、eslintrcもjsで用意してるので一応合わせます。
個人的にセミコロン絶対に許さないマンになりたいのでその辺の設定をしておきます。

module.exports = {
  trailingComma: "es5",
  tabWidth: 2,
  semi: false,
  singleQuote: true,
}

.eslintrc.jsの方にもセミコロン絡みの設定をしておきます。

~
  rules: {
    "semi": ["error", "never", {"beforeStatementContinuationChars": "never"}],
    "semi-spacing": ["error", {"after": true, "before": false}],
    "semi-style": ["error", "first"],
    "no-extra-semi": "error",
    "no-unexpected-multiline": "error",
    "no-unreachable": "error"
  },
~

これで大丈夫です。
この辺まで来たらIDEによってはファイルウォッチャーに登録しても良さそうです。

ちょっと長くなったのでAmplifyに関しては別記事に分けます。

YouTuber(VTuber)さんのライブ配信予定をGoogleカレンダーと自動同期する

自分は二次元おたくでありますが故、VTuberさんのライブ配信などを見ながら作業することがぼちぼちあります。
基本アーカイブ勢ではあるのですが、晩酌配信のような配信はリアルタイムで楽しみたいなぁというモチベーションがあります。
可能であればその予定がGoogleカレンダーに入っていると嬉しいです。

というわけで、YouTube上の特定チャンネルの予定をGoogleカレンダーに登録するスクリプトを書いていきます。
「プログラミングとか知らねえよ!」って方でもコピペすればたぶんできると思います。
とはいえ基本はパソコンで作業する用に書いてますのでスマホのみの方は参考程度に・・・

まずは例でも見て下さい。(みる時間によっては何も入ってないかも)

calendar.google.com

このカレンダーのiCal形式URLは https://calendar.google.com/calendar/ical/0fb4lch7recm0p1oktj9k6rh28%40group.calendar.google.com/public/basic.ics となっています。
もし自分のカレンダーに取り込んでみたい、という方は上記URLを「URLで追加する」を参照しながら追加してみて下さい。

※ただし、上記のカレンダーの動作を保証するものでは御座いませんので何卒・・・

この記事では上記のカレンダーを自分で作成する方法を紹介します。
例に雪花ラミィさんを取り上げているのは、完全に自分の趣味です。
(朝活で定期的に枠が立ってて確認しやすいので)

必要情報を揃える

なにはともあれ必要な情報を集めるところからです。
まずはチャンネルのIDが必要になるので、カレンダーに表示したいチャンネルを開きます。

f:id:ysmn_deus:20200819172945p:plain
kawaii

参考: Lamy Ch. 雪花ラミィ - YouTube

雪花ラミィさんのチャンネルのURLが https://www.youtube.com/channel/UCFKOVgVbGmX65RxO3EtH3iw となっています。このURLの最後にある UCFKOVgVbGmX65RxO3EtH3iw がチャンネルのIDです。

他にはYouTube Data APIを利用する為のキーが必要なのですが、コレに関しては後述します。

Googleアカウント

そもそもアカウントがない人がこの記事を見てることは少ないと思いますが、もし持っていなければ作成して下さい。
作成する方法は解説しませんが、こちらが参考になるかと。

office-hack.com

カレンダーを作成するアカウント

「カレンダーを公開していろんな人に共有したい!」という人は新規作成して、そのアカウントでカレンダーを作成することをオススメします。

f:id:ysmn_deus:20200819171800j:plain
共有したカレンダーには作成者の名前が表示される

YouTube Data APIAPIキーを作成する

別記事にまとめました。とりあえずこれは実行して下さい。

www.tech-note.info

更新するカレンダーを新規作成する

基本的に自動で予定が更新されるようにしますが、最初は手動でカレンダーをつくってあげる必要があります。
まずは一個作りましょう。Googleカレンダーを作成するアカウントにログインした状態でGoogleカレンダーのページを開きます。

f:id:ysmn_deus:20200819185246p:plain

左下の「他のカレンダー」と書かれた右にある「+」から「新しいカレンダーを作成」をクリックします。

f:id:ysmn_deus:20200819185623p:plain

「新しいカレンダーを作成」というページに移動します。「名前」に適当な名前を付けて「カレンダーを作成」をクリックします。
ここでは「雪花ラミィ配信予定」としました。
作成が完了したらブラウザ下部に「作成が完了しました」と表示されます。ページが自動で移動しないので、前のページに戻りましょう。

カレンダーIDをひかえておく

後に「カレンダーID」が必要になってきますので、それを確認します。
Googleカレンダーのページに先ほど追加した名前でカレンダーが追加されていると思いますので、そちらの右側に出てくる設定アイコンをクリックすると「設定と共有」という項目が出てきます。

f:id:ysmn_deus:20200819191509p:plain

f:id:ysmn_deus:20200819191359p:plain

カレンダーの設定画面に移動した後に、スクロール(もしくは左にある目次から移動)すると「カレンダーの統合」という項目があると思います。

f:id:ysmn_deus:20200820014429p:plain

そこにある「カレンダーID」と書かれた下にあるメールアドレスみたいなのがカレンダーIDです。
ここでは 0fb4lch7recm0p1oktj9k6rh28@group.calendar.google.com が該当します。
これを記録しておいて下さい。あとで使います。

Google Apps Scriptで自動同期する

こっからちょっとプログラミング要素が入りますが、最悪コピペでいいとは思います。

Google Apps Scriptのコンソール

GoogleカレンダーからスクリプトをくっつけたりできないのでGoogle Apps Scriptのコンソールにプロジェクトを作成して自動実行します。
なんのこっちゃ?という方はとりあえず下記の流れに沿って操作して下さい。

まずは下記のURLからコンソールを開きます。

www.google.com

このとき、先ほどカレンダーを作成したアカウントでログインされていることを確認して下さい。
別のアカウントからでは基本うまくいきません。

f:id:ysmn_deus:20200819190029p:plain

「Start Scripting」と書かれたボタンをクリックします。
Google Apps Scriptのコンソールに移動します。

f:id:ysmn_deus:20200819192843p:plain

新規プロジェクトの作成

自動でスケジュールを更新するプログラムを実行するプロジェクトを作成します。
左上の「新しいプロジェクト」と書かれたボタンをクリックします。

f:id:ysmn_deus:20200819193044p:plain

とりあえず後で修正が必要になったときに見つけやすい用に名前は付けておきます。
左上の「無題のプロジェクト」と書かれた箇所をクリックして、名前を変更します。
とりあえず自分は YouTube Live Calendar Sync としました。

f:id:ysmn_deus:20200819193400p:plain

OK押して更新すると名前が変わってると思います。

f:id:ysmn_deus:20200819193411p:plain

コーディング

詳しいことはさておきコードをはっつけておきます。

いつのまにV8実行できるようになってたんやお前

一応挙動について説明

上記のスクリプト

  1. 実行されるとYouTube Data APIからチャンネルに登録されている動画を「作成された」時間順でソートして25件取得する
  2. 上記で取得したものから、配信予定のものだけを選択(アーカイブとかは除外)
  3. 上記で選別した動画情報を取得
  4. 上記の情報からカレンダー上に予定を作成

て感じです。

変更するところ

一番上の

const APIKEY = '[APIキー]'

の箇所を、「YouTube Data APIのAPIキーを作成する」で作成したAPIキーに書き換えます。
次に

const CAKENDAR = {
  'UCFKOVgVbGmX65RxO3EtH3iw': '0fb4lch7recm0p1oktj9k6rh28@group.calendar.google.com', // 雪花ラミィ
}

UCFKOVgVbGmX65RxO3EtH3iw の箇所を「必要情報を揃える」でひかえてあるチャンネルIDに、 0fb4lch7recm0p1oktj9k6rh28@group.calendar.google.com の箇所を「カレンダーIDをひかえておく」でひかえておいたカレンダーIDにします。

複数チャンネルに対応する場合

上記のスクリプトを複数チャンネルに対応させる場合、新しくプロジェクトを作成してもいいですが

const CAKENDAR = {
  'UCFKOVgVbGmX65RxO3EtH3iw': '0fb4lch7recm0p1oktj9k6rh28@group.calendar.google.com', // 雪花ラミィ
}

の箇所に追加しても動くようにしています。
例えば

const CAKENDAR = {
  'UCFKOVgVbGmX65RxO3EtH3iw': '0fb4lch7recm0p1oktj9k6rh28@group.calendar.google.com', // 雪花ラミィ
  '[チャンネルID]': '[カレンダーID]', // 新しく追加したカレンダー
}

というように、何個も追加できます。
ただし、増やしすぎるとクォータ制限に引っかかるので「クォータ(使用量)のお話」を確認して下さい。

試しに実行してみる

コードができたら試しに実行してみましょう。
上側のボタンの虫マークの隣の項目「関数を選択」から「run」を選びます。

f:id:ysmn_deus:20200820011816p:plain

f:id:ysmn_deus:20200820011827p:plain

「run」が選択できると、三角形マークが押せるようになるので、押してみます。

f:id:ysmn_deus:20200820011840p:plain

スクリプトを実行すると認証が要るよ!(Authorization required)と表示されます。

f:id:ysmn_deus:20200819195136p:plain

「認証を許可」を押して次に進みます。

f:id:ysmn_deus:20200819195450p:plain

カレンダーを作成したアカウントを選択します。
ヤバそうな表示がでますが無視します。ヤバイのはあなたの作ったスクリプトです。
(心配な人はアカウントを分けて下さい。私が悪意のあるスーパーハッカーだったとしても、乗っ取れるのはその新しく作ったアカウントだけです。)

無視するには左下の詳細を押して

f:id:ysmn_deus:20200819195744p:plain

自分の作ったプロジェクト名の書かれた「(安全ではないページ)に移動」をクリックします。

f:id:ysmn_deus:20200819200159p:plain

一応この操作の説明をしておきますと、今作成しているプログラムがGoogleアカウントのカレンダーなどを変更していいかどうかの許可を確認しています。
右の方にあるiの○をクリックすると詳細が表示されます。気になる方は見てみると良いと思います。

とりあえず問題無ければ「許可」をクリックします。
ここまでで何か警告っぽいものが出なければ、とりあえず実行できてるはずです。
カレンダーを確認してみて下さい。

f:id:ysmn_deus:20200820012151p:plain

ライバーさんの立ててる配信予定が入っていればとりあえず問題無いと思います。

定期実行の設定

スクリプトは上記でOKですが、毎回自分で実行していてはめんどくさいこと極まりないです。
上記のスクリプトを自動で実行するように設定しておきます。

Google Apps Scriptで書いたスクリプトは標準機能で定期実行できます。
コードの編集画面にある実行ボタンの左にある時計アイコンをクリックします。

f:id:ysmn_deus:20200820092749p:plain

初めて起動するときはなんか出てきますが「ダッシュボードを表示」でモーダルを閉じます。

f:id:ysmn_deus:20200820092849p:plain

f:id:ysmn_deus:20200820093028p:plain

右下にある「トリガーを追加」ボタンを押してトリガーを追加します。

f:id:ysmn_deus:20200820093917p:plain

色々設定できます。
実行する関数は「run」を選択しといてもらえれば、後は適当に。デフォルトでは1時間おきに更新するような設定になっていますが、もし15分や30分などより細かく更新したい場合は「時間ベースのトリガーのタイプを選択」から「分ベースのタイマー」を選択して、「時間の間隔を選択(分)」を任意の分数にしてください。
時間を短くする場合はAPIの使用量制限があるので、しつこいようですが「クォータ(使用量)のお話」をご参照ください。

時間を設定できたら「保存」でトリガーを追加します。

f:id:ysmn_deus:20200820094531p:plain

こんな感じでトリガーが追加できていれば定期実行の設定完了です。
あとはGoogleさんが勝手に自動でカレンダーを更新してくれます。

更新のタイミング

1時間で設定した方は「12:00」に実行されそうなものですが、結構実行時間はまちまちです。
たぶんトリガーを設定したタイミングが10:15だと、次回実行されるのが11:15とかそんな感じになります。
まぁ、実行時間は目安だと思っといて下さい。

カレンダーを共有する

上記で作成したカレンダーを共有してライバーさんの配信を布教したい欲求もでてくると思います。
そういうときはGoogleカレンダーの共有機能を利用しましょう。
ここでは、iCal形式の公開URLを共有する方法を利用します。

一般公開の設定を行う

URL事態は既に生成されているのですが、一般公開できる設定をしていないと他の方は取り込めません。
なのでまずは一般公開できるように設定を変更します。

まずは該当カレンダーの設定画面を表示します。
カレンダーIDを確認したページです。

f:id:ysmn_deus:20200819191359p:plain

このページの「アクセス権限」を確認します。

f:id:ysmn_deus:20200820013412p:plain

該当項目の左側にあるチェックボックスをクリックしてチェックを入れます。

f:id:ysmn_deus:20200820013500p:plain

警告が出ますのでOKを押します。
これでOKです。もう少し下にスクロールして「iCal形式の公開URL」と書かれた箇所を確認します。

f:id:ysmn_deus:20200820013739p:plain

このURLでカレンダーを共有します。
このURLで追加された場合、閲覧しているユーザーは編集できないので悪戯などの被害は基本的に考慮しなくて良いかと思います。

共有リンクで共有する

上記の「アクセス権限」という項目の下に「共有可能なリンクを取得」というボタンがあります。

f:id:ysmn_deus:20200820113119p:plain

これをクリックするとURLが出てきますので、このURLにアクセスするとログインしているユーザーのGoogleカレンダーにカレンダーが追加されます。
一般的にこちらの方が楽だと思います。

URLで追加する

自分のGoogleカレンダーiCal形式のURLで追加する場合は、「他のカレンダー」の「+」ボタンから追加します。

f:id:ysmn_deus:20200820014022p:plain

「URLで追加」という項目があるので、そちらをクリックします。

f:id:ysmn_deus:20200820014137p:plain

先ほどの「iCal形式の公開URL」と書かれた箇所にあったURLを入力し、「カレンダーを追加」をクリックします。

これでOKだと思います。

注意点

クォータ(使用量)のお話

YouTube Data APIはなんぼでも使い放題というわけではなく、1ユーザーあたりに割り当てられた使用量みたいなものがあります。
いろんな制限があるのですが、関係するものとしては「1日あたり10,000ユニット」という制限が該当すると思います。
('ω')。o(????????????)という感じだと思いますが、先ほど紹介したスクリプトではおおよそ1チャンネルあたり100ユニットを消費するような仕組みになっておりますので、例えば1チャンネルを15分刻みで更新チェックすると

100 * 96(一日1440分 -> 96回)= 9600

となり、9600ユニット消費します。なので結構ギリギリです。(このユニットは毎日リセットされるので、一日あたりを考えればいいです。)
なので、2チャンネルを先ほどのスクリプトで処理すると、単純に2倍になるのでお昼のどこかでスクリプトが動かなくなります。

考えられる対策としては

  1. 更新頻度を下げる(30分で2チャンネル、1時間で4チャンネル、など)
  2. ユーザーを分ける(1ユーザー1カレンダーなど)

といった対応が考えられると思います。
ただし、ユーザーを分けるというのはGoogleの定めた制約を回避するためのサブアカウント作成と見なされる可能性があるので、基本的には前者をオススメ致します。
複数チャンネルの同期を考えている方は参考にしてみて下さい。

APIそのもののエラーの可能性

この記事で紹介した方法は基本「YouTube Data API」に依存しています。
なので、いわゆる枠バグのような現象が発生するライブに関してはもしかするとカレンダーに同期されない可能性があります。
本当にみたいライバーさんであれば、Twitterなど他媒体のチェックも欠かさないようにしましょうね。

公開カレンダー

一応自分が運用してるカレンダーを貼っておきます。
お好きにどうぞ。
(1時間に1回更新です)

YouTube Data API (v3)を利用する為のAPIキーを取得する

基本的に下記の公式リファレンスを参照すれば大丈夫なんですが、よくわからんかった!という方がいらっしゃったら参考にして下さい。

developers.google.com

Googleアカウントの作成

詳しくは解説しませんが、こちらが参考になるかと。

office-hack.com

Google開発者コンソールの認証ページを開く

YouTube Data APIGoogleの1サービスとして提供されているものです。
基本的に設定はGoogleの開発者コンソールからいろいろ設定します。下記にアクセスしてください。

console.developers.google.com

ログイン情報が求められた場合はメールアドレスとパスワードを入力してログインしましょう。
初めてコンソールにアクセスする場合は利用規約などの表示がでると思いますので、利用規約を一読しチェックを入れます。

f:id:ysmn_deus:20200819174540p:plain

その後、右下の「同意して続行」を押すとGoogle Cloudプラットフォームの「認証情報」の画面が表示されるかと思います。

f:id:ysmn_deus:20200819174731p:plain

なにはともあれプロジェクトを作成しないことには作業ができないようになっています。
まずはプロジェクトを作成しましょう。

プロジェクトの作成

左上の「プロジェクトの選択」をクリックします。

f:id:ysmn_deus:20200819175334p:plain

「プロジェクトの選択」モーダルが表示されると思いますので、右上の「新しいプロジェクト」をクリックします。

f:id:ysmn_deus:20200819175427p:plain

「新しいプロジェクト」と表示されたページに移動すると思います。
プロジェクト名に適当な名前(後で見て分かりやすい名前、ここでは YouTube Live Calendar Sync としておきますが、なんでもいいです)

f:id:ysmn_deus:20200819175725p:plain

「場所」は特に変更しなくて良いと思います。「作成」をクリックしてプロジェクトを作成します。

f:id:ysmn_deus:20200819175907p:plain
ぐるぐるがでて・・・

下記のような画面になれば、プロジェクトの作成は完了です。

f:id:ysmn_deus:20200819175925p:plain

APIキーの作成

ようやく本題です。「認証情報を作成」から「APIキー」を作成します。

f:id:ysmn_deus:20200819180512p:plain

f:id:ysmn_deus:20200819180616p:plain

あら簡単!APIキーができました。

f:id:ysmn_deus:20200819180857p:plain

上記の画像では黒で塗りつぶしていますが、なにか文字列が並んでいると思います。
これがAPIキーです。たぶんこの記事を見ている方はなにがしかに使うのでメモっておいてください。
また、基本的にこの文字列は公開しないようにしましょう。めんどくさいことになります。
一応「キーを制限」というボタンをクリックして、名前だけ変更しておきます。

f:id:ysmn_deus:20200819181204p:plain

f:id:ysmn_deus:20200819181323p:plain

YouTube Data API を有効にする

前までの項目でAPIキーは作成できてるのですが、YouTube Data APIが利用できる状況ではありません。
YouTube Data APIが利用できるようにしましょう。

同じ画面の「APIとサービスを検索します」というところに「youtube」と入力しましょう。

f:id:ysmn_deus:20200819181546p:plain

YouTube Data API v3」と書かれた箇所をクリックすると、YouTube Data API v3が表示されたページに移動すると思います。

f:id:ysmn_deus:20200819181700p:plain

「有効にする」というボタンがあるので、それをクリックします。
ぐるぐるが終わったらYouTube Data API v3の利用状況が表示されたページに移動します。

f:id:ysmn_deus:20200819181845p:plain

基本的にこれで作業は完了です。

ためしにAPIを使ってみる

APIキーがちゃんと有効になっているか確かめてみます。
ひかえてあるAPIキーで下記のURLを書き換えてブラウザでアクセスしてみて下さい。

https://www.googleapis.com/youtube/v3/search?part=id,snippet&q=youtube&key=[APIキー]

例えば、APIキーが ABCDEFG なら https://www.googleapis.com/youtube/v3/search?part=id,snippet&q=youtube&key=ABCDEFG です。

f:id:ysmn_deus:20200819182922p:plain

なにかゴチャゴチャと表示されていれば成功です。
一応上のAPIは、「youtube」というキーワードで検索したときの動画の結果になっています。
(上記の画像ではヒカキンさんの動画がヒットしている)

以上で大丈夫なはず!

f:id:ysmn_deus:20200819183342p:plain

NanoPi R2Sのリモートセットアップ(たぶんRaspberry Piとかでもいける)

所用でVPNブリッジのマシンを選定しておりまして、NanoPi R2Sである程度パフォーマンスが出ると導入しやすいし嬉しいなぁと言うことでNanoPi R2Sを複数台調達しました。
とりあえず現段階でのセットアップ方法をメモしておきます。
VPNブリッジにする記事は気が向いたら書きます。

なんでリモートでセットアップ?

NanoPi R2Sにはディスプレイ接続できないからです。
Raspberry Piもセットアップの時にディスプレイじゃまくさくないですか?

必要なもの

f:id:ysmn_deus:20200817135236j:plain

  • NanoPi R2S本体
    自分はFRIENDLY ELECから直接購入しました。
    NanoPi R2S
    1個だけ購入するなら本体代22 usd + 送料(8.9 usd ~ 27 usd、オススメはSF-Express)なので、1個だけにしろ基本的に公式から買うのが安いです。
    一応Amazonにも何種類かありますが、何故かケースありの方ケース無しより安かったのでそちらがオススメです。

  • ACアダプタ(USB電源でも可)
    よくラズパイとかとセットになっている、USB-microのACアダプタが妥当です。
    専用に購入するならこんなやつ
    こういうスマホを充電するUSB-ACアダプタでも問題無いかと思いますが、1ポートあたり2Aは確実に流せるものを準備しましょう。

  • SDカード
    今回は公式で用意されているイメージを利用しますので、少なくとも8GBぐらいは必要です。
    たぶん8GBだと詰むのでミニマム16GBのものとかでしょうか。(自分はよく32GBを利用します)
    SDカードは意外と破損しやすいので謎メーカーを避けるとして、SanDiskのもので十分かと思います。
    ちょっと心配な人なら東芝製とかがオススメです。(どっちも使ってて壊れたことないですが)

  • LANケーブル これは適宜。VPNブリッジとして利用するので少なくとも2本ぐらいはいると思います。

SDカードにイメージを書き込む

NanoPiの公式サイトから専用のイメージをダウンロードしてきます。
図の「download link」というところから保存できます。

f:id:ysmn_deus:20200817140215p:plain

BaiduとGoogle Driveの2択ありますが、無難にGoogle Driveで良いと思います。(Baidoはパスワード入力などが必要)

f:id:ysmn_deus:20200817232940p:plain

f:id:ysmn_deus:20200817232952p:plain

zipファイルが保存されるので、解凍して「rk3328-sd-friendlycore-bionic-5.4-arm64-20200707.img」を準備します。
通常のRaspberry PiであればRaspbianなどで良いと思います。 Linuxならマウントしてddコマンドでいいですが、Windowsの場合は

  • フォーマット
  • ファイルコピー

が必要になります。

フォーマット

無難にSDメモリカードフォーマッターを利用してフォーマットするのが良いと思いますが、たぶんWindowsの標準機能でフォーマットしてもいいんじゃないかと思います。

www.sdcard.org

利用方法などは上記ウェブサイトのユーザーマニュアルなどを参考にして下さい。

ファイルコピー

結構「Win32 Disk Imager」を利用している例が多かった(公式もそう)のですが、Windows10でうまく動かなかったのでUSBWriterを利用しました。

www.gigafree.net

たぶん使い方は起動すれば分かると思います。

f:id:ysmn_deus:20200817233655p:plain

上記2項目を行えば、SDカードの準備は完了です。

初期設定

とりあえず上記までで事前準備は完了なので、SDカードを挿してWAN側にLANケーブルを挿し、ルーターなどに接続します。
基本的にここまで行けばあとは全てSSH経由で行うので、電源が入っていれば手の届かない所にあっても良いと思います。

IPの確認

基本的にNanoPi R2SはDHCPでIPが割り当てられる環境にいるとします。
(普通にルーターを使っている環境であれば、どっかのポートに挿せばIPが割り当てられます)
NanoPi R2Sにはディスプレイポートなどはないので、IPはルーター側のIPリリース情報から取得します。
これは、ルーターによって異なるので割愛しますが、たぶん管理画面の「DHCP」の設定項目みたいな箇所で確認できます。
(もしくはログ?)

SSHでログイン

ちょっとここで躓きました。公式サイトには「rootでログイン」と書かれており、実際/etc/ssh/sshd_configにも

...
#LoginGraceTime 2m
PermitRootLogin yes
#StrictModes yes
...

となっているのでrootでログインできそうなもんですが、どうもうまくいきません。
(ブラウザからのアクセスもうまくいきませんでした)
とりあえず

  • ユーザー名: pi
  • パスワード: pi(通常のRaspberry Piの場合はraspberry)

でいけたので、これでSSHでログインします。
(Friendly Elecで販売されている「NanoPi」シリーズは、大体ユーザー名がpiでパスワードもpiなので、試してみたらいけました)

ssh pi@[前項目で確認したIPアドレス]

本当はとっととrootにパスワードを付けたい所ですが、念のためIPの固定からやっていきます。

IPの固定

これは色々方法(ルーター側からMACアドレスとIPを紐付けるなど)があると思いますが、とりあえず家のネットワークは基本マシン側からIPを固定しているのでNanoPi R2Sの設定で対応します。
中に乗ってるディストリはUbuntuですので、Ubuntuの公式ネットワーク設定に従います。

ネットワークインターフェースの確認

まずどのインターフェースがWANとLANに割り当てられているか見てみます。

pi@NanoPi-R2S:~$ ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether ba:88:4f:97:b2:26 brd ff:ff:ff:ff:ff:ff
    inet [前項目で確認したIPアドレス]/24 brd 192.168.x.255 scope global eth0
...

[前項目で確認したIPアドレス]があるインターフェースがWAN側のポートになります。
ここではeth0が該当します。

設定ファイルの作成

/etc/netplan/99_config.yamlを作成して、そこに設定を追記します。

pi@NanoPi-R2S:~$ sudo vi /etc/netplan/99_config.yaml
network:
  version: 2
  renderer: networkd
  ethernets:
    eth0:
      dhcp4: false
      addresses: [[前項目で確認したIPアドレス]/24]
      gateway4: 192.168.x.1
      nameservers:
          addresses: [192.168.x.1, 8.8.8.8, 8.8.4.4]

とりあえずGoogle Public DNS(8.8.8.8, 8.8.4.4)も適応しておきます。お好みでCloudflareにしてもいいでしょう。
ファイルを作成できたら

sudo netplan apply

で適応します。
たぶんこれでIPは変わらない筈。

パスワードの変更

とりあえずrootがパス無しなのと、デフォルトユーザーのpiがpiという脆弱極まりない環境が気になるので変更しておきます。

pi@NanoPi-R2S:~$ sudo passwd
Enter new UNIX password:  <- パスワード入力
Retype new UNIX password: <- もう一回入力
passwd: password updated successfully

一応あってるか確認

pi@NanoPi-R2S:~$ su -
Password: <- 先ほど設定したrootのパスワード入力

ユーザーが切り替わればrootのパスワードは変更完了です。
piも変更しておきます。(たぶんこっちをメインに使う事になる)

pi@NanoPi-R2S:~$ passwd
Changing password for pi.
(current) UNIX password: <- 現在のパスワード入力、今は「pi」
Enter new UNIX password: <- 新しいパスワード入力
Retype new UNIX password: <- もう一回入力
passwd: password updated successfully

ログアウトして確認しておくといいでしょう。
(現段階では最悪SSHのrootログインが有効になっているので、パスワードミスって入れなくなった場合はrootでパスワードを変更するとよさげ)

公開鍵認証でログインできるようにする

とりあえずサーバーにパスワードログインのまま放置するのは気持ち悪いので公開鍵認証でログインできるようにしておきます。
鍵の生成や詳しいことに関しては割愛します。(長くなるので)

公開鍵の追記

ログインするユーザーのホームディレクトリに.sshディレクトリを作成してauthorized_keysを追加しておきます。

pi@NanoPi-R2S:~$ mkdir .ssh
pi@NanoPi-R2S:~$ vi .ssh/authorized_keys <- 自分の公開鍵を追記
pi@NanoPi-R2S:~$ sudo chmod 600 .ssh/authorized_keys 
pi@NanoPi-R2S:~$ sudo chmod 700 .ssh/ 

SSHの設定

SSHの設定を変更しておきます。

pi@NanoPi-R2S:~$ sudo vi /etc/ssh/sshd_config

とりあえず個人的に変えておきたい場所だけ記載しておきます。

Protocol 2
Port [22以外の任意のポート]
PermitRootLogin no
PasswordAuthentication no
PermitEmptyPasswords no
#X11Forwarding yes <- デフォルト値はnoなのでコメントアウト

上記を反映させるためにsshをリスタートします。

pi@NanoPi-R2S:~$ sudo /etc/init.d/ssh restart

とりあえず再起動しなければSSHは繋がりっぱなしになってると思うので、今のうちに別クライアントか別窓で鍵認証でSSHログインできるか確かめます。
もしできない場合は上記の設定をしていたウィンドウで設定画面を確かめて下さい。