そういえば、古き良き時代は自分のブックマークは自動でTwitterに投稿されていたのだった。 今はBlueskyがメインになっているので、同じ仕組みが欲しい、と思った。ので、作った。
要件
自分のブックマークはRSSで取得できる。定期的にチェックして新しいのがあれば、といったロジックで検出できる。 なので、基本的にはプログラムを定期実行できる場所があればGitHub Actionsとかでも良い。 ただ、対象のブクマ内容をpostする前に、それを既にpostしているか否かを知る必要がある。
専用のbotアカウントとかであれば、そのアカウントのpost feedを取得して最近のものをチェックすれば確認可能だが、自分のアカウントに流す場合はその方法だと手動で大量にpostしたりしているタイミングだと埋もれてしまう。 よって、何らかの方法でpost済みのものを保持しておく必要がある。
先行事例
で、良いなと思ったのがこれ。
Cloudflare Workersでも Cron Triggers で定期実行できて、また Workers KV のようなものでデータを保存しておくことができる。 上述要件を満たすプラットフォームとしてはちょうど良さそう。
Rust版
前述のをそのまま使わせていただいても良かったのだけど、折角 ATProtocolのRustライブラリを作っている のだし、Rustで同じような機能のものを作ってみることにした。
WASM対応
まず拙作 ATrium を使ってみようとしたところ、早速ビルドに失敗した。
あまり考えていなかったが、 wasm32-unknown-unknown
のtargetでビルドしようとすると失敗する箇所が多々あった。
ので、まずはそこから。
主に問題は 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として問題なく利用できるようになった。
ただ、本来このFetch APIを使って Transform images もできるはずなのだが、どうもRust版はまだ対応できていないっぽい。
無理矢理やればできるのかな… とはいえOGPの画像はわりと変換かけずにそのまま uploadBlob
しても失敗することは少なそう?なのでしばらく様子見でいいかな。
KVでのSessionStore?
ちなみに atrium-api
ではセッション情報を管理する AtpAgent
というのも用意していて、ここでは SessionStore
も自分で実装することで認証情報を保持しておくことができるように設計していた。
通常は memoryに保持するだけの MemorySessionStore
を利用するが、今回のようなケースで KV
を使ってそこに保持しておくことができないか? と試みたが、やはり Send
trait要求の壁があり上手くいかなかった… これはちょっと対応するのも難しいかなぁ。
そもそも今回のように難しい並列処理とか無いような場合はわざわざsession保持しておかなくても都度ログイン(createSession
)する、で問題なさそうではある。