はしのした


2023-08-19 ActivityPub 対応(1)

新タグ誕生。 ここには、このブログを ActivityPub 対応にするまでに必要だったことをまとめます。 前編・中編・後編の3日間に分けています。

  実装のステップ

2023年1月から私 SoLa4 は、脱 Twitter を掲げ、活動のメイン拠点をこのブログに移してきました。 その頃から考えていたのが、このブログの ActivityPub 対応。 この度、夏休みに十分な時間が取れることになったので、えいやと実装してみました。 実現しようと思ってたこと はひと通り実現できたので、今のところは満足してます。

まずは、Mastodon 公式の解説記事海外の先人の解説 などを参考に、実装に必要なステップを以下の通りにまとめました。

  1. ブログの投稿を Note に変換する
  2. WebFinger と Actor を準備する
  3. Outbox への GET に応答する
  4. Inbox への POST を検証する
  5. Inbox に届いた Activity に応じた処理をする
  6. 相手方の Inbox に新しい投稿を配送する
  7. 届いた Like や返信をブログに表示する
  8. Mastodon や Misskey との連携を確認する

これ以降、それぞれのステップでのポイントを解説していこうかと思います。

※ 一応これで動いてますが、間違ってるところとかあればこっそり教えてください。

  投稿を Note に変換する

Note オブジェクト

ActivityPub では、投稿やユーザ、それらに対する操作などは、すべて ActivityVocablary という仕様で定義される JSON-LD オブジェクトになっています。 これらのオブジェクトを GET/POST したり、あるいは応答として受け取ることで、情報をやりとりします。

投稿はこうしたオブジェクトのうち、Note オブジェクトに分類されます。 実装によって色々拡張フィールドを持っていたりもしますが、あくまで今回の目的はブログの情報を配信することなので、画像つきの投稿が扱えれば十分です。

一例として、8/8 の日記 を Note に変換した結果を以下に示します。 (以降、可読性のために整形してますが、実際にはスペース・改行はありません)。

 1{
 2  "@context": [
 3    "https://www.w3.org/ns/activitystreams",
 4    {
 5      "@language": "ja"
 6    }
 7  ],
 8  "type": "Note",
 9  "id": "https://puyolayersbridge.sakura.ne.jp/ap/status.rb?id=20230808p02",
10  "published": "2023-08-08T13:24:28Z",
11  "to": [
12    "https://www.w3.org/ns/activitystreams#Public",
13    "https://puyolayersbridge.sakura.ne.jp/ap/followers.rb"
14  ],
15  "attributedTo": "https://puyolayersbridge.sakura.ne.jp/ap/",
16  "content": "<p>【メローブリーズ&ザ・マタギ】 ヘリオス(沖縄/岩手)のメローブリーズと、ザ・マタギ。スタイルはそれぞれヴァイツェンとペールエール。</p><p><a href=\"https://puyolayersbridge.sakura.ne.jp/?date=20230808\">https://puyolayersbridge.sakura.ne.jp/?date=20230808</a></p>",
17  "attachment": [
18    {
19      "type": "Document",
20      "url": "https://puyolayersbridge.sakura.ne.jp/images/20230808_0.jpg",
21      "width": 1200,
22      "height": 1800,
23      "mediaType": "image/jpeg"
24    }
25  ],
26  "source": {
27    "content": " ヘリオス(沖縄/岩手)のメローブリーズと、ザ・マタギ。スタイルはそれぞれヴァイツェンとペールエール。",
28    "mediaType": "text/plain"
29  }
30}

@context は、単に URL だけでも良いようですが、言語情報を @language に埋め込んでも良いということなので、今回はそうしています。 source は本来不要なフィールドですが、今回は投稿の更新が必要かどうかのチェックのために用意しています。 (tDiary は日単位で記事を登録する一方、投稿はセクション単位で管理しているため)

tDiary には RSS を配信する makerss.rb が標準プラグインとして添付されています。 それを参考に、各セクションを上記のような JSON-LD に変換する tDiary プラグインを最初に作成しました(約160行)。

