Open6

Twitterの興味関心チェックリストを自動で外したい

でつでつ

https://zenn.dev/detsu/articles/6e7c1ef636d8e2
↑これを自動化したい
このチェックボックス、すぐ復活して3日も放っとけばあの紫の星の通知が飛んでくるので


そもそもブラウザの操作を自動化する手段を全く知らなかったので色々調べた
今のところはなんか新しめでよさげなPlaywrightを使おうと思っている
https://playwright.dev/

とりあえずローカルでちゃんと動くところまで完成までさせて、余裕があればAWS LambdaかCloud Functionsにデプロイしたい
その場合、インストールにやや苦労する可能性があるらしいことをメモしておく
https://zenn.dev/captain_blue/articles/5a0d426453a350e089ed#playwright-aws-lambdaを使う

でつでつ

Twitterにログインする処理を書くとき参考になりそうな資料
https://zenn.dev/link/comments/699c6a0d654492
https://playwright.dev/docs/auth
多数のアカウントを処理するつもりなので、なるべく効率良くやりたい
上記記事で紹介されているcookiesやlocal storageを使う方法はその点良さそう

でつでつ

↓にログイン状態をjsonに保存する簡単な方法が載っていた
コマンドを打つだけなので、このためだけのスクリプトを書かなくて済む
https://playwright.dev/docs/cli#preserve-authenticated-state

--save-storageオプションをつけてcodegenコマンドを実行
ログイン後、ウィンドウを閉じるとauth.jsonが保存されている

npx playwright codegen --save-storage=auth.json
# Perform authentication and exit.
# auth.json will contain the storage state.

--load-storageオプションでちゃんとログインできることを確認できる

npx playwright open --load-storage=auth.json twitter.com

複数アカウントにログインしたらどうなるかはまだ調べていないが、腹減ったんで一旦終わり

でつでつ

取得したauth.jsonをスクリプトから参照するには次のように書けばよい

+ // Create a new context with the saved storage state.
+ const context = await browser.newContext({ storageState: 'auth.json' })
+
+ const page = await context.newPage()
- const page = await browser.newPage()

pagebrowser.newpage()から作っている場合はcontext.newpage()に直すのを忘れてはいけない

でつでつ

複数アカウントにログインしても問題なかった
アカウント切り替えのモーダルに自分のアカウントがズラッと並んでいるはずなので、あとはここのクリックを自動化できればok
codegenコマンドでいい感じのコードができるので、それをひな形にすれば良い

さて、ループ回数を決めるためにここからアカウントの数を取得したいわけだが、アカウント切り替えモーダルが表示されるまでのラグを明示的に待つ必要があることに気づかず、ちょっとつまずいた

https://playwright.dev/docs/actionability
↑のページにある通り、playwrightにはclickhoverなどの操作をする際、実行可能になるまで自動で待ってくれる仕組みがある
しかし、数を数えるcountは(対象が0個のときもありえるので)その仕組みがなく、表示待ちをしたいときは以下のように明示してやる必要がある

// Wait until all the accounts become visible (i.e. countable)
await page.waitForSelector('[data-testid="UserCell"]')

// Now count them
const myAccountNum = await page.locator('[data-testid="UserCell"]').count()

あとはアカウントを末尾から選んでいけば良い
N-th element selector-1を指定すればok

await Promise.all([
    page.waitForNavigation(),
    page.click('[data-testid="UserCell"] >> nth=-1')
])

なお、同じことをtwitterのホームでやるとおすすめユーザーのUserCellも引っかかって、しかも末尾にくるので、ひと工夫必要(アカウント切り替えモーダルを出す前のUserCellの数を数えておくとか)

でつでつ

https://twitter.com/i/api/1.1/account/personalization/p13n_preferences.jsonをブロックするのが中々うまくいかず断念してしまった

playwrightではroute(url, route => route.abort())でリクエストをブロックできる
しかし、現状service workerが絡むリクエストには介入できないようで、まさにここに引っかかってしまった
https://playwright.dev/docs/api/class-browsercontext#browser-context-route
https://github.com/microsoft/playwright/issues/1090

実は部分的には解決できていて、一番目のアカウントのみに限れば、service workerを無効化できて、リクエストブロックもできた
ただアカウント切替時、ゾンビのように復活するservice workerを無効化する手立てが見つからなかった

結局採用はしていないのだが、一応その方法を残しておく
具体的には↓のように書くと、service workerを登録解除する処理をページ内環境で実行させられる

async function killSW() {
    const registrations = await navigator.serviceWorker.getRegistrations()
    registrations.forEach( r => r.unregister() )
}

page.evaluate(killSW)

どうもアカウントが切り替わる瞬間にtwitter.com/homeに飛んでいくのが鍵な気はするのだが、割といろいろ試してもう疲れたのでここは妥協することにした

まあそもそもブロックが必要なのは、解除しなきゃいけないチェックがいっぱいあるという前提で、リクエストが大量に送信されるのを防ぐためだったりする
で、毎日自動実行してたらそんなにチェックたまらんやろ!wということでここらへんの対策は何もしないことにした
初回だけはブラウザでやってあげたほうがいいかもしれない