nazo6 knowledge

ActivityPub互換のものを作りたい

作成:2022/11/25 0:00:00

更新:2023/2/14 0:00:00

(2022-11-25)

RustでMastodonやMisskeyのサーバーと通信することをとりあえずの目標に

(2022-11-25)

用語

ActivityPub (2022-11-25)

仕様

ActivityPub

w3c.github.io

ActivityPub

The ActivityPub protocol is a decentralized social networking protocol based upon the [ActivityStreams] 2.0 data format. It provides a client to server API for creating, updating and deleting content, as well as a federated server to server API for delivering notifications and content.

argrath.github.io

mastodonの仕様

ActivityPub - Mastodon documentation

A decentralized social networking protocol based upon the ActivityStreams 2.0 data format and JSON-LD.

docs.joinmastodon.org

WebFinger (2022-11-25)

RFC 7033: WebFinger

www.rfc-editor.org

WebFinger - Mastodon documentation

Translate `user@domain` mentions to actor profile URIs.

docs.joinmastodon.org

Activity Streams / Activity Vocabulary (2022-11-25)

Activity Streams 2.0

www.w3.org

Activity Vocabulary

www.w3.org

ActivityPubはActivityStreamのデータ形式を使用していてそのデータ型がActivity Vocabularyってことだと思う

json-ld (2022-11-25)

JSON-LD 1.1

JSONは、データのシリアル化と送受信に便利な形式です。この仕様では、リンクト・データをシリアル化するためのJSONベースの形式であるJSON-LD 1.1を定義しています。この構文は、既にJSONを用いている展開済みのシステムに容易に統合できるように設計されており、JSONからJSON-LDにスムーズにアップグレードするための道筋を示します。これは、ウェブ・ベースのプログラミング環境におけるリンクト・データの利用、相互運用可能なウェブ・サービスの構築、JSONベースのストレージ・エンジン内のリンクト・データの格納のための手段となることを主に意図しています。

www.asahi-net.or.jp

JSON-LD 1.1

JSON is a useful data serialization and messaging format. This specification defines JSON-LD 1.1, a JSON-based format to serialize Linked Data. The syntax is designed to easily integrate into deployed systems that already use JSON, and provides a smooth upgrade path from JSON to JSON-LD. It is primarily intended to be a way to use Linked Data in Web-based programming environments, to build interoperable Web services, and to store Linked Data in JSON-based storage engines.

www.w3.org

最初JSON-LDってJSON Schemaと何が違うのと思ったけどそれは表面的なもの。
JSON SchemaはJSONのバリデーションをするがJSON-LDは要素にそれが何であるかの情報を与える。
それとapplication/ld+jsonapplication/activity+jsonの違いがよくわからない
json-ldで表されたデータの一種がacitivity streamってことでいいのかな?
W3C wikiによると「 What is the exact relation between JSON-LD and Activity Streams ? ("compatible" is not precise enough)」らしい。

(2022-11-25)

参考文献

(2022-11-25)

Mastodonにアカウントとして認識されるActivityPubを実装してみる #Python - Qiita

Mastodonの検索エリアにURLを突っ込んだ際にアカウント化(?)させるにはどういう実装が必要なのか、気になって調べて実装してみた。結局ActivityPub対応WebFinger対応H…

qiita.com

og image

とりあえずアカウントを認識させるところまで (2022-11-25)

use axum::{
    extract::{Host, Path, Query},
    routing::get,
    Json, Router,
};
use serde::{Deserialize, Serialize};
use tracing::info;

static HOST: &str = "https://example.com";
static HOSTNAME: &str = "example.com";

#[tokio::main]
async fn main() {
    tracing_subscriber::fmt::init();

    let app = Router::new()
        .route("/", get(|| async { "Hello, World!" }))
        .route("/users/:id", get(user_get_handler))
        .route("/.well-known/webfinger", get(webfinger_get_handler));

    axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
        .serve(app.into_make_service())
        .await
        .unwrap();
}

