最小限のBlueskyのカスタムフィードを作る

## 結論だけ欲しい

- Bluesky のカスタムフィードを作るには、以下の2つのエンドポイントがあればOK
    - `/.well-known/did.json`
    - `/xrpc/app.bsky.feed.getFeedSkeleton`
- ロジック不要であれば、JSON 2 つを配置した静的サイトでも動作可能

## Bluesky のカスタムフィード

https://docs.bsky.app/docs/starter-templates/custom-feeds

Bluesky の機能の1つとして、タイムラインを構築するアルゴリズムを外部で作成できるカスタムフィードがあります。

X(Twitter) などの既存 SNS において、基本的にタイムラインの構築ロジックは運営者が提供するロジックしか利用できませんが、 Bluesky ではその部分を自由に開発し、利用者も自分で好きなものを利用することができます。

例えば、リポストが表示されない [OnlyPosts](https://bsky.app/profile/skyfeed.xyz/feed/only-posts) のようなものや、リポストの直後のポストが流れてくる [RepostNextPost](https://bsky.app/profile/l-tan.dolciss.net/feed/rp-next-post) のようなものが有名です。

また、 Web クライアントである [SkyFeed](https://skyfeed.app/) ではカスタムフィード作成機能を提供しており、検索結果のタイムライン程度であればプログラム不要で誰でも簡単に作ることができます。

## カスタムフィードを自分で作るには

公式で提供されているフィードジェネレータのテンプレートがあります。

https://github.com/bluesky-social/feed-generator

このテンプレートは Node.js/Express で実装されており、任意のサーバにデプロイして登録コマンドを実行することで、オリジナルのカスタムフィードを作成可能です。

- Bluesky の全ポストを購読し、特定の条件に合致するポストを抽出して返す仕組み
- ロジック部分を差し替えるだけでカスタマイズ可能

Bluesky 自体が発展途上の SNS ということもあり、ドキュメントよりもテンプレートのコードを読むほうが良さそうです。また、問い合わせユーザーごとに異なるレスポンスを返す仕組みも備わっており、ただのサンプルというよりは、実際の運用にも繋げていけるようなアプリケーションかなと思います。

## 最小限はどこまで？

とはいえ、やっぱり自分で作るからには、どこまでが最低限ないといけなくて、どこからが追加要素なのかは知りたいところです。

[公式のドキュメント](https://docs.bsky.app/docs/starter-templates/custom-feeds) ではカスタムフィードの仕組みについて以下のように記載されています。

> - A user requests a feed from their server (PDS) using the at-uri of the declared feed
> - The PDS resolves the at-uri and finds the DID doc of the Feed Generator
> - The PDS sends a getFeedSkeleton request to the service endpoint declared in the Feed Generator's DID doc
> - This request is authenticated by a JWT signed by the user's repo signing key
> - The Feed Generator returns a skeleton of the feed to the user's PDS
> - The PDS hydrates the feed (user info, post contents, aggregates, etc.)
> - In the future, the PDS will hydrate the feed with the help of an App View, but for now, the PDS handles hydration itself
> - The PDS returns the hydrated feed to the user

ここで言う PDS は Bluesky 自体のことですね。

これを見る限りだと、「 DID の解決ができて」「 `getFeedSkeleton` が呼び出せる」がカスタムフィードのサーバに必要な最低限の要求に見えます。

### DID の解決

https://atproto.com/ja/specs/did

DID（Decentralized Identifier）は、分散型の識別子を提供する仕組みです。
Bluesky が使っている AT Protocol では現在は `did:web` と `did:plc` に対応しています。

例えば `did:web` の場合は `https://example.com/.well-known/did.json` のようなパスで DID ドキュメントを公開することで利用できます。

以下は Bluesky のカスタムフィードに 期待される DID ドキュメントの例です

https://github.com/bluesky-social/feed-generator/blob/c14c54bd65eb2ea638e0be4303a4b1af53a211a6/src/well-known.ts#L12-L21

```json
{
    "@context": [
        "https://www.w3.org/ns/did/v1"
    ],
    "id": "did:web:example.com",
    "service": [
        {
            "id": "#bsky_fg",
            "type": "BskyFeedGenerator",
            "serviceEndpoint": "https://example.com"
        }
    ]
}
```

service としてフィードジェネレータのエンドポイントになる URL を返すことで、 Bluesky からリクエストを受け付けることができます。

### getFeedSkeleton
https://docs.bsky.app/docs/api/app-bsky-feed-get-feed-skeleton

`getFeedSkeleton` はカスタムフィードに含めるポストの URI を配列で返す API です。

例えば以下のようなレスポンスを返します。

```json
{
    "feed": [
        {
            "post": "at://did:plc:zejpsk4otdzj2uzn6tsbktle/app.bsky.feed.post/3lafwhgzz7w2j"
        }
    ]
}
```

この API の仕様詳細は公式ドキュメントに記載されています。

これで最低限必要な要素は揃います。

## 静的サイトでもできる

ここまでを見ると、最小限の構成でよければ静的サイトでもできそうです。
というわけで実際に試してみました。

https://github.com/rutan/static-bluefeed

以下のような構造で 2 つの JSON ファイルを出力するだけのものです。

```
$ tree -a
.
|-- .well-known
|   `-- did.json
`-- xrpc
    `-- app.bsky.feed.getFeedSkeleton
```

これを任意のサーバにデプロイし、ドメインを設定することで動きます。今回は Cloudflare Pages に置いてみました。

- 用意したエンドポイント
    - https://static-bluefeed.rutan.dev/.well-known/did.json
    - https://static-bluefeed.rutan.dev/xrpc/app.bsky.feed.getFeedSkeleton
- Blusky 上で見られるようにしたもの
    - https://bsky.app/profile/rutan.bsky.social/feed/staticBluefeed

問題なく動いてそうですね！

ただ、静的サイト故に limit とかのパラメータが動かないので、あまり行儀は良くないカスタムフィードサーバではありますね！＞＜；

## おわり

Bluesky のカスタムフィードはとても面白いものです。

普段使いのタイムラインを構築する用途だけでなく、かつて Twitter にあったモーメント機能のように特定のポストをまとめておく場所のような使い方もできますし、考え方次第で面白いことがたくさんできそうです。

タイムラインに何を流すのか、というのは個人の生活にすごく大きな影響を与える要素でもあると考えています。そこをユーザー自身が取捨選択できる仕組みというのは個人的にとても興味深いと思っており、今後の Bluesky の発展に期待したいところです！