技術メモ

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

2021年買って良かったモノ13選

こういうポストをちらほら見かけるので自分もまとめてみます。毎年恒例にできればいいな。
なんやかんやでガジェットみたいなモノと酒しかねぇなという感じではあるんですが・・・

もの

YubiKey 5 NFC

2段階認証しておくのが当たり前になってきた時代ですが、いちいち二次元バーコード読んだりするのもめんどくさいし復元が謎のコードというのもしんどいなぁと思っていたので導入。
NFCも着いてるのでiPhoneの二段階認証もこれで管理できる。セキュリティ意識を高めたい人にはお奨めしたいデバイス
SSHのキーも内部に格納できるので、サーバー周りとAWSのアカウントをまとめて権限を委譲するとかも物理的にできるのでかなり便利です。

Easthills Outdoor(イーストヒルズ) 保冷リュック

これ次の酒カテゴリに入れるか迷いましたが一応汎用性もあるのでこちらで。
なんとこいつは一升瓶が入るのでちょっとした外飲み(夏のBBQなど)に一升瓶を保冷して持って行ける優れもの。
とはいえ今在庫が無いので同じ様なの買うならこちら?

Easthills Outdoors バックパック クーラー 32缶

ヘラマンタイトン ヘララップ

結束バンド「インシュロック」を作っているヘラマンタイトンが出してる電線などをまとめる保護チューブ。スパイラルチューブ自体はよく見ると思いますが、このヘララップはまとめる作業が圧倒的に楽になります。
φ5 ~ 30mmまでいろんな種類があるので適宜選んでもらえたら。自分は用途的に8mmを選択しましたが、PC周りの整理とかに使いたい場合は16mmのものがいいかも。
より詳細が知りたい場合はヘラマンタイトンのウェブサイトを確認して下さい。

www.hellermanntyton.co.jp

Elgato Stream Deck

自分が買ったのは一世代前ですが、妙に値上がりしてるので現行モデルを載せてます。
ストリーマー用のガジェットなんですが、普通に左手デバイスとして便利です。
青軸キーボードの左手デバイスを使っていましたが、アプリケーションによって配置を覚えるのが大変なんです。このデバイスはボタンがディスプレイになっているので、ボタンの役割が一目見ただけで分かるのがとても便利です。
更に、機能によっては動的な表示(例えばタイマーだとカウントダウンの数値がでる)もできるのでそういうところも便利。
Toggl Trackなどタイムトラッキングアプリを利用している人には超お薦めデバイスです。

ソニー 完全ワイヤレスイヤホン WF-SP900

ここ数年ぐらいは風呂でシャワーを浴びている時ですら音を聞いているのでIP68ぐらいの防水機能は欲しいところ。(スマホのスピーカーではシャワー時に音が聞けません)
最初は昔使っていたAftershokzのTitaniumを使っていた(とはいえコレはIP55なのでお風呂での使用は推奨しません)のですが、折れて故障するトラブルになってしまい別のモノを探すことに・・・
実は3000円~5000円ぐらいのデバイスを5つぐらい試したのですが、全て一ヶ月前後で故障しました。まぁシャワーまで耐えられるデバイスの方が本来不思議なんですが。
このデバイスを購入して2ヶ月ほどなので、ここまで故障しなかったデバイスはコレが初めてです。
とはいえ説明書にも「シャワーなどの利用は想定していません」と書かれているので一応非推奨です。ご注意を。

ソニー ワイヤレスノイズキャンセリングイヤホン WF-1000XM4

今まではWF-1000XM3を利用していてそこまでバージョンアップする必要性もないと思っていたんですが、コンプライ Ts-200を愛用しているとランニングコストがまぁまぁかかることに気づき、それなら新しいやつ買うかぁと思って買いました。
ノイズキャンセリングのレベルも上がっていますが、個人的に嬉しかったのが風切り音の低減(WF-1000XM3は少し風が吹くと鼓膜がなくなる)と外音取り込みがよりクリアに聞こえる様になったと感じてます。
あとはイヤーピースの劣化度合いがどんなもんか検証するのみです。

