Compare commits

...

2 Commits

Author SHA1 Message Date
Last2014 50db83cc8c バージョン表記を変更 2025-07-31 22:03:52 +09:00
Last2014 d7a5d8a43e v6.5(LTS)をリリース 2025-07-31 22:02:54 +09:00
8 changed files with 233 additions and 120 deletions

View File

@ -3,7 +3,7 @@ import config from "../config.js";
export default async function APICheck() {
try {
const req = await fetch(`https://${config.uwuzu.host}/api/me`, {
const req = await fetch(`https://${config.uwuzu.host}/api/me/`, {
method: "POST",
body: JSON.stringify({
token: config.uwuzu.apiToken,
@ -12,7 +12,7 @@ export default async function APICheck() {
const res = await req.json();
if (!res.userid) {
if (res.error_code !== undefined) {
console.log(styleText("red", "APIトークンあるいはuwuzuサーバーホストが無効です"));
process.exit();
}

View File

@ -26,16 +26,16 @@ successExit();
// 地震情報観測開始
earthquakeNotice();
// 時報・フォローバック(毎時)
/*// ()
cron.schedule("0 * * * *", () => {
timeNotice();
follows();
});
// 天気お知らせ(毎日7:01)
cron.schedule("1 7 * * *", () => {
cron.schedule("10 0 7 * * *", () => {
weatherNotice();
});
});*/
// コンソールで表示
// 起動表示
console.log("BOTサーバーが起動しました");

View File

@ -1,7 +1,7 @@
{
"name": "notice-uwuzu",
"version": "v6.0.3@uwuzu1.5.4",
"description": "uwuzu Notice Bot",
"version": "v6.5(LTS)@uwuzu1.5.4",
"description": "Notice Bot for uwuzu",
"main": "dist/main.js",
"scripts": {
"start": "node .",

View File

@ -2,6 +2,7 @@ import WebSocket from "ws";
import sendMail from "../src/mailer.js";
import config from "../config.js";
import { max } from "date-fns/fp";
class P2PEarthquakeClient {
private ws: WebSocket | null = null;
@ -37,8 +38,8 @@ class P2PEarthquakeClient {
try {
const message = JSON.parse(data.toString());
this.handleMessage(message);
} catch (error) {
console.error("メッセージのパースに失敗:", error);
} catch (err) {
console.error(`メッセージのパースでエラーが発生: ${err}`);
}
});
@ -63,23 +64,18 @@ class P2PEarthquakeClient {
private handleMessage(message: any): void {
console.log("----------------");
switch (message.code) {
case 551: // 地震情報
console.log("地震情報を受信しました");
this.executeEventFunc(message);
break;
case 554: // 緊急地震速報
console.log("緊急地震速報を受信しました");
this.executeEventFunc(message);
break;
default:
console.log(`未対応の情報を受信しました(コード: ${message.code})`);
break;
}
}
const supportCode: Array<number> = [
551,
552,
556,
];
private executeEventFunc(earthquakeInfo: any): void {
event(earthquakeInfo);
if (supportCode.indexOf(message.code) !== -1) {
event(message);
} else {
console.log(`未対応の情報を受信しました(コード: ${message.code})`);
console.log(`受信メッセージ:${message}`);
}
}
private scheduleReconnect(): void {
@ -87,6 +83,7 @@ class P2PEarthquakeClient {
console.log("地震情報サーバーから切断されました");
console.log(`${this.reconnectInterval / 1000}秒後に再接続を試みます`);
this.reconnectTimer = setTimeout(() => {
this.reconnectTimer = null;
this.connect();
@ -96,7 +93,7 @@ class P2PEarthquakeClient {
private setupCleanup(): void {
const cleanup = () => {
this.stop();
process.exit(0);
process.exit();
};
process.on("SIGINT", cleanup);
@ -116,7 +113,7 @@ class P2PEarthquakeClient {
}
}
// 地名オブジェクトマッピング
// 地名マッピング
async function areaMap(): Promise<Record<number, string>> {
const res = await fetch(config.earthquake.areasCsvUrl);
@ -141,12 +138,13 @@ async function areaMap(): Promise<Record<number, string>> {
// 情報受信
async function event(earthquakeInfo: any): Promise<void> {
console.log(`受信データ:${JSON.stringify(earthquakeInfo)}`);
console.log(`受信メッセージ:${earthquakeInfo}`);
// ----処理----
// 緊急地震速報の場合
if (earthquakeInfo.code === 554) {
if (earthquakeInfo.code === 556) {
console.log("緊急地震速報を受信しました");
// 地震詳細
let descriptionEarthquake: string = "";
@ -223,28 +221,7 @@ async function event(earthquakeInfo: any): Promise<void> {
// 地震情報
else if (earthquakeInfo.code === 551) {
// 国内津波
let domesticTsunami;
if (earthquakeInfo.earthquake.domesticTsunami === undefined) {
domesticTsunami = "この地震による国内の津波情報はありません";
} else if (earthquakeInfo.earthquake.domesticTsunami === "None") {
domesticTsunami = "この地震による国内の津波の心配はありません";
} else if (earthquakeInfo.earthquake.domesticTsunami === "Unknown") {
domesticTsunami = "この地震による国内の津波情報はありません";
} else if (earthquakeInfo.earthquake.domesticTsunami === "Checking") {
domesticTsunami = "この地震による国内の津波情報を調査中です";
} else if (earthquakeInfo.earthquake.domesticTsunami === "NonEffective") {
domesticTsunami =
"この地震による国内の津波影響は若干の海面変動が予想されますが被害の心配はありません";
} else if (earthquakeInfo.earthquake.domesticTsunami === "Watch") {
domesticTsunami = "この地震により国内で津波注意報が発令しています";
} else if (earthquakeInfo.earthquake.domesticTsunami === "Warning") {
domesticTsunami = "この地震による国内の津波予報があります";
}
// 最大震度
let maxScale: string = "最大深度:";
console.log("地震発生情報を受信しました");
if (
earthquakeInfo.earthquake.maxScale !== undefined &&
@ -254,29 +231,46 @@ async function event(earthquakeInfo: any): Promise<void> {
return;
}
// 国内津波
let domesticTsunami;
const TsunamiMessages = {
"None": "この地震による国内の津波の心配はありません",
"Unknown": "この地震による国内の津波情報はありません",
"Checking": "この地震による国内の津波情報を調査中です",
"NonEffective": "この地震による国内の津波影響は若干の海面変動が予想されますが被害の心配はありません",
"Watch": "この地震により国内で津波注意報が発令しています",
"Warning": "この地震による国内の津波予報があります",
} as { [key: string]: string };
if (earthquakeInfo.earthquake.domesticTsunami === undefined) {
domesticTsunami = "この地震による国内の津波情報はありません";
} else {
domesticTsunami = TsunamiMessages[earthquakeInfo.earthquake.domesticTsunami];
}
// 最大震度
let maxScale: string = "最大深度:";
const maxScales = {
10: "震度1",
20: "震度2",
30: "震度3",
40: "震度4",
45: "震度5弱",
50: "震度5強",
55: "震度6弱",
60: "震度6強",
70: "震度7",
} as { [key: number]: string };
if (
earthquakeInfo.earthquake.maxScale == -1 &&
earthquakeInfo.earthquake.maxScale == -1 ||
earthquakeInfo.earthquake.maxScale === undefined
) {
maxScale = "最大震度情報なし";
} else if (earthquakeInfo.earthquake.maxScale === 10) {
maxScale += "震度1";
} else if (earthquakeInfo.earthquake.maxScale === 20) {
maxScale += "震度2";
} else if (earthquakeInfo.earthquake.maxScale === 30) {
maxScale += "震度3";
} else if (earthquakeInfo.earthquake.maxScale === 40) {
maxScale += "震度4";
} else if (earthquakeInfo.earthquake.maxScale === 45) {
maxScale += "震度5弱";
} else if (earthquakeInfo.earthquake.maxScale === 50) {
maxScale += "震度5強";
} else if (earthquakeInfo.earthquake.maxScale === 55) {
maxScale += "震度6弱";
} else if (earthquakeInfo.earthquake.maxScale === 60) {
maxScale += "震度6強";
} else if (earthquakeInfo.earthquake.maxScale === 70) {
maxScale += "震度7";
maxScale = "最大震度:不明";
} else {
maxScale = `最大震度:${maxScales[earthquakeInfo.earthquake.maxScale]}`;
}
// 警告
@ -312,7 +306,7 @@ async function event(earthquakeInfo: any): Promise<void> {
}
// 対象地域
let areas: string = "";
let areas;
if (earthquakeInfo.points !== undefined) {
const areaNames: Array<string> = Array.from(
@ -324,7 +318,7 @@ async function event(earthquakeInfo: any): Promise<void> {
}
// 詳細
let description: string = "";
let description;
if (
earthquakeInfo.comments.freeFormComment !== "" &&
@ -334,29 +328,33 @@ async function event(earthquakeInfo: any): Promise<void> {
}
// 深さ
let depth: string = "";
let depth;
if (
earthquakeInfo.earthquake.hypocenter.depth !== null ||
earthquakeInfo.earthquake.hypocenter.depth !== undefined ||
earthquakeInfo.earthquake.hypocenter.depth != -1
earthquakeInfo.earthquake.hypocenter.depth !== undefined
) {
if (earthquakeInfo.earthquake.hypocenter.depth === 0) {
depth = "深さ:ごく浅い";
} else if (earthquakeInfo.earthquake.hypocenter.depth === -1) {
depth = "深さ:不明";
} else {
depth = `深さ:${String(earthquakeInfo.earthquake.hypocenter.depth)}km`;
}
}
// マグニチュード
let magnitude: string = "";
let magnitude;
if(
earthquakeInfo.earthquake.hypocenter.magnitude !== null ||
earthquakeInfo.earthquake.hypocenter.magnitude !== undefined ||
earthquakeInfo.earthquake.hypocenter.magnitude != -1
earthquakeInfo.earthquake.hypocenter.magnitude !== undefined
) {
magnitude = `マグニチュードM${String(earthquakeInfo.earthquake.hypocenter.magnitude)}`;
if (earthquakeInfo.earthquake.hypocenter.magnitude === -1) {
depth = "マグニチュード:不明";
} else {
magnitude = `マグニチュードM${String(earthquakeInfo.earthquake.hypocenter.magnitude)}`;
}
}
ueuse(`
@ -370,11 +368,116 @@ async function event(earthquakeInfo: any): Promise<void> {
${areas}
${domesticTsunami}
`);
} else if (earthquakeInfo.code === 552) {
console.log("津波予報情報を受信しました");
// 予報取り消し
if (earthquakeInfo.cancelled) {
ueuse(`
====
${earthquakeInfo.time}
`);
return;
}
let result: string = `
====
${earthquakeInfo.time}
\n
`;
for (let i = 0; i < earthquakeInfo.areas.length; i++) {
const data = earthquakeInfo.areas[i];
// 種類
const gradeMessages = {
"MajorWarning": "大津波警報",
"Warning": "津波警報",
"Watch": "津波注意報",
"Unknown": "不明",
} as { [key: string]: string };
let grade;
if (data.grade === undefined) {
grade = "予報種類:不明";
} else {
grade = `予報種類:${gradeMessages[data.grade]}`;
}
// 直後襲来
let immediate;
if (data.immediate === undefined) {
immediate = "津波の襲来が直後かの情報がありません";
} else if (data.immediate) {
immediate = "### 津波が直ちに襲来します";
} else if (!data.immediate) {
immediate = "津波は直ちには襲来しません";
}
// 第1波
let firstHeight;
if (data.firstHeight === undefined) {
firstHeight = "第1波の情報がありません";
} else if (
data.firstHeight.arrivalTime === undefined &&
data.firstHeight.condition === undefined
) {
firstHeight = "第1波の情報がありません";
} else {
let arrivalTime;
if (data.arrivalTime === undefined) {
arrivalTime = "不明";
} else {
arrivalTime = data.arrivalTime;
}
let condition;
if (data.condition === undefined) {
condition = "不明";
} else {
condition = data.condition;
}
firstHeight = `
1${arrivalTime}
1${condition}
`
}
// 予想高さ
let maxHeight;
if (data.maxHeight.description === undefined) {
maxHeight = "津波の高さ(予想):不明";
} else {
maxHeight = `津波の高さ(予想)${data.maxHeight.description}`;
}
result = `
${data.name}
${grade}
${immediate}
${firstHeight}
${maxHeight}\n\n
`;
}
ueuse(result);
}
}
async function ueuse(text: string) {
const res = await fetch(`https://${config.uwuzu.host}/api/ueuse/create`, {
console.log(text);
/*const res = await fetch(`https://${config.uwuzu.host}/api/ueuse/create/`, {
method: "POST",
body: JSON.stringify({
token: config.uwuzu.apiToken,
@ -384,7 +487,7 @@ async function ueuse(text: string) {
const resData = await res.json();
console.log(`地震情報投稿:${JSON.stringify(resData)}`);
console.log(`地震情報投稿:${JSON.stringify(resData)}`);*/
}
export default function earthquakeNotice(): void {

View File

@ -7,36 +7,38 @@ export default async function followBack() {
// フォロワーを取得
const resMe = await fetch(
`https://${config.uwuzu.host}/api/me?token=${config.uwuzu.apiToken}`,
`https://${config.uwuzu.host}/api/me/`,
{
method: "GET",
// uwuzu v1.5.4で/api/meのPOSTが死んでいるため簡易的にGET
method: "POST",
body: JSON.stringify({
token: config.uwuzu.apiToken,
}),
},
);
const meData: types.meApi = await resMe.json();
console.log(`BOTプロフィール${JSON.stringify(meData)}`);
const followers: Array<string> = meData.follower;
const followers = meData.follower;
// フォロー
for (let i = 0; i < followers.length; i++) {
const followerItem = followers[i];
const resFollow = await fetch(
`https://${config.uwuzu.host}/api/users/follow`,
{
method: "POST",
body: JSON.stringify({
token: config.uwuzu.apiToken,
userid: followerItem,
}),
},
);
setTimeout(async () => {
const resFollow = await fetch(
`https://${config.uwuzu.host}/api/users/follow/`,
{
method: "POST",
body: JSON.stringify({
token: config.uwuzu.apiToken,
userid: followerItem,
}),
},
);
const followData: types.followApi = await resFollow.json();
const followData: types.followApi = await resFollow.json();
console.log(`フォロー:${JSON.stringify(followData)}`);
console.log(`フォロー:${JSON.stringify(followData)}`);
}, 100);
}
}

View File

@ -3,25 +3,32 @@ import { meApi } from "types/types.js";
export default async function unFollowBack() {
const profile: meApi = await
(await fetch(`https://${config.uwuzu.host}/api/me`, {
(await fetch(`https://${config.uwuzu.host}/api/me/`, {
method: "POST",
body: JSON.stringify({
token: config.uwuzu.apiToken,
})
})).json();
profile.followee.forEach(async (followUser: string) => {
for (let i = 0; i < profile.followee.length; i++) {
const followUser = profile.followee[i];
if (
profile.follower[followUser] === undefined ||
profile.follower[followUser] === null
profile.follower.indexOf(followUser) === -1
) {
await fetch(`https://${config.uwuzu.host}/api/users/unfollow`, {
method: "POST",
body: JSON.stringify({
token: config.uwuzu.apiToken,
userId: followUser,
}),
})
setTimeout(async () => {
const req = await fetch(`https://${config.uwuzu.host}/api/users/unfollow/`, {
method: "POST",
body: JSON.stringify({
token: config.uwuzu.apiToken,
userId: followUser,
}),
});
const res = await req.text();
console.log(`フォロー解除: ${res}`)
}, 100);
}
});
}
}

View File

@ -1,7 +1,7 @@
{
"compilerOptions": {
"target": "es2022",
"module": "ES2022",
"target": "ES2024",
"module": "ESNext",
"moduleResolution": "bundler",
"outDir": "./dist",
"esModuleInterop": true,
@ -17,6 +17,7 @@
"paths": {
"ws": ["./node_modules/ws/index.js"],
"@types/ws": ["./node_modules/@types/ws/index.d.ts"]
}
}
},
"removeComments": true,
},
}

4
types/types.d.ts vendored
View File

@ -12,9 +12,9 @@ export interface meApi {
user_icon: string;
user_header: string;
registered_date: string;
followee: Array;
followee: Array<string>;
followee_cnt: number;
follower: Array;
follower: Array<string>;
follower_cnt: number;
ueuse_cnt: number;
isBot: Boolean;