Skip to content

feat: 既存Misskeyフォークの独自機能をAiscriptで再現 #28

Description

@hitalin

背景

Misskey のフォーク群には「サーバーを改造しないと無理」な機能と、「実はクライアント層だけで十分再現できる」機能が混在している。後者は本家 Misskey / NoteDeck に乗ったまま misstore の AiScript プラグイン・ウィジェット・クエリとして提供できるはずで、フォークを乗り換えずに同等の体験を得られるようにしたい。

主要フォーク 36 件の README / 独自 CHANGELOG / 独自ブランチのコミットを調査した結果は下のコメントにカタログとして残してある。

実現レイヤーの分け方

手段 再現できるもの
L1 AiScript プラグイン (register_note_view_interruptor / note_post_interruptor / note_action / user_action / post_form_action) ノートの表示加工、投稿前加工、メニュー追加、API 叩き
L2 NoteDeck ウィジェット (AiScript App) 常駐処理・定期実行・独自 UI が要るもの
L3 カラムフィルタクエリ (notedeck#783) 「見せる / 隠す」だけで済むもの
L4 再現不可 連合・DB・ロール・モデレーション・AP の実装が必須なもの

採否の基準(実装を通して固まったもの)

  1. UI がネイティブで完結するか — プラグインは CSS を注入できず独自 UI も持てない。使えるのはノート / ユーザーメニュー、投稿フォームのボタン、Plugin:config の設定画面だけ
  2. 本文テキストを汚さないかnote_view_interruptor で本文に印やバッジを差し込む手法はコピペや引用に混入するので採らない
  3. 常駐に依存しないか — 「時間が来たら消える / 投稿される」系はクライアントが開いている間しか動かない。発火保証のない機能を保証があるかのように出さない

実装状況

機能 出典 エントリ 状態
カクヨム形式のルビ振り 隠れ家 L1 plugins/kakuyomu-ruby e512fd8
技術系オートリンク tempura, taiyme L1 plugins/tech-autolink 52f7627
CW ⇄ 本文の入れ替え MISTEMS L1 plugins/cw-swap(仮) 📝 未着手
ハッシュタグミュート MISTEMS L3 queries/hashtag-hide(仮) 📝 未着手

✅ ルビ振り(e512fd8)

|漢字《かんじ》$[ruby 漢字 かんじ]。MFM の rubytext.split(' ') で親文字を切るため、親文字やルビに空白を含む場合は変換しない《》 を引用符として使う日本語文への誤爆を避けるため縦棒は必須。コード内も対象外。

✅ 技術系オートリンク(52f7627)

CVE / JVN / JVNVU / JVNTA / JVNDB / DLsite / ニコニコ動画 / GitHub コミットハッシュ。tempura の tms/rj-number.ts表示時に VNode を差し込むのに対し、こちらは投稿時に Markdown リンクへ落とすのでプラグイン未導入の閲覧者やリモートのサーバーでもリンクになる

誤検出対策として本家フォークの正規表現より条件を厳しくした:

  • コミットハッシュは 16 進の連なりを読み切って 40 桁ちょうどのみ採用(tempura の [0-9a-f]{40} は SHA-256 の先頭 40 桁にマッチする)
  • 前後に英数字が隣接する場合は語中とみなして変換しない(XCVE-2024-1234 / CVE-2024-1234x
  • コード / URL 内 / Markdown リンク内 / ハッシュタグは変換しない。二重変換しても壊れない
  • リンク先 URL は大文字の正規 ID に正規化する
  • ニコニコは sm に加えて nm / so にも対応

📝 残りの 2 件

CW ⇄ 本文の入れ替えMkPostForm.vueshowActions を確認済み。プラグインに渡るのは textcw だけで必要十分だが、updatetypeof value !== 'string' で握り潰すため null を渡して CW をオフにできない。CW が空の状態で swap すると欄が開いたまま空で残るので、そのケースは実行せずダイアログで知らせる。

ハッシュタグミュート — notedeck#783 の allowlist で note.tags は NormalizedNote に存在せず不採用とされているため、note.text.lower().incl("#タグ") の否定形になる。str.incl / str.lower は QIR の演算 allowlist 内なので層 1(高速パス)に乗るが、否定形は FTS5 プリフィルタに押し込めない(keyword-hide と同じ性質)。前方一致の誤爆(#技術書典#技術書典2024 に当たる)も meta に明記する。

実装メモ(AiScript / プラグイン API の落とし穴)

2 本の実装で踏んだもの。次のエントリでも効く。

  • out は予約語 — 変数名に使うと構文エラーになる
  • "...".incl("") は true を返す — 1 文字ずつ判定する関数では空文字チェックを先に入れないと、文末で誤判定する(実際にオートリンクの初版がこれで全件スキップした)
  • Str:tab は存在しない — 空白判定は /   / Str:lf で組む
  • 正規表現がない — 文字単位の手書きスキャナを書く。pakuru が先例
  • note_post_interruptor は同期実行 — 確認ダイアログも Mk:api も使えない。したがって「投稿時に黙って本文が書き換わる」形になるので、post_form_action の明示変換を既定にし、自動変換は Plugin:config のオプトインにするのを定石とする(post-guard の設計と同じ結論)
  • テストは実インタプリタで回せる@syuilo/aiscript@1.2.1 を入れて Plugin:*values.FN_NATIVE のダミーとして注入すれば、plugin.is にテストコードを連結して実行できる。ルビ振り 24 ケース / オートリンク 44 ケースはこの方法で検証した

保留(品質基準を満たせないもの)

機能 出典 理由
公開範囲の色分け / 青ふぁぼ・赤ふぁぼ Ebisskey, MISTEMS CSS を注入できず、絵文字プレフィックスでの近似は本文を汚す
セルフリノート省略 / Renote 省略条件 隠れ家 note_view_interruptor はノートの非表示化を保証しない
nyaize 無効化 / インク節約モード tempura, Ebisskey 表示テキストの後段書き換えになりコピー時に加工後テキストが混入する
エアリプ / ニックネーム登録 Ebisskey, tempura プラグインに自由入力 UI がない(Mk:dialog は表示のみ、Mk:confirm は yes/no のみ)
予約投稿 / ノートの自動消滅 tempura, 隠れ家 クライアント常駐依存で発火保証がない
アカウント整理(一括削除) tempura 破壊的 × 常駐依存
タイムマシン MISTEMS L2 なら実現可能。プラグイン 4 件が片付いてから再検討する

L4: 再現不可(参考・対象外)

  • ノート編集 + 編集履歴 + 編集の連合(serafuku)— AP の実装が必須
  • セミパブリック(LTL 非掲載の public ノート)、コミュニティロールロール宛お知らせ(tempura)
  • ホスト単位の公開範囲制限 / モデレーターによる公開範囲降格 / canIgnoreSensitiveWords(hoto.moe)
  • VRTL / VSTL 本体(anatawa12)— nodeinfo 拡張とサーバー側 TL 実装が必要。L3 クエリでサーバー allowlist による近似は可能
  • リモートと同名のリアクションPGroonga 全文検索(paricafe)、オブジェクトストレージ PATH 分離・孤立 S3 掃除(tempura, serafuku)
  • 禅モード(隠れ家)、加入承認制dimension(MisskeyIO)ほかサーバー設定・UI シェル本体に手を入れるもの

進め方

  • 1 機能 = 1 エントリ = 1 PR / コミット
  • 出典フォークは meta.json の説明と plugin.is の冒頭コメントに明記して敬意を払う
  • フォーク側の実装が読める場合は必ず読み、同等以上(誤爆耐性・連合先での見え方)を目標にする

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions