atcuteを使ってCARをブラウザ上で参照する

@nus.bsky.social

これは Bluesky / ATProtocol Advent Calendar 2025 16日目の記事です。

前日のmaril氏の記事 でさらに高度なことをされているので興味がある方はそちらもご覧ください。

ブラウザ上でCARファイルを開いてBlueskyのRepoの中身を見たい(過去ポストを高速検索したい)と思って、公式の @atproto/repo を試したことがあるのですが、こちらはNode.jsを必要とするためブラウザで使用することができませんでした。

仕方なく自前でNode.js非依存のCARパーサを実装してログ検索に使っていたのですが、現在活発に開発されているらしいTypeScriptのパッケージmary-ext/atcute にRepo操作用のパッケージも含まれており、これを試してみたところかなり軽快に動作したので簡単な使用例を紹介します。

  • A. Repoのトップレベル(Repoに関連付けられたアカウントDIDやリビジョンなど)へのアクセス方法です。
  • B. Repo内のレコードをcollection別に処理する方法です。
// サンプルコード
import { decode } from "@atcute/cbor";
import { fromUint8Array as carFromUint8Array } from "@atcute/car";
import { fromString as cidFromString, equals as cidEquals } from "@atcute/cid";
import { fromUint8Array as repoFromUint8Array } from "@atcute/repo";
//for lexicon schema check
import { is } from "@atcute/lexicons";
import { AppBskyFeedPost, AppBskyActorProfile } from "@atcute/bluesky";

  const  arraybuf: ArrayBuffer = {/*CAR data from file or /xrpc/com.atproto.sync.getRepo */ }

  const uint8data = new Uint8Array(arrayBuf);

  // A. Parse CAR to extract top-level data object
  // https://atproto.com/specs/repository#commit-objects
  const car = carFromUint8Array(uint8data);
  const roots = car.roots;
  const rootCid = cidFromString(roots[0].$link);
  for (const entry of car) {
    if (roots.length > 0 && cidEquals(entry.cid, rootCid)) {
      const decoded = decode(entry.bytes);
      console.log("Repo DID:", decoded["did"]);
      console.log("Repo Rev:", decoded["rev"]);
      break;
    }
  }

  // B. Parse repo entries
  const repo = repoFromUint8Array(uint8data);
  for (const entry of repo) {
    // ^? RepoEntry { collection: 'app.bsky.feed.post', rkey: '3lprcc55bb222', ... }
    switch (entry.collection) {
      case "app.bsky.feed.post": {
        const record = decode(entry.bytes);
        if (is(AppBskyFeedPost.mainSchema, record)) {
          // 各ポストに対する処理
        } else {
          console.warn(`Skipping invalid post record at rkey: ${entry.rkey}`);
        }
        break;
      }
      case "app.bsky.actor.profile": {
        const record = decode(entry.bytes);
        if (is(AppBskyActorProfile.mainSchema, record)) {
          console.log("profile record:", profile);
        } else {
          console.warn(
            `Skipping invalid profile record at rkey: ${entry.rkey}`
          );
        }
        break;
      }
      default:
        // do nothing
        break;
    }
  }

良いライブラリなので今後も利用したいと思います。

これを使って抽出したポストをDuckDB Wasmに保存し、ブラウザ上で完結する自分用の高速検索ログビューアを作って使っています。その様子がこちらです。

https://bsky.app/profile/nus.bsky.social/post/3m7umeiqpek2q

以上、簡単な紹介でした。

nus.bsky.social
なす(Nus)

@nus.bsky.social

フィードを作ったり絵を描いたりBlueskyのツール作ったり妄想したり。
適当に話しかけてもらったら適当に返します。

✒作ったものとか: https://linkat.blue/nus.bsky.social

Post reaction in Bluesky

*To be shown as a reaction, include article link in the post or add link card

Reactions from everyone (0)