はじめに
この記事は、Bluesky Advent Calendar 2024 16日目の記事です。
はじめまして、marilと申します。
今回は、ユーザーのPDSにアニメの視聴状況を保存できるツール「AniBlue」のベータ版としてひとまず動くものができたので、ご紹介させていただきます。
AT Protocolに関してまだまだ初心者なので、間違いや改善点等ありましたらご指摘いただけると助かります。
作ったもの
GitHub
「AniBlue」は、アニメの視聴ステータス/お気に入りなどの情報を、すべてユーザーのPDSに保存することができるアニメ視聴管理ツールです。
アニメのタイトルやエピソードなどの情報の取得には、Annict APIを使用させていただきました。
機能
選択したアニメを
- 見たい
- 視聴中
- 視聴済み
- お気に入り
の状態に設定して、一覧で管理することができます。
また、画像のように視聴しているエピソードを記録することもできます。
Lexicon
アニメの視聴ステータスは、以下のようなlexiconで配列として保存されます。
{
"lexicon": 1,
"id": "app.netlify.aniblue.status",
"defs": {
"main": {
"type": "record",
"description": "A record that stores the status of the anime.",
"key": "literal:self",
"record": {
"type": "object",
"required": ["status"],
"properties": {
"status": {
"type": "array",
"items": {
"type": "ref",
"ref": "#status"
}
}
}
}
},
"status": {
"type": "object",
"required": ["id", "title", "status", "episode_text"],
"properties": {
"id": {
"type": "integer",
"description": "Annict API ID for the anime"
},
"title": {
"type": "string",
"description": "Title of the anime"
},
"thumbnail": {
"type": "string",
"description": "URL of the anime thumbnail image"
},
"status": {
"type": "string",
"description": "Current watching status of the anime",
"enum": ["watching", "watched", "pending"]
},
"episode_text": {
"type": "string",
"description": "Current number text of episode"
},
"favorite": {
"type": "boolean",
"description": "Favorite flag of the anime"
}
}
}
}
}
例えば、私のアカウントに登録されているレコードは、このようになっています。
このレコードを取得、recoilのatomとして定義します。
export default function WorkDetail() {
const { work, status, error } = useLoaderData<typeof loader>();
const setAnimeState = useSetAnimeState();
if (status) {
setAnimeState(status.value.status);
} else {
setAnimeState([]);
}
先にstateを更新して、そのstateをもとにPDS側も更新しています。
const updateAnimeState = async (newState: AnimeStatus[]) => {
// ローカルのstateを更新
setAnimeState(newState);
// PDS側の更新
const res = await fetch("/api/status/update", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
$type: "app.netlify.aniblue.status",
status: newState,
}),
});
const json = await res.json();
if (!json.ok) {
throw new Error("情報の更新に失敗しました");
}
};
const handleStatusUpdate = async (status: Status) => {
try {
// prevStateが存在しない場合はレコードを新規作成
const newState = prevState
? animeState.map((item) =>
item.id === id ? { ...item, status } : item
)
: [
...animeState,
{
id,
title,
thumbnail: imageUrl,
status,
episode_text: "",
favorite: false,
},
];
await updateAnimeState(newState);
import { ActionFunction } from "@remix-run/node";
import {
isRecord,
validateRecord,
} from "~/generated/api/types/app/netlify/aniblue/status";
import { StatusAgent } from "~/lib/agent/statusAgent";
import { getSessionAgent } from "~/lib/auth/session";
export const action: ActionFunction = async ({ request }) => {
const agent = await getSessionAgent(request);
if (agent == null) return new Response(null, { status: 401 });
const record = await request.json();
const statusAgent = new StatusAgent(agent);
//バリデーション
if (isRecord(record) && validateRecord(record)) {
await statusAgent.put(record, agent.assertDid);
return { ok: true };
}
return new Response(null, { status: 500 });
};
認証
OAuthでの認証周りは、以下の記事を参考にして実装しました。
ただ、RemixとVercelの相性問題なのか、VercelにデプロイするとOAuthResolverError
が発生してしまったため、代替としてNetlifyにデプロイしています。
(issueによると、RemixのSingle Fetchに関連する問題っぽい?)
課題
-
self
レコードに配列で管理しているだけなので、ステータスの登録数が増えてくるとパフォーマンスに影響が出そう
FirehoseからDBを更新して管理するのが正解?
おわりに
最後まで読んでくださり、ありがとうございました。
「AT Protocol、なんか面白そう!」という思い付きと勢い、アドカレまでに何か作りたいという気持ちだけで作ったアプリでしたが、沢山の方に使っていただけて感謝しています。
改善点などありましたら、お気軽にコメントや@maril445.bsky.socialまでご連絡ください。