2026.4.0-alpha.0

This commit is contained in:
2026-04-27 19:55:50 +09:00
commit ecbfc828d6
24 changed files with 3131 additions and 0 deletions
+27
View File
@@ -0,0 +1,27 @@
import client from "@/lib/client";
import ueuseModule from "better-uwuzu-sdk/types/1.6.8/types/modules/ueuse";
import i18next from "i18next";
export default async function followCommand(ueuse: ueuseModule) {
const follow = await client.request("users/follow", {
userid: ueuse.account.userid,
});
if (!follow.success) {
console.warn("フォローに失敗:", follow.error_code);
return;
}
console.log("フォロー:", follow.userid);
const notice = await client.request("ueuse/create", {
text: i18next.t("followedNotification", { username: ueuse.account.username }),
replyid: ueuse.uniqid,
});
if (!notice.success) {
console.warn("フォロー通知に失敗:", notice.error_code);
}
console.log("フォロー通知:", notice.uniqid);
}
+66
View File
@@ -0,0 +1,66 @@
import client from "@/lib/client";
import ueuseModule from "better-uwuzu-sdk/types/1.6.8/types/modules/ueuse";
import i18next from "i18next";
import { EOL } from "node:os";
const helps = [
"help",
"follow",
"unfollow",
"weather",
"miq",
];
export default async function helpCommand(ueuse: ueuseModule, args: string[]) {
if (args[1] !== undefined) {
if (!(helps.includes(args[1]))) {
const response = await client.request("ueuse/create", {
text: i18next.t("invalidOption", { option: args[1], command: "help" }),
replyid: ueuse.uniqid,
});
if (!response.success) {
console.warn("コマンド詳細の返信に失敗:", response.error_code);
return;
}
console.warn("コマンド詳細:", response.uniqid);
return;
}
const response = await client.request("ueuse/create", {
text: i18next.t(`fullHelp${args[1].charAt(0).toUpperCase()}${args[1].slice(1)}`),
replyid: ueuse.uniqid,
});
if (!response.success) {
console.warn("コマンド詳細の返信に失敗:", response.error_code);
return;
}
console.warn("コマンド詳細:", response.uniqid);
return;
}
let summarys = "";
for (let i = 0; i < helps.length; i++) {
const help = helps[i];
if (!help)
break;
summarys += `${i18next.t(`help${help.charAt(0).toUpperCase()}${help.slice(1)}`)}${EOL}`;
}
const response = await client.request("ueuse/create", {
text: summarys.trim(),
replyid: ueuse.uniqid,
});
if (!response.success) {
console.warn("コマンド概要の返信に失敗:", response.error_code);
return;
}
console.warn("コマンド概要:", response.uniqid);
}
+127
View File
@@ -0,0 +1,127 @@
import client from "@/lib/client";
import Memory from "@/lib/memory";
import ueuseModule from "better-uwuzu-sdk/types/1.6.8/types/modules/ueuse";
import i18next from "i18next";
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";
export default async function commandExecute() {
let ueuses: ueuseModule[] = [];
{
const response = await client.request("me/notification/", {
limit: 20,
});
if (response.success) {
for (const notification of response.data) {
if (notification.category !== "reply")
break;
if (!notification.valueid) {
console.warn("返信通知にvalueidが存在しないため、スキップします");
break;
}
const ueuseResponse = await client.request("ueuse/get", {
uniqid: notification.valueid,
});
if (!ueuseResponse.success || !ueuseResponse.data[0]) {
console.warn("返信通知からユーズを参照できないため、スキップします");
break;
}
ueuses.push(ueuseResponse.data[0]);
}
} else {
console.warn("返信通知の取得に失敗しましたが、続行します");
}
}
{
const response = await client.request("ueuse/mentions", {
limit: 20,
});
if (response.success) {
ueuses.push(...response.data);
} else {
console.warn("メンションの取得に失敗しましたが、続行します");
}
}
ueuses = [...new Set(ueuses)];
for (const ueuse of ueuses) {
const repliedUeuse = (Memory.memory["repliedUeuse"] as string[]);
if (repliedUeuse.includes(ueuse.uniqid)) {
console.log("既に応答しているため、スキップします");
break;
}
const mem = Memory.memory;
let text = ueuse.text;
text = text.replace(`@${mem.userid}`, "");
text = text.trim();
const rows = text.split(/\r\n|\r|\n/).map(row => row.trim());
const commandRow = rows.filter(row => row.startsWith("/"))[0];
if (!commandRow || commandRow === "") {
console.warn("コマンドが本文から参照できません");
const response = await client.request("ueuse/create", {
text: i18next.t("commandNotFound"),
replyid: ueuse.uniqid,
});
if (!response.success)
console.warn("ユーズの作成に失敗しました:", response.error_code);
break;
}
const args = commandRow.replace("/", "").split(" ");
switch (args[0]) {
case "help":
await helpCommand(ueuse, args);
break;
case "weather":
await weatherCommand(ueuse);
break;
case "follow":
await followCommand(ueuse);
break;
case "unfollow":
await unfollowCommand(ueuse);
break;
case "miq":
await miqCommand(ueuse, args);
break;
default:
console.warn("不明なコマンドが入力されました:", args[0]);
const response = await client.request("ueuse/create", {
text: i18next.t("unknownCommand", { command: args[0] }),
replyid: ueuse.uniqid,
});
if (!response.success)
console.warn("ユーズの作成に失敗しました:", response.error_code);
break;
}
{
const repliedUeuse = (Memory.memory["repliedUeuse"] as string[]);
repliedUeuse.push(ueuse.uniqid);
const mem = Memory.memory;
mem["repliedUeuse"] = repliedUeuse;
Memory.memory = mem;
}
}
}
+418
View File
@@ -0,0 +1,418 @@
import client from "@/lib/client";
import Memory from "@/lib/memory";
import ueuseModule from "better-uwuzu-sdk/types/1.6.8/types/modules/ueuse";
import i18next from "i18next";
import MiQ from "miq";
import { EOL } from "node:os";
export default async function miqCommand(ueuse: ueuseModule, args: string[]) {
if (!args[1]) {
const response = await client.request("ueuse/create", {
text: i18next.t("lackOption", { command: "miq" }),
replyid: ueuse.uniqid,
});
if (!response.success) {
console.warn("返信に失敗:", response.error_code);
}
return;
}
let mem = Memory.memory;
switch (args[1]) {
case "generate":
const itUeuse = await client.request("ueuse/get", {
uniqid: ueuse.replyid,
});
if (!itUeuse.success || !itUeuse.data[0]) {
console.warn("返信元のユーズの取得に失敗:", "error_code" in itUeuse
? itUeuse.error_code
: "データなし");
const response = await client.request("ueuse/create", {
text: i18next.t("replySourceFailed"),
replyid: ueuse.uniqid,
});
if (!response.success) {
console.log("返信に失敗:", response.error_code);
}
return;
}
mem = Memory.memory;
const permission = mem["permissions"][itUeuse.data[0].account.userid] ?? "consent";
switch (permission) {
case "me":
if (itUeuse.data[0].account.userid !== ueuse.account.userid) {
const response = await client.request("ueuse/create", {
text: i18next.t("miqPermissionMe"),
replyid: ueuse.uniqid,
});
if (!response.success) {
console.warn("返信に失敗:", response.error_code);
}
return;
}
case "consent":
if (itUeuse.data[0].account.userid !== ueuse.account.userid) {
const response = await client.request("ueuse/create", {
text: i18next.t("miqPermissionConsent", { userid: itUeuse.data[0].account.userid }),
replyid: ueuse.uniqid,
});
if (!response.success) {
console.warn("返信に失敗:", response.error_code);
}
return;
}
}
const result = await MiQ({
type: "Base64Data",
color: (args[2] ?? "") === "color",
text: itUeuse.data[0].text,
iconURL: itUeuse.data[0].account.user_icon,
userid: itUeuse.data[0].account.userid,
username: itUeuse.data[0].account.username,
});
if (!(typeof result === "string")) {
const response = await client.request("ueuse/create", {
text: i18next.t("miqGenerateFailed"),
replyid: ueuse.uniqid,
});
if (!response.success) {
console.warn("返信に失敗:", response.error_code);
}
return;
}
const response = await client.request("ueuse/create", {
text: i18next.t("miqSuccess", { message: (args[2] ?? "") === "color"
? "カラーモードで生成しました。"
: "モノクロモードで生成しました。"
}),
media: {
photo: [result],
},
replyid: ueuse.uniqid,
});
if (!response.success) {
console.warn("返信に失敗:", response.error_code);
return;
}
console.log("MiQを生成:", response.uniqid);
break;
case "permission":
if (args[2] === undefined) {
mem = Memory.memory;
const permission = mem["permissions"][ueuse.account.userid] ?? "consent";
const response = await client.request("ueuse/create", {
text: i18next.t("permissionResponse", { permission }),
replyid: ueuse.uniqid,
});
if (!response.success) {
console.warn("返信に失敗:", response.error_code);
return;
}
console.log("返信:", response.uniqid);
return;
}
const availablePermission = ["me", "everyone", "consent"];
if (!(availablePermission.includes(args[2]))) {
const response = await client.request("ueuse/create", {
text: i18next.t("invalidOption", { option: args[2], command: "miq" }),
replyid: ueuse.uniqid,
});
if (!response.success) {
console.warn("返信に失敗:", response.error_code);
return;
}
console.log("返信:", response.uniqid);
return;
}
{
mem = Memory.memory;
mem["permissions"][ueuse.account.userid] = args[2];
Memory.memory = mem;
const response = await client.request("ueuse/create", {
text: i18next.t("permissionChangeSuccess", { username: ueuse.account.username, permission: args[2] }),
replyid: ueuse.uniqid,
});
if (!response.success) {
console.warn("返信に失敗:", response.error_code);
return;
}
console.log("返信:", response.uniqid);
}
break;
case "allow":
if (ueuse.replyid === "") {
const response = await client.request("ueuse/create", {
text: i18next.t("injusticeFormat"),
replyid: ueuse.uniqid,
});
if (!response.success) {
console.warn("返信に失敗:", response.error_code);
return;
}
console.log("返信:", response.uniqid);
return;
}
{
mem = Memory.memory;
const permission = mem["permissions"][ueuse.account.userid] ?? "consent";
if (permission !== "consent") {
const response = await client.request("ueuse/create", {
text: i18next.t("permisionIsNotConsent"),
replyid: ueuse.uniqid,
});
if (!response.success) {
console.warn("返信に失敗:", response.error_code);
return;
}
console.log("返信:", response.uniqid);
return;
}
}
const confirmUeuse = await client.request("ueuse/get", {
uniqid: ueuse.replyid,
});
if (!confirmUeuse.success || !confirmUeuse.data[0]) {
console.warn("返信元のユーズの取得に失敗:", "error_code" in confirmUeuse
? confirmUeuse.error_code
: "データなし");
const response = await client.request("ueuse/create", {
text: i18next.t("replySourceFailed"),
replyid: ueuse.uniqid,
});
if (!response.success) {
console.log("返信に失敗:", response.error_code);
}
return;
}
if (confirmUeuse.data[0].replyid === "") {
const response = await client.request("ueuse/create", {
text: i18next.t("injusticeFormat"),
replyid: ueuse.uniqid,
});
if (!response.success) {
console.warn("返信に失敗:", response.error_code);
return;
}
console.log("返信:", response.uniqid);
return;
}
mem = Memory.memory;
if (confirmUeuse.data[0].account.userid !== mem["userid"]) {
console.warn("返信元のユーズがBotではない:", "error_code" in confirmUeuse
? confirmUeuse.error_code
: "データなし");
const response = await client.request("ueuse/create", {
text: i18next.t("replySourceIsNotThis"),
replyid: ueuse.uniqid,
});
if (!response.success) {
console.log("返信に失敗:", response.error_code);
}
return;
}
const requestUeuse = await client.request("ueuse/get", {
uniqid: confirmUeuse.data[0].replyid,
});
if (!requestUeuse.success || !requestUeuse.data[0]) {
console.warn("返信元のユーズの取得に失敗:", "error_code" in requestUeuse
? requestUeuse.error_code
: "データなし");
const response = await client.request("ueuse/create", {
text: i18next.t("replySourceFailed"),
replyid: ueuse.uniqid,
});
if (!response.success) {
console.log("返信に失敗:", response.error_code);
}
return;
}
if (requestUeuse.data[0].replyid === "") {
const response = await client.request("ueuse/create", {
text: i18next.t("injusticeFormat"),
replyid: ueuse.uniqid,
});
if (!response.success) {
console.warn("返信に失敗:", response.error_code);
return;
}
console.log("返信:", response.uniqid);
return;
}
const sourceUeuse = await client.request("ueuse/get", {
uniqid: requestUeuse.data[0].replyid,
});
if (!sourceUeuse.success || !sourceUeuse.data[0]) {
console.warn("返信元のユーズの取得に失敗:", "error_code" in sourceUeuse
? sourceUeuse.error_code
: "データなし");
const response = await client.request("ueuse/create", {
text: i18next.t("replySourceFailed"),
replyid: ueuse.uniqid,
});
if (!response.success) {
console.log("返信に失敗:", response.error_code);
}
return;
}
if (sourceUeuse.data[0].account.userid !== ueuse.account.userid) {
const response = await client.request("ueuse/create", {
text: i18next.t("replySourceIsNotSourceUser"),
replyid: ueuse.uniqid,
});
if (!response.success) {
console.log("返信に失敗:", response.error_code);
}
console.warn("ソースのユーズと/miq allowのユーザーが一致しない:", );
return;
}
{
mem = Memory.memory;
let text = requestUeuse.data[0].text;
text = text.replace(`@${mem.userid}`, "");
text = text.trim();
const rows = text.split(/\r\n|\r|\n/).map(row => row.trim());
const commandRow = rows.filter(row => row.startsWith("/"))[0];
if (!commandRow || commandRow === "") {
console.warn("コマンドが本文から参照できません");
const response = await client.request("ueuse/create", {
text: i18next.t("commandNotFound"),
replyid: requestUeuse.data[0].uniqid,
});
if (!response.success)
console.warn("ユーズの作成に失敗しました:", response.error_code);
{
const response = await client.request("ueuse/create", {
text: i18next.t("injusticeFormat"),
replyid: ueuse.uniqid,
});
if (!response.success) {
console.warn("返信に失敗:", response.error_code);
return;
}
console.log("返信:", response.uniqid);
}
break;
}
const requestUeuseArgs = commandRow.replace("/", "").split(" ");
const result = await MiQ({
type: "Base64Data",
color: (requestUeuseArgs[2] ?? "") === "color",
text: sourceUeuse.data[0].text,
iconURL: sourceUeuse.data[0].account.user_icon,
userid: sourceUeuse.data[0].account.userid,
username: sourceUeuse.data[0].account.username,
});
if (!(typeof result === "string")) {
const response = await client.request("ueuse/create", {
text: i18next.t("miqGenerateFailed"),
replyid: ueuse.uniqid,
});
if (!response.success) {
console.warn("返信に失敗:", response.error_code);
}
return;
}
const response = await client.request("ueuse/create", {
text: i18next.t("miqSuccess", { message: ((requestUeuseArgs[2] ?? "") === "color"
? "カラーモードで生成しました。"
: "モノクロモードで生成しました。")
+ EOL + `@${requestUeuse.data[0].account.userid}`,
}),
media: {
photo: [result],
},
replyid: ueuse.uniqid,
});
if (!response.success) {
console.warn("返信に失敗:", response.error_code);
return;
}
console.log("MiQを生成:", response.uniqid);
}
break;
}
}
+27
View File
@@ -0,0 +1,27 @@
import client from "@/lib/client";
import ueuseModule from "better-uwuzu-sdk/types/1.6.8/types/modules/ueuse";
import i18next from "i18next";
export default async function unfollowCommand(ueuse: ueuseModule) {
const unfollow = await client.request("users/unfollow", {
userid: ueuse.account.userid,
});
if (!unfollow.success) {
console.warn("フォロー解除に失敗:", unfollow.error_code);
return;
}
console.log("フォロー解除:", unfollow.userid);
const notice = await client.request("ueuse/create", {
text: i18next.t("unfollowedNotification", { username: ueuse.account.username }),
replyid: ueuse.uniqid,
});
if (!notice.success) {
console.warn("フォロー解除通知に失敗:", notice.error_code);
}
console.log("フォロー解除通知:", notice.uniqid);
}
+6
View File
@@ -0,0 +1,6 @@
import ueuseModule from "better-uwuzu-sdk/types/1.6.8/types/modules/ueuse";
import { weatherReply } from "@/feature/weatherNotice";
export default async function weatherCommand(ueuse: ueuseModule) {
await weatherReply(ueuse.uniqid);
}
+269
View File
@@ -0,0 +1,269 @@
import client from "@/lib/client";
import config from "@/lib/config";
import { format } from "date-fns";
import i18next from "i18next";
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";
const P2PEarthquakeClient = () => {
console.log("P2P地震情報のWebSocketに接続します");
const socket = new WebSocket(WEBSOCKET_URL);
socket.addEventListener("open", () => {
console.log("P2P地震情報のWebSocketに接続しました");
});
socket.addEventListener("message", async (event) => {
let message;
try {
message = typeof event.data === "string"
? JSON.parse(event.data)
: event.data;
} catch (err) {
console.error(`メッセージのパースでエラーが発生: ${err}`);
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);
}
});
}
export default P2PEarthquakeClient;
+16
View File
@@ -0,0 +1,16 @@
import client from "@/lib/client";
import { format } from "date-fns";
import i18next from "i18next";
export default async function timeNotice() {
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;
}
console.log("時報投稿:", response.uniqid);
}
+149
View File
@@ -0,0 +1,149 @@
import client from "@/lib/client";
import config from "@/lib/config";
import i18next from "i18next";
import { readFileSync } from "node:fs";
import { EOL } from "node:os";
const cityList = [
"016010",
"020010",
"030010",
"040010",
"050010",
"060010",
"070010",
"080010",
"090010",
"100010",
"110010",
"120010",
"130010",
"140010",
"150010",
"160010",
"170010",
"180010",
"190010",
"200010",
"210010",
"220010",
"230010",
"240010",
"250010",
"260010",
"270000",
"280010",
"290010",
"300010",
"310010",
"320010",
"330010",
"340010",
"350010",
"360010",
"370000",
"380010",
"390010",
"400010",
"410010",
"420010",
"430010",
"440010",
"450010",
"460010",
"471010",
];
export async function weatherNotice() {
const provisionalUeuse = await client.request("ueuse/create", {
text: i18next.t("weatherProvisional"),
});
if (!provisionalUeuse.success) {
console.error("天気仮投稿に失敗しました:", provisionalUeuse.error_code);
return;
}
console.log("天気仮投稿:", provisionalUeuse.uniqid);
weatherReply(provisionalUeuse.uniqid);
}
export async function weatherReply(uniqid: string) {
// インデックス
const splitCount = config.weather.splits;
const total = cityList.length;
const chunkSizes = Array(splitCount).fill(0).map((_, i) =>
Math.floor((total + i) / splitCount)
);
// 分割インデックス
let start = 0;
const ranges: [number, number][] = chunkSizes.map(size => {
const range: [number, number] = [start, start + size];
start += size;
return range;
});
// 配列作成
const weatherResults = Array(splitCount).fill("");
// package.json取得
const packageJson = JSON.parse(readFileSync(`${import.meta.dirname}/../../package.json`, "utf-8"));
// 天気取得
for (let chunkIndex = 0; chunkIndex < splitCount; chunkIndex++) {
const range = ranges[chunkIndex];
if (!range) continue;
const [chunkStart, chunkEnd] = range;
for (let i = chunkStart; i < chunkEnd; i++) {
const res = await fetch(`https://weather.tsukumijima.net/api/forecast/city/${cityList[i]}`, {
headers: {
"User-Agent": `noticeUwuzu/${packageJson.version}`,
},
});
const data = await res.json();
const today = data.forecasts[0];
// 天気
const weather = today.telop ?? "取得できませんでした";
const maxTemp = today.temperature.max.celsius
? `${today.temperature.max.celsius}`
: "取得できませんでした";
const minTemp = today.temperature.min.celsius
? `${today.temperature.min.celsius}`
: "取得できませんでした";
const chanceOfRain = (
today.chanceOfRain.T06_12 !== null &&
today.chanceOfRain.T06_12 !== "--%"
)
? today.chanceOfRain.T06_12
: "取得できませんでした";
weatherResults[chunkIndex] += `${i18next.t("weatherReply", {
city: data.location.city,
weather,
maxTemp,
minTemp,
chanceOfRain,
})}${EOL.repeat(2)}`;
}
}
// 分割投稿
for (let i = 0; i < splitCount; i++) {
const replyUeuse = await client.request("ueuse/create", {
text: weatherResults[i].trim(),
replyid: uniqid,
});
if (!replyUeuse.success) {
console.error("天気返信に失敗しました:", replyUeuse.error_code);
}
console.log("天気返信:", replyUeuse.uniqid);
}
}