※ 実際には @context は後付けしています。

Note の配信

オブジェクトには URL が id としてついていて、Note の場合は投稿へのパーマリンクも兼ねています。 例えば Misskey の場合は、「リモートで表示」を選択すると、その URL にジャンプします。

ということは、何も考えずに作った JSON-LD の置き場所をそのまま id にしてしまうと、ブラウザで見たときにちょっと残念な感じになります。 ACCEPT ヘッダに text/html が含まれている場合にはブラウザと判断して、該当記事にリダイレクトするのが良さそうです。

多分 .htaccess でも出来るような気がしますが、今回はラッパスクリプトを書く形で対応しました(いいねの一覧を埋め込む処理も加えたので、約60行)。

  WebFinger と Actor を準備する

WebFinger

次に必要なのは、ユーザがいると他のサーバから認識してもらうことです。 といっても、/.well-known/webfinger という場所を探しに来るので、そこに所定のファイルを置く だけです。 ユーザが複数いる場合は、誰に対する問い合わせかを確認する必要がありますが、今回のように1人だけであれば手抜きができます。

ここの href 欄に書いた URL が、そのユーザを表す Actor オブジェクトへのリンクになります。

Actor オブジェクト

ということで、ユーザ情報は Actor オブジェクトとして定義されます。 これも Note のときと同様、ブラウザからのアクセスの(ACCEPT に text/html を含む)ときはブログのトップにリダイレクトし、 それ以外のときは JSON-LD を返すようにしておきます。

今回は、スクリプトで共通で使っている定数などを別ファイルで定義(約25行)した上で、 それらを使って出力する JSON-LD を組み立てるようにしています(約45行)。 結果として出力しているファイルは以下のとおりです。

 1{
 2  "@context": [
 3    "https://www.w3.org/ns/activitystreams",
 4    {"@language": "ja"}
 5  ],
 6  "id": "https://puyolayersbridge.sakura.ne.jp/ap/",
 7  "type": "Person",
 8  "preferredUsername": "under",
 9  "inbox": "https://puyolayersbridge.sakura.ne.jp/ap/inbox.rb",
10  "outbox": "https://puyolayersbridge.sakura.ne.jp/ap/outbox.rb",
11  "following": "https://puyolayersbridge.sakura.ne.jp/ap/following.rb",
12  "followers": "https://puyolayersbridge.sakura.ne.jp/ap/followers.rb",
13  "discoverable": true,
14  "name": "はしのしたの SoLa4",
15  "summary": "「はしのした」の投稿内容をお送りします。",
16  "icon": {
17    "type": "Image",
18    "mediaType": "image/jpeg",
19    "url": "https://puyolayersbridge.sakura.ne.jp/img/icon_sola4.jpg"
20  },
21  "publicKey": {
22    "id": "https://puyolayersbridge.sakura.ne.jp/ap/#main-key",
23    "owner": "https://puyolayersbridge.sakura.ne.jp/ap/",
24    "publicKeyPem": "-----BEGIN PUBLIC KEY-----\n(長いので中略)\n-----END PUBLIC KEY-----"
25  }

見ての通りの項目が多いです。 inbox と outbox については後のセクションで解説します。

注意すべき点として、discoverable を設定しておかないと Misskey との連携がうまく行かないことがあるようです。 また、publicKeyPem には Mastodon 公式の解説記事 を参考に RSA の鍵ペアを作成し、公開鍵を貼り付けておきます。改行は \n に置き換えておきます。

フォロー、フォロワー一覧

Actor オブジェクトの中で、フォロー一覧は following、フォロワー一覧は followers に、それぞれリンク先を記載しておきます。 following は、こちらからは一切フォローしないので、単に固定の出力をするスクリプトになっています(約20行)。 followers は、認識しているフォロワーの人数を totalItems に、それらの Actor オブジェクトの ID 一覧を orderedItems に、それぞれ出力します(約25行)。

具体的にどういう出力をすればいいかは、上記のリンク先から直接見てもらったほうが早いかと思いますので、詳細は省略します。