UNPKG

koishi-plugin-podris

Version:

连接 Podris WebSocket 服务以获取地震速报等实时信息

213 lines (211 loc) 9.37 kB
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 });