Cloudflare Workersで、自分のはてブをBlueskyに流す

bsky.app

そういえば、古き良き時代は自分のブックマークは自動でTwitterに投稿されていたのだった。 今はBlueskyがメインになっているので、同じ仕組みが欲しい、と思った。ので、作った。

github.com

要件

自分のブックマークはRSSで取得できる。定期的にチェックして新しいのがあれば、といったロジックで検出できる。 なので、基本的にはプログラムを定期実行できる場所があればGitHub Actionsとかでも良い。 ただ、対象のブクマ内容をpostする前に、それを既にpostしているか否かを知る必要がある。

専用のbotアカウントとかであれば、そのアカウントのpost feedを取得して最近のものをチェックすれば確認可能だが、自分のアカウントに流す場合はその方法だと手動で大量にpostしたりしているタイミングだと埋もれてしまう。 よって、何らかの方法でpost済みのものを保持しておく必要がある。

先行事例

で、良いなと思ったのがこれ。

github.com

Cloudflare Workersでも Cron Triggers で定期実行できて、また Workers KV のようなものでデータを保存しておくことができる。 上述要件を満たすプラットフォームとしてはちょうど良さそう。

Rust版

前述のをそのまま使わせていただいても良かったのだけど、折角 ATProtocolのRustライブラリを作っている のだし、Rustで同じような機能のものを作ってみることにした。

WASM対応

まず拙作 ATrium を使ってみようとしたところ、早速ビルドに失敗した。 あまり考えていなかったが、 wasm32-unknown-unknown のtargetでビルドしようとすると失敗する箇所が多々あった。

ので、まずはそこから。

github.com

主に問題は async-trait を使っているところで、これが Send を要求するため wasm32 targetの場合に問題になるようだった。 これは (?Send) を追加することで回避できるとのこと。 wasm32 targetのときだけこれを追加する形に変更した。他のtargetの場合は何も影響を受けない。

#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]

あとは atrium-xrpc-client という専用の非同期HTTP Clientを幾つか提供していたが、それらは reqwest backendのもの以外はすべてwasm32には対応していなかったので、諸々変更して wasm32 の場合は reqwest のClientだけが利用可能になるようにした。

Cloudflare Workersでの実装

以前の記事 でも書いた通り、Cloudflare WorkersはRust環境でも作成することができる。今回のものもJavaScript/TypeScriptを1行も書かずに作成できた。

Cron Triggers用に #[event(scheduled)] をつけた関数を実装するだけ。

1MB制限との戦い

うっかり色んなライブラリを依存に入れていると、ビルド後のサイズが無料枠上限の1MBを超えてしまう。

RSSの解析に最初は feed-rs を使っていたが、ちょっと大きいようだったのでもっとシンプルにRSS 1.0のものだけ扱える rss に変更したりした。

Fetch API

ローカル実行する時の感覚で reqwest を、コンテンツ取得や atrium-xrpc-client のバックエンドとして使っていたが、実は必要なかった… worker-rs には worker::Fetch があり、これを使うことでHTTPリクエストを処理できる。

ATriumでは、非同期HTTP Clientとしてはどんなものを使っても良いようにTraitだけ用意して開発者が自由にClientを実装・選択できるように設計していた。 ので、今回は worker::Fetch を使うように少し繋ぐ部分を書くだけでXRPC Clientとして問題なく利用できるようになった。

github.com

ただ、本来このFetch APIを使って Transform images もできるはずなのだが、どうもRust版はまだ対応できていないっぽい。

Add support for the `cf.image` field in `fetch()` properties by jakubadamw · Pull Request #351 · cloudflare/workers-rs · GitHub

無理矢理やればできるのかな… とはいえOGPの画像はわりと変換かけずにそのまま uploadBlob しても失敗することは少なそう?なのでしばらく様子見でいいかな。

KVでのSessionStore?

ちなみに atrium-api ではセッション情報を管理する AtpAgent というのも用意していて、ここでは SessionStore も自分で実装することで認証情報を保持しておくことができるように設計していた。 通常は memoryに保持するだけの MemorySessionStore を利用するが、今回のようなケースで KV を使ってそこに保持しておくことができないか? と試みたが、やはり Send trait要求の壁があり上手くいかなかった… これはちょっと対応するのも難しいかなぁ。

そもそも今回のように難しい並列処理とか無いような場合はわざわざsession保持しておかなくても都度ログイン(createSession)する、で問題なさそうではある。