Bridgy-Fedのまとめ

作成: 更新:

ActivityPubに対応するためにしたことのまとめ

Astroで生成しているwebサイト自体をAcitivityPubアカウントにするBridgy-Fedに対応するためにしたことをまとめる。

以下のサイトのお二人の情報が大変参考になりました。本当にありがとうございます。

このブログがFediverseに対応しました
このブログがFediverseに対応しました favicon https://blog.tyage.net/post/2023/2023-07-17-bridgy-fed/
このブログがFediverseに対応しました
Next.jsのSSGで作ってる個人ブログをFediverse対応させてつぶやいてみた
Next.jsのSSGで作ってる個人ブログをFediverse対応させてつぶやいてみた favicon https://zenn.dev/dl10yr/articles/c13c7b974b32d0
Next.jsのSSGで作ってる個人ブログをFediverse対応させてつぶやいてみた

Bridgy-Fedのためにプロフィールを設定する

Bridgy-Fedはサイトのルートに接続したときに見つけたh-cardクラスのブロックをActivityPubのプロフィールとして設定する。構造は以下のとおり。h-card以下の順序は関係ない。

詳細はmicroformatsとBridgy-FedのDocsを参照。

Bridgy Fed
Bridgy Fed favicon https://fed.brid.gy/docs
Microformats Wikiにようこそ! - Microformats Wiki
Microformats Wikiにようこそ! - Microformats Wiki favicon https://microformats.org/wiki/Main_Page-ja
h-cardブロックはプロフィールを表す
<div class="h-card">
  <img  class="u-photo" src="アバターアイコンのアドレス" alt="u-photoは投稿でも使える添付画像を表すclass" />
  <a class="u-url u-uid p-name" rel="me" href="/">  
    p-nameはユーザー名 rel="me"href="/"を必ずつける  
  </a>
  <p class="p-note">p-noteはSNSにあるアカウント説明文</p>
  <div style="display:none;">
    <a href="https://fed.brid.gy/web/ubanis.com" class="u-url">  
      u-urlをつけたリンクはプロフィールに追加されるリンクになる  
    </a>
  </div>
</div>

これをAstro用に合わせる。自分はプロフィール部分のアイコンや名前は/src/components/Bio.astroに書いてあるのでこれを修正しトップページに<Bio />を追加した。表示する必要はないのでdisplay:noneで非表示にしておいた。

<div class="bio_wrap h-card">
  <Picture
    class="u-photo"
    src={nyan}
    formats={["webp"]}
    height="143"
    width="128"
    alt=""
  />
  <p>
    <strong><a class="u-url u-uid p-name" rel="me" href="/">ubanis</a></strong>
  </p>
  <p class="p-note">ふたなり絵描き</p>
  <p style="display:none;">
    <a href="https://fed.brid.gy/web/ubanis.com" class="u-url"
      >ActivityPubアカウント</a>
  </p>
</div>

自分のAstroはトップページがまず/src/page/[...page].astroでインデックスページになっているのでそこにさきほどのコンポーネントである<Bio />を追加した。

  <div style="display:none;">
    <Bio />
  </div>

Bridgy-Fedに対応するためのclassを投稿に追加する

投稿はAstroがMarkdownファイルから生成するのでHTML生成部分である/src/layouts/BlogPost.astroに投稿に必要なclassを追加する。

Bridgy-Fedは以下のようなclassがつけられた部分をFediverseへの投稿とみなす。HTMLタグは関係がなくclassがついているかどうかである。

h-entryブロックがFediverseへの投稿ブロックを表す
<div class="h-entry">
  <p style="display: none" class="p-name">
    p-nameは投稿のタイトルを表す。  
    p-nameクラスがある場合は投稿がArticleとなりマストドンなどでは  
    タイトルとともに記事へのリンクだけの投稿となり本文が展開されない。  
    省略可能。
  </p>
  <div class="e-content">
    e-contentは投稿の本文。  
    このブロック内はHTMLタグなどが外された純粋なテキストに変換される。  
    当然省略不可。
  </div>
  <p><a class="u-in-reply-to" href="リプライ先投稿URL">  
    u-in-reply-toがある場合その投稿はhrefで指定された  
    マストドンなどの投稿URLへのリプライとなる。  
    省略可能。  
  </a></p>  
  以下はBridgy-Fedとやり取りするために必要なもの。  
  display:noneで非表示にしている。  
  <div style="display: none;">
    u-bridgy-fedのリンクのタグには何も無くて良い。
    <a class="u-bridgy-fed" href="https://fed.brid.gy/"></a>
    <p class="dt-published">投稿の日時</p>
  </div>
</div>

これに合わせてMarkdownの変換部分を以下のようにした。

    <div class="h-entry">
      {
        frontmatter.isPname && (
          <p style="display: none" class="p-name">
            {frontmatter.title}
          </p>
        )
      }
      <div class="e-content"><slot /></div>
      {
        frontmatter.replyTo && (
          <p><a class="u-in-reply-to" href={frontmatter.replyTo}>[リプライ先]</a></p>
        )
      }
      <div style="display: none;">
        <a class="u-bridgy-fed" href="https://fed.brid.gy/"></a>
        <time class="dt-published" datetime={postDate.toJSON()}>
          {postDate.toJSON()}
        </time>
      </div>
    </div