サンワダイレクト キーボードスライダー

基本はキーボードが目の前にあれば嬉しいのですが、何か書きながらキーボードを利用したいときなどはいくら机が広くてもどちらかを奥に追いやるワケにも行きません。
そういうときに便利なのがこのキーボードスライダー。机の下にキーボードをおいておけば、キーボードで作業しながらノートを書くということも出来ます。
机の広さに応じて大きさを選ぶと良いと思いますが、マウスもおきたいならMかLをオススメします。

個人的に買って良かった酒関連です。飲んで良かったというよりは1本買って良かったという意味合いが深いかも。
色々な要素を鑑みて絞りました。

グレンケアン ブレンダーズ モルトグラス 6個セット

いきなり酒ではないんですがやはり飲み比べするときに並べて飲み比べたいもの。
モルトグラスを必要最低限のクオリティで個数を揃えるならグレンケアン一択です。蒸留所などに行く機会がある人はおそらく蒸留所で買うのが安いしロゴが入っててオシャレなのでそこで買うのがオススメです。
(蒸留所では大体一脚500~800円ぐらいで売ってると思います)
有り難いことにモルト飲み比べをしてくれる友人がいるので今年買い足しました。

Douglas Laing XOP Miltonduff 25 yo [1994]

f:id:ysmn_deus:20211230152206j:plain

ダグラスレインのXOPシリーズのミルトンダフ。最高にうまい。蜂蜜系のあま~いウイスキーが好きなら絶対好き。
長熟にしかない複雑みも感じられるのに比較的低価格なのはとても良いリリース。

Bruichladdich The Ternary Project

f:id:ysmn_deus:20211230152244j:plain

国内で輸入してるお店はあまり見たことないですが直販から買えます
いわゆるクラシックラディとポートシャーロットとオクトモアのバッティングモルト。混ぜれば良いってもんじゃねぇぞ!と怒りたくもなる内容ですがおそらく飲めばその真価を感じられるかと。
ポートシャーロットとオクトモアの占める割合が高いので、それはもう煙くさいウイスキーなんですが麦芽の甘みや乳酸感とのバランスもしっかり取れているし余韻もかなり続くドチャクソ満足度の高い一本です。

ノブクリーク 9年 シングルバレル・リザーブ

今年買ったコスパ部門1位だと思うバーボン。60度を超しててシングルバレルで5000円以下なら文句の言い様はないでしょう。
やはりバーボンはハイプルーフバチバチのパンチ感にバニラと樽感が乗ってきたらそれはもう美味しい!
あんまりコスパコスパ言いたくはないですが、地域毎にコスパ良いものを考えて行くのも楽しそうです。

【九尾】なすひかり48% 生酒

f:id:ysmn_deus:20211230165524j:plain

個人的に勝手に推してる天鷹酒造さんの【九尾】シリーズで通年販売になったリリース。
香りや旨みなどのバランスが好きで、挑戦的なシリーズなのに良心的な価格設定なので是非いろんな人に飲んでいただきたいと思えるし、自分でもリピートしたいと思える日本酒です。

WAKAZE THE BARREL LIMITED -CRONOS-

f:id:ysmn_deus:20211230145254j:plain

限定ボトルで今は買えないんですが、もしかしたらそのうち買えるようになるかもしれないので。
貴醸酒の様な造りの原酒をコニャック樽で熟成させた日本酒で、香り味わいのほとんどが洋酒!って感じのフルーティーで甘いのですが、飲むと奥に日本酒を感じるとても面白いリリースでした。
同じ様なリンク8888も去年飲んで美味しかったのですが、今年もこんな完成度が高く面白味もあるSAKEに出会えて幸せでした。

ほんとはもっといっぱい紹介したいお酒はありますが、まぁ厳選ということでこの辺で。

Raspberry Pi Zero Wのセットアップ

