Compare commits
6 Commits
c4e0d2763b
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
| dd4a5beb03 | |||
| 0fe88a36ab | |||
| 6b35112d1e | |||
| 1dee79e3ec | |||
| 8b15157c81 | |||
| bd2c86658c |
@@ -1,5 +0,0 @@
|
||||
# # ###### ##### ### ###### ####### # # # # # # ###### # #
|
||||
## # # # # # # # # # # # # # # # # #
|
||||
# ## # # # # # # ####### # # # # # # # # ## # #
|
||||
# ## # # # # # # # # # # # # # # #
|
||||
# # ###### # ### ###### ####### ###### # # ###### ###### ######
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "earthquake-bot-for-mtweet",
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.3",
|
||||
"type": "module",
|
||||
"main": "dist/index.js",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { readFileSync, writeFileSync } from "node:fs";
|
||||
import { readFileSync } from "node:fs";
|
||||
import sharp from "sharp";
|
||||
|
||||
/**
|
||||
|
||||
+45
-56
@@ -6,72 +6,61 @@ import { EOL } from "node:os";
|
||||
import { WebSocket } from "ws";
|
||||
import generateImage from "@/earthquake/generateImage";
|
||||
|
||||
if (config.earthquake?.useHistoryData) {
|
||||
console.log("過去の地震情報を配信します");
|
||||
const history = JSON.parse(readFileSync(`${import.meta.dirname}/../../260420.json`, "utf-8"));
|
||||
history.reverse();
|
||||
export default function earthquake() {
|
||||
if (config.earthquake?.useHistoryData) {
|
||||
console.log("過去の地震情報を配信します");
|
||||
const history = JSON.parse(readFileSync(`${import.meta.dirname}/../../260420.json`, "utf-8"));
|
||||
history.reverse();
|
||||
|
||||
let i = 0;
|
||||
setInterval(() => {
|
||||
processMessage(history[i]);
|
||||
i++;
|
||||
}, 10 * 1000);
|
||||
} else {
|
||||
const connect = () => {
|
||||
const WEBSOCKET_URL = config.debug
|
||||
? "wss://api-realtime-sandbox.p2pquake.net/v2/ws"
|
||||
: "wss://api.p2pquake.net/v2/ws";
|
||||
let i = 0;
|
||||
setInterval(() => {
|
||||
processMessage(history[i]);
|
||||
i++;
|
||||
}, 10 * 1000);
|
||||
} else {
|
||||
const connect = () => {
|
||||
const WEBSOCKET_URL = config.debug
|
||||
? "wss://api-realtime-sandbox.p2pquake.net/v2/ws"
|
||||
: "wss://api.p2pquake.net/v2/ws";
|
||||
|
||||
console.log("P2P地震情報のWebSocketに接続します");
|
||||
const socket = new WebSocket(WEBSOCKET_URL);
|
||||
console.log("P2P地震情報のWebSocketに接続します");
|
||||
const socket = new WebSocket(WEBSOCKET_URL);
|
||||
|
||||
socket.addEventListener("open", () => {
|
||||
console.log("P2P地震情報のWebSocketに接続しました");
|
||||
});
|
||||
socket.addEventListener("open", () => {
|
||||
console.log("P2P地震情報のWebSocketに接続しました");
|
||||
});
|
||||
|
||||
socket.addEventListener("close", (err) => {
|
||||
const interval = config.earthquake.reconnectInterval;
|
||||
console.log(`WebSocketが切断されました。${interval / 1000}秒後に再接続します:`, err.reason);
|
||||
setTimeout(() => {
|
||||
connect();
|
||||
}, interval);
|
||||
});
|
||||
socket.addEventListener("close", (err) => {
|
||||
const interval = config.earthquake.reconnectInterval;
|
||||
console.log(`WebSocketが切断されました。${interval / 1000}秒後に再接続します:`, err.reason);
|
||||
setTimeout(() => {
|
||||
connect();
|
||||
}, interval);
|
||||
});
|
||||
|
||||
socket.addEventListener("error", (err) => {
|
||||
console.error("WebSocketでエラーが発生しました:", err);
|
||||
socket.close();
|
||||
});
|
||||
socket.addEventListener("error", (err) => {
|
||||
console.error("WebSocketでエラーが発生しました:", err);
|
||||
socket.close();
|
||||
});
|
||||
|
||||
socket.addEventListener("message", async (event) => {
|
||||
let message;
|
||||
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 {
|
||||
message = typeof event.data === "string"
|
||||
? JSON.parse(event.data)
|
||||
: event.data;
|
||||
} catch (err) {
|
||||
console.error("地震情報メッセージのパースでエラーが発生:", err);
|
||||
return;
|
||||
}
|
||||
|
||||
const id = message.id ?? message._id;
|
||||
const mem = Memory.memory;
|
||||
if (mem.processedInfo.includes(id) && !config.debug) {
|
||||
console.log("重複した地震情報:", message.id);
|
||||
return;
|
||||
}
|
||||
processMessage(message);
|
||||
});
|
||||
}
|
||||
|
||||
processMessage(message);
|
||||
|
||||
if (!config.debug) {
|
||||
const mem = Memory.memory;
|
||||
mem.processedInfo = mem.processedInfo.concat([id]);
|
||||
Memory.memory = mem;
|
||||
}
|
||||
});
|
||||
connect();
|
||||
}
|
||||
|
||||
connect();
|
||||
}
|
||||
|
||||
const processMessage = async (message: any) => {
|
||||
|
||||
+2
-43
@@ -1,12 +1,9 @@
|
||||
import { schedule } from "node-cron";
|
||||
import { readFileSync } from "node:fs";
|
||||
import config from "@/lib/config";
|
||||
import { initData } from "@/lib/memory";
|
||||
import { styleText } from "node:util";
|
||||
import { Worker } from "node:worker_threads";
|
||||
import earthquake from "@/earthquake";
|
||||
|
||||
try {
|
||||
console.log(readFileSync(`${import.meta.dirname}/../asciiart.txt`, "utf-8"));
|
||||
console.log(JSON.parse(readFileSync(`${import.meta.dirname}/../package.json`, "utf-8")).version);
|
||||
if (config.debug) {
|
||||
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
|
||||
@@ -18,9 +15,7 @@ try {
|
||||
}
|
||||
console.log();
|
||||
|
||||
await initData();
|
||||
|
||||
new Worker(`${import.meta.dirname}/feature/earthquake/index.js`);
|
||||
earthquake();
|
||||
|
||||
console.log("Botが起動しました");
|
||||
} catch (err: any) {
|
||||
@@ -29,39 +24,3 @@ try {
|
||||
: err);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
try {
|
||||
schedule("0 * * * *", async () => {
|
||||
new Worker(`${import.meta.dirname}/feature/time/index.js`);
|
||||
});
|
||||
|
||||
schedule("0 7 * * *", async () => {
|
||||
new Worker(`${import.meta.dirname}/feature/weatherNotice.js`, {
|
||||
workerData: "scheduledWeatherNotice",
|
||||
});
|
||||
});
|
||||
|
||||
schedule("0 18 * * *", async () => {
|
||||
new Worker(`${import.meta.dirname}/feature/weatherNotice.js`, {
|
||||
workerData: "scheduledWeatherNoticeTomorrow",
|
||||
});
|
||||
});
|
||||
|
||||
schedule(`*/${config.command.interval} * * * *`, async () => {
|
||||
new Worker(`${import.meta.dirname}/feature/command/index.js`);
|
||||
});
|
||||
|
||||
let hnyWorker: Worker | undefined = undefined;
|
||||
|
||||
schedule("57 59 23 31 12 *", () => {
|
||||
hnyWorker = new Worker(`${import.meta.dirname}/feature/hnyNotice.js`);
|
||||
});
|
||||
|
||||
schedule("0 0 0 1 1 *", () => {
|
||||
hnyWorker?.postMessage("");
|
||||
});
|
||||
} catch (err: any) {
|
||||
console.error("message" in err
|
||||
? err.message
|
||||
: err);
|
||||
}
|
||||
+37
-28
@@ -11,23 +11,35 @@ type SuccessPosted<T = string> = {
|
||||
post: {
|
||||
id: T;
|
||||
content: string;
|
||||
zone: "normal" | string;
|
||||
zone: "normal" | "free";
|
||||
created_at: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
const BASE_URL = "https://collapse.jp/api/v3/bot";
|
||||
const getRealLength = (str: string): number => {
|
||||
const segmenter = new Intl.Segmenter("ja", { granularity: "grapheme" });
|
||||
return [...segmenter.segment(str)].length;
|
||||
};
|
||||
|
||||
const realSlice = (str: string, limit: number): string => {
|
||||
const segmenter = new Intl.Segmenter("ja", { granularity: "grapheme" });
|
||||
const segments = [...segmenter.segment(str)];
|
||||
return segments.slice(0, limit).map(s => s.segment).join("");
|
||||
};
|
||||
|
||||
export const createPost = async (data: {
|
||||
text: string;
|
||||
zone?: "normal" | string;
|
||||
replyTarget?: "all" | string;
|
||||
zone?: "normal" | "free";
|
||||
replyTarget?: "all" | "followers" | "mutual" | "mentioned" | "none";
|
||||
replyid?: string;
|
||||
images?: [Blob, string][];
|
||||
}, title: string) => {
|
||||
const excessedMessage = "**👉返信に続きがあります。**";
|
||||
|
||||
const excessedMessageLength = getRealLength(excessedMessage);
|
||||
const eolLength = getRealLength(EOL);
|
||||
|
||||
let lines = data.text.split(EOL);
|
||||
let firstUniqid = "";
|
||||
let count = 0;
|
||||
@@ -40,23 +52,25 @@ export const createPost = async (data: {
|
||||
|
||||
const maxLength = config.post.maxLength;
|
||||
|
||||
const limit = maxLength - (excessedMessage.length + EOL.length * 2);
|
||||
const limit = maxLength - (excessedMessageLength + eolLength * 2);
|
||||
|
||||
while (lines.length > 0) {
|
||||
const nextLine = lines[0];
|
||||
if (nextLine === undefined)
|
||||
break;
|
||||
|
||||
if (nextLine.length > limit && currentText === "") {
|
||||
const nextLineLength = getRealLength(nextLine);
|
||||
if (nextLineLength > limit && currentText === "") {
|
||||
const targetLine = lines.shift() || "";
|
||||
currentText = targetLine.slice(0, limit);
|
||||
|
||||
lines.unshift(targetLine.slice(limit));
|
||||
currentText = realSlice(targetLine, limit);
|
||||
lines.unshift(realSlice(targetLine, limit * -1));
|
||||
break;
|
||||
}
|
||||
|
||||
const potentialText = currentText ? currentText + EOL + nextLine : nextLine;
|
||||
if (potentialText.length <= limit) {
|
||||
const potentialText = currentText
|
||||
? currentText + EOL + nextLine
|
||||
: nextLine;
|
||||
if (getRealLength(potentialText) <= limit) {
|
||||
currentText = potentialText;
|
||||
lines.shift();
|
||||
} else {
|
||||
@@ -78,31 +92,25 @@ export const createPost = async (data: {
|
||||
const replyid = data.replyid === undefined && firstUniqid !== ""
|
||||
? firstUniqid
|
||||
: data.replyid;
|
||||
const reply = replyid !== undefined
|
||||
? `/${replyid}/reply`
|
||||
: "";
|
||||
const url = new URL(`/posts${reply}`, BASE_URL);
|
||||
|
||||
const body = new FormData();
|
||||
body.append("content", data.text);
|
||||
if (!replyid) {
|
||||
body.append("zone", data.zone ?? "normal");
|
||||
body.append("reply_restriction", data.replyTarget ?? "all");
|
||||
const formData = new FormData();
|
||||
formData.append("content", currentText);
|
||||
formData.append("zone", data.zone ?? "normal");
|
||||
formData.append("reply_restriction", data.replyTarget ?? "all");
|
||||
if (replyid) {
|
||||
formData.append("reply_to_id", replyid);
|
||||
}
|
||||
if (data.images) {
|
||||
data.images.forEach(img => body.append("images", img[0], img[1]));
|
||||
data.images.forEach(img => formData.append("images", img[0], img[1]));
|
||||
}
|
||||
|
||||
const req = await fetch(url, {
|
||||
const req = await fetch(`https://collapse.jp/api/v3/bot/posts`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
content: data.text,
|
||||
zone: data.zone ?? "normal",
|
||||
reply_restriction: data.replyTarget ?? "all",
|
||||
}),
|
||||
headers: {
|
||||
"Authorization": `Bearer ${config.mtweet.token}`,
|
||||
"X-Idempotency-Key": String(process.hrtime.bigint()),
|
||||
},
|
||||
body: formData,
|
||||
});
|
||||
|
||||
const res = await req.json();
|
||||
@@ -114,11 +122,12 @@ export const createPost = async (data: {
|
||||
break;
|
||||
}
|
||||
|
||||
console.warn(`${title}の投稿に失敗しました (試行 ${attempt}/${config.post.maxRetries}):`, res.error.message);
|
||||
console.warn(`${title}の投稿に失敗しました (試行 ${attempt}/${config.post.maxRetries}):`, `${res.error.message}(${res.error.code})`);
|
||||
if (attempt < config.post.maxRetries) {
|
||||
const interval = res.error.details?.retry_after
|
||||
? res.error.details.retry_after * 1000
|
||||
: config.post.retryInterval;
|
||||
console.log(`${interval}ms待機します...`);
|
||||
await new Promise(resolve => setTimeout(resolve, interval));
|
||||
}
|
||||
} catch (err) {
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
import i18next from "i18next";
|
||||
import config from "@/lib/config";
|
||||
import { parse as yamlParse } from "yaml";
|
||||
import { readFileSync } from "node:fs";
|
||||
|
||||
const translation = Object.fromEntries(Object.entries(
|
||||
yamlParse(readFileSync(`${import.meta.dirname}/../../locales/ja.yaml`, "utf-8"))
|
||||
).map(([key, value]) => [
|
||||
key,
|
||||
typeof value === "string"
|
||||
? value.trim()
|
||||
: value,
|
||||
]));
|
||||
|
||||
export default async function initI18n() {
|
||||
await i18next.init({
|
||||
lng: "ja",
|
||||
debug: config.debug,
|
||||
resources: {
|
||||
ja: {
|
||||
translation,
|
||||
},
|
||||
},
|
||||
interpolation: {
|
||||
escapeValue: false,
|
||||
},
|
||||
});
|
||||
};
|
||||
Reference in New Issue
Block a user