⛓️

Blueskyでユーザー探しが便利になるツールを作った

2024/03/26に公開

Bluesky Follow Chainというユーザー探しを効率的にできるツールを作りました。
https://bluesky-fc.huuto.dev/ja

どんなサービス?

アカウントを指定してフォローを検索すると、フォローがフォローしているアカウント(フォローチェーン)がわかります。

  • 自分のフォロー同士が繋がっているユーザーが分かる
  • 界隈の人たちを検索してフォローできる

といった使い方ができます。

きっかけ

Blueskyを始めたばかりの頃、知り合いや界隈を探すのが大変でした。

ツイッターはプロフィール画面に「〇〇人にフォローされています」という表示があり、フォローするときの参考にしていました。

こういう機能がBlueskyでもあればいいなと思ったのと、最近Reactに触ってなかったのでリハビリを兼ねて簡単なサービスを作ろうと考えました。

技術スタック

フレームワーク

Next.jsStatic Exportsで使用しています。
シンプルにReactだけでもいいのですが、Next.jsは使い慣れており、React単体で使うことによる変な躓きを避けようと思いました。

CSSフレームワークとしてtailwindを使用。
色々言われてますが、個人でサクッと作るには便利です。

インフラ

CloudflarePagesGitHubからデプロイしています。
初めて使いましたがポチポチするだけでデプロイしてくれるのでデプロイ体験は非常に良かったです。

サーバーサイドやDBはありません。非常にシンプルな構成です。

エディタ

今回はCursorというVSCodeベースの生成AI補助エディタを使い、がっつり生成AIを使った開発をしてみました。

BlueskyAPI

@atproto/apiを使用しました。

https://github.com/bluesky-social/atproto

使い方は公式ドキュメントにあります。ちょっと説明不足なところもあり悩みました(自分にATプロトコルの知識がないのも大きいです)。

https://docs.bsky.app/docs/get-started

アプリパスワード

Blueskyはサードパーティー用のアプリパスワードを発行できるので、ユーザー各自で発行してもらいます。普通のサードパーティー系サービスはユーザーが増えるとリクエスト上限に引っかかったりしますが、ユーザー自身のアカウントを使うのでその心配はありません。

また、現時点ではすべてクライアントで完結しているのでID、パスワードを保管するリスクがありません。

フォローリストの取得

レスポンスごとに最大100アカウントしかフォローを取得できないのでfor文でぐるぐる回しました。

/**
 * ユーザーのフォローリストを取得
 * @param noLimmit 1000フォロー取得の上限をなくす
 */
export async function getFollows(agent: BskyAgent, did: string, message: any, noLimmit?: boolean): Promise<ProfileView[]> {
    let cursor: string | undefined = undefined;
    let follows: ProfileView[] = [];
    // 1レスポンスで取得できるフォローは100
    // デフォルトは最大1000フォローまで取得する。
    // APIの上限(3000/5分)をオーバーした場合はその時点までのフォローを返す。
    for (let i = 0; i < (noLimmit ? 2000 : 10); i++) {
        const followsRes = await agent.getFollows({ actor: did, limit: 100, cursor });
        if (!followsRes.success) {
            throw new Error(message["fetchFailure"]);
        }
        if (followsRes.headers)
            follows = [...follows, ...followsRes.data.follows];
        // console.log('フォローレスポンス:', followsRes);
        cursor = followsRes.data.cursor;
        if (cursor === undefined) break;
    }
    return follows;
}

API制限の悩み

APIの上限は3000リクエスト/5分です。普通のサービスであれば問題ない制限ですが、このサービスの仕組み上厳しいです。

検索対象のアカウントが3000フォローしていれば、その時点でAPIの上限に引っかかります。そうでなくとも一人が複数リクエストを消費することもある(1000フォローしているアカウントは100フォロー×10リクエストを消費する)ので検索上限は3000人より少なくなります。

そのため現時点では1000フォローを上限としています。フォロワーが多いアカウントはよく見かけますが、フォローが多い人は少ないのでそこまで問題はないと思ってます。

「API上限になったら5分待機して続きのフォローを取得する」という方法はありますが、クライアントに何十分もブラウザを開いてもらうのは現実的でなく、サーバーで処理しないといけないので対応するか悩みどころです。

Blueskyにシェア

2024年2月にBlueskyにインテントが追加され、Twitterのシェア機能のようなものが作れるようになりました。使いたかったのですが現時点では改行が機能しないみたいで断念しました(多分バグ?)。

代わりにシェアする内容をコピーして、ユーザー自身でBlueskyにペーストしてもらう形式にしています。

改行バグが直ったらインテントにする予定です。

多言語対応

一応英語も用意しておこうと思いnext-i18nextを導入しました。
ただ、AppRouter + Static Exportsの構成では上手く動きませんでした。

そのため、独自に動的ルーターごとにメッセージを切り替えるなんちゃって多言語対応を実装しました。メッセージ数も40ほどと少ないので問題なく動いてます。

「英語も対応した方が使ってくれるよね」と気軽に対応したものの、予想外に時間を取られてしまいました。

Cursorを使った開発

先述のとおりCursorという生成AIを使えるエディタで開発してみました。

https://qiita.com/railgun-0402/items/717f44df9d68d77309f1

料金

Cursor側でも月20ドルの有料プランがありますが、自分はOpenAI APIを発行して使っています。

利点としては

  • 有料プランはFastGPT-4に制限があるがAPIはない
  • 使わないときは使用料がかからない

先月は週4日くらい開発して20ドルほどだったので、毎日使うわけではない人は有料プランよりAPIの方が安いかもしれません。

使用感

今回はReact、Next.jsのような有名な言語、ライブラリしか使ってないのでかなり良い精度でコード生成してくれました。普段の半分くらいの期間で開発できたと思います。

ログインフォームやカードのようなコンポーネントをざっくりAIで生成して、細かいところは手動で修正していくスタイルが効率的でした。

特に便利だったのはtailwindのCSSで、適当にflexでも使えばいいだろくらいの自分では出てこないスタイルを生成してくれて勉強になります。tailwindはどうしてもクラス名が長くなってしまいますが、自動生成してくれるので時短になりました。

また、「クリップボードにコピー」ってどうやるんだっけ?みたいな場合もググらずにその場で生成できて便利でした。

あとは英語への翻訳で、日本語のメッセージを作ってから「英語に訳して」と指示するだけで自然な英語を生成してくれるのですごい楽でした。もし人気が出たら中国語とかも簡単に追加できます。

悩みどころとしては、Next.jsの場合、AppRouterの情報が少なくPagesRouterの知識で回答されることがよくありました。直近1年くらいの最新機能や利用者の少ないライブラリでは性能を存分に発揮できないかもしれません。

今後

「簡単なサービス」として作ったのであまり拡張するつもりはありませんが、次のような機能があれば便利だとは思います。

  1. フォローチェーンを永続化してリンクから他の人も見れるようにする
  2. フォローチェーン作成をサーバー側で実施(クライアントの負荷軽減)
  3. 1000フォロー以上を対応(サーバー側で3000リクエスト/5分のAPI制限を待機して突破する)

1.はシェアしやすくなるので作りたいです。NoSQLにすればコストも抑えられそうです。
2.3.は、サーバーのコストがかかりそう、サーバレスは起動時間制限がある、非同期になって面倒、などの理由でモチベ低めです。

以上となります。
とりあえず1サービス作り切ったので満足です。

Discussion