防備録も兼ねてRaspberry Pi Zero Wのセットアップ方法をかいておきます。
後で気づいたんですが多分IPの固定もheadless setupが可能だと思います。(時間があればどっかでチャレンジします)

必要なモノ

細々したのがめんどくさいという人はスターターキットを買ってしまうのも手でしょう。

amzn.to

セットアップ

OSのインストール

Raspberry Pi Imagerのセットアップ

今はRaspberry Pi Imagerという便利ツールがあるのでソレを利用すると簡単にSDカードのセットアップが出来ます。
Raspberry Piのサイトから該当のインストーラーをダウンロードしてインストールして下さい。

f:id:ysmn_deus:20210415142740p:plain

f:id:ysmn_deus:20210415142931p:plain
起動するとこんなかんじ

SDカードのセットアップ

Raspberry Pi Zeroは基本GUIは使わないと思うのでLite版をインストールします。Operating Systemの項目はPi OS LITEを選択します。

f:id:ysmn_deus:20210415143209p:plain f:id:ysmn_deus:20210415143218p:plain

Storageは各々のSDカードを指定してください。

上記が完了すればWRITEというボタンが押せると思うので書き込み開始してください。

f:id:ysmn_deus:20210415143407p:plain

f:id:ysmn_deus:20210415143502p:plain
SDの中身消えるけどほんまにええんやな?という確認

暫く待つと完了すると思います。

無線LANのセットアップ

画面とキーボードを接続してセットアップしても良いんですが、基本的にminiHDMIやmicroUSB A→USB Aみたいな気持ち悪い変換を普通は持っていないと思います。
なので画面など無しでセットアップ(headlessみたいなワードで検索すると色々出てくると思います)します。

SSHの有効化

公式ドキュメントに従い、SDカードのルートディレクトリ(一番上)に'ssh'という空ファイルを作ります。
どんな方法でも良いですが、Windowsならファイルパスを利用するバーを利用するのが手軽でいいです。
SDカードのディレクトリ(自分の場合はH: でした)にエクスプローラーで移動して、パスのバーに

cmd /C copy nul ssh

と入力してEnterを押すと、開いているディレクトリにsshという名前の空ファイルが作成されます。

f:id:ysmn_deus:20210416183337p:plain

ファイルが出来てればOKです。

WiFiの設定

公式ドキュメントに従いますが、bootフォルダを作成してくれと書かれていますがたぶんSD名がbootなのでこれは無視してカード直下にwpa_supplicant.confというファイルを作成します。このファイルに設定を記載します。

ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
update_config=1
country=JP

network={
 ssid="<WiFiのSSID>"
 psk="<WiFiのパスワード>"
}

上記で設定は完了です。
電源を入れてしばらく待ちます(たぶん5分以内ぐらいだったと思います。)

初期設定

SSHでログイン

初期設定を行う為、SSHでログインします。
上記までのセットアップでssh pi@raspberrypi.localでログインできる筈です。
デフォルトのパスワードはraspberryです。

raspi-config

基本的な初期設定はsudo raspi-configで行えるようになっています。

f:id:ysmn_deus:20210417184216p:plain

raspi-configでは

を調整します。

パスワード変更

raspi-configの画面は矢印キーとEnterで操作できますが、一番上の1 System OptionsでEnterを押します。
その次にS3 PasswordをEnterで選択します。

f:id:ysmn_deus:20210417184644p:plain

ウィザードに従ってパスワードを設定します。

f:id:ysmn_deus:20210417184738p:plain
二度パスワードを入力させられる

これで次回以降ログイン時にはパスワードが今決めたものになります。
あとでSSHの公開鍵認証に変更しようと思うので、ログインで使う事は無いかもしれませんが念のため変更しておくことをお薦めします。

地域情報の設定

sudo raspi-configで対話式の設定項目を出した後に5 Localisation Optionsに入ります。

f:id:ysmn_deus:20210420172128p:plain

次にL1 Localeを選択します。

f:id:ysmn_deus:20210420172933p:plain

