diff --git a/.gitignore b/.gitignore index 744f163..26f77c0 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ /config.ts log* +/iolog.json diff --git a/asciiart.txt b/asciiart.txt index 752595c..f97a7aa 100644 --- a/asciiart.txt +++ b/asciiart.txt @@ -1,5 +1,5 @@ -# # ##### ####### ### ##### ####### # # # # # # ###### # # -## # # # # # ## # # # # # # # # # # # -# ## # # # # # # ###### # # # # # # # # ## # # -# ## # # # # ## # # # # # # # # # # -# # ##### # ### ##### ####### ###### # # ###### ###### ####### +# # ###### ##### ### ###### ####### # # # # # # ###### # # +## # # # # # # # # # # # # # # # # # +# ## # # # # # # ####### # # # # # # # # ## # # +# ## # # # # # # # # # # # # # # # +# # ###### # ### ###### ####### ###### # # ###### ###### ###### diff --git a/examples/config.ts b/examples/config.ts index 1627856..9967bf6 100644 --- a/examples/config.ts +++ b/examples/config.ts @@ -22,6 +22,20 @@ const config: configTypes = { 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のサーバー }; diff --git a/main.ts b/main.ts index b7042ff..8136fa8 100644 --- a/main.ts +++ b/main.ts @@ -13,6 +13,10 @@ asciiArt(); // フォローバック機能読み込み import followBack from "./scripts/followBack.js"; +// 正常終了確認読み込み +import successExit from "./scripts/successExit.js"; +successExit(); + // 地震情報観測開始 earthquakeNotice(); diff --git a/package.json b/package.json index 61476ff..c896a69 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,12 @@ { "name": "noticeuwuzu", - "version": "v4.3.3@uwuzu1.5.4", + "version": "v5.0@uwuzu1.5.4", "description": "uwuzu Notice Bot", "main": "dist/main.js", "scripts": { "start": "node .", "build": "tsc", + "main": "tsc && node .", "dev": "tsx main.ts" }, "keywords": [ @@ -28,9 +29,11 @@ "@types/date-fns": "^2.5.3", "@types/dotenv": "^6.1.1", "@types/node-cron": "^3.0.11", + "@types/nodemailer": "^6.4.17", "date-fns": "^4.1.0", "fs": "^0.0.1-security", "node-cron": "^4.1.1", + "nodemailer": "^7.0.4", "tsx": "^4.20.3", "typescript": "^5.8.3", "ws": "^8.18.3" diff --git a/scripts/earthquakeNotice.ts b/scripts/earthquakeNotice.ts index e59157c..4f35865 100644 --- a/scripts/earthquakeNotice.ts +++ b/scripts/earthquakeNotice.ts @@ -1,5 +1,6 @@ import WebSocket from "ws"; import { differenceInMinutes, subMinutes } from "date-fns"; +import sendMail from "../src/mailer.js"; import config from "../config.js"; @@ -276,6 +277,38 @@ async function event(earthquakeInfo: any): Promise { maxScale += "震度7"; } + // 警告 + if ( + earthquakeInfo.earthquake.maxScale >= 60 || + config.emergency.function + ) { + console.log("----------------"); + + console.log("震度6強以上の地震を受信しました"); + console.log("サーバーがダウンする可能性があります"); + + // メール送信 + if (config.emergency.function) { + sendMail({ + from: "noticeUwuzu自動送信", + to: config.emergency.mail.to, + subject: "【警告】震度6強以上の地震を受信しました", + html: ` + ※noticeUwuzu自動送信によるメールです。 + 【警告】 + BOT管理者さん、noticeUwuzu自動送信メールです。 + 震度6強以上の地震を受信したため警告メールが送信されました。 + 物理、システム的にサーバーがダウンする可能性があります。 + ご自身の身をお守りください。 + ` + }); + + console.log("管理者へ警告メールを送信しました"); + } + + console.log("----------------"); + } + // 対象地域 let areas: string = ""; @@ -300,6 +333,7 @@ async function event(earthquakeInfo: any): Promise { if ( earthquakeInfo.earthquake.hypocenter.depth !== null || + earthquakeInfo.earthquake.hypocenter.depth !== undefined || earthquakeInfo.earthquake.hypocenter.depth !== -1 ) { if (earthquakeInfo.earthquake.hypocenter.depth === 0) { @@ -314,6 +348,7 @@ async function event(earthquakeInfo: any): Promise { if( earthquakeInfo.earthquake.hypocenter.magnitude !== null || + earthquakeInfo.earthquake.hypocenter.magnitude !== undefined || earthquakeInfo.earthquake.hypocenter.magnitude !== -1 ) { magnitude = `マグニチュード:${earthquakeInfo.earthquake.hypocenter.magnitude}`; diff --git a/scripts/followBack.ts b/scripts/followBack.ts index dec0c22..87e6ae0 100644 --- a/scripts/followBack.ts +++ b/scripts/followBack.ts @@ -16,7 +16,7 @@ export default async function followBack() { const meData: types.meApi = await resMe.json(); - console.log(`BOTプロフィール:${meData}`); + console.log(`BOTプロフィール:${JSON.stringify(meData)}`); const followers: Array = meData.follower; diff --git a/scripts/successExit.ts b/scripts/successExit.ts new file mode 100644 index 0000000..90c85ee --- /dev/null +++ b/scripts/successExit.ts @@ -0,0 +1,64 @@ +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({ + from: "noticeUwuzu自動送信", + to: config.emergency.mail.to, + subject: "【警告】前回終了が不適切な可能性", + html: ` + ※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(); diff --git a/scripts/timeNotice.ts b/scripts/timeNotice.ts index b15a8a6..b469249 100644 --- a/scripts/timeNotice.ts +++ b/scripts/timeNotice.ts @@ -4,8 +4,6 @@ import type * as types from "types/types"; import config from "../config.js"; export default async function timeNotice() { - console.log("----------------"); - // 停止時間 // 時刻取得 const start = config.time.stopTimes.start; @@ -24,6 +22,7 @@ export default async function timeNotice() { } if (inRange) { + console.log("----------------"); console.log("時報休止期間のため投稿されませんでした"); return; } else { @@ -41,6 +40,7 @@ export default async function timeNotice() { const ueuseData: types.ueuseCreateApi = await resUeuse.json(); + console.log("----------------"); console.log(`時報投稿:${JSON.stringify(ueuseData)}`); } } diff --git a/src/mailer.ts b/src/mailer.ts new file mode 100644 index 0000000..bd14db4 --- /dev/null +++ b/src/mailer.ts @@ -0,0 +1,53 @@ +import config from "../config"; +import * as nodemailer from "nodemailer"; + +import type SMTPTransport from "nodemailer/lib/smtp-transport"; + +export interface EmailMessage { + from: string; + 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 { + try { + const transporter = await createTransporter(); + + await transporter.sendMail({ + from: message.from, + 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; + } +} diff --git a/types/config.d.ts b/types/config.d.ts index 551a144..6e6dd58 100644 --- a/types/config.d.ts +++ b/types/config.d.ts @@ -19,11 +19,28 @@ 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; }