UNPKG

koishi-plugin-adapter-onebot

Version:
1,231 lines (1,215 loc) 45.9 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 name in all) __defProp(target, name, { get: all[name], 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, { BaseBot: () => BaseBot2, CQCode: () => CQCode, HttpServer: () => HttpServer, OneBot: () => utils_exports, OneBotBot: () => OneBotBot, OneBotMessageEncoder: () => OneBotMessageEncoder, PRIVATE_PFX: () => PRIVATE_PFX, QQGuildBot: () => QQGuildBot, WsClient: () => WsClient, WsServer: () => WsServer, accept: () => accept, default: () => src_default }); module.exports = __toCommonJS(src_exports); // src/bot/index.ts var import_koishi8 = require("koishi"); // src/http.ts var import_koishi3 = require("koishi"); // src/utils.ts var utils_exports = {}; __export(utils_exports, { Internal: () => Internal, SafetyLevel: () => SafetyLevel, TimeoutError: () => TimeoutError, adaptChannel: () => adaptChannel, adaptGuild: () => adaptGuild, adaptMessage: () => adaptMessage, adaptQQGuildMemberInfo: () => adaptQQGuildMemberInfo, adaptQQGuildMemberProfile: () => adaptQQGuildMemberProfile, adaptSession: () => adaptSession, decodeGuildMember: () => decodeGuildMember, decodeUser: () => decodeUser, dispatchSession: () => dispatchSession }); var import_koishi2 = require("koishi"); var qface = __toESM(require("qface")); // src/types.ts var import_koishi = require("koishi"); var SafetyLevel = /* @__PURE__ */ ((SafetyLevel2) => { SafetyLevel2[SafetyLevel2["safe"] = 0] = "safe"; SafetyLevel2[SafetyLevel2["unknown"] = 1] = "unknown"; SafetyLevel2[SafetyLevel2["danger"] = 2] = "danger"; return SafetyLevel2; })(SafetyLevel || {}); var TimeoutError = class extends Error { static { __name(this, "TimeoutError"); } constructor(args, url) { super(`Timeout with request ${url}, args: ${JSON.stringify(args)}`); Object.defineProperties(this, { args: { value: args }, url: { value: url } }); } }; var SenderError = class extends Error { static { __name(this, "SenderError"); } constructor(args, url, retcode) { super(`Error with request ${url}, args: ${JSON.stringify(args)}, retcode: ${retcode}`); Object.defineProperties(this, { code: { value: retcode }, args: { value: args }, url: { value: url } }); } }; var Internal = class _Internal { constructor(bot) { this.bot = bot; } static { __name(this, "Internal"); } async _get(action, params = {}) { this.bot.logger.debug("[request] %s %o", action, params); const response = await this._request(action, params); this.bot.logger.debug("[response] %o", response); const { data, retcode } = response; if (retcode === 0) return data; throw new SenderError(params, action, retcode); } async setGroupAnonymousBan(group_id, meta, duration) { const args = { group_id, duration }; args[typeof meta === "string" ? "flag" : "anonymous"] = meta; await this._get("set_group_anonymous_ban", args); } async setGroupAnonymousBanAsync(group_id, meta, duration) { const args = { group_id, duration }; args[typeof meta === "string" ? "flag" : "anonymous"] = meta; await this._get("set_group_anonymous_ban_async", args); } static asyncPrefixes = ["set", "send", "delete", "create", "upload", "move", "rename"]; static prepareMethod(name) { const prop = (0, import_koishi.camelize)(name.replace(/^[_.]/, "")); const isAsync = _Internal.asyncPrefixes.some((prefix) => prop.startsWith(prefix)); return [prop, isAsync]; } static prepareArg(name, params, args) { const fixedArg = Object.fromEntries(params.map((name2, index) => [name2, args[index]])); for (const key in fixedArg) { if (!name.includes("guild") && key.endsWith("_id")) { const value = +fixedArg[key]; if (Math.abs(value) < 4294967296) { fixedArg[key] = value; } } } return fixedArg; } static define(name, ...params) { const [prop, isAsync] = _Internal.prepareMethod(name); _Internal.prototype[prop] = async function(...args) { const data = await this._get(name, _Internal.prepareArg(name, params, args)); if (!isAsync) return data; }; isAsync && (_Internal.prototype[prop + "Async"] = async function(...args) { await this._get(name + "_async", _Internal.prepareArg(name, params, args)); }); } static defineExtract(name, key, ...params) { const [prop, isAsync] = _Internal.prepareMethod(name); _Internal.prototype[prop] = async function(...args) { const data = await this._get(name, _Internal.prepareArg(name, params, args)); return data[key]; }; isAsync && (_Internal.prototype[prop + "Async"] = async function(...args) { await this._get(name + "_async", _Internal.prepareArg(name, params, args)); }); } }; Internal.defineExtract("send_private_msg", "message_id", "user_id", "message", "auto_escape"); Internal.defineExtract("send_group_msg", "message_id", "group_id", "message", "auto_escape"); Internal.defineExtract("send_group_forward_msg", "message_id", "group_id", "messages"); Internal.defineExtract("send_private_forward_msg", "message_id", "user_id", "messages"); Internal.define("delete_msg", "message_id"); Internal.define("mark_msg_as_read", "message_id"); Internal.define("set_essence_msg", "message_id"); Internal.define("delete_essence_msg", "message_id"); Internal.define("send_group_sign", "group_id"); Internal.define("send_like", "user_id", "times"); Internal.define("get_msg", "message_id"); Internal.define("get_essence_msg_list", "group_id"); Internal.define("ocr_image", "image"); Internal.defineExtract("get_forward_msg", "messages", "message_id"); Internal.defineExtract(".get_word_slices", "slices", "content"); Internal.define("get_group_msg_history", "group_id", "message_seq"); Internal.define("set_friend_add_request", "flag", "approve", "remark"); Internal.define("set_group_add_request", "flag", "sub_type", "approve", "reason"); Internal.defineExtract("_get_model_show", "variants", "model"); Internal.define("_set_model_show", "model", "model_show"); Internal.define("set_group_kick", "group_id", "user_id", "reject_add_request"); Internal.define("set_group_ban", "group_id", "user_id", "duration"); Internal.define("set_group_whole_ban", "group_id", "enable"); Internal.define("set_group_admin", "group_id", "user_id", "enable"); Internal.define("set_group_anonymous", "group_id", "enable"); Internal.define("set_group_card", "group_id", "user_id", "card"); Internal.define("set_group_leave", "group_id", "is_dismiss"); Internal.define("set_group_special_title", "group_id", "user_id", "special_title", "duration"); Internal.define("set_group_name", "group_id", "group_name"); Internal.define("set_group_portrait", "group_id", "file", "cache"); Internal.define("_send_group_notice", "group_id", "content", "image", "pinned", "confirm_required"); Internal.define("_get_group_notice", "group_id"); Internal.define("_del_group_notice", "group_id", "notice_id"); Internal.define("get_group_at_all_remain", "group_id"); Internal.define("get_login_info"); Internal.define("qidian_get_login_info"); Internal.define("set_qq_profile", "nickname", "company", "email", "college", "personal_note"); Internal.define("set_qq_avatar", "file"); Internal.define("set_online_status", "status", "ext_status", "battery_status"); Internal.define("get_stranger_info", "user_id", "no_cache"); Internal.define("_get_vip_info", "user_id"); Internal.define("get_friend_list"); Internal.define("get_unidirectional_friend_list"); Internal.define("delete_friend", "user_id"); Internal.define("delete_unidirectional_friend", "user_id"); Internal.define("get_group_info", "group_id", "no_cache"); Internal.define("get_group_list", "no_cache"); Internal.define("get_group_member_info", "group_id", "user_id", "no_cache"); Internal.define("get_group_member_list", "group_id"); Internal.define("get_group_honor_info", "group_id", "type"); Internal.define("get_group_system_msg"); Internal.define("get_group_file_system_info", "group_id"); Internal.define("get_group_root_files", "group_id"); Internal.define("get_group_files_by_folder", "group_id", "folder_id"); Internal.define("upload_private_file", "user_id", "file", "name"); Internal.define("upload_group_file", "group_id", "file", "name", "folder"); Internal.define("create_group_file_folder", "group_id", "folder_id", "name"); Internal.define("delete_group_folder", "group_id", "folder_id"); Internal.define("delete_group_file", "group_id", "folder_id", "file_id", "busid"); Internal.defineExtract("get_group_file_url", "url", "group_id", "file_id", "busid"); Internal.defineExtract("download_file", "file", "url", "headers", "thread_count"); Internal.defineExtract("get_online_clients", "clients", "no_cache"); Internal.defineExtract("check_url_safely", "level", "url"); Internal.defineExtract("get_cookies", "cookies", "domain"); Internal.defineExtract("get_csrf_token", "token"); Internal.define("get_credentials", "domain"); Internal.define("get_record", "file", "out_format", "full_path"); Internal.define("get_image", "file"); Internal.defineExtract("can_send_image", "yes"); Internal.defineExtract("can_send_record", "yes"); Internal.define("get_status"); Internal.define("get_version_info"); Internal.define("set_restart", "delay"); Internal.define("reload_event_filter"); Internal.define("clean_cache"); Internal.define("get_guild_service_profile"); Internal.define("get_guild_list"); Internal.define("get_guild_meta_by_guest", "guild_id"); Internal.define("get_guild_channel_list", "guild_id", "no_cache"); Internal.define("get_guild_member_list", "guild_id", "next_token"); Internal.define("get_guild_member_profile", "guild_id", "user_id"); Internal.defineExtract("send_guild_channel_msg", "message_id", "guild_id", "channel_id", "message"); Internal.define("upload_image", "file"); Internal.defineExtract("get_private_file_url", "url", "user_id", "file_id", "file_hash"); Internal.define("move_group_file", "group_id", "file_id", "parent_directory", "target_directory"); Internal.define("delete_group_file_folder", "group_id", "folder_id"); Internal.define("rename_group_file_folder", "group_id", "folder_id", "new_folder_name"); // src/utils.ts var decodeUser = /* @__PURE__ */ __name((user) => ({ id: user.tiny_id || user.user_id.toString(), name: user.nickname, userId: user.tiny_id || user.user_id.toString(), avatar: user.user_id ? `http://q.qlogo.cn/headimg_dl?dst_uin=${user.user_id}&spec=640` : void 0, username: user.nickname }), "decodeUser"); var decodeGuildMember = /* @__PURE__ */ __name((user) => ({ user: decodeUser(user), nick: user.card, roles: [user.role] }), "decodeGuildMember"); var adaptQQGuildMemberInfo = /* @__PURE__ */ __name((user) => ({ user: { id: user.tiny_id, name: user.nickname, isBot: user.role_name === "机器人" }, name: user.nickname, roles: user.role_name ? [user.role_name] : [] }), "adaptQQGuildMemberInfo"); var adaptQQGuildMemberProfile = /* @__PURE__ */ __name((user) => ({ user: { id: user.tiny_id, name: user.nickname, isBot: user.roles?.some((r) => r.role_name === "机器人") }, name: user.nickname, roles: user.roles?.map((r) => r.role_name) || [] }), "adaptQQGuildMemberProfile"); async function adaptMessage(bot, data, message = {}, payload = message) { message.id = message.messageId = data.message_id.toString(); const chain = CQCode.parse(data.message); if (bot.config.advanced.splitMixedContent) { chain.forEach((item, index) => { if (item.type !== "image") return; const left = chain[index - 1]; if (left && left.type === "text" && left.attrs.content.trimEnd() === left.attrs.content) { left.attrs.content += " "; } const right = chain[index + 1]; if (right && right.type === "text" && right.attrs.content.trimStart() === right.attrs.content) { right.attrs.content = " " + right.attrs.content; } }); } message.elements = import_koishi2.h.transform(chain, { at(attrs) { if (attrs.qq !== "all") return import_koishi2.h.at(attrs.qq, { name: attrs.name }); return (0, import_koishi2.h)("at", { type: "all" }); }, face({ id }) { const name = qface.get(id)?.QDes.slice(1); return (0, import_koishi2.h)("face", { id, name, platform: bot.platform }, [ import_koishi2.h.image(qface.getUrl(id)) ]); }, image(attrs) { return (0, import_koishi2.h)("img", { src: attrs.url || attrs.file, ...(0, import_koishi2.omit)(attrs, ["url"]) }); }, record(attrs) { return (0, import_koishi2.h)("audio", { src: attrs.url || attrs.file, ...(0, import_koishi2.omit)(attrs, ["url"]) }); }, video(attrs) { return (0, import_koishi2.h)("video", { src: attrs.url || attrs.file, ...(0, import_koishi2.omit)(attrs, ["url"]) }); }, file(attrs) { return (0, import_koishi2.h)("file", { src: attrs.url || attrs.file, ...(0, import_koishi2.omit)(attrs, ["url"]) }); } }); const [guildId, channelId] = decodeGuildChannelId(data); if (message.elements[0]?.type === "reply") { const reply = message.elements.shift(); message.quote = await bot.getMessage(channelId, reply.attrs.id).catch((error) => { bot.logger.warn(error); return void 0; }); } message.content = message.elements.join(""); if (!payload) return message; payload.user = decodeUser(data.sender); payload.member = decodeGuildMember(data.sender); payload.timestamp = data.time * 1e3; payload.guild = guildId && { id: guildId }; payload.channel = channelId && { id: channelId, type: guildId ? import_koishi2.Universal.Channel.Type.TEXT : import_koishi2.Universal.Channel.Type.DIRECT }; return message; } __name(adaptMessage, "adaptMessage"); var decodeGuildChannelId = /* @__PURE__ */ __name((data) => { if (data.guild_id) { return [data.guild_id, data.channel_id]; } else if (data.group_id) { return [data.group_id.toString(), data.group_id.toString()]; } else { return [void 0, "private:" + data.sender.user_id]; } }, "decodeGuildChannelId"); var adaptGuild = /* @__PURE__ */ __name((info) => { if (info.guild_id) { const guild = info; return { id: guild.guild_id, name: guild.guild_name }; } else { const group = info; return { id: group.group_id.toString(), name: group.group_name }; } }, "adaptGuild"); var adaptChannel = /* @__PURE__ */ __name((info) => { if (info.channel_id) { const channel = info; return { id: channel.channel_id, name: channel.channel_name, type: import_koishi2.Universal.Channel.Type.TEXT }; } else { const group = info; return { id: group.group_id.toString(), name: group.group_name, type: import_koishi2.Universal.Channel.Type.TEXT }; } }, "adaptChannel"); async function dispatchSession(bot, data) { if (data.self_tiny_id) { bot = bot["guildBot"]; if (!bot) return; } const session = await adaptSession(bot, data); if (!session) return; session.setInternal("onebot", data); bot.dispatch(session); } __name(dispatchSession, "dispatchSession"); async function adaptSession(bot, data) { const session = bot.session(); session.selfId = data.self_tiny_id ? data.self_tiny_id : "" + data.self_id; session.type = data.post_type; if (data.post_type === "message" || data.post_type === "message_sent") { await adaptMessage(bot, data, session.event.message = {}, session.event); if (data.post_type === "message_sent" && !session.guildId) { session.channelId = "private:" + data.target_id; } session.type = "message"; session.subtype = data.message_type === "guild" ? "group" : data.message_type; session.isDirect = data.message_type === "private"; session.subsubtype = data.message_type; return session; } session.subtype = data.sub_type; if (data.user_id) session.userId = "" + data.user_id; if (data.group_id) session.guildId = session.channelId = "" + data.group_id; if (data.guild_id) session.guildId = "" + data.guild_id; if (data.channel_id) session.channelId = "" + data.channel_id; if (data.target_id) session["targetId"] = "" + data.target_id; if (data.operator_id) session.operatorId = "" + data.operator_id; if (data.message_id) session.messageId = "" + data.message_id; if (data.post_type === "request") { session.content = data.comment; session.messageId = data.flag; if (data.request_type === "friend") { session.type = "friend-request"; session.channelId = `private:${session.userId}`; } else if (data.sub_type === "add") { session.type = "guild-member-request"; } else { session.type = "guild-request"; } } else if (data.post_type === "notice") { switch (data.notice_type) { case "group_recall": session.type = "message-deleted"; session.subtype = "group"; break; case "friend_recall": session.type = "message-deleted"; session.subtype = "private"; session.channelId = `private:${session.userId}`; break; // from go-cqhttp source code, but not mentioned in official docs case "guild_channel_recall": session.type = "message-deleted"; session.subtype = "guild"; break; case "friend_add": session.type = "friend-added"; break; case "group_admin": session.type = "guild-member"; session.subtype = "role"; break; case "group_ban": session.type = "guild-member"; session.subtype = "ban"; break; case "group_decrease": session.type = session.userId === session.selfId ? "guild-deleted" : "guild-member-deleted"; session.subtype = session.userId === session.operatorId ? "active" : "passive"; break; case "group_increase": session.type = session.userId === session.selfId ? "guild-added" : "guild-member-added"; session.subtype = session.userId === session.operatorId ? "active" : "passive"; break; case "group_card": session.type = "guild-member"; session.subtype = "nickname"; break; case "notify": session.type = "notice"; session.subtype = (0, import_koishi2.hyphenate)(data.sub_type); if (session.subtype === "poke") { session.channelId ||= `private:${session.userId}`; } else if (session.subtype === "honor") { session.subsubtype = (0, import_koishi2.hyphenate)(data.honor_type); } break; case "message_reactions_updated": session.type = "onebot"; session.subtype = "message-reactions-updated"; break; case "channel_created": session.type = "onebot"; session.subtype = "channel-created"; break; case "channel_updated": session.type = "onebot"; session.subtype = "channel-updated"; break; case "channel_destroyed": session.type = "onebot"; session.subtype = "channel-destroyed"; break; // https://github.com/koishijs/koishi-plugin-adapter-onebot/issues/33 // case 'offline_file': // session.elements = [h('file', data.file)] // session.type = 'message' // session.subtype = 'private' // session.isDirect = true // session.subsubtype = 'offline-file-added' // break // case 'group_upload': // session.elements = [h('file', data.file)] // session.type = 'message' // session.subtype = 'group' // session.subsubtype = 'guild-file-added' // break default: return; } } else return; return session; } __name(adaptSession, "adaptSession"); // src/http.ts var import_crypto = require("crypto"); var HttpServer = class extends import_koishi3.Adapter { static { __name(this, "HttpServer"); } static inject = ["server"]; async fork(ctx, bot) { super.fork(ctx, bot); const config = bot.config; const { endpoint, token } = config; if (!endpoint) return; const http = ctx.http.extend(config).extend({ headers: { "Authorization": `Token ${token}` } }); bot.internal._request = async (action, params) => { return http.post("/" + action, params); }; return bot.initialize(); } async connect(bot) { const { secret, path = "/onebot" } = bot.config; this.ctx.server.post(path, (ctx) => { if (secret) { const signature = ctx.headers["x-signature"]; if (!signature) return ctx.status = 401; const sig = (0, import_crypto.createHmac)("sha1", secret).update(ctx.request.body[Symbol.for("unparsedBody")]).digest("hex"); if (signature !== `sha1=${sig}`) return ctx.status = 403; } const selfId = ctx.headers["x-self-id"].toString(); const bot2 = this.bots.find((bot3) => bot3.selfId === selfId); if (!bot2) return ctx.status = 403; bot2.logger.debug("[receive] %o", ctx.request.body); dispatchSession(bot2, ctx.request.body); }); } }; ((HttpServer2) => { HttpServer2.Options = import_koishi3.Schema.intersect([ import_koishi3.Schema.object({ protocol: import_koishi3.Schema.const("http").required(), path: import_koishi3.Schema.string().description("服务器监听的路径。").default("/onebot"), secret: import_koishi3.Schema.string().description("接收事件推送时用于验证的字段,应该与 OneBot 的 secret 配置保持一致。").role("secret") }).description("连接设置"), import_koishi3.HTTP.createConfig(true) ]); })(HttpServer || (HttpServer = {})); // src/ws.ts var import_koishi4 = require("koishi"); var WsClient = class extends import_koishi4.Adapter.WsClient { static { __name(this, "WsClient"); } accept(socket) { accept(socket, this.bot); } prepare() { const { token, endpoint } = this.bot.config; const http = this.ctx.http.extend(this.bot.config); if (token) http.config.headers.Authorization = `Bearer ${token}`; return http.ws(endpoint); } }; ((WsClient2) => { WsClient2.Options = import_koishi4.Schema.intersect([ import_koishi4.Schema.object({ protocol: import_koishi4.Schema.const("ws").required(process.env.KOISHI_ENV !== "browser"), responseTimeout: import_koishi4.Schema.natural().role("time").default(import_koishi4.Time.minute).description("等待响应的时间 (单位为毫秒)。") }).description("连接设置"), import_koishi4.HTTP.createConfig(true), import_koishi4.Adapter.WsClientConfig ]); })(WsClient || (WsClient = {})); var kSocket = Symbol("socket"); var WsServer = class extends import_koishi4.Adapter { static { __name(this, "WsServer"); } static inject = ["server"]; logger; wsServer; constructor(ctx, bot) { super(ctx); this.logger = ctx.logger("onebot"); const { path = "/onebot" } = bot.config; this.wsServer = ctx.server.ws(path, (socket, { headers }) => { this.logger.debug("connected with", headers); if (headers["x-client-role"] !== "Universal") { return socket.close(1008, "invalid x-client-role"); } const selfId = headers["x-self-id"].toString(); const bot2 = this.bots.find((bot3) => bot3.selfId === selfId); if (!bot2) return socket.close(1008, "invalid x-self-id"); bot2[kSocket] = socket; accept(socket, bot2); }); ctx.on("dispose", () => { this.logger.debug("ws server closing"); this.wsServer.close(); }); } async disconnect(bot) { bot[kSocket]?.close(); bot[kSocket] = null; } }; ((WsServer2) => { WsServer2.Options = import_koishi4.Schema.object({ protocol: import_koishi4.Schema.const("ws-reverse").required(process.env.KOISHI_ENV === "browser"), path: import_koishi4.Schema.string().description("服务器监听的路径。").default("/onebot"), responseTimeout: import_koishi4.Schema.natural().role("time").default(import_koishi4.Time.minute).description("等待响应的时间 (单位为毫秒)。") }).description("连接设置"); })(WsServer || (WsServer = {})); var counter = 0; var listeners = {}; function accept(socket, bot) { socket.addEventListener("message", ({ data }) => { let parsed; data = data.toString(); try { parsed = JSON.parse(data); } catch (error) { return bot.logger.warn("cannot parse message", data); } if ("post_type" in parsed) { bot.logger.debug("[receive] %o", parsed); dispatchSession(bot, parsed); } else if (parsed.echo in listeners) { listeners[parsed.echo](parsed); delete listeners[parsed.echo]; } }); socket.addEventListener("close", () => { delete bot.internal._request; }); bot.internal._request = (action, params) => { const data = { action, params, echo: ++counter }; data.echo = ++counter; return new Promise((resolve, reject) => { listeners[data.echo] = resolve; setTimeout(() => { delete listeners[data.echo]; reject(new TimeoutError(params, action)); }, bot.config.responseTimeout); socket.send(JSON.stringify(data)); }); }; bot.initialize(); } __name(accept, "accept"); // src/bot/base.ts var import_koishi6 = require("koishi"); // src/bot/message.ts var import_koishi5 = require("koishi"); var import_node_url = require("node:url"); var State = class { constructor(type) { this.type = type; } static { __name(this, "State"); } author = {}; children = []; }; var PRIVATE_PFX = "private:"; var OneBotMessageEncoder = class extends import_koishi5.MessageEncoder { static { __name(this, "OneBotMessageEncoder"); } stack = [new State("message")]; children = []; async prepare() { super.prepare(); const { event: { channel } } = this.session; if (!channel.type) { channel.type = channel.id.startsWith(PRIVATE_PFX) ? import_koishi5.Universal.Channel.Type.DIRECT : import_koishi5.Universal.Channel.Type.TEXT; } if (!this.session.isDirect) { this.session.guildId ??= this.channelId; } } async forward() { if (!this.stack[0].children.length) return; const session = this.bot.session(); session.content = ""; session.messageId = this.session.event.channel.type === import_koishi5.Universal.Channel.Type.DIRECT ? "" + await this.bot.internal.sendPrivateForwardMsg(this.channelId.slice(PRIVATE_PFX.length), this.stack[0].children) : "" + await this.bot.internal.sendGroupForwardMsg(this.channelId, this.stack[0].children); session.userId = this.bot.selfId; session.channelId = this.session.channelId; session.guildId = this.session.guildId; session.isDirect = this.session.isDirect; session.app.emit(session, "send", session); this.results.push(session.event.message); } async flush() { while (true) { const first = this.children[0]; if (first?.type !== "text") break; first.data.text = first.data.text.trimStart(); if (first.data.text) break; this.children.shift(); } while (true) { const last = this.children[this.children.length - 1]; if (last?.type !== "text") break; last.data.text = last.data.text.trimEnd(); if (last.data.text) break; this.children.pop(); } const { type, author } = this.stack[0]; if (!this.children.length && !author.messageId) return; if (type === "forward") { if (author.messageId) { this.stack[1].children.push({ type: "node", data: { id: author.messageId } }); } else { this.stack[1].children.push({ type: "node", data: { name: author.name || this.bot.user.name, uin: author.id || this.bot.userId, content: this.children, time: `${Math.floor((+author.time || Date.now()) / 1e3)}` } }); } this.children = []; return; } const session = this.bot.session(); session.content = ""; session.messageId = this.bot.parent ? "" + await this.bot.internal.sendGuildChannelMsg(this.session.guildId, this.channelId, this.children) : this.session.event.channel.type === import_koishi5.Universal.Channel.Type.DIRECT ? "" + await this.bot.internal.sendPrivateMsg(this.channelId.slice(PRIVATE_PFX.length), this.children) : "" + await this.bot.internal.sendGroupMsg(this.channelId, this.children); session.userId = this.bot.selfId; session.channelId = this.session.channelId; session.guildId = this.session.guildId; session.isDirect = this.session.isDirect; session.app.emit(session, "send", session); this.results.push(session.event.message); this.children = []; } async sendFile(attrs) { const src = attrs.src || attrs.url; const name = attrs.title || (await this.bot.ctx.http.file(src)).filename; const file = src.startsWith("file:") ? (0, import_node_url.fileURLToPath)(src) : await this.bot.internal.downloadFile(src); if (this.session.event.channel.type === import_koishi5.Universal.Channel.Type.DIRECT) { await this.bot.internal.uploadPrivateFile( this.channelId.slice(PRIVATE_PFX.length), file, name ); } else { await this.bot.internal.uploadGroupFile( this.channelId, file, name ); } const session = this.bot.session(); session.messageId = ""; session.content = ""; session.userId = this.bot.selfId; session.channelId = this.session.channelId; session.guildId = this.session.guildId; session.isDirect = this.session.isDirect; session.app.emit(session, "send", session); this.results.push(session.event.message); } text(text) { this.children.push({ type: "text", data: { text } }); } async visit(element) { let { type, attrs, children } = element; if (type === "text") { this.text(attrs.content); } else if (type === "br") { this.text("\n"); } else if (type === "p") { const prev = this.children[this.children.length - 1]; if (prev?.type === "text") { if (!prev.data.text.endsWith("\n")) { prev.data.text += "\n"; } } else { this.text("\n"); } await this.render(children); this.text("\n"); } else if (type === "at") { if (attrs.type === "all") { this.children.push({ type: "at", data: { qq: "all" } }); } else { this.children.push({ type: "at", data: { qq: attrs.id, name: attrs.name } }); } } else if (type === "sharp") { if (attrs.id) this.text(attrs.id); } else if (type === "face") { if (attrs.platform && attrs.platform !== this.bot.platform) { await this.render(children); } else { this.children.push({ type: "face", data: { id: attrs.id } }); } } else if (type === "a") { await this.render(children); if (attrs.href) this.text(`(${attrs.href})`); } else if (["video", "audio", "image", "img"].includes(type)) { if (type === "video" || type === "audio") await this.flush(); if (type === "audio") type = "record"; if (type === "img") type = "image"; attrs = { ...attrs }; attrs.file = attrs.src || attrs.url; delete attrs.src; delete attrs.url; if (attrs.cache) { attrs.cache = 1; } else { attrs.cache = 0; } const cap = /^data:([\w/.+-]+);base64,/.exec(attrs.file); if (cap) attrs.file = "base64://" + attrs.file.slice(cap[0].length); this.children.push({ type, data: attrs }); } else if (type === "file") { await this.flush(); await this.sendFile(attrs); } else if (type === "onebot:music") { await this.flush(); this.children.push({ type: "music", data: attrs }); } else if (type === "onebot:tts") { await this.flush(); this.children.push({ type: "tts", data: attrs }); } else if (type === "onebot:poke") { await this.flush(); this.children.push({ type: "poke", data: attrs }); } else if (type === "onebot:gift") { await this.flush(); this.children.push({ type: "gift", data: attrs }); } else if (type === "onebot:share") { await this.flush(); this.children.push({ type: "share", data: attrs }); } else if (type === "onebot:json") { await this.flush(); this.children.push({ type: "json", data: attrs }); } else if (type === "onebot:xml") { await this.flush(); this.children.push({ type: "xml", data: attrs }); } else if (type === "onebot:cardimage") { await this.flush(); this.children.push({ type: "cardimage", data: attrs }); } else if (type === "author") { Object.assign(this.stack[0].author, attrs); } else if (type === "figure" && !this.bot.parent) { await this.flush(); this.stack.unshift(new State("forward")); await this.render(children); await this.flush(); this.stack.shift(); await this.forward(); } else if (type === "figure") { await this.render(children); await this.flush(); } else if (type === "quote") { await this.flush(); this.children.push({ type: "reply", data: attrs }); } else if (type === "message") { await this.flush(); if ("forward" in attrs && !this.bot.parent) { this.stack.unshift(new State("forward")); await this.render(children); await this.flush(); this.stack.shift(); await this.forward(); } else if ("id" in attrs) { this.stack[0].author.messageId = attrs.id.toString(); } else { Object.assign(this.stack[0].author, (0, import_koishi5.pick)(attrs, ["userId", "username", "nickname", "time"])); await this.render(children); await this.flush(); } } else { await this.render(children); } } }; // src/bot/base.ts var BaseBot2 = class extends import_koishi6.Bot { static { __name(this, "BaseBot"); } static MessageEncoder = OneBotMessageEncoder; static inject = ["http"]; parent; internal; async createDirectChannel(userId) { return { id: `${PRIVATE_PFX}${userId}`, type: import_koishi6.Universal.Channel.Type.DIRECT }; } async getMessage(channelId, messageId) { const data = await this.internal.getMsg(messageId); return await adaptMessage(this, data); } async deleteMessage(channelId, messageId) { await this.internal.deleteMsg(messageId); } async getLogin() { const data = await this.internal.getLoginInfo(); this.user = decodeUser(data); return this.toJSON(); } async getUser(userId) { const data = await this.internal.getStrangerInfo(userId); return decodeUser(data); } async getFriendList() { const data = await this.internal.getFriendList(); return { data: data.map(decodeUser) }; } async handleFriendRequest(messageId, approve, comment) { await this.internal.setFriendAddRequest(messageId, approve, comment); } async handleGuildRequest(messageId, approve, comment) { await this.internal.setGroupAddRequest(messageId, "invite", approve, comment); } async handleGuildMemberRequest(messageId, approve, comment) { await this.internal.setGroupAddRequest(messageId, "add", approve, comment); } async deleteFriend(userId) { await this.internal.deleteFriend(userId); } async getMessageList(channelId, before, direction = "before") { if (direction !== "before") throw new Error("Unsupported direction."); let list; if (before) { const msg = await this.internal.getMsg(before); if (msg?.message_seq) { list = (await this.internal.getGroupMsgHistory(Number(channelId), msg.message_seq)).messages; } } else { list = (await this.internal.getGroupMsgHistory(Number(channelId))).messages; } return { data: await Promise.all(list.map((item) => adaptMessage(this, item))) }; } }; ((BaseBot3) => { BaseBot3.AdvancedConfig = import_koishi6.Schema.object({ splitMixedContent: import_koishi6.Schema.boolean().description("是否自动在混合内容间插入空格。").default(true) }).description("高级设置"); })(BaseBot2 || (BaseBot2 = {})); // src/bot/qqguild.ts var QQGuildBot = class extends BaseBot2 { static { __name(this, "QQGuildBot"); } hidden = true; constructor(ctx, config) { super(ctx, config, "qqguild"); this.platform = "qqguild"; this.selfId = config.profile.tiny_id; this.parent = config.parent; this.internal = config.parent.internal; this.user.name = config.profile.nickname; this.user.avatar = config.profile.avatar_url; this.parent.guildBot = this; } get status() { return this.parent.status; } set status(status) { this.parent.status = status; } async start() { await this.context.parallel("bot-connect", this); } async stop() { if (!this.parent) return; this.parent = void 0; await this.context.parallel("bot-disconnect", this); } async getChannel(channelId, guildId) { const { data } = await this.getChannelList(guildId); return data.find((channel) => channel.id === channelId); } async getChannelList(guildId) { const data = await this.internal.getGuildChannelList(guildId, false); return { data: (data || []).map(adaptChannel) }; } async getGuild(guildId) { const data = await this.internal.getGuildMetaByGuest(guildId); return adaptGuild(data); } async getGuildList() { const data = await this.internal.getGuildList(); return { data: data.map(adaptGuild) }; } async getGuildMember(guildId, userId) { const profile = await this.internal.getGuildMemberProfile(guildId, userId); return adaptQQGuildMemberProfile(profile); } async getGuildMemberList(guildId) { let nextToken; let list = []; while (true) { const data = await this.internal.getGuildMemberList(guildId, nextToken); if (!data.members?.length) break; list = list.concat(data.members.map(adaptQQGuildMemberInfo)); if (data.finished) break; nextToken = data.next_token; } return { data: list }; } }; // src/bot/cqcode.ts var import_koishi7 = require("koishi"); function CQCode(type, attrs) { if (type === "text") return attrs.content; let output = "[CQ:" + type; for (const key in attrs) { if (attrs[key]) output += `,${key}=${import_koishi7.h.escape(attrs[key], true)}`; } return output + "]"; } __name(CQCode, "CQCode"); ((CQCode2) => { function escape(source, inline = false) { const result = String(source).replace(/&/g, "&amp;").replace(/\[/g, "&#91;").replace(/\]/g, "&#93;"); return inline ? result.replace(/,/g, "&#44;").replace(/(\ud83c[\udf00-\udfff])|(\ud83d[\udc00-\ude4f\ude80-\udeff])|[\u2600-\u2B55]/g, " ") : result; } CQCode2.escape = escape; __name(escape, "escape"); function unescape(source) { return String(source).replace(/&#91;/g, "[").replace(/&#93;/g, "]").replace(/&#44;/g, ",").replace(/&amp;/g, "&"); } CQCode2.unescape = unescape; __name(unescape, "unescape"); const pattern = /\[CQ:(\w+)((,\w+=[^,\]]*)*)\]/; function from(source) { const capture = pattern.exec(source); if (!capture) return null; const [, type, attrs] = capture; const data = {}; attrs && attrs.slice(1).split(",").forEach((str) => { const index = str.indexOf("="); data[str.slice(0, index)] = unescape(str.slice(index + 1)); }); return { type, data, capture }; } CQCode2.from = from; __name(from, "from"); function parse(source) { if (typeof source !== "string") { return source.map(({ type, data }) => { if (type === "text") { return (0, import_koishi7.h)("text", { content: data.text }); } else { return (0, import_koishi7.h)(type, data); } }); } const elements = []; let result; while (result = from(source)) { const { type, data, capture } = result; if (capture.index) { elements.push((0, import_koishi7.h)("text", { content: unescape(source.slice(0, capture.index)) })); } elements.push((0, import_koishi7.h)(type, data)); source = source.slice(capture.index + capture[0].length); } if (source) elements.push((0, import_koishi7.h)("text", { content: unescape(source) })); return elements; } CQCode2.parse = parse; __name(parse, "parse"); })(CQCode || (CQCode = {})); // src/bot/index.ts var OneBotBot = class extends BaseBot2 { static { __name(this, "OneBotBot"); } guildBot; constructor(ctx, config) { super(ctx, config, "onebot"); this.selfId = config.selfId; this.internal = new Internal(this); this.user.avatar = `http://q.qlogo.cn/headimg_dl?dst_uin=${config.selfId}&spec=640`; if (config.protocol === "http") { ctx.plugin(HttpServer, this); } else if (config.protocol === "ws") { ctx.plugin(WsClient, this); } else if (config.protocol === "ws-reverse") { ctx.plugin(WsServer, this); } } async stop() { if (this.guildBot) { delete this.ctx.bots[this.guildBot.sid]; } await super.stop(); } async initialize() { await Promise.all([ this.getLogin(), this.setupGuildService().catch(import_koishi8.noop) ]).then(() => this.online(), (error) => this.offline(error)); } async setupGuildService() { const profile = await this.internal.getGuildServiceProfile(); if (!profile?.tiny_id || profile.tiny_id === "0") return; this.ctx.plugin(QQGuildBot, { profile, parent: this, advanced: this.config.advanced }); } async getChannel(channelId) { const data = await this.internal.getGroupInfo(channelId); return adaptChannel(data); } async getGuild(guildId) { const data = await this.internal.getGroupInfo(guildId); return adaptGuild(data); } async getGuildList() { const data = await this.internal.getGroupList(); return { data: data.map(adaptGuild) }; } async getChannelList(guildId) { return { data: [await this.getChannel(guildId)] }; } async getGuildMember(guildId, userId) { const data = await this.internal.getGroupMemberInfo(guildId, userId); return decodeGuildMember(data); } async getGuildMemberList(guildId) { const data = await this.internal.getGroupMemberList(guildId); return { data: data.map(decodeGuildMember) }; } async kickGuildMember(guildId, userId, permanent) { return this.internal.setGroupKick(guildId, userId, permanent); } async muteGuildMember(guildId, userId, duration) { return this.internal.setGroupBan(guildId, userId, Math.round(duration / 1e3)); } async muteChannel(channelId, guildId, enable) { return this.internal.setGroupWholeBan(channelId, enable); } async checkPermission(name, session) { if (name === "onebot.group.admin") { return session.author?.roles?.[0] === "admin"; } else if (name === "onebot.group.owner") { return session.author?.roles?.[0] === "owner"; } return super.checkPermission(name, session); } }; ((OneBotBot2) => { OneBotBot2.BaseConfig = import_koishi8.Schema.object({ selfId: import_koishi8.Schema.string().description("机器人的账号。").required(), token: import_koishi8.Schema.string().role("secret").description("发送信息时用于验证的字段,应与 OneBot 配置文件中的 `access_token` 保持一致。"), protocol: process.env.KOISHI_ENV === "browser" ? import_koishi8.Schema.const("ws").default("ws") : import_koishi8.Schema.union(["http", "ws", "ws-reverse"]).description("选择要使用的协议。").default("ws-reverse") }); OneBotBot2.Config = import_koishi8.Schema.intersect([ OneBotBot2.BaseConfig, import_koishi8.Schema.union([ HttpServer.Options, WsClient.Options, WsServer.Options ]), import_koishi8.Schema.object({ advanced: BaseBot2.AdvancedConfig }) ]); })(OneBotBot || (OneBotBot = {})); // src/index.ts var src_default = OneBotBot; // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { BaseBot, CQCode, HttpServer, OneBot, OneBotBot, OneBotMessageEncoder, PRIVATE_PFX, QQGuildBot, WsClient, WsServer, accept });