diff --git a/README.md b/README.md index af5fb62..3ccd6c5 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,8 @@ uwuzu v1.6.7以上で利用可能です。 ## 機能 - 時報 - 毎時0分に「h:00になりました。」と投稿します。 + 毎時0分に「h:00になりました。」と投稿します。 + 4:00は某画像で投稿します。 - 天気予報 [天気予報 API(livedoor 天気互換)](https://weather.tsukumijima.net/)を利用して、毎日7:00及び18:00に天気予報を投稿します。 18:00は明日の天気予報を投稿します。 diff --git a/locales/ja.yaml b/locales/ja.yaml index 21e0470..9c38b29 100644 --- a/locales/ja.yaml +++ b/locales/ja.yaml @@ -1,68 +1,3 @@ -timeNotice: "{{ time }}になりました。" -weatherProvisional: | - {{ day }}の天気 - ※タイムラインが埋まるため返信に記載しています。 -weatherReply: | - 【{{ city }}】 - 天気: {{ weather }} - 最高気温: {{ maxTemp }} - 最低気温: {{ minTemp }} - 降水確率: {{ chanceOfRain }} -earthquakeNotice: | - ### {{ type }} - ⏰時刻: {{ occuredTime }}頃 - 🫨最大震度: {{ maxScale }} - 📍震源地: {{ epicenter }} - 💪マグニチュード: {{ magnitude }} - 🪨深さ: {{ depth }} - {{ domesticTsunami }} - {{ foreignTsunami }}{{ points }} - {{ comment }} - 🔬情報源: P2P地震速報 - {{ source }} -earthquakeImageGenerated: | - 地震の震度分布画像を生成しました。 - 地震情報ユーズ: {{ url }} -tsunamiAreaMsg: | - 【{{ name }}】{{ immediate }} - 🏷️種別: {{ grade }} - ⏳第1波到達予想時刻: {{ arrivalTime }} - 🌊第1波の状態: {{ condition }} - 🗼予想される津波の高さ: {{ maxHeight }} -tsunamiForecastNotice: | - ### 津波予報**発表** - ⏰発表時刻: {{ announceTime }} - - {{ areasMsg }} - - 🔬情報源: P2P地震速報 - {{ source }} -tsunamiCancelNotice: | - ### 津波予報**解除** - ⏰発表時刻: {{ announceTime }} - 🔬情報源: P2P地震速報 - {{ source }} -eewAreaMsg: | - 【{{ name }}】 - 🫨最大予測震度: {{ maxScale }} - ⏰到達予想時刻: {{ arrivalTime }} - {{ kind }} -eewNotice: | - ### ***緊急地震速報(警報)*** - {{ isTest }}{{isAssume}} - ⏰発表時刻: {{ announceTime }} - ⏰地震発生時刻: {{ occuredTime }} - ⏰地震発現時刻: {{ arrivalTime }} - 📍震源地: {{ epicenter }} - 💪マグニチュード: {{ magnitude }} - 🪨深さ: {{ depth }}{{ areas }} - - 🔬情報源: P2P地震速報 -eewCancelNotice: | - ### 緊急地震速報(警報)**解除** - {{ isTest }} - ⏰発表時刻: {{ announceTime }} - - 🔬情報源: P2P地震速報 -hnyNotice: | - あけましておめでとうございます。今年は、{{ year }}年です。 commandNotFound: | コマンドが本文から参照できませんでした。 Botでは、このアカウントに対してのメンション部分を取り除きます。 diff --git a/package.json b/package.json index 35d8a2d..340e4c1 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "email": "info@last2014.com", "url": "https://about.last2014.com" }, - "packageManager": "pnpm@10.33.0", + "packageManager": "pnpm@11.1.3+sha512.c85357fe17ca12dd23dd7071822666dfd7e3cb76fe214e3370b5ea2fb34f2a231185509b63e717f3cd0acb38dd3f8d82bcd5e8172400ae678b70ea4fbed0896d", "dependencies": { "@types/node": "^25.5.2", "@types/ws": "^8.18.1", diff --git a/src/feature/earthquake/index.ts b/src/feature/earthquake/index.ts index 2074b8a..615da79 100644 --- a/src/feature/earthquake/index.ts +++ b/src/feature/earthquake/index.ts @@ -1,16 +1,12 @@ import { createUeuse } from "@/lib/client"; import config from "@/lib/config"; -import initI18n from "@/lib/i18n"; import Memory from "@/lib/memory"; import { format } from "date-fns"; -import i18next from "i18next"; import { readFileSync } from "node:fs"; import { EOL } from "node:os"; import { WebSocket } from "ws"; import generateImage from "@/feature/earthquake/generateImage"; -await initI18n(); - if (config.earthquake?.useHistoryData) { console.log("過去の地震情報を配信します"); const history = JSON.parse(readFileSync(`${import.meta.dirname}/../../260420.json`, "utf-8")); @@ -169,32 +165,32 @@ const processMessage = async (message: any) => { .join(EOL.repeat(2)) .trim(); + const earthquakeNoticeText = []; + earthquakeNoticeText.push(`### ${typeMessage[message.issue.type] ?? "地震情報"}`); + earthquakeNoticeText.push(`⏰時刻: ${format(new Date(message.earthquake.time), "yyyy年M月d日 H:mm")}頃`); + earthquakeNoticeText.push(`🫨最大震度: scaleMessages[String(message.earthquake.maxScale)]`); + earthquakeNoticeText.push(`📍震源地: ${message.earthquake.hypocenter.name === "" + ? "不明" + : message.earthquake.hypocenter.name}`); + earthquakeNoticeText.push(`💪マグニチュード: ${message.earthquake.hypocenter.magnitude === -1 + ? "不明" + : `M${message.earthquake.hypocenter.magnitude.toFixed(1)}`}`); + earthquakeNoticeText.push(`🪨深さ: ${message.earthquake.hypocenter.depth === 0 + ? "ごく浅い" + : message.earthquake.hypocenter.depth === -1 + ? "不明" + : `${message.earthquake.hypocenter.depth}km`}`); + earthquakeNoticeText.push(domesticTsunamiMessages[(message.earthquake.domesticTsunami ?? "Unknown")]); + earthquakeNoticeText.push(foreignTsunamiMessages[(message.earthquake.foreignTsunami ?? "Unknown")]); + earthquakeNoticeText.push(...(pointsMsg === "" + ? [] + : [EOL + pointsMsg])); + earthquakeNoticeText.push(...(message.comments.freeFormComment === "" + ? [] + : [message.comments.freeFormComment])); + earthquakeNoticeText.push(`🔬情報源: P2P地震速報 - ${message.issue.source ?? "不明"}`); const earthquakeUeuses = await createUeuse({ - text: i18next.t("earthquakeNotice", { - type: typeMessage[message.issue.type] ?? "地震情報", - occuredTime: format(new Date(message.earthquake.time), "yyyy年M月d日 H:mm"), - maxScale: scaleMessages[String(message.earthquake.maxScale)], - epicenter: message.earthquake.hypocenter.name === "" - ? "不明" - : message.earthquake.hypocenter.name, - magnitude: message.earthquake.hypocenter.magnitude === -1 - ? "不明" - : `M${message.earthquake.hypocenter.magnitude.toFixed(1)}`, - depth: message.earthquake.hypocenter.depth === 0 - ? "ごく浅い" - : (message.earthquake.hypocenter.depth === -1 - ? "不明" - : `${message.earthquake.hypocenter.depth}km`), - domesticTsunami: domesticTsunamiMessages[(message.earthquake.domesticTsunami ?? "Unknown")], - foreignTsunami: foreignTsunamiMessages[(message.earthquake.foreignTsunami ?? "Unknown")], - points: pointsMsg === "" - ? "" - : EOL.repeat(2) + pointsMsg, - source: message.issue.source ?? "不明", - comment: message.comments.freeFormComment === "" - ? "" - : EOL + message.comments.freeFormComment + EOL, - }), + text: earthquakeNoticeText.join(EOL), }, "地震発生情報"); try { @@ -203,12 +199,15 @@ const processMessage = async (message: any) => { if (typeof image === "string") { throw "情報が不足しているため、地震の画像生成ができませんでした"; } else { + const infoUeuseURL = earthquakeUeuses[0]?.uniqid + ? `${config.uwuzu.origin}/!${earthquakeUeuses[0].uniqid}` + : "不明"; + + const earthquakeImageText = []; + earthquakeImageText.push("地震の震度分布画像を生成しました。"); + earthquakeImageText.push(`地震情報ユーズ: ${infoUeuseURL}`); await createUeuse({ - text: i18next.t("earthquakeImageGenerated", { - url: earthquakeUeuses[0]?.uniqid - ? `${config.uwuzu.origin}/!${earthquakeUeuses[0].uniqid}` - : "不明", - }), + text: earthquakeImageText.join(EOL), media: { photo: [ image.toString("base64"), @@ -226,12 +225,14 @@ const processMessage = async (message: any) => { console.log("津波予報情報を受信しました"); if (message.cancelled) { + const tsunamiCancelledNoticeText = []; + tsunamiCancelledNoticeText.push("### 津波予報**解除**"); + tsunamiCancelledNoticeText.push(`⏰発表時刻: ${format(new Date(message.issue.time), "yyyy年M月d日 H:mm:ss")}`); + tsunamiCancelledNoticeText.push(`🔬情報源: P2P地震速報 - ${message.issue.source ?? "不明"}`); await createUeuse({ - text: i18next.t("tsunamiCancelNotice", { - announceTime: format(new Date(message.issue.time), "yyyy年M月d日 H:mm:ss"), - source: message.issue.source ?? "不明", - }), + text: tsunamiCancelledNoticeText.join(EOL), }, "津波予報解除情報"); + break; } @@ -244,30 +245,31 @@ const processMessage = async (message: any) => { let areasMsg = ""; for (const area of message.areas) { - areasMsg += i18next.t("tsunamiAreaMsg", { - name: area.name, - immediate: area.immediate - ? EOL + "🚨***直ちに津波が来襲すると予想されています。***" - : "", - grade: gradeMessages[area.grade], - arrivalTime: format(new Date(area.firstHeight.arrivalTime), "yyyy年M月d日 H:mm"), - condition: area.firstHeight.condition - ? `${area.firstHeight.condition}されています` - : "不明", - maxHeight: area.maxHeight.value === 0.2 - ? "0.2m未満" - : (area.maxHeight.value - ? `${area.maxHeight.value}m` - : area.maxHeight.description), - }) + EOL.repeat(2); + const tsunamiAreaText = []; + tsunamiAreaText.push(`【${area.name}】`); + tsunamiAreaText.push(...(area.immediate + ? ["🚨***直ちに津波が来襲すると予想されています。***"] + : [])); + tsunamiAreaText.push(`🏷️種別: ${gradeMessages[area.grade]}`); + tsunamiAreaText.push(`⏳第1波到達予想時刻: ${format(new Date(area.firstHeight.arrivalTime), "yyyy年M月d日 H:mm")}`); + tsunamiAreaText.push(`🌊第1波の状態: ${area.firstHeight.condition + ? `${area.firstHeight.condition}されています` + : "不明"}`); + tsunamiAreaText.push(`🗼予想される津波の高さ: ${area.maxHeight.value === 0.2 + ? "0.2m未満" + : (area.maxHeight.value + ? `${area.maxHeight.value}m` + : area.maxHeight.description)}`); + areasMsg += tsunamiAreaText.join(EOL) + EOL.repeat(2); } + const tsunamiNoticeText = []; + tsunamiNoticeText.push("### 津波予報**発表**"); + tsunamiNoticeText.push(`⏰発表時刻: ${format(new Date(message.issue.time), "yyyy年M月d日 H:mm:ss")}`); + tsunamiNoticeText.push(EOL + areasMsg.trim() + EOL); + tsunamiNoticeText.push(`🔬情報源: P2P地震速報 - message.issue.source ?? "不明"`); 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 ?? "不明", - }), + text: tsunamiNoticeText.join(EOL), }, "津波予報情報"); } break; @@ -276,13 +278,15 @@ const processMessage = async (message: any) => { console.log("緊急地震速報(警報)を受信しました"); if (message.cancelled) { + const eewCancelledNotice = []; + eewCancelledNotice.push("### 緊急地震速報(警報)**解除**"); + eewCancelledNotice.push(message.test + ? "⚒️これは**テストです。**" + : "🚨これは**テストではありません。**"); + eewCancelledNotice.push(`⏰発表時刻: ${format(new Date(message.issue.time), "yyyy年M月d日 H:mm:ss")}`); + eewCancelledNotice.push(EOL + "🔬情報源: P2P地震速報"); await createUeuse({ - text: i18next.t("eewCancelNotice", { - isTest: message.test - ? "⚒️これは**テストです。**" - : "🚨これは**テストではありません。**", - announceTime: format(new Date(message.issue.time), "yyyy年M月d日 H:mm:ss"), - }), + text: eewCancelledNotice.join(EOL), }, "緊急地震速報(警報)解除情報"); } @@ -294,45 +298,49 @@ const processMessage = async (message: any) => { let areasMsg = ""; for (const area of message.areas) { - areasMsg += i18next.t("eewAreaMsg", { - name: area.name, - maxScale: scaleMessages[String(Math.floor(area.scaleFrom))] + - (area.scaleTo === 99 - ? "程度以上" - : area.scaleFrom !== area.scaleTo - ? `から${scaleMessages[String(Math.floor(area.scaleTo))]}` - : ""), - kind: kindMessages[area.kindCode] ?? "😕主要動の***到達予想は***ありません。", - arrivalTime: typeof area.arrivalTime === "string" && area.arrivalTime !== "" - ? format(new Date(area.arrivalTime), "yyyy年M月d日 H:mm:ss") - : "不明", - }) + EOL.repeat(2); + const eewAreaText = []; + eewAreaText.push(`【${area.name}】`); + eewAreaText.push(`🫨最大予測震度: ${scaleMessages[String(Math.floor(area.scaleFrom))] + + (area.scaleTo === 99 + ? "程度以上" + : area.scaleFrom !== area.scaleTo + ? `から${scaleMessages[String(Math.floor(area.scaleTo))]}` + : "")}`); + eewAreaText.push(`⏰到達予想時刻: ${typeof area.arrivalTime === "string" && area.arrivalTime !== "" + ? format(new Date(area.arrivalTime), "yyyy年M月d日 H:mm:ss") + : "不明"}`); + eewAreaText.push(kindMessages[area.kindCode] ?? "😕主要動の***到達予想は***ありません。"); + + areasMsg += eewAreaText.join(EOL) + EOL.repeat(2); } + const eewNoticeText = []; + eewNoticeText.push("### ***緊急地震速報(警報)***"); + eewNoticeText.push(message.test + ? "⚒️これは**テストです。**" + : "🚨これは**テストではありません。**"); + eewNoticeText.push(...(message.earthquake.condition === "仮定震源要素" + ? ["❓これは、仮定震源要素です。そのため、震源に関する情報が保証できません。"] + : [])); + eewNoticeText.push(`⏰発表時刻: ${format(new Date(message.issue.time), "yyyy年M月d日 H:mm:ss")}`); + eewNoticeText.push(`⏰地震発生時刻: ${format(new Date(message.earthquake.originTime), "yyyy年M月d日 H:mm:ss")}`); + eewNoticeText.push(`⏰地震発現時刻: ${format(new Date(message.earthquake.arrivalTime), "yyyy年M月d日 H:mm:ss")}`); + eewNoticeText.push(`📍震源地: ${message.earthquake.hypocenter.name ?? "不明"}`); + eewNoticeText.push(`💪マグニチュード: ${message.earthquake.hypocenter.magnitude === undefined || + message.earthquake.hypocenter.magnitude === -1 + ? "不明" + : `M${message.earthquake.hypocenter.magnitude.toFixed(1)}`}`); + eewNoticeText.push(`🪨深さ: ${message.earthquake.hypocenter.depth === undefined || + message.earthquake.hypocenter.depth === -1 + ? "不明" + : `${Math.floor(message.earthquake.hypocenter.depth)}km`}`); + eewNoticeText.push(...(areasMsg !== "" + ? [EOL + areasMsg.trim()] + : [])); + eewNoticeText.push(EOL + "🔬情報源: P2P地震速報"); + await createUeuse({ - text: i18next.t("eewNotice", { - isTest: message.test - ? "⚒️これは**テストです。**" - : "🚨これは**テストではありません。**", - announceTime: format(new Date(message.issue.time), "yyyy年M月d日 H:mm:ss"), - occuredTime: format(new Date(message.earthquake.originTime), "yyyy年M月d日 H:mm:ss"), - arrivalTime: format(new Date(message.earthquake.arrivalTime), "yyyy年M月d日 H:mm:ss"), - isAssume: message.earthquake.condition === "仮定震源要素" - ? `${EOL}❓これは、仮定震源要素です。そのため、震源に関する情報が保証できません。` - : "", - epicenter: message.earthquake.hypocenter.name ?? "不明", - depth: message.earthquake.hypocenter.depth === undefined || - message.earthquake.hypocenter.depth === -1 - ? "不明" - : `${Math.floor(message.earthquake.hypocenter.depth)}km`, - magnitude: message.earthquake.hypocenter.magnitude === undefined || - message.earthquake.hypocenter.magnitude === -1 - ? "不明" - : `M${message.earthquake.hypocenter.magnitude.toFixed(1)}`, - areas: areasMsg !== "" - ? EOL.repeat(2) + areasMsg.trim() - : "", - }), + text: eewNoticeText.join(EOL), }, "緊急地震速報(警報)情報"); } break; diff --git a/src/feature/hnyNotice.ts b/src/feature/hnyNotice.ts index cba1df7..0ab74d6 100644 --- a/src/feature/hnyNotice.ts +++ b/src/feature/hnyNotice.ts @@ -1,15 +1,11 @@ import { createUeuse } from "@/lib/client"; -import initI18n from "@/lib/i18n"; import { format } from "date-fns"; -import i18next from "i18next"; - -await initI18n(); console.log("新年迎春の投稿を行います"); try { await createUeuse({ - text: i18next.t("hnyNotice", { year: String(new Date().getFullYear()) }), + text: `あけましておめでとうございます。今年は、${new Date().getFullYear()}年です。`, }, "新年迎春"); console.log("新年迎春投稿時刻:", format(new Date(), "yyyy/M/d H:mm:ss:SSS")); diff --git a/src/feature/time/asayozi.png b/src/feature/time/asayozi.png new file mode 100644 index 0000000..16a0f99 Binary files /dev/null and b/src/feature/time/asayozi.png differ diff --git a/src/feature/time/index.ts b/src/feature/time/index.ts new file mode 100644 index 0000000..5641e31 --- /dev/null +++ b/src/feature/time/index.ts @@ -0,0 +1,28 @@ +import { createUeuse } from "@/lib/client"; +import { format } from "date-fns"; +import { readFileSync } from "node:fs"; + +console.log("時報の投稿を行います"); + +try { + if (new Date().getHours() !== 4) { + await createUeuse({ + text: `${format(new Date(), "H:mm")}になりました。`, + }, "時報"); + } else { + const asayoziImg = readFileSync(`${import.meta.dirname}/asayozi.png`); + await createUeuse({ + text: "朝四時に何してるんだい?", + media: { + photo: [asayoziImg.toString("base64")], + }, + }, "時報(午前4時)"); + } + + process.exit(0); +} catch (err: any) { + console.error("message" in err + ? err.message + : err); + process.exit(1); +} \ No newline at end of file diff --git a/src/feature/timeNotice.ts b/src/feature/timeNotice.ts deleted file mode 100644 index 9a82b21..0000000 --- a/src/feature/timeNotice.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { createUeuse } from "@/lib/client"; -import initI18n from "@/lib/i18n"; -import { format } from "date-fns"; -import i18next from "i18next"; - -await initI18n(); - -console.log("時報の投稿を行います"); - -try { - await createUeuse({ - text: i18next.t("timeNotice", { time: format(new Date(), "H:mm") }), - }, "時報"); - - process.exit(0); -} catch (err: any) { - console.error("message" in err - ? err.message - : err); - process.exit(1); -} \ No newline at end of file diff --git a/src/feature/weatherNotice.ts b/src/feature/weatherNotice.ts index 1eb0233..73f171b 100644 --- a/src/feature/weatherNotice.ts +++ b/src/feature/weatherNotice.ts @@ -1,8 +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"; import { isMainThread, workerData } from "node:worker_threads"; @@ -62,8 +60,6 @@ if ( typeof workerData === "string" && workerData.startsWith("scheduledWeatherNotice") ) { - await initI18n(); - console.log("天気予報の投稿を行います"); try { @@ -71,12 +67,13 @@ if ( let success = false; for (let attempt = 1; attempt <= config.ueuse.maxRetries; attempt++) { + const provisionalText = []; + provisionalText.push(`${workerData.endsWith("Tomorrow") + ? "明日" + : "今日"}の天気`); + provisionalText.push("※タイムラインが埋まるため返信に記載しています。"); provisionalUeuse = await client.request("ueuse/create", { - text: i18next.t("weatherProvisional", { - day: workerData.endsWith("Tomorrow") - ? "明日" - : "今日", - }), + text: provisionalText.join(EOL), }); if (provisionalUeuse.success) { @@ -151,27 +148,21 @@ export async function weatherReply(uniqid: string, isTomorrow: boolean) { : data.forecasts[0]; // 天気 - const weather = itDay.telop ?? "取得できませんでした"; - const maxTemp = itDay.temperature.max.celsius + const areaText = []; + areaText.push(`【${data.location.city}】`); + areaText.push(`⛅天気: ${itDay.telop ?? "取得できませんでした"}`); + areaText.push(`🔆最高気温: ${itDay.temperature.max.celsius ? `${itDay.temperature.max.celsius}℃` - : "取得できませんでした"; - const minTemp = itDay.temperature.min.celsius + : "取得できませんでした"}`); + areaText.push(`🔅最低気温: ${itDay.temperature.min.celsius ? `${itDay.temperature.min.celsius}℃` - : "取得できませんでした"; - const chanceOfRain = ( - itDay.chanceOfRain.T06_12 !== null && - itDay.chanceOfRain.T06_12 !== "--%" - ) - ? itDay.chanceOfRain.T06_12 - : "取得できませんでした"; - - weatherResults[chunkIndex] += `${i18next.t("weatherReply", { - city: data.location.city, - weather, - maxTemp, - minTemp, - chanceOfRain, - })}${EOL.repeat(2)}`; + : "取得できませんでした"}`); + areaText.push(`☔降水確率: ${ + itDay.chanceOfRain.T06_12 !== null && + itDay.chanceOfRain.T06_12 !== "--%" + ? itDay.chanceOfRain.T06_12 + : "取得できませんでした"}`); + weatherResults[chunkIndex] += areaText.join(EOL) + EOL.repeat(2); } } diff --git a/src/index.ts b/src/index.ts index 4d0846d..4880cfe 100644 --- a/src/index.ts +++ b/src/index.ts @@ -32,7 +32,7 @@ try { try { schedule("0 * * * *", async () => { - new Worker(`${import.meta.dirname}/feature/timeNotice.js`); + new Worker(`${import.meta.dirname}/feature/time/index.js`); }); schedule("0 7 * * *", async () => {