Compare commits

...

27 Commits

Author SHA1 Message Date
last2014 3955d91978 深さ判定に.earthquake.をスキップしてhypocenterを参照していた問題を修正(v5.1.1) 2025-07-08 13:47:10 +09:00
last2014 d28ca6ce4c メールに改行を追加・起動時にバージョンを表示・v5.1へ 2025-07-07 15:24:36 +09:00
last2014 ad0d975a12 fromをメールアドレスに統一(v5.0.3) 2025-07-07 15:11:49 +09:00
last2014 7f07710d23 地震情報でnullではなくundefinedによる存在確認を使用(v5.0.2) 2025-07-07 14:50:42 +09:00
last2014 3b58e7ee2d tsc後用に.jsを追加 2025-07-07 03:19:00 +09:00
last2014 22ec582e0a v5.0 2025-07-06 22:11:59 +09:00
last2014 05194ad7b8 停止時間のBooleanを使いまわしている問題を修正(v4.3.3) 2025-07-06 08:53:02 +09:00
last2014 ef374b1639 レート制限が聞かないのを修正(v4.3.2) 2025-07-05 16:24:59 +09:00
last2014 563d70aacd 地域情報更新のレート時刻初期化を修正(v4.3.1) 2025-07-05 16:18:02 +09:00
last2014 568ae7abf6 デバッグ表示を分かりやすく2(v4.3) 2025-07-05 14:06:37 +09:00
last2014 c3ab3a8456 時報休止期間を修正・package.jsonの情報を修正・v4.2 2025-07-05 11:46:43 +09:00
last2014 678f4aa1b1 誤字を修正・デバッグ表示の--線の位置を変更・v4.1.2 2025-07-04 21:55:13 +09:00
last2014 a96c3f0e4f サーバー起動時の時刻を使用した上HH:mm以外のフォーマットで時報をしている問題を修正(v4.1.1) 2025-07-04 21:44:54 +09:00
last2014 53ba26f417 デバッグ用の情報を分かりやすく・v4.1 2025-07-04 20:07:34 +09:00
last2014 6b68b6e1b5 地域情報更新のレート制限がnullで初期化されていない(v4.0.1) 2025-07-04 19:17:31 +09:00
last2014 e3451323f3 時報
の停止期間設定機能を追加・地震発生お知らせにマグニチュード/深さを追加・天気お知らせに%が2つ付く問題を修正・地域情報更新が正常に発信されない問題を修正・地域情報更新のレート制限のデフォルト設定を30分に一回に・v4.0
2025-07-04 19:07:05 +09:00
last2014 8be3684a78 <>のミスを修正(v3.5.6) 2025-07-03 15:02:00 +09:00
last2014 600626d071 v3.5.5 2025-07-03 07:57:08 +09:00
last2014 5c0c34bbb4 ゆずねっと向け早急対処 2025-07-03 07:56:57 +09:00
last2014 88bd2e88a5 緊急地震速報にも反映→v3.5.3 2025-07-02 16:49:06 +09:00
last2014 7c7dfdcf3c examples/config.tsにREADMEを参照する旨をコメント・v3.5.2へ 2025-07-02 16:44:18 +09:00
last2014 dd70fe5494 文字列を参照していた問題を修正 2025-07-02 16:42:06 +09:00
last2014 73091bb92f .filterによるクラッシュを修正 2025-07-01 18:54:02 +09:00
last2014 6c69ce80ef v3.5.1へ・ライセンス追加・.env.exampleを削除 2025-07-01 17:10:52 +09:00
last2014 7e3c4840d3 dotenvを廃止しconfig.tsへ・天気の分割数をconfigで変更できるように・デバッグ用のサンプルログを.gitignoreに追加・地震情報解析の修正 2025-07-01 16:59:45 +09:00
last2014 007efd85a8 v3.5 2025-07-01 07:46:38 +09:00
last2014 2689dfd777 地震速報の情報を増量 2025-07-01 07:44:44 +09:00
18 changed files with 637 additions and 341 deletions
-2
View File
@@ -1,2 +0,0 @@
TOKEN=
SERVER=
+4 -2
View File
@@ -1,5 +1,7 @@
/dist/ /dist/
/.env /.env*
/.env.local
/node_modules/ /node_modules/
/package-lock.json /package-lock.json
/config.ts
*log*
+13
View File
@@ -0,0 +1,13 @@
Copyright 2025 Last2014
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+19 -2
View File
@@ -4,9 +4,23 @@
uwuzuで動作するお知らせBOTです。 uwuzuで動作するお知らせBOTです。
# .env形式 # 設定
examples/config.tsをプロジェクトルートへ移動し各設定を更新してください。
`.env.example`をご覧ください。 ## 設定項目
time.stopTimes.start: 時報休止期間の開始時刻(HH)
time.stopTimes.stop: 時報休止期間の停止時刻(HH)
earthquake.reconnectTimes:地震情報のWebSocketが切断されたときに自動再接続する時間(ミリ秒)
earthquake.websocketUrl:地震情報のWebSocket接続先URL
earthquake.areasCsvUrl:地域情報のデータベース(CSV)ファイルのURL
earthquake.maxScaleMin: 地震発生投稿の最低震度(10-70)
earthquake.rateLimit: 地域情報更新のレート制限(分)
weather.splitCount:天気お知らせの返信の分割数(4分割を推奨)
apiTokenBOTアカウントのAPIキー
uwuzuServer:使用するuwuzuサーバーのホスト名(uwuzu.netなど)
# サーバー起動 # サーバー起動
@@ -17,3 +31,6 @@ npm run start
``` ```
※Node.js・npmがインストールされている必要があります。 ※Node.js・npmがインストールされている必要があります。
# ライセンス
Apache 2.0 License
+5
View File
@@ -0,0 +1,5 @@
# # ###### ##### ### ###### ####### # # # # # # ###### # #
## # # # # # # # # # # # # # # # # #
# ## # # # # # # ####### # # # # # # # # ## # #
# ## # # # # # # # # # # # # # # #
# # ###### # ### ###### ####### ###### # # ###### ###### ######
+43
View File
@@ -0,0 +1,43 @@
import type { configTypes } from "types/config";
// READMEの設定項目を参照
const config: configTypes = {
// 時報設定
time: {
// 時報休止期間
stopTimes: {
start: 23, // 開始
stop: 6, // 停止
}
},
// 地震速報設定
earthquake: {
reconnectTimes: 5000, // 再接続時間(ミリ秒)
websocketUrl: "wss://api.p2pquake.net/v2/ws", // WebSocketのURL
areasCsvUrl: "https://raw.githubusercontent.com/p2pquake/epsp-specifications/master/epsp-area.csv", // 対象地域CSVファイルのURL
maxScaleMin: 30, // 地震発生の際の最低震度(10-70)
rateLimit: 30, // 地域情報更新のレート制限(分)
},
weather: {
splitCount: 4, // 返信の分割数
},
// 緊急時設定
emergency: {
function: true, // 緊急時のコンソール表示
mail: {
function: true, // 緊急時のメール送信
host: "smtp.example.com", // SMTPサーバー
port: 465, // SMTPポート
user: "mailUser@example.com", // BOTメール送信元
password: "mailPassword", // SMTPパスワード
secure: false, // SMTPsecure設定
to: "admin@noticeuwuzu.example.com" // 緊急時メール送信先(配列可)
}
},
apiToken: "TOKEN_EXAMPLE", // BOTアカウントのAPIトークン
uwuzuServer: "uwuzu.example.com", // uwuzuのサーバー
};
export default config;
-166
View File
@@ -1,166 +0,0 @@
地震情報サーバーに接続します
P2P地震情報に接続中
サーバーが起動しました
P2P地震情報に接続しました
受信したメッセージ: {
_id: '6862177ac5875700073d7010',
areas: [
{ id: 900, peer: 211 }, { id: 250, peer: 266 }, { id: 70, peer: 4 },
{ id: 905, peer: 13 }, { id: 150, peer: 16 }, { id: 460, peer: 48 },
{ id: 125, peer: 19 }, { id: 601, peer: 8 }, { id: 275, peer: 28 },
{ id: 231, peer: 69 }, { id: 205, peer: 15 }, { id: 405, peer: 14 },
{ id: 411, peer: 12 }, { id: 425, peer: 60 }, { id: 416, peer: 11 },
{ id: 570, peer: 1 }, { id: 301, peer: 5 }, { id: 351, peer: 8 },
{ id: 410, peer: 5 }, { id: 270, peer: 122 }, { id: 225, peer: 26 },
{ id: 475, peer: 33 }, { id: 130, peer: 14 }, { id: 455, peer: 14 },
{ id: 430, peer: 15 }, { id: 230, peer: 15 }, { id: 420, peer: 14 },
{ id: 241, peer: 60 }, { id: 215, peer: 14 }, { id: 115, peer: 13 },
{ id: 560, peer: 12 }, { id: 665, peer: 14 }, { id: 465, peer: 23 },
{ id: 550, peer: 4 }, { id: 240, peer: 8 }, { id: 105, peer: 7 },
{ id: 525, peer: 15 }, { id: 210, peer: 3 }, { id: 310, peer: 4 },
{ id: 620, peer: 2 }, { id: 100, peer: 14 }, { id: 575, peer: 4 },
{ id: 315, peer: 4 }, { id: 480, peer: 6 }, { id: 300, peer: 6 },
{ id: 200, peer: 6 }, { id: 641, peer: 3 }, { id: 302, peer: 10 },
{ id: 705, peer: 2 }, { id: 625, peer: 2 }, { id: 535, peer: 20 },
{ id: 415, peer: 17 }, { id: 515, peer: 3 }, { id: 651, peer: 6 },
{ id: 10, peer: 26 }, { id: 55, peer: 9 }, { id: 701, peer: 7 },
{ id: 20, peer: 2 }, { id: 345, peer: 3 }, { id: 335, peer: 1 },
{ id: 545, peer: 5 }, { id: 325, peer: 10 }, { id: 45, peer: 1 },
{ id: 350, peer: 4 }, { id: 901, peer: 2 }, { id: 600, peer: 14 },
{ id: 151, peer: 3 }, { id: 670, peer: 4 }, { id: 581, peer: 5 },
{ id: 330, peer: 4 }, { id: 355, peer: 7 }, { id: 135, peer: 7 },
{ id: 152, peer: 5 }, { id: 242, peer: 4 }, { id: 440, peer: 1 },
{ id: 120, peer: 15 }, { id: 646, peer: 2 }, { id: 65, peer: 8 },
{ id: 60, peer: 1 }, { id: 35, peer: 2 }, { id: 320, peer: 6 },
{ id: 110, peer: 2 }, { id: 220, peer: 1 }, { id: 400, peer: 2 },
{ id: 656, peer: 1 }, { id: 530, peer: 2 }, { id: 50, peer: 3 },
{ id: 490, peer: 8 }, { id: 605, peer: 5 }, { id: 645, peer: 1 },
{ id: 541, peer: 2 }, { id: 450, peer: 1 }, { id: 675, peer: 2 },
{ id: 142, peer: 9 }, { id: 685, peer: 1 }, { id: 576, peer: 1 },
{ id: 30, peer: 2 }, { id: 15, peer: 3 }, { id: 615, peer: 2 },
{ id: 305, peer: 1 },
... 13 more items
],
code: 555,
created_at: '2025/06/30 13:50:02.640',
expire: '2025/06/30 13:55:02',
hop: 4,
time: '2025/06/30 13:50:02.634',
uid: '2025/06/30 13:55:02',
ver: '20150406'
}
震度情報を受信しました
{"_id":"6862177ac5875700073d7010","areas":[{"id":900,"peer":211},{"id":250,"peer":266},{"id":70,"peer":4},{"id":905,"peer":13},{"id":150,"peer":16},{"id":460,"peer":48},{"id":125,"peer":19},{"id":601,"peer":8},{"id":275,"peer":28},{"id":231,"peer":69},{"id":205,"peer":15},{"id":405,"peer":14},{"id":411,"peer":12},{"id":425,"peer":60},{"id":416,"peer":11},{"id":570,"peer":1},{"id":301,"peer":5},{"id":351,"peer":8},{"id":410,"peer":5},{"id":270,"peer":122},{"id":225,"peer":26},{"id":475,"peer":33},{"id":130,"peer":14},{"id":455,"peer":14},{"id":430,"peer":15},{"id":230,"peer":15},{"id":420,"peer":14},{"id":241,"peer":60},{"id":215,"peer":14},{"id":115,"peer":13},{"id":560,"peer":12},{"id":665,"peer":14},{"id":465,"peer":23},{"id":550,"peer":4},{"id":240,"peer":8},{"id":105,"peer":7},{"id":525,"peer":15},{"id":210,"peer":3},{"id":310,"peer":4},{"id":620,"peer":2},{"id":100,"peer":14},{"id":575,"peer":4},{"id":315,"peer":4},{"id":480,"peer":6},{"id":300,"peer":6},{"id":200,"peer":6},{"id":641,"peer":3},{"id":302,"peer":10},{"id":705,"peer":2},{"id":625,"peer":2},{"id":535,"peer":20},{"id":415,"peer":17},{"id":515,"peer":3},{"id":651,"peer":6},{"id":10,"peer":26},{"id":55,"peer":9},{"id":701,"peer":7},{"id":20,"peer":2},{"id":345,"peer":3},{"id":335,"peer":1},{"id":545,"peer":5},{"id":325,"peer":10},{"id":45,"peer":1},{"id":350,"peer":4},{"id":901,"peer":2},{"id":600,"peer":14},{"id":151,"peer":3},{"id":670,"peer":4},{"id":581,"peer":5},{"id":330,"peer":4},{"id":355,"peer":7},{"id":135,"peer":7},{"id":152,"peer":5},{"id":242,"peer":4},{"id":440,"peer":1},{"id":120,"peer":15},{"id":646,"peer":2},{"id":65,"peer":8},{"id":60,"peer":1},{"id":35,"peer":2},{"id":320,"peer":6},{"id":110,"peer":2},{"id":220,"peer":1},{"id":400,"peer":2},{"id":656,"peer":1},{"id":530,"peer":2},{"id":50,"peer":3},{"id":490,"peer":8},{"id":605,"peer":5},{"id":645,"peer":1},{"id":541,"peer":2},{"id":450,"peer":1},{"id":675,"peer":2},{"id":142,"peer":9},{"id":685,"peer":1},{"id":576,"peer":1},{"id":30,"peer":2},{"id":15,"peer":3},{"id":615,"peer":2},{"id":305,"peer":1},{"id":580,"peer":1},{"id":706,"peer":1},{"id":140,"peer":1},{"id":445,"peer":4},{"id":143,"peer":3},{"id":650,"peer":1},{"id":25,"peer":2},{"id":340,"peer":1},{"id":660,"peer":2},{"id":510,"peer":1},{"id":470,"peer":1},{"id":661,"peer":1},{"id":265,"peer":1}],"code":555,"created_at":"2025/06/30 13:50:02.640","expire":"2025/06/30 13:55:02","hop":4,"time":"2025/06/30 13:50:02.634","uid":"2025/06/30 13:55:02","ver":"20150406"}
=== 地震速報検知 ===
時刻: 2025/06/30 13:50:02.634
コード: 555
対象地域: [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object]
📊 震度情報が更新されました
受信したメッセージ: {
_id: '686217a9c5875700073d7011',
areas: [
{ id: 10, peer: 26 }, { id: 15, peer: 3 }, { id: 20, peer: 2 },
{ id: 25, peer: 2 }, { id: 30, peer: 2 }, { id: 35, peer: 2 },
{ id: 45, peer: 1 }, { id: 50, peer: 3 }, { id: 55, peer: 9 },
{ id: 60, peer: 1 }, { id: 65, peer: 9 }, { id: 70, peer: 4 },
{ id: 100, peer: 14 }, { id: 105, peer: 7 }, { id: 110, peer: 2 },
{ id: 115, peer: 13 }, { id: 120, peer: 15 }, { id: 125, peer: 19 },
{ id: 130, peer: 14 }, { id: 135, peer: 7 }, { id: 140, peer: 1 },
{ id: 142, peer: 9 }, { id: 143, peer: 3 }, { id: 150, peer: 16 },
{ id: 151, peer: 3 }, { id: 152, peer: 5 }, { id: 200, peer: 6 },
{ id: 205, peer: 15 }, { id: 210, peer: 3 }, { id: 215, peer: 14 },
{ id: 220, peer: 1 }, { id: 225, peer: 26 }, { id: 230, peer: 15 },
{ id: 231, peer: 69 }, { id: 240, peer: 8 }, { id: 241, peer: 60 },
{ id: 242, peer: 4 }, { id: 250, peer: 267 }, { id: 265, peer: 1 },
{ id: 270, peer: 122 }, { id: 275, peer: 28 }, { id: 300, peer: 6 },
{ id: 301, peer: 5 }, { id: 302, peer: 10 }, { id: 305, peer: 1 },
{ id: 310, peer: 4 }, { id: 315, peer: 4 }, { id: 320, peer: 5 },
{ id: 325, peer: 10 }, { id: 330, peer: 4 }, { id: 335, peer: 1 },
{ id: 340, peer: 1 }, { id: 345, peer: 3 }, { id: 350, peer: 4 },
{ id: 351, peer: 8 }, { id: 355, peer: 7 }, { id: 400, peer: 2 },
{ id: 405, peer: 14 }, { id: 410, peer: 5 }, { id: 411, peer: 12 },
{ id: 415, peer: 17 }, { id: 416, peer: 11 }, { id: 420, peer: 14 },
{ id: 425, peer: 61 }, { id: 430, peer: 15 }, { id: 440, peer: 1 },
{ id: 445, peer: 4 }, { id: 450, peer: 1 }, { id: 455, peer: 14 },
{ id: 460, peer: 48 }, { id: 465, peer: 23 }, { id: 470, peer: 1 },
{ id: 475, peer: 32 }, { id: 480, peer: 6 }, { id: 490, peer: 8 },
{ id: 510, peer: 1 }, { id: 515, peer: 3 }, { id: 525, peer: 15 },
{ id: 530, peer: 2 }, { id: 535, peer: 20 }, { id: 541, peer: 2 },
{ id: 545, peer: 5 }, { id: 550, peer: 4 }, { id: 560, peer: 12 },
{ id: 570, peer: 1 }, { id: 575, peer: 4 }, { id: 576, peer: 1 },
{ id: 580, peer: 1 }, { id: 581, peer: 5 }, { id: 600, peer: 14 },
{ id: 601, peer: 8 }, { id: 605, peer: 5 }, { id: 615, peer: 2 },
{ id: 620, peer: 2 }, { id: 625, peer: 2 }, { id: 641, peer: 3 },
{ id: 645, peer: 1 }, { id: 646, peer: 2 }, { id: 650, peer: 1 },
{ id: 651, peer: 6 },
... 13 more items
],
code: 555,
created_at: '2025/06/30 13:50:49.437',
expire: '2025/06/30 13:53:48',
hop: 11,
time: '2025/06/30 13:50:49.434',
uid: '2025/06/30 13:53:48',
ver: '20150406'
}
震度情報を受信しました
{"_id":"686217a9c5875700073d7011","areas":[{"id":10,"peer":26},{"id":15,"peer":3},{"id":20,"peer":2},{"id":25,"peer":2},{"id":30,"peer":2},{"id":35,"peer":2},{"id":45,"peer":1},{"id":50,"peer":3},{"id":55,"peer":9},{"id":60,"peer":1},{"id":65,"peer":9},{"id":70,"peer":4},{"id":100,"peer":14},{"id":105,"peer":7},{"id":110,"peer":2},{"id":115,"peer":13},{"id":120,"peer":15},{"id":125,"peer":19},{"id":130,"peer":14},{"id":135,"peer":7},{"id":140,"peer":1},{"id":142,"peer":9},{"id":143,"peer":3},{"id":150,"peer":16},{"id":151,"peer":3},{"id":152,"peer":5},{"id":200,"peer":6},{"id":205,"peer":15},{"id":210,"peer":3},{"id":215,"peer":14},{"id":220,"peer":1},{"id":225,"peer":26},{"id":230,"peer":15},{"id":231,"peer":69},{"id":240,"peer":8},{"id":241,"peer":60},{"id":242,"peer":4},{"id":250,"peer":267},{"id":265,"peer":1},{"id":270,"peer":122},{"id":275,"peer":28},{"id":300,"peer":6},{"id":301,"peer":5},{"id":302,"peer":10},{"id":305,"peer":1},{"id":310,"peer":4},{"id":315,"peer":4},{"id":320,"peer":5},{"id":325,"peer":10},{"id":330,"peer":4},{"id":335,"peer":1},{"id":340,"peer":1},{"id":345,"peer":3},{"id":350,"peer":4},{"id":351,"peer":8},{"id":355,"peer":7},{"id":400,"peer":2},{"id":405,"peer":14},{"id":410,"peer":5},{"id":411,"peer":12},{"id":415,"peer":17},{"id":416,"peer":11},{"id":420,"peer":14},{"id":425,"peer":61},{"id":430,"peer":15},{"id":440,"peer":1},{"id":445,"peer":4},{"id":450,"peer":1},{"id":455,"peer":14},{"id":460,"peer":48},{"id":465,"peer":23},{"id":470,"peer":1},{"id":475,"peer":32},{"id":480,"peer":6},{"id":490,"peer":8},{"id":510,"peer":1},{"id":515,"peer":3},{"id":525,"peer":15},{"id":530,"peer":2},{"id":535,"peer":20},{"id":541,"peer":2},{"id":545,"peer":5},{"id":550,"peer":4},{"id":560,"peer":12},{"id":570,"peer":1},{"id":575,"peer":4},{"id":576,"peer":1},{"id":580,"peer":1},{"id":581,"peer":5},{"id":600,"peer":14},{"id":601,"peer":8},{"id":605,"peer":5},{"id":615,"peer":2},{"id":620,"peer":2},{"id":625,"peer":2},{"id":641,"peer":3},{"id":645,"peer":1},{"id":646,"peer":2},{"id":650,"peer":1},{"id":651,"peer":6},{"id":656,"peer":1},{"id":660,"peer":2},{"id":661,"peer":1},{"id":665,"peer":14},{"id":670,"peer":4},{"id":675,"peer":2},{"id":685,"peer":1},{"id":701,"peer":7},{"id":705,"peer":2},{"id":706,"peer":1},{"id":900,"peer":211},{"id":901,"peer":2},{"id":905,"peer":13}],"code":555,"created_at":"2025/06/30 13:50:49.437","expire":"2025/06/30 13:53:48","hop":11,"time":"2025/06/30 13:50:49.434","uid":"2025/06/30 13:53:48","ver":"20150406"}
=== 地震速報検知 ===
時刻: 2025/06/30 13:50:49.434
コード: 555
対象地域: [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object]
📊 震度情報が更新されました
受信したメッセージ: {
_id: '68621858c5875700073d7012',
areas: [
{ id: 10, peer: 26 }, { id: 15, peer: 3 }, { id: 20, peer: 2 },
{ id: 25, peer: 2 }, { id: 30, peer: 2 }, { id: 35, peer: 2 },
{ id: 45, peer: 1 }, { id: 50, peer: 3 }, { id: 55, peer: 9 },
{ id: 60, peer: 1 }, { id: 65, peer: 9 }, { id: 70, peer: 4 },
{ id: 100, peer: 13 }, { id: 105, peer: 7 }, { id: 110, peer: 2 },
{ id: 115, peer: 13 }, { id: 120, peer: 15 }, { id: 125, peer: 19 },
{ id: 130, peer: 14 }, { id: 135, peer: 7 }, { id: 140, peer: 1 },
{ id: 142, peer: 9 }, { id: 143, peer: 3 }, { id: 150, peer: 16 },
{ id: 151, peer: 3 }, { id: 152, peer: 5 }, { id: 200, peer: 6 },
{ id: 205, peer: 15 }, { id: 210, peer: 3 }, { id: 215, peer: 14 },
{ id: 220, peer: 1 }, { id: 225, peer: 26 }, { id: 230, peer: 15 },
{ id: 231, peer: 69 }, { id: 240, peer: 8 }, { id: 241, peer: 60 },
{ id: 242, peer: 4 }, { id: 250, peer: 264 }, { id: 265, peer: 1 },
{ id: 270, peer: 123 }, { id: 275, peer: 28 }, { id: 300, peer: 5 },
{ id: 301, peer: 5 }, { id: 302, peer: 10 }, { id: 305, peer: 1 },
{ id: 310, peer: 4 }, { id: 315, peer: 4 }, { id: 320, peer: 5 },
{ id: 325, peer: 9 }, { id: 330, peer: 4 }, { id: 335, peer: 1 },
{ id: 340, peer: 1 }, { id: 345, peer: 3 }, { id: 350, peer: 4 },
{ id: 351, peer: 8 }, { id: 355, peer: 7 }, { id: 400, peer: 2 },
{ id: 405, peer: 14 }, { id: 410, peer: 5 }, { id: 411, peer: 12 },
{ id: 415, peer: 17 }, { id: 416, peer: 12 }, { id: 420, peer: 14 },
{ id: 425, peer: 60 }, { id: 430, peer: 14 }, { id: 440, peer: 1 },
{ id: 445, peer: 4 }, { id: 450, peer: 1 }, { id: 455, peer: 14 },
{ id: 460, peer: 48 }, { id: 465, peer: 22 }, { id: 470, peer: 1 },
{ id: 475, peer: 32 }, { id: 480, peer: 6 }, { id: 490, peer: 8 },
{ id: 510, peer: 1 }, { id: 515, peer: 3 }, { id: 525, peer: 15 },
{ id: 530, peer: 2 }, { id: 535, peer: 20 }, { id: 541, peer: 2 },
{ id: 545, peer: 5 }, { id: 550, peer: 4 }, { id: 560, peer: 12 },
{ id: 570, peer: 1 }, { id: 575, peer: 4 }, { id: 576, peer: 1 },
{ id: 580, peer: 1 }, { id: 581, peer: 5 }, { id: 600, peer: 14 },
{ id: 601, peer: 8 }, { id: 605, peer: 5 }, { id: 615, peer: 2 },
{ id: 620, peer: 2 }, { id: 625, peer: 2 }, { id: 641, peer: 3 },
{ id: 645, peer: 1 }, { id: 646, peer: 2 }, { id: 650, peer: 1 },
{ id: 651, peer: 6 },
... 13 more items
],
code: 555,
created_at: '2025/06/30 13:53:44.508',
expire: '2025/06/30 13:56:43',
hop: 6,
time: '2025/06/30 13:53:44.505',
uid: '2025/06/30 13:56:43',
ver: '20150406'
}
震度情報を受信しました
{"_id":"68621858c5875700073d7012","areas":[{"id":10,"peer":26},{"id":15,"peer":3},{"id":20,"peer":2},{"id":25,"peer":2},{"id":30,"peer":2},{"id":35,"peer":2},{"id":45,"peer":1},{"id":50,"peer":3},{"id":55,"peer":9},{"id":60,"peer":1},{"id":65,"peer":9},{"id":70,"peer":4},{"id":100,"peer":13},{"id":105,"peer":7},{"id":110,"peer":2},{"id":115,"peer":13},{"id":120,"peer":15},{"id":125,"peer":19},{"id":130,"peer":14},{"id":135,"peer":7},{"id":140,"peer":1},{"id":142,"peer":9},{"id":143,"peer":3},{"id":150,"peer":16},{"id":151,"peer":3},{"id":152,"peer":5},{"id":200,"peer":6},{"id":205,"peer":15},{"id":210,"peer":3},{"id":215,"peer":14},{"id":220,"peer":1},{"id":225,"peer":26},{"id":230,"peer":15},{"id":231,"peer":69},{"id":240,"peer":8},{"id":241,"peer":60},{"id":242,"peer":4},{"id":250,"peer":264},{"id":265,"peer":1},{"id":270,"peer":123},{"id":275,"peer":28},{"id":300,"peer":5},{"id":301,"peer":5},{"id":302,"peer":10},{"id":305,"peer":1},{"id":310,"peer":4},{"id":315,"peer":4},{"id":320,"peer":5},{"id":325,"peer":9},{"id":330,"peer":4},{"id":335,"peer":1},{"id":340,"peer":1},{"id":345,"peer":3},{"id":350,"peer":4},{"id":351,"peer":8},{"id":355,"peer":7},{"id":400,"peer":2},{"id":405,"peer":14},{"id":410,"peer":5},{"id":411,"peer":12},{"id":415,"peer":17},{"id":416,"peer":12},{"id":420,"peer":14},{"id":425,"peer":60},{"id":430,"peer":14},{"id":440,"peer":1},{"id":445,"peer":4},{"id":450,"peer":1},{"id":455,"peer":14},{"id":460,"peer":48},{"id":465,"peer":22},{"id":470,"peer":1},{"id":475,"peer":32},{"id":480,"peer":6},{"id":490,"peer":8},{"id":510,"peer":1},{"id":515,"peer":3},{"id":525,"peer":15},{"id":530,"peer":2},{"id":535,"peer":20},{"id":541,"peer":2},{"id":545,"peer":5},{"id":550,"peer":4},{"id":560,"peer":12},{"id":570,"peer":1},{"id":575,"peer":4},{"id":576,"peer":1},{"id":580,"peer":1},{"id":581,"peer":5},{"id":600,"peer":14},{"id":601,"peer":8},{"id":605,"peer":5},{"id":615,"peer":2},{"id":620,"peer":2},{"id":625,"peer":2},{"id":641,"peer":3},{"id":645,"peer":1},{"id":646,"peer":2},{"id":650,"peer":1},{"id":651,"peer":6},{"id":656,"peer":1},{"id":660,"peer":2},{"id":661,"peer":1},{"id":665,"peer":14},{"id":670,"peer":4},{"id":675,"peer":2},{"id":685,"peer":1},{"id":701,"peer":7},{"id":705,"peer":2},{"id":706,"peer":1},{"id":900,"peer":211},{"id":901,"peer":2},{"id":905,"peer":12}],"code":555,"created_at":"2025/06/30 13:53:44.508","expire":"2025/06/30 13:56:43","hop":6,"time":"2025/06/30 13:53:44.505","uid":"2025/06/30 13:56:43","ver":"20150406"}
=== 地震速報検知 ===
時刻: 2025/06/30 13:53:44.505
コード: 555
対象地域: [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object]
📊 震度情報が更新されました
+18 -5
View File
@@ -1,11 +1,23 @@
// 定期実行読み込み
import * as cron from "node-cron"; import * as cron from "node-cron";
// 機能読み込み
import timeNotice from "./scripts/timeNotice.js"; import timeNotice from "./scripts/timeNotice.js";
import weatherNotice from "./scripts/weatherNotice.js"; import weatherNotice from "./scripts/weatherNotice.js";
import earthquakeNotice from "./scripts/earthquakeNotice.js" import earthquakeNotice from "./scripts/earthquakeNotice.js";
// アスキーアート読み込み
import asciiArt from "./scripts/asciiart.js";
asciiArt();
// フォローバック機能読み込み
import followBack from "./scripts/followBack.js"; import followBack from "./scripts/followBack.js";
// 正常終了確認読み込み
import successExit from "./scripts/successExit.js";
successExit();
// 地震情報観測開始
earthquakeNotice(); earthquakeNotice();
// 時報・フォローバック(毎時) // 時報・フォローバック(毎時)
@@ -14,11 +26,12 @@ cron.schedule("0 * * * *", () => {
followBack(); followBack();
}); });
// 天気お知らせ(毎日7:00) // 天気お知らせ(毎日7:01)
cron.schedule("0 7 * * *", () => { cron.schedule("1 7 * * *", () => {
setTimeout(() => { setTimeout(() => {
weatherNotice(); weatherNotice();
}, 100) }, 100);
}); });
console.log("サーバーが起動しました"); // コンソールで表示
console.log("BOTサーバーが起動しました");
+9 -5
View File
@@ -1,35 +1,39 @@
{ {
"name": "noticeuwuzu", "name": "noticeuwuzu",
"version": "v3.0@uwuzu1.5.4", "version": "v5.1.1@uwuzu1.5.4",
"description": "uwuzu Notice Bot", "description": "uwuzu Notice Bot",
"main": "dist/main.js", "main": "dist/main.js",
"scripts": { "scripts": {
"start": "node .", "start": "node .",
"build": "tsc", "build": "tsc",
"main": "tsc && node .",
"dev": "tsx main.ts" "dev": "tsx main.ts"
}, },
"keywords": [ "keywords": [
"uwuzu", "uwuzu",
"bot", "bot",
"cron", "cron",
"notice",
"weather",
"time", "time",
"timenotice", "earthquake"
"notice"
], ],
"author": { "author": {
"name": "Last2014", "name": "Last2014",
"url": "https://last2014.com", "url": "https://last2014.com",
"email": "info@last2014.com" "email": "info@last2014.com"
}, },
"license": "ISC", "license": "Apache-2.0",
"type": "module", "type": "module",
"dependencies": { "dependencies": {
"@types/date-fns": "^2.5.3", "@types/date-fns": "^2.5.3",
"@types/dotenv": "^6.1.1", "@types/dotenv": "^6.1.1",
"@types/node-cron": "^3.0.11", "@types/node-cron": "^3.0.11",
"@types/nodemailer": "^6.4.17",
"date-fns": "^4.1.0", "date-fns": "^4.1.0",
"dotenv": "^17.0.0", "fs": "^0.0.1-security",
"node-cron": "^4.1.1", "node-cron": "^4.1.1",
"nodemailer": "^7.0.4",
"tsx": "^4.20.3", "tsx": "^4.20.3",
"typescript": "^5.8.3", "typescript": "^5.8.3",
"ws": "^8.18.3" "ws": "^8.18.3"
+8
View File
@@ -0,0 +1,8 @@
import * as fs from "fs";
const version = JSON.parse(fs.readFileSync("package.json", "utf-8")).version;
export default function asciiArt() {
console.log(fs.readFileSync("asciiart.txt", "utf-8").replace(/(\r?\n)$/, ''));
console.log(`${version}\n`);
}
+228 -45
View File
@@ -1,11 +1,14 @@
import WebSocket from "ws"; import WebSocket from "ws";
import * as dotenv from "dotenv"; import { differenceInMinutes, subMinutes } from "date-fns";
import sendMail from "../src/mailer.js";
dotenv.config(); import config from "../config.js";
let rateLimit: Date | null = null;
class P2PEarthquakeClient { class P2PEarthquakeClient {
private ws: WebSocket | null = null; private ws: WebSocket | null = null;
private reconnectInterval: number = 5000; private reconnectInterval: number = config.earthquake.reconnectTimes;
private reconnectTimer: NodeJS.Timeout | null = null; private reconnectTimer: NodeJS.Timeout | null = null;
private isConnecting: boolean = false; private isConnecting: boolean = false;
@@ -18,13 +21,13 @@ class P2PEarthquakeClient {
if (this.isConnecting) return; if (this.isConnecting) return;
this.isConnecting = true; this.isConnecting = true;
console.log("P2P地震情報に接続中"); console.log("地震情報サーバーに接続中");
try { try {
this.ws = new WebSocket("wss://api.p2pquake.net/v2/ws"); this.ws = new WebSocket(config.earthquake.websocketUrl);
this.ws.on("open", () => { this.ws.on("open", () => {
console.log("P2P地震情報に接続しました"); console.log("地震情報サーバーに接続しました");
this.isConnecting = false; this.isConnecting = false;
if (this.reconnectTimer) { if (this.reconnectTimer) {
@@ -43,7 +46,7 @@ class P2PEarthquakeClient {
}); });
this.ws.on("close", (code: number, reason: Buffer) => { this.ws.on("close", (code: number, reason: Buffer) => {
console.log(`接続が閉じられました: ${code} - ${reason.toString()}`); console.log(`切断されました: ${code} - ${reason.toString()}`);
this.isConnecting = false; this.isConnecting = false;
this.scheduleReconnect(); this.scheduleReconnect();
}); });
@@ -61,6 +64,8 @@ class P2PEarthquakeClient {
} }
private handleMessage(message: any): void { private handleMessage(message: any): void {
console.log("----------------");
switch (message.code) { switch (message.code) {
case 551: // 地震情報 case 551: // 地震情報
console.log("地震情報を受信しました"); console.log("地震情報を受信しました");
@@ -70,12 +75,12 @@ class P2PEarthquakeClient {
console.log("緊急地震速報を受信しました"); console.log("緊急地震速報を受信しました");
this.executeEventFunc(message); this.executeEventFunc(message);
break; break;
case 555: // 震度情報 case 555: // 地域情報更新情報
console.log("震度情報を受信しました"); console.log("地域情報更新を受信しました");
this.executeEventFunc(message); this.executeEventFunc(message);
break; break;
default: default:
console.log(`その他の情報 (コード: ${message.code})`); console.log(`未対応の情報を受信しました(コード: ${message.code})`);
break; break;
} }
} }
@@ -87,6 +92,7 @@ class P2PEarthquakeClient {
private scheduleReconnect(): void { private scheduleReconnect(): void {
if (this.reconnectTimer) return; if (this.reconnectTimer) return;
console.log("地震情報サーバーから切断されました");
console.log(`${this.reconnectInterval / 1000}秒後に再接続を試みます`); console.log(`${this.reconnectInterval / 1000}秒後に再接続を試みます`);
this.reconnectTimer = setTimeout(() => { this.reconnectTimer = setTimeout(() => {
this.reconnectTimer = null; this.reconnectTimer = null;
@@ -119,9 +125,7 @@ class P2PEarthquakeClient {
// 地名オブジェクトマッピング // 地名オブジェクトマッピング
async function areaMap(): Promise<Record<number, string>> { async function areaMap(): Promise<Record<number, string>> {
const res = await fetch( const res = await fetch(config.earthquake.areasCsvUrl);
"https://raw.githubusercontent.com/p2pquake/epsp-specifications/master/epsp-area.csv",
);
const text = await res.text(); const text = await res.text();
@@ -142,24 +146,96 @@ async function areaMap(): Promise<Record<number, string>> {
return map; return map;
} }
// 地震発生 // 情報受信
async function event(earthquakeInfo: any): Promise<void> { async function event(earthquakeInfo: any): Promise<void> {
console.log(JSON.stringify(earthquakeInfo)); console.log(`受信データ:${JSON.stringify(earthquakeInfo)}`);
// ----処理----
// 緊急地震速報の場合 // 緊急地震速報の場合
if (earthquakeInfo.code === 554) { if (earthquakeInfo.code === 554) {
// 地震詳細
let descriptionEarthquake: string = "";
if (
earthquakeInfo.earthquake.description !== "" ||
earthquakeInfo.earthquake.description !== undefined
) {
descriptionEarthquake = `この地震について:${earthquakeInfo.earthquake.description}`;
}
// 発令詳細
let description: string = "";
if (
earthquakeInfo.comments.freeFormComment !== "" ||
earthquakeInfo.comments.freeFormComment !== undefined
) {
description = `この発令について:${earthquakeInfo.comments.freeFormComment}`;
}
// テスト・訓練
let test: string = "";
if (earthquakeInfo.test !== undefined) {
test = "この情報にテスト・訓練かの情報はありません";
} else if (earthquakeInfo.test) {
test = "これはテスト、あるいは訓練です";
} else if (earthquakeInfo.test === false) {
test = "これはテスト・訓練ではありません";
}
// 対象地域
let areas: string = "";
if (earthquakeInfo.areas !== undefined) {
const areaNames: Array<string> = Array.from(
new Set(
earthquakeInfo.areas.map((point: any) => point.name).filter(Boolean),
),
);
areas = `対象地域:${areaNames.join("・")}`;
}
// 速報取り消し
let cancelled: string = "";
if (earthquakeInfo.cancelled) {
cancelled = "※以下の緊急地震速報が取り消されました※";
}
// マグニチュード
let magnitude: string = "マグニチュード:";
if (
earthquakeInfo.earthquake.hypocenter.magnitude !== -1 ||
earthquakeInfo.earthquake.hypocenter.magnitude === undefined
) {
magnitude += "マグニチュードの情報はありません";
} else {
magnitude += `M${String(earthquakeInfo.earthquake.hypocenter.magnitude)}`;
}
ueuse(` ueuse(`
==地震情報== ==地震情報==
【緊急地震速報】 【緊急地震速報】
${cancelled}
時刻:${earthquakeInfo.time} 時刻:${earthquakeInfo.time}
${descriptionEarthquake}
${description}
${test}
${areas}
`); `);
} }
// 地震情報 // 地震情報
else if (earthquakeInfo.code === 551) { else if (earthquakeInfo.code === 551) {
// 国内津波
let domesticTsunami; let domesticTsunami;
if (earthquakeInfo.earthquake.domesticTsunami === "None") { if (earthquakeInfo.earthquake.domesticTsunami === undefined) {
domesticTsunami = "この地震による国内の津波情報はありません";
} else if (earthquakeInfo.earthquake.domesticTsunami === "None") {
domesticTsunami = "この地震による国内の津波の心配はありません"; domesticTsunami = "この地震による国内の津波の心配はありません";
} else if (earthquakeInfo.earthquake.domesticTsunami === "Unknown") { } else if (earthquakeInfo.earthquake.domesticTsunami === "Unknown") {
domesticTsunami = "この地震による国内の津波情報はありません"; domesticTsunami = "この地震による国内の津波情報はありません";
@@ -172,82 +248,189 @@ async function event(earthquakeInfo: any): Promise<void> {
domesticTsunami = "この地震により国内で津波注意報が発令しています"; domesticTsunami = "この地震により国内で津波注意報が発令しています";
} else if (earthquakeInfo.earthquake.domesticTsunami === "Warning") { } else if (earthquakeInfo.earthquake.domesticTsunami === "Warning") {
domesticTsunami = "この地震による国内の津波予報があります"; domesticTsunami = "この地震による国内の津波予報があります";
} else {
domesticTsunami = "この地震による国内の津波情報はありません";
} }
let maxScale; // 最大震度
let maxScale: string = "最大深度:";
if (earthquakeInfo.earthquake.maxScale === -1) { if (
earthquakeInfo.earthquake.maxScale !== undefined &&
earthquakeInfo.earthquake.maxScale < config.earthquake.maxScaleMin
) {
console.log("最低震度に満たしていないため投稿されませんでした");
return;
}
if (
earthquakeInfo.earthquake.maxScale === -1 &&
earthquakeInfo.earthquake.maxScale === undefined
) {
maxScale = "最大震度情報なし"; maxScale = "最大震度情報なし";
} else if (earthquakeInfo.earthquake.maxScale === 10) { } else if (earthquakeInfo.earthquake.maxScale === 10) {
maxScale = "震度1"; maxScale += "震度1";
} else if (earthquakeInfo.earthquake.maxScale === 20) { } else if (earthquakeInfo.earthquake.maxScale === 20) {
maxScale = "震度2"; maxScale += "震度2";
} else if (earthquakeInfo.earthquake.maxScale === 30) { } else if (earthquakeInfo.earthquake.maxScale === 30) {
maxScale = "震度3"; maxScale += "震度3";
} else if (earthquakeInfo.earthquake.maxScale === 40) { } else if (earthquakeInfo.earthquake.maxScale === 40) {
maxScale = "震度4"; maxScale += "震度4";
} else if (earthquakeInfo.earthquake.maxScale === 45) { } else if (earthquakeInfo.earthquake.maxScale === 45) {
maxScale = "震度5弱"; maxScale += "震度5弱";
} else if (earthquakeInfo.earthquake.maxScale === 50) { } else if (earthquakeInfo.earthquake.maxScale === 50) {
maxScale = "震度5強"; maxScale += "震度5強";
} else if (earthquakeInfo.earthquake.maxScale === 55) { } else if (earthquakeInfo.earthquake.maxScale === 55) {
maxScale = "震度6弱"; maxScale += "震度6弱";
} else if (earthquakeInfo.earthquake.maxScale === 60) { } else if (earthquakeInfo.earthquake.maxScale === 60) {
maxScale = "震度6強"; maxScale += "震度6強";
} else if (earthquakeInfo.earthquake.maxScale === 70) { } else if (earthquakeInfo.earthquake.maxScale === 70) {
maxScale = "震度7"; maxScale += "震度7";
}
// 警告
if (
earthquakeInfo.earthquake.maxScale !== undefined &&
earthquakeInfo.earthquake.maxScale >= 60 &&
config.emergency.function
) {
console.log("----------------");
console.log("震度6強以上の地震を受信しました");
console.log("サーバーがダウンする可能性があります");
// メール送信
if (config.emergency.function) {
sendMail({
to: config.emergency.mail.to,
subject: "【警告】震度6強以上の地震を受信しました",
text: `
※noticeUwuzu自動送信によるメールです
【警告】
BOT管理者さん、noticeUwuzu自動送信メールです。
震度6強以上の地震を受信したため警告メールが送信されました。
物理、システム的にサーバーがダウンする可能性があります。
ご自身の身をお守りください。
`
});
console.log("管理者へ警告メールを送信しました");
}
console.log("----------------");
}
// 対象地域
let areas: string = "";
if (earthquakeInfo.points !== undefined) {
const areaNames: Array<string> = Array.from(
new Set(
earthquakeInfo.points.map((point: any) => point.addr).filter(Boolean),
),
);
areas = `対象地域:${areaNames.join("・")}`;
}
// 詳細
let description: string = "";
if (
earthquakeInfo.comments.freeFormComment !== "" &&
earthquakeInfo.comments.freeFormComment !== undefined
) {
description = `この地震について:${earthquakeInfo.comments.freeFormComment}`;
}
// 深さ
let depth: string = "";
if (
earthquakeInfo.earthquake.hypocenter.depth !== null ||
earthquakeInfo.earthquake.hypocenter.depth !== undefined ||
earthquakeInfo.earthquake.hypocenter.depth !== -1
) {
if (earthquakeInfo.earthquake.hypocenter.depth === 0) {
depth = "深さ:ごく浅い";
} else {
depth = `深さ:${String(earthquakeInfo.earthquake.hypocenter.depth)}km`;
}
}
// マグニチュード
let magnitude: string = "";
if(
earthquakeInfo.earthquake.hypocenter.magnitude !== null ||
earthquakeInfo.earthquake.hypocenter.magnitude !== undefined ||
earthquakeInfo.earthquake.hypocenter.magnitude !== -1
) {
magnitude = `マグニチュード:${String(earthquakeInfo.earthquake.hypocenter.magnitude)}`;
} }
ueuse(` ueuse(`
==地震情報== ==地震情報==
【地域情報更新 【地震発生
時刻:${earthquakeInfo.time} 時刻:${earthquakeInfo.time}
最大深度:${maxScale} ${description}
${magnitude}
${depth}
${maxScale}
${areas}
国内の津波:${domesticTsunami} 国内の津波:${domesticTsunami}
`); `);
} }
// 地域情報更新の場合 // 地域情報更新の場合
else if (earthquakeInfo.code === 555) { else if (earthquakeInfo.code === 555) {
if (rateLimit === null) {
rateLimit = subMinutes(new Date(), config.earthquake.rateLimit + 15);
}
// 対象地域マッピング // 対象地域マッピング
const areaMaps: any = await areaMap(); const areaMaps: any = await areaMap();
const areaNames: Array<string> = Array.from( const areaNames: Array<string> = Array.from(
new Set( new Set(
earthquakeInfo.areas.map((a: any) => { earthquakeInfo.areas
areaMaps[a.id].filter(Boolean); .map((i: any) => {
}), return areaMaps[i.id];
})
.filter(Boolean),
), ),
); );
const result = areaNames.join("・"); const areas = areaNames.join("・");
ueuse(` if (Math.abs(differenceInMinutes(rateLimit, new Date())) >= config.earthquake.rateLimit) {
==地震情報== ueuse(`
【地域情報更新】 ==地震情報==
時刻:${earthquakeInfo.time} 【地域情報更新】
対象地域${result} 時刻${earthquakeInfo.time}
`); 対象地域:${areas}
`);
rateLimit = new Date();
} else {
console.log("レート制限に満たしていないため投稿されませんでした");
return;
}
} }
} }
async function ueuse(text: string) { async function ueuse(text: string) {
const res = await fetch(`https://${process.env.SERVER}/api/ueuse/create`, { const res = await fetch(`https://${config.uwuzuServer}/api/ueuse/create`, {
method: "POST", method: "POST",
body: JSON.stringify({ body: JSON.stringify({
token: process.env.TOKEN, token: config.apiToken,
text: text, text: text,
}), }),
}); });
const resData = await res.json(); const resData = await res.json();
console.log(JSON.stringify(resData)); console.log(`地震情報投稿:${JSON.stringify(resData)}`);
} }
export default function earthquake(): void { export default function earthquakeNotice(): void {
console.log("地震情報サーバーに接続します"); console.log("地震情報サーバーに接続します");
const client = new P2PEarthquakeClient(); const client = new P2PEarthquakeClient();
client.start(); client.start();
+10 -10
View File
@@ -1,22 +1,22 @@
import * as dotenv from "dotenv"; import type * as types from "types/types";
import type * as types from "../types"; import config from "../config.js";
dotenv.config();
export default async function followBack() { export default async function followBack() {
console.log("----------------");
// フォロワーを取得 // フォロワーを取得
const resMe = await fetch( const resMe = await fetch(
`https://${process.env.SERVER}/api/me?token=${process.env.TOKEN}`, `https://${config.uwuzuServer}/api/me?token=${config.apiToken}`,
{ {
method: "GET", method: "GET",
// uwuzu.netで/api/meのPOSTが死んでいるため簡易的にGET // uwuzu v1.5.4で/api/meのPOSTが死んでいるため簡易的にGET
}, },
); );
const meData: types.meApi = await resMe.json(); const meData: types.meApi = await resMe.json();
console.log(meData); console.log(`BOTプロフィール:${JSON.stringify(meData)}`);
const followers: Array<string> = meData.follower; const followers: Array<string> = meData.follower;
@@ -25,11 +25,11 @@ export default async function followBack() {
const followerItem = followers[i]; const followerItem = followers[i];
const resFollow = await fetch( const resFollow = await fetch(
`https://${process.env.SERVER}/api/users/follow`, `https://${config.uwuzuServer}/api/users/follow`,
{ {
method: "POST", method: "POST",
body: JSON.stringify({ body: JSON.stringify({
token: process.env.TOKEN, token: config.apiToken,
userid: followerItem, userid: followerItem,
}), }),
}, },
@@ -37,6 +37,6 @@ export default async function followBack() {
const followData: types.followApi = await resFollow.json(); const followData: types.followApi = await resFollow.json();
console.log(JSON.stringify(followData)); console.log(`フォロー:${JSON.stringify(followData)}`);
} }
} }
+63
View File
@@ -0,0 +1,63 @@
import * as fs from "fs";
import { format, isAfter } from "date-fns";
import { parse } from "date-fns/fp";
import config from "../config.js";
import sendMail from "../src/mailer.js";
const formatParse = parse(new Date(), "yyyy-MM-dd HH:mm:ss.SSS")
// 初期化
if (fs.existsSync("iolog.json") === false) {
fs.writeFileSync("iolog.json", JSON.stringify({
start: format(new Date(), "yyyy-MM-dd HH:mm:ss.SSS"),
stop: "",
}), "utf-8");
}
export default function successExit() {
const iolog = JSON.parse(fs.readFileSync("iolog.json", "utf-8"));
if (config.emergency.function) {
// 前回の終了確認
const start = formatParse(iolog.start);
const stop = formatParse(iolog.stop);
if (isAfter(start, stop)) {
console.log("前回の終了が適切でない可能性があります");
if (config.emergency.mail.function) {
sendMail({
to: config.emergency.mail.to,
subject: "【警告】前回終了が不適切な可能性",
text: `
※noticeUwuzu自動送信によるメールです。
【警告】
BOT管理者さん、noticeUwuzu自動送信メールです。
BOTの前回終了で不適切なデータを検出しました。
これは適切な終了時にはデータを残しデータがない場合に送信されます。
電源を強制的に遮断するなどの行為による可能性があります。
その場合は今後やめ、OSからのシャットダウンを使用してください。
BOTのプログラムが破損していないかご確認ください。
`
});
}
console.log("----------------");
}
}
// 起動時に起動時刻を保存
iolog.start = format(new Date(), "yyyy-MM-dd HH:mm:ss.SSS");
fs.writeFileSync("iolog.json", JSON.stringify(iolog), "utf-8");
// 終了時に終了時刻を保存
process.on("exit", () => {
const iolog = JSON.parse(fs.readFileSync("iolog.json", "utf-8"));
iolog.stop = format(new Date(), "yyyy-MM-dd HH:mm:ss.SSS");
fs.writeFileSync("iolog.json", JSON.stringify(iolog), "utf-8");
});
}
successExit();
+38 -19
View File
@@ -1,27 +1,46 @@
import * as dotenv from "dotenv";
import { format } from "date-fns"; import { format } from "date-fns";
import type * as types from "../types"; import type * as types from "types/types";
import config from "../config.js";
dotenv.config();
export default async function timeNotice() { export default async function timeNotice() {
// 現在時刻を取得 // 停止時間
const now = format(new Date(), "HH:mm"); // 時刻取得
const start = config.time.stopTimes.start;
const stop = config.time.stopTimes.stop;
// 投稿 // 現在の時間を取得
const resUeuse = await fetch( const nowHour = new Date().getHours();
`https://${process.env.SERVER}/api/ueuse/create`,
{
method: "POST",
body: JSON.stringify({
token: process.env.TOKEN,
text: `${now}になりました`,
}),
},
);
const ueuseData: types.ueuseCreateApi = await resUeuse.json(); // 停止時刻内かどうかの判定
let inRange: boolean = false;
console.log(JSON.stringify(ueuseData)); if (start < stop) {
inRange = nowHour >= start && nowHour < stop;
} else {
inRange = nowHour >= start || nowHour < stop;
}
if (inRange) {
console.log("----------------");
console.log("時報休止期間のため投稿されませんでした");
return;
} else {
// 投稿
const resUeuse = await fetch(
`https://${config.uwuzuServer}/api/ueuse/create`,
{
method: "POST",
body: JSON.stringify({
token: config.apiToken,
text: `${format(new Date(), "HH:mm")}になりました`,
}),
},
);
const ueuseData: types.ueuseCreateApi = await resUeuse.json();
console.log("----------------");
console.log(`時報投稿:${JSON.stringify(ueuseData)}`);
}
} }
+81 -85
View File
@@ -1,17 +1,19 @@
import * as dotenv from "dotenv";
import { cityList } from "../src/weatherId.js"; import { cityList } from "../src/weatherId.js";
import type * as types from "../types";
dotenv.config(); import type * as types from "types/types.js";
import config from "../config.js";
export default async function weatherNotice() { export default async function weatherNotice() {
console.log("----------------");
// 仮投稿 // 仮投稿
const resUeuse = await fetch( const resUeuse = await fetch(
`https://${process.env.SERVER}/api/ueuse/create`, `https://${config.uwuzuServer}/api/ueuse/create`,
{ {
method: "POST", method: "POST",
body: JSON.stringify({ body: JSON.stringify({
token: process.env.TOKEN, token: config.apiToken,
text: ` text: `
本日の天気 本日の天気
※タイムラインが埋まるため返信に記載しています ※タイムラインが埋まるため返信に記載しています
@@ -22,104 +24,98 @@ export default async function weatherNotice() {
const ueuseData: types.ueuseCreateApi = await resUeuse.json(); const ueuseData: types.ueuseCreateApi = await resUeuse.json();
console.log(JSON.stringify(ueuseData)); console.log(`天気仮投稿:${JSON.stringify(ueuseData)}`);
// 3分割処理
// インデックス
const splitCount = config.weather.splitCount;
const total = cityList.length; const total = cityList.length;
const firstLength = Math.trunc(total / 3); const chunkSizes = Array(splitCount).fill(0).map((_, i) =>
const secondLength = Math.trunc((total - firstLength) / 2); Math.floor((total + i) / splitCount)
const thirdLength = total - firstLength - secondLength; );
// インデックス作成 // 分割インデックス
const firstStart = 0; let start = 0;
const firstEnd = firstStart + firstLength; const ranges = chunkSizes.map(size => {
const secondStart = firstEnd; const range = [start, start + size];
const secondEnd = secondStart + secondLength; start += size;
const thirdStart = secondEnd; return range;
const thirdEnd = total; });
let weatherResults = ["", "", ""]; // 配列作成
const weatherResults = Array(splitCount).fill("");
// 投稿1 // 天気取得
for (let i = firstStart; i < firstEnd; i++) { for (let chunkIndex = 0; chunkIndex < splitCount; chunkIndex++) {
const res = await fetch( const [chunkStart, chunkEnd] = ranges[chunkIndex];
`https://weather.tsukumijima.net/api/forecast/city/${cityList[i]}`,
);
const data = await res.json();
const today = data.forecasts[0];
const weather = today.telop ?? "取得できませんでした"; for (let i = chunkStart; i < chunkEnd; i++) {
const maxTemp = today.temperature.max?.celsius ?? "取得できませんでした"; const res = await fetch(
const minTemp = today.temperature.min?.celsius ?? "取得できませんでした"; `https://weather.tsukumijima.net/api/forecast/city/${cityList[i]}`,
const chanceOfRain = data.chanceOfRain?.["T06_12"] ?? "取得できませんでした"; );
weatherResults[0] += ` const data = await res.json();
${data.location.city}\n const today = data.forecasts[0];
天気:${weather}\n
最高気温:${maxTemp}\n // 天気
最低気温:${minTemp}\n const weather = today.telop ?? "取得できませんでした";
降水確率:${chanceOfRain}\n
`; // 最高気温
let maxTemp: string;
if (today.temperature.max.celsius !== null) {
maxTemp = `${today.temperature.max.celsius}`;
} else {
maxTemp = "取得できませんでした";
}
// 最低気温
let minTemp: string;
if (today.temperature.min.celsius !== null) {
minTemp = `${today.temperature.min.celsius}`;
} else {
minTemp = "取得できませんでした";
}
// 降水確率
let chanceOfRain: string;
if (
today.chanceOfRain.T06_12 !== null ||
today.chanceOfRain.T06_12 !== "--%"
) {
chanceOfRain = today.chanceOfRain.T06_12;
} else {
chanceOfRain = "取得できませんでした";
}
weatherResults[chunkIndex] += `
${data.location.city}
天気:${weather}
最高気温:${maxTemp}
最低気温:${minTemp}
降水確率:${chanceOfRain}
`;
}
} }
// 投稿2 // 分割投稿
for (let i = secondStart; i < secondEnd; i++) { for (let i = 0; i < splitCount; i++) {
const res = await fetch(
`https://weather.tsukumijima.net/api/forecast/city/${cityList[i]}`,
);
const data = await res.json();
const today = data.forecasts[0];
const weather = today.telop ?? "取得できませんでした";
const maxTemp = today.temperature.max?.celsius ?? "取得できませんでした";
const minTemp = today.temperature.min?.celsius ?? "取得できませんでした";
const chanceOfRain = data.chanceOfRain?.["T06_12"] ?? "取得できませんでした";
weatherResults[1] += `
${data.location.city}\n
天気:${weather}\n
最高気温:${maxTemp}\n
最低気温:${minTemp}\n
降水確率:${chanceOfRain}\n
`;
}
// 投稿3
for (let i = thirdStart; i < thirdEnd; i++) {
const res = await fetch(
`https://weather.tsukumijima.net/api/forecast/city/${cityList[i]}`,
);
const data = await res.json();
const today = data.forecasts[0];
const weather = today.telop ?? "取得できませんでした";
const maxTemp = today.temperature.max?.celsius ?? "取得できませんでした";
const minTemp = today.temperature.min?.celsius ?? "取得できませんでした";
const chanceOfRain = data.chanceOfRain?.["T06_12"] ?? "取得できませんでした";
weatherResults[2] += `
${data.location.city}\n
天気:${weather}\n
最高気温:${maxTemp}\n
最低気温:${minTemp}\n
降水確率:${chanceOfRain}\n
`;
}
// 3分割投稿
for (let i = 0; i < 3; i++) {
const resReply = await fetch( const resReply = await fetch(
`https://${process.env.SERVER}/api/ueuse/create`, `https://${config.uwuzuServer}/api/ueuse/create`,
{ {
method: "POST", method: "POST",
body: JSON.stringify({ body: JSON.stringify({
token: process.env.TOKEN, token: config.apiToken,
text: weatherResults[i], text: weatherResults[i],
replyid: ueuseData.uniqid replyid: ueuseData.uniqid
}), }),
}, },
); );
const replyData: types.ueuseCreateApi = await resReply.json(); const replyData: types.ueuseCreateApi = await resReply.json();
console.log(JSON.stringify(replyData));
console.log(`天気投稿:${JSON.stringify(replyData)}`);
} }
} }
+52
View File
@@ -0,0 +1,52 @@
import config from "../config.js";
import * as nodemailer from "nodemailer";
import type SMTPTransport from "nodemailer/lib/smtp-transport";
export interface EmailMessage {
to: string | string[];
subject: string;
text?: string;
html?: string;
}
async function createTransporter() {
const transporter = nodemailer.createTransport({
host: config.emergency.mail.host,
port: config.emergency.mail.port,
secure: config.emergency.mail.secure,
auth: {
user: config.emergency.mail.user,
pass: config.emergency.mail.password,
},
} as SMTPTransport.Options);
// 接続テスト
try {
await transporter.verify();
console.log("SMTPサーバーに接続できました");
} catch (error) {
console.error("SMTP接続テストに失敗:", error);
throw error;
}
return transporter;
}
export default async function sendMail(message: EmailMessage): Promise<void> {
try {
const transporter = await createTransporter();
await transporter.sendMail({
from: config.emergency.mail.user,
to: Array.isArray(message.to) ? message.to.join(",") : message.to,
subject: message.subject,
text: message.text,
html: message.html,
});
console.log("メール送信成功");
} catch (error) {
console.error("メール送信に失敗しました:", error);
throw error;
}
}
+46
View File
@@ -0,0 +1,46 @@
interface earthquakeTypes {
reconnectTimes: number;
websocketUrl: string;
areasCsvUrl: string;
maxScaleMin: number;
rateLimit: number;
}
interface weatherTypes {
splitCount: number;
}
interface stopsTypes {
start: number;
stop: number;
}
interface timeTypes {
stopTimes: stopsTypes;
}
interface emergencyMailTypes {
function: Boolean;
host: string | undefined;
port: number;
user: string;
password: string;
secure: Boolean;
to: string;
}
interface emergencyTypes {
function: Boolean;
mail: emergencyMailTypes;
}
export interface configTypes {
time: timeTypes,
earthquake: earthquakeTypes;
weather: weatherTypes;
emergency: emergencyTypes;
apiToken: string;
uwuzuServer: string;
}
View File