Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions public/registry/index.json
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
{
"version": 1,
"updatedAt": "2026-08-14T16:53:54.431Z",
"updatedAt": "2026-08-14T18:32:52.260Z",
"plugins": {
"count": 27,
"updatedAt": "2026-08-14T16:53:54.431Z"
"updatedAt": "2026-08-14T18:32:52.260Z"
},
"themes": {
"count": 54,
"updatedAt": "2026-08-14T16:53:54.431Z"
"updatedAt": "2026-08-14T18:32:52.260Z"
},
"widgets": {
"count": 45,
"updatedAt": "2026-08-14T16:53:54.431Z"
"count": 47,
"updatedAt": "2026-08-14T18:32:52.260Z"
},
"skills": {
"count": 32,
"updatedAt": "2026-08-14T16:53:54.431Z"
"updatedAt": "2026-08-14T18:32:52.260Z"
},
"queries": {
"count": 24,
"updatedAt": "2026-08-14T16:53:54.431Z"
"updatedAt": "2026-08-14T18:32:52.260Z"
}
}
4 changes: 2 additions & 2 deletions public/registry/plugins.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"version": 1,
"updatedAt": "2026-08-14T16:53:54.431Z",
"updatedAt": "2026-08-14T18:32:52.260Z",
"plugins": [
{
"id": "ai-actions",
Expand All @@ -22,7 +22,7 @@
"apiUrl": "https://store.notedeck.io/registry/plugins/ai-actions/api.json",
"sha512": "5c9a60976f798c3adbee963451daab12bbdfff538c877206b525c8590d6694129d6eb43991730b71d9f4221e2545822d2d6dcb5983d514af0bde3159bc96082f",
"createdAt": "2026-08-13T03:19:12+09:00",
"updatedAt": "2026-08-15T01:45:34+09:00",
"updatedAt": "2026-08-15T01:54:09+09:00",
"authorUrl": "https://github.com/notedeck-dev",
"license": "MIT",
"permissions": [
Expand Down
2 changes: 1 addition & 1 deletion public/registry/queries.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"version": 1,
"updatedAt": "2026-08-14T16:53:54.431Z",
"updatedAt": "2026-08-14T18:32:52.260Z",
"queries": [
{
"id": "no-cw",
Expand Down
2 changes: 1 addition & 1 deletion public/registry/skills.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"version": 1,
"updatedAt": "2026-08-14T16:53:54.431Z",
"updatedAt": "2026-08-14T18:32:52.260Z",
"skills": [
{
"id": "ai-cost-pulse",
Expand Down
2 changes: 1 addition & 1 deletion public/registry/themes.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"version": 1,
"updatedAt": "2026-08-14T16:53:54.431Z",
"updatedAt": "2026-08-14T18:32:52.260Z",
"themes": [
{
"id": "adwaita-dark",
Expand Down
60 changes: 59 additions & 1 deletion public/registry/widgets.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"version": 1,
"updatedAt": "2026-08-14T16:53:54.431Z",
"updatedAt": "2026-08-14T18:32:52.260Z",
"widgets": [
{
"id": "atcoder",
Expand Down Expand Up @@ -340,6 +340,35 @@
"license": "MIT",
"iconUrl": "https://store.notedeck.io/registry/widgets/misskey-online/icon.svg"
},
{
"id": "nodeinfo",
"name": "nodeinfo 調査",
"version": "1.0.0",
"author": "@hitalin",
"description": "任意の Fediverse サーバーの nodeinfo を引いて、ソフトウェア・バージョン・規模・登録受付状態を表示。Misskey / Mastodon / Pleroma など ActivityPub 実装に共通で通り、認証不要",
"icon": "ti-scan",
"autoRun": true,
"category": "stats",
"capabilities": [
"notedeck-api"
],
"tags": [
"fediverse",
"nodeinfo",
"server",
"stats",
"misskey",
"mastodon"
],
"sourceUrl": "https://store.notedeck.io/registry/widgets/nodeinfo/widget.is",
"apiUrl": "https://store.notedeck.io/registry/widgets/nodeinfo/api.json",
"sha512": "107ca7daa16937e18fab8abf7838ad83885fbb19046b2b0150260fb68799e1230ea93d89b41e64fa0dab815cd1eaca0e92099765df9cedbfd35830a6470738f3",
"createdAt": "2026-08-15T03:32:33+09:00",
"updatedAt": "2026-08-15T03:32:33+09:00",
"authorUrl": "https://github.com/hitalin",
"license": "MIT",
"iconUrl": "https://store.notedeck.io/registry/widgets/nodeinfo/icon.svg"
},
{
"id": "okj-prices",
"name": "OKJ 円建て価格ボード",
Expand Down Expand Up @@ -369,6 +398,35 @@
"license": "MIT",
"iconUrl": "https://store.notedeck.io/registry/widgets/okj-prices/icon.svg"
},
{
"id": "osv-vulns",
"name": "OSV 脆弱性",
"version": "1.0.0",
"author": "@hitalin",
"description": "OSV.dev にパッケージ名を投げて、そのパッケージの既知脆弱性を重大度・修正バージョン付きで新しい順に表示。既定は npm:misskey-js。npm / PyPI / crates.io など 8 エコシステム対応、API キー不要",
"icon": "ti-package-search",
"autoRun": true,
"category": "display",
"capabilities": [
"notedeck-api"
],
"tags": [
"security",
"vulnerability",
"osv",
"cve",
"supply-chain",
"misskey"
],
"sourceUrl": "https://store.notedeck.io/registry/widgets/osv-vulns/widget.is",
"apiUrl": "https://store.notedeck.io/registry/widgets/osv-vulns/api.json",
"sha512": "3144f60a44effc996ad98616e5af4360cb18fc9acf2288ff6c81ad7ea54d3fdbbb9da1c0bffb1bd67d64c4452697d4996a1a30d2852364afb80f7e7d4c00a39b",
"createdAt": "2026-08-14T18:32:54.089Z",
"updatedAt": "2026-08-14T18:32:54.089Z",
"authorUrl": "https://github.com/hitalin",
"license": "MIT",
"iconUrl": "https://store.notedeck.io/registry/widgets/osv-vulns/icon.svg"
},
{
"id": "rss-reader",
"name": "RSSリーダー",
Expand Down
1 change: 1 addition & 0 deletions public/registry/widgets/nodeinfo/api.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"type":"widget","data":"/// @ 1.2.1\n/// nodeinfo ウィジェット\n/// 任意の Fediverse サーバーの nodeinfo を引いて、ソフトウェア・規模・登録受付状態を表示。\n/// 認証不要・レスポンス数 KB で、Misskey に限らず Mastodon / Pleroma / Akkoma などにも通る。\n///\n/// 実装メモ:\n/// - nodeinfo は 2 段構え。/.well-known/nodeinfo で本体 URL を discovery してから取得する。\n/// 本体パスは実装ごとに違う (Pleroma は /nodeinfo/2.1.json) ので、discovery は省略できない\n/// - 2.1 を優先し、無ければ 2.0。Mastodon は 2.0 のみを出す\n/// - discovery が返す href は**相手サーバーが自由に決められる値**なので、\n/// 「調査対象ホスト自身の https URL」以外は捨てる (別ドメインへ取りに行かせないため)\n/// - nodeName も相手の自由入力。MFM / リンク / HTML 記法は safeLabel で全角に潰す。\n/// nodeDescription は HTML 混じりで無害化のコストが高いので最初から表示しない\n/// - 内容はほぼ静的なので自動更新は 6 時間おき\n\nlet WELL_KNOWN_REL_20 = \"http://nodeinfo.diaspora.software/ns/schema/2.0\"\nlet WELL_KNOWN_REL_21 = \"http://nodeinfo.diaspora.software/ns/schema/2.1\"\nlet HOST_KEY = \"nodeinfo:host\"\nlet DEFAULT_HOST = \"misskey.io\"\nlet HOST_CHARS = \"abcdefghijklmnopqrstuvwxyz0123456789.-\"\n// nodeinfo は大きくても数 KB。相手が返す量を無制限にパースしない\nlet MAX_BODY = 200000\n\n// カラーパレット (ink-new-servers と同系統)\nlet COL_OPEN = \"86b300\" // 登録開放\nlet COL_DOWN = \"f74e52\" // エラー\nlet COL_SUB = \"6b7480\" // 補助・招待制\n\n// ─────────────────────────────\n// ヘルパー\n// ─────────────────────────────\n@dflt(v, d) { if (Core:type(v) == \"null\") d else v }\n\n@get(o, k, d) {\n if (Core:type(o) != \"obj\") { d }\n elif (Obj:has(o, k)) { dflt(o[k], d) }\n else { d }\n}\n\n/// レスポンス本文を JSON オブジェクトとして読む。\n/// Json:parse は失敗時に throw せず error 値を返すので、obj 以外はすべて null に潰す\n@parseObj(body) {\n if (Core:type(body) != \"str\") { return null }\n if (body.len > MAX_BODY) { return null }\n let v = Json:parse(body)\n if (Core:type(v) == \"obj\") v else null\n}\n\n/// 数値を日本語の桁表記に (731141 -> 73.1万, 172951329 -> 1.7億)\n@fmtNum(n) {\n if (Core:type(n) != \"num\") { \"?\" }\n elif (n >= 100000000) { `{Math:round(n / 10000000) / 10}億` }\n elif (n >= 10000) { `{Math:round(n / 1000) / 10}万` }\n else { `{Math:round(n)}` }\n}\n\n/// 相手サーバー由来の文字列を表示用に無害化する。\n/// MFM ($[...])・リンク ([...])・装飾 (*)・HTML (<>) を全角に置換して発火させない\n@safeLabel(s) {\n if (Core:type(s) != \"str\") { \"?\" }\n else {\n s.replace(\"$\", \"$\").replace(\"[\", \"[\").replace(\"]\", \"]\").replace(\"*\", \"*\").replace(\"<\", \"<\").replace(\">\", \">\")\n }\n}\n\n@trunc(s, n) {\n if (s.len > n) `{s.slice(0, n)}…` else s\n}\n\n/// ホスト名として妥当か。ドットを含む小文字英数 + ハイフンのみ許す\n/// (ポート付き・IDN・localhost は弾く)\n@validHost(h) {\n if (!h.incl(\".\")) { return false }\n if (h.starts_with(\".\") || h.starts_with(\"-\")) { return false }\n if (h.ends_with(\".\") || h.ends_with(\"-\")) { return false }\n var ok = true\n each (let c, h.to_char_arr()) {\n if (!HOST_CHARS.incl(c)) { ok = false }\n }\n ok\n}\n\n/// 入力欄の値からホスト名を取り出す。URL 貼り付け・@user@host・大文字を許容。\n/// 妥当でなければ null\n@normalizeHost(s) {\n if (Core:type(s) != \"str\") { return null }\n var h = s.trim().lower()\n if (h.starts_with(\"https://\")) { h = h.slice(8, h.len) }\n elif (h.starts_with(\"http://\")) { h = h.slice(7, h.len) }\n // @user@host 形式は最後の @ より後ろを取る\n let parts = h.split(\"@\")\n h = parts[parts.len - 1]\n let slash = h.index_of(\"/\")\n if (slash >= 0) { h = h.slice(0, slash) }\n if (h == \"\") { return null }\n if (!validHost(h)) { return null }\n h\n}\n\n/// href が調査対象ホスト自身の https URL か\n@ownHttpsUrl(href, host) {\n if (Core:type(href) != \"str\") { return false }\n href.lower().starts_with(`https://{host}/`)\n}\n\n/// discovery の links から本体 URL を選ぶ (2.1 優先、自ホストの https のみ)\n@pickNodeinfoUrl(links, host) {\n if (Core:type(links) != \"arr\") { return null }\n var url20 = null\n var url21 = null\n each (let l, links) {\n let href = get(l, \"href\", null)\n if (ownHttpsUrl(href, host)) {\n let rel = get(l, \"rel\", \"\")\n if (rel == WELL_KNOWN_REL_21) { url21 = href }\n elif (rel == WELL_KNOWN_REL_20) { url20 = href }\n }\n }\n if (Core:type(url21) == \"str\") url21 else url20\n}\n\n/// ソフトウェア名ごとの色 (Misskey 系は緑、Mastodon は紫、Pleroma 系は橙)\n@softwareColor(name) {\n if (name == \"misskey\" || name == \"sharkey\" || name == \"cherrypick\") { COL_OPEN }\n elif (name == \"firefish\" || name == \"iceshrimp\") { COL_OPEN }\n elif (name == \"mastodon\" || name == \"hometown\") { \"6364ff\" }\n elif (name == \"pleroma\" || name == \"akkoma\") { \"fba457\" }\n else { COL_SUB }\n}\n\n@softwareName(ni) {\n let name = get(get(ni, \"software\", null), \"name\", null)\n if (Core:type(name) != \"str\") \"\" else name.lower()\n}\n\n/// \"misskey v2025.4.1\" 形式のバッジ本文\n@softwareLabel(ni) {\n let sw = get(ni, \"software\", null)\n let name = get(sw, \"name\", null)\n if (Core:type(name) != \"str\") { return \"不明\" }\n let ver = get(sw, \"version\", null)\n if (Core:type(ver) != \"str\") { safeLabel(trunc(name, 24)) }\n else { `{safeLabel(trunc(name, 24))} v{safeLabel(trunc(ver, 32))}` }\n}\n\n/// 登録受付状態 (色付き)。emailRequiredForSignup は Misskey 系のみ持つ\n@regLabel(ni) {\n if (get(ni, \"openRegistrations\", false) != true) {\n `$[fg.color={COL_SUB} 招待制]`\n } elif (get(get(ni, \"metadata\", null), \"emailRequiredForSignup\", false) == true) {\n `$[fg.color={COL_OPEN} 登録開放] $[fg.color={COL_SUB} ✉要メール]`\n } else {\n `$[fg.color={COL_OPEN} 登録開放]`\n }\n}\n\n/// 表示行 (MFM 文字列) を組み立てる。常に 5 行\n@infoRows(ni, host) {\n let name = trunc(safeLabel(dflt(get(get(ni, \"metadata\", null), \"nodeName\", null), host)), 28)\n let users = get(get(get(ni, \"usage\", null), \"users\", null), \"total\", null)\n let posts = get(get(ni, \"usage\", null), \"localPosts\", null)\n let active = get(get(get(ni, \"usage\", null), \"users\", null), \"activeMonth\", null)\n var stats = `👥 **{fmtNum(users)}** 📝 **{fmtNum(posts)}**`\n if (Core:type(active) == \"num\") {\n stats = `{stats} 🔥 **{fmtNum(active)}**`\n }\n let protocols = get(ni, \"protocols\", null)\n var footer = `nodeinfo {dflt(get(ni, \"version\", null), \"?\")}`\n if (Core:type(protocols) == \"arr\" && protocols.len > 0) {\n footer = `{footer} ・ {safeLabel(trunc(protocols.join(\", \"), 40))}`\n }\n [\n `<center>🌐 [**{name}**](https://{host})</center>`\n `<center>$[bg.color={softwareColor(softwareName(ni))} $[fg.color=ffffff {softwareLabel(ni)} ]]</center>`\n `<center>{stats}</center>`\n `<center><small>{regLabel(ni)}</small></center>`\n `<center><small>$[fg.color={COL_SUB} {footer}]</small></center>`\n ]\n}\n\n// ---- ここから UI 層 (ハーネス検証はこの行より上の純粋関数のみ) ----\n\n// 保存値も信用せず normalizeHost に通す (壊れた値が URL 組み立てに乗らないように)\nvar host = dflt(normalizeHost(Mk:load(HOST_KEY)), DEFAULT_HOST)\nvar input = host\nvar firstRun = true\nvar errorMsg = null\n\n/// nodeinfo を取得する。失敗時は null を返し errorMsg に理由を入れる\n@fetchNodeinfo(h) {\n errorMsg = null\n let disc = Nd:http(`https://{h}/.well-known/nodeinfo`, {\n method: \"GET\"\n headers: { \"Accept\": \"application/json\" }\n })\n if (disc.status != 200) {\n errorMsg = `nodeinfo が見つかりません (HTTP {disc.status})`\n return null\n }\n let url = pickNodeinfoUrl(get(parseObj(disc.body), \"links\", null), h)\n if (Core:type(url) != \"str\") {\n errorMsg = \"対応する nodeinfo (2.0 / 2.1) のリンクがありません\"\n return null\n }\n let res = Nd:http(url, {\n method: \"GET\"\n headers: { \"Accept\": \"application/json\" }\n })\n if (res.status != 200) {\n errorMsg = `nodeinfo を取得できません (HTTP {res.status})`\n return null\n }\n let ni = parseObj(res.body)\n if (Core:type(ni) != \"obj\") {\n errorMsg = \"nodeinfo の応答を読めませんでした\"\n }\n ni\n}\n\n@configFolder() {\n Ui:C:folder({\n title: \"⚙️ 調べるサーバー\"\n opened: false\n children: [\n Ui:C:textInput({\n default: input\n label: \"ホスト\"\n text: \"misskey.io / https://mastodon.social など\"\n onInput: @(v) { input = v }\n })\n Ui:C:button({\n text: \"🔍 調べる\"\n rounded: true\n primary: true\n onClick: @() {\n let h = normalizeHost(input)\n if (Core:type(h) != \"str\") {\n Mk:dialog(\"エラー\", \"ホスト名が不正です。misskey.io のような形式で入力してください\", \"error\")\n } else {\n // 保存は取得成功後 (render 内)。名前が引けないホストで Nd:http が\n // 落ちた場合に、その値が次回起動時にも読み込まれて詰むのを避ける\n host = h\n input = h\n firstRun = true\n render()\n }\n }\n })\n ]\n })\n}\n\n@render() {\n if (firstRun) {\n Ui:render([\n Ui:C:container({\n align: \"center\"\n children: [Ui:C:text({ text: `{host} を調べています… 🌐` })]\n })\n ])\n }\n firstRun = false\n\n let ni = fetchNodeinfo(host)\n if (Core:type(ni) != \"obj\") {\n Ui:render([\n Ui:C:container({\n align: \"center\"\n children: [\n Ui:C:mfm({ text: `<center>$[fg.color={COL_SUB} {safeLabel(host)}]</center>` })\n Ui:C:mfm({ text: `<center>$[fg.color={COL_DOWN} ⚠️ {dflt(errorMsg, \"取得に失敗しました\")}]</center>` })\n Ui:C:button({ text: \"🔄 再試行\", rounded: true, onClick: @() { render() } })\n configFolder()\n ]\n })\n ])\n return null\n }\n\n Mk:save(HOST_KEY, host)\n\n var body = []\n each (let r, infoRows(ni, host)) {\n body.push(Ui:C:mfm({ text: r }))\n }\n body.push(Ui:C:button({ text: \"🔄 更新\", rounded: true, onClick: @() { render() } }))\n body.push(configFolder())\n Ui:render([\n Ui:C:container({\n align: \"center\"\n children: body\n })\n ])\n}\n\nrender()\n// nodeinfo の内容はほぼ静的なので 6 時間おきで十分\nAsync:interval(21600000, render, false)\n"}
19 changes: 19 additions & 0 deletions public/registry/widgets/nodeinfo/icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 14 additions & 0 deletions public/registry/widgets/nodeinfo/meta.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"id": "nodeinfo",
"name": "nodeinfo 調査",
"version": "1.0.0",
"author": "@hitalin",
"authorUrl": "https://github.com/hitalin",
"description": "任意の Fediverse サーバーの nodeinfo を引いて、ソフトウェア・バージョン・規模・登録受付状態を表示。Misskey / Mastodon / Pleroma など ActivityPub 実装に共通で通り、認証不要",
"icon": "ti-scan",
"autoRun": true,
"category": "stats",
"capabilities": ["notedeck-api"],
"license": "MIT",
"tags": ["fediverse", "nodeinfo", "server", "stats", "misskey", "mastodon"]
}
Loading
Loading