@koishijs/core
Version:
Core Features for Koishi
1,350 lines (1,338 loc) • 94.7 kB
JavaScript
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
var __export = (target, all) => {
for (var 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