そうするとConfigureing localesという画面に切り替わります。
キー操作で上下に移動出来ると思います。デフォルトではen_GB.UTF-8 UTF-8が選択されていますが、一般的なen_US.UTF-8 UTF-8も追加しておきます。
日本語が使いたい人はja_JP.UTF-8 UTF-8も選択しても良いかもしれません。

f:id:ysmn_deus:20210420173038p:plain

default localeをen_US.UTF-8 UTF-8にして完了

f:id:ysmn_deus:20210420173824p:plain

次にタイムゾーンを変更します。
先ほどの5 Localisation Optionsに入ったあとで次はL2 Timezoneを選択します。

f:id:ysmn_deus:20210420173834p:plain

自分は日本に合わせたいのでAsiaを選択します。

f:id:ysmn_deus:20210420173841p:plain

町の一覧が出てくるのでTokyoを選択します。

f:id:ysmn_deus:20210420173845p:plain

これで完了です。一応時間がちゃんと設定されているか確認するにはdateコマンドなどを使ってみるのも良いでしょう。

IPの固定

今はDHCPでIPが降ってきてるだけなので固定しておきます。
公式サイトに従い、/etc/dhcpcd.confの最後に追記します。

interface wlan0
static ip_address=192.168.0.4/24    
static routers=192.168.0.254
static domain_name_servers=192.168.0.254

(この辺の設定は適宜変えて下さい。)

IPは適当なモノか、思いつかなければ降ってきてるIPを固定するのでも良いでしょう。
自分はip addr showコマンドで確認してそれを利用しました。

WiFiの追加設定

一応WiFiのパスワードを平文で入力しているのでハッシュ化しておきます。
wpa_passphraseコマンドで必要情報が出力できるので、コレを利用します。

sudo sh -c 'wpa_passphrase "SSHD名" "パスワード" >> /etc/wpa_supplicant/wpa_supplicant.conf'

以前のWiFiの設定やパスワードなどが記載された状態なので、適宜/etc/wpa_supplicant/wpa_supplicant.confを編集して下さい。
たぶん

network={
 ssid="<WiFiのSSID>"
 psk="<WiFiのパスワード>"
}
network={
 ssid="<WiFiのSSID>"
 #psk="<WiFiのパスワード>"
 psk=ハッシュ値
}

みたいになってるので、

network={
 ssid="<WiFiのSSID>"
 psk=ハッシュ値
}

にしてあげて下さい。

公開鍵認証の設定

ここは通常のLinuxと変わらないと思いますので割愛します。

パッケージなどの更新

この辺まできたら大体完了なのでパッケージの更新などをしておきます。

sudo apt update && sudo apt upgrade -y
sudo apt dist-upgrade

速度を気にされる方はリポジトリを日本にしてから更新した方が早いと思います。
pipのインストールあたりでたぶん悪さするので推奨しません。

sudo sh -c 'mv /etc/apt/sources.list /etc/apt/sources_old.list; echo "deb http://ftp.jaist.ac.jp/raspbian stretch main contrib non-free rpi" > /etc/apt/sources.list'

create-react-appで作成したプロジェクトのテスト

f:id:ysmn_deus:20210329174147p:plain React周辺は周りの誰にも聞けないし本当に手探りで色々やってます。
いいかげんフロントでもテストを導入しないと意味不明になってきたので、まずはReact自体のテストを学習します。
基本的にはJest公式などを参考にしながら進めています。

環境構築

プロジェクトの作成

この辺はすっ飛ばしても良さそうですが、今回は学習ということで新規にプロジェクトを作成していきます。
いつもはNext.jsを利用していますが、極力シンプルに進めたいので通常のReactプロジェクトを作成していきます。

npx create-react-app react-test-learning --template typescript

とりあえず実行しておきます

cd react-test-learning
yarn start

いつものくるくるReactマークが出ればOKです。

ロジックのテスト

簡単なテスト

まずはReactのテストというよりJavaScriptのテストを行ってみます。
srcディレクトリにsum.tssum.test.tsを追加します。

