Del: tsxの開発環境 / Chg: typescriptを5.9.3にダウングレード / Feat: 緊急地震速報の最大予測震度の上限に99(〜程度以上)を実装 / Chg: 緊急地震速報の最大予測震度で上限と下限が一致する場合に"から〜"を投稿内容に含めない / Feat: worker_threadsによる並列処理 / Del: 各地震情報の情報源の信頼できるかどうかの内容を削除 / Chg: 津波予報情報の各時刻情報を実際に取得できる値まで削減(例: 2:03:00 > 2:03) / Fix: フォロー・フォロー解除に失敗した際にreturnできていない問題 / Feat: デバッグ用とで過去の地震情報を読み込み10秒おきに投稿する機能

This commit is contained in:
2026-04-28 16:44:41 +09:00
parent ecbfc828d6
commit ebc770ab21
14 changed files with 25176 additions and 590 deletions
+24841
View File
File diff suppressed because it is too large Load Diff
+6 -1
View File
@@ -21,4 +21,9 @@ uwuzu v1.6.3以上で利用可能です。
P2P地震情報の接続先を本番環境から[テスト用サンドボックスのエンドポイント](https://www.p2pquake.net/develop/json_api_v2/)に変更します。
そのため、最新の情報は配信されません。
- i18nextのdebugを有効化
[i18nextでのdebug](https://www.i18next.com/overview/configuration-options#logging)が有効化されます。
[i18nextでのdebug](https://www.i18next.com/overview/configuration-options#logging)が有効化されます。
## 260420.jsonについて
このデータは2026年4月20日に三陸で発生した地震の後21件を記録したものです。
P2P地震情報APIのhistoryで取得しました。
検証などにご利用ください。
+4 -1
View File
@@ -2,6 +2,9 @@ command:
interval: 10
weather:
splits: 4
earthquake:
useHistoryData: false
uwuzu:
token: API_TOKEN
origin: https://uwuzu.example.com
origin: https://uwuzu.example.com
debug: false
+1 -1
View File
@@ -38,7 +38,7 @@ tsunamiCancelNotice: |
🔬情報源: P2P地震速報 - {{ source }}
eewAreaMsg: |
【{{ name }}】
🫨最大予測震度: {{ scaleFrom }}から{{ scaleTo }}
🫨最大予測震度: {{ maxScale }}
⏰到達予想時刻: {{ arrivalTime }}
{{ kind }}
eewNotice: |
+2 -6
View File
@@ -5,8 +5,7 @@
"main": "dist/index.js",
"scripts": {
"start": "node .",
"build": "tsc & tsc-alias",
"dev": "tsx --watch src/index.ts"
"build": "tsc && tsc-alias"
},
"author": {
"name": "Last2014",
@@ -25,13 +24,10 @@
"node-cron": "^4.2.1",
"os": "^0.1.2",
"tsc-alias": "^1.8.16",
"typescript": "^6.0.2",
"typescript": "^5.9.3",
"util": "^0.12.5",
"ws": "^8.20.0",
"yaml": "^2.8.3",
"zod": "^4.3.6"
},
"devDependencies": {
"tsx": "^4.21.0"
}
}
+5 -296
View File
@@ -25,7 +25,7 @@ importers:
version: 0.0.1-security
i18next:
specifier: ^26.0.3
version: 26.0.3(typescript@6.0.2)
version: 26.0.3(typescript@5.9.3)
miq:
specifier: git+https://gitea.last2014.com/last2014/miq.git#1.0.1
version: git+https://gitea.last2014.com/last2014/miq.git#5eb4a2e01c6379b185d44b02c483e7572c29eb6d
@@ -37,8 +37,8 @@ importers:
specifier: ^1.8.16
version: 1.8.16
typescript:
specifier: ^6.0.2
version: 6.0.2
specifier: ^5.9.3
version: 5.9.3
util:
specifier: ^0.12.5
version: 0.12.5
@@ -51,10 +51,6 @@ importers:
zod:
specifier: ^4.3.6
version: 4.3.6
devDependencies:
tsx:
specifier: ^4.21.0
version: 4.21.0
packages:
@@ -65,162 +61,6 @@ packages:
'@emnapi/runtime@1.9.2':
resolution: {integrity: sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==}
'@esbuild/aix-ppc64@0.27.7':
resolution: {integrity: sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==}
engines: {node: '>=18'}
cpu: [ppc64]
os: [aix]
'@esbuild/android-arm64@0.27.7':
resolution: {integrity: sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==}
engines: {node: '>=18'}
cpu: [arm64]
os: [android]
'@esbuild/android-arm@0.27.7':
resolution: {integrity: sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==}
engines: {node: '>=18'}
cpu: [arm]
os: [android]
'@esbuild/android-x64@0.27.7':
resolution: {integrity: sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==}
engines: {node: '>=18'}
cpu: [x64]
os: [android]
'@esbuild/darwin-arm64@0.27.7':
resolution: {integrity: sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==}
engines: {node: '>=18'}
cpu: [arm64]
os: [darwin]
'@esbuild/darwin-x64@0.27.7':
resolution: {integrity: sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==}
engines: {node: '>=18'}
cpu: [x64]
os: [darwin]
'@esbuild/freebsd-arm64@0.27.7':
resolution: {integrity: sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==}
engines: {node: '>=18'}
cpu: [arm64]
os: [freebsd]
'@esbuild/freebsd-x64@0.27.7':
resolution: {integrity: sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==}
engines: {node: '>=18'}
cpu: [x64]
os: [freebsd]
'@esbuild/linux-arm64@0.27.7':
resolution: {integrity: sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==}
engines: {node: '>=18'}
cpu: [arm64]
os: [linux]
'@esbuild/linux-arm@0.27.7':
resolution: {integrity: sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==}
engines: {node: '>=18'}
cpu: [arm]
os: [linux]
'@esbuild/linux-ia32@0.27.7':
resolution: {integrity: sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==}
engines: {node: '>=18'}
cpu: [ia32]
os: [linux]
'@esbuild/linux-loong64@0.27.7':
resolution: {integrity: sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==}
engines: {node: '>=18'}
cpu: [loong64]
os: [linux]
'@esbuild/linux-mips64el@0.27.7':
resolution: {integrity: sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==}
engines: {node: '>=18'}
cpu: [mips64el]
os: [linux]
'@esbuild/linux-ppc64@0.27.7':
resolution: {integrity: sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==}
engines: {node: '>=18'}
cpu: [ppc64]
os: [linux]
'@esbuild/linux-riscv64@0.27.7':
resolution: {integrity: sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==}
engines: {node: '>=18'}
cpu: [riscv64]
os: [linux]
'@esbuild/linux-s390x@0.27.7':
resolution: {integrity: sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==}
engines: {node: '>=18'}
cpu: [s390x]
os: [linux]
'@esbuild/linux-x64@0.27.7':
resolution: {integrity: sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==}
engines: {node: '>=18'}
cpu: [x64]
os: [linux]
'@esbuild/netbsd-arm64@0.27.7':
resolution: {integrity: sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==}
engines: {node: '>=18'}
cpu: [arm64]
os: [netbsd]
'@esbuild/netbsd-x64@0.27.7':
resolution: {integrity: sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==}
engines: {node: '>=18'}
cpu: [x64]
os: [netbsd]
'@esbuild/openbsd-arm64@0.27.7':
resolution: {integrity: sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==}
engines: {node: '>=18'}
cpu: [arm64]
os: [openbsd]
'@esbuild/openbsd-x64@0.27.7':
resolution: {integrity: sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==}
engines: {node: '>=18'}
cpu: [x64]
os: [openbsd]
'@esbuild/openharmony-arm64@0.27.7':
resolution: {integrity: sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==}
engines: {node: '>=18'}
cpu: [arm64]
os: [openharmony]
'@esbuild/sunos-x64@0.27.7':
resolution: {integrity: sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==}
engines: {node: '>=18'}
cpu: [x64]
os: [sunos]
'@esbuild/win32-arm64@0.27.7':
resolution: {integrity: sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==}
engines: {node: '>=18'}
cpu: [arm64]
os: [win32]
'@esbuild/win32-ia32@0.27.7':
resolution: {integrity: sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==}
engines: {node: '>=18'}
cpu: [ia32]
os: [win32]
'@esbuild/win32-x64@0.27.7':
resolution: {integrity: sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==}
engines: {node: '>=18'}
cpu: [x64]
os: [win32]
'@img/colour@1.1.0':
resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==}
engines: {node: '>=18'}
@@ -497,11 +337,6 @@ packages:
resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==}
engines: {node: '>= 0.4'}
esbuild@0.27.7:
resolution: {integrity: sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==}
engines: {node: '>=18'}
hasBin: true
expand-template@2.0.3:
resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==}
engines: {node: '>=6'}
@@ -801,11 +636,6 @@ packages:
tslib@2.8.1:
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
tsx@4.21.0:
resolution: {integrity: sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==}
engines: {node: '>=18.0.0'}
hasBin: true
tunnel-agent@0.6.0:
resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==}
@@ -814,11 +644,6 @@ packages:
engines: {node: '>=14.17'}
hasBin: true
typescript@6.0.2:
resolution: {integrity: sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ==}
engines: {node: '>=14.17'}
hasBin: true
undici-types@7.18.2:
resolution: {integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==}
@@ -864,84 +689,6 @@ snapshots:
tslib: 2.8.1
optional: true
'@esbuild/aix-ppc64@0.27.7':
optional: true
'@esbuild/android-arm64@0.27.7':
optional: true
'@esbuild/android-arm@0.27.7':
optional: true
'@esbuild/android-x64@0.27.7':
optional: true
'@esbuild/darwin-arm64@0.27.7':
optional: true
'@esbuild/darwin-x64@0.27.7':
optional: true
'@esbuild/freebsd-arm64@0.27.7':
optional: true
'@esbuild/freebsd-x64@0.27.7':
optional: true
'@esbuild/linux-arm64@0.27.7':
optional: true
'@esbuild/linux-arm@0.27.7':
optional: true
'@esbuild/linux-ia32@0.27.7':
optional: true
'@esbuild/linux-loong64@0.27.7':
optional: true
'@esbuild/linux-mips64el@0.27.7':
optional: true
'@esbuild/linux-ppc64@0.27.7':
optional: true
'@esbuild/linux-riscv64@0.27.7':
optional: true
'@esbuild/linux-s390x@0.27.7':
optional: true
'@esbuild/linux-x64@0.27.7':
optional: true
'@esbuild/netbsd-arm64@0.27.7':
optional: true
'@esbuild/netbsd-x64@0.27.7':
optional: true
'@esbuild/openbsd-arm64@0.27.7':
optional: true
'@esbuild/openbsd-x64@0.27.7':
optional: true
'@esbuild/openharmony-arm64@0.27.7':
optional: true
'@esbuild/sunos-x64@0.27.7':
optional: true
'@esbuild/win32-arm64@0.27.7':
optional: true
'@esbuild/win32-ia32@0.27.7':
optional: true
'@esbuild/win32-x64@0.27.7':
optional: true
'@img/colour@1.1.0': {}
'@img/sharp-darwin-arm64@0.34.5':
@@ -1170,35 +917,6 @@ snapshots:
dependencies:
es-errors: 1.3.0
esbuild@0.27.7:
optionalDependencies:
'@esbuild/aix-ppc64': 0.27.7
'@esbuild/android-arm': 0.27.7
'@esbuild/android-arm64': 0.27.7
'@esbuild/android-x64': 0.27.7
'@esbuild/darwin-arm64': 0.27.7
'@esbuild/darwin-x64': 0.27.7
'@esbuild/freebsd-arm64': 0.27.7
'@esbuild/freebsd-x64': 0.27.7
'@esbuild/linux-arm': 0.27.7
'@esbuild/linux-arm64': 0.27.7
'@esbuild/linux-ia32': 0.27.7
'@esbuild/linux-loong64': 0.27.7
'@esbuild/linux-mips64el': 0.27.7
'@esbuild/linux-ppc64': 0.27.7
'@esbuild/linux-riscv64': 0.27.7
'@esbuild/linux-s390x': 0.27.7
'@esbuild/linux-x64': 0.27.7
'@esbuild/netbsd-arm64': 0.27.7
'@esbuild/netbsd-x64': 0.27.7
'@esbuild/openbsd-arm64': 0.27.7
'@esbuild/openbsd-x64': 0.27.7
'@esbuild/openharmony-arm64': 0.27.7
'@esbuild/sunos-x64': 0.27.7
'@esbuild/win32-arm64': 0.27.7
'@esbuild/win32-ia32': 0.27.7
'@esbuild/win32-x64': 0.27.7
expand-template@2.0.3: {}
fast-glob@3.3.3:
@@ -1285,11 +1003,11 @@ snapshots:
dependencies:
function-bind: 1.1.2
i18next@26.0.3(typescript@6.0.2):
i18next@26.0.3(typescript@5.9.3):
dependencies:
'@babel/runtime': 7.29.2
optionalDependencies:
typescript: 6.0.2
typescript: 5.9.3
ieee754@1.2.1: {}
@@ -1538,21 +1256,12 @@ snapshots:
tslib@2.8.1:
optional: true
tsx@4.21.0:
dependencies:
esbuild: 0.27.7
get-tsconfig: 4.13.7
optionalDependencies:
fsevents: 2.3.3
tunnel-agent@0.6.0:
dependencies:
safe-buffer: 5.2.1
typescript@5.9.3: {}
typescript@6.0.2: {}
undici-types@7.18.2: {}
util-deprecate@1.0.2: {}
+1
View File
@@ -21,6 +21,7 @@ export default async function followCommand(ueuse: ueuseModule) {
if (!notice.success) {
console.warn("フォロー通知に失敗:", notice.error_code);
return;
}
console.log("フォロー通知:", notice.uniqid);
+13 -2
View File
@@ -6,9 +6,13 @@ import weatherCommand from "@/feature/command/weather";
import helpCommand from "@/feature/command/help";
import followCommand from "@/feature/command/follow";
import unfollowCommand from "@/feature/command/unfollow";
import miqCommand from "./miq";
import miqCommand from "@/feature/command/miq";
import initI18n from "@/lib/i18n";
export default async function commandExecute() {
await initI18n();
console.log("コマンドの処理を行います");
try {
let ueuses: ueuseModule[] = [];
{
@@ -124,4 +128,11 @@ export default async function commandExecute() {
Memory.memory = mem;
}
}
process.exit(0);
} catch (err: any) {
console.error("message" in err
? err.message
: err);
process.exit(1);
}
+1
View File
@@ -21,6 +21,7 @@ export default async function unfollowCommand(ueuse: ueuseModule) {
if (!notice.success) {
console.warn("フォロー解除通知に失敗:", notice.error_code);
return;
}
console.log("フォロー解除通知:", notice.uniqid);
+258 -239
View File
@@ -1,15 +1,29 @@
import client from "@/lib/client";
import config from "@/lib/config";
import initI18n from "@/lib/i18n";
import { format } from "date-fns";
import i18next from "i18next";
import { readFileSync } from "node:fs";
import { EOL } from "node:os";
import { WebSocket } from "ws";
const WEBSOCKET_URL = config.debug
? "wss://api-realtime-sandbox.p2pquake.net/v2/ws"
: "wss://api.p2pquake.net/v2/ws";
await initI18n();
if (config.earthquake?.useHistoryData) {
console.log("過去の地震情報を配信します");
const history = JSON.parse(readFileSync(`${import.meta.dirname}/../../260420.json`, "utf-8"));
history.reverse();
let i = 0;
setInterval(() => {
processMessage(history[i]);
i++;
}, 10 * 1000);
} else {
const WEBSOCKET_URL = config.debug
? "wss://api-realtime-sandbox.p2pquake.net/v2/ws"
: "wss://api.p2pquake.net/v2/ws";
const P2PEarthquakeClient = () => {
console.log("P2P地震情報のWebSocketに接続します");
const socket = new WebSocket(WEBSOCKET_URL);
@@ -29,241 +43,246 @@ const P2PEarthquakeClient = () => {
return;
}
try {
const scaleMessages: Record<string, string> = {
"-1": "不明",
"10": "震度1",
"20": "震度2",
"30": "震度3",
"40": "震度4",
"45": "震度5弱",
"46": "**推定**震度5弱以上***(正確には不明)***",
"50": "震度5強",
"55": "震度6弱",
"60": "震度6強",
"70": "震度7",
}
switch (message.code) {
case 551:
{
console.log("地震発生情報を受信しました");
const domesticTsunamiMessages: Record<string, string> = {
"None": "😌この地震による**国内**の津波の心配はありません。",
"Unknown": "😕この地震による**国内**の***津波情報は***不明です。",
"Checking": "🧐この地震による**国内**の津波情報を**調査中です。**",
"NonEffective": "😌この地震による**国内**の**海面変動が予想されますが**、被害の心配はありません。",
"Watch": "⚠️この地震により**国内**で津波注意報が発令しています。",
"Warning": "🚨この地震による**国内**の津波予報があります。",
}
const foreignTsunamiMessages: Record<string, string> = {
"None": "😌この地震による**国外**の津波の心配はありません。",
"Unknown": "😕この地震による**国外**の***津波情報は***不明です。",
"Checking": "🧐この地震による**国外**の津波情報を**調査中です。**",
"NonEffectiveNearby": "😌この地震によって**国外**にて震源の近傍で**小さな津波の可能性はありますが**、被害の心配はありません。",
"WarningNearby": "⚠️この地震によって**国外**にて震源の近傍で**津波の可能性**があります。",
"WarningPacific": "⚠️この地震によって**太平洋**にて**津波の可能性**があります。",
"WarningPacificWide": "🚨この地震によって**太平洋の広域**にて**津波の可能性**があります。",
"WarningIndian": "⚠️この地震によって**インド洋**にて**津波の可能性**があります。",
"WarningIndianWide": "🚨この地震によって**インド洋の広域**にて**津波の可能性**があります。",
"Potential": "🚨この地震によって**一般的に**この規模では津波の可能性があると考えられています。",
}
let points: Record<string, any[]> = {};
for (const point of message.points) {
const scaleMsg = scaleMessages[String(point.scale)];
if (!scaleMsg)
break;
points[scaleMsg]?.push(point);
}
const grouped: Record<string, { scale: number; addrs: string[] }> = {};
for (const point of message.points) {
const { addr, scale } = point;
const label = scaleMessages[String(scale)] ?? "不明";
if (!grouped[label]) {
grouped[label] = { scale, addrs: [] };
}
grouped[label].addrs.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();
const response = await client.request("ueuse/create", {
text: i18next.t("earthquakeNotice", {
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,
}),
});
if (!response.success) {
console.warn("ユーズの作成に失敗しました:", response.error_code);
break;
}
console.log("地震発生情報を投稿:", response.uniqid);
}
break;
case 552:
{
console.log("津波予報情報を受信しました");
if (message.cancelled) {
const response = await client.request("ueuse/create", {
text: i18next.t("tsunamiCancelNotice", {
announceTime: format(new Date(message.issue.time), "yyyy年M月d日 H:mm:ss.SSS"),
source: (message.issue.source ?? "不明") + (message.issue.source === "気象庁"
? "(✅信頼できます)"
: "(😕信頼できない可能性があります)"),
}),
});
if (!response.success) {
console.warn("ユーズの作成に失敗しました:", response.error_code);
break;
}
console.log("津波予報解除情報を投稿:", response.uniqid);
break;
}
const gradeMessages: Record<string, string> = {
"Unknown": "不明",
"Watch": "津波注意報",
"Warning": "津波警報",
"MajorWarning": "大津波警報",
}
let areasMsg = "";
for (const area of message.areas) {
areasMsg += i18next.t("tsunamiAreaMsg", {
name: area.name,
immediate: area.immediate
? EOL + "🚨***直ちに津波が来襲すると予想されています。***"
: "",
grade: gradeMessages[area.grade],
arrivalTime: area.firstHeight.arrivalTime,
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 response = await client.request("ueuse/create", {
text: i18next.t("tsunamiForecastNotice", {
announceTime: format(new Date(message.issue.time), "yyyy年M月d日 H:mm:ss.SSS"),
areasMsg: areasMsg.trim(),
source: message.issue.source ?? "不明",
}),
});
if (!response.success) {
console.warn("ユーズの作成に失敗しました:", response.error_code);
break;
}
console.log("津波予報情報を投稿:", response.uniqid);
}
break;
case 556:
{
console.log("緊急地震速報(警報)を受信しました");
const kindMessages: Record<string, string> = {
"10": "⏳主要動は、**未到達と予測**されています。",
"11": "🫨主要動が、**既に到達していると予測**されています。",
"19": "🧐PLUM法によると、主要動の***到達予想は***ありません。",
}
let areasMsg = "";
for (const area of message.areas) {
areasMsg += i18next.t("eewAreaMsg", {
name: area.name,
scaleFrom: scaleMessages[String(Math.floor(area.scaleFrom))],
scaleTo: scaleMessages[String(Math.floor(area.scaleTo))],
kind: kindMessages[area.kindCode] ?? "😕主要動の***到達予想は***ありません。",
arrivalTime: area.arrivalTime !== undefined
? format(new Date(area.arrivalTime), "yyyy年M月d日 H:mm:ss.SSS")
: "不明",
}) + EOL.repeat(2);
}
const response = await client.request("ueuse/create", {
text: i18next.t("eewNotice", {
isTest: message.test
? "⚒️これは**テストです。**"
: "🚨これは**テストではありません。**",
announceTime: format(new Date(message.issue.time), "yyyy年M月d日 H:mm:ss.SSS"),
occuredTime: format(new Date(message.earthquake.originTime), "yyyy年M月d日 H:mm:ss.SSS"),
arrivalTime: format(new Date(message.earthquake.arrivalTime), "yyyy年M月d日 H:mm:ss.SSS"),
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}`,
areas: areasMsg !== ""
? EOL.repeat(2) + areasMsg.trim()
: "",
}),
});
if (!response.success) {
console.warn("ユーズの作成に失敗しました:", response.error_code);
break;
}
console.log("緊急地震速報(警報)情報を投稿:", response.uniqid);
}
break;
default:
console.log("未対応の情報:", message);
break;
}
} catch (err) {
console.warn("メッセージの処理に失敗しました:", err);
}
processMessage(message);
});
}
export default P2PEarthquakeClient;
const processMessage = async (message: any) => {
try {
const scaleMessages: Record<string, string> = {
"-1": "不明",
"10": "震度1",
"20": "震度2",
"30": "震度3",
"40": "震度4",
"45": "震度5弱",
"46": "**推定**震度5弱以上***(正確には不明)***",
"50": "震度5強",
"55": "震度6弱",
"60": "震度6強",
"70": "震度7",
"99": "程度以上",
}
switch (message.code) {
case 551:
{
console.log("地震発生情報を受信しました");
const domesticTsunamiMessages: Record<string, string> = {
"None": "😌この地震による**国内**の津波の心配はありません。",
"Unknown": "😕この地震による**国内**の***津波情報は***不明です。",
"Checking": "🧐この地震による**国内**の津波情報を**調査中です。**",
"NonEffective": "😌この地震による**国内**の**海面変動が予想されますが**、被害の心配はありません。",
"Watch": "⚠️この地震により**国内**で津波注意報が発令しています。",
"Warning": "🚨この地震による**国内**の津波予報があります。",
}
const foreignTsunamiMessages: Record<string, string> = {
"None": "😌この地震による**国外**の津波の心配はありません。",
"Unknown": "😕この地震による**国外**の***津波情報は***不明です。",
"Checking": "🧐この地震による**国外**の津波情報を**調査中です。**",
"NonEffectiveNearby": "😌この地震によって**国外**にて震源の近傍で**小さな津波の可能性はありますが**、被害の心配はありません。",
"WarningNearby": "⚠️この地震によって**国外**にて震源の近傍で**津波の可能性**があります。",
"WarningPacific": "⚠️この地震によって**太平洋**にて**津波の可能性**があります。",
"WarningPacificWide": "🚨この地震によって**太平洋の広域**にて**津波の可能性**があります。",
"WarningIndian": "⚠️この地震によって**インド洋**にて**津波の可能性**があります。",
"WarningIndianWide": "🚨この地震によって**インド洋の広域**にて**津波の可能性**があります。",
"Potential": "🚨この地震によって**一般的に**この規模では津波の可能性があると考えられています。",
}
let points: Record<string, any[]> = {};
for (const point of message.points) {
const scaleMsg = scaleMessages[String(point.scale)];
if (!scaleMsg)
break;
points[scaleMsg]?.push(point);
}
const grouped: Record<string, { scale: number; addrs: string[] }> = {};
for (const point of message.points) {
const { addr, scale } = point;
const label = scaleMessages[String(scale)] ?? "不明";
if (!grouped[label]) {
grouped[label] = { scale, addrs: [] };
}
grouped[label].addrs.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();
const response = await client.request("ueuse/create", {
text: i18next.t("earthquakeNotice", {
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,
}),
});
if (!response.success) {
console.warn("ユーズの作成に失敗しました:", response.error_code);
break;
}
console.log("地震発生情報を投稿:", response.uniqid);
}
break;
case 552:
{
console.log("津波予報情報を受信しました");
if (message.cancelled) {
const response = await client.request("ueuse/create", {
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;
}
const gradeMessages: Record<string, string> = {
"Unknown": "不明",
"Watch": "津波注意報",
"Warning": "津波警報",
"MajorWarning": "大津波警報",
}
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 response = await client.request("ueuse/create", {
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:
{
console.log("緊急地震速報(警報)を受信しました");
const kindMessages: Record<string, string> = {
"10": "⏳主要動は、**未到達と予測**されています。",
"11": "🫨主要動が、**既に到達していると予測**されています。",
"19": "🧐PLUM法によると、主要動の***到達予想は***ありません。",
}
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: area.arrivalTime !== undefined
? format(new Date(area.arrivalTime), "yyyy年M月d日 H:mm:ss")
: "不明",
}) + EOL.repeat(2);
}
const response = await client.request("ueuse/create", {
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}`,
areas: areasMsg !== ""
? EOL.repeat(2) + areasMsg.trim()
: "",
}),
});
if (!response.success) {
console.warn("ユーズの作成に失敗しました:", response.error_code);
break;
}
console.log("緊急地震速報(警報)情報を投稿:", response.uniqid);
}
break;
default:
console.log("未対応の情報:", message);
break;
}
} catch (err) {
console.warn("メッセージの処理に失敗しました:", err);
}
}
+12 -2
View File
@@ -1,16 +1,26 @@
import client from "@/lib/client";
import initI18n from "@/lib/i18n";
import { format } from "date-fns";
import i18next from "i18next";
export default async function timeNotice() {
await initI18n();
console.log("時報の投稿を行います");
try {
const response = await client.request("ueuse/create", {
text: i18next.t("timeNotice", { time: format(new Date(), "HH:mm") }),
});
if (!response.success) {
console.warn("時報投稿に失敗しました:", response.error_code);
return;
process.exit(1);
}
console.log("時報投稿:", response.uniqid);
process.exit(0);
} catch (err: any) {
console.error("message" in err
? err.message
: err);
process.exit(1);
}
+24 -11
View File
@@ -1,8 +1,10 @@
import client from "@/lib/client";
import config from "@/lib/config";
import initI18n from "@/lib/i18n";
import i18next from "i18next";
import { readFileSync } from "node:fs";
import { EOL } from "node:os";
import { isMainThread, workerData } from "node:worker_threads";
const cityList = [
"016010",
@@ -54,19 +56,30 @@ const cityList = [
"471010",
];
export async function weatherNotice() {
const provisionalUeuse = await client.request("ueuse/create", {
text: i18next.t("weatherProvisional"),
});
if (!isMainThread && workerData === "scheduledWeatherNotice") {
await initI18n();
console.log("天気予報の投稿を行います");
if (!provisionalUeuse.success) {
console.error("天気仮投稿に失敗しました:", provisionalUeuse.error_code);
return;
try {
const provisionalUeuse = await client.request("ueuse/create", {
text: i18next.t("weatherProvisional"),
});
if (!provisionalUeuse.success) {
console.error("天気仮投稿に失敗しました:", provisionalUeuse.error_code);
process.exit(1);
}
console.log("天気仮投稿:", provisionalUeuse.uniqid);
weatherReply(provisionalUeuse.uniqid);
process.exit(0);
} catch (err: any) {
console.error("message" in err
? err.message
: err);
process.exit(1);
}
console.log("天気仮投稿:", provisionalUeuse.uniqid);
weatherReply(provisionalUeuse.uniqid);
}
export async function weatherReply(uniqid: string) {
+5 -31
View File
@@ -1,13 +1,9 @@
import { schedule } from "node-cron";
import { readFileSync } from "node:fs";
import config from "@/lib/config";
import initI18n from "@/lib/i18n";
import { initUserID } from "@/lib/memory";
import { styleText } from "node:util";
import timeNotice from "@/feature/timeNotice";
import { weatherNotice } from "@/feature/weatherNotice";
import commandExecute from "@/feature/command";
import P2PEarthquakeClient from "@/feature/earthquakeNotice";
import { Worker } from "node:worker_threads";
try {
console.log(readFileSync(`${import.meta.dirname}/../asciiart.txt`, "utf-8"));
@@ -22,10 +18,9 @@ try {
}
console.log();
await initI18n();
await initUserID();
P2PEarthquakeClient();
new Worker(`${import.meta.dirname}/feature/earthquakeNotice.js`);
console.log("Botが起動しました");
} catch (err: any) {
@@ -36,34 +31,13 @@ try {
}
schedule("0 * * * *", async () => {
console.log("時報の投稿を行います");
try {
await timeNotice();
} catch (err: any) {
console.error("message" in err
? err.message
: err);
}
new Worker(`${import.meta.dirname}/feature/timeNotice.js`);
});
schedule("0 7 * * *", async () => {
console.log("天気予報の投稿を行います");
try {
await weatherNotice();
} catch (err: any) {
console.error("message" in err
? err.message
: err);
}
new Worker(`${import.meta.dirname}/feature/weatherNotice.js`);
});
schedule(`*/${config.command.interval} * * * *`, async () => {
console.log("コマンドの処理を行います");
try {
await commandExecute();
} catch (err: any) {
console.error("message" in err
? err.message
: err);
}
new Worker(`${import.meta.dirname}/feature/command/index.js`);
});
+3
View File
@@ -10,6 +10,9 @@ const schema = z.object({
weather: z.object({
splits: z.number().int().positive(),
}),
earthquake: z.object({
useHistoryData: z.boolean(),
}).optional(),
uwuzu: z.object({
token: z.string().length(64),
origin: z.string().refine(data => {