p-nameをつけるかどうかはfrontmatterにisPnameを追加することで対応した。
同様にリプライかどうかの判断もreplyToのfrontmatterを追加。

---
import { z, defineCollection } from "astro:content";

const blogCollection = defineCollection({
  schema: z.object({
    title: z.string(),
    date: z.string().transform((str) => new Date(str)),
    description: z.string().optional(),
    tags: z.array(z.string()).optional(),
    baseUrl: z.string().optional(),
    featured: z.string().optional(),
    heroImage: z.string().optional(),
    author: z.string().optional(),
    isDraft: z.boolean().optional(),
    category: z.string().optional(),
    isPname: z.boolean().optional(),// p-name
    replyTo: z.string().optional(),// u-in-reply-to
    updatedDate: z
      .string()
      .transform((str) => new Date(str))
      .optional(),
  }),
});
export const collections = {
  blog: blogCollection,
  note: blogCollection,
};

リプライとp-nameは以下のようにMarkdwonファイルの中で指定する。

---
isPname: true
replyTo: "https://misskey.io/notes/9l7ixwi3nq"
---

isPnameは省略すればfalseになる。
replayToにはリプライ先のURLを必ず書く。

画像の添付

投稿に画像を添付したい場合はプロフィールの時のようにh-entryの中でu-photoクラスのついた<img>を追加する。

AstroのMarkdown生成では投稿内(ここではe-content)に含まれる<img>タグのclassが書けないので(やり方はあるかもしれない)u-photoクラスをつけて画像を添付したい場合はmdxファイルに直接<img>タグを書くことにした。

---
title: mdxファイルでやってみる
date: " 2023-10-24 00:00:00"
description: mdxファイルならばu-photoが使えそうだ
tags:
  - astro
  - activitypub
  - bridgy
baseUrl: /note/
category: Astro
---

Astro の mdx ファイルは HTML タグが普通に使えるので u-photo クラスを付けて画像が貼れるので画像が投稿に添付されるはず 。

とりあえずねこ。

<img class="u-photo" src="/img/nyan4.jpg" />

webfingerなどのリダイレクトを設定する

私のサイトはVercelにあるのでBridgy-Fedへリダイレクトするためのvercel.jsonをルートディレクトリに作成する。公式の説明にあったワイルドカードだと正しく動作しなかったので以下のようにした。

参考: https://kwaa.dev/indieweb

2024-03-22 vercel.json を修正
redirects ではなく rewrites であり :path もつける必要があった。

{
  "trailingSlash": true,
  "rewrites": [
    {
      "source": "/.well-known/host-meta:path(.*)",
      "destination": "https://fed.brid.gy/.well-known/host-meta:path*",
      "statusCode": 302
    },
    {
      "source": "/.well-known/webfinger:path(.*)",
      "destination": "https://fed.brid.gy/.well-known/webfinger:path*",
      "statusCode": 302
    }
  ]
}

Bridgy-Fedから接続確認

https://fed.brid.gy/ から右側のConnect directly to the fediverse:@yoursite.comを選択する。

What's your web site?の下の入力欄に自分のドメインのアドレスを入力してOKを押す。

Next step: add the .well-known redirects. の部分は先程のリダイレクトの確認を行う部分なのでCheck nowを押しリダイレクト接続確認を行う。

Next step: add a representative h-card. サイトのルートにアクセスしてプロフィールの設定を確認するのでこれも同様にCheck nowを押す。

このふたつに成功するとめでたくFediverseに自分のサイトのアカウントが作成される。

アカウントのページ

例として自分のプロフィールが以下になる。

ubanis.com profile - Bridgy Fed
ubanis.com profile - Bridgy Fed favicon https://fed.brid.gy/web/ubanis.com

上部にある@[email protected]がFediverseにおけるユーザー名になる。

Profile以下のactivityにはアカウントのアクションが表示される。

followersfollowingはそのままフォローとフォロワー。

Feedはフォローしているユーザーのアクションが表示される。

Notificationsは通知。

このままだとユーザーをフォローしたりはできないのでそれは後半で説明する。

記事の投稿からFediverseへのポスト

上記のプロフィールが無事作成されている状態ならば以下の通知を行えばFediverseに投稿される。

とりあえずテストとして/src/content/note/test.mdファイルを作成する。

---
title: このタイトルはp-nameで使われる
date: " 2023-10-23 00:00:00"
description: ここはBridgy-Fedに関係なし
tags:
  - タグも関係なし
baseUrl: /note/
category: Web
---

この本文がe-contentになる。

baseUrlとcategoryは  
うちのサイトだけの都合で  
ついているものなので  
気にしないでください。

これをすでにvercelと連携しているGithubにpushする。

デプロイ後いつものようにサイトに記事が追加される。

これだけではBridgy-Fedに投稿が通知されるわけではないのでcurlで記事のURLを通知しなければならない。

curl https://fed.brid.gy/webmention -d source=記事のURL -d target=https://fed.brid.gy

