Skip to content

murlog ドメインモデル

murlog の世界を構成する概念と、それらがどう振る舞うかを記述する。 技術的な実装方法は murlog API / ActivityPub 仕様 を、設計判断の背景は プロダクト概要 を参照。

概念マップ

┌─────────────────────────────────────────────────────┐
│                    インスタンス                      │
│  1人のオーナーが所有する murlog サーバー              │
│                                                     │
│  ┌──────────┐  ┌──────────┐                         │
│  │ ペルソナA │  │ ペルソナB │  (通常は1つ、追加可能)  │
│  │ @alice   │  │ @photos  │                         │
│  └────┬─────┘  └──────────┘                         │
│       │                                             │
│  ┌────┴────────────────────┐                        │
│  │  投稿  添付  リプライ    │                        │
│  │  メンション  CW          │                        │
│  │  フォロー  ブロック      │                        │
│  │  通知  タイムライン      │                        │
│  │  ピン留め                │                        │
│  └─────────────────────────┘                        │
└──────────────────────────┬──────────────────────────┘
                           │ ActivityPub
              ┌────────────┴────────────┐
              │  リモートサーバー群      │
              │  Mastodon / GoToSocial   │
              │  Misskey / 他の murlog   │
              └─────────────────────────┘

インスタンス

murlog の1つのインストール。1人のオーナーが所有し、1つのドメインに紐づく。

  • マルチユーザーではない。ログインする人間は常に1人
  • 外部からは通常の ActivityPub サーバーと区別がつかない
  • 初回アクセス時にセットアップウィザードが走り、ドメイン・ペルソナ・パスワードを設定して運用開始

ペルソナ

インスタンス上の ActivityPub Actor。外部からは独立したアカウントに見える。

  • デフォルトはプライマリペルソナ 1 人。必要に応じて追加できるオプトイン構造(例: 本アカウントと写真用アカウント)
  • 外部からは 1 ペルソナ = 1 アカウントに見える(通常のマルチユーザーサーバーと区別なし)
  • ペルソナごとにユーザー名・表示名・自己紹介・アバター・ヘッダー画像・カスタムフィールド・公開設定を持つ
  • Locked: 承認制フォロー(manuallyApprovesFollowers)。false なら自動承認
  • ShowFollows: フォロー・フォロワー一覧の公開/非公開
  • Discoverable: 検索結果への表示可否
  • ペルソナごとに RSA 鍵ペアを持ち、ActivityPub の署名に使う
  • フォロー・フォロワー・通知はペルソナごとに完全独立
  • DB は共有(テーブルレベルで persona_id により分離)
  • 認証はインスタンス全体で共通。1 回のログインで全ペルソナを操作できる

カスタムフィールド

