UNPKG

koishi-plugin-adapter-iirose

Version:
1,576 lines (1,543 loc) 143 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, IIROSE_Bot: () => IIROSE_Bot, IIROSE_WSsend: () => IIROSE_WSsend, WsClient: () => WsClient, apply: () => apply, filter: () => filter, inject: () => inject, name: () => name, reusable: () => reusable, usage: () => usage }); module.exports = __toCommonJS(src_exports); var import_koishi11 = require("koishi"); // src/bot/bot.ts var import_koishi9 = require("koishi"); // src/utils/utils.ts var import_koishi2 = require("koishi"); // src/utils/entities.ts var entityMap = { "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;", "/": "&#x2F;" }; var encodeRegex = /[&<>"'/]/g; function encode(str) { if (typeof str !== "string") return ""; return str.replace(encodeRegex, (s) => entityMap[s]); } __name(encode, "encode"); var decodeEntityMap = { "&amp;": "&", "&lt;": "<", "&gt;": ">", "&quot;": '"', "&#39;": "'", "&#x2F;": "/" }; var decodeRegex = /&amp;|&lt;|&gt;|&quot;|&#39;|&#x2F;/g; function decode(str) { if (typeof str !== "string") return ""; let last, decoded = str; do { last = decoded; decoded = last.replace(decodeRegex, (entity) => decodeEntityMap[entity]); } while (last !== decoded); return decoded; } __name(decode, "decode"); // src/decoder/clearMsg.ts var import_koishi = require("koishi"); async function clearMsg(msg, bot) { msg = msg.replace(/^(?:&#092;){3}\*\n\s*/, ""); msg = msg.replace(/\\(https*:\/\/[\s\S]+)/g, "$1"); msg = msg.replace(/\[((https*:\/\/[\s\S]+?\.(png|jpg|jpeg|gif))(#e)*)\]/g, "$1"); async function replaceAsync(str, regex, asyncFn) { const promises = []; str.replace(regex, (...args) => { promises.push(asyncFn(...args)); return ""; }); const data = await Promise.all(promises); return str.replace(regex, () => data.shift()); } __name(replaceAsync, "replaceAsync"); const imageUrlRegex = /((?:https?:\/\/[\s\S]+?)\.(?:png|jpg|jpeg|gif)(?:#e)?)/g; msg = msg.replace(imageUrlRegex, (match) => { const cleanUrl = match.replace(/#e$/, ""); return import_koishi.h.image(cleanUrl).toString(); }); const audioUrlRegex = /((?:https?:\/\/[\s\S]+?)\.weba)/g; msg = msg.replace(audioUrlRegex, (match) => { return import_koishi.h.audio(match).toString(); }); const videoUrlRegex = /\[((?:https?:\/\/[\s\S]+?)\.(?:mp4|webm|ogg))\]/g; msg = msg.replace(videoUrlRegex, (match, url) => { return import_koishi.h.video(url).toString(); }); const atNameRegex = /(\s+)((?:\[\*[\s\S]+?\*\])+)(\s)/g; msg = await replaceAsync(msg, atNameRegex, async (raw, space1, mentionBlock, space2, offset, originalString) => { const name2 = mentionBlock.slice(2, -2); const isMultiBlock = mentionBlock.lastIndexOf("[*") > 0; const user = await bot.internal.getUserByName(name2); const leadingSpace = offset === 0 || originalString.substring(0, offset).trim() === "" ? "" : space1; if (user) { return `${leadingSpace}${(0, import_koishi.h)("at", { id: user.id, name: name2 }).toString()}${space2}`; } else if (isMultiBlock) { return `${leadingSpace}${(0, import_koishi.h)("at", { id: "error", name: name2 }).toString()}${space2}`; } return raw; }); const atIdRegex = /(\s+)((?:\[@[\s\S]+?@\])+)(\s)/g; msg = await replaceAsync(msg, atIdRegex, async (raw, space1, mentionBlock, space2, offset, originalString) => { const id = mentionBlock.slice(2, -2); const isMultiBlock = mentionBlock.lastIndexOf("[@") > 0; const user = await bot.getUser(id); const leadingSpace = offset === 0 || originalString.substring(0, offset).trim() === "" ? "" : space1; if (user) { return `${leadingSpace}${(0, import_koishi.h)("at", { id, name: user.name }).toString()}${space2}`; } else if (isMultiBlock) { return `${leadingSpace}${(0, import_koishi.h)("at", { id: "error", name: id }).toString()}${space2}`; } return raw; }); const sharpRegex = /(\s+)((?:\[_[\s\S]+?_\])+)(\s)/g; msg = await replaceAsync(msg, sharpRegex, async (raw, space1, mentionBlock, space2, offset, originalString) => { const leadingSpace = offset === 0 || originalString.substring(0, offset).trim() === "" ? "" : space1; const channelId = mentionBlock.slice(2, -2); const cleanChannelId = channelId.replace(/_+$/, ""); return `${leadingSpace}${(0, import_koishi.h)("sharp", { id: cleanChannelId }).toString()}${space2}`; }); return msg; } __name(clearMsg, "clearMsg"); // src/utils/utils.ts var fs = __toESM(require("node:fs/promises")); var path = __toESM(require("node:path")); var Unknown_User_Name = "Unknown User"; var Unknown_Guild_Name = "Unknown Guild"; var Unknown_Channel_Name = "Unknown Channel"; function rgbaToHex(rgba) { if (/^[0-9a-fA-F]{6}$/.test(rgba)) { return rgba; } const match = rgba.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)/); if (!match) { return "66ccff"; } const r = parseInt(match[1]); const g = parseInt(match[2]); const b = parseInt(match[3]); const rHex = r.toString(16).padStart(2, "0"); const gHex = g.toString(16).padStart(2, "0"); const bHex = b.toString(16).padStart(2, "0"); return `${rHex}${gHex}${bHex}`; } __name(rgbaToHex, "rgbaToHex"); function generateMessageId() { return Math.random().toString().substring(2, 14); } __name(generateMessageId, "generateMessageId"); var parseAvatar = /* @__PURE__ */ __name((avatar) => { if (!avatar) return ""; if (avatar.startsWith("http")) { return avatar; } return `http://s.iirose.com/images/icon/${avatar}.jpg`; }, "parseAvatar"); var startEventsServer = /* @__PURE__ */ __name((bot) => { let event = []; return event; }, "startEventsServer"); var stopEventsServer = /* @__PURE__ */ __name((event) => { event.forEach((element) => { element(); }); }, "stopEventsServer"); var writeWJ = /* @__PURE__ */ __name(async (bot, relativePath, data) => { try { const instanceDataDir = path.join(bot.ctx.baseDir, "data", "adapter-iirose", bot.config.uid.trim()); const filePath = path.join(instanceDataDir, relativePath); await fs.mkdir(path.dirname(filePath), { recursive: true }); await fs.writeFile(filePath, JSON.stringify(data, null, 2)); bot.logInfo(`[iirose-writeWJ] 数据已更新至: ${filePath}`); } catch (error) { bot.logger.error(`[iirose-writeWJ] 写入 ${relativePath} 失败:`, error); } }, "writeWJ"); var readJsonData = /* @__PURE__ */ __name(async (bot, filename) => { try { const instanceDataDir = path.join(bot.ctx.baseDir, "data", "adapter-iirose", bot.config.uid.trim()); const filePath = path.join(instanceDataDir, filename); const content = await fs.readFile(filePath, "utf-8"); return JSON.parse(content); } catch (error) { if (error.code === "ENOENT") { return null; } bot.logger.error(`[iirose-readJsonData] 读取或解析 ${filename} 失败:`, error); return null; } }, "readJsonData"); var findRoomInGuild = /* @__PURE__ */ __name((guildData, roomId) => { if (!guildData || typeof guildData !== "object") { return null; } for (const key in guildData) { const room = guildData[key]; if (room && room.id === roomId) { return room; } if (typeof room === "object") { const found = findRoomInGuild(room, roomId); if (found) { return found; } } } return null; }, "findRoomInGuild"); var flattenRooms = /* @__PURE__ */ __name((guildData) => { const allRooms = []; function recurse(data) { if (!data || typeof data !== "object") { return; } for (const key in data) { const room = data[key]; if (room && room.id && room.name) { allRooms.push(room); } if (typeof room === "object") { recurse(room); } } } __name(recurse, "recurse"); recurse(guildData); return allRooms; }, "flattenRooms"); var findUserNameById = /* @__PURE__ */ __name(async (bot, userId) => { const userlist = await readJsonData(bot, "wsdata/userlist.json"); if (!userlist) return void 0; const user = userlist.find((u) => u.uid === userId); return user ? user.username : void 0; }, "findUserNameById"); var findUserIdByName = /* @__PURE__ */ __name(async (bot, username) => { const userlist = await readJsonData(bot, "wsdata/userlist.json"); if (!userlist) return void 0; const user = userlist.find((u) => u.username === username); return user ? user.uid : void 0; }, "findUserIdByName"); async function cacheSentMessage(bot, channelId, messageId, content) { if (!bot.sessionCache) return; const processedContent = await clearMsg(content, bot); const event = { type: "message", platform: "iirose", selfId: bot.selfId, timestamp: Date.now(), user: { id: bot.user.id, name: bot.user.name, avatar: parseAvatar(bot.user.avatar) }, message: { id: messageId, messageId, content: processedContent, elements: import_koishi2.h.parse(processedContent) }, channel: { id: channelId, type: channelId.startsWith("private:") ? 1 : 0 } }; if (!channelId.startsWith("private:")) { event.guild = { id: channelId }; } const session = bot.session(event); bot.sessionCache.add(session); } __name(cacheSentMessage, "cacheSentMessage"); function ensureNewlineBefore(text) { if (text.length > 0 && !text.endsWith("\n")) { return text + "\n"; } return text; } __name(ensureNewlineBefore, "ensureNewlineBefore"); async function transformUrl(bot, elementString) { if (!bot.ctx.assets) { bot.loggerWarn("Assets service is not available, skipping transformation."); return null; } try { const transformedContent = await bot.ctx.assets.transform(elementString); const urlMatch = transformedContent.match(/src="([^"]+)"/); if (urlMatch && urlMatch[1]) { return decode(urlMatch[1]); } else { bot.loggerWarn(`Could not extract URL from transformed content: ${transformedContent}`); return null; } } catch (error) { if (error?.message?.includes("context disposed")) return null; bot.loggerError("Asset transformation failed:", error); return null; } } __name(transformUrl, "transformUrl"); // src/bot/sendMessage.ts var import_koishi8 = require("koishi"); // src/encoder/messages/PrivateMessage.ts var PrivateMessage_default = /* @__PURE__ */ __name((uid, message, color) => { const messageId = generateMessageId(); return { messageId, data: JSON.stringify({ g: uid, m: message, mc: color, i: messageId }) }; }, "default"); // src/encoder/messages/PublicMessage.ts var PublicMessage_default = /* @__PURE__ */ __name((message, color) => { const messageId = generateMessageId(); if (message === "cut") { return { messageId, data: `{0${JSON.stringify({ m: message, mc: color, i: messageId })}` }; } return { messageId, data: JSON.stringify({ m: message, mc: color, i: messageId }) }; }, "default"); // src/utils/ws/client.ts var import_koishi7 = require("koishi"); // src/utils/password.ts var import_node_crypto = require("node:crypto"); function md5(data) { return (0, import_node_crypto.createHash)("md5").update(data).digest("hex"); } __name(md5, "md5"); function isMd5Format(password) { return password && password.length === 32 && /^[a-z0-9]{32}$/.test(password); } __name(isMd5Format, "isMd5Format"); function getMd5Password(password) { if (!password) { return null; } return isMd5Format(password) ? password : md5(password); } __name(getMd5Password, "getMd5Password"); function comparePassword(password, targetMd5) { const passwordMd5 = getMd5Password(password); return passwordMd5 !== null && passwordMd5 === targetMd5; } __name(comparePassword, "comparePassword"); // src/utils/ws/retry.ts function calculateRetryDelay(retryCount, maxRetryIntervalMinutes) { const maxDelay = maxRetryIntervalMinutes * 60 * 1e3; if (retryCount < 3) { return 5 * 1e3; } if (retryCount < 6) { return Math.min(30 * 1e3, maxDelay); } if (retryCount < 9) { return Math.min(3 * 60 * 1e3, maxDelay); } return maxDelay; } __name(calculateRetryDelay, "calculateRetryDelay"); async function waitWithCancel(ctx, bot, delayMs, checkDisposed) { let cancelled = false; await new Promise((resolve) => { let elapsed = 0; const interval = 100; const dispose = ctx.setInterval(() => { if (checkDisposed()) { dispose(); bot.logInfo("websocket准备:插件正在停用,取消连接"); cancelled = true; resolve(); return; } elapsed += interval; if (elapsed >= delayMs) { dispose(); resolve(); } }, interval); }); return cancelled; } __name(waitWithCancel, "waitWithCancel"); // src/utils/ws/send.ts var zlib = __toESM(require("node:zlib")); var wsSendLock = Promise.resolve(); function toArrayBuffer(data) { const arrayBuffer = new ArrayBuffer(data.byteLength); const view = new Uint8Array(arrayBuffer); view.set(data); return arrayBuffer; } __name(toArrayBuffer, "toArrayBuffer"); async function IIROSE_WSsend(bot, data) { const callId = Math.random().toString(36).substring(2, 8); wsSendLock = wsSendLock.then(async () => { try { if (!bot.socket) { bot.loggerError("布豪! !bot.socket !!! 请联系开发者"); return; } if (bot.socket.readyState === 0) { bot.loggerError("布豪! bot.socket.readyState == 0 !!! 请联系开发者"); return; } const shortData = data.length > 50 ? data.substring(0, 50) + "..." : data; if (shortData.trim().length > 0) { bot.fulllogInfo(`[WS发送-${callId}] 发送数据: ${shortData}`); } const buffer = Buffer.from(data); const uintArray = Uint8Array.from(buffer); if (uintArray.length > 256) { const deflatedData = zlib.gzipSync(data); const deflatedArray = new Uint8Array(deflatedData.length + 1); deflatedArray[0] = 1; deflatedArray.set(deflatedData, 1); bot.socket.send(toArrayBuffer(deflatedArray)); } else { bot.socket.send(toArrayBuffer(uintArray)); } } catch (error) { bot.loggerError(`[WS发送-${callId}] 发送失败:`, error); } }); await wsSendLock; } __name(IIROSE_WSsend, "IIROSE_WSsend"); // src/utils/ws/connection.ts async function getLatency(ctx, bot, url, disposed) { return new Promise((resolve) => { if (disposed()) { resolve("error"); return; } let ws = null; let timeoutId = null; let disposingCheckId = null; let resolved = false; const cleanup = /* @__PURE__ */ __name(() => { if (timeoutId) { timeoutId(); timeoutId = null; } if (disposingCheckId) { disposingCheckId(); disposingCheckId = null; } if (ws && (ws.readyState === 1 || ws.readyState === 0)) { try { ws.close(); } catch (e) { } } ws = null; }, "cleanup"); const safeResolve = /* @__PURE__ */ __name((value) => { if (!resolved) { resolved = true; cleanup(); resolve(value); } }, "safeResolve"); try { const startTime = Date.now(); const timeout = Math.max(bot.config.timeout, 2e3); ws = ctx.http.ws(url); timeoutId = ctx.setTimeout(() => { safeResolve("error"); }, timeout); disposingCheckId = ctx.setInterval(() => { if (disposed()) { safeResolve("error"); } }, 200); ws.addEventListener("open", () => { const endTime = Date.now(); const latency = endTime - startTime; safeResolve(latency); }); ws.addEventListener("error", () => { safeResolve("error"); }); ws.addEventListener("close", () => { if (!resolved) { safeResolve("error"); } }); } catch (error) { safeResolve("error"); } }); } __name(getLatency, "getLatency"); async function prepareConnection(ctx, bot, disposed) { const iiroseList = ["m1", "m2", "m8", "m9", "m"]; let fastest = "www"; let maximumSpeed = 1e5; let allErrors; let retryCount = 0; const maxRetryIntervalMinutes = bot.config.maxRetryInterval; do { if (disposed()) { throw new Error("插件正在停用"); } allErrors = true; const speedTests = []; for (let webIndex of iiroseList) { speedTests.push( getLatency(ctx, bot, `wss://${webIndex}.iirose.com:8778`, disposed).then((speed) => ({ index: webIndex, speed })).catch(() => ({ index: webIndex, speed: "error" })) ); } try { const results = await Promise.race([ Promise.allSettled(speedTests).then( (settledResults) => settledResults.map( (result) => result.status === "fulfilled" ? result.value : { index: "", speed: "error" } ).filter((r) => r.index !== "") ), new Promise( (resolve) => ctx.setTimeout(() => resolve(iiroseList.map((index) => ({ index, speed: "error" }))), 5e3) ) ]); if (disposed()) { throw new Error("插件正在停用"); } for (const result of results) { if (result.speed !== "error") { allErrors = false; if (maximumSpeed > result.speed) { fastest = result.index; maximumSpeed = result.speed; } } } if (!allErrors) { break; } } catch (error) { bot.loggerWarn("服务器测试过程中出现错误:", error); } if (allErrors) { const delayMs = calculateRetryDelay(retryCount, maxRetryIntervalMinutes); const delaySec = Math.round(delayMs / 1e3); if (!bot.config.silentRetry || bot.config.debugMode) { bot.loggerWarn(`所有服务器都无法连接,将在${delaySec}秒后重试... (重试次数: ${retryCount})`); } const cancelled = await waitWithCancel(ctx, bot, delayMs, disposed); if (cancelled) { throw new Error("插件正在停用"); } retryCount++; if (disposed()) { throw new Error("插件正在停用"); } } } while (allErrors && !disposed()); if (!fastest) { fastest = "www"; } const targetUrl = `wss://${fastest}.iirose.com:8778`; bot.loggerInfo(`找到可用服务器: ${targetUrl}, 延迟: ${maximumSpeed}ms`); const socket = ctx.http.ws(targetUrl); socket.binaryType = "arraybuffer"; const dispose = ctx.on("dispose", () => { if (socket && socket.readyState === 1) { socket.close(); } dispose(); }); return socket; } __name(prepareConnection, "prepareConnection"); function createLoginObj(bot) { const roomIdReg = /\s*\[_([\\s\\S]+)_\]\s*/; const userNameReg = /\s*\[\\*([\\s\\S]+)\\*\]\s*/; const roomIdConfig = bot.config.roomId; const userNameConfig = bot.config.usename; let username = userNameReg.test(userNameConfig) ? userNameConfig.match(userNameReg)?.[1] : userNameConfig; let room = roomIdReg.test(roomIdConfig) ? roomIdConfig.match(roomIdReg)?.[1] : roomIdConfig; let loginObj; if (bot.config.smStart && bot.config.smPassword === "ec3a4ac482b483ac02d26e440aa0a948") { loginObj = { r: bot.config.smRoom, n: bot.config.smUsername, i: bot.config.smImage, nc: bot.config.smColor, s: bot.config.smGender, st: bot.config.smst, mo: bot.config.smmo, uid: bot.config.smUid, li: bot.config.smli, mb: bot.config.smmb, mu: bot.config.smmu, la: bot.config.smLocation, vc: bot.config.smvc, fp: `@${md5(bot.config.smUsername)}` }; bot.loggerInfo("已启用蔷薇游客模式"); } else { const hashedPassword = getMd5Password(bot.config.password); if (!hashedPassword) { bot.loggerError("登录失败:密码不能为空"); throw new Error("密码不能为空"); } loginObj = { r: room || bot.config.roomId, n: username || bot.config.usename, p: hashedPassword, st: bot.config.botStatus, mo: bot.config.signature, mb: "", mu: "01", lr: bot.config.oldRoomId, rp: bot.config.roomPassword, fp: `@${md5(username || bot.config.usename)}` }; } if (!loginObj.lr) { delete loginObj.lr; } return loginObj; } __name(createLoginObj, "createLoginObj"); // src/utils/ws/message.ts var import_koishi5 = require("koishi"); var zlib2 = __toESM(require("node:zlib")); // src/decoder/messages/MailboxMessage.ts var mailboxMessage = /* @__PURE__ */ __name((message) => { if (!message.startsWith("@")) return null; const parts = message.slice(2).split("<"); for (const part of parts) { const tmp = part.split(">"); if (tmp.length === 3) { return { type: "roomNotice", notice: decode(tmp[0]), background: tmp[1], timestamp: Number(tmp[2]) }; } if (tmp.length === 7) { if (/^'\^/.test(tmp[3])) { return { type: "follower", username: decode(tmp[0]), avatar: parseAvatar(tmp[1]), gender: tmp[2], background: tmp[4], timestamp: Number(tmp[5]), color: tmp[6] }; } else if (/^'\*/.test(tmp[3])) { return { type: "like", username: decode(tmp[0]), avatar: parseAvatar(tmp[1]), gender: tmp[2], background: tmp[4], timestamp: Number(tmp[5]), color: tmp[6], message: decode(tmp[3].substring(2)) }; } else if (/^'h/.test(tmp[3])) { return { type: "dislike", username: decode(tmp[0]), avatar: parseAvatar(tmp[1]), gender: tmp[2], background: tmp[4], timestamp: Number(tmp[5]), color: tmp[6], message: decode(tmp[3].substring(2)) }; } else if (/^'\$/.test(tmp[3])) { return { type: "payment", username: decode(tmp[0]), avatar: parseAvatar(tmp[1]), gender: tmp[2], money: parseInt(tmp[3].split(" ")[0].substring(1)), message: decode(tmp[3].split(" ")[1] || ""), background: tmp[4], timestamp: Number(tmp[5]), color: tmp[6] }; } } } return null; }, "mailboxMessage"); // src/decoder/messages/BroadcastMessage.ts var broadcastMessage = /* @__PURE__ */ __name((msg) => { if (!msg.startsWith("=")) { return void 0; } const parts = msg.slice(1).split(">"); if (parts.length < 8) { return void 0; } return { username: parts[0], message: parts[1], color: parts[2], avatar: parseAvatar(parts[5]), timestamp: parts[6], messageId: parts[7] }; }, "broadcastMessage"); // src/decoder/messages/MessageDeleted.ts function MessageDeleted(bot, message) { const publicDeleteMatch = message.match(/^v0#([^_]+)_([^"]+)"?$/); if (publicDeleteMatch) { const [, userId, messageId] = publicDeleteMatch; let channelId = bot.config.roomId; if (bot.config.smStart) { channelId = bot.config.smRoom; } return { type: "message-deleted", userId, messageId, channelId, timestamp: Date.now() }; } const privateDeleteMatch = message.match(/^v0\*([^"]+)"([^_]+)_(\d+)$/); if (privateDeleteMatch) { const [, receiverId, senderId, messageId] = privateDeleteMatch; const channelId = `private:${senderId}`; return { type: "message-deleted", userId: senderId, messageId, channelId, timestamp: Date.now() }; } return null; } __name(MessageDeleted, "MessageDeleted"); // src/decoder/messages/PublicMessage.ts var _PublicMessage = class _PublicMessage { constructor(data) { this.timestamp = data.timestamp; this.avatar = data.avatar; this.username = data.username; this.message = data.message; this.color = data.color; this.uid = data.uid; this.title = data.title; this.messageId = data.messageId; this.replyMessage = data.replyMessage; } }; __name(_PublicMessage, "PublicMessage"); var PublicMessage = _PublicMessage; var replyMsg = /* @__PURE__ */ __name((msg) => { if (!msg.includes(" (_hr) ")) { return [msg, null]; } const parts = msg.split(" (hr_) "); const newMsg = parts.pop(); const replies = []; for (const part of parts) { const quoteParts = part.split(" (_hr) "); if (quoteParts.length === 2) { const [message, authorPart] = quoteParts; const authorMatch = authorPart.match(/(.*)_(\d+)$/); if (authorMatch) { const [, username, time] = authorMatch; replies.push({ message: decode(message), username: decode(username.trim()), time: Number(time) }); } } } return [newMsg, replies.length > 0 ? replies : null]; }, "replyMsg"); var publicMessage = /* @__PURE__ */ __name((input) => { if (input.substring(0, 1) !== '"') return null; const message = input.substring(1); if (message.indexOf("<") === -1) { const tmp = message.split(">"); if (tmp.length === 11) { if (/^\d+$/.test(tmp[0])) { const [message2, reply] = replyMsg(tmp[3]); if (message2.startsWith("m__4@")) { return null; } const msg = { timestamp: Number(tmp[0]), avatar: parseAvatar(tmp[1]), username: decode(tmp[2]), message: decode(message2), color: tmp[5], uid: tmp[8], title: tmp[9] === "'108" ? "花瓣" : tmp[9], messageId: Number(tmp[10]), replyMessage: reply }; return new PublicMessage(msg); } } } }, "publicMessage"); // src/decoder/messages/PrivateMessage.ts var _PrivateMessage = class _PrivateMessage { constructor(data) { this.timestamp = data.timestamp; this.uid = data.uid; this.username = data.username; this.avatar = data.avatar; this.message = data.message; this.color = data.color; this.messageId = data.messageId; this.replyMessage = data.replyMessage; } }; __name(_PrivateMessage, "PrivateMessage"); var PrivateMessage = _PrivateMessage; var privateMessage = /* @__PURE__ */ __name((message) => { if (message.substring(0, 2) === '""') { const item = message.substring(2).split("<"); for (const msg of item) { const tmp = msg.split(">"); if (tmp.length === 11) { if (/^\d+$/.test(tmp[0])) { const [realMessage, reply] = replyMsg(tmp[4]); const msg2 = new PrivateMessage({ timestamp: Number(tmp[0]), uid: tmp[1], username: decode(tmp[2]), avatar: parseAvatar(tmp[3]), message: decode(realMessage), color: tmp[5], messageId: Number(tmp[10]), replyMessage: reply }); return msg2; } } } return null; } }, "privateMessage"); // src/decoder/messages/MemberUpdate.ts var parseSingleMemberUpdate = /* @__PURE__ */ __name((message) => { const parts = message.split(">"); if (parts.length < 10) return; const timestamp = parts[0].slice(1); const avatar = parts[1]; const username = parts[2]; const uid = parts[8]; const lastPart = parts[parts.length - 1]; if (parts[3] === "'1") { let status = ""; for (let i = lastPart.length - 1; i >= 0; i--) { if (lastPart[i] !== "'") { status = lastPart[i]; break; } } if (status === "n" || status === "d") { return { type: "join", timestamp, avatar, username, uid, joinType: status === "n" ? "new" : "reconnect" }; } } const secondToLastPart = parts[parts.length - 2]; if (parts[3] === "'3" && secondToLastPart === "" && lastPart === "2") { return { type: "leave", // 离开和刷新都被视为离开事件。刷新会触发一个离开事件,然后是一个加入事件。 timestamp, avatar, username, uid, isMove: false }; } const moveRoomIdMarker = "'2"; if (parts[3].startsWith(moveRoomIdMarker)) { const targetRoomIdFromPart3 = parts[3].slice(moveRoomIdMarker.length); const moveEndMarker = "3"; if (lastPart.startsWith(moveEndMarker)) { const targetRoomIdFromLastPart = lastPart.slice(moveEndMarker.length); if (targetRoomIdFromPart3 === targetRoomIdFromLastPart) { return { type: "leave", // "移动" 本质上是离开当前房间,所以我们下发 leave 事件 timestamp, avatar, username, uid, isMove: true, // 附带 isMove 标志 targetRoomId: targetRoomIdFromPart3, // 和目标房间 ID color: parts[5], title: parts[9], room: parts[10] }; } } } }, "parseSingleMemberUpdate"); var memberUpdate = /* @__PURE__ */ __name((message) => { return parseSingleMemberUpdate(message); }, "memberUpdate"); // src/decoder/messages/BulkDataPacket.ts var import_koishi3 = require("koishi"); // src/encoder/system/consume/stock.ts var stockGet = /* @__PURE__ */ __name(() => { return ">#"; }, "stockGet"); var stockBuy = /* @__PURE__ */ __name((quantity) => { return `>$${quantity}`; }, "stockBuy"); var stockSell = /* @__PURE__ */ __name((quantity) => { return `>@${quantity}`; }, "stockSell"); // src/encoder/system/consume/bank.ts var bankGet = /* @__PURE__ */ __name(() => { return ">*"; }, "bankGet"); var bankDeposit = /* @__PURE__ */ __name((amount) => { return `>^a${amount}`; }, "bankDeposit"); var bankWithdraw = /* @__PURE__ */ __name((amount) => { return `>^b${amount}`; }, "bankWithdraw"); // src/decoder/messages/BulkDataPacket.ts var bulkDataPacket = /* @__PURE__ */ __name(async (message, bot) => { if (message.startsWith("%")) { if (bot.config.debugMode) { await writeWJ(bot, "wsdata/message.log", message); } const rawData = message.substring(3); const parts = rawData.split('\\"'); let userAndRoomDataRaw = parts[0]; if (userAndRoomDataRaw.endsWith("'")) { userAndRoomDataRaw = userAndRoomDataRaw.slice(0, -1); } const userList = []; const roomList = {}; const segments = userAndRoomDataRaw.split("<"); const roomIdRegex = /^(?=.*[a-f])([a-f0-9]{10,}_?)+$/; for (const segment of segments) { if (!segment.trim()) continue; const fields = segment.split(">"); const candidateId = fields[0]; if (roomIdRegex.test(candidateId)) { const idPath = candidateId.split("_"); const roomName = fields[1] || ""; const rawDescField = fields[5] || ""; let description = ""; let background = ""; if (rawDescField.startsWith("s://") || rawDescField.startsWith("://")) { const firstSpaceIndex = rawDescField.indexOf(" "); const protocol = rawDescField.startsWith("s://") ? "https" : "http"; if (firstSpaceIndex !== -1) { const urlPart = rawDescField.substring(rawDescField.startsWith("s://") ? 4 : 3, firstSpaceIndex); background = `${protocol}://${urlPart}`; description = rawDescField.substring(firstSpaceIndex + 1).split("&&")[0].trim(); } else { const urlPart = rawDescField.substring(rawDescField.startsWith("s://") ? 4 : 3); background = `${protocol}://${urlPart}`; } } else { description = rawDescField.split("&&")[0].trim(); } let currentLevel = roomList; for (let j = 0; j < idPath.length - 1; j++) { const idPart = idPath[j]; if (!currentLevel[idPart]) { currentLevel[idPart] = {}; } currentLevel = currentLevel[idPart]; } const finalId = idPath[idPath.length - 1]; if (idPath.length > 1) { const parent = currentLevel; if (!parent.rooms) { parent.rooms = []; } if (!parent.rooms.includes(finalId)) { parent.rooms.push(finalId); } } currentLevel[finalId] = { ...currentLevel[finalId], id: finalId, name: roomName, description, background, users: [], // 先置空,后续统一填充 online: 0 // 先置空,后续统一计算 }; } else if (fields[0].includes("/")) { userList.push({ avatar: parseAvatar(fields[0]), username: import_koishi3.h.unescape(fields[2]), color: fields[3], room: fields[4], uid: fields[8] }); } } if (Object.keys(roomList).length > 0) { let collectRooms = function(level) { for (const key in level) { const item = level[key]; if (item.id && item.name) { roomMap.set(item.id, item); } else if (typeof item === "object" && item !== null) { collectRooms(item); } } }; __name(collectRooms, "collectRooms"); const roomMap = /* @__PURE__ */ new Map(); collectRooms(roomList); for (const user of userList) { if (user.room && roomMap.has(user.room)) { const room = roomMap.get(user.room); room.users.push(user.uid); room.online++; } } } if (userList.length > 0) { await writeWJ(bot, "wsdata/userlist.json", userList); } if (Object.keys(roomList).length > 0) { await writeWJ(bot, "wsdata/roomlist.json", roomList); } bot.sendAndWaitForResponse(stockGet(), ">", false); bot.sendAndWaitForResponse(bankGet(), ">$", false); (async () => { try { const self = await bot.getSelf(); if (self) { bot.user.name = self.name; bot.user.avatar = self.avatar; bot.selfId = self.id; bot.userId = self.id; } else { bot.loggerWarn("更新机器人信息失败,未能从 userlist.json 中找到自身数据。请稍后重试。"); } } catch (error) { bot.loggerError("更新机器人信息时出错:", error); } })(); return userList; } }, "bulkDataPacket"); // src/decoder/messages/MusicMessage.ts var replyMsg2 = /* @__PURE__ */ __name((msg) => { if (msg.includes(" (_hr) ")) { const replies = []; msg.split(" (hr_) ").forEach((e) => { if (e.includes(" (_hr) ")) { const tmp = e.split(" (_hr) "); const user = tmp[1].split("_"); replies.unshift({ message: decode(tmp[0]), username: decode(user[0]), time: Number(user[1]) }); replies.sort((a, b) => { return a.time - b.time; }); } else { replies.unshift(e); } }); return replies; } return null; }, "replyMsg"); var musicMessageAnalyze = /* @__PURE__ */ __name((input) => { const { timestamp, avatar, username, message, color, uid, title, messageId } = input; const musicData = message.split(">"); return { timestamp, avatar, username, color, uid, title, messageId, // 对音乐名称和歌手进行解码 musicName: decode(musicData[1]), musicSinger: decode(musicData[2]), musicPic: musicData[3], musicColor: musicData[4] }; }, "musicMessageAnalyze"); var musicMessage = /* @__PURE__ */ __name((input) => { if (input.substring(0, 1) !== '"') return null; const message = input.substring(1); if (message.indexOf("<") === -1) { const tmp = message.split(">"); if (tmp.length === 11) { if (/^\d+$/.test(tmp[0])) { const reply = replyMsg2(tmp[3]); const message2 = reply ? String(reply.shift()) : tmp[3]; const msg = { timestamp: Number(tmp[0]), avatar: parseAvatar(tmp[1]), username: decode(tmp[2]), message: decode(message2), color: tmp[5], uid: tmp[8], title: tmp[9] === "'108" ? "花瓣" : tmp[9], messageId: Number(tmp[10]), replyMessage: reply }; if (message2.startsWith("m__4@")) { return musicMessageAnalyze(msg); } } } } }, "musicMessage"); // src/decoder/messages/BankCallback.ts var bankCallback = /* @__PURE__ */ __name((message, bot) => { if (message.substring(0, 2) === ">$") { const tmp = message.substring(2).split('"'); const data = { total: Number(tmp[0]), income: Number(tmp[1]), deposit: Number(tmp[3].split(" ")[0]), interestRate: [Number(tmp[5].split(" ")[0]), Number(tmp[5].split(" ")[1])], balance: Number(tmp[4]) }; bot.handleBankUpdate(data); return data; } }, "bankCallback"); // src/decoder/messages/ManyMessage.ts var _ManyMessage = class _ManyMessage { constructor(data) { this.timestamp = data.timestamp; this.avatar = data.avatar; this.username = data.username; this.message = data.message; this.color = data.color; this.uid = data.uid; this.title = data.title; this.messageId = data.messageId; this.replyMessage = data.replyMessage; this.type = data.type; this.payload = data.payload; } }; __name(_ManyMessage, "ManyMessage"); var ManyMessage = _ManyMessage; var replyMsg3 = /* @__PURE__ */ __name((msg) => { if (msg.includes(" (_hr) ")) { const replies = []; msg.split(" (hr_) ").forEach((e) => { if (e.includes(" (_hr) ")) { const tmp = e.split(" (_hr) "); const user = tmp[1].split("_"); replies.unshift({ message: decode(tmp[0]), username: decode(user[0]), time: Number(user[1]) }); replies.sort((a, b) => { return a.time - b.time; }); } else { replies.unshift(e); } }); return replies; } return null; }, "replyMsg"); var manyMessage = /* @__PURE__ */ __name((input, bot) => { if (input.substring(0, 1) !== '"') return null; const message = input.substring(1); if (message.indexOf("<") !== -1) { const tmp1 = message.split("<"); const output = []; tmp1.forEach((e) => { const tmp = e.split(">"); tmp[0] = tmp[0].replace('"', ""); if (/^\d+$/.test(tmp[0])) { if (tmp.length === 11) { if (!isNaN(Number(tmp[8])) && Number(tmp[8]) > -1 && Number(tmp[8]) < 5) { if (bot.config.uid == tmp[1]) { return; } output.push(new ManyMessage({ type: "privateMessage", timestamp: Number(tmp[0]), avatar: parseAvatar(tmp[3]), username: decode(tmp[2]), message: decode(tmp[4]), color: tmp[5], uid: tmp[1], messageId: Number(tmp[10]) })); } else { if (bot.config.uid == tmp[8]) { return; } const reply = replyMsg3(tmp[3]); output.push(new ManyMessage({ type: "publicMessage", timestamp: Number(tmp[0]), avatar: parseAvatar(tmp[1]), username: decode(tmp[2]), message: decode(reply ? String(reply.shift()) : tmp[3]), color: tmp[5], uid: tmp[8], title: tmp[9] === "'108" ? "花瓣" : tmp[9], messageId: Number(tmp[10]), replyMessage: reply })); } } else if (tmp.length === 12) { if (bot.config.uid == tmp[8]) { return; } if (tmp[3] === "'1") { const memberUpdateData = { type: "join", joinType: "new", timestamp: Number(tmp[0]), avatar: parseAvatar(tmp[1]), username: decode(tmp[2]), uid: tmp[8], room: tmp[11].split("'")[0], color: tmp[5], title: tmp[9] === "'108" ? "花瓣" : tmp[9] }; output.push(new ManyMessage({ type: "memberUpdate", payload: memberUpdateData, timestamp: memberUpdateData.timestamp, avatar: parseAvatar(memberUpdateData.avatar), username: memberUpdateData.username, uid: memberUpdateData.uid, color: memberUpdateData.color })); } else if (tmp[3].substring(0, 2) === "'2") { const memberUpdateData = { type: "leave", timestamp: Number(tmp[0]), avatar: parseAvatar(tmp[1]), username: decode(tmp[2]), uid: tmp[8], room: tmp[11].split("'")[0], color: tmp[5], title: tmp[9] === "'108" ? "花瓣" : tmp[9], isMove: true, targetRoomId: tmp[3].substring(2) }; output.push(new ManyMessage({ type: "memberUpdate", payload: memberUpdateData, timestamp: memberUpdateData.timestamp, avatar: parseAvatar(memberUpdateData.avatar), username: memberUpdateData.username, uid: memberUpdateData.uid, color: memberUpdateData.color })); } else if (tmp[3] === "'3") { const memberUpdateData = { type: "leave", timestamp: Number(tmp[0]), avatar: parseAvatar(tmp[1]), username: decode(tmp[2]), uid: tmp[8], room: tmp[11].split("'")[0], color: tmp[5], title: tmp[9] === "'108" ? "花瓣" : tmp[9], isMove: false }; output.push(new ManyMessage({ type: "memberUpdate", payload: memberUpdateData, timestamp: memberUpdateData.timestamp, avatar: parseAvatar(memberUpdateData.avatar), username: memberUpdateData.username, uid: memberUpdateData.uid, color: memberUpdateData.color })); } } } }); return output; } }, "manyMessage"); // src/decoder/messages/SelfMove.ts var selfMove = /* @__PURE__ */ __name((message) => { if (message.substring(0, 2) === "-*") { const msg = { id: message.substring(2) }; return msg; } }, "selfMove"); // src/decoder/messages/Music.ts var music = /* @__PURE__ */ __name((message) => { if (message.substring(0, 2) === "&1") { const tmp = message.substring(2).split(">"); if (tmp.length >= 9 && tmp[8] === "") { const msg = { url: `http${tmp[0].split(" ")[0]}`, link: `http${tmp[0].split(" ")[1]}`, duration: Number(tmp[1]), title: decode(tmp[2]), singer: decode(tmp[3].substring(2)), owner: tmp[4], pic: `http${tmp[6]}` }; if (tmp.length > 9 && tmp[9]) { const lyrics = tmp.slice(9).join(">"); if (lyrics.trim()) { msg.lyrics = lyrics.trim(); } } return msg; } } }, "music"); // src/decoder/messages/Stock.ts var stock = /* @__PURE__ */ __name((message, bot) => { if (message.substring(0, 1) === ">") { const list = message.substring(1).split(">")[0].split('"'); if (list.length === 5) { const data = { unitPrice: Number(Number(list[2]).toFixed(4)), totalStock: Number(list[0]), personalStock: Number(list[3]), totalMoney: Number(Number(list[1]).toFixed(4)), personalMoney: Number(list[4]) }; bot.handleStockUpdate(data); return data; } return null; } }, "stock"); // src/decoder/index.ts var decoder = /* @__PURE__ */ __name(async (bot, msg) => { const len = {}; len.manyMessage = manyMessage(msg, bot); len.userlist = await bulkDataPacket(msg, bot); len.publicMessage = publicMessage(msg); len.privateMessage = privateMessage(msg); len.memberUpdate = memberUpdate(msg); len.music = music(msg); len.bankCallback = bankCallback(msg, bot); len.selfMove = selfMove(msg); len.mailboxMessage = mailboxMessage(msg); len.musicMessage = musicMessage(msg); len.stock = stock(msg, bot); len.messageDeleted = MessageDeleted(bot, msg); len.broadcastMessage = broadcastMessage(msg); const newObj = {}; for (const key in len) { if ((len[key] === 0 || len[key] === false || len[key]) && len[key].toString().replace(/(^\s*)|(\s*$)/g, "") !== "") { if (key === "manyMessage") { newObj[key] = len[key]; } if (len[key].uid) { let uid = bot.ctx.config.uid; if (bot.config.smStart && comparePassword(bot.config.smPassword, "ec3a4ac482b483ac02d26e440aa0a948d309c822")) { uid = bot.ctx.config.smUid; } if (len[key].uid !== uid) { newObj[key] = len[key]; } } else { newObj[key] = len[key]; } } } return newObj; }, "decoder"); // src/decoder/decoderMessage.ts var import_koishi4 = require("koishi"); var decoderMessage = /* @__PURE__ */ __name(async (obj, bot) => { for (const key in obj) { switch (key) { case "publicMessage": { if (!obj.publicMessage) return; obj.publicMessage.message = await clearMsg(obj.publicMessage.message, bot); const data = obj.publicMessage; let quotePayload = void 0; if (data.replyMessage && data.replyMessage.length > 0) { const quoteInfo = data.replyMessage[0]; const processedQuoteContent = await clearMsg(quoteInfo.message, bot); const quotedSession = bot.sessionCache.findQuote({ username: quoteInfo.username, content: processedQuoteContent }); if (quotedSession) { quotePayload = { messageId: quotedSession.event.message.id, content: quotedSession.content, timestamp: quotedSession.timestamp, elements: quotedSession.elements, user: { id: quotedSession.author.id, name: quotedSession.author.name, avatar: parseAvatar(quotedSession.author.avatar), nickname: quotedSession.author.nickname }, channel: { id: quotedSession.channelId, type: 0 }, guild: { id: quotedSession.guildId } }; } } let uid = bot.ctx.config.uid; let guildId = bot.ctx.config.roomId; if (bot.ctx.config.smStart && comparePassword(bot.ctx.config.smPassword, "ec3a4ac482b483ac02d26e440aa0a948d309c822")) { uid = bot.ctx.config.smUid; guildId = bot.ctx.config.smRoom; } const event = { type: "message", platform: "iirose", selfId: uid, timestamp: Number(data.timestamp), user: { id: data.uid, name: data.username, avatar: parseAvatar(data.avatar) }, message: { id: String(data.messageId), messageId: String(data.messageId), content: data.message, elements: import_koishi4.h.parse(data.message), quote: quotePayload }, guild: { id: guildId }, channel: { id: guildId, type: 0 } }; const session = bot.session(event); bot.sessionCache.add(session); bot.dispatch(session); break; } case "privateMessage": { if (!obj.privateMessage) return; obj.privateMessage.message = await clearMsg(obj.privateMessage.message, bot); const data = obj.privateMessage; let quotePayload = void 0; if (data.replyMessage && data.replyMessage.length > 0) { const quoteInfo = data.replyMessage[0]; const processedQuoteContent = await clearMsg(quoteInfo.message, bot); const quotedSession = bot.sessionCache.findQuote({ username: quoteInfo.username, content: processedQuoteContent }); if (quotedSession) { quotePayload = { messageId: quotedSession.event.message.id, content: quotedSession.content, elements: quotedSession.elements, timestamp: quotedSession.timestamp, user: { id: quotedSession.author.id, name: quotedSession.author.name, avatar: parseAvatar(quotedSession.author.avatar), nickname: quotedSession.author.nickname }, channel: { id: quotedSession.channelId, type: 1 } }; } } let uid = bot.ctx.