1.1K Views
November 23, 24
スライド概要
第5回FediLUG勉強会 (2024/11/23) の発表資料です。
「評論・情報」ジャンルで活動している個人サークルです。 主に住宅街や地方の国道沿いなどで見かけるマルフク看板・キリスト看板を題材にした同人誌を作っています。関東地方に大量に看板を出している「きぬた歯科」系列の看板も好きです(ファンクラブ「大日本きぬた連盟」会員)。 お一人様Pixelfedの鯖缶もやっています (2023/8/20~)。
FedifyでActivityPubサーバを作ってみた hira / モナコ広告 @monaco_koukoku @fedibird.com 2024.11.23
自己紹介 • hira(ハンドルネーム) / モナコ広告(サークル名) • 赤白の「マルフク看板」と黒の「キリスト看板」ほか、 懐かしさを感じる看板たちを撮影して収集する活動 • 2021年から同人誌を制作して即売会に出展 • 看板の画像を定期投稿するActivityPubのBotを運営 • 詳しくは第2回勉強会(2024年5月)の資料で 2
目次 • Fedifyとは? • 環境構築 • チュートリアル完了 • 機能拡張 • Tips • まとめ 3
Fedifyとは?
Fedify https://fedify.dev/ • ActivityPubサーバを自作するためのフレームワーク(TypeScript) • Node.js, Deno, Bunなどのランタイムで動く • やってくれること • ActivityPubプロトコルに基づくリモートサーバとの通信 これが地味に面倒 • 自分でやること • ユーザ管理 • 投稿の管理 • データベースの管理 • ストレージの管理 • ユーザインタフェース • などなど... • やることが多いように見えるが、一般的なWebアプ リでも必要なこと • フレームワークから分離されていることで、実装の自 由度が高まる • S3 vs ローカルのストレージ • MySQL vs PostgreSQL • WebUIを持たせるか、CUIベースにするか 5
なぜFedifyに興味を持ったのか? • 画像Botをもっと見通しよく実装できるのではないか https://mi.signboard.fun/@029bot • 現状は Nexkey + 自作の管理アプリ • 管理アプリ側の投稿コマンドをCronで発行 • 画像データを二重に持っている状態 • Fedifyで実装できると… • 画像データを共通化できる • 管理している画像URLを投稿に含めればよい • データベースを一体化 • 画像単位でリアクションの統計が取れる • その他、もろもろ管理が簡単になる(はず) 6
Fedifyのテキスト • 洪 民憙(ホン・ミンヒ)『自分だけのフェディバースのマイクロブログを作ろう!』 • https://zenn.dev/hongminhee/books/4a38b6358a027b • Fedifyの開発者自らが書いたチュートリアル • 物理本あり(2024年10月のOSC東京で購入) • テキストに沿って進めると、リモートと相互にフォロー できるActivityPubサーバが作れる • この発表では一部コードも出すが、細かい説明は割愛 7
環境構築
開発環境 • 今回は、Windows上に WSL + Docker の構成とする • Nodeのインストールで環境を汚したくないので、Node用のコンテナを作る • ポート転送を設定 • WSLのディレクトリをコンテナからマウント • WSLのディレクトリはVisual Studio Codeから編集可能(VS Codeの拡張機能を使用) • 色分け表示、文法エラーの表示、デバッガ機能(後述) VSCodeから 参照・編集 WSL /home/janedoe/fedify/code ├ index.js localhost:8000 │ マウント (bind) Dockerコンテナ (node:20) /home/node/code ├ index.js 8000:8000 │ 9
Dockerfileの例 Dockerfile docker-compose.yml FROM node:20-bullseye-slim services: node: build: . container_name: fedify_sandbox tty: true volumes: - ./code:/home/node/code ports: - 8000:8000 RUN apt-get update ¥ && apt-get install -y xz-utils ¥ && apt-get clean ¥ && rm -rf /var/lib/apt/lists/* RUN npm install -g @fedify/cli WORKDIR /home/node/code USER node EXPOSE 8000 新しいaptパッケージが必要になったら 追記してコンテナを再作成 コマンド (WSL) $ cd /home/janedoe/fedify $ docker compose build $ docker compose up -d $ docker exec -it fedify_sandbox bash # ここでコンテナに入る $ fedify init microblog $ cd microblog $ npm run dev 10
VS Codeから見た図 • テキストに従って初期プロジェクトを作成し、VS Codeから表示 WSL上のディレクトリツリーが見える 11
チュートリアル完了
できたもの 自鯖 (Fedify) 他鯖 (Mastodon) • 投稿した内容が他鯖のフォロワーのタイムラインに流れる ↑Fedifyサーバからの投稿 他鯖(ActivityPub.Academy) APの学習用途で誰でも実験用アカウントを作れる。 APのやりとりのログを確認できる。 13
できたもの 自鯖 (Fedify) 他鯖 (Mastodon) • 他鯖のアカウントをフォローし、自鯖のタイムラインでその投稿を読める 他鯖 (ActivityPub.Academy) の投稿 コードはZennの記事からコピペ&処理 の内容を大まかに確認するという進め方 で、Fedify本の完走までちょうど7日間 14
機能拡張
とりあえずBot化してみる
• 決まった時刻に自動投稿するような処理を追加
# コンテナに入る
$ npm install node-cron moment # momentは投稿内容に現在時刻を含めるために使用
$ npm install --save-dev @types/node-cron
app.tsx: 投稿処理 (POST /users/{username}/posts) を切り出す(元の実装はFedify本の13章までを参照)
import { type RequestContext } from "@fedify/fedify";
async function postNote(
ctx: RequestContext<unknown>, username: string, content: string
): Promise<string> {
const actor = db.prepare<unknown[], Actor>(...);
if (actor == null) throw Error(`Actor for user '${username}' not found`);
const post: Post | null = db.transaction(() => {
... (中略)
return post;
})();
if (post == null) throw Error("Failed to create post");
... (中略)
return ctx.getObjectUri(Note, noteArgs).href;
}
16
とりあえずBot化してみる
app.tsx に追記
import cron from "node-cron";
import moment from "moment";
cron.schedule("*/2 * * * *", async () => { // 2分ごとに投稿
const user = db.prepare<unknown[], User & Actor>( // このサーバの唯一のユーザを取得
"SELECT users.*, actors.* FROM users JOIN actors ON users.id = actors.user_id LIMIT 1",
).get();
if (user == null) return;
const username = user.username;
const content = moment().format("YYYY年MM月DD日 HH時mm分ss秒"); // 現在時刻を整形
const ctx = fedi.createContext(
new Request("https://xxxx.serveo.net/"), // サーバのURLを指定
undefined,
);
const noteUrl = await postNote(ctx, username, content);
console.log(`Post successfully created: ${noteUrl}`);
});
17
結果 • 2分ごとに自動投稿され、 他鯖のタイムラインに表示できた 18
Botフラグを設定する • 自動投稿させるなら設定すべし(参照: misskey.io利用規約) • 通常のユーザはAPのPersonオブジェクトで表現されるが、代わりにService オブジェクトを返すとBotとして認識される(参照: Actorの種類について (W3C) ) 19
画像を添付する
• Noteオブジェクトにattachmentsを設定すればよい
federation.setObjectDispatcher(
Note,
"/users/{identifier}/posts/{id}",
(ctx, values) => {
... (中略)
return new Note({
外部からアクセスできるURL
... (中略)
絶対パスで指定
attachments: [
new Document({
name: "キャプション",
url: new URL("https://xxxx.serveo.net/static/image.jpg"),
mediaType: "image/jpeg",
}),
],
});
}
);
20
画像を添付する
• Noteオブジェクトにattachmentsを設定すればよい
federation.setObjectDispatcher(
Note,
"/users/{identifier}/posts/{id}",
(ctx, values) => {
... (中略)
return new Note({
外部からアクセスできるURL
... (中略)
絶対パスで指定
attachments: [
new Document({
name: "キャプション",
url: new URL("https://xxxx.serveo.net/static/image.jpg"),
mediaType: "image/jpeg",
}),
],
});
}
);
21
Tips
Fedify CLIの便利機能
• ActivityPubのオブジェクトを表示する
• 既存の実装がどんなオブジェクトを返しているかは実装の参考になる
$ fedify lookup https://fedibird.com/@monaco_koukoku/112426724907620002
Note {
id: URL "https://fedibird.com/users/monaco_koukoku/statuses/112426724907620002",
attribution: URL "https://fedibird.com/users/monaco_koukoku",
contents: [ ...(略)... ],
contexts: [ URL "https://fedibird.com/contexts/112426724907627073" ],
generators: [
Application { id: URL "https://fedibird.com/generators/1", name: "Web" }
],
published: 2024-05-12T06:34:40Z,
replies: Collection {
id: URL "https://fedibird.com/users/monaco_koukoku/statuses/112426724907620002/replies",
first: CollectionPage {
...(略)...
}
},
tags: [
Hashtag { href: URL "https://fedibird.com/tags/fedilug", name: "#fedilug" }
],
url: URL "https://fedibird.com/@monaco_koukoku/112426724907620002",
to: URL "https://www.w3.org/ns/activitystreams#Public",
cc: URL "https://fedibird.com/users/monaco_koukoku/followers",
sensitive: false
}
23
Fedify CLIの便利機能 • ローカル環境のサーバを、一時的にインターネットに公開する • Serveoというサービスを使っている模様 • https://*.serveo.net または https://*.lhr.life というサブドメインが割り当てられる $ fedify tunnel 8000 # URLが表示され、そのURLでインターネットからアクセスできる • その他、開発者ご本人によるTipsまとめ • Fedify CLIの隠れた便利機能:見逃しているかもしれないテクニック 24
デバッグ環境を作るには • VS Codeでブレークポイントを設定し、そこで実行を一時停止させたい ステップ実行も できる 25
デバッガを待ち受ける設定
• テキスト通りにプロジェクトを作ると、サーバの起動コマンドはtsx
• コンテナ側でサーバを起動する際、9229番ポートでデバッガを待ち受ける
• コンテナ外からのアクセス許可のため、0.0.0.0:9229 と書いている
• watch を書いておくと、ソースコードの更新時に自動的に変更が反映される
package.json
{
}
"type": "module",
"dependencies": {
(略)
},
"devDependencies": {
(略)
},
"scripts": {
"dev": "dotenvx run -- tsx watch ./src/index.ts",
"debug": "dotenvx run -- tsx watch --inspect=0.0.0.0:9229 ./src/index.ts",
"prod": "dotenvx run -- node --import tsx ./src/index.ts"
}
26
VS Codeのデバッガの設定
• tsxの公式ドキュメントを参考に、VS Codeのデバッグ環境を設定する
.vscode/launch.json
{
"version": "0.2.0",
VS Codeのデバッガはこのポートにつなぎに行く。
"configurations": [
よってコンテナ側でこのポートの開放が必要。
{
"name": "Attach to process",
Docker環境で動かすための差分
"type": "node",
ホスト (VS Code・WSL) 側と
"request": "attach",
コンテナ (Node) 側で絶対パスが異なるため、
"port": 9229,
ディレクトリの対応関係を示すために書く。
"localRoot": "${workspaceFolder}",
"remoteRoot": "/home/node/code/microblog",
"restart": true,
ソースコードの更新時に、
"skipFiles": [
デバッガを自動的に再接続する。
// Node.js internal core modules
"<node_internals>/**",
// Ignore all dependencies (optional)
"${workspaceFolder}/node_modules/**"
]
}
]
}
27
コンテナの再作成・起動 Dockerfile docker-compose.yml FROM node:20-bullseye-slim services: node: build: . container_name: fedify_sandbox tty: true volumes: - ./code:/home/node/code ports: - 8000:8000 - 9229:9229 RUN apt-get update ¥ && apt-get install -y xz-utils ¥ && apt-get clean ¥ && rm -rf /var/lib/apt/lists/* RUN npm install -g @fedify/cli WORKDIR /home/node/code USER node EXPOSE 8000 EXPOSE 9229 コマンド (WSL) $ cd /home/janedoe/fedify $ docker compose build $ docker compose up -d $ docker exec -it fedify_sandbox bash # ここでコンテナに入る $ cd microblog $ npm run debug 28
VS Codeの操作 ②Attach to process ①実行とデバッグ ブレークポイント ③ブラウザで http://localhost:8000/ を表示 29
まとめ
まとめ • Fedifyを使うとActivityPubサーバを自分で作れる • 特に、Botなど特殊用途のサーバを作るのに向いている気がする • 公式チュートリアルで、他鯖と連合するための最低限の処理が作れる • そこから好きな機能をどんどん追加していけばよい • ActivityPubサーバ開発に広く役立つツールがある • CLIだけインストールする使い方もありだと思う (npm install -g @fedify/cli) • 最後に告知:12月1日 文学フリマ東京(東京ビッグサイト)に出店します • https://bunfree.net/event/tokyo39/ 31
To Be Continued... ご清聴ありがとうございました 32