UNPKG

@koishijs/core

Version:

Core Features for Koishi

1,350 lines (1,338 loc) 94.7 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 __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default")); 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, { Adapter: () => import_core12.Adapter, App: () => Context, Argv: () => Argv, Bot: () => import_core12.Bot, Channel: () => Channel, Command: () => Command, Commander: () => Commander, Context: () => Context, Element: () => import_core12.Element, FilterService: () => FilterService, HTTP: () => import_core12.HTTP, I18n: () => I18n, Logger: () => import_core12.Logger, MessageEncoder: () => import_core12.MessageEncoder, Messenger: () => import_core12.Messenger, Next: () => Next, Permissions: () => Permissions, Processor: () => Processor, Quester: () => import_core12.Quester, Schema: () => import_core12.Schema, SchemaService: () => SchemaService, Service: () => Service3, SessionError: () => SessionError, SharedCache: () => SharedCache, Universal: () => import_core12.Universal, User: () => User, createMatch: () => createMatch, defineConfig: () => defineConfig, h: () => import_core12.h, resolveConfig: () => import_cordis.resolveConfig, segment: () => import_core12.segment, version: () => import_package.version, z: () => import_core12.z }); module.exports = __toCommonJS(src_exports); var import_package = require("../package.json"); __reExport(src_exports, require("@koishijs/utils"), module.exports); __reExport(src_exports, require("minato"), module.exports); // src/context.ts var import_cosmokit12 = require("cosmokit"); var import_core11 = require("@satorijs/core"); var satori = __toESM(require("@satorijs/core")); var cordis = __toESM(require("cordis")); var minato = __toESM(require("minato")); // src/filter.ts var import_cosmokit = require("cosmokit"); function property(ctx, key, ...values) { return ctx.intersect((session) => { return values.length ? values.includes(session[key]) : !!session[key]; }); } __name(property, "property"); var FilterService = class { constructor(ctx) { this.ctx = ctx; (0, import_cosmokit.defineProperty)(this, Context.current, ctx); ctx.filter = () => true; ctx.on("internal/runtime", (runtime) => { if (!runtime.uid) return; runtime.ctx.filter = (session) => { return runtime.children.some((p) => p.ctx.filter(session)); }; }); } static { __name(this, "FilterService"); } any() { return this.ctx.extend({ filter: /* @__PURE__ */ __name(() => true, "filter") }); } never() { return this.ctx.extend({ filter: /* @__PURE__ */ __name(() => false, "filter") }); } union(arg) { const filter2 = typeof arg === "function" ? arg : arg.filter; return this.ctx.extend({ filter: /* @__PURE__ */ __name((s) => this.ctx.filter(s) || filter2(s), "filter") }); } intersect(arg) { const filter2 = typeof arg === "function" ? arg : arg.filter; return this.ctx.extend({ filter: /* @__PURE__ */ __name((s) => this.ctx.filter(s) && filter2(s), "filter") }); } exclude(arg) { const filter2 = typeof arg === "function" ? arg : arg.filter; return this.ctx.extend({ filter: /* @__PURE__ */ __name((s) => this.ctx.filter(s) && !filter2(s), "filter") }); } user(...values) { return property(this.ctx, "userId", ...values); } self(...values) { return property(this.ctx, "selfId", ...values); } guild(...values) { return property(this.ctx, "guildId", ...values); } channel(...values) { return property(this.ctx, "channelId", ...values); } platform(...values) { return property(this.ctx, "platform", ...values); } private() { return this.ctx.intersect((session) => session.isDirect); } }; // src/command/index.ts var import_cosmokit6 = require("cosmokit"); var import_core5 = require("@satorijs/core"); // src/command/command.ts var import_cosmokit4 = require("cosmokit"); var import_utils3 = require("@koishijs/utils"); var import_core4 = require("@satorijs/core"); // src/command/parser.ts var import_cosmokit2 = require("cosmokit"); var import_utils = require("@koishijs/utils"); var import_core = require("@satorijs/core"); var leftQuotes = `"'“‘`; var rightQuotes = `"'”’`; var Argv; ((Argv2) => { const bracs = {}; function interpolate(initiator, terminator, parse2) { bracs[initiator] = { terminator, parse: parse2 }; } Argv2.interpolate = interpolate; __name(interpolate, "interpolate"); interpolate("$(", ")"); let whitespace; ((whitespace2) => { whitespace2.unescape = /* @__PURE__ */ __name((source) => source.replace(/@__KOISHI_SPACE__@/g, " ").replace(/@__KOISHI_NEWLINE__@/g, "\n").replace(/@__KOISHI_RETURN__@/g, "\r").replace(/@__KOISHI_TAB__@/g, " "), "unescape"); whitespace2.escape = /* @__PURE__ */ __name((source) => source.replace(/ /g, "@__KOISHI_SPACE__@").replace(/\n/g, "@__KOISHI_NEWLINE__@").replace(/\r/g, "@__KOISHI_RETURN__@").replace(/\t/g, "@__KOISHI_TAB__@"), "escape"); })(whitespace = Argv2.whitespace || (Argv2.whitespace = {})); class Tokenizer { static { __name(this, "Tokenizer"); } bracs; constructor() { this.bracs = Object.create(bracs); } interpolate(initiator, terminator, parse2) { this.bracs[initiator] = { terminator, parse: parse2 }; } parseToken(source, stopReg = "$") { const parent = { inters: [] }; const index = leftQuotes.indexOf(source[0]); const quote = rightQuotes[index]; let content = ""; if (quote) { source = source.slice(1); stopReg = `${quote}(?=${stopReg})|$`; } stopReg += `|${Object.keys({ ...this.bracs, ...bracs }).map(import_utils.escapeRegExp).join("|")}`; const regExp = new RegExp(stopReg); while (true) { const capture = regExp.exec(source); content += whitespace.unescape(source.slice(0, capture.index)); if (capture[0] in this.bracs) { source = source.slice(capture.index + capture[0].length).trimStart(); const { parse: parse2, terminator } = this.bracs[capture[0]]; const argv = parse2?.(source) || this.parse(source, terminator); source = argv.rest; parent.inters.push({ ...argv, pos: content.length, initiator: capture[0] }); } else { const quoted = capture[0] === quote; const rest = source.slice(capture.index + +quoted); parent.rest = rest.trimStart(); parent.quoted = quoted; parent.terminator = capture[0]; if (quoted) { parent.terminator += rest.slice(0, -parent.rest.length); } else if (quote) { content = leftQuotes[index] + content; parent.inters.forEach((inter) => inter.pos += 1); } parent.content = content; if (quote === "'") Argv2.revert(parent); return parent; } } } parse(source, terminator = "") { const tokens = []; source = import_core.h.parse(source).map((el) => { return el.type === "text" ? el.toString() : whitespace.escape(el.toString()); }).join(""); let rest = source, term = ""; const stopReg = `\\s+|[${(0, import_utils.escapeRegExp)(terminator)}]|$`; while (rest && !(terminator && rest.startsWith(terminator))) { const token = this.parseToken(rest, stopReg); tokens.push(token); rest = token.rest; term = token.terminator; delete token.rest; } if (rest.startsWith(terminator)) rest = rest.slice(1); source = source.slice(0, -(rest + term).length); rest = whitespace.unescape(rest); source = whitespace.unescape(source); return { tokens, rest, source }; } stringify(argv) { const output = argv.tokens.reduce((prev, token) => { if (token.quoted) prev += leftQuotes[rightQuotes.indexOf(token.terminator[0])] || ""; return prev + token.content + token.terminator; }, ""); if (argv.rest && !rightQuotes.includes(output[output.length - 1]) || argv.initiator) { return output.slice(0, -1); } return output; } } Argv2.Tokenizer = Tokenizer; const defaultTokenizer = new Tokenizer(); function parse(source, terminator = "") { return defaultTokenizer.parse(source, terminator); } Argv2.parse = parse; __name(parse, "parse"); function stringify(argv) { return defaultTokenizer.stringify(argv); } Argv2.stringify = stringify; __name(stringify, "stringify"); function revert(token) { while (token.inters.length) { const { pos, source, initiator } = token.inters.pop(); token.content = token.content.slice(0, pos) + initiator + source + bracs[initiator].terminator + token.content.slice(pos); } } Argv2.revert = revert; __name(revert, "revert"); const SYNTAX = /(?:-[\w\x80-\uffff-]*|[^,\s\w\x80-\uffff]+)/.source; const BRACKET = /((?:\s*\[[^\]]+?\]|\s*<[^>]+?>)*)/.source; const OPTION_REGEXP = new RegExp(`^(${SYNTAX}(?:,\\s*${SYNTAX})*(?=\\s|$))?${BRACKET}(.*)$`); class CommandBase { constructor(name, declaration, ctx, config) { this.name = name; this.ctx = ctx; this.config = config; if (!name) throw new Error("expect a command name"); const declList = this._arguments = ctx.$commander.parseDecl(declaration); this.declaration = declList.stripped; for (const decl of declList) { this._disposables.push(this.ctx.i18n.define("", `commands.${this.name}.arguments.${decl.name}`, decl.name)); } } static { __name(this, "CommandBase"); } declaration; _arguments; _options = {}; _disposables = []; _namedOptions = {}; _symbolicOptions = {}; _createOption(name, def, config) { const cap = OPTION_REGEXP.exec(def); const param = (0, import_cosmokit2.paramCase)(name); let syntax = cap[1] || "--" + param; const bracket = cap[2] || ""; const desc = cap[3].trim(); const aliases = config.aliases ?? []; const symbols = config.symbols ?? []; for (let param2 of syntax.trim().split(",")) { param2 = param2.trimStart(); const name2 = param2.replace(/^-+/, ""); if (!name2 || !param2.startsWith("-")) { symbols.push(import_core.h.escape(param2)); } else { aliases.push(name2); } } if (!("value" in config) && !aliases.includes(param)) { syntax += ", --" + param; } const declList = this.ctx.$commander.parseDecl(bracket.trimStart()); if (declList.stripped) syntax += " " + declList.stripped; const option = this._options[name] ||= { ...declList[0], ...config, name, values: {}, valuesSyntax: {}, variants: {}, syntax }; let path2 = `commands.${this.name}.options.${name}`; const fallbackType = typeof option.fallback; if ("value" in config) { path2 += "." + config.value; option.variants[config.value] = { ...config, syntax }; option.valuesSyntax[config.value] = syntax; aliases.forEach((name2) => option.values[name2] = config.value); } else if (!bracket.trim()) { option.type = "boolean"; } else if (!option.type && (fallbackType === "string" || fallbackType === "number")) { option.type = fallbackType; } this._disposables.push(this.ctx.i18n.define("", path2, desc)); this._assignOption(option, aliases, this._namedOptions); this._assignOption(option, symbols, this._symbolicOptions); if (!this._namedOptions[param]) { this._namedOptions[param] = option; } } _assignOption(option, names, optionMap) { for (const name of names) { if (name in optionMap) { throw new Error(`duplicate option name "${name}" for command "${this.name}"`); } optionMap[name] = option; } } removeOption(name) { if (!this._options[name]) return false; const option = this._options[name]; delete this._options[name]; for (const key in this._namedOptions) { if (this._namedOptions[key] === option) { delete this._namedOptions[key]; } } for (const key in this._symbolicOptions) { if (this._symbolicOptions[key] === option) { delete this._symbolicOptions[key]; } } return true; } parse(argv, terminator) { if (typeof argv === "string") { argv = Argv2.parse(argv, terminator); } const args = [...argv.args || []]; const options = { ...argv.options }; if (!argv.source && argv.tokens) { argv.source = this.name + " " + Argv2.stringify(argv); } let lastArgDecl; while (!argv.error && argv.tokens?.length) { const token = argv.tokens[0]; let { content, quoted } = token; const argDecl = this._arguments[args.length] || lastArgDecl || {}; if (args.length === this._arguments.length - 1 && argDecl.variadic) { lastArgDecl = argDecl; } if (content[0] !== "-" && this.ctx.$commander.resolveDomain(argDecl.type).greedy) { args.push(this.ctx.$commander.parseValue(Argv2.stringify(argv), "argument", argv, argDecl)); break; } argv.tokens.shift(); let option; let names; let param; if (!quoted && (option = this._symbolicOptions[content])) { names = [(0, import_cosmokit2.paramCase)(option.name)]; } else { if (content[0] !== "-" || quoted || +content * 0 === 0 && this.ctx.$commander.resolveDomain(argDecl.type).numeric) { args.push(this.ctx.$commander.parseValue(content, "argument", argv, argDecl)); continue; } let i = 0; for (; i < content.length; ++i) { if (content.charCodeAt(i) !== 45) break; } let j = i + 1; for (; j < content.length; j++) { if (content.charCodeAt(j) === 61) break; } const name = content.slice(i, j); if (this.config.strictOptions && !this._namedOptions[name]) { if (this.ctx.$commander.resolveDomain(argDecl.type).greedy) { argv.tokens.unshift(token); args.push(this.ctx.$commander.parseValue(Argv2.stringify(argv), "argument", argv, argDecl)); break; } args.push(this.ctx.$commander.parseValue(content, "argument", argv, argDecl)); continue; } if (i > 1 && name.startsWith("no-") && !this._namedOptions[name]) { options[(0, import_cosmokit2.camelCase)(name.slice(3))] = false; continue; } names = i > 1 ? [name] : name; param = content.slice(++j); option = this._namedOptions[names[names.length - 1]]; } quoted = false; if (!param) { const { type, values } = option || {}; if (this.ctx.$commander.resolveDomain(type).greedy) { param = Argv2.stringify(argv); quoted = true; argv.tokens = []; } else { const isValued = names[names.length - 1] in (values || {}) || type === "boolean"; if (!isValued && argv.tokens.length && (type || argv.tokens[0]?.content !== "-")) { const token2 = argv.tokens.shift(); param = token2.content; quoted = token2.quoted; } } } for (let j = 0; j < names.length; j++) { const name = names[j]; const optDecl = this._namedOptions[name]; const key = optDecl ? optDecl.name : (0, import_cosmokit2.camelCase)(name); if (optDecl && name in optDecl.values) { options[key] = optDecl.values[name]; } else { const source = j + 1 < names.length ? "" : param; options[key] = this.ctx.$commander.parseValue(source, "option", argv, optDecl); } if (argv.error) break; } } for (const { name, fallback: fallback2 } of Object.values(this._options)) { if (fallback2 !== void 0 && !(name in options)) { options[name] = fallback2; } } delete argv.tokens; return { ...argv, options, args, error: argv.error || "", command: this }; } stringifyArg(value) { value = "" + value; return value.includes(" ") ? `"${value}"` : value; } stringify(args, options) { let output = this.name; for (const key in options) { const value = options[key]; if (value === true) { output += ` --${key}`; } else if (value === false) { output += ` --no-${key}`; } else { output += ` --${key} ${this.stringifyArg(value)}`; } } for (const arg of args) { output += " " + this.stringifyArg(arg); } return output; } } Argv2.CommandBase = CommandBase; })(Argv || (Argv = {})); // src/middleware.ts var import_utils2 = require("@koishijs/utils"); var import_cosmokit3 = require("cosmokit"); var import_core3 = require("@satorijs/core"); // src/database.ts var import_core2 = require("@satorijs/core"); var User; ((User2) => { let Flag; ((Flag2) => { Flag2[Flag2["ignore"] = 1] = "ignore"; })(Flag = User2.Flag || (User2.Flag = {})); })(User || (User = {})); var Channel; ((Channel2) => { let Flag; ((Flag2) => { Flag2[Flag2["ignore"] = 1] = "ignore"; Flag2[Flag2["silent"] = 4] = "silent"; })(Flag = Channel2.Flag || (Channel2.Flag = {})); })(Channel || (Channel = {})); var KoishiDatabase = class { constructor(ctx) { this.ctx = ctx; ctx.mixin(this, { getUser: "database.getUser", setUser: "database.setUser", createUser: "database.createUser", getChannel: "database.getChannel", getAssignedChannels: "database.getAssignedChannels", setChannel: "database.setChannel", createChannel: "database.createChannel", broadcast: "database.broadcast" }); ctx.mixin("database", ["broadcast"]); ctx.model.extend("user", { id: "unsigned(8)", name: { type: "string", length: 255 }, flag: "unsigned(8)", authority: "unsigned(4)", locales: "list(255)", permissions: "list", createdAt: "timestamp" }, { autoInc: true }); ctx.model.extend("binding", { aid: "unsigned(8)", bid: "unsigned(8)", pid: "string(255)", platform: "string(255)" }, { primary: ["pid", "platform"] }); ctx.model.extend("channel", { id: "string(255)", platform: "string(255)", flag: "unsigned(8)", assignee: "string(255)", guildId: "string(255)", locales: "list(255)", permissions: "list", createdAt: "timestamp" }, { primary: ["id", "platform"] }); ctx.on("login-added", ({ platform }) => { if (platform in ctx.model.tables.user.fields) return; ctx.model.migrate("user", { [platform]: "string(255)" }, async (db) => { const users = await db.get("user", { [platform]: { $exists: true } }, ["id", platform]); await db.upsert("binding", users.filter((u) => u[platform]).map((user) => ({ aid: user.id, bid: user.id, pid: user[platform], platform }))); }); }); } static { __name(this, "KoishiDatabase"); } async getUser(platform, pid, modifier) { const [binding] = await this.get("binding", { platform, pid }, ["aid"]); if (!binding) return; const [user] = await this.get("user", { id: binding.aid }, modifier); return user; } async setUser(platform, pid, data) { const [binding] = await this.get("binding", { platform, pid }, ["aid"]); if (!binding) throw new Error("user not found"); return this.set("user", binding.aid, data); } async createUser(platform, pid, data) { const user = await this.create("user", data); await this.create("binding", { aid: user.id, bid: user.id, pid, platform }); return user; } async getChannel(platform, id, modifier) { const data = await this.get("channel", { platform, id }, modifier); if (Array.isArray(id)) return data; if (data[0]) Object.assign(data[0], { platform, id }); return data[0]; } getSelfIds(platforms) { const selfIdMap = /* @__PURE__ */ Object.create(null); for (const bot of this.ctx.bots) { if (platforms && !platforms.includes(bot.platform)) continue; (selfIdMap[bot.platform] ||= []).push(bot.selfId); } return selfIdMap; } async getAssignedChannels(fields, selfIdMap = this.getSelfIds()) { return this.get("channel", { $or: Object.entries(selfIdMap).map(([platform, assignee]) => ({ platform, assignee })) }, fields); } setChannel(platform, id, data) { return this.set("channel", { platform, id }, data); } createChannel(platform, id, data) { return this.create("channel", { platform, id, ...data }); } async broadcast(...args) { let channels, platforms; if (Array.isArray(args[0])) { channels = args.shift(); platforms = channels.map((c) => c.split(":")[0]); } const [content, forced] = args; if (!content) return []; const selfIdMap = this.getSelfIds(platforms); const data = await this.getAssignedChannels(["id", "assignee", "flag", "platform", "guildId", "locales"], selfIdMap); const assignMap = {}; for (const channel of data) { const { platform, id, assignee, flag } = channel; if (channels) { const index = channels?.indexOf(`${platform}:${id}`); if (index < 0) continue; channels.splice(index, 1); } if (!forced && flag & 4 /* silent */) continue; ((assignMap[platform] ||= {})[assignee] ||= []).push(channel); } if (channels?.length) { this.ctx.logger("app").warn("broadcast", "channel not found: ", channels.join(", ")); } return (await Promise.all(this.ctx.bots.map((bot) => { const targets = assignMap[bot.platform]?.[bot.selfId]; if (!targets) return Promise.resolve([]); const sessions = targets.map(({ id, guildId, locales }) => { const session = bot.session({ type: "message", channel: { id, type: import_core2.Universal.Channel.Type.TEXT }, guild: { id: guildId } }); session.locales = locales; return session; }); return bot.broadcast(sessions, content); }))).flat(1); } }; var database_default = KoishiDatabase; // src/middleware.ts var SessionError = class extends Error { constructor(path2, param) { super((0, import_utils2.makeArray)(path2)[0]); this.path = path2; this.param = param; } static { __name(this, "SessionError"); } }; var Next; ((Next2) => { Next2.MAX_DEPTH = 64; async function compose(callback, next) { return typeof callback === "function" ? callback(next) : callback; } Next2.compose = compose; __name(compose, "compose"); })(Next || (Next = {})); var Processor = class { constructor(ctx) { this.ctx = ctx; (0, import_cosmokit3.defineProperty)(this, Context.current, ctx); this.middleware(this.attach.bind(this), true); ctx.on("message", this._handleMessage.bind(this)); ctx.before("attach-user", (session, fields) => { session.collect("user", session.argv, fields); }); ctx.before("attach-channel", (session, fields) => { session.collect("channel", session.argv, fields); }); ctx.component("execute", async (attrs, children, session) => { return session.execute(children.join(""), true); }, { session: true }); ctx.component("prompt", async (attrs, children, session) => { await session.send(children); return session.prompt(); }, { session: true }); ctx.component("i18n", async (attrs, children, session) => { return session.i18n(attrs.path, children); }, { session: true }); ctx.component("random", async (attrs, children) => { return import_utils2.Random.pick(children); }); ctx.component("plural", async (attrs, children) => { const path2 = attrs.count in children ? attrs.count : children.length - 1; return children[path2]; }); const units = ["day", "hour", "minute", "second"]; ctx.component("i18n:time", (attrs, children, session) => { let ms = +attrs.value; for (let index = 0; index < 3; index++) { const major = import_cosmokit3.Time[units[index]]; const minor = import_cosmokit3.Time[units[index + 1]]; if (ms >= major - minor / 2) { ms += minor / 2; let result = Math.floor(ms / major) + " " + session.text("general." + units[index]); if (ms % major > minor) { result += ` ${Math.floor(ms % major / minor)} ` + session.text("general." + units[index + 1]); } return result; } } return Math.round(ms / import_cosmokit3.Time.second) + " " + session.text("general.second"); }, { session: true }); ctx.before("attach", (session) => { for (const matcher of this._matchers) { this._executeMatcher(session, matcher); if (session.response) return; } }); } static { __name(this, "Processor"); } _hooks = []; _sessions = /* @__PURE__ */ Object.create(null); _userCache = new SharedCache(); _channelCache = new SharedCache(); _matchers = /* @__PURE__ */ new Set(); middleware(middleware, options) { if (typeof options !== "object") { options = { prepend: options }; } return this.ctx.lifecycle.register("middleware", this._hooks, middleware, options); } match(pattern, response, options) { const matcher = { ...options, context: this.ctx, pattern, response }; this._matchers.add(matcher); return this.ctx.collect("shortcut", () => { return this._matchers.delete(matcher); }); } _executeMatcher(session, matcher) { const { stripped, quote } = session; const { appel, context, i18n, regex, fuzzy, pattern, response } = matcher; if ((appel || stripped.hasAt) && !stripped.appel) return; if (!context.filter(session)) return; let content = stripped.content; if (quote?.content) content += " " + quote.content; let params = null; const match = /* @__PURE__ */ __name((pattern2) => { if (!pattern2) return; if (typeof pattern2 === "string") { if (!fuzzy && content !== pattern2 || !content.startsWith(pattern2)) return; params = [content, content.slice(pattern2.length)]; if (fuzzy && !stripped.appel && params[1].match(/^\S/)) { params = null; } } else { params = pattern2.exec(content); } }, "match"); if (!i18n) { match(pattern); } else { for (const locale of this.ctx.i18n.fallback([])) { const store = this.ctx.i18n._data[locale]; let value = store?.[pattern]; if (!value) continue; if (regex) { const rest = fuzzy ? `(?:${stripped.appel ? "" : "\\s+"}([\\s\\S]*))?` : ""; value = new RegExp(`^(?:${value})${rest}$`); } match(value); if (!params) continue; session.locales = [locale]; break; } } if (!params) return; session.response = async () => { const output = await session.resolve(response, params); return import_core3.h.normalize(output, params.map((source) => source ? import_core3.h.parse(source) : "")); }; } async attach(session, next) { this.ctx.emit(session, "before-attach", session); if (this.ctx.database) { if (!session.isDirect) { const channelFields = /* @__PURE__ */ new Set(["flag", "assignee", "guildId", "permissions", "locales"]); this.ctx.emit("before-attach-channel", session, channelFields); const channel = await session.observeChannel(channelFields); channel.guildId = session.guildId; if (await this.ctx.serial(session, "attach-channel", session)) return; if (channel.flag & Channel.Flag.ignore) return; if (channel.assignee !== session.selfId && !session.stripped.atSelf) return; } const userFields = /* @__PURE__ */ new Set(["id", "flag", "authority", "permissions", "locales"]); this.ctx.emit("before-attach-user", session, userFields); const user = await session.observeUser(userFields); if (await this.ctx.serial(session, "attach-user", session)) return; if (user.flag & User.Flag.ignore) return; } this.ctx.emit(session, "attach", session); if (session.response) return session.response(); return next(); } async _handleMessage(session) { if (session.selfId === session.userId) return; this._sessions[session.id] = session; const queue = this.ctx.lifecycle.filterHooks(this._hooks, session).map(({ callback }) => callback.bind(null, session)); let index = 0; const next = /* @__PURE__ */ __name(async (callback) => { try { if (!this._sessions[session.id]) { throw new Error("isolated next function detected"); } if (callback !== void 0) { queue.push((next2) => Next.compose(callback, next2)); if (queue.length > Next.MAX_DEPTH) { throw new Error(`middleware stack exceeded ${Next.MAX_DEPTH}`); } } return await queue[index++]?.(next); } catch (error) { if (error instanceof SessionError) { return session.text(error.path, error.param); } const stack = (0, import_utils2.coerce)(error); this.ctx.logger("session").warn(`${session.content} ${stack}`); } }, "next"); try { const result = await next(); if (result) await session.send(result); } finally { delete this._sessions[session.id]; this._userCache.delete(session.id); this._channelCache.delete(session.id); await session.user?.$update(); await session.channel?.$update(); await session.guild?.$update(); this.ctx.emit(session, "middleware", session); } } }; var SharedCache = class { static { __name(this, "SharedCache"); } #keyMap = /* @__PURE__ */ new Map(); get(ref, key) { const entry = this.#keyMap.get(key); if (!entry) return; entry.refs.add(ref); return entry.value; } set(ref, key, value) { let entry = this.#keyMap.get(key); if (entry) { entry.value = value; } else { entry = { value, key, refs: /* @__PURE__ */ new Set() }; this.#keyMap.set(key, entry); } entry.refs.add(ref); } delete(ref) { for (const key of [...this.#keyMap.keys()]) { const { refs } = this.#keyMap.get(key); refs.delete(ref); if (!refs.size) { this.#keyMap.delete(key); } } } }; // src/command/command.ts var logger = new import_core4.Logger("command"); var Command = class _Command extends Argv.CommandBase { static { __name(this, "Command"); } children = []; _parent = null; _aliases = /* @__PURE__ */ Object.create(null); _examples = []; _usage; _userFields = [["locales"]]; _channelFields = [["locales"]]; _actions = []; _checkers = [async (argv) => { return this.ctx.serial(argv.session, "command/before-execute", argv); }]; constructor(name, decl, ctx, config) { super(name, decl, ctx, { showWarning: true, handleError: true, slash: true, ...config }); this.config.permissions ??= [`authority:${config?.authority ?? 1}`]; this._registerAlias(name); ctx.$commander._commandList.push(this); } get caller() { return this[Context.current] || this.ctx; } get displayName() { return Object.keys(this._aliases)[0]; } set displayName(name) { this._registerAlias(name, true); } get parent() { return this._parent; } set parent(parent) { if (this._parent === parent) return; if (this._parent) { (0, import_cosmokit4.remove)(this._parent.children, this); } this._parent = parent; if (parent) { parent.children.push(this); } } static normalize(name) { return name.toLowerCase().replace(/_/g, "-"); } _registerAlias(name, prepend = false, options = {}) { name = _Command.normalize(name); if (name.startsWith(".")) name = this.parent.name + name; const previous = this.ctx.$commander.get(name); if (previous && previous !== this) { throw new Error(`duplicate command names: "${name}"`); } const existing = this._aliases[name]; if (existing) { if (prepend) { this._aliases = { [name]: existing, ...this._aliases }; } } else if (prepend) { this._aliases = { [name]: options, ...this._aliases }; } else { this._aliases[name] = options; } } [Symbol.for("nodejs.util.inspect.custom")]() { return `Command <${this.name}>`; } userFields(fields) { this._userFields.push(fields); return this; } channelFields(fields) { this._channelFields.push(fields); return this; } alias(...args) { if (typeof args[1] === "object") { this._registerAlias(args[0], false, args[1]); } else { for (const name of args) { this._registerAlias(name); } } this.caller.emit("command-updated", this); return this; } _escape(source) { if (typeof source !== "string") return source; return source.replace(/\$\$/g, "@@__PLACEHOLDER__@@").replace(/\$\d/g, (s) => `{${s[1]}}`).replace(/@@__PLACEHOLDER__@@/g, "$"); } shortcut(pattern, config = {}) { let content = this.displayName; for (const key in config.options || {}) { content += ` --${(0, import_cosmokit4.camelize)(key)}`; const value = config.options[key]; if (value !== true) { content += " " + this._escape(value); } } for (const arg of config.args || []) { content += " " + this._escape(arg); } if (config.fuzzy) content += " {1}"; const regex = config.i18n; if (typeof pattern === "string") { if (config.i18n) { pattern = `commands.${this.name}.shortcuts.${pattern}`; } else { config.i18n = true; const key = `commands.${this.name}.shortcuts._${Math.random().toString(36).slice(2)}`; this.ctx.i18n.define("", key, pattern); pattern = key; } } const dispose = this.ctx.match(pattern, `<execute>${content}</execute>`, { appel: config.prefix, fuzzy: config.fuzzy, i18n: config.i18n, regex }); this._disposables.push(dispose); return this; } subcommand(def, ...args) { def = this.name + (def.charCodeAt(0) === 46 ? "" : "/") + def; const desc = typeof args[0] === "string" ? args.shift() : ""; const config = args[0] || {}; return this.ctx.command(def, desc, config); } usage(text) { this._usage = text; return this; } example(example) { this._examples.push(example); return this; } option(name, ...args) { let desc = ""; if (typeof args[0] === "string") { desc = args.shift(); } const config = { ...args[0] }; config.permissions ??= [`authority:${config.authority ?? 0}`]; this._createOption(name, desc, config); this.caller.emit("command-updated", this); this.caller.collect("option", () => this.removeOption(name)); return this; } match(session) { return this.ctx.filter(session); } check(callback, append = false) { return this.before(callback, append); } before(callback, append = false) { if (append) { this._checkers.push(callback); } else { this._checkers.unshift(callback); } this.caller.scope.disposables?.push(() => (0, import_cosmokit4.remove)(this._checkers, callback)); return this; } action(callback, prepend = false) { if (prepend) { this._actions.unshift(callback); } else { this._actions.push(callback); } this.caller.scope.disposables?.push(() => (0, import_cosmokit4.remove)(this._actions, callback)); return this; } /** @deprecated */ use(callback, ...args) { return callback(this, ...args); } async execute(argv, fallback2 = Next.compose) { argv.command ??= this; argv.args ??= []; argv.options ??= {}; const { args, options, error } = argv; if (error) return error; if (logger.level >= 3) logger.debug(argv.source ||= this.stringify(args, options)); for (const validator of this._checkers) { const result = await validator.call(this, argv, ...args); if (!(0, import_cosmokit4.isNullable)(result)) return result; } if (!this._actions.length) return ""; let index = 0; const queue = this._actions.map((action) => async () => { return await action.call(this, argv, ...args); }); queue.push(fallback2); const length = queue.length; argv.next = async (callback) => { if (callback !== void 0) { queue.push((next) => Next.compose(callback, next)); if (queue.length > Next.MAX_DEPTH) { throw new Error(`middleware stack exceeded ${Next.MAX_DEPTH}`); } } return queue[index++]?.(argv.next); }; try { const result = await argv.next(); if (!(0, import_cosmokit4.isNullable)(result)) return result; } catch (error2) { if (index === length) throw error2; if (error2 instanceof SessionError) { return argv.session.text(error2.path, error2.param); } const stack = (0, import_utils3.coerce)(error2); logger.warn(`${argv.source ||= this.stringify(args, options)} ${stack}`); this.ctx.emit(argv.session, "command-error", argv, error2); if (typeof this.config.handleError === "function") { const result = await this.config.handleError(error2, argv); if (!(0, import_cosmokit4.isNullable)(result)) return result; } else if (this.config.handleError) { return argv.session.text("internal.error-encountered"); } } return ""; } dispose() { this._disposables.splice(0).forEach((dispose) => dispose()); this.ctx.emit("command-removed", this); for (const cmd of this.children.slice()) { cmd.dispose(); } (0, import_cosmokit4.remove)(this.ctx.$commander._commandList, this); this.parent = null; } toJSON() { return { name: this.name, description: this.ctx.i18n.get(`commands.${this.name}.description`), arguments: this._arguments.map((arg) => ({ name: arg.name, type: toStringType(arg.type), description: this.ctx.i18n.get(`commands.${this.name}.arguments.${arg.name}`), required: arg.required })), options: Object.entries(this._options).map(([name, option]) => ({ name, type: toStringType(option.type), description: this.ctx.i18n.get(`commands.${this.name}.options.${name}`), required: option.required })), children: this.children.filter((child) => child.name.includes(".")).map((child) => child.toJSON()) }; } }; function toStringType(type) { return typeof type === "string" ? type : "string"; } __name(toStringType, "toStringType"); ((Command2) => { Command2.Config = import_core4.Schema.object({ permissions: import_core4.Schema.array(String).role("perms").default(["authority:1"]).description("权限继承。"), dependencies: import_core4.Schema.array(String).role("perms").description("权限依赖。"), slash: import_core4.Schema.boolean().description("启用斜线指令功能。").default(true), captureQuote: import_core4.Schema.boolean().description("是否捕获引用文本。").default(true).hidden(), checkUnknown: import_core4.Schema.boolean().description("是否检查未知选项。").default(false).hidden(), checkArgCount: import_core4.Schema.boolean().description("是否检查参数数量。").default(false).hidden(), showWarning: import_core4.Schema.boolean().description("是否显示警告。").default(true).hidden(), handleError: import_core4.Schema.union([import_core4.Schema.boolean(), import_core4.Schema.function()]).description("是否处理错误。").default(true).hidden() }); })(Command || (Command = {})); // src/command/validate.ts var import_cosmokit5 = require("cosmokit"); function validate(ctx) { ctx.permissions.define("command:(name)", { depends: /* @__PURE__ */ __name(({ name }) => { const command = ctx.$commander.get(name); if (!command) return; const depends = [...command.config.dependencies ?? []]; if (command.parent) depends.push(`command:${command.parent.name}`); return depends; }, "depends"), inherits: /* @__PURE__ */ __name(({ name }) => { return ctx.$commander.get(name)?.config.permissions; }, "inherits"), list: /* @__PURE__ */ __name(() => { return ctx.$commander._commandList.map((command) => `command:${command.name}`); }, "list") }); ctx.permissions.define("command:(name):option:(name2)", { depends: /* @__PURE__ */ __name(({ name, name2 }) => { return ctx.$commander.get(name)?._options[name2]?.dependencies; }, "depends"), inherits: /* @__PURE__ */ __name(({ name, name2 }) => { return ctx.$commander.get(name)?._options[name2]?.permissions; }, "inherits"), list: /* @__PURE__ */ __name(() => { return ctx.$commander._commandList.flatMap((command) => { return Object.keys(command._options).map((name) => `command:${command.name}:option:${name}`); }); }, "list") }); ctx.before("command/execute", async (argv) => { const { session, options, command } = argv; if (!session.user) return; function sendHint(message, ...param) { return command.config.showWarning ? session.text(message, param) : ""; } __name(sendHint, "sendHint"); const permissions = [`command:${command.name}`]; for (const option of Object.values(command._options)) { if (option.name in options) { permissions.push(`command:${command.name}:option:${option.name}`); } } if (!await ctx.permissions.test(permissions, session)) { return sendHint("internal.low-authority"); } }, true); ctx.before("command/execute", async (argv) => { const { args, options, command, session } = argv; function sendHint(message, ...param) { return command.config.showWarning ? session.text(message, param) : ""; } __name(sendHint, "sendHint"); if (command.config.checkArgCount) { let index = args.length; while (command._arguments[index]?.required) { const decl = command._arguments[index]; await session.send(session.text("internal.prompt-argument", [ session.text(`commands.${command.name}.arguments.${decl.name}`) ])); const source = await session.prompt(); if ((0, import_cosmokit5.isNullable)(source)) { return sendHint("internal.insufficient-arguments", decl.name); } args.push(ctx.$commander.parseValue(source, "argument", argv, decl)); index++; } const finalArg = command._arguments[command._arguments.length - 1] || {}; if (args.length > command._arguments.length && !finalArg.variadic) { return sendHint("internal.redunant-arguments"); } } if (command.config.checkUnknown) { const unknown = Object.keys(options).filter((key) => !command._options[key]); if (unknown.length) { return sendHint("internal.unknown-option", unknown.join(", ")); } } }, true); } __name(validate, "validate"); // src/command/index.ts var isArray = Array.isArray; var BRACKET_REGEXP = /<[^>]+>|\[[^\]]+\]/g; var Commander = class { constructor(ctx, config = {}) { this.ctx = ctx; this.config = config; (0, import_cosmokit6.defineProperty)(this, Context.current, ctx); ctx.plugin(validate); ctx.before("parse", (content, session) => { const { isDirect, stripped: { prefix, appel } } = session; if (!isDirect && typeof prefix !== "string" && !appel) return; return Argv.parse(content); }); ctx.on("interaction/command", (session) => { if (session.event?.argv) { const { name, options, arguments: args } = session.event.argv; session.execute({ name, args, options }); } else { session.stripped.hasAt = true; session.stripped.appel = true; session.stripped.atSelf = true; session.stripped.prefix = ""; (0, import_cosmokit6.defineProperty)(session, "argv", ctx.bail("before-parse", session.content, session)); if (!session.argv) { ctx.logger("command").warn("failed to parse interaction command:", session.content); return; } session.argv.root = true; session.argv.session = session; session.execute(session.argv); } }); ctx.before("attach", (session) => { const { hasAt, appel } = session.stripped; if (!appel && hasAt) return; let content = session.stripped.content; for (const prefix of this._resolvePrefixes(session)) { if (!content.startsWith(prefix)) continue; session.stripped.prefix = prefix; content = content.slice(prefix.length); break; } (0, import_cosmokit6.defineProperty)(session, "argv", ctx.bail("before-parse", content, session)); if (!session.argv) return; session.argv.root = true; session.argv.session = session; }); ctx.middleware((session, next) => { if (!this.resolveCommand(session.argv)) return next(); return session.execute(session.argv, next); }); ctx.middleware((session, next) => { const { argv, quote, isDirect, stripped: { prefix, appel } } = session; if (argv?.command || !isDirect && !prefix && !appel) return next(); const content = session.stripped.content.slice((prefix ?? "").length); const actual = content.split(/\s/, 1)[0].toLowerCase(); if (!actual) return next(); return next(async (next2) => { const cache = /* @__PURE__ */ new Map(); const name = await session.suggest({ actual, expect: this.available(session), suffix: session.text("internal.suggest-command"), filter: /* @__PURE__ */ __name((name2) => { const command = this.resolve(name2, session); if (!command) return false; return ctx.permissions.test(`command:${command.name}`, session, cache); }, "filter") }); if (!name) return next2(); const message = name + content.slice(actual.length) + (quote?.content ? " " + quote.content : ""); return session.execute(message, next2); }); }); ctx.schema.extend("command", Command.Config, 1e3); ctx.schema.extend("command-option", import_core5.Schema.object({ permissions: import_core5.Schema.array(String).role("perms").default(["authority:0"]).description("权限继承。"), dependencies: import_core5.Schema.array(String).role("perms").description("权限依赖。") }), 1e3); ctx.on("ready", () => { const bots = ctx.bots.filter((v) => v.status === import_core5.Universal.Status.ONLINE && v.updateCommands); bots.forEach((bot) => this.updateCommands(bot)); }); ctx.on("bot-status-updated", async (bot) => { if (bot.status !== import_core5.Universal.Status.ONLINE || !bot.updateCommands) return; this.updateCommands(bot); }); this.domain("el", (source) => import_core5.h.parse(source), { greedy: true }); this.domain("elements", (source) => import_core5.h.parse(source), { greedy: true }); this.domain("string", (source) => import_core5.h.unescape(source)); this.domain("text", (source) => import_core5.h.unescape(source), { greedy: true }); this.domain("rawtext", (source) => (0, import_core5.h)("", import_core5.h.parse(source)).toString(true), { greedy: true }); this.domain("boolean", () => true); this.domain("number", (source, session) => { const value = +source.replace(/[,_]/g, ""); if (Number.isFinite(value)) return value; throw new Error("internal.invalid-number"); }, { numeric: true }); this.domain("integer", (source, session) => { const value = +source.replace(/[,_]/g, ""); if (value * 0 === 0 && Math.floor(value) === value) return value; throw new Error("internal.invalid-integer"); }, { numeric: true }); thi