From 8045fdacec27a28eb0dae356311994e5d8e6a391 Mon Sep 17 00:00:00 2001 From: Last2014 Date: Sat, 2 May 2026 18:56:07 +0900 Subject: [PATCH 01/10] =?UTF-8?q?Fix:=20=E3=82=B3=E3=83=9E=E3=83=B3?= =?UTF-8?q?=E3=83=89=E5=87=A6=E7=90=86=E6=B8=88=E3=81=BF=E3=81=AE=E3=83=A6?= =?UTF-8?q?=E3=83=BC=E3=82=BA=E3=81=AEid=E9=99=A4=E5=A4=96=E3=81=8C?= =?UTF-8?q?=E6=AD=A3=E3=81=97=E3=81=8F=E5=8B=95=E4=BD=9C=E3=81=97=E3=81=AA?= =?UTF-8?q?=E3=81=84=E5=95=8F=E9=A1=8C=20/=20Fix:=20miq=E3=82=B3=E3=83=9E?= =?UTF-8?q?=E3=83=B3=E3=83=89=E3=81=AE=E3=82=B3=E3=83=B3=E3=82=BD=E3=83=BC?= =?UTF-8?q?=E3=83=AB=E5=87=BA=E5=8A=9B=E3=81=AB=E5=80=A4=E3=81=8C=E5=AD=98?= =?UTF-8?q?=E5=9C=A8=E3=81=97=E3=81=AA=E3=81=84=E5=95=8F=E9=A1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/feature/command/index.ts | 36 ++++++++++++++++++------------------ src/feature/command/miq.ts | 3 ++- 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/src/feature/command/index.ts b/src/feature/command/index.ts index 0d04155..ec2d3c2 100644 --- a/src/feature/command/index.ts +++ b/src/feature/command/index.ts @@ -22,19 +22,17 @@ try { }); if (response.success) { - for (const [index, notification] of response.data.entries()) { - if (notification.category !== "reply") + const notifications = response.data.filter(notification => notification.category === "reply" && typeof notification.valueid === "string"); + + const mem = Memory.memory; + const lastReadReply = mem.lastReadReply; + + for (const [index, notification] of notifications.entries()) { + if (notification.category !== "reply" || typeof notification.valueid !== "string") continue; - if (!notification.valueid) { - console.warn("返信通知にvalueidが存在しないため、スキップします"); - continue; - } - - const mem = Memory.memory; - if (mem.lastReadReply === notification.valueid) { + if (lastReadReply === notification.valueid) break; - } const ueuseResponse = await client.request("ueuse/get", { uniqid: notification.valueid, @@ -48,6 +46,7 @@ try { if (index === 0) { const mem = Memory.memory; mem.lastReadReply = ueuseResponse.data[0].uniqid; + Memory.memory = mem; } ueuses.push(ueuseResponse.data[0]); @@ -64,15 +63,16 @@ try { }); if (response.success) { - for (const [index, mention] of response.data.entries()) { - const mem = Memory.memory; - if (mem.lastReadMention === mention.uniqid) { - break; - } + const mentions = response.data; - if (index === 0) { - const mem = Memory.memory; - mem.lastReadMention = mention.uniqid; + const mem = Memory.memory; + const lastReadMention = mem.lastReadMention; + mem.lastReadMention = mentions[0]?.uniqid ?? lastReadMention; + Memory.memory = mem; + + for (const mention of mentions) { + if (lastReadMention === mention.uniqid) { + break; } ueuses.push(mention); diff --git a/src/feature/command/miq.ts b/src/feature/command/miq.ts index eb19602..d02eff3 100644 --- a/src/feature/command/miq.ts +++ b/src/feature/command/miq.ts @@ -325,9 +325,10 @@ export default async function miqCommand(ueuse: ueuseModule, args: string[]) { if (!response.success) { console.log("返信に失敗:", response.error_code); + return; } - console.warn("ソースのユーズと/miq allowのユーザーが一致しない:", ); + console.warn("ソースのユーズと/miq allowのユーザーが一致しない:", response.uniqid); return; } From c0df3d73441d66468bdb812b90a08327e4766780 Mon Sep 17 00:00:00 2001 From: Last2014 Date: Sat, 2 May 2026 20:07:37 +0900 Subject: [PATCH 02/10] =?UTF-8?q?Feat:=20=E5=A4=A9=E6=B0=97=E4=BA=88?= =?UTF-8?q?=E5=A0=B1=E3=81=AE=E5=88=86=E5=89=B2=E6=95=B0=E3=82=92=E8=87=AA?= =?UTF-8?q?=E5=8B=95=E7=9A=84=E3=81=AB=E8=A8=88=E7=AE=97=E3=81=99=E3=82=8B?= =?UTF-8?q?=E6=A9=9F=E8=83=BD=20/=20Fix:=20=E9=9C=87=E5=BA=A60=E3=81=AE?= =?UTF-8?q?=E5=9C=B0=E9=9C=87=E6=83=85=E5=A0=B1=E3=81=AB=E5=AF=BE=E5=BF=9C?= =?UTF-8?q?=E3=81=97=E3=81=A6=E3=81=84=E3=81=AA=E3=81=84=E5=95=8F=E9=A1=8C?= =?UTF-8?q?=20/=20Feat:=20=E6=9C=80=E5=A4=A7=E9=9C=87=E5=BA=A6=E3=81=AE?= =?UTF-8?q?=E8=A6=81=E6=B1=82=E3=82=92=E8=A8=AD=E5=AE=9A=E3=81=A7=E3=81=8D?= =?UTF-8?q?=E3=82=8B=E6=A9=9F=E8=83=BD=20/=20Feat:=20=E6=9C=80=E5=A4=A7?= =?UTF-8?q?=E9=9C=87=E5=BA=A6=E3=81=8C=E4=B8=8D=E6=98=8E=E3=81=AA=E5=A0=B4?= =?UTF-8?q?=E5=90=88=E3=81=AB=E6=8A=95=E7=A8=BF=E3=81=99=E3=82=8B=E3=81=8B?= =?UTF-8?q?=E3=81=A9=E3=81=86=E3=81=8B=E3=82=92=E8=A8=AD=E5=AE=9A=E3=81=A7?= =?UTF-8?q?=E3=81=8D=E3=82=8B=E6=A9=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/example.yaml | 16 ++++++++++++++-- src/feature/earthquakeNotice.ts | 18 ++++++++++++++++++ src/feature/weatherNotice.ts | 5 +++-- src/index.ts | 4 ++-- src/lib/config.ts | 17 +++++++++++++---- src/lib/memory.ts | 26 +++++++++++++++++++------- 6 files changed, 69 insertions(+), 17 deletions(-) diff --git a/config/example.yaml b/config/example.yaml index d33f0c6..cc794f6 100644 --- a/config/example.yaml +++ b/config/example.yaml @@ -1,10 +1,22 @@ command: + # コマンド処理の間隔(分) number + # 自然数のみが有効です。 interval: 10 -weather: - splits: 4 earthquake: + # 地震発生情報を投稿することに必要な最大震度 number + # 例: 30を指定すると、最大震度が震度3以上の地震発生情報のみを投稿します。 + # 10(震度1), 20(震度2), 30(震度3), 40(震度4), 45(震度5弱), 50(震度5強), 55(震度6弱), 60(震度6強), 70(震度7)が有効です。 + requireMaxScale: 30 + # 最大震度が不明な地震発生情報を投稿するかどうか boolean + canUnknownMaxScale: true + # 過去のデバッグ用データを使用して地震情報を配信するかどうか boolean + # デバッグ用途のみで使用してください。 useHistoryData: false uwuzu: + # APIトークン string + # とくに理由がない限り、全権限を与えてください。 token: API_TOKEN + # uwuzuサーバーのorigin string origin: https://uwuzu.example.com +# デバッグモードにするかどうか boolean debug: false \ No newline at end of file diff --git a/src/feature/earthquakeNotice.ts b/src/feature/earthquakeNotice.ts index 3e43130..a3a1954 100644 --- a/src/feature/earthquakeNotice.ts +++ b/src/feature/earthquakeNotice.ts @@ -64,6 +64,7 @@ const processMessage = async (message: any) => { try { const scaleMessages: Record = { "-1": "不明", + "0": "震度0", "10": "震度1", "20": "震度2", "30": "震度3", @@ -85,6 +86,23 @@ const processMessage = async (message: any) => { { console.log("地震発生情報を受信しました"); + if ( + (message.earthquake.maxScale === -1 || + message.earthquake.maxScale === undefined) && + !config.earthquake.canUnknownMaxScale + ) { + console.log("最大震度が不明であり、最大震度が不明な場合の投稿が許可されていないため、スキップします"); + break; + } + + if ( + message.earthquake.maxScale !== -1 && + message.earthquake.maxScale < config.earthquake.requireMaxScale + ) { + console.log("投稿に必要な最大震度に満たないため、スキップします"); + break; + } + const domesticTsunamiMessages: Record = { "None": "😌この地震による**国内**の津波の心配はありません。", "Unknown": "😕この地震による**国内**の***津波情報は***不明です。", diff --git a/src/feature/weatherNotice.ts b/src/feature/weatherNotice.ts index ef55938..79412d0 100644 --- a/src/feature/weatherNotice.ts +++ b/src/feature/weatherNotice.ts @@ -1,6 +1,6 @@ import client from "@/lib/client"; -import config from "@/lib/config"; import initI18n from "@/lib/i18n"; +import Memory from "@/lib/memory"; import i18next from "i18next"; import { readFileSync } from "node:fs"; import { EOL } from "node:os"; @@ -84,7 +84,8 @@ if (!isMainThread && workerData === "scheduledWeatherNotice") { export async function weatherReply(uniqid: string) { // インデックス - const splitCount = config.weather.splits; + const mem = Memory.memory; + const splitCount = Math.round(3100 / mem.max_length); const total = cityList.length; const chunkSizes = Array(splitCount).fill(0).map((_, i) => Math.floor((total + i) / splitCount) diff --git a/src/index.ts b/src/index.ts index 18be265..dc70c41 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,7 +1,7 @@ import { schedule } from "node-cron"; import { readFileSync } from "node:fs"; import config from "@/lib/config"; -import { initUserID } from "@/lib/memory"; +import { initData } from "@/lib/memory"; import { styleText } from "node:util"; import { Worker } from "node:worker_threads"; @@ -18,7 +18,7 @@ try { } console.log(); - await initUserID(); + await initData(); new Worker(`${import.meta.dirname}/feature/earthquakeNotice.js`); diff --git a/src/lib/config.ts b/src/lib/config.ts index d85056b..f8e6ae3 100644 --- a/src/lib/config.ts +++ b/src/lib/config.ts @@ -7,12 +7,21 @@ const schema = z.object({ command: z.object({ interval: z.number().int().positive(), }), - weather: z.object({ - splits: z.number().int().positive(), - }), earthquake: z.object({ + requireMaxScale: z.union([ + z.literal(10), + z.literal(20), + z.literal(30), + z.literal(40), + z.literal(45), + z.literal(50), + z.literal(55), + z.literal(60), + z.literal(70), + ]), + canUnknownMaxScale: z.boolean(), useHistoryData: z.boolean(), - }).optional(), + }), uwuzu: z.object({ token: z.string().length(64), origin: z.string().refine(data => { diff --git a/src/lib/memory.ts b/src/lib/memory.ts index 0e41425..f0c6962 100644 --- a/src/lib/memory.ts +++ b/src/lib/memory.ts @@ -14,6 +14,7 @@ class MemoryClass { lastReadMention: "", lastReadReply: "", userid: "", + max_length: 0, })); } @@ -32,15 +33,26 @@ class MemoryClass { const Memory = new MemoryClass(); -export const initUserID = async () => { - const response = await client.request("me/"); +export const initData = async () => { + await Promise.all([ + (async () => { + const response = await client.request("me/"); - if (!response.success) - throw new Error("meの取得に失敗しました"); + if (!response.success) + throw new Error("meの取得に失敗しました"); - const mem = Memory.memory; - mem.userid = response.userid; - Memory.memory = mem; + const mem = Memory.memory; + mem.userid = response.userid; + Memory.memory = mem; + })(), + (async () => { + const response = await client.request("serverinfo-api"); + + const mem = Memory.memory; + mem.max_length = response.server_info.max_ueuse_length; + Memory.memory = mem; + })(), + ]); } export default Memory; \ No newline at end of file From d61fc56a386114adfd290e23a0cda2bfc7e83321 Mon Sep 17 00:00:00 2001 From: Last2014 Date: Sat, 2 May 2026 20:32:15 +0900 Subject: [PATCH 03/10] =?UTF-8?q?Feat:=20=E5=9C=B0=E9=9C=87=E6=83=85?= =?UTF-8?q?=E5=A0=B1=E3=81=AEWebSocket=E5=86=8D=E6=8E=A5=E7=B6=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/example.yaml | 2 + src/feature/earthquakeNotice.ts | 77 ++++++++++++++++++++------------- src/lib/config.ts | 1 + 3 files changed, 50 insertions(+), 30 deletions(-) diff --git a/config/example.yaml b/config/example.yaml index cc794f6..71cdc52 100644 --- a/config/example.yaml +++ b/config/example.yaml @@ -12,6 +12,8 @@ earthquake: # 過去のデバッグ用データを使用して地震情報を配信するかどうか boolean # デバッグ用途のみで使用してください。 useHistoryData: false + # 再接続の間隔(ミリ秒) number + reconnectInterval: 3000 uwuzu: # APIトークン string # とくに理由がない限り、全権限を与えてください。 diff --git a/src/feature/earthquakeNotice.ts b/src/feature/earthquakeNotice.ts index a3a1954..45f37d8 100644 --- a/src/feature/earthquakeNotice.ts +++ b/src/feature/earthquakeNotice.ts @@ -21,43 +21,60 @@ if (config.earthquake?.useHistoryData) { i++; }, 10 * 1000); } else { - const WEBSOCKET_URL = config.debug - ? "wss://api-realtime-sandbox.p2pquake.net/v2/ws" - : "wss://api.p2pquake.net/v2/ws"; + const connect = () => { + const WEBSOCKET_URL = config.debug + ? "wss://api-realtime-sandbox.p2pquake.net/v2/ws" + : "wss://api.p2pquake.net/v2/ws"; - console.log("P2P地震情報のWebSocketに接続します"); - const socket = new WebSocket(WEBSOCKET_URL); + console.log("P2P地震情報のWebSocketに接続します"); + const socket = new WebSocket(WEBSOCKET_URL); - socket.addEventListener("open", () => { - console.log("P2P地震情報のWebSocketに接続しました"); - }); + socket.addEventListener("open", () => { + console.log("P2P地震情報のWebSocketに接続しました"); + }); - socket.addEventListener("message", async (event) => { - let message; + socket.addEventListener("close", (err) => { + const interval = config.earthquake.reconnectInterval; + console.log(`WebSocketが切断されました。${interval / 1000}秒後に再接続します:`, err.reason); + setTimeout(() => { + connect(); + }, interval); + }); - try { - message = typeof event.data === "string" - ? JSON.parse(event.data) - : event.data; - } catch (err) { - console.error(`地震情報メッセージのパースでエラーが発生: ${err}`); - return; - } + socket.addEventListener("error", (err) => { + console.error("WebSocketでエラーが発生しました:", err); + socket.close(); + }); - const id = message.id ?? message._id; - const mem = Memory.memory; - if (mem.processedInfo.includes(id) && !config.debug) { - console.log("重複した地震情報: ", message.id); - return; - } + socket.addEventListener("message", async (event) => { + let message; - processMessage(message); + try { + message = typeof event.data === "string" + ? JSON.parse(event.data) + : event.data; + } catch (err) { + console.error("地震情報メッセージのパースでエラーが発生:", err); + return; + } - if (!config.debug) { - mem.processedInfo.concat([id]); - Memory.memory = mem; - } - }); + const id = message.id ?? message._id; + const mem = Memory.memory; + if (mem.processedInfo.includes(id) && !config.debug) { + console.log("重複した地震情報:", message.id); + return; + } + + processMessage(message); + + if (!config.debug) { + mem.processedInfo.concat([id]); + Memory.memory = mem; + } + }); + } + + connect(); } const processMessage = async (message: any) => { diff --git a/src/lib/config.ts b/src/lib/config.ts index f8e6ae3..7e0fa71 100644 --- a/src/lib/config.ts +++ b/src/lib/config.ts @@ -21,6 +21,7 @@ const schema = z.object({ ]), canUnknownMaxScale: z.boolean(), useHistoryData: z.boolean(), + reconnectInterval: z.number().positive(), }), uwuzu: z.object({ token: z.string().length(64), From 0012fea2de205e94bafba496753ed3a4a46bc4a6 Mon Sep 17 00:00:00 2001 From: Last2014 Date: Sun, 3 May 2026 11:41:31 +0900 Subject: [PATCH 04/10] =?UTF-8?q?Feat:=20=E3=82=B3=E3=83=9E=E3=83=B3?= =?UTF-8?q?=E3=83=89=E3=82=92=E4=B8=A6=E5=88=97=E5=87=A6=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/feature/command/index.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/feature/command/index.ts b/src/feature/command/index.ts index ec2d3c2..d7bcd73 100644 --- a/src/feature/command/index.ts +++ b/src/feature/command/index.ts @@ -84,7 +84,7 @@ try { ueuses = [...new Set(ueuses)]; - for (const ueuse of ueuses) { + await Promise.all(ueuses.map(async (ueuse) => { const mem = Memory.memory; let text = ueuse.text; text = text.replace(`@${mem.userid}`, ""); @@ -103,7 +103,8 @@ try { if (!response.success) console.warn("ユーズの作成に失敗しました:", response.error_code); - break; + + return; } const args = commandRow.replace("/", "").split(" "); @@ -136,7 +137,7 @@ try { console.warn("ユーズの作成に失敗しました:", response.error_code); break; } - } + })); process.exit(0); } catch (err: any) { From 74c155247284a4c364aefe9c52e149482a518c8e Mon Sep 17 00:00:00 2001 From: Last2014 Date: Sun, 3 May 2026 11:47:50 +0900 Subject: [PATCH 05/10] =?UTF-8?q?Feat:=20=E3=82=B3=E3=83=9E=E3=83=B3?= =?UTF-8?q?=E3=83=89=E3=81=AE=E4=B8=A6=E5=88=97=E5=87=A6=E7=90=86=E6=95=B0?= =?UTF-8?q?=E3=82=92=E5=88=B6=E9=99=90=E3=81=A7=E3=81=8D=E3=82=8B=E6=A9=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/example.yaml | 5 ++ src/feature/command/index.ts | 97 +++++++++++++++++++----------------- src/lib/config.ts | 1 + 3 files changed, 57 insertions(+), 46 deletions(-) diff --git a/config/example.yaml b/config/example.yaml index 71cdc52..5dd5dda 100644 --- a/config/example.yaml +++ b/config/example.yaml @@ -2,6 +2,11 @@ command: # コマンド処理の間隔(分) number # 自然数のみが有効です。 interval: 10 + # コマンドの最大並列処理数 number + # 例: 2を指定すると、コマンドを並列に2つずつ処理します。 + # 並列処理の数であるため、最終的なコマンドの処理数は変わりません。 + # 自然数のみが有効です。 + maxParallels: 3 earthquake: # 地震発生情報を投稿することに必要な最大震度 number # 例: 30を指定すると、最大震度が震度3以上の地震発生情報のみを投稿します。 diff --git a/src/feature/command/index.ts b/src/feature/command/index.ts index d7bcd73..9b18e70 100644 --- a/src/feature/command/index.ts +++ b/src/feature/command/index.ts @@ -8,6 +8,7 @@ import followCommand from "@/feature/command/follow"; import unfollowCommand from "@/feature/command/unfollow"; import miqCommand from "@/feature/command/miq"; import initI18n from "@/lib/i18n"; +import config from "@/lib/config"; await initI18n(); console.log("コマンドの処理を行います"); @@ -84,60 +85,64 @@ try { ueuses = [...new Set(ueuses)]; - await Promise.all(ueuses.map(async (ueuse) => { - const mem = Memory.memory; - let text = ueuse.text; - text = text.replace(`@${mem.userid}`, ""); - text = text.trim(); - - const rows = text.split(/\r\n|\r|\n/).map(row => row.trim()); - const commandRow = rows.filter(row => row.startsWith("/"))[0]; - - if (!commandRow || commandRow === "") { - console.warn("コマンドが本文から参照できません"); - - const response = await client.request("ueuse/create", { - text: i18next.t("commandNotFound"), - replyid: ueuse.uniqid, - }); - - if (!response.success) - console.warn("ユーズの作成に失敗しました:", response.error_code); - - return; - } - - const args = commandRow.replace("/", "").split(" "); + for (let i = 0; i < ueuses.length; i += config.command.maxParallels) { + const chunk = ueuses.slice(i, i + config.command.maxParallels); - switch (args[0]) { - case "help": - await helpCommand(ueuse, args); - break; - case "weather": - await weatherCommand(ueuse); - break; - case "follow": - await followCommand(ueuse); - break; - case "unfollow": - await unfollowCommand(ueuse); - break; - case "miq": - await miqCommand(ueuse, args); - break; - default: - console.warn("不明なコマンドが入力されました:", args[0]); + await Promise.all(chunk.map(async (ueuse) => { + const mem = Memory.memory; + let text = ueuse.text; + text = text.replace(`@${mem.userid}`, ""); + text = text.trim(); + + const rows = text.split(/\r\n|\r|\n/).map(row => row.trim()); + const commandRow = rows.filter(row => row.startsWith("/"))[0]; + + if (!commandRow || commandRow === "") { + console.warn("コマンドが本文から参照できません"); const response = await client.request("ueuse/create", { - text: i18next.t("unknownCommand", { command: args[0] }), + text: i18next.t("commandNotFound"), replyid: ueuse.uniqid, }); if (!response.success) console.warn("ユーズの作成に失敗しました:", response.error_code); - break; - } - })); + + return; + } + + const args = commandRow.replace("/", "").split(" "); + + switch (args[0]) { + case "help": + await helpCommand(ueuse, args); + break; + case "weather": + await weatherCommand(ueuse); + break; + case "follow": + await followCommand(ueuse); + break; + case "unfollow": + await unfollowCommand(ueuse); + break; + case "miq": + await miqCommand(ueuse, args); + break; + default: + console.warn("不明なコマンドが入力されました:", args[0]); + + const response = await client.request("ueuse/create", { + text: i18next.t("unknownCommand", { command: args[0] }), + replyid: ueuse.uniqid, + }); + + if (!response.success) + console.warn("ユーズの作成に失敗しました:", response.error_code); + break; + } + })); + } process.exit(0); } catch (err: any) { diff --git a/src/lib/config.ts b/src/lib/config.ts index 7e0fa71..20ce3d4 100644 --- a/src/lib/config.ts +++ b/src/lib/config.ts @@ -6,6 +6,7 @@ import { EOL } from "node:os"; const schema = z.object({ command: z.object({ interval: z.number().int().positive(), + maxParallels: z.number().int().positive(), }), earthquake: z.object({ requireMaxScale: z.union([ From d429503b78164c8334dc78450818bc7f2edb578e Mon Sep 17 00:00:00 2001 From: Last2014 Date: Sun, 3 May 2026 13:50:11 +0900 Subject: [PATCH 06/10] =?UTF-8?q?Feat:=20=E3=83=A6=E3=83=BC=E3=82=BA?= =?UTF-8?q?=E3=81=AE=E5=86=8D=E8=A9=A6=E8=A1=8C=20/=20Feat:=20=E3=83=A6?= =?UTF-8?q?=E3=83=BC=E3=82=BA=E3=81=AE=E6=96=87=E5=AD=97=E6=95=B0=E5=88=B6?= =?UTF-8?q?=E9=99=90=E5=9B=9E=E9=81=BF=20/=20Feat:=20=E3=83=A6=E3=83=BC?= =?UTF-8?q?=E3=82=BA=E9=80=81=E4=BF=A1=E9=96=A2=E6=95=B0=20/=20Chg:=20weat?= =?UTF-8?q?herNotice.ts=E3=81=AE=E3=83=9E=E3=82=B8=E3=83=83=E3=82=AF?= =?UTF-8?q?=E3=83=8A=E3=83=B3=E3=83=90=E3=83=BC=E3=81=AB=E5=91=BD=E5=90=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/example.yaml | 8 ++ src/feature/command/follow.ts | 13 +- src/feature/command/help.ts | 33 ++--- src/feature/command/index.ts | 15 +-- src/feature/command/miq.ts | 211 +++++++------------------------- src/feature/command/unfollow.ts | 13 +- src/feature/earthquakeNotice.ts | 57 ++------- src/feature/hnyNotice.ts | 13 +- src/feature/timeNotice.ts | 12 +- src/feature/weatherNotice.ts | 57 +++++++-- src/lib/client.ts | 62 +++++++++- src/lib/config.ts | 4 + 12 files changed, 201 insertions(+), 297 deletions(-) diff --git a/config/example.yaml b/config/example.yaml index 5dd5dda..ab1c040 100644 --- a/config/example.yaml +++ b/config/example.yaml @@ -18,6 +18,7 @@ earthquake: # デバッグ用途のみで使用してください。 useHistoryData: false # 再接続の間隔(ミリ秒) number + # 正数のみが有効です。 reconnectInterval: 3000 uwuzu: # APIトークン string @@ -25,5 +26,12 @@ uwuzu: token: API_TOKEN # uwuzuサーバーのorigin string origin: https://uwuzu.example.com +ueuse: + # 最大再試行数 number + # 自然数のみが有効です。 + maxRetries: 3 + # 再試行の間隔(ミリ秒) number + # 正数のみが有効です。 + retryInterval: 1000 # デバッグモードにするかどうか boolean debug: false \ No newline at end of file diff --git a/src/feature/command/follow.ts b/src/feature/command/follow.ts index cb71eda..ff33b58 100644 --- a/src/feature/command/follow.ts +++ b/src/feature/command/follow.ts @@ -1,4 +1,4 @@ -import client from "@/lib/client"; +import client, { createUeuse } from "@/lib/client"; import ueuseModule from "better-uwuzu-sdk/types/1.6.8/types/modules/ueuse"; import i18next from "i18next"; @@ -14,15 +14,8 @@ export default async function followCommand(ueuse: ueuseModule) { console.log("フォロー:", follow.userid); - const notice = await client.request("ueuse/create", { + await createUeuse({ text: i18next.t("followedNotification", { username: ueuse.account.username }), replyid: ueuse.uniqid, - }); - - if (!notice.success) { - console.warn("フォロー通知に失敗:", notice.error_code); - return; - } - - console.log("フォロー通知:", notice.uniqid); + }, "フォロー通知"); } \ No newline at end of file diff --git a/src/feature/command/help.ts b/src/feature/command/help.ts index 552cde8..649aeff 100644 --- a/src/feature/command/help.ts +++ b/src/feature/command/help.ts @@ -1,4 +1,4 @@ -import client from "@/lib/client"; +import { createUeuse } from "@/lib/client"; import ueuseModule from "better-uwuzu-sdk/types/1.6.8/types/modules/ueuse"; import i18next from "i18next"; import { EOL } from "node:os"; @@ -14,31 +14,19 @@ const helps = [ export default async function helpCommand(ueuse: ueuseModule, args: string[]) { if (args[1] !== undefined) { if (!(helps.includes(args[1]))) { - const response = await client.request("ueuse/create", { + await createUeuse({ text: i18next.t("invalidOption", { option: args[1], command: "help" }), replyid: ueuse.uniqid, - }); + }, "無効なオプションである旨"); - if (!response.success) { - console.warn("コマンド詳細の返信に失敗:", response.error_code); - return; - } - - console.warn("コマンド詳細:", response.uniqid); return; } - const response = await client.request("ueuse/create", { + await createUeuse({ text: i18next.t(`fullHelp${args[1].charAt(0).toUpperCase()}${args[1].slice(1)}`), replyid: ueuse.uniqid, - }); + }, "コマンド詳細"); - if (!response.success) { - console.warn("コマンド詳細の返信に失敗:", response.error_code); - return; - } - - console.warn("コマンド詳細:", response.uniqid); return; } @@ -52,15 +40,8 @@ export default async function helpCommand(ueuse: ueuseModule, args: string[]) { summarys += `${i18next.t(`help${help.charAt(0).toUpperCase()}${help.slice(1)}`)}${EOL}`; } - const response = await client.request("ueuse/create", { + await createUeuse({ text: summarys.trim(), replyid: ueuse.uniqid, - }); - - if (!response.success) { - console.warn("コマンド概要の返信に失敗:", response.error_code); - return; - } - - console.warn("コマンド概要:", response.uniqid); + }, "コマンド概要"); } diff --git a/src/feature/command/index.ts b/src/feature/command/index.ts index 9b18e70..0f70e07 100644 --- a/src/feature/command/index.ts +++ b/src/feature/command/index.ts @@ -1,4 +1,4 @@ -import client from "@/lib/client"; +import client, { createUeuse } from "@/lib/client"; import Memory from "@/lib/memory"; import ueuseModule from "better-uwuzu-sdk/types/1.6.8/types/modules/ueuse"; import i18next from "i18next"; @@ -100,13 +100,10 @@ try { if (!commandRow || commandRow === "") { console.warn("コマンドが本文から参照できません"); - const response = await client.request("ueuse/create", { + await createUeuse({ text: i18next.t("commandNotFound"), replyid: ueuse.uniqid, - }); - - if (!response.success) - console.warn("ユーズの作成に失敗しました:", response.error_code); + }, "コマンドが見つからない旨"); return; } @@ -132,13 +129,11 @@ try { default: console.warn("不明なコマンドが入力されました:", args[0]); - const response = await client.request("ueuse/create", { + await createUeuse({ text: i18next.t("unknownCommand", { command: args[0] }), replyid: ueuse.uniqid, - }); + }, "コマンドが不明である旨"); - if (!response.success) - console.warn("ユーズの作成に失敗しました:", response.error_code); break; } })); diff --git a/src/feature/command/miq.ts b/src/feature/command/miq.ts index d02eff3..8618f99 100644 --- a/src/feature/command/miq.ts +++ b/src/feature/command/miq.ts @@ -1,4 +1,4 @@ -import client from "@/lib/client"; +import client, { createUeuse } from "@/lib/client"; import Memory from "@/lib/memory"; import ueuseModule from "better-uwuzu-sdk/types/1.6.8/types/modules/ueuse"; import i18next from "i18next"; @@ -7,14 +7,10 @@ import { EOL } from "node:os"; export default async function miqCommand(ueuse: ueuseModule, args: string[]) { if (!args[1]) { - const response = await client.request("ueuse/create", { + await createUeuse({ text: i18next.t("lackOption", { command: "miq" }), replyid: ueuse.uniqid, - }); - - if (!response.success) { - console.warn("返信に失敗:", response.error_code); - } + }, "オプションが不足している旨"); return; } @@ -32,14 +28,10 @@ export default async function miqCommand(ueuse: ueuseModule, args: string[]) { ? itUeuse.error_code : "データなし"); - const response = await client.request("ueuse/create", { + await createUeuse({ text: i18next.t("replySourceFailed"), replyid: ueuse.uniqid, - }); - - if (!response.success) { - console.log("返信に失敗:", response.error_code); - } + }, "ソースが見つからない旨"); return; } @@ -49,27 +41,19 @@ export default async function miqCommand(ueuse: ueuseModule, args: string[]) { switch (permission) { case "me": if (itUeuse.data[0].account.userid !== ueuse.account.userid) { - const response = await client.request("ueuse/create", { + await createUeuse({ text: i18next.t("miqPermissionMe"), replyid: ueuse.uniqid, - }); - - if (!response.success) { - console.warn("返信に失敗:", response.error_code); - } + }, "権限が自分自身のみである旨"); return; } case "consent": if (itUeuse.data[0].account.userid !== ueuse.account.userid) { - const response = await client.request("ueuse/create", { + await createUeuse({ text: i18next.t("miqPermissionConsent", { userid: itUeuse.data[0].account.userid }), replyid: ueuse.uniqid, - }); - - if (!response.success) { - console.warn("返信に失敗:", response.error_code); - } + }, "権限が許可制である旨"); return; } @@ -85,19 +69,15 @@ export default async function miqCommand(ueuse: ueuseModule, args: string[]) { }); if (!(typeof result === "string")) { - const response = await client.request("ueuse/create", { + await createUeuse({ text: i18next.t("miqGenerateFailed"), replyid: ueuse.uniqid, - }); - - if (!response.success) { - console.warn("返信に失敗:", response.error_code); - } + }, "Make it a Quoteの生成に失敗した旨"); return; } - const response = await client.request("ueuse/create", { + await createUeuse({ text: i18next.t("miqSuccess", { message: (args[2] ?? "") === "color" ? "カラーモードで生成しました。" : "モノクロモードで生成しました。" @@ -106,14 +86,7 @@ export default async function miqCommand(ueuse: ueuseModule, args: string[]) { photo: [result], }, replyid: ueuse.uniqid, - }); - - if (!response.success) { - console.warn("返信に失敗:", response.error_code); - return; - } - - console.log("MiQを生成:", response.uniqid); + }, "Make it a Quote"); break; case "permission": @@ -121,33 +94,21 @@ export default async function miqCommand(ueuse: ueuseModule, args: string[]) { mem = Memory.memory; const permission = mem["permissions"][ueuse.account.userid] ?? "consent"; - const response = await client.request("ueuse/create", { + await createUeuse({ text: i18next.t("permissionResponse", { permission }), replyid: ueuse.uniqid, - }); + }, "権限"); - if (!response.success) { - console.warn("返信に失敗:", response.error_code); - return; - } - - console.log("返信:", response.uniqid); return; } const availablePermission = ["me", "everyone", "consent"]; if (!(availablePermission.includes(args[2]))) { - const response = await client.request("ueuse/create", { + await createUeuse({ text: i18next.t("invalidOption", { option: args[2], command: "miq" }), replyid: ueuse.uniqid, - }); + }, "無効なオプションである旨"); - if (!response.success) { - console.warn("返信に失敗:", response.error_code); - return; - } - - console.log("返信:", response.uniqid); return; } @@ -156,33 +117,20 @@ export default async function miqCommand(ueuse: ueuseModule, args: string[]) { mem["permissions"][ueuse.account.userid] = args[2]; Memory.memory = mem; - const response = await client.request("ueuse/create", { + await createUeuse({ text: i18next.t("permissionChangeSuccess", { username: ueuse.account.username, permission: args[2] }), replyid: ueuse.uniqid, - }); - - if (!response.success) { - console.warn("返信に失敗:", response.error_code); - return; - } - - console.log("返信:", response.uniqid); + }, "権限の変更に成功した旨"); } break; case "allow": if (ueuse.replyid === "") { - const response = await client.request("ueuse/create", { + await createUeuse({ text: i18next.t("injusticeFormat"), replyid: ueuse.uniqid, - }); + }, "形式が異なる旨"); - if (!response.success) { - console.warn("返信に失敗:", response.error_code); - return; - } - - console.log("返信:", response.uniqid); return; } @@ -191,17 +139,11 @@ export default async function miqCommand(ueuse: ueuseModule, args: string[]) { const permission = mem["permissions"][ueuse.account.userid] ?? "consent"; if (permission !== "consent") { - const response = await client.request("ueuse/create", { + await createUeuse({ text: i18next.t("permisionIsNotConsent"), replyid: ueuse.uniqid, - }); + }, "権限が許可制ではない旨"); - if (!response.success) { - console.warn("返信に失敗:", response.error_code); - return; - } - - console.log("返信:", response.uniqid); return; } } @@ -215,30 +157,20 @@ export default async function miqCommand(ueuse: ueuseModule, args: string[]) { ? confirmUeuse.error_code : "データなし"); - const response = await client.request("ueuse/create", { + await createUeuse({ text: i18next.t("replySourceFailed"), replyid: ueuse.uniqid, - }); - - if (!response.success) { - console.log("返信に失敗:", response.error_code); - } + }, "ソースが見つからない旨"); return; } if (confirmUeuse.data[0].replyid === "") { - const response = await client.request("ueuse/create", { + await createUeuse({ text: i18next.t("injusticeFormat"), replyid: ueuse.uniqid, - }); + }, "形式が異なる旨"); - if (!response.success) { - console.warn("返信に失敗:", response.error_code); - return; - } - - console.log("返信:", response.uniqid); return; } @@ -248,14 +180,10 @@ export default async function miqCommand(ueuse: ueuseModule, args: string[]) { ? confirmUeuse.error_code : "データなし"); - const response = await client.request("ueuse/create", { + await createUeuse({ text: i18next.t("replySourceIsNotThis"), replyid: ueuse.uniqid, - }); - - if (!response.success) { - console.log("返信に失敗:", response.error_code); - } + }, "返信元がこのBotではない旨"); return; } @@ -269,30 +197,19 @@ export default async function miqCommand(ueuse: ueuseModule, args: string[]) { ? requestUeuse.error_code : "データなし"); - const response = await client.request("ueuse/create", { + await createUeuse({ text: i18next.t("replySourceFailed"), replyid: ueuse.uniqid, - }); - - if (!response.success) { - console.log("返信に失敗:", response.error_code); - } + }, "ソースが見つからない旨"); return; } if (requestUeuse.data[0].replyid === "") { - const response = await client.request("ueuse/create", { + await createUeuse({ text: i18next.t("injusticeFormat"), replyid: ueuse.uniqid, - }); - - if (!response.success) { - console.warn("返信に失敗:", response.error_code); - return; - } - - console.log("返信:", response.uniqid); + }, "形式が異なる旨"); return; } @@ -305,30 +222,19 @@ export default async function miqCommand(ueuse: ueuseModule, args: string[]) { ? sourceUeuse.error_code : "データなし"); - const response = await client.request("ueuse/create", { + await createUeuse({ text: i18next.t("replySourceFailed"), replyid: ueuse.uniqid, - }); - - if (!response.success) { - console.log("返信に失敗:", response.error_code); - } + }, "ソースが見つからない旨"); return; } if (sourceUeuse.data[0].account.userid !== ueuse.account.userid) { - const response = await client.request("ueuse/create", { + await createUeuse({ text: i18next.t("replySourceIsNotSourceUser"), replyid: ueuse.uniqid, - }); - - if (!response.success) { - console.log("返信に失敗:", response.error_code); - return; - } - - console.warn("ソースのユーズと/miq allowのユーザーが一致しない:", response.uniqid); + }, "ソースのユーズと/miq allowのユーザーが一致しない旨"); return; } @@ -345,27 +251,15 @@ export default async function miqCommand(ueuse: ueuseModule, args: string[]) { if (!commandRow || commandRow === "") { console.warn("コマンドが本文から参照できません"); - const response = await client.request("ueuse/create", { + await createUeuse({ text: i18next.t("commandNotFound"), replyid: requestUeuse.data[0].uniqid, - }); + }, "コマンドが見つからない旨"); - if (!response.success) - console.warn("ユーズの作成に失敗しました:", response.error_code); - - { - const response = await client.request("ueuse/create", { - text: i18next.t("injusticeFormat"), - replyid: ueuse.uniqid, - }); - - if (!response.success) { - console.warn("返信に失敗:", response.error_code); - return; - } - - console.log("返信:", response.uniqid); - } + await createUeuse({ + text: i18next.t("injusticeFormat"), + replyid: ueuse.uniqid, + }, "形式が異なる旨"); break; } @@ -382,19 +276,15 @@ export default async function miqCommand(ueuse: ueuseModule, args: string[]) { }); if (!(typeof result === "string")) { - const response = await client.request("ueuse/create", { + await createUeuse({ text: i18next.t("miqGenerateFailed"), replyid: ueuse.uniqid, - }); - - if (!response.success) { - console.warn("返信に失敗:", response.error_code); - } + }, "Make it a Quoteの生成に失敗した旨"); return; } - const response = await client.request("ueuse/create", { + await createUeuse({ text: i18next.t("miqSuccess", { message: ((requestUeuseArgs[2] ?? "") === "color" ? "カラーモードで生成しました。" : "モノクロモードで生成しました。") @@ -404,14 +294,7 @@ export default async function miqCommand(ueuse: ueuseModule, args: string[]) { photo: [result], }, replyid: ueuse.uniqid, - }); - - if (!response.success) { - console.warn("返信に失敗:", response.error_code); - return; - } - - console.log("MiQを生成:", response.uniqid); + }, "Make it a Quote"); } break; diff --git a/src/feature/command/unfollow.ts b/src/feature/command/unfollow.ts index d9f7ed8..2ff6916 100644 --- a/src/feature/command/unfollow.ts +++ b/src/feature/command/unfollow.ts @@ -1,4 +1,4 @@ -import client from "@/lib/client"; +import client, { createUeuse } from "@/lib/client"; import ueuseModule from "better-uwuzu-sdk/types/1.6.8/types/modules/ueuse"; import i18next from "i18next"; @@ -14,15 +14,8 @@ export default async function unfollowCommand(ueuse: ueuseModule) { console.log("フォロー解除:", unfollow.userid); - const notice = await client.request("ueuse/create", { + await createUeuse({ text: i18next.t("unfollowedNotification", { username: ueuse.account.username }), replyid: ueuse.uniqid, - }); - - if (!notice.success) { - console.warn("フォロー解除通知に失敗:", notice.error_code); - return; - } - - console.log("フォロー解除通知:", notice.uniqid); + }, "フォロー解除通知"); } \ No newline at end of file diff --git a/src/feature/earthquakeNotice.ts b/src/feature/earthquakeNotice.ts index 45f37d8..5fcc4bd 100644 --- a/src/feature/earthquakeNotice.ts +++ b/src/feature/earthquakeNotice.ts @@ -1,4 +1,4 @@ -import client from "@/lib/client"; +import { createUeuse } from "@/lib/client"; import config from "@/lib/config"; import initI18n from "@/lib/i18n"; import Memory from "@/lib/memory"; @@ -169,7 +169,7 @@ const processMessage = async (message: any) => { `【${label}】${EOL}${addrs.join("・")}`) .join(EOL.repeat(2)).trim(); - const response = await client.request("ueuse/create", { + await createUeuse({ text: i18next.t("earthquakeNotice", { occuredTime: format(new Date(message.earthquake.time), "yyyy年M月d日 H:mm"), maxScale: scaleMessages[String(message.earthquake.maxScale)], @@ -194,14 +194,7 @@ const processMessage = async (message: any) => { ? "" : EOL + message.comments.freeFormComment + EOL, }), - }); - - if (!response.success) { - console.warn("ユーズの作成に失敗しました:", response.error_code); - break; - } - - console.log("地震発生情報を投稿:", response.uniqid); + }, "地震発生情報"); } break; case 552: @@ -209,19 +202,12 @@ const processMessage = async (message: any) => { console.log("津波予報情報を受信しました"); if (message.cancelled) { - const response = await client.request("ueuse/create", { + await createUeuse({ text: i18next.t("tsunamiCancelNotice", { announceTime: format(new Date(message.issue.time), "yyyy年M月d日 H:mm:ss"), source: message.issue.source ?? "不明", }), - }); - - if (!response.success) { - console.warn("ユーズの作成に失敗しました:", response.error_code); - break; - } - - console.log("津波予報解除情報を投稿:", response.uniqid); + }, "津波予報解除情報"); break; } @@ -252,20 +238,13 @@ const processMessage = async (message: any) => { }) + EOL.repeat(2); } - const response = await client.request("ueuse/create", { + await createUeuse({ text: i18next.t("tsunamiForecastNotice", { announceTime: format(new Date(message.issue.time), "yyyy年M月d日 H:mm:ss"), areasMsg: areasMsg.trim(), source: message.issue.source ?? "不明", }), - }); - - if (!response.success) { - console.warn("ユーズの作成に失敗しました:", response.error_code); - break; - } - - console.log("津波予報情報を投稿:", response.uniqid); + }, "津波予報情報"); } break; case 556: @@ -273,21 +252,14 @@ const processMessage = async (message: any) => { console.log("緊急地震速報(警報)を受信しました"); if (message.cancelled) { - const response = await client.request("ueuse/create", { + await createUeuse({ text: i18next.t("eewCancelNotice", { isTest: message.test ? "⚒️これは**テストです。**" : "🚨これは**テストではありません。**", announceTime: format(new Date(message.issue.time), "yyyy年M月d日 H:mm:ss"), }), - }); - - if (!response.success) { - console.warn("ユーズの作成に失敗しました:", response.error_code); - break; - } - - console.log("緊急地震速報(警報)解除情報を投稿:", response.uniqid); + }, "緊急地震速報(警報)解除情報"); } const kindMessages: Record = { @@ -313,7 +285,7 @@ const processMessage = async (message: any) => { }) + EOL.repeat(2); } - const response = await client.request("ueuse/create", { + await createUeuse({ text: i18next.t("eewNotice", { isTest: message.test ? "⚒️これは**テストです。**" @@ -337,14 +309,7 @@ const processMessage = async (message: any) => { ? EOL.repeat(2) + areasMsg.trim() : "", }), - }); - - if (!response.success) { - console.warn("ユーズの作成に失敗しました:", response.error_code); - break; - } - - console.log("緊急地震速報(警報)情報を投稿:", response.uniqid); + }, "緊急地震速報(警報)情報"); } break; default: diff --git a/src/feature/hnyNotice.ts b/src/feature/hnyNotice.ts index e9036a5..8868313 100644 --- a/src/feature/hnyNotice.ts +++ b/src/feature/hnyNotice.ts @@ -1,4 +1,4 @@ -import client from "@/lib/client"; +import { createUeuse } from "@/lib/client"; import initI18n from "@/lib/i18n"; import { format } from "date-fns"; import i18next from "i18next"; @@ -10,16 +10,11 @@ parentPort?.on("message", async () => { console.log("新年迎春の投稿を行います"); try { - const response = await client.request("ueuse/create", { + await createUeuse({ text: i18next.t("hnyNotice", { year: String(new Date().getFullYear()) }), - }); + }, "新年迎春"); - if (!response.success) { - console.warn("新年迎春投稿に失敗しました:", response.error_code); - process.exit(1); - } - - console.log("新年迎春投稿:", `${response.uniqid} (${format(new Date(), "yyyy/M/d H:mm:ss:SSS")})`); + console.log("新年迎春投稿時刻:", format(new Date(), "yyyy/M/d H:mm:ss:SSS")); process.exit(0); } catch (err: any) { console.error("message" in err diff --git a/src/feature/timeNotice.ts b/src/feature/timeNotice.ts index f3141cf..4ad0bfe 100644 --- a/src/feature/timeNotice.ts +++ b/src/feature/timeNotice.ts @@ -1,4 +1,4 @@ -import client from "@/lib/client"; +import { createUeuse } from "@/lib/client"; import initI18n from "@/lib/i18n"; import { format } from "date-fns"; import i18next from "i18next"; @@ -7,16 +7,10 @@ await initI18n(); console.log("時報の投稿を行います"); try { - const response = await client.request("ueuse/create", { + await createUeuse({ text: i18next.t("timeNotice", { time: format(new Date(), "H:mm") }), - }); + }, "時報"); - if (!response.success) { - console.warn("時報投稿に失敗しました:", response.error_code); - process.exit(1); - } - - console.log("時報投稿:", response.uniqid); process.exit(0); } catch (err: any) { console.error("message" in err diff --git a/src/feature/weatherNotice.ts b/src/feature/weatherNotice.ts index 79412d0..2d69206 100644 --- a/src/feature/weatherNotice.ts +++ b/src/feature/weatherNotice.ts @@ -1,4 +1,5 @@ import client from "@/lib/client"; +import config from "@/lib/config"; import initI18n from "@/lib/i18n"; import Memory from "@/lib/memory"; import i18next from "i18next"; @@ -61,12 +62,27 @@ if (!isMainThread && workerData === "scheduledWeatherNotice") { console.log("天気予報の投稿を行います"); try { - const provisionalUeuse = await client.request("ueuse/create", { - text: i18next.t("weatherProvisional"), - }); + let provisionalUeuse; + let success = false; - if (!provisionalUeuse.success) { - console.error("天気仮投稿に失敗しました:", provisionalUeuse.error_code); + for (let attempt = 1; attempt <= config.ueuse.maxRetries; attempt++) { + provisionalUeuse = await client.request("ueuse/create", { + text: i18next.t("weatherProvisional"), + }); + + if (provisionalUeuse.success) { + success = true; + break; + } + + console.warn(`天気仮投稿に失敗しました (試行 ${attempt}/${config.ueuse.maxRetries}):`, provisionalUeuse.error_code); + if (attempt < config.ueuse.maxRetries) { + await new Promise(resolve => setTimeout(resolve, 1000)); + } + } + + if (!success || !provisionalUeuse?.success) { + console.error("天気仮投稿の全試行に失敗したため、終了します。"); process.exit(1); } @@ -84,8 +100,9 @@ if (!isMainThread && workerData === "scheduledWeatherNotice") { export async function weatherReply(uniqid: string) { // インデックス + const aboutFullLength = 3100; const mem = Memory.memory; - const splitCount = Math.round(3100 / mem.max_length); + const splitCount = Math.round(aboutFullLength / mem.max_length); const total = cityList.length; const chunkSizes = Array(splitCount).fill(0).map((_, i) => Math.floor((total + i) / splitCount) @@ -149,13 +166,29 @@ export async function weatherReply(uniqid: string) { // 分割投稿 for (let i = 0; i < splitCount; i++) { - const replyUeuse = await client.request("ueuse/create", { - text: weatherResults[i].trim(), - replyid: uniqid, - }); + let replyUeuse; + let success = false; - if (!replyUeuse.success) { - console.error("天気返信に失敗しました:", replyUeuse.error_code); + for (let attempt = 1; attempt <= config.ueuse.maxRetries; attempt++) { + replyUeuse = await client.request("ueuse/create", { + text: weatherResults[i].trim(), + replyid: uniqid, + }); + + if (replyUeuse.success) { + success = true; + break; + } + + console.warn(`天気返信に失敗しました (試行 ${attempt}/${config.ueuse.maxRetries}):`, replyUeuse.error_code); + if (attempt < config.ueuse.maxRetries) { + await new Promise(resolve => setTimeout(resolve, config.ueuse.retryInterval)); + } + } + + if (!success || !replyUeuse?.success) { + console.error("天気返信の全試行に失敗したため、終了します。"); + process.exit(1); } console.log("天気返信:", replyUeuse.uniqid); diff --git a/src/lib/client.ts b/src/lib/client.ts index d5bc847..2db59f2 100644 --- a/src/lib/client.ts +++ b/src/lib/client.ts @@ -2,6 +2,8 @@ import uwuzu from "better-uwuzu-sdk"; import config from "@/lib/config"; import Parser from "better-uwuzu-sdk/1.6.8/parser"; import ApiMap from "better-uwuzu-sdk/types/1.6.8/map"; +import Memory from "@/lib/memory"; +import { EOL } from "node:os"; const client = new uwuzu({ origin: config.uwuzu.origin, @@ -9,5 +11,63 @@ const client = new uwuzu({ }); client.token = config.uwuzu.token; +export default client; -export default client; \ No newline at end of file +export const createUeuse = async (data: ApiMap["ueuse/create"]["body"], title: string) => { + const mem = Memory.memory; + const maxLength = mem.max_length; + const excessedMessage = "👉返信に続きがあります。"; + + let remainingText = data.text; + let firstUniqid = ""; + + while (remainingText.length > 0) { + let currentText = ""; + let bodyTextLength = 0; + + if (remainingText.length <= maxLength) { + currentText = remainingText; + bodyTextLength = remainingText.length; + remainingText = ""; + } else { + bodyTextLength = maxLength - excessedMessage.length - 2; + currentText = remainingText.slice(0, bodyTextLength) + EOL.repeat(2) + excessedMessage; + remainingText = remainingText.slice(bodyTextLength); + } + + let postedUniqid = ""; + let success = false; + + for (let attempt = 1; attempt <= config.ueuse.maxRetries; attempt++) { + const response = await client.request("ueuse/create", { + ...data, + text: currentText, + replyid: data.replyid === undefined && firstUniqid !== "" + ? firstUniqid + : data.replyid, + }); + + if (response.success) { + success = true; + postedUniqid = response.uniqid; + break; + } + + console.warn(`${title}の投稿に失敗しました (試行 ${attempt}/${config.ueuse.maxRetries}):`, response.error_code); + if (attempt < config.ueuse.maxRetries) { + await new Promise(resolve => setTimeout(resolve, config.ueuse.retryInterval)); + } + } + + if (!success) { + console.error(`${title}の全試行が失敗したため、処理を中断します。`); + break; + } + + if (firstUniqid === "") { + firstUniqid = postedUniqid; + } + + console.log(`${title}を投稿:`, postedUniqid); + } +} diff --git a/src/lib/config.ts b/src/lib/config.ts index 20ce3d4..76c9c64 100644 --- a/src/lib/config.ts +++ b/src/lib/config.ts @@ -34,6 +34,10 @@ const schema = z.object({ } }), }), + ueuse: z.object({ + maxRetries: z.number().int().positive(), + retryInterval: z.number().positive(), + }), debug: z.boolean().optional(), }); From 2b21401587967ed7922a189311f493cafc91cea1 Mon Sep 17 00:00:00 2001 From: Last2014 Date: Sun, 3 May 2026 14:01:45 +0900 Subject: [PATCH 07/10] =?UTF-8?q?Chg:=20=E6=96=87=E5=AD=97=E6=95=B0?= =?UTF-8?q?=E5=88=B6=E9=99=90=E5=9B=9E=E9=81=BF=E3=82=921=E8=A1=8C?= =?UTF-8?q?=E3=81=9A=E3=81=A4=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/client.ts | 48 ++++++++++++++++++++++++++++++----------------- 1 file changed, 31 insertions(+), 17 deletions(-) diff --git a/src/lib/client.ts b/src/lib/client.ts index 2db59f2..0780bcc 100644 --- a/src/lib/client.ts +++ b/src/lib/client.ts @@ -17,22 +17,39 @@ export const createUeuse = async (data: ApiMap["ueuse/create"]["body"], title: s const mem = Memory.memory; const maxLength = mem.max_length; const excessedMessage = "👉返信に続きがあります。"; - - let remainingText = data.text; + + // 行単位で分割 + let lines = data.text.split(EOL); let firstUniqid = ""; - while (remainingText.length > 0) { + while (lines.length > 0) { let currentText = ""; - let bodyTextLength = 0; + + const limit = maxLength - (excessedMessage.length + EOL.length * 2); - if (remainingText.length <= maxLength) { - currentText = remainingText; - bodyTextLength = remainingText.length; - remainingText = ""; - } else { - bodyTextLength = maxLength - excessedMessage.length - 2; - currentText = remainingText.slice(0, bodyTextLength) + EOL.repeat(2) + excessedMessage; - remainingText = remainingText.slice(bodyTextLength); + while (lines.length > 0) { + const nextLine = lines[0]; + if (nextLine === undefined) break; + + if (nextLine.length > limit && currentText === "") { + const targetLine = lines.shift() || ""; + currentText = targetLine.slice(0, limit); + + lines.unshift(targetLine.slice(limit)); + break; + } + + const potentialText = currentText ? currentText + EOL + nextLine : nextLine; + if (potentialText.length <= limit) { + currentText = potentialText; + lines.shift(); + } else { + break; + } + } + + if (lines.length > 0) { + currentText += EOL.repeat(2) + excessedMessage; } let postedUniqid = ""; @@ -64,10 +81,7 @@ export const createUeuse = async (data: ApiMap["ueuse/create"]["body"], title: s break; } - if (firstUniqid === "") { - firstUniqid = postedUniqid; - } - + if (firstUniqid === "") firstUniqid = postedUniqid; console.log(`${title}を投稿:`, postedUniqid); } -} +} \ No newline at end of file From 6fd92973f80a44f28c451636594b3f140c9d81b2 Mon Sep 17 00:00:00 2001 From: Last2014 Date: Sun, 3 May 2026 14:09:35 +0900 Subject: [PATCH 08/10] =?UTF-8?q?Chg:=20=E5=9C=B0=E9=9C=87=E7=99=BA?= =?UTF-8?q?=E7=94=9F=E6=83=85=E5=A0=B1=E3=81=AE=E6=99=82=E5=88=BB=E3=81=AB?= =?UTF-8?q?=E9=A0=83=E3=82=92=E8=BF=BD=E5=8A=A0=20/=20Feat:=20=E3=83=A6?= =?UTF-8?q?=E3=83=BC=E3=82=BA=E3=81=8C=E5=88=86=E5=89=B2=E3=81=AE=E4=BD=95?= =?UTF-8?q?=E5=80=8B=E7=9B=AE=E3=81=AE=E3=83=A6=E3=83=BC=E3=82=BA=E3=81=8B?= =?UTF-8?q?=E3=82=92=E3=82=B3=E3=83=B3=E3=82=BD=E3=83=BC=E3=83=AB=E3=81=AB?= =?UTF-8?q?=E8=A1=A8=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- locales/ja.yaml | 6 +++++- src/lib/client.ts | 12 ++++++++---- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/locales/ja.yaml b/locales/ja.yaml index 813145d..89ab965 100644 --- a/locales/ja.yaml +++ b/locales/ja.yaml @@ -10,7 +10,7 @@ weatherReply: | 降水確率: {{ chanceOfRain }} earthquakeNotice: | ### ==地震発生== - ⏰時刻: {{ occuredTime }} + ⏰時刻: {{ occuredTime }}頃 🫨最大震度: {{ maxScale }} 📍震源地: {{ epicenter }} 💪マグニチュード: {{ magnitude }} @@ -50,10 +50,14 @@ eewNotice: | 📍震源地: {{ epicenter }} 💪マグニチュード: {{ magnitude }} 🪨深さ: {{ depth }}{{ areas }} + + 🔬情報源: P2P地震速報 eewCancelNotice: | ### ==緊急地震速報(警報)**解除**== {{ isTest }} ⏰発表時刻: {{ announceTime }} + + 🔬情報源: P2P地震速報 hnyNotice: | あけましておめでとうございます。今年は、{{ year }}年です。 commandNotFound: | diff --git a/src/lib/client.ts b/src/lib/client.ts index 0780bcc..0024124 100644 --- a/src/lib/client.ts +++ b/src/lib/client.ts @@ -18,18 +18,20 @@ export const createUeuse = async (data: ApiMap["ueuse/create"]["body"], title: s const maxLength = mem.max_length; const excessedMessage = "👉返信に続きがあります。"; - // 行単位で分割 let lines = data.text.split(EOL); let firstUniqid = ""; + let count = 0; while (lines.length > 0) { + count++; let currentText = ""; const limit = maxLength - (excessedMessage.length + EOL.length * 2); while (lines.length > 0) { const nextLine = lines[0]; - if (nextLine === undefined) break; + if (nextLine === undefined) + break; if (nextLine.length > limit && currentText === "") { const targetLine = lines.shift() || ""; @@ -81,7 +83,9 @@ export const createUeuse = async (data: ApiMap["ueuse/create"]["body"], title: s break; } - if (firstUniqid === "") firstUniqid = postedUniqid; - console.log(`${title}を投稿:`, postedUniqid); + if (firstUniqid === "") + firstUniqid = postedUniqid; + + console.log(`${title}を投稿(${count}):`, postedUniqid); } } \ No newline at end of file From 95aa0bdb45571a6e36039610eee5b464b4340ba0 Mon Sep 17 00:00:00 2001 From: Last2014 Date: Sun, 3 May 2026 14:23:49 +0900 Subject: [PATCH 09/10] =?UTF-8?q?Feat:=20=E5=9C=B0=E9=9C=87=E7=99=BA?= =?UTF-8?q?=E7=94=9F=E6=83=85=E5=A0=B1=E3=81=AE=E5=9C=B0=E5=9F=9F=E3=82=92?= =?UTF-8?q?=E9=83=BD=E9=81=93=E5=BA=9C=E7=9C=8C=E3=81=A7=E3=82=B0=E3=83=AB?= =?UTF-8?q?=E3=83=BC=E3=83=97=E5=8C=96=E3=81=99=E3=82=8B=E6=A9=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/feature/earthquakeNotice.ts | 34 +++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/src/feature/earthquakeNotice.ts b/src/feature/earthquakeNotice.ts index 5fcc4bd..e9572a4 100644 --- a/src/feature/earthquakeNotice.ts +++ b/src/feature/earthquakeNotice.ts @@ -142,32 +142,34 @@ const processMessage = async (message: any) => { "Potential": "🚨この地震によって**一般的に**この規模では津波の可能性があると考えられています。", } - let points: Record = {}; + const grouped: Record }> = {}; + for (const point of message.points) { - const scaleMsg = scaleMessages[String(point.scale)]; - if (!scaleMsg) - continue; - - points[scaleMsg]?.push(point); - } - - const grouped: Record = {}; - for (const point of message.points) { - const { addr, scale } = point; + const { addr, scale, pref } = point; const label = scaleMessages[String(scale)] ?? "不明"; if (!grouped[label]) { - grouped[label] = { scale, addrs: [] }; + grouped[label] = { scale, prefs: {} }; } - grouped[label].addrs.push(addr); + if (!grouped[label].prefs[pref]) { + grouped[label].prefs[pref] = []; + } + + grouped[label].prefs[pref].push(addr); } const pointsMsg = Object.entries(grouped) .sort((a, b) => b[1].scale - a[1].scale) - .map(([label, { addrs }]) => - `【${label}】${EOL}${addrs.join("・")}`) - .join(EOL.repeat(2)).trim(); + .map(([label, { prefs }]) => { + const prefLines = Object.entries(prefs) + .map(([pref, addrs]) => `${pref}: ${addrs.join("・")}`) + .join(EOL); + + return `【${label}】${EOL}${prefLines}`; + }) + .join(EOL.repeat(2)) + .trim(); await createUeuse({ text: i18next.t("earthquakeNotice", { From 251fc0f403b5e2054f455f898f28c79ce25fbec4 Mon Sep 17 00:00:00 2001 From: Last2014 Date: Sun, 3 May 2026 14:28:01 +0900 Subject: [PATCH 10/10] 2026.4.0-beta.0 --- CHANGELOG.md | 16 ++++++++++++++++ package.json | 2 +- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 759d015..733a69e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,19 @@ +# 2026.4.0-beta.0 +- Feat: 地震発生情報の地域を都道府県でグループ化する機能 +- Feat: ユーズの再試行 +- Feat: ユーズの文字数制限回避 +- Feat: ユーズ送信関数 +- Feat: コマンドを並列処理 +- Feat: 地震情報のWebSocket再接続 +- Feat: 天気予報の分割数を自動的に計算する機能 +- Feat: 最大震度の要求を設定できる機能 +- Feat: 最大震度が不明な場合に投稿するかどうかを設定できる機能 +- Chg: 地震発生情報の時刻に頃を追加 +- Chg: weatherNotice.tsのマジックナンバーに命名 +- Fix: 震度0の地震情報に対応していない問題 +- Fix: コマンド処理済みのユーズのid除外が正しく動作しない問題 +- Fix: miqコマンドのコンソール出力に値が存在しない問題 + # 2026.4.0-alpha.2 - Feat: 地震情報のid除外 - Feat: 新年迎春 diff --git a/package.json b/package.json index b30f999..aa3946b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "notice-uwuzu", - "version": "26.4.0-alpha.2", + "version": "26.4.0-beta.0", "type": "module", "main": "dist/index.js", "scripts": {