#[derive(Serialize)]
struct PersonActivity {
    #[serde(rename = "@context")]
    context: String,
    #[serde(rename = "type")]
    _type: String,
    id: String,
    name: String,
    #[serde(rename = "preferredUsername")]
    preferred_username: String,
    summary: String,
    inbox: String,
    outbox: String,
    url: String,
}
async fn user_get_handler(Path(user_id): Path<String>, host: Host) -> Json<PersonActivity> {
    info!("user_get: query: {:?}", user_id);
    info!("from: {:?}", host);
    Json(PersonActivity {
        context: "https://www.w3.org/ns/activitystreams".to_string(),
        _type: "Person".to_string(),
        id: format!("{}/users/{}", HOST, user_id),
        name: user_id.clone(),
        preferred_username: user_id.clone(),
        summary: "".to_string(),
        inbox: format!("{}/users/{}/inbox", HOST, user_id),
        outbox: format!("{}/users/{}/outbox", HOST, user_id),
        url: format!("{}/users/{}", HOST, user_id),
    })
}

#[derive(Deserialize, Debug)]
struct WebFingerQuery {
    resource: String,
}
#[derive(Serialize)]
struct WebFingerResponse {
    subject: String,
    aliases: Vec<String>,
    links: Vec<WebFingerResponseLink>,
}
#[derive(Serialize)]
struct WebFingerResponseLink {
    #[serde(skip_serializing_if = "Option::is_none")]
    rel: Option<String>,
    #[serde(rename = "type")]
    _type: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    href: Option<String>,
}
async fn webfinger_get_handler(query: Query<WebFingerQuery>) -> Json<WebFingerResponse> {
    info!("webfinger_get query: {:?}", query);
    let query_cap = regex::Regex::new(r"^acct:([^@]+)@(.+)$")
        .unwrap()
        .captures(&query.resource)
        .unwrap();
    let user_name = query_cap.get(1).unwrap().as_str();
    let response = WebFingerResponse {
        subject: format!("acct:{}@{}", user_name, HOSTNAME),
        aliases: vec![format!("{}/user/{}", HOST, user_name)],
        links: vec![WebFingerResponseLink {
            rel: Some("self".to_string()),
            _type: "application/activity+json".to_string(),
            href: Some(format!("{HOST}/users/{}", user_name)),
        }],
    };

    Json(response)
}

色々つっこみ所が多いがまあとりあえず

ハマったところ (2022-11-25)

webfingerとは?
これはusername@example.comという一意のindetityについて関連するURLを取りにいくもののようでmastodonやmisskeyでは/.well-known/webfinger?resource=に取りにくる。
このときresouceにはacct:username@example.comというリクエストが来る。
WebFinger仕様にはacctスキーマについて
   WebFinger requests include a "resource" parameter (see Section 4.1)
   specifying the query target (URI) for which the client requests
   information.  WebFinger is neutral regarding the scheme of such a
   URI: it could be an "acct" URI [18], an "http" or "https" URI, a
   "mailto" URI [19], or some other scheme.
とある。
webfingerで決まっているわけではないがこの形式の一意のIDを扱うときはacctスキームを付けるのが普通みたい?
エンドポイントのURLは?
mastodonだとURL(example.com/users/xxx)をつっこんでも取りにきてくれなかった。
misskeyだとこれを入れるとこのURLにactivitystreamを取りにきた。そりゃそうか。

(2022-11-25)

というかよく考えたらjson-ldにちゃんと対応させるとめちゃくちゃ大変では
serde用のパーサはあるけど静的に型を付けるのとすごく相性が悪いような・・・
このクレートだとattribute macroでいろいろ頑張ってくれてるみたいだけどそれでもキツそう

(2022-11-25)

と思ったけどどうやらjson-ldに対応している必要は一応ないみたい
ただスキーマを詠み込めるといろいろいいことがある感じかな

(2022-11-25)

あと思ったのがフロントを分離しづらいということ
フロントはNext.jsでVercelとかにデプロイして別にバックエンドサーバーを作るつもりだったけど
フロントがexample.com、APIサーバーがapi.example.comにあったとしてインスタンスとして認識されるのはexample.comだからそっちにFederationの情報を取りにくる
まあWorkersとかでリダイレクトさせればいいのかもしれないけどフロントにそれを意識させたくないのでstaticなファイルもaxumから配信するしかない

(2023-02-14)

そういえば鍵垢ってどうやって実現してるんだろうか

ActivityPub

The ActivityPub protocol is a decentralized social networking protocol based upon the [ActivityStreams] 2.0 data format. It provides a client to server API for creating, updating and deleting content, as well as a federated server to server API for delivering notifications and content.

argrath.github.io

この内容からすると認証なしでリクエストが来た場合にはパブリックな投稿のみを返すという仕様だと思われる
そもそも鍵垢というのはどういうことかというとフォローを自由にできないというだけであって投稿の公開範囲には関係がないはずだ