koishi-plugin-adapter-iirose
Version:
[IIROSE-蔷薇花园](https://iirose.com/)适配器
1,537 lines (1,510 loc) • 140 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_koishi9 = require("koishi");
// src/bot/bot.ts
var import_koishi7 = require("koishi");
// src/utils/utils.ts
var import_koishi2 = require("koishi");
// 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"));
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("public:") ? 0 : 1
}
};
if (channelId.startsWith("public:")) {
event.guild = { id: channelId.substring(7) };
}
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 getImageAsBase64(bot, url) {
if (!url) return null;
try {
const { data, type } = await bot.ctx.http.file(url);
const buffer = Buffer.from(data);
const base64 = buffer.toString("base64");
const mimeType = type || "image/jpeg";
return `data:${mimeType};base64,${base64}`;
} catch (error) {
bot.logger.warn(`获取或转换图片失败: ${url}`, error);
return null;
}
}
__name(getImageAsBase64, "getImageAsBase64");
// src/bot/sendMessage.ts
var import_koishi6 = 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/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.ts
var import_koishi5 = require("koishi");
var zlib = __toESM(require("node:zlib"));
// 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/decoder/decoderMessage.ts
var import_koishi3 = 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_koishi3.h.parse(data.message),
quote: quotePayload
},
guild: {
id: guildId
},
channel: {
id: `public:${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.config.uid;
if (bot.ctx.config.smStart && comparePassword(bot.ctx.config.smPassword, "ec3a4ac482b483ac02d26e440aa0a948d309c822")) {
uid = bot.ctx.config.smUid;
}
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_koishi3.h.parse(data.message),
quote: quotePayload
},
channel: {
id: `private:${data.uid}`,
type: 1
}
};
const session = bot.session(event);
bot.sessionCache.add(session);
bot.dispatch(session);
break;
}
case "memberUpdate": {
const data = obj.memberUpdate;
if (!data) return;
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 createEvent = /* @__PURE__ */ __name((type) => {
const session = bot.session({
type,
platform: "iirose",
selfId: uid,
timestamp: Number(data.timestamp),
guild: { id: guildId },
channel: {
id: `public:${guildId}`,
type: 0
},
user: {
id: data.uid,
name: data.username,
avatar: parseAvatar(data.avatar)
}
});
bot.fulllogInfo(type, session);
if (type === "iirose/guild-member-refresh") {
bot.ctx.emit("iirose/guild-member-refresh", session);
} else {
bot.dispatch(session);
}
}, "createEvent");
const handleRefresh = /* @__PURE__ */ __name((uid2) => {
if (bot.userLeaveTimers.has(uid2)) {
bot.userLeaveTimers.get(uid2)();
bot.userLeaveTimers.delete(uid2);
}
if (bot.userJoinTimers.has(uid2)) {
bot.userJoinTimers.get(uid2)();
bot.userJoinTimers.delete(uid2);
}
createEvent("iirose/guild-member-refresh");
}, "handleRefresh");
if (data.type === "join") {
if (bot.userLeaveTimers.has(data.uid)) {
handleRefresh(data.uid);
} else {
const joinTimer = bot.ctx.setTimeout(() => {
if (data.joinType === "new" || data.joinType === "reconnect") {
createEvent("guild-member-added");
}
bot.userJoinTimers.delete(data.uid);
}, 1e3);
bot.userJoinTimers.set(data.uid, joinTimer);
}
} else if (data.type === "leave") {
if (bot.userJoinTimers.has(data.uid)) {
handleRefresh(data.uid);
} else {
if (!data.isMove) {
const leaveTimerDisposer = bot.ctx.setTimeout(() => {
const session = bot.session({
type: "guild-member-removed",
platform: "iirose",
selfId: uid,
timestamp: Date.now(),
guild: { id: guildId },
channel: {
id: `public:${guildId}`,
type: 0
},
user: {
id: data.uid,
name: data.username,
avatar: parseAvatar(data.avatar)
}
});
bot.dispatch(session);
bot.fulllogInfo("guild-member-removed", session);
bot.userLeaveTimers.delete(data.uid);
}, bot.config.refreshTimeout);
bot.userLeaveTimers.set(data.uid, leaveTimerDisposer);
} else {
const session = bot.session({
type: "guild-member-removed",
platform: "iirose",
selfId: uid,
timestamp: Date.now(),
guild: { id: guildId },
channel: {
id: `public:${guildId}`,
type: 0
},
user: {
id: data.uid,
name: data.username,
avatar: parseAvatar(data.avatar)
}
});
bot.dispatch(session);
bot.fulllogInfo("guild-member-removed", session);
}
}
if (data.isMove) {
const switchRoomData = {
timestamp: Number(data.timestamp),
avatar: parseAvatar(data.avatar),
username: data.username,
color: data.color,
uid: data.uid,
title: data.title,
room: data.room,
targetRoom: data.targetRoomId
};
const switchRoomEvent = {
type: "iirose/guild-member-switchRoom",
platform: "iirose",
guildId,
timestamp: Number(data.timestamp),
user: {
id: data.uid,
name: data.username
},
_data: switchRoomData
};
const switchRoomSession = bot.session(switchRoomEvent);
bot.fulllogInfo("iirose/guild-member-switchRoom", switchRoomSession, switchRoomData);
bot.ctx.emit("iirose/guild-member-switchRoom", switchRoomSession, switchRoomData);
}
} else if (data.type === "refresh") {
handleRefresh(data.uid);
}
break;
}
case "music": {
const data = obj.music;
const event = {
type: "music",
platform: "iirose",
guildId: bot.config.roomId
};
const session = bot.session(event);
bot.fulllogInfo("iirose/music-play", session, data);
bot.ctx.emit("iirose/music-play", session, data);
break;
}
case "selfMove": {
const data = obj.selfMove;
const event = {
type: "selfMove",
platform: "iirose",
guildId: bot.config.roomId
};
const session = bot.session(event);
bot.fulllogInfo("iirose/selfMove", session, data);
bot.ctx.emit("iirose/selfMove", session, data);
break;
}
case "messageDeleted": {
const data = obj.messageDeleted;
if (!data) return;
let uid = bot.ctx.config.uid;
if (bot.ctx.config.smStart && comparePassword(bot.ctx.config.smPassword, "ec3a4ac482b483ac02d26e440aa0a948d309c822")) {
uid = bot.ctx.config.smUid;
}
const session = bot.session({
type: "message-deleted",
user: {
id: data.userId,
name: data.userId
},
message: {
id: data.messageId,
messageId: data.messageId,
content: "",
elements: []
},
timestamp: data.timestamp,
platform: "iirose"
});
session.channelId = data.channelId;
session.selfId = uid;
bot.fulllogInfo("message-deleted 事件", session);
bot.dispatch(session);
break;
}
case "mailboxMessage": {
const data = obj.mailboxMessage;
if (!data) break;
const session = bot.session({
type: `iirose/${data.type}`,
platform: "iirose",
guild: { id: bot.config.roomId },
_data: data
});
bot.fulllogInfo(`iirose/${data.type}`, session, data);
switch (data.type) {
case "roomNotice":
bot.ctx.emit("iirose/roomNotice", session, data);
break;
case "follower":
bot.ctx.emit("iirose/follower", session, data);
break;
case "like":
bot.ctx.emit("iirose/like", session, data);
break;
case "dislike":
bot.ctx.emit("iirose/dislike", session, data);
break;
case "payment":
bot.ctx.emit("iirose/payment", session, data);
break;
}
break;
}
case "broadcastMessage": {
const data = obj.broadcastMessage;
if (!data) return;
const processedContent = await clearMsg(data.message, bot);
const event = {
type: "broadcast",
platform: "iirose",
guildId: bot.config.roomId,
timestamp: Number(data.timestamp),
user: {
id: data.username,
// 广播消息没有提供用户ID,暂用用户名代替
name: data.username
},
message: {
id: data.messageId,
messageId: data.messageId,
content: processedContent,
elements: import_koishi3.h.parse(processedContent)
}
};
const session = bot.session(event);
bot.fulllogInfo("iirose/broadcast", session, data);
bot.ctx.emit("iirose/broadcast", session, data);
break;
}
case "musicMessage": {
const data = obj.musicMessage;
if (!data) return;
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 musicData = {
type: "iirose:music",
name: data.musicName,
singer: data.musicSinger,
pic: data.musicPic,
color: data.musicColor
};
const elements = (0, import_koishi3.h)("json", { data: JSON.stringify(musicData) });
const content = elements.toString();
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,
elements: [elements]
},
guild: {
id: guildId
},
channel: {
id: `public:${guildId}`,
type: 0
}
};
const session = bot.session(event);
bot.sessionCache.add(session);
bot.dispatch(session);
break;
}
default: {
break;
}
}
}
}, "decoderMessage");
// 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 = `public:${bot.config.roomId}`;
if (bot.config.smStart) {
channelId = `public:${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_koishi4 = 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_koishi4.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 = await getImageAsBase64(bot, 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]