---
title: FedifyでActivityPubサーバを作ってみた（第5回FediLUG勉強会）
tags: 
author: [hira / モナコ広告](https://convert.docswell.com/user/monaco_koukoku)
site: [Docswell](https://www.docswell.com/)
thumbnail: https://bcdn.docswell.com/page/2JVDL5Q9JQ.jpg?width=480
description: 第5回FediLUG勉強会 (2024/11/23) の発表資料です。
published: November 23, 24
canonical: https://convert.docswell.com/s/monaco_koukoku/5DN28R-fedilug05-20241123
---
# Page. 1

![Page Image](https://bcdn.docswell.com/page/2JVDL5Q9JQ.jpg)

FedifyでActivityPubサーバを作ってみた
hira / モナコ広告
@monaco_koukoku
@fedibird.com
2024.11.23


# Page. 2

![Page Image](https://bcdn.docswell.com/page/5EGYX6W4EL.jpg)

自己紹介
• hira（ハンドルネーム） / モナコ広告（サークル名）
• 赤白の「マルフク看板」と黒の「キリスト看板」ほか、
懐かしさを感じる看板たちを撮影して収集する活動
• 2021年から同人誌を制作して即売会に出展
• 看板の画像を定期投稿するActivityPubのBotを運営
•
詳しくは第2回勉強会（2024年5月）の資料で
2


# Page. 3

![Page Image](https://bcdn.docswell.com/page/4JQM843R7P.jpg)

目次
• Fedifyとは？
• 環境構築
• チュートリアル完了
• 機能拡張
• Tips
• まとめ
3


# Page. 4

![Page Image](https://bcdn.docswell.com/page/K74V2Q1QJ1.jpg)

Fedifyとは？


# Page. 5

![Page Image](https://bcdn.docswell.com/page/LJ13MPGL7G.jpg)

Fedify
https://fedify.dev/
• ActivityPubサーバを自作するためのフレームワーク（TypeScript）
• Node.js, Deno, Bunなどのランタイムで動く
• やってくれること
• ActivityPubプロトコルに基づくリモートサーバとの通信
これが地味に面倒
• 自分でやること
• ユーザ管理
• 投稿の管理
• データベースの管理
• ストレージの管理
• ユーザインタフェース
• などなど...
• やることが多いように見えるが、一般的なWebアプ
リでも必要なこと
• フレームワークから分離されていることで、実装の自
由度が高まる
• S3 vs ローカルのストレージ
• MySQL vs PostgreSQL
• WebUIを持たせるか、CUIベースにするか
5


# Page. 6

![Page Image](https://bcdn.docswell.com/page/GJWP32KQ72.jpg)

なぜFedifyに興味を持ったのか？
• 画像Botをもっと見通しよく実装できるのではないか
https://mi.signboard.fun/@029bot
• 現状は Nexkey + 自作の管理アプリ
• 管理アプリ側の投稿コマンドをCronで発行
• 画像データを二重に持っている状態
• Fedifyで実装できると…
• 画像データを共通化できる
• 管理している画像URLを投稿に含めればよい
• データベースを一体化
• 画像単位でリアクションの統計が取れる
• その他、もろもろ管理が簡単になる（はず）
6


# Page. 7

![Page Image](https://bcdn.docswell.com/page/4EZKVDZ3E3.jpg)

Fedifyのテキスト
• 洪 民憙（ホン・ミンヒ）『自分だけのフェディバースのマイクロブログを作ろう！』
• https://zenn.dev/hongminhee/books/4a38b6358a027b
• Fedifyの開発者自らが書いたチュートリアル
• 物理本あり（2024年10月のOSC東京で購入）
• テキストに沿って進めると、リモートと相互にフォロー
できるActivityPubサーバが作れる
• この発表では一部コードも出すが、細かい説明は割愛
7


# Page. 8

![Page Image](https://bcdn.docswell.com/page/Y76Q51ZZ7V.jpg)

環境構築


# Page. 9

![Page Image](https://bcdn.docswell.com/page/G75RY4W9E4.jpg)

開発環境
• 今回は、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


# Page. 10

![Page Image](https://bcdn.docswell.com/page/9J2DGVQ5JR.jpg)

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 ¥
&amp;&amp; apt-get install -y xz-utils ¥
&amp;&amp; apt-get clean ¥
&amp;&amp; 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


# Page. 11

![Page Image](https://bcdn.docswell.com/page/DEY2V3W6EM.jpg)

VS Codeから見た図
• テキストに従って初期プロジェクトを作成し、VS Codeから表示
WSL上のディレクトリツリーが見える
11


# Page. 12

![Page Image](https://bcdn.docswell.com/page/VJN8MVP178.jpg)

チュートリアル完了


# Page. 13

![Page Image](https://bcdn.docswell.com/page/YE9KN63YE3.jpg)

できたもの
自鯖
(Fedify)
他鯖
(Mastodon)
• 投稿した内容が他鯖のフォロワーのタイムラインに流れる
↑Fedifyサーバからの投稿
他鯖（ActivityPub.Academy）
APの学習用途で誰でも実験用アカウントを作れる。
APのやりとりのログを確認できる。
13


# Page. 14

![Page Image](https://bcdn.docswell.com/page/GE8RKZMKJD.jpg)

できたもの
自鯖
(Fedify)
他鯖
(Mastodon)
• 他鯖のアカウントをフォローし、自鯖のタイムラインでその投稿を読める
他鯖 (ActivityPub.Academy) の投稿
コードはZennの記事からコピペ＆処理
の内容を大まかに確認するという進め方
で、Fedify本の完走までちょうど7日間
14


# Page. 15

![Page Image](https://bcdn.docswell.com/page/LELKZD3PJR.jpg)

機能拡張


# Page. 16

![Page Image](https://bcdn.docswell.com/page/4JM2VPD2JW.jpg)

とりあえず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 &quot;@fedify/fedify&quot;;
async function postNote(
ctx: RequestContext&lt;unknown&gt;, username: string, content: string
): Promise&lt;string&gt; {
const actor = db.prepare&lt;unknown[], Actor&gt;(...);
if (actor == null) throw Error(`Actor for user &#039;${username}&#039; not found`);
const post: Post | null = db.transaction(() =&gt; {
... (中略)
return post;
})();
if (post == null) throw Error(&quot;Failed to create post&quot;);
... (中略)
return ctx.getObjectUri(Note, noteArgs).href;
}
16


# Page. 17

![Page Image](https://bcdn.docswell.com/page/PJRM2145E9.jpg)

とりあえずBot化してみる
app.tsx に追記
import cron from &quot;node-cron&quot;;
import moment from &quot;moment&quot;;
cron.schedule(&quot;*/2 * * * *&quot;, async () =&gt; { // 2分ごとに投稿
const user = db.prepare&lt;unknown[], User &amp; Actor&gt;( // このサーバの唯一のユーザを取得
&quot;SELECT users.*, actors.* FROM users JOIN actors ON users.id = actors.user_id LIMIT 1&quot;,
).get();
if (user == null) return;
const username = user.username;
const content = moment().format(&quot;YYYY年MM月DD日 HH時mm分ss秒&quot;); // 現在時刻を整形
const ctx = fedi.createContext(
new Request(&quot;https://xxxx.serveo.net/&quot;), // サーバのURLを指定
undefined,
);
const noteUrl = await postNote(ctx, username, content);
console.log(`Post successfully created: ${noteUrl}`);
});
17


# Page. 18

![Page Image](https://bcdn.docswell.com/page/PEXVDY2X7X.jpg)

結果
• 2分ごとに自動投稿され、
他鯖のタイムラインに表示できた
18


# Page. 19

![Page Image](https://bcdn.docswell.com/page/3EKQX3M9ED.jpg)

Botフラグを設定する
• 自動投稿させるなら設定すべし（参照: misskey.io利用規約）
• 通常のユーザはAPのPersonオブジェクトで表現されるが、代わりにService
オブジェクトを返すとBotとして認識される（参照: Actorの種類について (W3C) ）
19


# Page. 20

![Page Image](https://bcdn.docswell.com/page/L73P5GY9E5.jpg)

画像を添付する
• Noteオブジェクトにattachmentsを設定すればよい
federation.setObjectDispatcher(
Note,
&quot;/users/{identifier}/posts/{id}&quot;,
(ctx, values) =&gt; {
... (中略)
return new Note({
外部からアクセスできるURL
... (中略)
絶対パスで指定
attachments: [
new Document({
name: &quot;キャプション&quot;,
url: new URL(&quot;https://xxxx.serveo.net/static/image.jpg&quot;),
mediaType: &quot;image/jpeg&quot;,
}),
],
});
}
);
20


# Page. 21

![Page Image](https://bcdn.docswell.com/page/87DLD158EG.jpg)

画像を添付する
• Noteオブジェクトにattachmentsを設定すればよい
federation.setObjectDispatcher(
Note,
&quot;/users/{identifier}/posts/{id}&quot;,
(ctx, values) =&gt; {
... (中略)
return new Note({
外部からアクセスできるURL
... (中略)
絶対パスで指定
attachments: [
new Document({
name: &quot;キャプション&quot;,
url: new URL(&quot;https://xxxx.serveo.net/static/image.jpg&quot;),
mediaType: &quot;image/jpeg&quot;,
}),
],
});
}
);
21


# Page. 22

![Page Image](https://bcdn.docswell.com/page/VJP2XQGWJ8.jpg)

Tips


# Page. 23

![Page Image](https://bcdn.docswell.com/page/2EVDL569EQ.jpg)

Fedify CLIの便利機能
• ActivityPubのオブジェクトを表示する
• 既存の実装がどんなオブジェクトを返しているかは実装の参考になる
$ fedify lookup https://fedibird.com/@monaco_koukoku/112426724907620002
Note {
id: URL &quot;https://fedibird.com/users/monaco_koukoku/statuses/112426724907620002&quot;,
attribution: URL &quot;https://fedibird.com/users/monaco_koukoku&quot;,
contents: [ ...(略)... ],
contexts: [ URL &quot;https://fedibird.com/contexts/112426724907627073&quot; ],
generators: [
Application { id: URL &quot;https://fedibird.com/generators/1&quot;, name: &quot;Web&quot; }
],
published: 2024-05-12T06:34:40Z,
replies: Collection {
id: URL &quot;https://fedibird.com/users/monaco_koukoku/statuses/112426724907620002/replies&quot;,
first: CollectionPage {
...(略)...
}
},
tags: [
Hashtag { href: URL &quot;https://fedibird.com/tags/fedilug&quot;, name: &quot;#fedilug&quot; }
],
url: URL &quot;https://fedibird.com/@monaco_koukoku/112426724907620002&quot;,
to: URL &quot;https://www.w3.org/ns/activitystreams#Public&quot;,
cc: URL &quot;https://fedibird.com/users/monaco_koukoku/followers&quot;,
sensitive: false
}
23


# Page. 24

![Page Image](https://bcdn.docswell.com/page/57GYX6N47L.jpg)

Fedify CLIの便利機能
• ローカル環境のサーバを、一時的にインターネットに公開する
• Serveoというサービスを使っている模様
• https://*.serveo.net または https://*.lhr.life というサブドメインが割り当てられる
$ fedify tunnel 8000
# URLが表示され、そのURLでインターネットからアクセスできる
• その他、開発者ご本人によるTipsまとめ
• Fedify CLIの隠れた便利機能：見逃しているかもしれないテクニック
24


# Page. 25

![Page Image](https://bcdn.docswell.com/page/4EQM84QRJP.jpg)

デバッグ環境を作るには
• VS Codeでブレークポイントを設定し、そこで実行を一時停止させたい
ステップ実行も
できる
25


# Page. 26

![Page Image](https://bcdn.docswell.com/page/KJ4V2QNQE1.jpg)

デバッガを待ち受ける設定
• テキスト通りにプロジェクトを作ると、サーバの起動コマンドはtsx
• コンテナ側でサーバを起動する際、9229番ポートでデバッガを待ち受ける
• コンテナ外からのアクセス許可のため、0.0.0.0:9229 と書いている
• watch を書いておくと、ソースコードの更新時に自動的に変更が反映される
package.json
{
}
&quot;type&quot;: &quot;module&quot;,
&quot;dependencies&quot;: {
(略)
},
&quot;devDependencies&quot;: {
(略)
},
&quot;scripts&quot;: {
&quot;dev&quot;: &quot;dotenvx run -- tsx watch ./src/index.ts&quot;,
&quot;debug&quot;: &quot;dotenvx run -- tsx watch --inspect=0.0.0.0:9229 ./src/index.ts&quot;,
&quot;prod&quot;: &quot;dotenvx run -- node --import tsx ./src/index.ts&quot;
}
26


# Page. 27

![Page Image](https://bcdn.docswell.com/page/LE13MP5LJG.jpg)

VS Codeのデバッガの設定
• tsxの公式ドキュメントを参考に、VS Codeのデバッグ環境を設定する
.vscode/launch.json
{
&quot;version&quot;: &quot;0.2.0&quot;,
VS Codeのデバッガはこのポートにつなぎに行く。
&quot;configurations&quot;: [
よってコンテナ側でこのポートの開放が必要。
{
&quot;name&quot;: &quot;Attach to process&quot;,
Docker環境で動かすための差分
&quot;type&quot;: &quot;node&quot;,
ホスト (VS Code・WSL) 側と
&quot;request&quot;: &quot;attach&quot;,
コンテナ (Node) 側で絶対パスが異なるため、
&quot;port&quot;: 9229,
ディレクトリの対応関係を示すために書く。
&quot;localRoot&quot;: &quot;${workspaceFolder}&quot;,
&quot;remoteRoot&quot;: &quot;/home/node/code/microblog&quot;,
&quot;restart&quot;: true,
ソースコードの更新時に、
&quot;skipFiles&quot;: [
デバッガを自動的に再接続する。
// Node.js internal core modules
&quot;&lt;node_internals&gt;/**&quot;,
// Ignore all dependencies (optional)
&quot;${workspaceFolder}/node_modules/**&quot;
]
}
]
}
27


# Page. 28

![Page Image](https://bcdn.docswell.com/page/GEWP325QJ2.jpg)

コンテナの再作成・起動
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 ¥
&amp;&amp; apt-get install -y xz-utils ¥
&amp;&amp; apt-get clean ¥
&amp;&amp; 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


# Page. 29

![Page Image](https://bcdn.docswell.com/page/47ZKVDN373.jpg)

VS Codeの操作
②Attach to process
①実行とデバッグ
ブレークポイント
③ブラウザで
http://localhost:8000/ を表示
29


# Page. 30

![Page Image](https://bcdn.docswell.com/page/YJ6Q518ZJV.jpg)

まとめ


# Page. 31

![Page Image](https://bcdn.docswell.com/page/GJ5RY49974.jpg)

まとめ
• Fedifyを使うとActivityPubサーバを自分で作れる
• 特に、Botなど特殊用途のサーバを作るのに向いている気がする
• 公式チュートリアルで、他鯖と連合するための最低限の処理が作れる
• そこから好きな機能をどんどん追加していけばよい
• ActivityPubサーバ開発に広く役立つツールがある
• CLIだけインストールする使い方もありだと思う (npm install -g @fedify/cli)
• 最後に告知：12月1日 文学フリマ東京（東京ビッグサイト）に出店します
• https://bunfree.net/event/tokyo39/
31


# Page. 32

![Page Image](https://bcdn.docswell.com/page/9E2DGV25ER.jpg)

To Be Continued...
ご清聴ありがとうございました
32


