210 lines
5.3 KiB
TypeScript
210 lines
5.3 KiB
TypeScript
import client from "@/lib/client";
|
|
import config from "@/lib/config";
|
|
import initI18n from "@/lib/i18n";
|
|
import Memory from "@/lib/memory";
|
|
import CronExpressionParser from "cron-parser";
|
|
import i18next from "i18next";
|
|
import { readFileSync } from "node:fs";
|
|
import { EOL } from "node:os";
|
|
import { isMainThread, workerData } from "node:worker_threads";
|
|
|
|
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",
|
|
];
|
|
|
|
if (
|
|
!isMainThread &&
|
|
typeof workerData === "string" &&
|
|
workerData.startsWith("scheduledWeatherNotice")
|
|
) {
|
|
await initI18n();
|
|
|
|
const cronStr = workerData.endsWith("Tomorrow")
|
|
? "0 18 * * *"
|
|
: "0 7 * * *"
|
|
const next = BigInt(CronExpressionParser.parse(cronStr).next().getTime() * 1_000_000);
|
|
while (process.hrtime.bigint() > next) {}
|
|
|
|
console.log("天気予報の投稿を行います");
|
|
|
|
try {
|
|
let provisionalUeuse;
|
|
let success = false;
|
|
|
|
for (let attempt = 1; attempt <= config.ueuse.maxRetries; attempt++) {
|
|
provisionalUeuse = await client.request("ueuse/create", {
|
|
text: i18next.t("weatherProvisional"),
|
|
});
|
|
|
|
if (provisionalUeuse.success) {
|
|
success = true;
|
|
break;
|
|
}
|
|
|
|
console.warn(`天気仮投稿に失敗しました (試行 ${attempt}/${config.ueuse.maxRetries}):`, provisionalUeuse.error_code);
|
|
if (attempt < config.ueuse.maxRetries) {
|
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
}
|
|
}
|
|
|
|
if (!success || !provisionalUeuse?.success) {
|
|
console.error("天気仮投稿の全試行に失敗したため、終了します。");
|
|
process.exit(1);
|
|
}
|
|
|
|
console.log("天気仮投稿:", provisionalUeuse.uniqid);
|
|
|
|
await weatherReply(provisionalUeuse.uniqid, workerData.endsWith("Tomorrow"));
|
|
process.exit(0);
|
|
} catch (err: any) {
|
|
console.error("message" in err
|
|
? err.message
|
|
: err);
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
export async function weatherReply(uniqid: string, isTomorrow: boolean) {
|
|
// インデックス
|
|
const aboutFullLength = 3100;
|
|
const mem = Memory.memory;
|
|
const splitCount = Math.round(aboutFullLength / mem.max_length);
|
|
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 itDay = isTomorrow
|
|
? data.forecasts[1]
|
|
: data.forecasts[0];
|
|
|
|
// 天気
|
|
const weather = itDay.telop ?? "取得できませんでした";
|
|
const maxTemp = itDay.temperature.max.celsius
|
|
? `${itDay.temperature.max.celsius}℃`
|
|
: "取得できませんでした";
|
|
const minTemp = itDay.temperature.min.celsius
|
|
? `${itDay.temperature.min.celsius}℃`
|
|
: "取得できませんでした";
|
|
const chanceOfRain = (
|
|
itDay.chanceOfRain.T06_12 !== null &&
|
|
itDay.chanceOfRain.T06_12 !== "--%"
|
|
)
|
|
? itDay.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++) {
|
|
let replyUeuse;
|
|
let success = false;
|
|
|
|
for (let attempt = 1; attempt <= config.ueuse.maxRetries; attempt++) {
|
|
replyUeuse = await client.request("ueuse/create", {
|
|
text: weatherResults[i].trim(),
|
|
replyid: uniqid,
|
|
});
|
|
|
|
if (replyUeuse.success) {
|
|
success = true;
|
|
break;
|
|
}
|
|
|
|
console.warn(`天気返信に失敗しました (試行 ${attempt}/${config.ueuse.maxRetries}):`, replyUeuse.error_code);
|
|
if (attempt < config.ueuse.maxRetries) {
|
|
await new Promise(resolve => setTimeout(resolve, config.ueuse.retryInterval));
|
|
}
|
|
}
|
|
|
|
if (!success || !replyUeuse?.success) {
|
|
console.error("天気返信の全試行に失敗したため、終了します。");
|
|
process.exit(1);
|
|
}
|
|
|
|
console.log("天気返信:", replyUeuse.uniqid);
|
|
}
|
|
} |