koishi-core
Version:
Core features for Koishi
1,490 lines (1,480 loc) • 88.1 kB
JavaScript
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