diff --git a/public/registry/index.json b/public/registry/index.json index dd02a08..a0081f1 100644 --- a/public/registry/index.json +++ b/public/registry/index.json @@ -1,24 +1,24 @@ { "version": 1, - "updatedAt": "2026-11-04T00:13:26.700Z", + "updatedAt": "2026-11-04T00:27:05.105Z", "plugins": { "count": 44, - "updatedAt": "2026-11-04T00:13:26.700Z" + "updatedAt": "2026-11-04T00:27:05.105Z" }, "themes": { "count": 77, - "updatedAt": "2026-11-04T00:13:26.700Z" + "updatedAt": "2026-11-04T00:27:05.105Z" }, "widgets": { "count": 70, - "updatedAt": "2026-11-04T00:13:26.700Z" + "updatedAt": "2026-11-04T00:27:05.105Z" }, "skills": { "count": 44, - "updatedAt": "2026-11-04T00:13:26.700Z" + "updatedAt": "2026-11-04T00:27:05.105Z" }, "queries": { "count": 35, - "updatedAt": "2026-11-04T00:13:26.700Z" + "updatedAt": "2026-11-04T00:27:05.105Z" } } diff --git a/public/registry/plugins.json b/public/registry/plugins.json index a77a9c8..dd67187 100644 --- a/public/registry/plugins.json +++ b/public/registry/plugins.json @@ -1,6 +1,6 @@ { "version": 1, - "updatedAt": "2026-11-04T00:13:26.700Z", + "updatedAt": "2026-11-04T00:27:05.105Z", "plugins": [ { "id": "ai-actions", @@ -67,7 +67,7 @@ { "id": "etherscan-autolink", "name": "Etherscan オートリンク", - "version": "1.0.0", + "version": "1.0.1", "author": "hitalin", "description": "Ethereum のアドレス (0x + 40 桁) / トランザクションハッシュ (0x + 64 桁) / ENS 名 (name.eth) を投稿時に Etherscan と app.ens.domains へのリンクにするプラグイン。ノートメニューの「Etherscan で開く」で本文中の最初の 1 件をブラウザで開ける。エクスプローラーの URL は設定で basescan.org などに変えられる。API キー・外部通信・権限は不要", "category": "posting", @@ -82,7 +82,7 @@ ], "sourceUrl": "https://store.notedeck.io/registry/plugins/etherscan-autolink/plugin.is", "apiUrl": "https://store.notedeck.io/registry/plugins/etherscan-autolink/api.json", - "sha512": "2232153b654aafcd0d63e304d9cfeeed0b2021e6e0395e3bdb7089a5cc045547b4ddb36371b08b580fa48f94a141e47e2d7b810cc3434fe0c344ddf6b010ab1c", + "sha512": "7381f997888a25ed41678eb25ffb6832e5a577d7a7d93d54f93fdcdd8a9a4ab20d62daa414a2f65711430bbba82f60f76416ae64ee317af915970d58fa7894f1", "createdAt": "2026-10-29T04:34:22+09:00", "updatedAt": "2026-10-29T04:35:41+09:00", "authorUrl": "https://github.com/hitalin", @@ -130,8 +130,8 @@ "sourceUrl": "https://store.notedeck.io/registry/plugins/mfm-search-engine/plugin.is", "apiUrl": "https://store.notedeck.io/registry/plugins/mfm-search-engine/api.json", "sha512": "7d1e5ca3fc6f99c231f6fd4de7cb146c8284dd0210662069fd88a1c4b833f47a6fb9bd2c6f70182f48f68481840984559d955c3be9033855eb5e7f02fbd3f90d", - "createdAt": "2026-11-04T00:13:27.209Z", - "updatedAt": "2026-11-04T00:13:27.209Z", + "createdAt": "2026-11-04T09:14:01+09:00", + "updatedAt": "2026-11-04T09:14:01+09:00", "authorUrl": "https://github.com/hitalin", "license": "MIT", "locales": { @@ -541,7 +541,7 @@ { "id": "web-search", "name": "Web 検索 & エゴサ", - "version": "1.0.0", + "version": "1.0.1", "author": "hitalin", "description": "ノート本文を Web 検索、ユーザーの表示名と username でエゴサ。検索エンジンは brave / startpage / searxng / google / duckduckgo から選択", "category": "utility", @@ -553,7 +553,7 @@ ], "sourceUrl": "https://store.notedeck.io/registry/plugins/web-search/plugin.is", "apiUrl": "https://store.notedeck.io/registry/plugins/web-search/api.json", - "sha512": "1031e2cadc7f74f1f9eaec402dd79f577bb7a8a0efce721a099587a7b7c5129c7e0f76165770dde7ef22345bb8ff00c3e9a355e82a1441bba6f4fafa498dd8e1", + "sha512": "12dc49a10c1fd871dfaf45ab3b25ff55336cf3ae98659b67e17d41cb893674279ff620e5e189c437b4fa084e9cd7cfa13ff7058676a20398343ba2e1a294fc93", "createdAt": "2026-07-15T06:44:02+09:00", "updatedAt": "2026-09-25T18:20:03+09:00", "authorUrl": "https://github.com/hitalin", diff --git a/public/registry/plugins/etherscan-autolink/api.json b/public/registry/plugins/etherscan-autolink/api.json index 296b83e..ef9f1aa 100644 --- a/public/registry/plugins/etherscan-autolink/api.json +++ b/public/registry/plugins/etherscan-autolink/api.json @@ -1 +1 @@ -{"type":"plugin","data":"/// @ 1.2.1\n\n// Etherscan オートリンク — アドレス / tx ハッシュ / ENS 名を自動リンク化\n//\n// tech-autolink と同じ投稿時テキスト変換。本文中の\n// - 0x + 40 桁 16 進 (アドレス) → {explorerBase}/address/0x…\n// - 0x + 64 桁 16 進 (tx ハッシュ) → {explorerBase}/tx/0x…\n// - name.eth (ENS 名) → https://app.ens.domains/name.eth\n// を Markdown リンクにする。表示時変換ではなく本文に残るので、\n// このプラグインを入れていない閲覧者やリモートのサーバーでもリンクになる。\n// ノートメニューの「Etherscan で開く」は本文中の最初の 1 件をブラウザで開く。\n//\n// 誤爆を避けるため、以下は変換しない:\n// - コード (` / ```) の中\n// - URL の中、Markdown リンク ([ラベル](URL)) の中、ハッシュタグ\n// - 前後に英数字が隣接しているもの、桁数が 40 / 64 以外の 16 進\n//\n// API キー・外部通信・権限は不要 (リンクを組み立てるだけ)。\n\n### {\n\tname: \"Etherscan オートリンク\"\n\tversion: \"1.0.0\"\n\tauthor: \"@hitalin\"\n\tdescription: \"Ethereum のアドレス / tx ハッシュ / ENS 名を投稿時に Etherscan へのリンクにします。ノートメニューから Etherscan で開くこともできます\"\n\tconfig: {\n\t\taddress: {\n\t\t\ttype: \"boolean\"\n\t\t\tlabel: \"アドレス (0x + 40 桁)\"\n\t\t\tdefault: true\n\t\t}\n\t\ttx: {\n\t\t\ttype: \"boolean\"\n\t\t\tlabel: \"トランザクションハッシュ (0x + 64 桁)\"\n\t\t\tdefault: true\n\t\t}\n\t\tens: {\n\t\t\ttype: \"boolean\"\n\t\t\tlabel: \"ENS 名 (name.eth)\"\n\t\t\tdescription: \"リンク先は app.ens.domains\"\n\t\t\tdefault: true\n\t\t}\n\t\texplorerBase: {\n\t\t\ttype: \"string\"\n\t\t\tlabel: \"エクスプローラーの URL\"\n\t\t\tdescription: \"https://etherscan.io 以外のチェーンを使うなら https://basescan.org や https://arbiscan.io に変える\"\n\t\t\tdefault: \"https://etherscan.io\"\n\t\t}\n\t\tautoConvert: {\n\t\t\ttype: \"boolean\"\n\t\t\tlabel: \"投稿時に自動で変換する\"\n\t\t\tdescription: \"オフの場合は投稿フォームのプラグインメニューから手動で変換します\"\n\t\t\tdefault: false\n\t\t}\n\t}\n}\n\nlet DEFAULT_BASE = \"https://etherscan.io\"\nlet HEXCHARS = \"0123456789abcdefABCDEF\"\nlet WORDCHARS = \"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ\"\nlet ENSCHARS = `{WORDCHARS}-.`\n// 1 回の変換で処理する上限。ENS 名を数百個並べた本文でもステップ上限 (100k) に届かないようにする\nlet MAX_LINKS = 100\n\n// 本文を 1 文字ずつ舐めると 3000 字でステップ上限 (100k) を超えるので、\n// 候補の出現位置 (0x / .eth) だけを str.index_of で拾い、その前後だけを検査する\n\n@isWordChar(c) {\n\t// 空文字に対して incl は true を返すため明示的に除く\n\tc != \"\" && WORDCHARS.incl(c)\n}\n\n@sub(chars, from, to) {\n\tchars.slice(from, to).join(\"\")\n}\n\n// chars[start] から allowed に含まれる文字を読み進める\n@readRun(chars, start, allowed) {\n\tvar i = start\n\tloop {\n\t\tif (i >= chars.len) break\n\t\tif (!allowed.incl(chars[i])) break\n\t\ti += 1\n\t}\n\t{\n\t\tvalue: sub(chars, start, i)\n\t\tnext: i\n\t}\n}\n\n// --- 各記法のマッチャー: 成立すれば { label, url, start, next }、しなければ null ---\n\n// chars[p] == \"0\" (0x の先頭) を起点に、0x + 40 桁 (アドレス) / 0x + 64 桁 (tx)。それ以外の桁数は拾わない\n@matchHexAt(chars, p, opts) {\n\tlet hex = readRun(chars, p + 2, HEXCHARS)\n\tvar path = null\n\tif (opts.address && hex.value.len == 40) path = \"address\"\n\tif (opts.tx && hex.value.len == 64) path = \"tx\"\n\tif (path == null) return null\n\tlet label = sub(chars, p, hex.next)\n\t{\n\t\tlabel: label\n\t\turl: `{opts.base}/{path}/{label}`\n\t\tstart: p\n\t\tnext: hex.next\n\t}\n}\n\n// chars[p] == \".\" (.eth の先頭) を起点に name.eth / sub.name.eth。ラベルは 3 文字以上、- で始まるものは拾わない\n@matchEnsAt(chars, p) {\n\tvar start = p\n\tloop {\n\t\tif (start <= 0) break\n\t\tif (!ENSCHARS.incl(chars[start - 1])) break\n\t\tstart -= 1\n\t}\n\tvar end = readRun(chars, p + 4, ENSCHARS).next\n\t// 文末の句点 (vitalik.eth.) は名前に含めない\n\tloop {\n\t\tif (end <= p) break\n\t\tif (chars[end - 1] != \".\") break\n\t\tend -= 1\n\t}\n\tlet label = sub(chars, start, end)\n\tlet lower = label.lower()\n\tif (!lower.ends_with(\".eth\")) return null\n\tlet name = lower.slice(0, lower.len - 4)\n\tif (name.len < 3) return null\n\tif (name.starts_with(\"-\")) return null\n\tvar ok = true\n\teach (let part, name.split(\".\")) {\n\t\tif (part == \"\") ok = false\n\t}\n\tif (!ok) return null\n\t{\n\t\tlabel: label\n\t\turl: `https://app.ens.domains/{lower}`\n\t\tstart: start\n\t\tnext: end\n\t}\n}\n\n// --- 変換対象外の判定 (候補ごとに前後を見る) ---\n\n// [ラベル](URL) の終端。成立しなければ null\n@readMdLink(chars, i) {\n\tvar j = i + 1\n\tloop {\n\t\tif (j >= chars.len) return null\n\t\tlet c = chars[j]\n\t\tif (c == \"]\") break\n\t\tif (c == Str:lf) return null\n\t\tj += 1\n\t}\n\tif (j + 1 >= chars.len || chars[j + 1] != \"(\") return null\n\tvar k = j + 2\n\tloop {\n\t\tif (k >= chars.len) return null\n\t\tlet c = chars[k]\n\t\tif (c == \")\") break\n\t\tif (c == \" \" || c == \" \" || c == Str:lf) return null\n\t\tk += 1\n\t}\n\tk + 1\n}\n\n// 空白区切りの直前部分 (候補を含むトークンの先頭から候補まで)\n@tokenBefore(before) {\n\tlet a = before.split(Str:lf)\n\tlet b = a[a.len - 1].split(\" \")\n\tlet c = b[b.len - 1].split(\" \")\n\tc[c.len - 1]\n}\n\n// start〜next の範囲をリンクにしてよいか\n@isLinkable(chars, start, next) {\n\t// 前後に英数字が隣接しているものは語の一部とみなす\n\tif (start > 0 && isWordChar(chars[start - 1])) return false\n\tif (next < chars.len && isWordChar(chars[next])) return false\n\tlet before = sub(chars, 0, start)\n\t// ` の数が奇数ならコードの中 (``` も 1 文字ずつ数えて辻褄が合う)\n\tif ((before.split(\"`\").len - 1) % 2 == 1) return false\n\t// URL / ハッシュタグを含むトークンの中\n\tlet tok = tokenBefore(before)\n\tif (tok.incl(\"https://\") || tok.incl(\"http://\")) return false\n\tif (tok.starts_with(\"#\")) return false\n\t// 同じ行の [ラベル](URL) の中\n\tlet lines = before.split(Str:lf)\n\tlet lineStart = start - lines[lines.len - 1].split().len\n\tvar q = chars.index_of(\"[\", lineStart)\n\tloop {\n\t\tif (q < 0 || q >= start) break\n\t\tlet end = readMdLink(chars, q)\n\t\tif (end != null && end > start) return false\n\t\tq = chars.index_of(\"[\", q + 1)\n\t}\n\ttrue\n}\n\n// from 以降で最も手前の候補位置 (0x の 0、.eth の .)。無ければ -1\n// str.index_of / split() はどちらもコードポイント単位 (stringz) なので添字を混ぜてよい\n@nextCandidate(text, from, opts) {\n\tvar p = -1\n\tif (opts.address || opts.tx) p = text.index_of(\"0x\", from)\n\tif (opts.ens) {\n\t\tlet q = text.index_of(\".eth\", from)\n\t\tif (q >= 0 && (p < 0 || q < p)) p = q\n\t}\n\tp\n}\n\n@convertLinks(text, opts) {\n\tlet chars = text.split()\n\tvar parts = []\n\tvar urls = []\n\tvar segStart = 0\n\tvar from = 0\n\tloop {\n\t\tif (urls.len >= MAX_LINKS) break\n\t\tlet p = nextCandidate(text, from, opts)\n\t\tif (p < 0) break\n\t\tlet hit = if (chars[p] == \"0\") matchHexAt(chars, p, opts) else matchEnsAt(chars, p)\n\t\tif (hit == null || !isLinkable(chars, hit.start, hit.next)) {\n\t\t\tfrom = p + 1\n\t\t} else {\n\t\t\tparts.push(sub(chars, segStart, hit.start))\n\t\t\tparts.push(`[{hit.label}]({hit.url})`)\n\t\t\turls.push(hit.url)\n\t\t\tsegStart = hit.next\n\t\t\tfrom = hit.next\n\t\t}\n\t}\n\tparts.push(sub(chars, segStart, chars.len))\n\t{\n\t\ttext: parts.join(\"\")\n\t\tcount: urls.len\n\t\turls: urls\n\t}\n}\n\n// 設定の URL が https でなければ既定に戻し、末尾の / は落とす\n@explorerBase() {\n\tvar base = Plugin:config.explorerBase\n\tif (Core:type(base) != \"str\") base = DEFAULT_BASE\n\tif (base.slice(0, 8) != \"https://\") base = DEFAULT_BASE\n\tloop {\n\t\tif (base.len <= 8) break\n\t\tif (base.slice(base.len - 1, base.len) != \"/\") break\n\t\tbase = base.slice(0, base.len - 1)\n\t}\n\tbase\n}\n\n@currentOpts() {\n\t{\n\t\taddress: Plugin:config.address\n\t\ttx: Plugin:config.tx\n\t\tens: Plugin:config.ens\n\t\tbase: explorerBase()\n\t}\n}\n\nPlugin:register_post_form_action(\"Etherscan リンクに変換\", @(form, update) {\n\tlet text = if (form.text == null) \"\" else form.text\n\tlet result = convertLinks(text, currentOpts())\n\tif (result.count == 0) {\n\t\tMk:dialog(\"Etherscan オートリンク\", \"リンクにできるアドレス / tx / ENS 名が見つかりません\", \"info\")\n\t\treturn null\n\t}\n\tupdate(\"text\", result.text)\n\tMk:toast(`{result.count} 件をリンクにしました`, \"success\")\n})\n\nPlugin:register_note_post_interruptor(@(note) {\n\tif (Plugin:config.autoConvert && note.text != null) {\n\t\tnote.text = convertLinks(note.text, currentOpts()).text\n\t}\n\tnote\n})\n\nPlugin:register_note_action(\"Etherscan で開く\", @(note) {\n\tlet text = if (note.text == null) \"\" else note.text\n\tlet result = convertLinks(text, currentOpts())\n\tif (result.count == 0) {\n\t\tMk:toast(\"Etherscan で開けるアドレス / tx / ENS 名がありません\")\n\t\treturn null\n\t}\n\tPlugin:open_url(result.urls[0])\n})\n"} +{"type":"plugin","data":"/// @ 1.2.1\n\n// Etherscan オートリンク — アドレス / tx ハッシュ / ENS 名を自動リンク化\n//\n// tech-autolink と同じ投稿時テキスト変換。本文中の\n// - 0x + 40 桁 16 進 (アドレス) → {explorerBase}/address/0x…\n// - 0x + 64 桁 16 進 (tx ハッシュ) → {explorerBase}/tx/0x…\n// - name.eth (ENS 名) → https://app.ens.domains/name.eth\n// を Markdown リンクにする。表示時変換ではなく本文に残るので、\n// このプラグインを入れていない閲覧者やリモートのサーバーでもリンクになる。\n// ノートメニューの「Etherscan で開く」は本文中の最初の 1 件をブラウザで開く。\n//\n// 誤爆を避けるため、以下は変換しない:\n// - コード (` / ```) の中\n// - URL の中、Markdown リンク ([ラベル](URL)) の中、ハッシュタグ\n// - 前後に英数字が隣接しているもの、桁数が 40 / 64 以外の 16 進\n//\n// API キー・外部通信・権限は不要 (リンクを組み立てるだけ)。\n\n### {\n\tname: \"Etherscan オートリンク\"\n\tversion: \"1.0.1\"\n\tauthor: \"@hitalin\"\n\tdescription: \"Ethereum のアドレス / tx ハッシュ / ENS 名を投稿時に Etherscan へのリンクにします。ノートメニューから Etherscan で開くこともできます\"\n\tconfig: {\n\t\taddress: {\n\t\t\ttype: \"boolean\"\n\t\t\tlabel: \"アドレス (0x + 40 桁)\"\n\t\t\tdefault: true\n\t\t}\n\t\ttx: {\n\t\t\ttype: \"boolean\"\n\t\t\tlabel: \"トランザクションハッシュ (0x + 64 桁)\"\n\t\t\tdefault: true\n\t\t}\n\t\tens: {\n\t\t\ttype: \"boolean\"\n\t\t\tlabel: \"ENS 名 (name.eth)\"\n\t\t\tdescription: \"リンク先は app.ens.domains\"\n\t\t\tdefault: true\n\t\t}\n\t\texplorerBase: {\n\t\t\ttype: \"string\"\n\t\t\tlabel: \"エクスプローラーの URL\"\n\t\t\tdescription: \"etherscan.io 以外のチェーンを使うなら basescan.org や arbiscan.io に変える (https の URL でも可)\"\n\t\t\tdefault: \"etherscan.io\"\n\t\t}\n\t\tautoConvert: {\n\t\t\ttype: \"boolean\"\n\t\t\tlabel: \"投稿時に自動で変換する\"\n\t\t\tdescription: \"オフの場合は投稿フォームのプラグインメニューから手動で変換します\"\n\t\t\tdefault: false\n\t\t}\n\t}\n}\n\nlet DEFAULT_BASE = \"https://etherscan.io\"\nlet HEXCHARS = \"0123456789abcdefABCDEF\"\nlet WORDCHARS = \"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ\"\nlet ENSCHARS = `{WORDCHARS}-.`\n// 1 回の変換で処理する上限。ENS 名を数百個並べた本文でもステップ上限 (100k) に届かないようにする\nlet MAX_LINKS = 100\n\n// 本文を 1 文字ずつ舐めると 3000 字でステップ上限 (100k) を超えるので、\n// 候補の出現位置 (0x / .eth) だけを str.index_of で拾い、その前後だけを検査する\n\n@isWordChar(c) {\n\t// 空文字に対して incl は true を返すため明示的に除く\n\tc != \"\" && WORDCHARS.incl(c)\n}\n\n@sub(chars, from, to) {\n\tchars.slice(from, to).join(\"\")\n}\n\n// chars[start] から allowed に含まれる文字を読み進める\n@readRun(chars, start, allowed) {\n\tvar i = start\n\tloop {\n\t\tif (i >= chars.len) break\n\t\tif (!allowed.incl(chars[i])) break\n\t\ti += 1\n\t}\n\t{\n\t\tvalue: sub(chars, start, i)\n\t\tnext: i\n\t}\n}\n\n// --- 各記法のマッチャー: 成立すれば { label, url, start, next }、しなければ null ---\n\n// chars[p] == \"0\" (0x の先頭) を起点に、0x + 40 桁 (アドレス) / 0x + 64 桁 (tx)。それ以外の桁数は拾わない\n@matchHexAt(chars, p, opts) {\n\tlet hex = readRun(chars, p + 2, HEXCHARS)\n\tvar path = null\n\tif (opts.address && hex.value.len == 40) path = \"address\"\n\tif (opts.tx && hex.value.len == 64) path = \"tx\"\n\tif (path == null) return null\n\tlet label = sub(chars, p, hex.next)\n\t{\n\t\tlabel: label\n\t\turl: `{opts.base}/{path}/{label}`\n\t\tstart: p\n\t\tnext: hex.next\n\t}\n}\n\n// chars[p] == \".\" (.eth の先頭) を起点に name.eth / sub.name.eth。ラベルは 3 文字以上、- で始まるものは拾わない\n@matchEnsAt(chars, p) {\n\tvar start = p\n\tloop {\n\t\tif (start <= 0) break\n\t\tif (!ENSCHARS.incl(chars[start - 1])) break\n\t\tstart -= 1\n\t}\n\tvar end = readRun(chars, p + 4, ENSCHARS).next\n\t// 文末の句点 (vitalik.eth.) は名前に含めない\n\tloop {\n\t\tif (end <= p) break\n\t\tif (chars[end - 1] != \".\") break\n\t\tend -= 1\n\t}\n\tlet label = sub(chars, start, end)\n\tlet lower = label.lower()\n\tif (!lower.ends_with(\".eth\")) return null\n\tlet name = lower.slice(0, lower.len - 4)\n\tif (name.len < 3) return null\n\tif (name.starts_with(\"-\")) return null\n\tvar ok = true\n\teach (let part, name.split(\".\")) {\n\t\tif (part == \"\") ok = false\n\t}\n\tif (!ok) return null\n\t{\n\t\tlabel: label\n\t\turl: `https://app.ens.domains/{lower}`\n\t\tstart: start\n\t\tnext: end\n\t}\n}\n\n// --- 変換対象外の判定 (候補ごとに前後を見る) ---\n\n// [ラベル](URL) の終端。成立しなければ null\n@readMdLink(chars, i) {\n\tvar j = i + 1\n\tloop {\n\t\tif (j >= chars.len) return null\n\t\tlet c = chars[j]\n\t\tif (c == \"]\") break\n\t\tif (c == Str:lf) return null\n\t\tj += 1\n\t}\n\tif (j + 1 >= chars.len || chars[j + 1] != \"(\") return null\n\tvar k = j + 2\n\tloop {\n\t\tif (k >= chars.len) return null\n\t\tlet c = chars[k]\n\t\tif (c == \")\") break\n\t\tif (c == \" \" || c == \" \" || c == Str:lf) return null\n\t\tk += 1\n\t}\n\tk + 1\n}\n\n// 空白区切りの直前部分 (候補を含むトークンの先頭から候補まで)\n@tokenBefore(before) {\n\tlet a = before.split(Str:lf)\n\tlet b = a[a.len - 1].split(\" \")\n\tlet c = b[b.len - 1].split(\" \")\n\tc[c.len - 1]\n}\n\n// start〜next の範囲をリンクにしてよいか\n@isLinkable(chars, start, next) {\n\t// 前後に英数字が隣接しているものは語の一部とみなす\n\tif (start > 0 && isWordChar(chars[start - 1])) return false\n\tif (next < chars.len && isWordChar(chars[next])) return false\n\tlet before = sub(chars, 0, start)\n\t// ` の数が奇数ならコードの中 (``` も 1 文字ずつ数えて辻褄が合う)\n\tif ((before.split(\"`\").len - 1) % 2 == 1) return false\n\t// URL / ハッシュタグを含むトークンの中\n\tlet tok = tokenBefore(before)\n\tif (tok.incl(\"https://\") || tok.incl(\"http://\")) return false\n\tif (tok.starts_with(\"#\")) return false\n\t// 同じ行の [ラベル](URL) の中\n\tlet lines = before.split(Str:lf)\n\tlet lineStart = start - lines[lines.len - 1].split().len\n\tvar q = chars.index_of(\"[\", lineStart)\n\tloop {\n\t\tif (q < 0 || q >= start) break\n\t\tlet end = readMdLink(chars, q)\n\t\tif (end != null && end > start) return false\n\t\tq = chars.index_of(\"[\", q + 1)\n\t}\n\ttrue\n}\n\n// from 以降で最も手前の候補位置 (0x の 0、.eth の .)。無ければ -1\n// str.index_of / split() はどちらもコードポイント単位 (stringz) なので添字を混ぜてよい\n@nextCandidate(text, from, opts) {\n\tvar p = -1\n\tif (opts.address || opts.tx) p = text.index_of(\"0x\", from)\n\tif (opts.ens) {\n\t\tlet q = text.index_of(\".eth\", from)\n\t\tif (q >= 0 && (p < 0 || q < p)) p = q\n\t}\n\tp\n}\n\n@convertLinks(text, opts) {\n\tlet chars = text.split()\n\tvar parts = []\n\tvar urls = []\n\tvar segStart = 0\n\tvar from = 0\n\tloop {\n\t\tif (urls.len >= MAX_LINKS) break\n\t\tlet p = nextCandidate(text, from, opts)\n\t\tif (p < 0) break\n\t\tlet hit = if (chars[p] == \"0\") matchHexAt(chars, p, opts) else matchEnsAt(chars, p)\n\t\tif (hit == null || !isLinkable(chars, hit.start, hit.next)) {\n\t\t\tfrom = p + 1\n\t\t} else {\n\t\t\tparts.push(sub(chars, segStart, hit.start))\n\t\t\tparts.push(`[{hit.label}]({hit.url})`)\n\t\t\turls.push(hit.url)\n\t\t\tsegStart = hit.next\n\t\t\tfrom = hit.next\n\t\t}\n\t}\n\tparts.push(sub(chars, segStart, chars.len))\n\t{\n\t\ttext: parts.join(\"\")\n\t\tcount: urls.len\n\t\turls: urls\n\t}\n}\n\n// 設定の URL にスキームが無ければ https:// を足し、https でなければ既定に戻し、末尾の / は落とす\n// (既定値がホストだけなのは、NoteDeck のメタデータ解析が ### 内の // 以降を落とすため)\n@explorerBase() {\n\tvar base = Plugin:config.explorerBase\n\tif (Core:type(base) != \"str\") base = DEFAULT_BASE\n\tif (base.trim() == \"\") base = DEFAULT_BASE\n\tif (base.index_of(\"://\") == -1) base = [\"https://\", base].join(\"\")\n\tif (base.slice(0, 8) != \"https://\") base = DEFAULT_BASE\n\tloop {\n\t\tif (base.len <= 8) break\n\t\tif (base.slice(base.len - 1, base.len) != \"/\") break\n\t\tbase = base.slice(0, base.len - 1)\n\t}\n\tbase\n}\n\n@currentOpts() {\n\t{\n\t\taddress: Plugin:config.address\n\t\ttx: Plugin:config.tx\n\t\tens: Plugin:config.ens\n\t\tbase: explorerBase()\n\t}\n}\n\nPlugin:register_post_form_action(\"Etherscan リンクに変換\", @(form, update) {\n\tlet text = if (form.text == null) \"\" else form.text\n\tlet result = convertLinks(text, currentOpts())\n\tif (result.count == 0) {\n\t\tMk:dialog(\"Etherscan オートリンク\", \"リンクにできるアドレス / tx / ENS 名が見つかりません\", \"info\")\n\t\treturn null\n\t}\n\tupdate(\"text\", result.text)\n\tMk:toast(`{result.count} 件をリンクにしました`, \"success\")\n})\n\nPlugin:register_note_post_interruptor(@(note) {\n\tif (Plugin:config.autoConvert && note.text != null) {\n\t\tnote.text = convertLinks(note.text, currentOpts()).text\n\t}\n\tnote\n})\n\nPlugin:register_note_action(\"Etherscan で開く\", @(note) {\n\tlet text = if (note.text == null) \"\" else note.text\n\tlet result = convertLinks(text, currentOpts())\n\tif (result.count == 0) {\n\t\tMk:toast(\"Etherscan で開けるアドレス / tx / ENS 名がありません\")\n\t\treturn null\n\t}\n\tPlugin:open_url(result.urls[0])\n})\n"} diff --git a/public/registry/plugins/etherscan-autolink/meta.json b/public/registry/plugins/etherscan-autolink/meta.json index 2bd6aa9..16bc476 100644 --- a/public/registry/plugins/etherscan-autolink/meta.json +++ b/public/registry/plugins/etherscan-autolink/meta.json @@ -1,7 +1,7 @@ { "id": "etherscan-autolink", "name": "Etherscan オートリンク", - "version": "1.0.0", + "version": "1.0.1", "author": "hitalin", "authorUrl": "https://github.com/hitalin", "description": "Ethereum のアドレス (0x + 40 桁) / トランザクションハッシュ (0x + 64 桁) / ENS 名 (name.eth) を投稿時に Etherscan と app.ens.domains へのリンクにするプラグイン。ノートメニューの「Etherscan で開く」で本文中の最初の 1 件をブラウザで開ける。エクスプローラーの URL は設定で basescan.org などに変えられる。API キー・外部通信・権限は不要", diff --git a/public/registry/plugins/etherscan-autolink/plugin.is b/public/registry/plugins/etherscan-autolink/plugin.is index 550123a..36c5b28 100644 --- a/public/registry/plugins/etherscan-autolink/plugin.is +++ b/public/registry/plugins/etherscan-autolink/plugin.is @@ -19,7 +19,7 @@ ### { name: "Etherscan オートリンク" - version: "1.0.0" + version: "1.0.1" author: "@hitalin" description: "Ethereum のアドレス / tx ハッシュ / ENS 名を投稿時に Etherscan へのリンクにします。ノートメニューから Etherscan で開くこともできます" config: { @@ -42,8 +42,8 @@ explorerBase: { type: "string" label: "エクスプローラーの URL" - description: "https://etherscan.io 以外のチェーンを使うなら https://basescan.org や https://arbiscan.io に変える" - default: "https://etherscan.io" + description: "etherscan.io 以外のチェーンを使うなら basescan.org や arbiscan.io に変える (https の URL でも可)" + default: "etherscan.io" } autoConvert: { type: "boolean" @@ -237,10 +237,13 @@ let MAX_LINKS = 100 } } -// 設定の URL が https でなければ既定に戻し、末尾の / は落とす +// 設定の URL にスキームが無ければ https:// を足し、https でなければ既定に戻し、末尾の / は落とす +// (既定値がホストだけなのは、NoteDeck のメタデータ解析が ### 内の // 以降を落とすため) @explorerBase() { var base = Plugin:config.explorerBase if (Core:type(base) != "str") base = DEFAULT_BASE + if (base.trim() == "") base = DEFAULT_BASE + if (base.index_of("://") == -1) base = ["https://", base].join("") if (base.slice(0, 8) != "https://") base = DEFAULT_BASE loop { if (base.len <= 8) break diff --git a/public/registry/plugins/web-search/api.json b/public/registry/plugins/web-search/api.json index fff6fdb..f1d1abe 100644 --- a/public/registry/plugins/web-search/api.json +++ b/public/registry/plugins/web-search/api.json @@ -1 +1 @@ -{"type":"plugin","data":"/// @ 1.2.1\n\n// Web 検索 & エゴサ — ノート本文やユーザー名を検索エンジンで開く\n//\n// 本家 Web UI プラグイン文化の古典「Google で検索」系のオマージュ。\n// - 本文を Web 検索: ノート本文をそのまま検索 (話題の出典・文脈調べ)\n// - エゴサする: ユーザーの表示名と username を OR 検索\n// 検索エンジンはプライバシー重視の brave をデフォルトに、\n// startpage (Google 結果のプロキシ) / searxng (要インスタンス URL) /\n// google / duckduckgo から選択。\n\n### {\n\tname: \"Web 検索 & エゴサ\"\n\tversion: \"1.0.0\"\n\tauthor: \"@hitalin\"\n\tdescription: \"ノートメニューから本文を Web 検索、ユーザーメニューから表示名と username でエゴサします。検索エンジンは brave / startpage / searxng / google / duckduckgo から選択\"\n\tconfig: {\n\t\tengine: {\n\t\t\ttype: \"string\"\n\t\t\tlabel: \"検索エンジン (brave / startpage / searxng / google / duckduckgo)\"\n\t\t\tdefault: \"brave\"\n\t\t}\n\t\tsearxngUrl: {\n\t\t\ttype: \"string\"\n\t\t\tlabel: \"SearXNG インスタンス URL (engine が searxng のとき必須。例: https://searx.example.com)\"\n\t\t\tdefault: \"\"\n\t\t}\n\t}\n}\n\n// 検索クエリとして長すぎる本文は先頭のみ使う\nlet maxQueryLen = 150\n\n@searchUrl(query) {\n\tlet enc = Uri:encode_component(query)\n\tlet engine = Plugin:config.engine\n\tif (engine == \"google\") return `https://www.google.com/search?q={enc}`\n\tif (engine == \"startpage\") return `https://www.startpage.com/sp/search?query={enc}`\n\tif (engine == \"duckduckgo\") return `https://duckduckgo.com/?q={enc}`\n\tif (engine == \"searxng\") {\n\t\tvar base = Plugin:config.searxngUrl\n\t\tif (base == null || base == \"\") {\n\t\t\tMk:toast(\"SearXNG インスタンス URL を設定してください\", \"error\")\n\t\t\treturn null\n\t\t}\n\t\tif (base.slice(base.len - 1, base.len) == \"/\") {\n\t\t\tbase = base.slice(0, base.len - 1)\n\t\t}\n\t\treturn `{base}/search?q={enc}`\n\t}\n\t`https://search.brave.com/search?q={enc}`\n}\n\nPlugin:register_note_action(\"本文を Web 検索\", @(note) {\n\tif (note.text == null || note.text == \"\") {\n\t\tMk:toast(\"本文がありません\", \"error\")\n\t\treturn null\n\t}\n\tvar query = note.text\n\tif (query.len > maxQueryLen) {\n\t\tquery = query.slice(0, maxQueryLen)\n\t}\n\tlet url = searchUrl(query)\n\tif (url != null) Plugin:open_url(url)\n})\n\nPlugin:register_user_action(\"エゴサする\", @(user) {\n\tvar query = `\"{user.username}\"`\n\tif (user.name != null && user.name != \"\" && user.name != user.username) {\n\t\tquery = `\"{user.name}\" OR \"{user.username}\"`\n\t}\n\tlet url = searchUrl(query)\n\tif (url != null) Plugin:open_url(url)\n})\n"} +{"type":"plugin","data":"/// @ 1.2.1\n\n// Web 検索 & エゴサ — ノート本文やユーザー名を検索エンジンで開く\n//\n// 本家 Web UI プラグイン文化の古典「Google で検索」系のオマージュ。\n// - 本文を Web 検索: ノート本文をそのまま検索 (話題の出典・文脈調べ)\n// - エゴサする: ユーザーの表示名と username を OR 検索\n// 検索エンジンはプライバシー重視の brave をデフォルトに、\n// startpage (Google 結果のプロキシ) / searxng (要インスタンス URL) /\n// google / duckduckgo から選択。\n\n### {\n\tname: \"Web 検索 & エゴサ\"\n\tversion: \"1.0.1\"\n\tauthor: \"@hitalin\"\n\tdescription: \"ノートメニューから本文を Web 検索、ユーザーメニューから表示名と username でエゴサします。検索エンジンは brave / startpage / searxng / google / duckduckgo から選択\"\n\tconfig: {\n\t\tengine: {\n\t\t\ttype: \"string\"\n\t\t\tlabel: \"検索エンジン (brave / startpage / searxng / google / duckduckgo)\"\n\t\t\tdefault: \"brave\"\n\t\t}\n\t\tsearxngUrl: {\n\t\t\ttype: \"string\"\n\t\t\tlabel: \"SearXNG インスタンス URL (engine が searxng のとき必須。例: searx.example.com)\"\n\t\t\tdefault: \"\"\n\t\t}\n\t}\n}\n\n// 検索クエリとして長すぎる本文は先頭のみ使う\nlet maxQueryLen = 150\n\n@searchUrl(query) {\n\tlet enc = Uri:encode_component(query)\n\tlet engine = Plugin:config.engine\n\tif (engine == \"google\") return `https://www.google.com/search?q={enc}`\n\tif (engine == \"startpage\") return `https://www.startpage.com/sp/search?query={enc}`\n\tif (engine == \"duckduckgo\") return `https://duckduckgo.com/?q={enc}`\n\tif (engine == \"searxng\") {\n\t\tvar base = Plugin:config.searxngUrl\n\t\tif (base == null || base == \"\") {\n\t\t\tMk:toast(\"SearXNG インスタンス URL を設定してください\", \"error\")\n\t\t\treturn null\n\t\t}\n\t\tif (base.index_of(\"://\") == -1) base = [\"https://\", base].join(\"\")\n\t\tif (base.slice(base.len - 1, base.len) == \"/\") {\n\t\t\tbase = base.slice(0, base.len - 1)\n\t\t}\n\t\treturn `{base}/search?q={enc}`\n\t}\n\t`https://search.brave.com/search?q={enc}`\n}\n\nPlugin:register_note_action(\"本文を Web 検索\", @(note) {\n\tif (note.text == null || note.text == \"\") {\n\t\tMk:toast(\"本文がありません\", \"error\")\n\t\treturn null\n\t}\n\tvar query = note.text\n\tif (query.len > maxQueryLen) {\n\t\tquery = query.slice(0, maxQueryLen)\n\t}\n\tlet url = searchUrl(query)\n\tif (url != null) Plugin:open_url(url)\n})\n\nPlugin:register_user_action(\"エゴサする\", @(user) {\n\tvar query = `\"{user.username}\"`\n\tif (user.name != null && user.name != \"\" && user.name != user.username) {\n\t\tquery = `\"{user.name}\" OR \"{user.username}\"`\n\t}\n\tlet url = searchUrl(query)\n\tif (url != null) Plugin:open_url(url)\n})\n"} diff --git a/public/registry/plugins/web-search/meta.json b/public/registry/plugins/web-search/meta.json index 90acd81..6b5a082 100644 --- a/public/registry/plugins/web-search/meta.json +++ b/public/registry/plugins/web-search/meta.json @@ -1,7 +1,7 @@ { "id": "web-search", "name": "Web 検索 & エゴサ", - "version": "1.0.0", + "version": "1.0.1", "author": "hitalin", "authorUrl": "https://github.com/hitalin", "description": "ノート本文を Web 検索、ユーザーの表示名と username でエゴサ。検索エンジンは brave / startpage / searxng / google / duckduckgo から選択", diff --git a/public/registry/plugins/web-search/plugin.is b/public/registry/plugins/web-search/plugin.is index 226a8bd..0346811 100644 --- a/public/registry/plugins/web-search/plugin.is +++ b/public/registry/plugins/web-search/plugin.is @@ -11,7 +11,7 @@ ### { name: "Web 検索 & エゴサ" - version: "1.0.0" + version: "1.0.1" author: "@hitalin" description: "ノートメニューから本文を Web 検索、ユーザーメニューから表示名と username でエゴサします。検索エンジンは brave / startpage / searxng / google / duckduckgo から選択" config: { @@ -22,7 +22,7 @@ } searxngUrl: { type: "string" - label: "SearXNG インスタンス URL (engine が searxng のとき必須。例: https://searx.example.com)" + label: "SearXNG インスタンス URL (engine が searxng のとき必須。例: searx.example.com)" default: "" } } @@ -43,6 +43,7 @@ let maxQueryLen = 150 Mk:toast("SearXNG インスタンス URL を設定してください", "error") return null } + if (base.index_of("://") == -1) base = ["https://", base].join("") if (base.slice(base.len - 1, base.len) == "/") { base = base.slice(0, base.len - 1) } diff --git a/public/registry/queries.json b/public/registry/queries.json index a2ce64c..59949ff 100644 --- a/public/registry/queries.json +++ b/public/registry/queries.json @@ -1,6 +1,6 @@ { "version": 1, - "updatedAt": "2026-11-04T00:13:26.700Z", + "updatedAt": "2026-11-04T00:27:05.105Z", "queries": [ { "id": "bot-only", diff --git a/public/registry/skills.json b/public/registry/skills.json index 0f6ec7b..b2dfbb3 100644 --- a/public/registry/skills.json +++ b/public/registry/skills.json @@ -1,6 +1,6 @@ { "version": 1, - "updatedAt": "2026-11-04T00:13:26.700Z", + "updatedAt": "2026-11-04T00:27:05.105Z", "skills": [ { "id": "aiscript-surgeon", diff --git a/public/registry/themes.json b/public/registry/themes.json index 7e7668d..b50102a 100644 --- a/public/registry/themes.json +++ b/public/registry/themes.json @@ -1,6 +1,6 @@ { "version": 1, - "updatedAt": "2026-11-04T00:13:26.700Z", + "updatedAt": "2026-11-04T00:27:05.105Z", "themes": [ { "id": "adwaita-dark", diff --git a/public/registry/widgets.json b/public/registry/widgets.json index 7fd8a88..ac6b6d6 100644 --- a/public/registry/widgets.json +++ b/public/registry/widgets.json @@ -1,6 +1,6 @@ { "version": 1, - "updatedAt": "2026-11-04T00:13:26.700Z", + "updatedAt": "2026-11-04T00:27:05.105Z", "widgets": [ { "id": "aiscript-health",