sum.ts

export const sum = (a: number, b: number): number => {
  return a + b
}

sum.test.ts

import {sum} from "./sum"

test("add 1 + 2 to equal 3", () => {
  expect(sum(1,2)).toBe(3)
})

これでとりあえずテストは回る筈です。
yarn testでJestを実行します。

 PASS  src/sum.test.ts
  √ add 1 + 2 to equal 3 (2 ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        1.888 s, estimated 4 s
Ran all test suites related to changed files.

とりあえず良さそうです。
クリーンなプロジェクトだとTypeScriptを利用しているのでBabelの設定などいるんでしょうが、create-react-appを利用してプロジェクトを作成しているのでその辺はよしなにしてくれているっぽいです。

ちゃんとしたテスト入門であればexpectMatcherを色々見ていくところですが、その辺はやりながら覚えることにします。
基本に立ち返る際にはJestの公式を参照したいところです。

jestjs.io

非同期のテスト

非同期のコードが含まれているテストにはテストに渡す関数の引数に与えられるdoneを使うかPromiseで処理するかのどちらかだそうです。
おそらくナウいのはPromiseだと思いますし、今は async/await を多用すると思うのでその辺で試してみます。
めんどくさいので先ほどのsum.tsに追記します。

sum.ts

~

export const promiseFunction = (value: number) => {
  return new Promise((resolve)=>{
    setTimeout(() => {
      resolve(value)
    }, 1000)
  })
}

sum.test.ts

import {sum, promiseFunction} from "./sum"

~

test("async test", async () => {
  const value = await promiseFunction(100)
  expect(value).toBe(100)
})

Jestを実行します。

 PASS  src/sum.test.ts
  √ add 1 + 2 to equal 3 (1 ms)
  √ async test (1000 ms)

Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        3.8 s
Ran all test suites related to changed files.

ちゃんとasyncの方はsetTimeoutで設定した1000msかかってます。

describeブロックとかスコープの話

簡単なテストであれば上記までのtestで処理出来そうですが、もう少し大がかりなテストになってくるとブロック化してテストを回したくなるはずです。(例えば、どこかに通信して取得したデータに対して、それぞれ処理を行うモジュールなど)
テストファイルのグローバルにbeforeEachbeforeAllなどを記載することも出来ますが、describeブロックに分けて実行する方がシンプルだと思います。
セットアップとティアダウンに関しては公式の情報が参考になると思います。

セットアップと破棄 · Jest

testとit

ウェブ上ではitを利用しているテストもありましたが、testはitのエイリアスなので全く同じモノのようです。
ただ公式ドキュメントでもitを利用してテストを書いているケースはないので可読性?からかtestを利用する方がよさそうです。

Reactのテスト

スナップショットテスト

JavaScript(TypeScript)のテストは上記までのもので大丈夫そうではある(モックとかあるけどその辺は後回し)ので、Reactのテストを行っていきます。
まずはレンダリングがうまくいってるか分かりやすいスナップショットテストを行ってみます。
create-react-appでプロジェクトを作成していると、App.tsxApp.test.tsxが自動で作られているのでコレを使い回します。

テストするコードはJestの公式を参考に、react-test-rendererを使用しない形でスナップショットを取っています。

App.tsx

import React, {useState} from 'react';
import logo from './logo.svg';
import './App.css';

type Status = "normal" | "hovered"

const App = () => {
  const [status, setStatus] = useState<Status>("normal")

  const onMouseEnter = () => {
    setStatus("hovered")
  }

  const onMouseLeave = () => {
    setStatus("normal")
  }

  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>
          Edit <code>src/App.tsx</code> and save to reload.
        </p>
        <a
          className={status}
          href="https://reactjs.org"
          onMouseEnter={onMouseEnter}
          onMouseLeave={onMouseLeave}
        >
          Learn React
        </a>
      </header>
    </div>
  )
}

export default App

App.test.tsx

import React from 'react'
import { render, screen, fireEvent } from '@testing-library/react'
import App from './App'

