Bridgy-Fedのまとめ
ActivityPubに対応するためにしたことのまとめ
Astroで生成しているwebサイト自体をAcitivityPubアカウントにするBridgy-Fedに対応するためにしたことをまとめる。
以下のサイトのお二人の情報が大変参考になりました。本当にありがとうございます。
Bridgy-Fedのためにプロフィールを設定する
Bridgy-Fedはサイトのルートに接続したときに見つけたh-card
クラスのブロックをActivityPubのプロフィールとして設定する。構造は以下のとおり。h-card
以下の順序は関係ない。
詳細はmicroformatsとBridgy-FedのDocsを参照。
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
をルートディレクトリに作成する。公式の説明にあったワイルドカードだと正しく動作しなかったので以下のようにした。
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に自分のサイトのアカウントが作成される。
アカウントのページ
例として自分のプロフィールが以下になる。
上部にある@[email protected]
がFediverseにおけるユーザー名になる。
Profile
以下のactivity
にはアカウントのアクションが表示される。
followers
とfollowing
はそのままフォローとフォロワー。
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ページ自体をアカウントとして成立させる仕様と説明されていた。
webサイトのトップページの<a>
又は<link>
に自分のGithubアドレスを追加する。
<link rel="me" href="https://github.com/ubanis" />
GithubのプロフィールページからEdit Profile
を押してPronouns
とSocial accounts
に自分のサイトのアドレスを追加してSave
する。
以下のページの最下段にあるTry it!
から自分のサイトアドレスを入力する。
ログインしようとするとGithubとIndieAuthとの連携許可が表示されるのでこれを許可する。
これでIndieAuthを使ったログインが可能になった。
他のユーザーをフォローする
サイトを登録すると分かるがBridgy-Fed自体にログインするような仕組みはない。
他のユーザーをフォローするためにここでさきほどのIndieAuthを使ったログインが必要になる。
Bridgy-FedのプロフィールのFollowing
からフォローしたいアカウントのアカウント名を入力しFollow
ボタンを押す。
IndieAuthで認証されフォローが完了すれば上部に通知が表示される。
記事のデプロイ後自動でBridgy-Fedに通知する
記事を増やすごとに手元でcurlコマンドを打ち通知していてはやってられないのでこれをGithub Actionsで自動化する。
こちらのコピーです。大変申し訳ありません!
/.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対応に際して参考とさせていただいた方々にお礼申し上げます。