0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

モジュラー型 Nostr クライアント mojimoji を作りました

Posted at

この記事はNostr Advent Calendar 2025 の 22 日目の記事です。
<< 前回 mono さん | 次回 penpenpeng さん >>

誰でもお好みの Nostr クライアントが作れるサイト mojimoji(もじもじ)を作ってみました。

image.png

上記は初期画面ですが、wss://yabu.me に投稿された投稿、たとえば「ぽわ~」が線を流れていってタイムラインに入るので、左のタイムラインに表示されるわけですね。

こんな感じでパーツとパーツを繋いで回路を作るようなスタイルを「モジュラー」といいます。ギターのコンパクトエフェクターとか、VSTPlugin とか Max/Msp とか、楽器はモジュラー型になっていることが多いですね。あとは Blender とか Unity のシェーダーとか、3D モデリングのソフトウェアはモジュラー型になっていることが多いです。

この箱の形をしたパーツのことを、このアプリではノードと呼びます。上記の画面だとリレーノードとタイムラインノードがあります。各ノードは端子として入力ソケット出力ソケットを持っています。入力ソケットからは別の入力ソケットへパイプが繋げます。この繋がり全体のことをグラフと呼びます。

いろんなフィルタ

mojimoji にはいろいろなフィルタがあって、組み合わせることができます。

例: ロシア語フィルタ

例えば、リレーを wss://relay.damus.io にして、左上の「+フィルタ」→「+言語」を選んで言語ノードを作って、"Russian" を選択すると、wss://relay.damus.io に投稿されたロシア語の投稿だけがフィルタされてタイムラインに見えてきます。

image.png

例: yabu.me にあって relay.damus.io にない投稿を取り出す

もうちょっと複雑なのだと、wss://yabu.me の投稿から wss://relay.damus.io の投稿を引き算すると、「yabu.me にはあるけど relay.damus.io にはない投稿」だけが抽出できます。

image.png

グラフの保存と共有

作ったグラフは auto save によって勝手に localStorage に保存されているので、サイトを閉じても閉じる前に開いていたグラフが戻ってきます。

複数のグラフを保存したいときは、保存ボタンを用いて、localStorage / Nostr Relay / File の3つの保存先を選ぶことができます。フォルダも作れちゃったりなんかしてます。
image.png

Nostr Relay を選ぶと、kind:30078 の上書き可能イベントとしてリレーに放流されます。

Nostr Relay で保存したときは共有ボタンが出てきて、パーマリンク URL をコピーすることができます。例えばさっきの「yabu.me にあって relay.damus.io にない投稿を取り出すグラフ」のアドレスは
https://koteitan.github.io/mojimoji/?e=nevent1qqs9up775g30e28wge75s42sl46udkzxqk5v7hrdsm4jc7a6ks5dqhg63lukt
です。クリックすると開けますね。(読込で選択した他人のグラフのパーマリンクを取得することもできます)

保存仕様の全体はここに載っています。

アプリ固有データは今回初めて使ったので勉強になりました。

実装の話

今回は observable パンクです。

複数のリレーから同じようなイベントがわちゃわちゃと流れてきて「observable で実装して下さい」と言わんばかりの分散プロトコル nostr。そんなものをさばくライブラリはもちろん rx-nostrRxJS で実装された nostr ライブラリですね。線を繋ぐ UI は rete.js。余りある適材適所感。

全体的には下記を使っています:

モジュラー型 UI rete.js
UI React
Nostr ライブラリ rx-nostr
多言語化(英語・日本語) react-i18next
言語判定 franc
プログラミング言語 TypeScript
ビルドツール Vite

演算ノード

演算ノードはちょっと面白いので解説します。

AND ノード

AND ノードは入力 A, B を持っていて、両方から流れてきたイベントだけが OUT に出ていきます。集合論でいうところの積集合(∩)ですね。

でもこれは非同期なシステムなので、全員揃った A と全員揃った B で積集合を計算して~とかができません。A からは来てるけど B にはあるけどまだ来てない、みたいなこともありえます。