test('changes the class when hovered', () => {
  const testComponent = render(<App />)
  expect(testComponent).toMatchSnapshot()
  fireEvent.mouseEnter(screen.getByRole("link"))
  expect(testComponent).toMatchSnapshot()
});

Jestを実行すると、srcディレクトリ下に__snapshots__ディレクトリが作成され、スナップショットが保存されていると思います。

とはいえ、この辺は個人的にはStorybookのVisual Testingで良い気がするのでたぶん使わないと思います。

レンダリングテスト

データなどを受信したり非同期処理を行った後に正しくレンダリングされているかテストします。
とはいえこの辺は実際に作って見て考える要素が大きいので、ここではTesting Libraryの公式例を挙げておくにとどめておきます。

testing-library.com

基本的にはロジックのテストと同様に行いますが、下記に注意しながらテストを組み立てていくようです。

  • 検証したい要素をTesting LibraryのscreenのメソッドのgetByTextgetByRoleなどで取得して評価する
  • イベントの発火はTesting LibraryのfireEventを利用して発火させる
  • イベントの発火の影響を待つ場合はTesting LibraryのwaitForを利用してawaitで待つ

という感じのようです。
もしテストに慣れて記事に出来る程度に知識がついたら別の記事にするかも知れません。

Next.js+TypeScript+CSS Modulesの環境でStorybookを使う

去年の10月に同じような記事を書いた気がするんですが、もううまく動かなくなってました。
元々フロントは専門外なのでちょくちょくしか触らないのですが、なかなかに辛い・・・
と思ってたのも1日。やはり世界には優秀な方々がたくさんいるようで、既に解決策を提示して下さっております。

Configure Storybook to work with Next.js, TypeScript, and CSS Modules · GitHub

main.jswebpackFinal/\.module\.css$/CSS Modulesのファイル)をstyle-loader+css-loaderで読み込ませようという処理を試みている記事はウェブ上にいくらか見つけていたんですが、何故かうまくいってませんでした。
この記事では一旦普通のCSSファイルをエスケープするために

~
    newConfig.module.rules.find(
      rule => rule.test.toString() === '/\\.css$/'
    ).exclude = /\.module\.css$/;
~

という処理を挟んでいるので、コレが効いてくるのでしょう。
おそらくStorybookのCSSをWebpackのどこかの処理で読み込んでいるのがCSS Modulesと競合してうにゃうにゃなっているんでしょうか。フロントエンド何も分からないマンとしては意味不明です。

上記のgistとその参照元を参考に、CSS Modulesだけ適応したいので、.storybook内にあるファイルは下記のように変更しました。
一応style-loaderとcss-loaderが入ってない場合は入れといて下さい。

yarn add -D style-loader css-loader

まずはmain.js

const path = require('path')

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

次にnext-preset.jsを作成して、下記の通りにしました。

module.exports = {
  webpackFinal: async (config) => {
    const { module = {} } = config

    const newConfig = {
      ...config,
      module: {
        ...module,
        rules: [...(module.rules || [])],
      },
    }

    newConfig.module.rules.find(
      (rule) => rule.test.toString() === '/\\.css$/'
    ).exclude = /\.module\.css$/

    newConfig.module.rules.push({
      test: /\.module\.css$/,
      use: [
        'style-loader',
        {
          loader: 'css-loader',
          options: {
            importLoaders: 1,
            modules: true,
          },
        },
      ],
    })

    return newConfig
  },
}

preview.jsはとりあえずそのままです。グローバルでCSSを読み込ませたい場合はここで読み込ませるみたいです。
一応例を挙げるなら

import '../src/styles.css';

とか。

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を適応する

※2021-02-10 追記、下記の方法や他の方法でStorybook上でCSS Moduleがうまくあたらないので、今後はstyled-componentsの採用を検討しています。 そのうちまたこの方法でうまくいくようになるかも知れないので残しておきますが、現段階ではうまくいきませんでした

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リポジトリからデプロイされる方式を採る方がいいかもしれません。