UNPKG

koishi-core

Version:
1,490 lines (1,480 loc) 88.1 kB
var __create = Object.create; var __defProp = Object.defineProperty; var __defProps = Object.defineProperties; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropDescs = Object.getOwnPropertyDescriptors; var __getOwnPropNames = Object.getOwnPropertyNames; var __getOwnPropSymbols = Object.getOwnPropertySymbols; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __propIsEnum = Object.prototype.propertyIsEnumerable; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __spreadValues = (a, b) => { for (var prop in b || (b = {})) if (__hasOwnProp.call(b, prop)) __defNormalProp(a, prop, b[prop]); if (__getOwnPropSymbols) for (var prop of __getOwnPropSymbols(b)) { if (__propIsEnum.call(b, prop)) __defNormalProp(a, prop, b[prop]); } return a; }; var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b)); var __markAsModule = (target) => __defProp(target, "__esModule", { value: true }); var __export = (target, all) => { __markAsModule(target); for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __reExport = (target, module2, desc) => { if (module2 && typeof module2 === "object" || typeof module2 === "function") { for (let key of __getOwnPropNames(module2)) if (!__hasOwnProp.call(target, key) && key !== "default") __defProp(target, key, { get: () => module2[key], enumerable: !(desc = __getOwnPropDesc(module2, key)) || desc.enumerable }); } return target; }; var __toModule = (module2) => { return __reExport(__markAsModule(__defProp(module2 != null ? __create(__getProtoOf(module2)) : {}, "default", module2 && module2.__esModule && "default" in module2 ? { get: () => module2.default, enumerable: true } : { value: module2, enumerable: true })), module2); }; // packages/koishi-core/src/index.ts __export(exports, { Adapter: () => Adapter, App: () => App, Argv: () => Argv, Bot: () => Bot, Channel: () => Channel, Command: () => Command, Context: () => Context, Database: () => Database, Plugin: () => Plugin, Query: () => Query, Session: () => Session, Tables: () => Tables, User: () => User, checkTimer: () => checkTimer, checkUsage: () => checkUsage, createBots: () => createBots, getCommandNames: () => getCommandNames, getSessionId: () => getSessionId, getUsage: () => getUsage, getUsageName: () => getUsageName, version: () => version }); __reExport(exports, __toModule(require("koishi-utils"))); // packages/koishi-core/src/adapter.ts var import_koishi_utils7 = __toModule(require("koishi-utils")); // packages/koishi-core/src/session.ts var import_lru_cache = __toModule(require("lru-cache")); var import_fastest_levenshtein = __toModule(require("fastest-levenshtein")); // packages/koishi-core/src/database.ts var utils = __toModule(require("koishi-utils")); var Tables; (function(Tables3) { let Field; (function(Field2) { Field2.number = ["integer", "unsigned", "float", "double", "decimal"]; Field2.string = ["char", "string", "text"]; Field2.date = ["timestamp", "date", "time"]; Field2.object = ["list", "json"]; const regexp = /^(\w+)(?:\((.+)\))?$/; function parse(source) { if (typeof source !== "string") return source; const capture = regexp.exec(source); if (!capture) throw new TypeError("invalid field definition"); const type = capture[1]; const args = (capture[2] || "").split(","); const field = { type }; if (field.initial === void 0) { if (Field2.number.includes(field.type)) field.initial = 0; if (Field2.string.includes(field.type)) field.initial = ""; if (field.type === "list") field.initial = []; if (field.type === "json") field.initial = {}; } if (type === "decimal") { field.precision = +args[0]; field.scale = +args[1]; } else if (args[0]) { field.length = +args[0]; } return field; } Field2.parse = parse; })(Field = Tables3.Field || (Tables3.Field = {})); Tables3.config = {}; function extend(name, meta = {}) { var _a; const { primary, type, unique = [], foreign, fields = {} } = meta; const table = (_a = Tables3.config)[name] || (_a[name] = { primary: "id", unique: [], foreign: {}, fields: {} }); table.type = type || table.type; table.primary = primary || table.primary; table.unique.push(...unique); Object.assign(table.foreign, foreign); for (const key in fields) { table.fields[key] = Field.parse(fields[key]); } } Tables3.extend = extend; function create(name) { const { fields, primary } = Tables3.config[name]; const result = {}; for (const key in fields) { if (key !== primary && fields[key].initial !== void 0) { result[key] = utils.clone(fields[key].initial); } } return result; } Tables3.create = create; extend("user", { type: "incremental", fields: { id: "string(63)", name: "string(63)", flag: "unsigned(20)", authority: "unsigned(4)", usage: "json", timers: "json" } }); extend("channel", { fields: { id: "string(63)", flag: "unsigned(20)", assignee: "string(63)", disable: "list" } }); })(Tables || (Tables = {})); var Query; (function(Query2) { function resolve(name, query = {}) { if (Array.isArray(query) || query instanceof RegExp || ["string", "number"].includes(typeof query)) { const { primary } = Tables.config[name]; return { [primary]: query }; } return query; } Query2.resolve = resolve; function resolveModifier(modifier) { if (Array.isArray(modifier)) return { fields: modifier }; return modifier || {}; } Query2.resolveModifier = resolveModifier; })(Query || (Query = {})); var User; (function(User3) { let Flag; (function(Flag2) { Flag2[Flag2["ignore"] = 1] = "ignore"; })(Flag = User3.Flag || (User3.Flag = {})); User3.fields = []; const getters = []; function extend(getter) { getters.push(getter); User3.fields.push(...Object.keys(getter(null, "0"))); } User3.extend = extend; function create(type, id) { const result = Tables.create("user"); result[type] = id; for (const getter of getters) { Object.assign(result, getter(type, id)); } return result; } User3.create = create; })(User || (User = {})); var Channel; (function(Channel2) { let Flag; (function(Flag2) { Flag2[Flag2["ignore"] = 1] = "ignore"; Flag2[Flag2["silent"] = 4] = "silent"; })(Flag = Channel2.Flag || (Channel2.Flag = {})); Channel2.fields = []; const getters = []; function extend(getter) { getters.push(getter); Channel2.fields.push(...Object.keys(getter(null, ""))); } Channel2.extend = extend; function create(type, id) { const result = Tables.create("channel"); result.id = `${type}:${id}`; for (const getter of getters) { Object.assign(result, getter(type, id)); } return result; } Channel2.create = create; })(Channel || (Channel = {})); var Database; (function(Database3) { function extend(module2, extension) { let Database4; try { Database4 = typeof module2 === "string" ? require(module2).default : module2; } catch (error) { return; } if (typeof extension === "function") { extension(Database4); } else { Object.assign(Database4.prototype, extension); } } Database3.extend = extend; })(Database || (Database = {})); // packages/koishi-core/src/command.ts var import_koishi_utils2 = __toModule(require("koishi-utils")); // packages/koishi-core/src/parser.ts var import_koishi_utils = __toModule(require("koishi-utils")); var import_util = __toModule(require("util")); var leftQuotes = `"'“‘`; var rightQuotes = `"'”’`; var Argv; (function(Argv2) { const bracs = {}; function interpolate(initiator, terminator, parse2) { bracs[initiator] = { terminator, parse: parse2 }; } Argv2.interpolate = interpolate; interpolate("$(", ")"); class Tokenizer { 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(__spreadValues(__spreadValues({}, this.bracs), bracs)).map(import_koishi_utils.escapeRegExp).join("|")}`; const regExp = new RegExp(stopReg); while (true) { const capture = regExp.exec(source); content += 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 == null ? void 0 : parse2(source)) || this.parse(source, terminator); source = argv.rest; parent.inters.push(__spreadProps(__spreadValues({}, 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 = []; let rest = source, term = ""; const stopReg = `\\s+|[${(0, import_koishi_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); 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; function stringify(argv) { return defaultTokenizer.stringify(argv); } Argv2.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; function parsePid(target) { const index = target.indexOf(":"); const platform = target.slice(0, index); const id = target.slice(index + 1); return [platform, id]; } Argv2.parsePid = parsePid; function resolveConfig(type) { return typeof type === "string" ? builtin[type] || {} : {}; } function resolveType(type) { var _a; if (typeof type === "function") { return type; } else if (type instanceof RegExp) { return (source) => { if (type.test(source)) return source; throw new Error(); }; } else if (Array.isArray(type)) { return (source) => { if (type.includes(source)) return source; throw new Error(); }; } return (_a = builtin[type]) == null ? void 0 : _a.transform; } const builtin = {}; function createDomain(name, transform, options) { builtin[name] = __spreadProps(__spreadValues({}, options), { transform }); } Argv2.createDomain = createDomain; createDomain("rawtext", (source) => source); createDomain("string", (source) => source); createDomain("text", (source) => source, { greedy: true }); createDomain("rawtext", (source) => import_koishi_utils.segment.unescape(source), { greedy: true }); createDomain("boolean", () => true); createDomain("number", (source) => { const value = +source; if (Number.isFinite(value)) return value; throw new Error((0, import_koishi_utils.template)("internal.invalid-number")); }); createDomain("integer", (source) => { const value = +source; if (value * 0 === 0 && Math.floor(value) === value) return value; throw new Error((0, import_koishi_utils.template)("internal.invalid-integer")); }); createDomain("posint", (source) => { const value = +source; if (value * 0 === 0 && Math.floor(value) === value && value > 0) return value; throw new Error((0, import_koishi_utils.template)("internal.invalid-posint")); }); createDomain("natural", (source) => { const value = +source; if (value * 0 === 0 && Math.floor(value) === value && value >= 0) return value; throw new Error((0, import_koishi_utils.template)("internal.invalid-natural")); }); createDomain("date", (source) => { const timestamp = import_koishi_utils.Time.parseDate(source); if (+timestamp) return timestamp; throw new Error((0, import_koishi_utils.template)("internal.invalid-date")); }); createDomain("user", (source, session) => { if (source.startsWith("@")) { source = source.slice(1); if (source.includes(":")) return source; return `${session.platform}:${source}`; } const code = import_koishi_utils.segment.from(source); if (code && code.type === "at") { return `${session.platform}:${code.data.id}`; } throw new Error((0, import_koishi_utils.template)("internal.invalid-user")); }); createDomain("channel", (source, session) => { if (source.startsWith("#")) { source = source.slice(1); if (source.includes(":")) return source; return `${session.platform}:${source}`; } const code = import_koishi_utils.segment.from(source); if (code && code.type === "sharp") { return `${session.platform}:${code.data.id}`; } throw new Error((0, import_koishi_utils.template)("internal.invalid-channel")); }); const BRACKET_REGEXP = /<[^>]+>|\[[^\]]+\]/g; function parseDecl(source) { let cap; const result = []; while (cap = BRACKET_REGEXP.exec(source)) { let rawName = cap[0].slice(1, -1); let variadic = false; if (rawName.startsWith("...")) { rawName = rawName.slice(3); variadic = true; } const [name, rawType] = rawName.split(":"); const type = rawType ? rawType.trim() : void 0; result.push({ name, variadic, type, required: cap[0][0] === "<" }); } result.stripped = source.replace(/:[\w-]+[>\]]/g, (str) => str.slice(-1)).trimEnd(); return result; } function parseValue(source, quoted, kind, argv, decl = {}) { const { name, type, fallback } = decl; const implicit = source === "" && !quoted; if (implicit && fallback !== void 0) return fallback; const transform = resolveType(type); if (transform) { try { return transform(source, argv.session); } catch (err) { const message = err["message"] || (0, import_koishi_utils.template)("internal.check-syntax"); argv.error = (0, import_koishi_utils.template)(`internal.invalid-${kind}`, name, message); return; } } if (implicit) return true; if (quoted) return source; const n = +source; return n * 0 === 0 ? n : source; } Argv2.parseValue = parseValue; class CommandBase { constructor(name, declaration, description) { this.name = name; this.description = description; this._options = {}; this._namedOptions = {}; this._symbolicOptions = {}; if (!name) throw new Error("expect a command name"); const decl = this._arguments = parseDecl(declaration); this.declaration = decl.stripped; } _createOption(name, def, config) { var _a; const param = (0, import_koishi_utils.paramCase)(name); const decl = def.replace(/(?<=^|\s)[\w\x80-\uffff].*/, ""); const desc = def.slice(decl.length); let syntax = decl.replace(/(?<=^|\s)(<[^<]+>|\[[^[]+\]).*/, ""); const bracket = decl.slice(syntax.length); syntax = syntax.trim() || "--" + param; const names = []; const symbols = []; for (let param2 of syntax.trim().split(",")) { param2 = param2.trimStart(); const name2 = param2.replace(/^-+/, ""); if (!name2 || !param2.startsWith("-")) { symbols.push(param2); } else { names.push(name2); } } if (!config.value && !names.includes(param)) { syntax += ", --" + param; } const declList = parseDecl(bracket); if (declList.stripped) syntax += " " + declList.stripped; if (desc) syntax += " " + desc; const option = (_a = this._options)[name] || (_a[name] = __spreadProps(__spreadValues(__spreadValues(__spreadValues({}, Command.defaultOptionConfig), declList[0]), config), { name, values: {}, description: syntax })); const fallbackType = typeof option.fallback; if ("value" in config) { names.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._assignOption(option, names, 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((0, import_util.format)('duplicate option name "%s" for command "%s"', name, 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, args = [], options = {}) { var _a; if (typeof argv === "string") argv = Argv2.parse(argv, terminator); const source = this.name + " " + Argv2.stringify(argv); while (!argv.error && argv.tokens.length) { const token = argv.tokens[0]; let { content, quoted } = token; const argDecl = this._arguments[args.length]; if (content[0] !== "-" && resolveConfig(argDecl == null ? void 0 : argDecl.type).greedy) { args.push(Argv2.parseValue(Argv2.stringify(argv), true, "argument", argv, argDecl)); break; } argv.tokens.shift(); let option; let names; let param; if (!quoted && (option = this._symbolicOptions[content])) { names = [(0, import_koishi_utils.paramCase)(option.name)]; } else { if (content[0] !== "-" || quoted) { args.push(Argv2.parseValue(content, quoted, "argument", argv, argDecl || { type: "string" })); continue; } let i = 0; let name; for (; i < content.length; ++i) { if (content.charCodeAt(i) !== 45) break; } if (content.slice(i, i + 3) === "no-" && !this._namedOptions[content.slice(i)]) { name = content.slice(i + 3); options[(0, import_koishi_utils.camelCase)(name)] = false; continue; } let j = i + 1; for (; j < content.length; j++) { if (content.charCodeAt(j) === 61) break; } name = content.slice(i, j); names = i > 1 ? [name] : name; param = content.slice(++j); option = this._namedOptions[names[names.length - 1]]; } quoted = false; if (!param) { const { type } = option || {}; if (resolveConfig(type).greedy) { param = Argv2.stringify(argv); quoted = true; argv.tokens = []; } else if (type !== "boolean" && argv.tokens.length && (type || ((_a = argv.tokens[0]) == null ? void 0 : _a.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_koishi_utils.camelCase)(name); if (optDecl && name in optDecl.values) { options[key] = optDecl.values[name]; } else { const source2 = j + 1 < names.length ? "" : param; options[key] = Argv2.parseValue(source2, quoted, "option", argv, optDecl); } if (argv.error) break; } } for (const { name, fallback } of Object.values(this._options)) { if (fallback !== void 0 && !(name in options)) { options[name] = fallback; } } delete argv.tokens; return { options, args, source, rest: argv.rest, error: argv.error || "" }; } 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 = {})); // packages/koishi-core/src/command.ts var import_util2 = __toModule(require("util")); var logger = new import_koishi_utils2.Logger("command"); var _Command = class extends Argv.CommandBase { constructor(name, decl, desc, context) { super(name, decl, desc); this.context = context; this.children = []; this.parent = null; this._aliases = []; this._examples = []; this._userFields = []; this._channelFields = []; this._actions = []; this._checkers = []; this.config = __spreadValues({}, _Command.defaultConfig); this._registerAlias(this.name); context.app._commandList.push(this); context.app.emit("command-added", this); } static userFields(fields) { this._userFields.push(fields); return this; } static channelFields(fields) { this._channelFields.push(fields); return this; } get app() { return this.context.app; } _registerAlias(name) { name = name.toLowerCase(); this._aliases.push(name); const previous = this.app._commands.get(name); if (!previous) { this.app._commands.set(name, this); } else if (previous !== this) { throw new Error((0, import_util2.format)('duplicate command names: "%s"', name)); } } [import_util2.inspect.custom]() { return `Command <${this.name}>`; } userFields(fields) { this._userFields.push(fields); return this; } channelFields(fields) { this._channelFields.push(fields); return this; } alias(...names) { var _a; if (this._disposed) return this; for (const name of names) { this._registerAlias(name); (_a = this._disposables) == null ? void 0 : _a.push(() => { (0, import_koishi_utils2.remove)(this._aliases, name); this.app._commands.delete(name); }); } return this; } shortcut(name, config = {}) { var _a; if (this._disposed) return this; config.name = name; config.command = this; config.authority || (config.authority = this.config.authority); this.app._shortcuts.push(config); (_a = this._disposables) == null ? void 0 : _a.push(() => (0, import_koishi_utils2.remove)(this.app._shortcuts, config)); 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] || {}; if (this._disposed) config.patch = true; return this.context.command(def, desc, config); } usage(text) { this._usage = text; return this; } example(example) { this._examples.push(example); return this; } option(name, desc, config = {}) { var _a; this._createOption(name, desc, config); (_a = this._disposables) == null ? void 0 : _a.push(() => this.removeOption(name)); return this; } match(session) { const { authority = Infinity } = session.user || {}; const { disable = [] } = session.channel || {}; return this.context.match(session) && this.config.authority <= authority && !disable.includes(this.name); } getConfig(key, session) { const value = this.config[key]; return typeof value === "function" ? value(session.user) : value; } check(callback, prepend = false) { var _a; if (prepend) { this._checkers.unshift(callback); } else { this._checkers.push(callback); } (_a = this._disposables) == null ? void 0 : _a.push(() => (0, import_koishi_utils2.remove)(this._checkers, callback)); return this; } action(callback, append = false) { var _a; if (append) { this._actions.push(callback); } else { this._actions.unshift(callback); } (_a = this._disposables) == null ? void 0 : _a.push(() => (0, import_koishi_utils2.remove)(this._actions, callback)); return this; } async execute(argv0, next = (fallback) => fallback == null ? void 0 : fallback()) { const argv = argv0; if (!argv.args) argv.args = []; if (!argv.options) argv.options = {}; let state = "before command"; argv.next = async (fallback) => { const oldState = state; state = ""; await next(fallback); state = oldState; }; const { args, options, session, error } = argv; if (error) return error; if (logger.level >= 3) logger.debug(argv.source || (argv.source = this.stringify(args, options))); const lastCall = this.app.options.prettyErrors && new Error().stack.split("\n", 4)[3]; try { for (const validator of this._checkers) { const result2 = await validator.call(this, argv, ...args); if (typeof result2 === "string") return result2; } const result = await this.app.serial(session, "before-command", argv); if (typeof result === "string") return result; state = "executing command"; for (const action of this._actions) { const result2 = await action.call(this, argv, ...args); if (typeof result2 === "string") return result2; } state = "after command"; await this.app.parallel(session, "command", argv); return ""; } catch (error2) { if (!state) throw error2; let stack = (0, import_koishi_utils2.coerce)(error2); if (lastCall) { const index = error2.stack.indexOf(lastCall); stack = stack.slice(0, index - 1); } logger.warn(`${state}: ${argv.source || (argv.source = this.stringify(args, options))} ${stack}`); return ""; } } dispose() { this._disposed = true; this.app.emit("command-removed", this); for (const cmd of this.children.slice()) { cmd.dispose(); } this.app._shortcuts = this.app._shortcuts.filter((s) => s.command !== this); this._aliases.forEach((name) => this.app._commands.delete(name)); (0, import_koishi_utils2.remove)(this.app._commandList, this); if (this.parent) { (0, import_koishi_utils2.remove)(this.parent.children, this); } } }; var Command = _Command; Command.defaultConfig = { authority: 1, showWarning: true, maxUsage: Infinity, minInterval: 0 }; Command.defaultOptionConfig = { authority: 0 }; Command._userFields = []; Command._channelFields = []; function getUsageName(command) { return command.config.usageName || command.name; } Command.channelFields(["disable"]); Command.userFields(({ tokens, command, options = {} }, fields) => { if (!command) return; const { maxUsage, minInterval, authority } = command.config; let shouldFetchAuthority = authority > 0; let shouldFetchUsage = !!(maxUsage || minInterval); for (const { name, authority: authority2, notUsage } of Object.values(command._options)) { if (name in options) { if (authority2 > 0) shouldFetchAuthority = true; if (notUsage) shouldFetchUsage = false; } else if (tokens) { if (authority2 > 0) shouldFetchAuthority = true; } } if (shouldFetchAuthority) fields.add("authority"); if (shouldFetchUsage) { if (maxUsage) fields.add("usage"); if (minInterval) fields.add("timers"); } }); function apply(ctx) { ctx.before("command", ({ session, command }) => { if (!session.channel) return; while (command) { if (session.channel.disable.includes(command.name)) return ""; command = command.parent; } }); ctx.before("command", (argv) => { const { session, options, command } = argv; if (!session.user) return; function sendHint(message, ...param) { return command.config.showWarning ? (0, import_koishi_utils2.template)(message, param) : ""; } let isUsage = true; if (command.config.authority > session.user.authority) { return sendHint("internal.low-authority"); } for (const option of Object.values(command._options)) { if (option.name in options) { if (option.authority > session.user.authority) { return sendHint("internal.low-authority"); } if (option.notUsage) isUsage = false; } } if (isUsage) { const name = getUsageName(command); const minInterval = command.getConfig("minInterval", session); const maxUsage = command.getConfig("maxUsage", session); if (maxUsage < Infinity && checkUsage(name, session.user, maxUsage)) { return sendHint("internal.usage-exhausted"); } if (minInterval > 0 && checkTimer(name, session.user, minInterval)) { return sendHint("internal.too-frequent"); } } }); ctx.before("command", (argv) => { const { args, options, command } = argv; function sendHint(message, ...param) { return command.config.showWarning ? (0, import_koishi_utils2.template)(message, param) : ""; } if (command.config.checkArgCount) { const nextArg = command._arguments[args.length] || {}; if (nextArg.required) { return sendHint("internal.insufficient-arguments"); } const finalArg = command._arguments[command._arguments.length - 1] || {}; if (args.length > command._arguments.length && finalArg.type !== "text" && !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(", ")); } } }); } function getUsage(name, user) { const $date = import_koishi_utils2.Time.getDateNumber(); if (user.usage.$date !== $date) { user.usage = { $date }; } return user.usage[name] || 0; } function checkUsage(name, user, maxUsage) { if (!user.usage) return; const count = getUsage(name, user); if (count >= maxUsage) return true; if (maxUsage) { user.usage[name] = count + 1; } } function checkTimer(name, { timers }, offset) { const now = Date.now(); if (!(now <= timers.$date)) { for (const key in timers) { if (now > timers[key]) delete timers[key]; } timers.$date = now + import_koishi_utils2.Time.day; } if (now <= timers[name]) return true; if (offset !== void 0) { timers[name] = now + offset; } } // packages/koishi-core/src/session.ts var import_koishi_utils3 = __toModule(require("koishi-utils")); var logger2 = new import_koishi_utils3.Logger("session"); var _Session = class { constructor(app, session) { Object.assign(this, session); (0, import_koishi_utils3.defineProperty)(this, "app", app); (0, import_koishi_utils3.defineProperty)(this, "user", null); (0, import_koishi_utils3.defineProperty)(this, "channel", null); (0, import_koishi_utils3.defineProperty)(this, "sid", `${this.platform}:${this.selfId}`); (0, import_koishi_utils3.defineProperty)(this, "uid", `${this.platform}:${this.userId}`); (0, import_koishi_utils3.defineProperty)(this, "cid", `${this.platform}:${this.channelId}`); (0, import_koishi_utils3.defineProperty)(this, "gid", `${this.platform}:${this.groupId}`); (0, import_koishi_utils3.defineProperty)(this, "bot", app.bots[this.sid]); (0, import_koishi_utils3.defineProperty)(this, "id", import_koishi_utils3.Random.uuid()); (0, import_koishi_utils3.defineProperty)(this, "_queued", Promise.resolve()); (0, import_koishi_utils3.defineProperty)(this, "_hooks", []); } toJSON() { return Object.fromEntries(Object.entries(this).filter(([key]) => { return !key.startsWith("_") && !key.startsWith("$"); })); } async _preprocess() { let node; let content = this.app.options.processMessage(this.content); if (node = import_koishi_utils3.segment.from(content, { type: "quote", caret: true })) { content = content.slice(node.capture[0].length).trimStart(); this.quote = await this.bot.getMessage(node.data.channelId || this.channelId, node.data.id).catch(import_koishi_utils3.noop); } return content; } async preprocess() { return this._promise || (this._promise = this._preprocess()); } get username() { const defaultName = this.user && this.user["name"] ? this.user["name"] : this.author ? this.author.nickname || this.author.username : this.userId; return this.app.chain("appellation", defaultName, this); } get database() { return this.app.database; } async send(message) { if (this.bot[_Session.send]) { return this.bot[_Session.send](this, message); } if (!message) return; await this.bot.sendMessage(this.channelId, message, this.groupId); } cancelQueued(delay = this.app.options.delay.cancel) { this._hooks.forEach(Reflect.apply); this._delay = delay; } async sendQueued(content, delay) { if (!content) return; if (typeof delay === "undefined") { const { message, character } = this.app.options.delay; delay = Math.max(message, character * content.length); } return this._queued = this._queued.then(() => new Promise((resolve) => { const hook = () => { resolve(); clearTimeout(timer); (0, import_koishi_utils3.remove)(this._hooks, hook); }; this._hooks.push(hook); const timer = setTimeout(async () => { await this.send(content); this._delay = delay; hook(); }, this._delay || 0); })); } resolveValue(source) { return typeof source === "function" ? Reflect.apply(source, null, [this]) : source; } async getChannel(id = this.channelId, assignee = "", fields = []) { const group = await this.database.getChannel(this.platform, id, fields); if (group) return group; const fallback = Channel.create(this.platform, id); fallback.assignee = assignee; if (assignee) { await this.database.createChannel(this.platform, id, fallback); } return fallback; } async observeChannel(fields = []) { const fieldSet = new Set(fields); const { platform, channelId, channel } = this; if (channel) { for (const key in channel) { fieldSet.delete(key); } if (fieldSet.size) { const data2 = await this.getChannel(channelId, "", [...fieldSet]); this.app._channelCache.set(this.cid, channel._merge(data2)); } return channel; } const cache = this.app._channelCache.get(this.cid); const fieldArray = [...fieldSet]; const hasActiveCache = cache && (0, import_koishi_utils3.contain)(Object.keys(cache), fieldArray); if (hasActiveCache) return this.channel = cache; const assignee = this.resolveValue(this.app.options.autoAssign) ? this.selfId : ""; const data = await this.getChannel(channelId, assignee, fieldArray); const newChannel = (0, import_koishi_utils3.observe)(data, (diff) => this.database.setChannel(platform, channelId, diff), `channel ${this.cid}`); this.app._channelCache.set(this.cid, newChannel); return this.channel = newChannel; } async getUser(id = this.userId, authority = 0, fields = []) { const user = await this.database.getUser(this.platform, id, fields); if (user) return user; const fallback = User.create(this.platform, id); fallback.authority = authority; if (authority) { await this.database.createUser(this.platform, id, fallback); } return fallback; } async observeUser(fields = []) { var _a, _b; const fieldSet = new Set(fields); const { userId, user } = this; let userCache = this.app._userCache[this.platform]; if (!userCache) { userCache = this.app._userCache[this.platform] = new import_lru_cache.default({ max: this.app.options.userCacheLength, maxAge: this.app.options.userCacheAge }); } if (user && !((_a = this.author) == null ? void 0 : _a.anonymous)) { for (const key in user) { fieldSet.delete(key); } if (fieldSet.size) { const data2 = await this.getUser(userId, 0, [...fieldSet]); userCache.set(userId, user._merge(data2)); } } if (user) return user; if ((_b = this.author) == null ? void 0 : _b.anonymous) { const fallback = User.create(this.platform, userId); fallback.authority = this.resolveValue(this.app.options.autoAuthorize); const user2 = (0, import_koishi_utils3.observe)(fallback, () => Promise.resolve()); return this.user = user2; } const cache = userCache.get(userId); const fieldArray = [...fieldSet]; const hasActiveCache = cache && (0, import_koishi_utils3.contain)(Object.keys(cache), fieldArray); if (hasActiveCache) return this.user = cache; const data = await this.getUser(userId, this.resolveValue(this.app.options.autoAuthorize), fieldArray); const newUser = (0, import_koishi_utils3.observe)(data, (diff) => this.database.setUser(this.platform, userId, diff), `user ${this.uid}`); userCache.set(userId, newUser); return this.user = newUser; } collect(key, argv, fields = new Set()) { const collect = (argv2) => { argv2.session = this; if (argv2.tokens) { for (const { inters } of argv2.tokens) { inters.forEach(collect); } } if (!this.resolve(argv2)) return; collectFields(argv2, Command[`_${key}Fields`], fields); collectFields(argv2, argv2.command[`_${key}Fields`], fields); }; collect(argv); return fields; } resolve(argv) { var _a; if (!argv.command) { const { name = this.app.bail("parse", argv, this) } = argv; if (!(argv.command = this.app._commands.get(name))) return; } if ((_a = argv.tokens) == null ? void 0 : _a.every((token) => !token.inters.length)) { const { options, args, error } = argv.command.parse(argv); argv.options = __spreadValues(__spreadValues({}, argv.options), options); argv.args = [...argv.args || [], ...args]; argv.error = error; } return argv.command; } async execute(argv, next) { if (typeof argv === "string") argv = Argv.parse(argv); argv.session = this; if (argv.tokens) { for (const arg of argv.tokens) { const { inters } = arg; const output = []; for (let i = 0; i < inters.length; ++i) { output.push(await this.execute(inters[i], true)); } for (let i = inters.length - 1; i >= 0; --i) { const { pos } = inters[i]; arg.content = arg.content.slice(0, pos) + output[i] + arg.content.slice(pos); } arg.inters = []; } if (!this.resolve(argv)) return ""; } else { argv.command || (argv.command = this.app._commands.get(argv.name)); if (!argv.command) { logger2.warn(new Error(`cannot find command ${argv.name}`)); return ""; } } if (!argv.command.context.match(this)) return ""; if (this.database) { if (this.subtype === "group") { await this.observeChannel(this.collect("channel", argv)); } await this.observeUser(this.collect("user", argv)); } let shouldEmit = true; if (next === true) { shouldEmit = false; next = (fallback) => fallback(); } const result = await argv.command.execute(argv, next); if (!shouldEmit) return result; await this.send(result); return ""; } middleware(middleware) { const identifier = getSessionId(this); return this.app.middleware(async (session, next) => { if (identifier && getSessionId(session) !== identifier) return next(); return middleware(session, next); }, true); } prompt(timeout = this.app.options.delay.prompt) { return new Promise((resolve) => { const dispose = this.middleware((session) => { clearTimeout(timer); dispose(); resolve(session.content); }); const timer = setTimeout(() => { dispose(); resolve(""); }, timeout); }); } suggest(options) { const { target, items, prefix = "", suffix, apply: apply3, next = (callback) => callback(), minSimilarity = this.app.options.minSimilarity } = options; let suggestions, minDistance = Infinity; for (const name of items) { const dist = (0, import_fastest_levenshtein.distance)(name, target); if (name.length <= 2 || dist > name.length * minSimilarity) continue; if (dist === minDistance) { suggestions.push(name); } else if (dist < minDistance) { suggestions = [name]; minDistance = dist; } } if (!suggestions) return next(() => this.send(prefix)); return next(() => { const message = prefix + (0, import_koishi_utils3.template)("internal.suggestion", suggestions.map(import_koishi_utils3.template.quote).join(import_koishi_utils3.template.get("basic.or"))); if (suggestions.length > 1) return this.send(message); const dispose = this.middleware((session, next2) => { dispose(); const message2 = session.content.trim(); if (message2 && message2 !== "." && message2 !== "。") return next2(); return apply3.call(session, suggestions[0], next2); }); return this.send(message + suffix); }); } }; var Session = _Session; Session.send = Symbol.for("koishi.session.send"); function getSessionId(session) { return "" + session.userId + session.channelId; } function collectFields(argv, collectors, fields) { for (const collector of collectors) { if (typeof collector === "function") { collector(argv, fields); continue; } for (const field of collector) { fields.add(field); } } return fields; } // packages/koishi-core/src/app.ts var import_koishi_utils6 = __toModule(require("koishi-utils")); // packages/koishi-core/src/context.ts var import_koishi_utils4 = __toModule(require("koishi-utils")); var import_util3 = __toModule(require("util")); var import_router = __toModule(require("@koa/router")); var Plugin; (function(Plugin2) { class Registry extends Map { resolve(plugin) { return plugin && (typeof plugin === "function" ? plugin : plugin.apply); } get(plugin) { return super.get(this.resolve(plugin)); } set(plugin, state) { return super.set(this.resolve(plugin), state); } has(plugin) { return super.has(this.resolve(plugin)); } delete(plugin) { return super.delete(this.resolve(plugin)); } } Plugin2.Registry = Registry; })(Plugin || (Plugin = {})); function isBailed(value) { return value !== null && value !== false && value !== void 0; } function safeRequire(id) { try { return require(id); } catch { } } var _Context = class { constructor(filter, app, _plugin = null) { this.filter = filter; this.app = app; this._plugin = _plugin; } static inspect(plugin) { return !plugin ? "root" : typeof plugin === "object" && plugin.name || "anonymous"; } [import_util3.inspect.custom]() { return `Context <${_Context.inspect(this._plugin)}>`; } createSelector(key) { const selector = (...args) => this.select(key, ...args); selector.except = (...args) => this.unselect(key, ...args); return selector; } get user() { return this.createSelector("userId"); } get self() { return this.createSelector("selfId"); } get group() { return this.createSelector("groupId"); } get channel() { return this.createSelector("channelId"); } get platform() { return this.createSelector("platform"); } get private() { return this.unselect("groupId").user; } get bots() { return this.app._bots; } logger(name) { return new import_koishi_utils4.Logger(name); } select(key, ...values) { return this.intersect((session) => { return values.length ? values.includes(session[key]) : !!session[key]; }); } unselect(key, ...values) { return this.intersect((session) => { return values.length ? !values.includes(session[key]) : !session[key]; }); } all() { return new _Context(() => true, this.app, this._plugin); } union(arg) { const filter = typeof arg === "function" ? arg : arg.filter; return new _Context((s) => this.filter(s) || filter(s), this.app, this._plugin); } intersect(arg) { const filter = typeof arg === "function" ? arg : arg.filter; return new _Context((s) => this.filter(s) && filter(s), this.app, this._plugin); } except(arg) { const filter = typeof arg === "function" ? arg : arg.filter; return new _Context((s) => this.filter(s) && !filter(s), this.app, this._plugin); } match(session) { return !session || this.filter(session); } get state() { return this.app.registry.get(this._plugin); } addSideEffect(state = this.state) { while (state && !state.sideEffect) { state.sideEffect = true; state = state.parent; } } teleport(modules, callback) { const states = []; for (const module2 of modules) { const state = this.app.registry.get(module2); if (!state) return; states.push(state); } const plugin = (ctx) => callback(ctx, ...modules); const dispose = () => this.dispose(plugin); this.plugin(plugin); states.every((state) => state.disposables.push(dispose)); this.before("disconnect", () => { states.every((state) => (0, import_koishi_utils4.remove)(state.disposables, dispose)); }); } with(deps, callback) { const modules = deps.map(safeRequire); if (!modules.every((val) => val)) return this; this.teleport(modules, callback); this.on("plugin-added", (added) => { const modules2 = deps.map(safeRequire); if (modules2.includes(added)) this.teleport(modules2, callback); }); return this; } plugin(plugin, options) { if (options === false) return this; if (options === true) options = void 0; if (this.app.registry.has(plugin)) { this.logger("app").warn(new Error(`duplicate plugin <${_Context.inspect(plugin)}> detected`)); return this; } const ctx = Object.create(this); (0, import_koishi_utils4.defineProperty)(ctx, "_plugin", plugin); this.app.registry.set(plugin, { plugin, id: import_koishi_utils4.Random.uuid(), context: this, config: options, parent: this.state, childr