koishi-plugin-adapter-iirose
Version:
[IIROSE-蔷薇花园](https://iirose.com/)适配器
1,576 lines (1,543 loc) • 143 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,
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 = {
"&": "&",
"<": "<",
">": ">",
'"': """,
"'": "'",
"/": "/"
};
var encodeRegex = /[&<>"'/]/g;
function encode(str) {
if (typeof str !== "string") return "";
return str.replace(encodeRegex, (s) => entityMap[s]);
}
__name(encode, "encode");
var decodeEntityMap = {
"&": "&",
"<": "<",
">": ">",
""": '"',
"'": "'",
"/": "/"
};
var decodeRegex = /&|<|>|"|'|//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(/^(?:\){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.