ペルソナのプロフィールに表示する key-value メタデータ。

  • 最大 4 件(Mastodon の慣習に合わせる)
  • 名前と値のペア(例: 「Webサイト」「https://example.com」)
  • 値にはリンクを含められる
  • ActivityPub の Actor attachment 配列に PropertyValue タイプとして含める
  • リモート Actor のカスタムフィールドも受信時に保存し、プロフィール表示で使う

ペルソナが1つのとき

  • アカウント切り替え UI は表示しない
  • 単一アカウントの ActivityPub サーバーとして振る舞う

ペルソナが2つ以上のとき

  • グローバルスイッチャー(Twitter 風)が有効化される
  • 投稿・フォローなど全操作が「どのペルソナとして行うか」の文脈を持つ
  • 各画面にローカルなペルソナ選択 UI は置かない(切り替えはグローバルスイッチャーに集約)

投稿

テキスト(HTML)+ メディア添付で構成されるコンテンツ。ローカル投稿とリモート投稿がある。

ローカル投稿

オーナーが自分のペルソナとして作成する投稿。

  • 作成 → 編集 → 削除のライフサイクル
  • 公開範囲を指定できる(公開 / 未収載 / フォロワー限定)
  • 多言語コンテンツ(contentMap)を持てる
  • 作成するとフォロワー全員に配送される
  • 編集するとフォロワー全員に更新が配送される
  • 削除するとフォロワー全員に削除が配送される

リモート投稿

フォローしているリモート Actor から Inbox に届いた投稿。

  • 受信時に URI ベースで重複排除し、HTML をサニタイズして保存
  • ローカル投稿と同じテーブルに格納し、タイムラインで統合表示される
  • 投稿者の Actor URI を保持し、プロフィール参照に使う
  • リモート側で削除されると Delete Activity が届き、ローカルからも消える

添付ファイル

投稿に紐づくメディアファイル。

  • 画像をアップロードすると、リサイズ・サムネイル生成・EXIF 除去・HEIC→JPEG 変換が行われる
  • ファイルはメディアストア(ローカルファイルシステム or S3 互換)に保存
  • 代替テキスト(alt)を設定できる
  • 投稿に紐づく前(アップロード直後)は PostID がゼロ値の孤立状態で存在する

リプライ

他の投稿への返信。ローカル投稿・リモート投稿の両方にリプライできる。

  • 投稿は in_reply_to(返信先の投稿 URI)を持てる
  • リプライ先がローカル投稿の場合は投稿 ID で参照、リモート投稿の場合は URI で参照
  • リプライを作成すると、通常のフォロワー配送に加えてリプライ先の Actor の Inbox にも配送される
  • ActivityPub の inReplyTo プロパティにマッピング

スレッド表示

  • 投稿の詳細画面で、その投稿に連なるリプライチェーンをスレッドとして表示する
  • 祖先(自分の返信先を遡ったチェーン)と子孫(自分への返信ツリー)を取得して表示
  • リモート投稿のスレッドは、手元にあるデータの範囲で表示する(未取得の投稿は辿れない)

CW(Content Warning)と sensitive メディア

投稿の内容を折りたたむ機能と、メディアをぼかす機能。それぞれ独立した概念だが連動する。

CW

  • 投稿は summary(警告テキスト)を持てる
  • summary が設定された投稿は、タイムライン上で本文が折りたたまれ、警告テキストと展開ボタンのみ表示される
  • ユーザーが展開ボタンを押すと本文と添付が表示される
  • ActivityPub の summary プロパティにマッピング
  • リモート投稿の summary も受信時に保存し、同様に折りたたみ表示する

sensitive

  • 投稿は sensitive フラグを持てる
  • sensitive が true の投稿は、添付メディアがぼかし表示され、タップで表示される
  • CW を設定すると sensitive も自動的に true になる(Mastodon 準拠)
  • CW なしで sensitive のみを設定することもできる(メディアだけセンシティブにしたい場合)
  • ActivityPub の sensitive プロパティにマッピング
  • リモート投稿の sensitive も受信時に保存し、同様にぼかし表示する

メンション

投稿内で他の Actor を参照する機能。

  • 投稿本文に @user@domain を記述すると、その Actor へのメンションとして認識される
  • メンション先の Actor は投稿作成時に WebFinger で解決し、Actor URI を取得する
  • メンション先の Actor の Inbox に投稿が配送される(フォロー関係がなくても届く)
  • ActivityPub の tag 配列に Mention タイプとして含め、cc にメンション先の Actor URI を追加する
  • リモート投稿に自分へのメンションが含まれる場合、mention 通知が生成される(既存の通知定義と連動)

公開範囲

投稿がどこまで届くかを制御する。

範囲ローカルでの見え方連合での振る舞い
公開公開タイムラインに載るフォロワー全員 + Public 宛に配送
未収載公開タイムラインには載らないフォロワー全員に配送、Public は cc
フォロワー限定フォロワーのみ閲覧可能フォロワー全員に配送、Public なし
ダイレクト指定受信者のみ閲覧可能指定受信者のみに配送、Public なし、フォロワーコレクションなし

ダイレクト (DM) はタイムラインから除外される。

フォロー

ペルソナが他の Actor をフォローすること、またはその逆。方向によって異なる概念。

送信フォロー(自分 → リモート)

自分のペルソナがリモート Actor をフォローする。

  1. ユーザーが @user@remote.example を指定してフォロー操作
  2. WebFinger でリモート Actor の URI と Inbox を解決
  3. Follow Activity を相手の Inbox に配送するジョブがキューに入る
  4. この時点では「申請中」(未承認)状態
  5. 相手が Accept を返すと「承認済み」になる
  6. 承認後、相手の投稿が自分の Inbox に届くようになる
  7. 相手が Reject を返すとフォローレコードが削除される

受信フォロー(リモート → 自分)

リモート Actor が自分のペルソナをフォローする。ペルソナの Locked 設定により動作が変わる。

Locked = false(自動承認):

  1. 相手が Follow Activity を自分の Inbox に POST
  2. フォロワーレコードを作成(Approved = true
  3. Accept Activity が自動的に相手に配送される
  4. 以降、自分が投稿するとこのフォロワーにも配送される

Locked = true(承認制):

  1. 相手が Follow Activity を自分の Inbox に POST
  2. フォロワーレコードを作成(Approved = false、保留中)
  3. Accept は送らない。Actor JSON の manuallyApprovesFollowers: true で承認制を宣言
  4. オーナーが followers.pending で未承認リストを確認
  5. followers.approveApproved = true + Accept 配送
  6. followers.reject → フォロワー削除 + Reject 配送

フォロー解除

  • 自分から: Undo Follow を相手の Inbox に配送し、ローカルのフォローレコードを削除
  • 相手から: Undo Follow が Inbox に届き、フォロワーレコードを削除

ブロック

特定のリモート Actor またはドメインとのやり取りを拒否する機能。インスタンス全体に適用される(ペルソナごとではない)。一人用サーバーなので、ブロック判断はオーナー個人の意思決定として扱う。

アクターブロック

特定のリモート Actor をブロックする。

送信ブロック(自分 → リモート)

  1. ブロックレコードを作成
  2. 既存のフォロー関係を双方向とも削除(全ペルソナの送信フォロー + 受信フォロワー)
  3. Block Activity を相手の Inbox に配送
  4. 以降、その Actor からの Activity を Inbox 入口で拒否

受信ブロック(リモート → 自分)

  1. Block Activity が Inbox に届く
  2. 既存のフォロー関係を双方向とも削除
  3. ブロックレコードは保存しない(こちらから再フォローしても相手側で拒否されるだけ)

ブロック解除

  • 自分から: ブロックレコードを削除し、Undo Block を相手に配送。フォロー関係は自動復元しない
  • 相手から: Undo Block が届いても特に処理なし(レコードを保存していないため)

ドメインブロック

特定ドメインのサーバー全体とのやり取りを拒否する。

  1. ドメインブロックレコードを作成
  2. そのドメインに属する全フォロー関係を一括削除(全ペルソナ)
  3. Activity 配送はしない(サイレント拒否)
  4. 以降、そのドメインの任意の Actor からの Activity を Inbox 入口で拒否

ドメインブロック解除時は、レコードを削除するのみ。フォロー関係は自動復元しない。

ブロック中の振る舞い

操作振る舞い
ブロック済みアクター/ドメインからの FollowInbox で拒否(202 返却、処理しない)
ブロック済みアクター/ドメインからの Like / Announce同上
ブロック済みアクター/ドメインからの Create (Note)同上
ブロック済みアクター/ドメインの既存投稿タイムラインに残す(受信済みデータは削除しない)
ブロック済みアクターへのフォロー操作API でエラーを返す

将来の拡張(v2 以降)

  • ブロックリストの CSV インポート / エクスポート(Mastodon 互換フォーマット: アカウント user@domain、ドメイン domain の1行1エントリ)

タイムライン

ペルソナに紐づく投稿の時系列表示。

ホームタイムライン

  • 自分のローカル投稿 + フォロー先から受信したリモート投稿を時系列で統合表示
  • 内部的には、そのペルソナの persona_id で紐づく投稿を新しい順に取得しているだけ
    • ローカル投稿: persona_id = 投稿者(自分)
    • リモート投稿: persona_id = 受信先ペルソナ(自分)
  • カーソルベースのページネーション

通知

ペルソナに対して発生するイベントの記録。

種類トリガー
followリモート Actor が自分をフォローした
mentionリモート投稿で自分がメンションされた
reblog自分の投稿がリブログされた
favourite自分の投稿がお気に入りされた
  • 既読 / 未読の状態を持つ
  • 個別既読、全既読の操作ができる
  • WebSocket 接続中はリアルタイムプッシュ、CGI 環境ではポーリングで取得

ピン留め投稿

プロフィールページの先頭に固定表示する投稿。

  • ペルソナごとに最大 1 件のローカル投稿をピン留めできる
  • ピン留めした投稿はプロフィールページの投稿一覧の先頭に表示される
  • ActivityPub の Featured コレクション(/users/:id/collections/featured)として公開する
  • リモートサーバーがプロフィールを表示する際に Featured を取得してピン留め投稿を表示できる

リブログ / お気に入り

リモート Actor が自分のローカル投稿に対して行うリアクション。

  • リブログ(Announce): 相手が自分の投稿をリブログした記録。通知を生成
  • お気に入り(Like): 相手が自分の投稿をお気に入りした記録。通知を生成
  • Undo Announce / Undo Like が届くと記録を削除

リモート Announce の受信(リブログ投稿)

フォロー中のユーザが第三者の投稿をリブログした場合の処理。

  • Announce Activity の Object URI がローカル投稿でない場合、FetchNoteSigned でリモート Note を取得
  • storeRemoteNoteorigin="remote" の投稿として保存。reblogged_by_uri にリブログ元 Actor URI をセット
  • ensureRemoteActorCached で投稿者・リブログ元の Actor 情報を自動フェッチ+キャッシュ
  • フロントでは reblogged_by_uri がある投稿にのみリブログラベルを表示
  • 冪等性: 同じ Note URI が既に存在する場合はスキップ

リモート Actor

フォロー・フォロワー関係にあるリモート ActivityPub Actor のキャッシュ。

  • Actor URI をキーに、ユーザー名・表示名・Inbox URL・公開鍵・アバター URL を保持
  • HTTP Signature の検証やプロフィール表示に使う
  • FetchedAt でキャッシュの鮮度を管理

認証

インスタンスのオーナーを認証する仕組み。ログインする人間は1人。

ログイン

  • パスワードで認証し、HttpOnly Cookie(セッション)を発行
  • 以降のリクエストで Cookie が自動送信される
  • セッションは DB に保存され、有効期限を持つ

パスワードリセット

メールに依存しない物理アクセスベースのリセットフロー。

  1. サーバー上で murlog reset を実行(または murlog.reset ファイルを手動作成)
  2. トークン入りのリセット URL でブラウザからアクセス
  3. 新パスワードを設定

FTP/SSH でサーバーにアクセスできる = オーナーである、という前提に立つ。

セットアップ

インスタンスを初めて使い始めるまでの流れ。2ステップで完了する。

  1. サーバー設定 — データの保存先を決める
  2. サイトセットアップ — ドメイン名・ペルソナ・パスワードを設定して運用開始

未完了の場合、全アクセスがセットアップ画面に誘導される。

ハッシュタグ

投稿にハッシュタグを付けて分類する機能。

  • 投稿本文の #tag を解析し、HashtagsJSON に保存(hashtag/ パッケージ)
  • タグ別の投稿一覧を posts.list_by_tag RPC で取得できる
  • 公開タグページ /tags/:tag で SSR + SPA 表示(handler/tags.go
  • ActivityPub の tag 配列に Hashtag タイプとして含める

v2 以降の拡張

  • ダイレクトメッセージ(direct visibility)
  • ミュート(ブロックより柔軟なフィルタリング)
  • ブックマーク(ローカル保存、連合しない)
  • 全文検索