diff --git a/LICENSE b/LICENSE index dc11dcc..491e2b9 100644 --- a/LICENSE +++ b/LICENSE @@ -4,7 +4,7 @@ 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 + https://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, diff --git a/README.md b/README.md index 9f6c4cb..6cc4eaa 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,37 @@ -# uwuzuお知らせBOT +# noticeUwuzu -# uwuzuお知らせBOTについて +# Overview -uwuzuで動作するお知らせBOTです。 +Automatic notification bot for uwuzu -# 設定 -examples/config.tsをプロジェクトルートへ移動し各設定を更新してください。 +# Functions -# サーバー起動 +- Time notification +- Earthquake information notification + - Earthquake Early Warning + - Earthquake occurs + - Tsunami forecast +- Weather notification +- Startup requirements check + - Check package existence + - Required package version check + - Check uwuzu API + - Check the configuration file +- Version change notification +- ASCII art at startup +- Confirm normal completion -``` +# Config +An example configuration file is available in `examples/config.ts`. + +# Start the server + +```bash npm install npm run build npm run start ``` +Recommended Node.js version: v22.16.0 -※Node.js・npmがインストールされている必要があります。 - -# ライセンス -Apache 2.0 License +# License +[Apache 2.0 License](https://www.apache.org/licenses/LICENSE-2.0) diff --git a/main.ts b/main.ts index 9aca990..89a857d 100644 --- a/main.ts +++ b/main.ts @@ -9,33 +9,33 @@ import * as cron from "node-cron"; // 機能読み込み 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 Commands from "scripts/commands/main.js"; -// アスキーアート読み込み +// その他機能 import asciiArt from "./scripts/asciiart.js"; asciiArt(); - -// フォロー機能読み込み -import follows from "./scripts/follow/main.js"; - -// 正常終了確認読み込み import successExit from "./scripts/successExit.js"; successExit(); // 地震情報観測開始 earthquakeNotice(); -/*// 時報・フォローバック(毎時) +// 時報(1時間/1回) cron.schedule("0 * * * *", () => { timeNotice(); - follows(); }); -// 天気お知らせ(毎日7:01) -cron.schedule("10 0 7 * * *", () => { +// コマンド(10分/1回) +cron.schedule('*/10 * * * *', () => { + Commands(); +}); + +// 天気お知らせ(毎日7:00) +cron.schedule("0 7 * * *", () => { weatherNotice(); -});*/ +}); // 起動表示 console.log("BOTサーバーが起動しました"); diff --git a/package.json b/package.json index ecb256a..10f7e5c 100644 --- a/package.json +++ b/package.json @@ -1,13 +1,14 @@ { "name": "notice-uwuzu", - "version": "v6.5(LTS)@uwuzu1.5.4", + "version": "v7.0(unconfirmed)@uwuzu1.5.4", "description": "Notice Bot for uwuzu", "main": "dist/main.js", "scripts": { "start": "node .", "build": "tsc", "main": "tsc && node .", - "dev": "tsx main.ts" + "dev": "tsx main.ts", + "clean": "tsc && node dist/scripts/clean/main.js" }, "keywords": [ "uwuzu", @@ -28,10 +29,11 @@ "dependencies": { "@types/date-fns": "^2.5.3", "@types/dotenv": "^6.1.1", + "@types/node": "^24.0.7", "@types/node-cron": "^3.0.11", "@types/nodemailer": "^6.4.17", - "@types/node": "^24.0.7", "@types/ws": "^8.18.1", + "child_process": "^1.0.2", "date-fns": "^4.1.0", "fs": "^0.0.1-security", "node-cron": "^4.1.1", diff --git a/scripts/clean/logsDel.ts b/scripts/clean/logsDel.ts new file mode 100644 index 0000000..9c04bf5 --- /dev/null +++ b/scripts/clean/logsDel.ts @@ -0,0 +1,16 @@ +import { unlinkSync } from "fs"; + +export default function logsDelete() { + console.log("ログを削除中します"); + + try { + unlinkSync("logs/boot.json"); + unlinkSync("logs/version.txt"); + + console.log("ログファイルを削除しました"); + console.log("----------------"); + } catch (err) { + console.log("ログファイルの削除中にエラーが発生しました:\n", err); + process.exit(); + } +} diff --git a/scripts/clean/main.ts b/scripts/clean/main.ts new file mode 100644 index 0000000..ebcd921 --- /dev/null +++ b/scripts/clean/main.ts @@ -0,0 +1,7 @@ +import logsDelete from "./logsDel.js"; +import packageLockJsonDelete from "./packageLockDel.js"; +import npmInstall from "./npmInstall.js"; + +logsDelete(); +packageLockJsonDelete(); +npmInstall(); diff --git a/scripts/clean/npmInstall.ts b/scripts/clean/npmInstall.ts new file mode 100644 index 0000000..8d69bf2 --- /dev/null +++ b/scripts/clean/npmInstall.ts @@ -0,0 +1,23 @@ +import { execSync } from "child_process"; + +export default function npmInstall() { + // npm install実行 + console.log("npm installを実行します"); + + try { + const npmInstall = execSync("npm install"); + + console.log("----"); + console.log("$ npm install"); + console.log(npmInstall.toString()); + console.log("----"); + + console.log("npm installを実行しました"); + } catch (err) { + console.log("npm installの実行中にエラーが発生しました:\n", err); + process.exit(); + } + + console.log("初期化が完了しました"); + process.exit(); +} diff --git a/scripts/clean/packageLockDel.ts b/scripts/clean/packageLockDel.ts new file mode 100644 index 0000000..22cb0d7 --- /dev/null +++ b/scripts/clean/packageLockDel.ts @@ -0,0 +1,15 @@ +import { unlinkSync } from "fs"; + +export default function packageLockJsonDelete() { + console.log("package-lock.jsonを削除します"); + + try { + unlinkSync("package-lock.json"); + + console.log("package-lock.jsonを削除しました"); + console.log("----------------"); + } catch (err) { + console.log("package-lock.jsonの削除中にエラーが発生しました:\n", err); + process.exit(); + } +} diff --git a/scripts/commands/follow.ts b/scripts/commands/follow.ts new file mode 100644 index 0000000..2199972 --- /dev/null +++ b/scripts/commands/follow.ts @@ -0,0 +1,32 @@ +import { ueuse } from "types/types.js"; +import config from "../../config.js"; + +export default async function Follow(data: ueuse) { + const followReq = await fetch(`https://${config.uwuzu.host}/api/users/follow/`, { + method: "POST", + body: JSON.stringify({ + token: config.uwuzu.apiToken, + userid: data.account.userid, + }), + }); + + const followRes = await followReq.json(); + + console.log("フォロー: ", followRes); + + + const noticeReq = await fetch(`https://${config.uwuzu.host}/api/ueuse/create/`, { + method: "POST", + body: JSON.stringify({ + token: config.uwuzu.apiToken, + text: ` + ${data.account.username}さんをフォローしました + `, + replyid: data.uniqid, + }), + }); + + const noticeRes = await noticeReq.json(); + + console.log("フォロー通知: ", noticeRes); +} diff --git a/scripts/commands/main.ts b/scripts/commands/main.ts new file mode 100644 index 0000000..978c723 --- /dev/null +++ b/scripts/commands/main.ts @@ -0,0 +1,114 @@ +import * as fs from "fs"; +import config from "../../config.js"; +import type { ueuse } from "types/types.js"; + +const initialFile: Array = []; + +// コマンド読み込み +import Follow from "./follow.js"; +import UnFollow from "./unfollow.js"; +import Weather from "./weather.js"; + +// 初期化 +if (!fs.existsSync("logs/alreadyCommands.json")) { + fs.writeFileSync( + "logs/alreadyCommands.json", + JSON.stringify(initialFile), + "utf-8", + ); +} + +// 対応済みユーズ一覧 +const alreadyCommands: Array = JSON.parse(fs.readFileSync("alreadyCommands.json", "utf-8")); + +function cutAfterChar(str: string, char: string) { + const index = str.indexOf(char); + + if (index === -1) { + return ""; + } + + return str.substring(index + 1); +} + +async function Reply(text: string, reply: string) { + const req = await fetch(`https://${config.uwuzu.host}/api/ueuse/create`, { + method: "POST", + body: JSON.stringify({ + token: config.uwuzu.apiToken, + text: text, + replyid: reply, + }), + }); + + const res = await req.json(); + + return res; +} + +function alreadyAdd(data: string) { + alreadyCommands[alreadyCommands.length] = data; + + fs.writeFileSync( + "logs/alreadyCommands.json", + JSON.stringify(alreadyCommands), + "utf-8", + ); +} + +export default async function Commands() { + const mentions: Array = await (await fetch( + `https://${config.uwuzu.host}/api/ueuse/mentions/`, { + method: "POST", + body: JSON.stringify({ + token: config.uwuzu.apiToken, + }), + } + )).json(); + + console.log("----------------"); + console.log("コマンド処理"); + + for (let i = 0; i < mentions.length; i++) { + const data = mentions[i]; + + // 除外ユーズ + if (alreadyCommands.indexOf(data.uniqid) !== -1) { + break; + } + + if (data.text.charAt(0) === "!") { + break; + } + + // コマンド処理 + console.log("--------"); + + const commandName = cutAfterChar(data.text, "/"); + + switch (commandName) { + case "follow": + alreadyAdd(data.uniqid); + Follow(data); + break; + case "unfollow": + alreadyAdd(data.uniqid); + UnFollow(data); + break; + case "weather": + alreadyAdd(data.uniqid); + Weather(data); + break; + default: + alreadyAdd(data.uniqid); + + const reply = await Reply(` + 不明なコマンドです。 + コマンド実行を除外する場合は1文字目に\`!\`を入れてください。 + `, data.uniqid); + + console.log("未対応コマンド: ", reply); + break; + } + } +} diff --git a/scripts/commands/unfollow.ts b/scripts/commands/unfollow.ts new file mode 100644 index 0000000..05c3b94 --- /dev/null +++ b/scripts/commands/unfollow.ts @@ -0,0 +1,31 @@ +import { ueuse } from "types/types.js"; +import config from "../../config.js"; + +export default async function UnFollow(data: ueuse) { + const unfollowReq = await fetch(`https://${config.uwuzu.host}/api/users/unfollow/`, { + method: "POST", + body: JSON.stringify({ + token: config.uwuzu.apiToken, + userid: data.account.userid, + }), + }); + + const unfollowRes = await unfollowReq.json(); + + console.log("フォロー解除: ", unfollowRes); + + const noticeReq = await fetch(`https://${config.uwuzu.host}/api/ueuse/create/`, { + method: "POST", + body: JSON.stringify({ + token: config.uwuzu.apiToken, + text: ` + ${data.account.username}さんをフォロー解除しました + `, + replyid: data.uniqid, + }), + }); + + const noticeRes = await noticeReq.json(); + + console.log("フォロー解除通知: ", noticeRes); +} diff --git a/scripts/commands/weather.ts b/scripts/commands/weather.ts new file mode 100644 index 0000000..e762ea5 --- /dev/null +++ b/scripts/commands/weather.ts @@ -0,0 +1,6 @@ +import { weatherReply } from "scripts/weatherNotice"; +import { ueuse } from "types/types.js"; + +export default function Weather(data: ueuse) { + weatherReply(data.uniqid); +} diff --git a/scripts/follow/follow.ts b/scripts/follow/follow.ts deleted file mode 100644 index 49fd1a6..0000000 --- a/scripts/follow/follow.ts +++ /dev/null @@ -1,44 +0,0 @@ -import type * as types from "types/types"; - -import config from "../../config.js"; - -export default async function followBack() { - console.log("----------------"); - - // フォロワーを取得 - const resMe = await fetch( - `https://${config.uwuzu.host}/api/me/`, - { - method: "POST", - body: JSON.stringify({ - token: config.uwuzu.apiToken, - }), - }, - ); - - const meData: types.meApi = await resMe.json(); - - const followers = meData.follower; - - // フォロー - for (let i = 0; i < followers.length; i++) { - const followerItem = followers[i]; - - 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(); - - console.log(`フォロー:${JSON.stringify(followData)}`); - }, 100); - } -} diff --git a/scripts/follow/main.ts b/scripts/follow/main.ts deleted file mode 100644 index 5edd24d..0000000 --- a/scripts/follow/main.ts +++ /dev/null @@ -1,7 +0,0 @@ -import followBack from "./follow.js"; -import unFollowBack from "./unfollow.js"; - -export default function follows() { - unFollowBack(); - followBack(); -} diff --git a/scripts/follow/unfollow.ts b/scripts/follow/unfollow.ts deleted file mode 100644 index 78817f6..0000000 --- a/scripts/follow/unfollow.ts +++ /dev/null @@ -1,34 +0,0 @@ -import config from "../../config.js"; -import { meApi } from "types/types.js"; - -export default async function unFollowBack() { - const profile: meApi = await - (await fetch(`https://${config.uwuzu.host}/api/me/`, { - method: "POST", - body: JSON.stringify({ - token: config.uwuzu.apiToken, - }) - })).json(); - - for (let i = 0; i < profile.followee.length; i++) { - const followUser = profile.followee[i]; - - if ( - profile.follower.indexOf(followUser) === -1 - ) { - 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); - } - } -} diff --git a/scripts/weatherNotice.ts b/scripts/weatherNotice.ts index 1dc5d6c..209b26e 100644 --- a/scripts/weatherNotice.ts +++ b/scripts/weatherNotice.ts @@ -4,7 +4,7 @@ import type * as types from "types/types.js"; import config from "../config.js"; -export default async function weatherNotice() { +export async function weatherNotice() { console.log("----------------"); // 仮投稿 @@ -26,7 +26,10 @@ export default async function weatherNotice() { console.log(`天気仮投稿:${JSON.stringify(ueuseData)}`); + weatherReply(ueuseData.uniqid); +} +export async function weatherReply(uniqid: string) { // インデックス const splitCount = config.weather.splitCount; const total = cityList.length; @@ -109,13 +112,13 @@ export default async function weatherNotice() { body: JSON.stringify({ token: config.uwuzu.apiToken, text: weatherResults[i], - replyid: ueuseData.uniqid + replyid: uniqid, }), }, ); const replyData: types.ueuseCreateApi = await resReply.json(); - console.log(`天気投稿:${JSON.stringify(replyData)}`); + console.log(`天気返信:${JSON.stringify(replyData)}`); } } diff --git a/types/types.d.ts b/types/types.d.ts index 1d82b53..9fbeac5 100644 --- a/types/types.d.ts +++ b/types/types.d.ts @@ -23,6 +23,28 @@ export interface meApi { language: String; } +export interface ueuse { + uniqid: string; + text: string; + account: { + username: string; + userid: string; + user_icon: string; + user_header: string; + }; + photo1: Base64URLString; + photo2: Base64URLString; + photo3: Base64URLString; + photo4: Base64URLString; + video1: Base64URLString; + favorite: Array; + favorite_cnt: string; + datetime: string; + abi: string; + abidatetime: string; + nsfw: boolean; +} + export interface ueuseCreateApi { uniqid: string; userid: string;