Bridgy-Fedのまとめ

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

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

以下のサイトのお二人の情報が大変参考になった。

https://blog.tyage.net/post/2023/2023-07-17-bridgy-fed/

https://zenn.dev/dl10yr/articles/c13c7b974b32d0

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

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

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

https://fed.brid.gy/docs

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: truereplyTo: "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:00description: mdxファイルならばu-photoが使えそうだtags:  - astro  - activitypub  - bridgybaseUrl: /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もつける必要があった。

vercel.json
{  "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に自分のサイトのアカウントが作成される。

アカウントのページ

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

https://fed.brid.gy/web/ubanis.com

上部にある@ubanis.com@ubanis.comがFediverseにおけるユーザー名になる。

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

followersとfollowingはそのままフォローとフォロワー。

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

Notificationsは通知。

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

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

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

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

---title: このタイトルはp-nameで使われるdate: 2023-10-23 00:00:00description: ここは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ページ自体をアカウントとして成立させる仕様と説明されていた。

https://qiita.com/okuoku/items/edf0079f472e548041f3

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

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

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

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

https://indieauth.com/

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

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

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

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

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

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

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

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

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

こちらのコードを参考にさせていただいた。

https://zenn.dev/dl10yr/articles/c13c7b974b32d0

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

/.github/workflows/send-webmention.yml
name: send webmentionon:  push:    branches:      - mainjobs:  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のほうも通知したい場合は改良が必要。

sendwebmention.mjs
const child_process = await import('child_process')const MAX_MENTION_SENDS = 10const 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で記事への反応を表示する

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