2
1

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 を作りました

2
Last updated at Posted at 2025-12-22

この記事はNostr Advent Calendar 2025 の 22 日目の記事です。
<< 前回 mono さん「今年もいろんなことがありましたね」 | 今回 | 次回 penpenpeng さん「unipls」 >>

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

image.png

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

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

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

いろんなフィルタ

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

例: 言語フィルタ

例えば、リレーを wss://relay.damus.io にして、左上の「+フィルタ」→「+言語」を選んで言語ノードを作って、"中国語" を選択すると、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

フォロータイムライン

通常のクライアントがやっているフォロータイムラインを表示するのは、実は nostr では結構大変で、下記の手順を踏む必要があります。

  • 購読1: bootstrap リレーからユーザの kind:10002 を購読
  • 購読2: kind:10002 にある r タグの値をリレーリストとして抽出してユーザーの kind:3 を購読
  • 購読3: kind:3 にある p タグの値をフォロイーとして抽出してフォロイーの kind:1 を購読

このように、3段階の購読を経てやっと可能になります。

これを mojimoji で実現したものはこれです。

image.png

もじゃもじゃですね。

  • 赤色: kind:10002 を購読してリレーリストを抽出
  • 緑色: kind:3 を購読してフォローリストを抽出
  • 青色: kind:1 を購読してタイムライン形成

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

グラフの保存と共有

作ったグラフは 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 な関数が動く、みたいなプラグインを作ればいいのかも。

2
1
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
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?