これが実際に投稿されたかどうかはBridgy-Fedのプロフィールページを見ればわかる。

マストドンなどにアカウントを作成して自分のサイトをフォローしてみて投稿が流れているか確認したほうがいいかもしれない。

現状ではMisskey.ioには投稿が全く流れずリプライも動作しなかった。

IndieAuthを設定する

IndieAuthはOAuth2拡張の1つで、Webページ自体をアカウントとして成立させる仕様と説明されていた。

GistをIndieAuth.comアカウントにする - Qiita
IndieAuth( https://indieweb.org/IndieAuth )はOAuth2拡張の1つで、Webページ自体をアカウントとして成立させる仕様となっている。昔なつかしの、Conn…
GistをIndieAuth.comアカウントにする - Qiita favicon https://qiita.com/okuoku/items/edf0079f472e548041f3
GistをIndieAuth.comアカウントにする - Qiita

webサイトのトップページの<a>又は<link>に自分のGithubアドレスを追加する。

<link rel="me" href="https://github.com/ubanis" />

GithubのプロフィールページからEdit Profileを押してPronounsSocial accountsに自分のサイトのアドレスを追加してSaveする。

以下のページの最下段にあるTry it!から自分のサイトアドレスを入力する。

IndieAuth - Sign in with your domain name
IndieAuth - Sign in with your domain name favicon https://indieauth.com/

ログインしようとするとGithubとIndieAuthとの連携許可が表示されるのでこれを許可する。

これでIndieAuthを使ったログインが可能になった。

他のユーザーをフォローする

サイトを登録すると分かるがBridgy-Fed自体にログインするような仕組みはない。

他のユーザーをフォローするためにここでさきほどのIndieAuthを使ったログインが必要になる。

Bridgy-FedのプロフィールのFollowingからフォローしたいアカウントのアカウント名を入力しFollowボタンを押す。

IndieAuthで認証されフォローが完了すれば上部に通知が表示される。

記事のデプロイ後自動でBridgy-Fedに通知する

記事を増やすごとに手元でcurlコマンドを打ち通知していてはやってられないのでこれをGithub Actionsで自動化する。

Next.jsのSSGで作ってる個人ブログをFediverse対応させてつぶやいてみた
Next.jsのSSGで作ってる個人ブログをFediverse対応させてつぶやいてみた favicon https://zenn.dev/dl10yr/articles/c13c7b974b32d0
Next.jsのSSGで作ってる個人ブログをFediverse対応させてつぶやいてみた

こちらのコピーです。大変申し訳ありません!

/.github/workflows/send-webmention.ymlを以下のようにする。

name: send webmention

on:
  push:
    branches:
      - main

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
        with:
          submodules: false
          fetch-depth: 0

      - name: Send webmention
        uses: actions/setup-node@v3
      - run: git fetch origin ${{ github.event.before }} # 直前のコミットハッシュをフェッチします。
      - run: git fetch origin ${{ github.sha }} # 最新のコミットハッシュをフェッチします。
      - run: node sendwebmention.mjs ${{ github.event.before }} ${{ github.sha }}

/sendwebmention.mjsを追加する。

Astroはmdxも扱うのでそれに対応した。

このままだとnoteのほうだけ通知されるのでblogのほうも通知したい場合は改良が必要。

const child_process = await import('child_process')

const MAX_MENTION_SENDS = 10

const sleep = (sec) => new Promise((res) => setTimeout(res, sec * 1000))

async function sendWebmention(oldCommitHash, newCommitHash) {
  const diff = child_process
    .spawnSync('git', ['diff', '--name-only', oldCommitHash, newCommitHash])
    .stdout.toString()
  const files = diff.split('\n')

  const filtered = files.filter((file) => file.indexOf('src/content/note/') !== -1)
  console.log(filtered)

  if (filtered.length >= 1) {
    await sleep(110)
  }

  for (let i = 0; i < filtered.length && i < MAX_MENTION_SENDS; ++i) {
    const filePath = filtered[i].split('/')
    const linkPath = filePath.slice(3)
    const tempurl = 'https://ubanis.com/note/' + linkPath.join('/')
    const url = tempurl.replace(/\.mdx|\.md/g, "")
    // eslint-disable-next-line no-console
    console.log('sending webmention of ' + url)
    child_process.spawnSync('curl', [
      'https://fed.brid.gy/webmention',
      '-d',
      `source=${encodeURIComponent(url)}`,
      '-d',
      `target=https://fed.brid.gy`,
    ])
  }
}

const args = process.argv.slice(2)
sendWebmention(args[0], args[1])

すべてデプロイした後に改めて記事を追加してデプロイした後Bridgy-Fedに投稿のアクションが表示されるかしばらく待ってから確認する。

webmentionで記事への反応を表示する

これは現在正しく動いてないようなので動作するようになってから書こうと思う。

最後に

webサイトのActivityPub対応は正直アカウントとしては使いづらい上に気軽につぶやくこともできないのでSNSアカウントとしてはイマイチなもののサイト自体が繋がれるというおもしろさがある。

ActivityPub対応に際して参考とさせていただいた方々にお礼申し上げます。