これから「まだ片側しか来ていないイベントは AND ノードで待たされる」という実装になっています。

image.png
両方から同じイベントが来たら、やっと通れます。
image.png

引き算ノード

集合論でいうところの差集合(/)です。仕様としては、A にあって B にないものだけが通れます。

まずリレーから来たイベントは一旦すべて「+」の符号が付けられます。

そして、この引き算ノードを通ると、B の入り口から入ってきたものの符号が反転します。
image.png
上の図では「ぽわ~」は「+」の符号を持った状態で B の入り口から入ってきたので、その後は「-」の符号に反転しています。「うわ~」は A から入ってきたので「+」のまま出て行ってますね。

ここで、「ぽわ~」は A のみから、「うわ~」は A と B の両方からやってきたとしましょう。
image.png
そのあと、これら3つはタイムラインに貯まるのですが、タイムラインでは、同じイベントの「+」と「-」があった場合にはそれらは対消滅するように出来ています。

かくしてぷらすぽわだけが残りました。よかったですね。

Yahoo! Pipes

試験的にこの UI を nostr で人に見せてみたら、「Yahoo! Pipes みたい」という声がいくつかありました。

Yahoo_Pipes_screenshot.png

Yahoo! Pipes(ヤフー パイプス)は 2007年2月から2015年9月まで Yahoo! が提供していたアプリで、Pipes Editor というアプリ上でモジュールをドラッグしてパイプで接続し、異なるソースから取得した情報をどのように加工(例えばフィルタリング)すべきかのルールを設定して、RSS フィード や JSON、KML などで出力する、というものだったらしいです。

twitter のローンチが2006年7月なので、そこから半年くらいでもう作られていたんですねぇ。

今後

関数ノード

関数定義入力ノードと関数定義出力ノードがあって、それらの間に好きなノードを挟んでテンプレートを作ったら、そのインスタンスを小さなノードとして表示できて、再帰的に構造化できると巨大なものが作りやすいなと思いました。

Bluesky のモデレーションフィルター化

もともと Bluesky のモデレーションフィルターとして、こういう自由に組み合わせて誰でも作れるものが作りたいなぁと思っていたのが発端でした。あれなんかサーバーとか起動しとかないといけないんでしたっけ。面倒そうで止めてたところ claude code のトークンが余ってたので適当に Nostr で作り始めたらできました。なので、Bluesky 化も時間があればやりたいですね

プラグイン化

Rabbit (Nostr クライアント)はいろいろなカラムを作れるマルチカラムクライアントなので、その1カラムを作れるように出来たらいいなと思っていました。グラフの json を与えると json2json な関数が動く、みたいなプラグインを作ればいいのかも。

フォロータイムライン完全再現

ここまでやっても実は通常のクライアントがやっているフォロータイムラインは得られません。なぜなら、あれを得るためには

  • 購読1: bootstrap リレーからユーザの kind:10002 イベントを取得
  • 購読2: kind:10002 にある r タグの値をリレーリストとして抽出してユーザーの kind:3 イベントを取得
  • 購読3: kind:3 にある p タグの値をフォローリストとして抽出してフォロイーの投稿を取得
    と3段階の購読を行わないといけないからです。

現在の mojimoji には下記の機能が足りません。

  • タグの値を抽出するタグ値抽出ノード
  • タグ値抽出ノードの出力はもはやイベントではなく relay URL だったり pubkey だったりするので流れるものに関して型の概念が必要になる(各ノードは複数の型に対応したテンプレートとなる)
  • リレーノードに購読スタートを指令するトリガーソケット
  • リレーの EOSE を出力してそれによって別の購読を開始してもらうためのリレー状態ソケット

今現在これのデバッグ中で、標準的タイムラインを得るグラフは下記になります(正しく動いてないけど)
image.png
もじゃもじゃですね。

  • 黄色: kind:10002 を購読して relay list を抽出
  • 緑色: kind:3 を購読してフォローリストを抽出
  • 青色: authors にそのフォローリストを突っ込んでタイムライン形成

いやぁ、クライアントってすごいですね。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?