diff --git a/public/registry/index.json b/public/registry/index.json index ca44d0c..bb9d6d4 100644 --- a/public/registry/index.json +++ b/public/registry/index.json @@ -1,24 +1,24 @@ { "version": 1, - "updatedAt": "2026-10-15T11:17:46.820Z", + "updatedAt": "2026-10-17T12:32:32.717Z", "plugins": { "count": 41, - "updatedAt": "2026-10-15T11:17:46.820Z" + "updatedAt": "2026-10-17T12:32:32.717Z" }, "themes": { "count": 77, - "updatedAt": "2026-10-15T11:17:46.820Z" + "updatedAt": "2026-10-17T12:32:32.717Z" }, "widgets": { "count": 64, - "updatedAt": "2026-10-15T11:17:46.820Z" + "updatedAt": "2026-10-17T12:32:32.717Z" }, "skills": { "count": 44, - "updatedAt": "2026-10-15T11:17:46.820Z" + "updatedAt": "2026-10-17T12:32:32.717Z" }, "queries": { "count": 35, - "updatedAt": "2026-10-15T11:17:46.820Z" + "updatedAt": "2026-10-17T12:32:32.717Z" } } diff --git a/public/registry/plugins.json b/public/registry/plugins.json index ddbe100..d1f0bd0 100644 --- a/public/registry/plugins.json +++ b/public/registry/plugins.json @@ -1,6 +1,6 @@ { "version": 1, - "updatedAt": "2026-10-15T11:17:46.820Z", + "updatedAt": "2026-10-17T12:32:32.717Z", "plugins": [ { "id": "ai-actions", @@ -714,7 +714,7 @@ "apiUrl": "https://store.notedeck.io/registry/plugins/smart-delete/api.json", "sha512": "bba0644c86ac0c45dbaf9349e029a698c7344e5a4c86f3bf10eb857b5455ece86c0d6b5eb1f0b7dfd38d3ab1175b71d10935fb756617838aa5673bfa65374504", "createdAt": "2026-07-20T09:16:14+09:00", - "updatedAt": "2026-10-15T20:13:44+09:00", + "updatedAt": "2026-09-25T18:20:03+09:00", "authorUrl": "https://github.com/hitalin", "license": "MIT", "locales": { @@ -840,7 +840,7 @@ "apiUrl": "https://store.notedeck.io/registry/plugins/dominator/api.json", "sha512": "cc9af17a82161008c7bb8a9f41e80f8de27e84e26c93839e95f5d465112dfe5ddcac598f6f88cd20f00fc79a77d41f5af77ef4a91289ed72dec0a1f9fbfa0f35", "createdAt": "2026-07-14T13:43:33+09:00", - "updatedAt": "2026-10-15T20:13:44+09:00", + "updatedAt": "2026-09-25T18:20:03+09:00", "authorUrl": "https://github.com/hitalin", "license": "MIT", "locales": { @@ -1045,7 +1045,7 @@ "apiUrl": "https://store.notedeck.io/registry/plugins/misskey-translate/api.json", "sha512": "99162928e722cb4abbb6512c3c1b8c20dd3f46a84af56fe9c0bdde135fb8da7de01205409083108f58f99dc5a887a8e2b7f64a15681468d73419b0069378834a", "createdAt": "2026-10-12T17:16:47+09:00", - "updatedAt": "2026-10-15T20:13:44+09:00", + "updatedAt": "2026-10-12T17:16:47+09:00", "authorUrl": "https://github.com/hitalin", "license": "MIT", "locales": { @@ -1102,7 +1102,7 @@ "apiUrl": "https://store.notedeck.io/registry/plugins/pakuru/api.json", "sha512": "1075e1d86a32040c67800dd0d9732b1fa27750b3f7a13b0815a65433a745e2d635aef6d793e68ba69dee5bb1cc0e66e301a1a9f37b895a93904bd8f10b3faa8d", "createdAt": "2026-07-13T22:03:54+09:00", - "updatedAt": "2026-10-15T20:13:44+09:00", + "updatedAt": "2026-09-25T18:20:03+09:00", "authorUrl": "https://github.com/hitalin", "license": "MIT", "locales": { @@ -1158,7 +1158,7 @@ "apiUrl": "https://store.notedeck.io/registry/plugins/block-checker/api.json", "sha512": "e4fe2a6260892a7eba7439aeb716d2d914e9dd84dfcfe7d1c91aca8646ce86608deccc65353d43bd1e3cb2b519d61b6b98eade937dc328f022595ed302191a36", "createdAt": "2026-07-16T07:08:20+09:00", - "updatedAt": "2026-10-15T20:13:44+09:00", + "updatedAt": "2026-09-25T18:20:03+09:00", "authorUrl": "https://github.com/hitalin", "license": "MIT", "locales": { @@ -1210,7 +1210,7 @@ "apiUrl": "https://store.notedeck.io/registry/plugins/block-combo/api.json", "sha512": "2af88eca65cd26993f5326d8f1c279d0b5bc2eff083be24114b6d345a594e894f6c405d6fbff6dbd0930c7c8bcc21272354adff15de9107daaf12a28bf946c39", "createdAt": "2026-07-17T07:17:06+09:00", - "updatedAt": "2026-10-15T20:13:44+09:00", + "updatedAt": "2026-09-25T18:20:03+09:00", "authorUrl": "https://github.com/hitalin", "license": "MIT", "locales": { @@ -1272,7 +1272,7 @@ "apiUrl": "https://store.notedeck.io/registry/plugins/reaction-piggyback/api.json", "sha512": "f237acfb9f0532b64caecfdd1817691810064d8a844e613ac36269e38577a1a64d71903261aa0952dbfcb284c27e64dff224356629a7cfdff612324c5a8a507c", "createdAt": "2026-08-12T13:30:08+09:00", - "updatedAt": "2026-10-15T20:13:44+09:00", + "updatedAt": "2026-09-25T18:20:03+09:00", "authorUrl": "https://github.com/hitalin", "license": "MIT", "locales": { @@ -1541,7 +1541,7 @@ "apiUrl": "https://store.notedeck.io/registry/plugins/same-emoji-react/api.json", "sha512": "5c6deb32aaf324f5a0699f99f88a6d481b96f71e0e1b77932f13f60b264faf0b4b923e657caa78328c25042c3b0e8d464428d097bb6a316bf1d301f9cdae567e", "createdAt": "2026-08-12T13:21:58+09:00", - "updatedAt": "2026-10-15T20:13:44+09:00", + "updatedAt": "2026-09-25T18:20:03+09:00", "authorUrl": "https://github.com/hitalin", "license": "MIT", "locales": { @@ -1728,9 +1728,9 @@ { "id": "reading-list", "name": "後で読む", - "version": "1.0.0", + "version": "1.1.0", "author": "hitalin", - "description": "ノートメニューから「後で読む」クリップにワンクリックで追加 (Pocket 的な後で読む)。クリップは名前 (既定「後で読む」、設定で変更可) で探し、無ければ自動で作成するので ID 設定は不要。ウィジェット「後で読む」(reading-list-widget) と対で使うと、溜めたノートを一覧して「読んだ」で消化できる", + "description": "ノートメニュー、またはコマンド (フォーカス中のノート / クリップボードのノート URL) から「後で読む」クリップにワンクリックで追加 (Pocket 的な後で読む)。クリップは名前 (既定「後で読む」、設定で変更可) で探し、無ければ自動で作成するので ID 設定は不要。コマンドはキーバインド設定の「プラグイン」節でキーを割り当てると 1 キーで溜められ、クリップボード取り込みを OS グローバルショートカットにすれば NoteDeck が背面でも動く (1.85.2+)。ウィジェット「後で読む」(reading-list-widget) と対で使うと、溜めたノートを一覧して「読んだ」で消化できる", "category": "utility", "capabilities": [ "notedeck-api" @@ -1741,24 +1741,30 @@ "bookmark", "pocket", "note-action", - "reading-list" + "reading-list", + "command", + "hotkey", + "clipboard" ], "sourceUrl": "https://store.notedeck.io/registry/plugins/reading-list/plugin.is", "apiUrl": "https://store.notedeck.io/registry/plugins/reading-list/api.json", - "sha512": "680e1e1990fc44dcab4d8d8e7b4013b3f9a4bc4fc07c949c80b2e71d7ec6196be0c20186121d3a2c9730308132f281b87ef974cf8a57344e2cb8924318e2ca65", + "sha512": "7db69c9d0bbf1bf9715d0e2c46ba41d51a6b4805d666e5bd1df035b1ec1ee8e3307848e31855b874a17b1ca660e175ed6c4aa3f8547f96f6c98a84983ee23c2d", "createdAt": "2026-10-12T19:09:59+09:00", - "updatedAt": "2026-10-12T19:09:59+09:00", + "updatedAt": "2026-10-17T21:30:28+09:00", "authorUrl": "https://github.com/hitalin", "license": "MIT", "locales": { "en": { "name": "Read Later", - "description": "Adds a note to your \"Read Later\" clip in one click from the note menu (Pocket-style read-it-later). The clip is looked up by name (default 「後で読む」, changeable in settings) and created automatically if missing, so no clip ID setup is needed. Pair it with the \"Read Later\" widget (reading-list-widget) to list what you saved and clear items with \"Read\"." + "description": "Adds a note to your \"Read Later\" clip in one click (Pocket-style read-it-later) from the note menu or from two commands: the focused note, or a note URL in your clipboard. The clip is looked up by name (default 「後で読む」, changeable in settings) and created automatically if missing, so no clip ID setup is needed. Bind the commands to keys in the Keybinds settings (Plugins section) to save with a single key; bind the clipboard command to an OS global shortcut and it works even while NoteDeck is in the background (1.85.2+). Pair it with the \"Read Later\" widget (reading-list-widget) to list what you saved and clear items with \"Read\"." } }, "permissions": [ "clips.read", - "clips.write" + "clips.write", + "notes.read", + "clipboard", + "account.read" ], "iconUrl": "https://store.notedeck.io/registry/plugins/reading-list/icon.svg", "contract": { @@ -1767,16 +1773,25 @@ ], "profile": "safe", "permissions": [ + "account.read", + "clipboard", "clips.read", - "clips.write" + "clips.write", + "notes.read" ], "ndCalls": [ + "account.current", + "clipboard.read", "clips.addNote", "clips.create", - "clips.list" + "clips.list", + "column.focusedNote" + ], + "mkApi": [ + "ap/show" ], - "mkApi": [], "hooks": [ + "command", "note_action" ], "external": false, @@ -1982,42 +1997,53 @@ { "id": "night-post-guard", "name": "深夜投稿ガード", - "version": "1.0.0", + "version": "1.1.0", "author": "hitalin", - "description": "設定した深夜帯 (既定 1 時〜5 時) に投稿すると公開範囲をパブリックからホームに自動で下げ、任意で CW も付けるうっかり防止プラグイン。投稿ガードの時間版", + "description": "設定した深夜帯 (既定 1 時〜5 時) に投稿すると公開範囲をパブリックからホームに自動で下げ、任意で CW も付けるうっかり防止プラグイン。投稿ガードの時間版。NoteDeck では投稿フォームのメニューに「朝 N:00 に予約投稿」が加わり、本文を次の朝の予約投稿に回せる (Misskey 2025.10+ の予約投稿機能を使う)", "category": "posting", - "capabilities": [], + "capabilities": [ + "notedeck-api" + ], "tags": [ "safety", "visibility", "cw", - "night" + "night", + "schedule", + "draft" ], "sourceUrl": "https://store.notedeck.io/registry/plugins/night-post-guard/plugin.is", "apiUrl": "https://store.notedeck.io/registry/plugins/night-post-guard/api.json", - "sha512": "be879412deb438ed6093faa120e21f5e73e8f9365f17133d830cc6b94d3b933de92f0395aff49af4a474eff7b2d0ea44d126365b17f3d838f4a2158e01964821", + "sha512": "ec15a77e873e42a465661223cbd2ac0d75da77a1fc8f43df18c96b7f6922231be3fcc179764247504e73ba1db43ea1a496bf31d572d94f47c19b3fe925e9b72b", "createdAt": "2026-10-12T17:59:14+09:00", - "updatedAt": "2026-10-12T17:59:14+09:00", + "updatedAt": "2026-10-17T21:30:28+09:00", "authorUrl": "https://github.com/hitalin", "license": "MIT", "locales": { "en": { "name": "Night Post Guard", - "description": "Prevents late-night accidents: posts made during the hours you set (1:00-5:00 by default) automatically have their visibility lowered from Public to Home, optionally with a CW added. The time-based counterpart of Post Guard." + "description": "Prevents late-night accidents: posts made during the hours you set (1:00-5:00 by default) automatically have their visibility lowered from Public to Home, optionally with a CW added. The time-based counterpart of Post Guard. On NoteDeck the post form menu also gets \"Schedule for N:00 in the morning\", which turns the draft into a scheduled post for the next morning (uses the scheduled-post feature of Misskey 2025.10+)." } }, + "permissions": [ + "drafts.write" + ], "iconUrl": "https://store.notedeck.io/registry/plugins/night-post-guard/icon.svg", "contract": { "runsOn": [ - "notedeck", - "misskey-web" + "notedeck" + ], + "profile": "safe", + "permissions": [ + "drafts.write" + ], + "ndCalls": [ + "drafts.create" ], - "profile": "readonly", - "permissions": [], - "ndCalls": [], "mkApi": [], "hooks": [ - "note_post_interruptor" + "note_post_interruptor", + "post_form_action" ], "external": false, "unresolved": [], @@ -2098,7 +2124,7 @@ "apiUrl": "https://store.notedeck.io/registry/plugins/federation-checker/api.json", "sha512": "66791625edc52f7bd27cf4b95654216e7643c525e156f5f550f5b92cb29084dca45213b511b16616879334d245f9194906ac0a6a22c81048e8a440bf512eb323", "createdAt": "2026-07-16T07:49:09+09:00", - "updatedAt": "2026-10-15T20:13:44+09:00", + "updatedAt": "2026-09-25T18:20:03+09:00", "authorUrl": "https://github.com/hitalin", "license": "MIT", "locales": { diff --git a/public/registry/plugins/night-post-guard/api.json b/public/registry/plugins/night-post-guard/api.json index 6494702..8b86057 100644 --- a/public/registry/plugins/night-post-guard/api.json +++ b/public/registry/plugins/night-post-guard/api.json @@ -1 +1 @@ -{"type":"plugin","data":"/// @ 1.2.1\n\n// 深夜投稿ガード — 深夜帯の投稿をホームに下げる\n//\n// 投稿ガード (post-guard) の時間版。note_post_interruptor は同期実行のため\n// 確認ダイアログは挟めない。そのため「確認して止める」ではなく「自動で保護する」\n// 方式: 設定した深夜帯に投稿すると、公開範囲がパブリックならホームに下げ、\n// CW ラベルが設定されていれば CW も付ける (既に CW があれば上書きしない)。\n//\n// 深夜帯は「開始時 <= 時刻 < 終了時」。開始 > 終了なら日付をまたぐ\n// (既定の 1〜5 は 1:00 から 4:59 まで、22〜6 なら 22:00 から 5:59 まで)。\n// 開始と終了が同じ値なら無効 (何もしない)。時刻は端末のローカル時刻\n// (Date:hour(Date:now()))。\n//\n// 同じフックは本家 Misskey Web にもあるので NoteDeck 専用ではない。\n\n### {\n\tname: \"深夜投稿ガード\"\n\tversion: \"1.0.0\"\n\tauthor: \"@hitalin\"\n\tdescription: \"設定した深夜帯に投稿すると公開範囲をパブリックからホームに自動で下げ、任意で CW も付けるうっかり防止プラグイン\"\n\tconfig: {\n\t\tstartHour: {\n\t\t\ttype: \"number\"\n\t\t\tlabel: \"深夜帯の開始時 (0-23)\"\n\t\t\tdefault: 1\n\t\t}\n\t\tendHour: {\n\t\t\ttype: \"number\"\n\t\t\tlabel: \"深夜帯の終了時 (0-23、この時刻になったら解除)\"\n\t\t\tdescription: \"開始時より小さい値なら日付をまたぎます。開始時と同じ値なら無効\"\n\t\t\tdefault: 5\n\t\t}\n\t\taddCw: {\n\t\t\ttype: \"string\"\n\t\t\tlabel: \"深夜帯の投稿に付ける CW (空なら付けない)\"\n\t\t\tdefault: \"\"\n\t\t}\n\t}\n}\n\n@hourOf(value, fallback) {\n\tif (Core:type(value) != \"num\") return fallback\n\tif (value < 0 || value > 23) return fallback\n\tvalue\n}\n\nlet startHour = hourOf(Plugin:config.startHour, 1)\nlet endHour = hourOf(Plugin:config.endHour, 5)\nlet cwLabel = if (Core:type(Plugin:config.addCw) == \"str\") Plugin:config.addCw.trim() else \"\"\n\n@isNight(hour) {\n\tif (startHour == endHour) return false\n\tif (startHour < endHour) {\n\t\thour >= startHour && hour < endHour\n\t} else {\n\t\thour >= startHour || hour < endHour\n\t}\n}\n\nPlugin:register_note_post_interruptor(@(note) {\n\tif (!isNight(Date:hour(Date:now()))) return note\n\tif (note.visibility == \"public\") note.visibility = \"home\"\n\tif (cwLabel != \"\" && note.cw == null) note.cw = cwLabel\n\tnote\n})\n"} +{"type":"plugin","data":"/// @ 1.2.1\n\n// 深夜投稿ガード — 深夜帯の投稿をホームに下げる / 朝に予約投稿する\n//\n// 投稿ガード (post-guard) の時間版。note_post_interruptor は同期実行のため\n// 確認ダイアログは挟めない。そのため「確認して止める」ではなく「自動で保護する」\n// 方式: 設定した深夜帯に投稿すると、公開範囲がパブリックならホームに下げ、\n// CW ラベルが設定されていれば CW も付ける (既に CW があれば上書きしない)。\n//\n// 深夜帯は「開始時 <= 時刻 < 終了時」。開始 > 終了なら日付をまたぐ\n// (既定の 1〜5 は 1:00 から 4:59 まで、22〜6 なら 22:00 から 5:59 まで)。\n// 開始と終了が同じ値なら無効 (何もしない)。時刻は端末のローカル時刻\n// (Date:hour(Date:now()))。\n//\n// 「朝に予約投稿」(NoteDeck 1.85.0+、サーバーは Misskey 2025.10+ の予約投稿が必要):\n// 投稿フォームのメニューに「朝 8:00 に予約投稿」を足す。本文と CW を drafts.create に\n// scheduledAt (次に来る morningHour:00、端末のローカル時刻) + isActuallyScheduled で\n// 渡し、確認ダイアログを通ったらフォームを空にする。深夜帯かどうかは問わない\n// (いつでも使える)。公開範囲はフォームから取れない (post_form_action に渡るのは\n// text と cw だけ) ので設定 scheduleVisibility に従う。\n// 予約投稿に対応しないサーバーでは drafts.create が throw してそこで止まる\n// (AiScript に try/catch は無い)。\n//\n// 深夜帯のガード自体は本家 Misskey Web にも同じフックがあるので NoteDeck 専用ではない。\n// 使う NoteDeck 権限 (meta.json に記載): drafts.write … drafts.create\n\n### {\n\tname: \"深夜投稿ガード\"\n\tversion: \"1.1.0\"\n\tauthor: \"@hitalin\"\n\tdescription: \"設定した深夜帯に投稿すると公開範囲をパブリックからホームに自動で下げ、任意で CW も付けるうっかり防止プラグイン。NoteDeck では投稿フォームのメニューから「朝に予約投稿」もできます\"\n\tconfig: {\n\t\tstartHour: {\n\t\t\ttype: \"number\"\n\t\t\tlabel: \"深夜帯の開始時 (0-23)\"\n\t\t\tdefault: 1\n\t\t}\n\t\tendHour: {\n\t\t\ttype: \"number\"\n\t\t\tlabel: \"深夜帯の終了時 (0-23、この時刻になったら解除)\"\n\t\t\tdescription: \"開始時より小さい値なら日付をまたぎます。開始時と同じ値なら無効\"\n\t\t\tdefault: 5\n\t\t}\n\t\taddCw: {\n\t\t\ttype: \"string\"\n\t\t\tlabel: \"深夜帯の投稿に付ける CW (空なら付けない)\"\n\t\t\tdefault: \"\"\n\t\t}\n\t\tmorningHour: {\n\t\t\ttype: \"number\"\n\t\t\tlabel: \"予約投稿する朝の時刻 (0-23、NoteDeck のみ)\"\n\t\t\tdescription: \"投稿フォームのメニュー「朝 N:00 に予約投稿」の N。次に来るその時刻に投稿されます\"\n\t\t\tdefault: 8\n\t\t}\n\t\tscheduleVisibility: {\n\t\t\ttype: \"string\"\n\t\t\tlabel: \"予約投稿の公開範囲 (public / home / followers)\"\n\t\t\tdefault: \"public\"\n\t\t}\n\t}\n}\n\nlet title = \"深夜投稿ガード\"\n\n@hourOf(value, fallback) {\n\tif (Core:type(value) != \"num\") return fallback\n\tif (value < 0 || value > 23) return fallback\n\tvalue\n}\n\nlet startHour = hourOf(Plugin:config.startHour, 1)\nlet endHour = hourOf(Plugin:config.endHour, 5)\nlet cwLabel = if (Core:type(Plugin:config.addCw) == \"str\") Plugin:config.addCw.trim() else \"\"\nlet morningHour = hourOf(Plugin:config.morningHour, 8)\nlet scheduleVisibility = if ([\"public\", \"home\", \"followers\"].incl(Plugin:config.scheduleVisibility)) Plugin:config.scheduleVisibility else \"public\"\n\n@isNight(hour) {\n\tif (startHour == endHour) return false\n\tif (startHour < endHour) {\n\t\thour >= startHour && hour < endHour\n\t} else {\n\t\thour >= startHour || hour < endHour\n\t}\n}\n\nPlugin:register_note_post_interruptor(@(note) {\n\tif (!isNight(Date:hour(Date:now()))) return note\n\tif (note.visibility == \"public\") note.visibility = \"home\"\n\tif (cwLabel != \"\" && note.cw == null) note.cw = cwLabel\n\tnote\n})\n\n// now (ms) から見て次に来る morningHour:00 (端末のローカル時刻) を ISO 8601 で返す。\n// Date:parse はゾーン無しの日時をローカル時刻として読み、Date:to_iso_str は\n// ローカルのオフセット付きで書く\n@nextMorningIso(now) {\n\tlet y = Date:year(now)\n\tlet mm = `{Date:month(now)}`.pad_start(2, \"0\")\n\tlet dd = `{Date:day(now)}`.pad_start(2, \"0\")\n\tlet hh = `{morningHour}`.pad_start(2, \"0\")\n\tvar at = Date:parse(`{y}-{mm}-{dd}T{hh}:00:00`)\n\tif (at <= now) at = at + 86400000\n\tDate:to_iso_str(at)\n}\n\nif (exists NOTEDECK) {\n\tPlugin:register_post_form_action(`朝 {morningHour}:00 に予約投稿`, @(form, update) {\n\t\tlet text = if (Core:type(form.text) == \"str\") form.text else \"\"\n\t\tif (text.trim() == \"\") {\n\t\t\tMk:dialog(title, \"本文が空です\", \"info\")\n\t\t\treturn null\n\t\t}\n\t\tlet params = { text: text, visibility: scheduleVisibility, scheduledAt: nextMorningIso(Date:now()), isActuallyScheduled: true }\n\t\tif (Core:type(form.cw) == \"str\" && form.cw != \"\") params.cw = form.cw\n\t\tlet created = Nd:call(\"drafts.create\", params)\n\t\t// 確認ダイアログでキャンセルされた\n\t\tif (Core:type(created) == \"error\") return null\n\t\tupdate(\"text\", \"\")\n\t\tupdate(\"cw\", null)\n\t\tMk:toast(`{morningHour}:00 に投稿を予約しました`)\n\t\tnull\n\t})\n}\n"} diff --git a/public/registry/plugins/night-post-guard/locales/en.json b/public/registry/plugins/night-post-guard/locales/en.json index 9f500a4..68a775e 100644 --- a/public/registry/plugins/night-post-guard/locales/en.json +++ b/public/registry/plugins/night-post-guard/locales/en.json @@ -1,5 +1,5 @@ { - "sourceHash": "ee608aad4d0d", + "sourceHash": "51cd7159c5e2", "name": "Night Post Guard", - "description": "Prevents late-night accidents: posts made during the hours you set (1:00-5:00 by default) automatically have their visibility lowered from Public to Home, optionally with a CW added. The time-based counterpart of Post Guard." + "description": "Prevents late-night accidents: posts made during the hours you set (1:00-5:00 by default) automatically have their visibility lowered from Public to Home, optionally with a CW added. The time-based counterpart of Post Guard. On NoteDeck the post form menu also gets \"Schedule for N:00 in the morning\", which turns the draft into a scheduled post for the next morning (uses the scheduled-post feature of Misskey 2025.10+)." } diff --git a/public/registry/plugins/night-post-guard/meta.json b/public/registry/plugins/night-post-guard/meta.json index 87d56f9..1f6a79f 100644 --- a/public/registry/plugins/night-post-guard/meta.json +++ b/public/registry/plugins/night-post-guard/meta.json @@ -1,14 +1,25 @@ { "id": "night-post-guard", "name": "深夜投稿ガード", - "version": "1.0.0", + "version": "1.1.0", "author": "hitalin", "authorUrl": "https://github.com/hitalin", - "description": "設定した深夜帯 (既定 1 時〜5 時) に投稿すると公開範囲をパブリックからホームに自動で下げ、任意で CW も付けるうっかり防止プラグイン。投稿ガードの時間版", + "description": "設定した深夜帯 (既定 1 時〜5 時) に投稿すると公開範囲をパブリックからホームに自動で下げ、任意で CW も付けるうっかり防止プラグイン。投稿ガードの時間版。NoteDeck では投稿フォームのメニューに「朝 N:00 に予約投稿」が加わり、本文を次の朝の予約投稿に回せる (Misskey 2025.10+ の予約投稿機能を使う)", "license": "MIT", "category": "posting", - "capabilities": [], - "tags": ["safety", "visibility", "cw", "night"], - "permissions": [], - "minNoteDeckVersion": "1.8.0" + "capabilities": [ + "notedeck-api" + ], + "tags": [ + "safety", + "visibility", + "cw", + "night", + "schedule", + "draft" + ], + "permissions": [ + "drafts.write" + ], + "minNoteDeckVersion": "1.85.0" } diff --git a/public/registry/plugins/night-post-guard/plugin.is b/public/registry/plugins/night-post-guard/plugin.is index 2ed977c..2583608 100644 --- a/public/registry/plugins/night-post-guard/plugin.is +++ b/public/registry/plugins/night-post-guard/plugin.is @@ -1,6 +1,6 @@ /// @ 1.2.1 -// 深夜投稿ガード — 深夜帯の投稿をホームに下げる +// 深夜投稿ガード — 深夜帯の投稿をホームに下げる / 朝に予約投稿する // // 投稿ガード (post-guard) の時間版。note_post_interruptor は同期実行のため // 確認ダイアログは挟めない。そのため「確認して止める」ではなく「自動で保護する」 @@ -12,13 +12,23 @@ // 開始と終了が同じ値なら無効 (何もしない)。時刻は端末のローカル時刻 // (Date:hour(Date:now()))。 // -// 同じフックは本家 Misskey Web にもあるので NoteDeck 専用ではない。 +// 「朝に予約投稿」(NoteDeck 1.85.0+、サーバーは Misskey 2025.10+ の予約投稿が必要): +// 投稿フォームのメニューに「朝 8:00 に予約投稿」を足す。本文と CW を drafts.create に +// scheduledAt (次に来る morningHour:00、端末のローカル時刻) + isActuallyScheduled で +// 渡し、確認ダイアログを通ったらフォームを空にする。深夜帯かどうかは問わない +// (いつでも使える)。公開範囲はフォームから取れない (post_form_action に渡るのは +// text と cw だけ) ので設定 scheduleVisibility に従う。 +// 予約投稿に対応しないサーバーでは drafts.create が throw してそこで止まる +// (AiScript に try/catch は無い)。 +// +// 深夜帯のガード自体は本家 Misskey Web にも同じフックがあるので NoteDeck 専用ではない。 +// 使う NoteDeck 権限 (meta.json に記載): drafts.write … drafts.create ### { name: "深夜投稿ガード" - version: "1.0.0" + version: "1.1.0" author: "@hitalin" - description: "設定した深夜帯に投稿すると公開範囲をパブリックからホームに自動で下げ、任意で CW も付けるうっかり防止プラグイン" + description: "設定した深夜帯に投稿すると公開範囲をパブリックからホームに自動で下げ、任意で CW も付けるうっかり防止プラグイン。NoteDeck では投稿フォームのメニューから「朝に予約投稿」もできます" config: { startHour: { type: "number" @@ -36,9 +46,22 @@ label: "深夜帯の投稿に付ける CW (空なら付けない)" default: "" } + morningHour: { + type: "number" + label: "予約投稿する朝の時刻 (0-23、NoteDeck のみ)" + description: "投稿フォームのメニュー「朝 N:00 に予約投稿」の N。次に来るその時刻に投稿されます" + default: 8 + } + scheduleVisibility: { + type: "string" + label: "予約投稿の公開範囲 (public / home / followers)" + default: "public" + } } } +let title = "深夜投稿ガード" + @hourOf(value, fallback) { if (Core:type(value) != "num") return fallback if (value < 0 || value > 23) return fallback @@ -48,6 +71,8 @@ let startHour = hourOf(Plugin:config.startHour, 1) let endHour = hourOf(Plugin:config.endHour, 5) let cwLabel = if (Core:type(Plugin:config.addCw) == "str") Plugin:config.addCw.trim() else "" +let morningHour = hourOf(Plugin:config.morningHour, 8) +let scheduleVisibility = if (["public", "home", "followers"].incl(Plugin:config.scheduleVisibility)) Plugin:config.scheduleVisibility else "public" @isNight(hour) { if (startHour == endHour) return false @@ -64,3 +89,35 @@ Plugin:register_note_post_interruptor(@(note) { if (cwLabel != "" && note.cw == null) note.cw = cwLabel note }) + +// now (ms) から見て次に来る morningHour:00 (端末のローカル時刻) を ISO 8601 で返す。 +// Date:parse はゾーン無しの日時をローカル時刻として読み、Date:to_iso_str は +// ローカルのオフセット付きで書く +@nextMorningIso(now) { + let y = Date:year(now) + let mm = `{Date:month(now)}`.pad_start(2, "0") + let dd = `{Date:day(now)}`.pad_start(2, "0") + let hh = `{morningHour}`.pad_start(2, "0") + var at = Date:parse(`{y}-{mm}-{dd}T{hh}:00:00`) + if (at <= now) at = at + 86400000 + Date:to_iso_str(at) +} + +if (exists NOTEDECK) { + Plugin:register_post_form_action(`朝 {morningHour}:00 に予約投稿`, @(form, update) { + let text = if (Core:type(form.text) == "str") form.text else "" + if (text.trim() == "") { + Mk:dialog(title, "本文が空です", "info") + return null + } + let params = { text: text, visibility: scheduleVisibility, scheduledAt: nextMorningIso(Date:now()), isActuallyScheduled: true } + if (Core:type(form.cw) == "str" && form.cw != "") params.cw = form.cw + let created = Nd:call("drafts.create", params) + // 確認ダイアログでキャンセルされた + if (Core:type(created) == "error") return null + update("text", "") + update("cw", null) + Mk:toast(`{morningHour}:00 に投稿を予約しました`) + null + }) +} diff --git a/public/registry/plugins/reading-list/api.json b/public/registry/plugins/reading-list/api.json index 5e27cb3..fdf9984 100644 --- a/public/registry/plugins/reading-list/api.json +++ b/public/registry/plugins/reading-list/api.json @@ -1 +1 @@ -{"type":"plugin","data":"/// @ 1.2.1\n\n// 後で読む — ノートメニューから「後で読む」クリップへ 1 クリックで送る\n//\n// Pocket 的な「後で読む」ワークフローの入口。ウィジェット「後で読む」(reading-list-widget)\n// と同じ名前のクリップ (既定「後で読む」、設定 clipName で変更可) を共有し、\n// プラグインで溜めてウィジェットで消化する。\n//\n// クリップ id はサーバー / アカウントごとに違うので名前で解決する:\n// 1. Mk:save に覚えた { name, id } がアカウント・名前とも一致すれば clips.list を省く\n// 2. 無ければ clips.list から名前一致を探す\n// 3. それも無ければ clips.create で非公開クリップを作る (確認ダイアログあり)\n// 続けて clips.addNote (確認ダイアログあり) → トースト。\n//\n// Nd:call はキャンセル時だけ error 値 (user_cancelled) を返し、それ以外の失敗\n// (クリップが削除済み等) は throw して handler がそこで止まる。AiScript に try/catch は\n// 無いので、addNote を呼ぶ「前」に覚えた id を捨て、戻ってきたら書き戻す。\n// throw した回は書き戻しが走らないため、次回は clips.list から解決し直す。\n//\n// quick-clip との違い: クリップ id を設定しなくてよく、無ければ自動で作る。\n// 消化 (読んだ → クリップから外す) はウィジェット側が担当する。\n//\n// NoteDeck 専用。使う NoteDeck 権限 (meta.json に記載):\n// - clips.read … clips.list (名前からクリップを探す)\n// - clips.write … clips.create (無ければ作成) / clips.addNote (追加)\n\n### {\n\tname: \"後で読む\"\n\tversion: \"1.0.0\"\n\tauthor: \"@hitalin\"\n\tdescription: \"ノートメニューから「後で読む」クリップに追加します。クリップが無ければ自動で作成。ウィジェット「後で読む」と対で使うと一覧・既読化ができます (NoteDeck 専用)\"\n\tconfig: {\n\t\tclipName: {\n\t\t\ttype: \"string\"\n\t\t\tlabel: \"クリップ名\"\n\t\t\tdescription: \"ウィジェット「後で読む」のクリップ名と同じにしてください\"\n\t\t\tdefault: \"後で読む\"\n\t\t}\n\t}\n}\n\nlet title = \"後で読む\"\nlet defaultName = \"後で読む\"\n// Mk:save の鍵。値は { \"\": { name, id } | null }\nlet storeKey = \"clips\"\n\n@clipName() {\n\tlet n = Plugin:config.clipName.trim()\n\tif (n == \"\") defaultName else n\n}\n\n// NoteDeck のノートには取得アカウントが _accountId で付く。無ければ単一鍵に倒す\n@accountKey(note) {\n\tif (Obj:has(note, \"_accountId\") && Core:type(note._accountId) == \"str\") note._accountId else \"default\"\n}\n\n// (`else {}` は空ブロックに解釈されるので、空オブジェクトは let で作る)\n@loadStore() {\n\tlet v = Mk:load(storeKey)\n\tif (Core:type(v) == \"obj\") return v\n\tlet empty = {}\n\tempty\n}\n\n@remember(key, entry) {\n\tlet store = loadStore()\n\tstore[key] = entry\n\tMk:save(storeKey, store)\n}\n\n// 覚えている id (名前が一致するときだけ)。無ければ null\n@rememberedId(key, name) {\n\tlet store = loadStore()\n\tif (!Obj:has(store, key)) return null\n\tlet e = store[key]\n\tif (Core:type(e) != \"obj\" || e.name != name || Core:type(e.id) != \"str\") return null\n\te.id\n}\n\n// clips.list で名前一致を探す。見つからなければ null\n@findClip(name) {\n\tlet clips = Nd:call(\"clips.list\", {})\n\tif (Core:type(clips) != \"arr\") return null\n\teach (let c, clips) {\n\t\tif (Core:type(c) == \"obj\" && c.name == name && Core:type(c.id) == \"str\") return c.id\n\t}\n\tnull\n}\n\n// 探して無ければ作る。作成をキャンセルされたら null\n@resolveClip(name) {\n\tlet found = findClip(name)\n\tif (found != null) return found\n\tlet created = Nd:call(\"clips.create\", { name: name, description: \"reading-list プラグインが作成\", isPublic: false })\n\tif (Core:type(created) == \"error\") return null\n\tif (Core:type(created) != \"obj\" || Core:type(created.id) != \"str\") {\n\t\tMk:dialog(title, \"クリップを作成できませんでした\", \"error\")\n\t\treturn null\n\t}\n\tcreated.id\n}\n\nPlugin:register_note_action(\"後で読む\", @(note) {\n\tif (!(exists NOTEDECK)) {\n\t\tMk:dialog(title, \"このプラグインは NoteDeck 専用です。Misskey Web では動作しません\", \"info\")\n\t\treturn null\n\t}\n\tlet name = clipName()\n\tlet key = accountKey(note)\n\tvar clipId = rememberedId(key, name)\n\tif (clipId == null) {\n\t\tclipId = resolveClip(name)\n\t\t// 作成の確認ダイアログでキャンセルされた\n\t\tif (clipId == null) return null\n\t}\n\t// addNote が throw した回 (クリップ削除済み等) は書き戻しが走らず、次回は解決し直す\n\tremember(key, null)\n\tlet added = Nd:call(\"clips.addNote\", { clipId: clipId, noteId: note.id })\n\tremember(key, { name: name, id: clipId })\n\t// 追加の確認ダイアログでキャンセルされた\n\tif (Core:type(added) == \"error\") return null\n\tMk:toast(`「{name}」に追加しました`, \"success\")\n\tnull\n})\n\nif (!(exists NOTEDECK)) {\n\tMk:toast(\"後で読むは NoteDeck 専用です (Misskey Web では動作しません)\")\n}\n"} +{"type":"plugin","data":"/// @ 1.2.1\n\n// 後で読む — ノートメニュー / キー 1 つで「後で読む」クリップへ送る\n//\n// Pocket 的な「後で読む」ワークフローの入口。ウィジェット「後で読む」(reading-list-widget)\n// と同じ名前のクリップ (既定「後で読む」、設定 clipName で変更可) を共有し、\n// プラグインで溜めてウィジェットで消化する。\n//\n// 入口は 3 つ:\n// - ノートメニュー「後で読む」(本家 Web 互換のフック。実行は NoteDeck 専用)\n// - コマンド「後で読む: フォーカス中のノートを追加」\n// column.focusedNote で今フォーカスしているノートを取る。キーバインド設定の\n// 「プラグイン」節でキーを割り当てると、カラムで j/k しながら 1 キーで溜められる\n// - コマンド「後で読む: クリップボードのノート URL を追加」\n// clipboard.read のテキストが https:///notes/ なら Mk:api(\"ap/show\")\n// で (リモートのノートでも) 自分のサーバー上のノートに解決して追加する。\n// OS グローバルショートカットに割り当てると、ブラウザで URL をコピー → キー\n// 1 つで NoteDeck に溜まる (NoteDeck が背面でも動く)\n// コマンドへのキーバインドは NoteDeck 1.85.2 から届く (notedeck#1243)。\n//\n// クリップ id はサーバー / アカウントごとに違うので名前で解決する:\n// 1. Mk:save に覚えた { name, id } がアカウント・名前とも一致すれば clips.list を省く\n// 2. 無ければ clips.list から名前一致を探す\n// 3. それも無ければ clips.create で非公開クリップを作る (確認ダイアログあり)\n// 続けて clips.addNote (確認ダイアログあり) → トースト。\n//\n// アカウントは、ノートメニューなら note._accountId、フォーカス中なら\n// column.focusedNote の accountId を clips.* に明示する。クリップボードからは\n// 呼び出し文脈 (active) のアカウントで、記憶の鍵だけ account.current から引く。\n//\n// Nd:call はキャンセル時だけ error 値 (user_cancelled) を返し、それ以外の失敗\n// (クリップが削除済み等) は throw して handler がそこで止まる。AiScript に try/catch は\n// 無いので、addNote を呼ぶ「前」に覚えた id を捨て、戻ってきたら書き戻す。\n// throw した回は書き戻しが走らないため、次回は clips.list から解決し直す。\n// NoteDeck の Mk:api も失敗時は throw する (ap/show が解決できない URL など)。\n//\n// quick-clip との違い: クリップ id を設定しなくてよく、無ければ自動で作る。\n// 消化 (読んだ → クリップから外す) はウィジェット側が担当する。\n//\n// NoteDeck 専用。使う NoteDeck 権限 (meta.json に記載):\n// - clips.read … clips.list (名前からクリップを探す)\n// - clips.write … clips.create (無ければ作成) / clips.addNote (追加)\n// - notes.read … column.focusedNote (フォーカス中のノート)\n// - clipboard … clipboard.read (ノート URL の取り込み)\n// - account.read … Mk:api(\"ap/show\") (URL をノートに解決) / account.current (記憶の鍵)\n\n### {\n\tname: \"後で読む\"\n\tversion: \"1.1.0\"\n\tauthor: \"@hitalin\"\n\tdescription: \"ノートメニュー、またはコマンド (フォーカス中のノート / クリップボードのノート URL) から「後で読む」クリップに追加します。クリップが無ければ自動で作成。コマンドにキーを割り当てると 1 キーで溜められます。ウィジェット「後で読む」と対で使うと一覧・既読化ができます (NoteDeck 専用)\"\n\tconfig: {\n\t\tclipName: {\n\t\t\ttype: \"string\"\n\t\t\tlabel: \"クリップ名\"\n\t\t\tdescription: \"ウィジェット「後で読む」のクリップ名と同じにしてください\"\n\t\t\tdefault: \"後で読む\"\n\t\t}\n\t}\n}\n\nlet title = \"後で読む\"\nlet defaultName = \"後で読む\"\n// Mk:save の鍵。値は { \"\": { name, id } | null }\nlet storeKey = \"clips\"\n\n@clipName() {\n\tlet n = Plugin:config.clipName.trim()\n\tif (n == \"\") defaultName else n\n}\n\n// accountId が分かっているときだけ params に足す (null なら呼び出し文脈のアカウント)\n@withAccount(params, accountId) {\n\tif (accountId != null) params.accountId = accountId\n\tparams\n}\n\n// 記憶の鍵。accountId が無ければ account.current から引き、それも無ければ単一鍵に倒す\n@storeKeyOf(accountId) {\n\tif (accountId != null) return accountId\n\tlet cur = Nd:call(\"account.current\", {})\n\tif (Core:type(cur) == \"obj\" && Core:type(cur.id) == \"str\") cur.id else \"default\"\n}\n\n// (`else {}` は空ブロックに解釈されるので、空オブジェクトは let で作る)\n@loadStore() {\n\tlet v = Mk:load(storeKey)\n\tif (Core:type(v) == \"obj\") return v\n\tlet empty = {}\n\tempty\n}\n\n@remember(key, entry) {\n\tlet store = loadStore()\n\tstore[key] = entry\n\tMk:save(storeKey, store)\n}\n\n// 覚えている id (名前が一致するときだけ)。無ければ null\n@rememberedId(key, name) {\n\tlet store = loadStore()\n\tif (!Obj:has(store, key)) return null\n\tlet e = store[key]\n\tif (Core:type(e) != \"obj\" || e.name != name || Core:type(e.id) != \"str\") return null\n\te.id\n}\n\n// clips.list で名前一致を探す。見つからなければ null\n@findClip(name, accountId) {\n\tlet clips = Nd:call(\"clips.list\", withAccount({}, accountId))\n\tif (Core:type(clips) != \"arr\") return null\n\teach (let c, clips) {\n\t\tif (Core:type(c) == \"obj\" && c.name == name && Core:type(c.id) == \"str\") return c.id\n\t}\n\tnull\n}\n\n// 探して無ければ作る。作成をキャンセルされたら null\n@resolveClip(name, accountId) {\n\tlet found = findClip(name, accountId)\n\tif (found != null) return found\n\tlet created = Nd:call(\"clips.create\", withAccount({ name: name, description: \"reading-list プラグインが作成\", isPublic: false }, accountId))\n\tif (Core:type(created) == \"error\") return null\n\tif (Core:type(created) != \"obj\" || Core:type(created.id) != \"str\") {\n\t\tMk:dialog(title, \"クリップを作成できませんでした\", \"error\")\n\t\treturn null\n\t}\n\tcreated.id\n}\n\n// noteId を「後で読む」クリップへ追加する。accountId が null なら呼び出し文脈のアカウント\n@addToList(noteId, accountId) {\n\tlet name = clipName()\n\tlet key = storeKeyOf(accountId)\n\tvar clipId = rememberedId(key, name)\n\tif (clipId == null) {\n\t\tclipId = resolveClip(name, accountId)\n\t\t// 作成の確認ダイアログでキャンセルされた\n\t\tif (clipId == null) return null\n\t}\n\t// addNote が throw した回 (クリップ削除済み等) は書き戻しが走らず、次回は解決し直す\n\tremember(key, null)\n\tlet added = Nd:call(\"clips.addNote\", withAccount({ clipId: clipId, noteId: noteId }, accountId))\n\tremember(key, { name: name, id: clipId })\n\t// 追加の確認ダイアログでキャンセルされた\n\tif (Core:type(added) == \"error\") return null\n\tMk:toast(`「{name}」に追加しました`, \"success\")\n\tnull\n}\n\n// クリップボードのテキストが https:///notes/ なら、クエリや断片を落とした\n// ノート URL を返す。違えば null\n@noteUrlOf(text) {\n\tlet t = text.trim()\n\tif (!t.starts_with(\"https://\")) return null\n\tlet parts = t.split(\"/notes/\")\n\tif (parts.len != 2) return null\n\tlet host = parts[0].slice(8, parts[0].len)\n\tif (host == \"\" || host.incl(\"/\")) return null\n\tvar id = parts[1]\n\teach (let sep, [\"/\", \"?\", \"#\"]) {\n\t\tlet i = id.index_of(sep)\n\t\tif (i >= 0) id = id.slice(0, i)\n\t}\n\tif (id == \"\") return null\n\t`https://{host}/notes/{id}`\n}\n\nPlugin:register_note_action(\"後で読む\", @(note) {\n\tif (!(exists NOTEDECK)) {\n\t\tMk:dialog(title, \"このプラグインは NoteDeck 専用です。Misskey Web では動作しません\", \"info\")\n\t\treturn null\n\t}\n\t// NoteDeck のノートには取得アカウントが _accountId で付く\n\tlet accountId = if (Obj:has(note, \"_accountId\") && Core:type(note._accountId) == \"str\") note._accountId else null\n\taddToList(note.id, accountId)\n})\n\nif (exists NOTEDECK) {\n\tNd:register_command(\"add-focused\", \"後で読む: フォーカス中のノートを追加\", \"ti-bookmark\", @() {\n\t\tlet focused = Nd:call(\"column.focusedNote\", {})\n\t\tif (Core:type(focused) != \"obj\" || focused.note == null || Core:type(focused.noteId) != \"str\") {\n\t\t\tMk:toast(\"追加するノートをカラムでフォーカスしてください\")\n\t\t\treturn null\n\t\t}\n\t\tlet accountId = if (Core:type(focused.accountId) == \"str\") focused.accountId else null\n\t\taddToList(focused.noteId, accountId)\n\t})\n\n\tNd:register_command(\"add-clipboard\", \"後で読む: クリップボードのノート URL を追加\", \"ti-clipboard-text\", @() {\n\t\tlet clip = Nd:call(\"clipboard.read\", {})\n\t\tlet text = if (Core:type(clip) == \"obj\" && Core:type(clip.text) == \"str\") clip.text else \"\"\n\t\tlet uri = noteUrlOf(text)\n\t\tif (uri == null) {\n\t\t\tMk:dialog(title, \"クリップボードに Misskey のノート URL (https://…/notes/…) がありません\", \"info\")\n\t\t\treturn null\n\t\t}\n\t\t// ap/show はリモートのノートも自分のサーバー上のノートに解決する\n\t\tlet res = Mk:api(\"ap/show\", { uri: uri })\n\t\tif (Core:type(res) != \"obj\" || res.type != \"Note\" || Core:type(res.object) != \"obj\" || Core:type(res.object.id) != \"str\") {\n\t\t\tMk:dialog(title, \"ノートとして取得できませんでした\", \"error\")\n\t\t\treturn null\n\t\t}\n\t\taddToList(res.object.id, null)\n\t})\n} else {\n\tMk:toast(\"後で読むは NoteDeck 専用です (Misskey Web では動作しません)\")\n}\n"} diff --git a/public/registry/plugins/reading-list/locales/en.json b/public/registry/plugins/reading-list/locales/en.json index 3ad5a28..665d09f 100644 --- a/public/registry/plugins/reading-list/locales/en.json +++ b/public/registry/plugins/reading-list/locales/en.json @@ -1,5 +1,5 @@ { - "sourceHash": "12bfb00a5707", + "sourceHash": "e5c93b053405", "name": "Read Later", - "description": "Adds a note to your \"Read Later\" clip in one click from the note menu (Pocket-style read-it-later). The clip is looked up by name (default 「後で読む」, changeable in settings) and created automatically if missing, so no clip ID setup is needed. Pair it with the \"Read Later\" widget (reading-list-widget) to list what you saved and clear items with \"Read\"." + "description": "Adds a note to your \"Read Later\" clip in one click (Pocket-style read-it-later) from the note menu or from two commands: the focused note, or a note URL in your clipboard. The clip is looked up by name (default 「後で読む」, changeable in settings) and created automatically if missing, so no clip ID setup is needed. Bind the commands to keys in the Keybinds settings (Plugins section) to save with a single key; bind the clipboard command to an OS global shortcut and it works even while NoteDeck is in the background (1.85.2+). Pair it with the \"Read Later\" widget (reading-list-widget) to list what you saved and clear items with \"Read\"." } diff --git a/public/registry/plugins/reading-list/meta.json b/public/registry/plugins/reading-list/meta.json index de3ee82..8d3b285 100644 --- a/public/registry/plugins/reading-list/meta.json +++ b/public/registry/plugins/reading-list/meta.json @@ -1,14 +1,32 @@ { "id": "reading-list", "name": "後で読む", - "version": "1.0.0", + "version": "1.1.0", "author": "hitalin", "authorUrl": "https://github.com/hitalin", - "description": "ノートメニューから「後で読む」クリップにワンクリックで追加 (Pocket 的な後で読む)。クリップは名前 (既定「後で読む」、設定で変更可) で探し、無ければ自動で作成するので ID 設定は不要。ウィジェット「後で読む」(reading-list-widget) と対で使うと、溜めたノートを一覧して「読んだ」で消化できる", + "description": "ノートメニュー、またはコマンド (フォーカス中のノート / クリップボードのノート URL) から「後で読む」クリップにワンクリックで追加 (Pocket 的な後で読む)。クリップは名前 (既定「後で読む」、設定で変更可) で探し、無ければ自動で作成するので ID 設定は不要。コマンドはキーバインド設定の「プラグイン」節でキーを割り当てると 1 キーで溜められ、クリップボード取り込みを OS グローバルショートカットにすれば NoteDeck が背面でも動く (1.85.2+)。ウィジェット「後で読む」(reading-list-widget) と対で使うと、溜めたノートを一覧して「読んだ」で消化できる", "license": "MIT", "category": "utility", - "capabilities": ["notedeck-api"], - "tags": ["clip", "read-later", "bookmark", "pocket", "note-action", "reading-list"], - "permissions": ["clips.read", "clips.write"], - "minNoteDeckVersion": "1.55.0" + "capabilities": [ + "notedeck-api" + ], + "tags": [ + "clip", + "read-later", + "bookmark", + "pocket", + "note-action", + "reading-list", + "command", + "hotkey", + "clipboard" + ], + "permissions": [ + "clips.read", + "clips.write", + "notes.read", + "clipboard", + "account.read" + ], + "minNoteDeckVersion": "1.85.2" } diff --git a/public/registry/plugins/reading-list/plugin.is b/public/registry/plugins/reading-list/plugin.is index 6abd233..a4100b1 100644 --- a/public/registry/plugins/reading-list/plugin.is +++ b/public/registry/plugins/reading-list/plugin.is @@ -1,34 +1,54 @@ /// @ 1.2.1 -// 後で読む — ノートメニューから「後で読む」クリップへ 1 クリックで送る +// 後で読む — ノートメニュー / キー 1 つで「後で読む」クリップへ送る // // Pocket 的な「後で読む」ワークフローの入口。ウィジェット「後で読む」(reading-list-widget) // と同じ名前のクリップ (既定「後で読む」、設定 clipName で変更可) を共有し、 // プラグインで溜めてウィジェットで消化する。 // +// 入口は 3 つ: +// - ノートメニュー「後で読む」(本家 Web 互換のフック。実行は NoteDeck 専用) +// - コマンド「後で読む: フォーカス中のノートを追加」 +// column.focusedNote で今フォーカスしているノートを取る。キーバインド設定の +// 「プラグイン」節でキーを割り当てると、カラムで j/k しながら 1 キーで溜められる +// - コマンド「後で読む: クリップボードのノート URL を追加」 +// clipboard.read のテキストが https:///notes/ なら Mk:api("ap/show") +// で (リモートのノートでも) 自分のサーバー上のノートに解決して追加する。 +// OS グローバルショートカットに割り当てると、ブラウザで URL をコピー → キー +// 1 つで NoteDeck に溜まる (NoteDeck が背面でも動く) +// コマンドへのキーバインドは NoteDeck 1.85.2 から届く (notedeck#1243)。 +// // クリップ id はサーバー / アカウントごとに違うので名前で解決する: // 1. Mk:save に覚えた { name, id } がアカウント・名前とも一致すれば clips.list を省く // 2. 無ければ clips.list から名前一致を探す // 3. それも無ければ clips.create で非公開クリップを作る (確認ダイアログあり) // 続けて clips.addNote (確認ダイアログあり) → トースト。 // +// アカウントは、ノートメニューなら note._accountId、フォーカス中なら +// column.focusedNote の accountId を clips.* に明示する。クリップボードからは +// 呼び出し文脈 (active) のアカウントで、記憶の鍵だけ account.current から引く。 +// // Nd:call はキャンセル時だけ error 値 (user_cancelled) を返し、それ以外の失敗 // (クリップが削除済み等) は throw して handler がそこで止まる。AiScript に try/catch は // 無いので、addNote を呼ぶ「前」に覚えた id を捨て、戻ってきたら書き戻す。 // throw した回は書き戻しが走らないため、次回は clips.list から解決し直す。 +// NoteDeck の Mk:api も失敗時は throw する (ap/show が解決できない URL など)。 // // quick-clip との違い: クリップ id を設定しなくてよく、無ければ自動で作る。 // 消化 (読んだ → クリップから外す) はウィジェット側が担当する。 // // NoteDeck 専用。使う NoteDeck 権限 (meta.json に記載): -// - clips.read … clips.list (名前からクリップを探す) -// - clips.write … clips.create (無ければ作成) / clips.addNote (追加) +// - clips.read … clips.list (名前からクリップを探す) +// - clips.write … clips.create (無ければ作成) / clips.addNote (追加) +// - notes.read … column.focusedNote (フォーカス中のノート) +// - clipboard … clipboard.read (ノート URL の取り込み) +// - account.read … Mk:api("ap/show") (URL をノートに解決) / account.current (記憶の鍵) ### { name: "後で読む" - version: "1.0.0" + version: "1.1.0" author: "@hitalin" - description: "ノートメニューから「後で読む」クリップに追加します。クリップが無ければ自動で作成。ウィジェット「後で読む」と対で使うと一覧・既読化ができます (NoteDeck 専用)" + description: "ノートメニュー、またはコマンド (フォーカス中のノート / クリップボードのノート URL) から「後で読む」クリップに追加します。クリップが無ければ自動で作成。コマンドにキーを割り当てると 1 キーで溜められます。ウィジェット「後で読む」と対で使うと一覧・既読化ができます (NoteDeck 専用)" config: { clipName: { type: "string" @@ -49,9 +69,17 @@ let storeKey = "clips" if (n == "") defaultName else n } -// NoteDeck のノートには取得アカウントが _accountId で付く。無ければ単一鍵に倒す -@accountKey(note) { - if (Obj:has(note, "_accountId") && Core:type(note._accountId) == "str") note._accountId else "default" +// accountId が分かっているときだけ params に足す (null なら呼び出し文脈のアカウント) +@withAccount(params, accountId) { + if (accountId != null) params.accountId = accountId + params +} + +// 記憶の鍵。accountId が無ければ account.current から引き、それも無ければ単一鍵に倒す +@storeKeyOf(accountId) { + if (accountId != null) return accountId + let cur = Nd:call("account.current", {}) + if (Core:type(cur) == "obj" && Core:type(cur.id) == "str") cur.id else "default" } // (`else {}` は空ブロックに解釈されるので、空オブジェクトは let で作る) @@ -78,8 +106,8 @@ let storeKey = "clips" } // clips.list で名前一致を探す。見つからなければ null -@findClip(name) { - let clips = Nd:call("clips.list", {}) +@findClip(name, accountId) { + let clips = Nd:call("clips.list", withAccount({}, accountId)) if (Core:type(clips) != "arr") return null each (let c, clips) { if (Core:type(c) == "obj" && c.name == name && Core:type(c.id) == "str") return c.id @@ -88,10 +116,10 @@ let storeKey = "clips" } // 探して無ければ作る。作成をキャンセルされたら null -@resolveClip(name) { - let found = findClip(name) +@resolveClip(name, accountId) { + let found = findClip(name, accountId) if (found != null) return found - let created = Nd:call("clips.create", { name: name, description: "reading-list プラグインが作成", isPublic: false }) + let created = Nd:call("clips.create", withAccount({ name: name, description: "reading-list プラグインが作成", isPublic: false }, accountId)) if (Core:type(created) == "error") return null if (Core:type(created) != "obj" || Core:type(created.id) != "str") { Mk:dialog(title, "クリップを作成できませんでした", "error") @@ -100,29 +128,81 @@ let storeKey = "clips" created.id } -Plugin:register_note_action("後で読む", @(note) { - if (!(exists NOTEDECK)) { - Mk:dialog(title, "このプラグインは NoteDeck 専用です。Misskey Web では動作しません", "info") - return null - } +// noteId を「後で読む」クリップへ追加する。accountId が null なら呼び出し文脈のアカウント +@addToList(noteId, accountId) { let name = clipName() - let key = accountKey(note) + let key = storeKeyOf(accountId) var clipId = rememberedId(key, name) if (clipId == null) { - clipId = resolveClip(name) + clipId = resolveClip(name, accountId) // 作成の確認ダイアログでキャンセルされた if (clipId == null) return null } // addNote が throw した回 (クリップ削除済み等) は書き戻しが走らず、次回は解決し直す remember(key, null) - let added = Nd:call("clips.addNote", { clipId: clipId, noteId: note.id }) + let added = Nd:call("clips.addNote", withAccount({ clipId: clipId, noteId: noteId }, accountId)) remember(key, { name: name, id: clipId }) // 追加の確認ダイアログでキャンセルされた if (Core:type(added) == "error") return null Mk:toast(`「{name}」に追加しました`, "success") null +} + +// クリップボードのテキストが https:///notes/ なら、クエリや断片を落とした +// ノート URL を返す。違えば null +@noteUrlOf(text) { + let t = text.trim() + if (!t.starts_with("https://")) return null + let parts = t.split("/notes/") + if (parts.len != 2) return null + let host = parts[0].slice(8, parts[0].len) + if (host == "" || host.incl("/")) return null + var id = parts[1] + each (let sep, ["/", "?", "#"]) { + let i = id.index_of(sep) + if (i >= 0) id = id.slice(0, i) + } + if (id == "") return null + `https://{host}/notes/{id}` +} + +Plugin:register_note_action("後で読む", @(note) { + if (!(exists NOTEDECK)) { + Mk:dialog(title, "このプラグインは NoteDeck 専用です。Misskey Web では動作しません", "info") + return null + } + // NoteDeck のノートには取得アカウントが _accountId で付く + let accountId = if (Obj:has(note, "_accountId") && Core:type(note._accountId) == "str") note._accountId else null + addToList(note.id, accountId) }) -if (!(exists NOTEDECK)) { +if (exists NOTEDECK) { + Nd:register_command("add-focused", "後で読む: フォーカス中のノートを追加", "ti-bookmark", @() { + let focused = Nd:call("column.focusedNote", {}) + if (Core:type(focused) != "obj" || focused.note == null || Core:type(focused.noteId) != "str") { + Mk:toast("追加するノートをカラムでフォーカスしてください") + return null + } + let accountId = if (Core:type(focused.accountId) == "str") focused.accountId else null + addToList(focused.noteId, accountId) + }) + + Nd:register_command("add-clipboard", "後で読む: クリップボードのノート URL を追加", "ti-clipboard-text", @() { + let clip = Nd:call("clipboard.read", {}) + let text = if (Core:type(clip) == "obj" && Core:type(clip.text) == "str") clip.text else "" + let uri = noteUrlOf(text) + if (uri == null) { + Mk:dialog(title, "クリップボードに Misskey のノート URL (https://…/notes/…) がありません", "info") + return null + } + // ap/show はリモートのノートも自分のサーバー上のノートに解決する + let res = Mk:api("ap/show", { uri: uri }) + if (Core:type(res) != "obj" || res.type != "Note" || Core:type(res.object) != "obj" || Core:type(res.object.id) != "str") { + Mk:dialog(title, "ノートとして取得できませんでした", "error") + return null + } + addToList(res.object.id, null) + }) +} else { Mk:toast("後で読むは NoteDeck 専用です (Misskey Web では動作しません)") } diff --git a/public/registry/queries.json b/public/registry/queries.json index 724e044..37faab0 100644 --- a/public/registry/queries.json +++ b/public/registry/queries.json @@ -1,6 +1,6 @@ { "version": 1, - "updatedAt": "2026-10-15T11:17:46.820Z", + "updatedAt": "2026-10-17T12:32:32.717Z", "queries": [ { "id": "bot-only", diff --git a/public/registry/skills.json b/public/registry/skills.json index d64efef..7959dfe 100644 --- a/public/registry/skills.json +++ b/public/registry/skills.json @@ -1,6 +1,6 @@ { "version": 1, - "updatedAt": "2026-10-15T11:17:46.820Z", + "updatedAt": "2026-10-17T12:32:32.717Z", "skills": [ { "id": "aiscript-surgeon", diff --git a/public/registry/themes.json b/public/registry/themes.json index c4ea883..0994f39 100644 --- a/public/registry/themes.json +++ b/public/registry/themes.json @@ -1,6 +1,6 @@ { "version": 1, - "updatedAt": "2026-10-15T11:17:46.820Z", + "updatedAt": "2026-10-17T12:32:32.717Z", "themes": [ { "id": "adwaita-dark", diff --git a/public/registry/widgets.json b/public/registry/widgets.json index 3932ec0..d202676 100644 --- a/public/registry/widgets.json +++ b/public/registry/widgets.json @@ -1,6 +1,6 @@ { "version": 1, - "updatedAt": "2026-10-15T11:17:46.820Z", + "updatedAt": "2026-10-17T12:32:32.717Z", "widgets": [ { "id": "aiscript-health",