koishi-plugin-podris
Version:
连接 Podris WebSocket 服务以获取地震速报等实时信息
213 lines (211 loc) • 9.37 kB
JavaScript
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
var __export = (target, all) => {
for (var name2 in all)
__defProp(target, name2, { get: all[name2], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var src_exports = {};
__export(src_exports, {
Config: () => Config,
apply: () => apply,
name: () => name
});
module.exports = __toCommonJS(src_exports);
var import_koishi = require("koishi");
var import_ws = __toESM(require("ws"));
var import_zlib = require("zlib");
var name = "podris";
var Config = import_koishi.Schema.object({
token: import_koishi.Schema.string().description("访问令牌").required(),
endpoint: import_koishi.Schema.string().description("WebSocket 服务器地址").default("ws://server:port"),
targetGroups: import_koishi.Schema.array(String).description("目标群组ID列表,留空则发送到所有群组").default([])
});
function apply(ctx, config) {
const wsContext = ctx.guild().extend();
let ws = null;
let reconnectAttempts = 0;
const maxReconnectAttempts = 5;
let reconnectTimer = null;
let disposed = false;
const processedMessages = /* @__PURE__ */ new Set();
const MAX_CACHE_SIZE = 1e3;
function connect() {
if (ws) {
ws.removeAllListeners();
ws.close();
}
if (reconnectTimer) {
clearTimeout(reconnectTimer);
reconnectTimer = null;
}
ws = new import_ws.default(config.endpoint, {
headers: {
"Authorization": `Bearer ${config.token}`
}
});
ws.on("open", () => {
ctx.logger.info("已连接到 Project Podris 服务器");
reconnectAttempts = 0;
});
function sendMessage(content) {
if (config.targetGroups && config.targetGroups.length > 0) {
for (const groupId of config.targetGroups) {
try {
wsContext.bots[0]?.sendMessage(groupId, content);
} catch (error) {
ctx.logger.error(`发送消息到群 ${groupId} 失败:`, error);
}
}
} else {
ctx.broadcast(content);
}
}
__name(sendMessage, "sendMessage");
function cleanupOldMessages() {
if (processedMessages.size > MAX_CACHE_SIZE) {
const messagesArray = Array.from(processedMessages);
const toRemove = messagesArray.slice(0, processedMessages.size - MAX_CACHE_SIZE);
toRemove.forEach((id) => processedMessages.delete(id));
}
}
__name(cleanupOldMessages, "cleanupOldMessages");
ws.on("message", (data) => {
try {
const base64Data = data.toString();
const compressed = Buffer.from(base64Data, "base64");
const decompressed = (0, import_zlib.brotliDecompressSync)(compressed);
const message = JSON.parse(decompressed.toString());
const messageId = message.event_id || message.id || `${message.event_type}_${message.time}_${message.region}`;
if (processedMessages.has(messageId)) {
ctx.logger.debug(`跳过重复消息: ${messageId}`);
return;
}
processedMessages.add(messageId);
cleanupOldMessages();
switch (message.event_type) {
case "EEW":
const reportNumText = message.report_num ? ` (第${message.report_num}报)` : "";
const finalReportText = message.report_final ? "【最终报】" : "";
sendMessage(`[地震预警${finalReportText}] ${message.region} 发生 ${message.magnitude} 级地震,震源深度 ${message.depth} 公里,预估最大烈度 ${message.intensity}${message.int_type},发震时间 ${message.time},${reportNumText}`);
break;
case "EQR":
sendMessage(`[地震报告] ${message.region} 发生 ${message.magnitude} 级地震,震源深度 ${message.depth} 公里,最大烈度 ${message.intensity}${message.int_type},发震时间 ${message.time},详情链接:${message.detail_link}`);
break;
case "ESP":
if (Array.isArray(message.list)) {
for (const region of message.list) {
const regionMessageId = `${messageId}_${region.region}`;
if (!processedMessages.has(regionMessageId)) {
processedMessages.add(regionMessageId);
sendMessage(`[摇晃感知] ${region.region} 感知到地震摇晃,实测烈度 ${region.intensity}${region.int_type}`);
}
}
} else {
sendMessage(`[摇晃感知] ${message.region} 感知到地震摇晃,实测烈度 ${message.intensity}${message.int_type}`);
}
break;
case "CMT":
let cmtMessage = `[震源机制] ${message.region} 发生 ${message.magnitude} 级地震,震源深度 ${message.depth} 公里`;
if (message.mw_mag !== void 0) {
cmtMessage += `,矩震级 ${message.mw_mag}`;
}
if (message.mt_1 && Array.isArray(message.mt_1) && message.mt_1.length >= 3) {
cmtMessage += `,节面1:走向${message.mt_1[0]}° 倾角${message.mt_1[1]}° 滑动角${message.mt_1[2]}°`;
}
if (message.mt_2 && Array.isArray(message.mt_2) && message.mt_2.length >= 3) {
cmtMessage += `,节面2:走向${message.mt_2[0]}° 倾角${message.mt_2[1]}° 滑动角${message.mt_2[2]}°`;
}
sendMessage(cmtMessage);
break;
case "TNW":
if (Array.isArray(message.list)) {
for (const region of message.list) {
const regionMessageId = `${messageId}_${region.region}`;
if (!processedMessages.has(regionMessageId)) {
processedMessages.add(regionMessageId);
sendMessage(`[海啸预警] ${region.region} 可能发生海啸,请注意防范`);
}
}
} else {
sendMessage(`[海啸预警] ${message.region} 可能发生海啸,请注意防范`);
}
break;
case "TYP":
const typhoonName = Array.isArray(message.name) ? message.name.join(" / ") : message.name || "未知";
sendMessage(`[台风情报] ${typhoonName},中心位置 ${message.location},移速 ${message.move_speed} km/h,风速 ${message.wind_speed} km/h,方向 ${message.direction},中心气压 ${message.pressure} hPa,更新时间 ${message.time},详情链接:${message.detail_link}`);
break;
// case 'MMW': // 气象灾害,还在重置先放着不管
// sendMessage(`[气象灾害] ${message.type}: ${message.description}`)
// break
case "CAN":
sendMessage(`[情报取消] 事件编号 ${message.event_id} 的情报已取消,详情: ${message.description}`);
break;
}
} catch (err) {
ctx.logger.warn("处理消息时出错:", err);
}
});
ws.on("error", (err) => {
ctx.logger.warn("WebSocket 错误:", err);
});
ws.on("close", (code, reason) => {
ctx.logger.info(`连接已关闭,代码: ${code},原因: ${reason || "未知"}`);
if (disposed) {
ctx.logger.info("插件已卸载,停止重连");
return;
}
if (reconnectAttempts < maxReconnectAttempts) {
reconnectAttempts++;
const delay = reconnectAttempts === 1 ? 1e4 : Math.min(1e3 * Math.pow(2, reconnectAttempts), 5e4);
ctx.logger.info(`将在 ${delay}ms 后尝试重连 (第 ${reconnectAttempts} 次)`);
reconnectTimer = setTimeout(connect, delay);
} else {
ctx.logger.error("达到最大重连次数,停止重连");
}
});
}
__name(connect, "connect");
connect();
ctx.on("dispose", () => {
if (ws) {
ws.close();
ws = null;
}
if (reconnectTimer) {
clearTimeout(reconnectTimer);
reconnectTimer = null;
}
reconnectAttempts = 0;
disposed = true;
});
}
__name(apply, "apply");
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
Config,
apply,
name
});