技術メモ

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

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

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"
  },
~

※20210210加筆

なんかESLintに怒られた(Warning: React version not specified in eslint-plugin-react settings.)ので、.eslintrc.jsの該当箇所に設定を追記。

~
  settings: {
    react: {
      version: 'detect',
    },
  },
~

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

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