koishi-core
Advanced tools
Comparing version 2.4.2 to 3.0.0-alpha.0
@@ -0,1 +1,2 @@ | ||
/// <reference types="node" /> | ||
import { Observed } from 'koishi-utils'; | ||
@@ -5,8 +6,9 @@ import { Command } from './command'; | ||
import { Group, User, Database } from './database'; | ||
import { BotOptions, Server } from './server'; | ||
import { BotOptions, Server, Bot } from './server'; | ||
import { Session } from './session'; | ||
import LruCache from 'lru-cache'; | ||
import * as http from 'http'; | ||
import type Router from 'koa-router'; | ||
export interface AppOptions extends BotOptions { | ||
port?: number; | ||
type?: string; | ||
bots?: BotOptions[]; | ||
@@ -37,4 +39,7 @@ prefix?: string | string[]; | ||
options: AppOptions; | ||
server: Server; | ||
bots: Record<string, Bot>; | ||
servers: Record<string, Server>; | ||
status: AppStatus; | ||
router?: Router; | ||
_httpServer?: http.Server; | ||
_database: Database; | ||
@@ -45,13 +50,14 @@ _commands: Command[]; | ||
_hooks: Record<keyof any, [Context, (...args: any[]) => any][]>; | ||
_userCache: LruCache<number, Observed<Partial<User>, Promise<void>>>; | ||
_groupCache: LruCache<number, Observed<Partial<Group>, Promise<void>>>; | ||
_userCache: Record<string, LruCache<string, Observed<Partial<User>, Promise<void>>>>; | ||
_groupCache: LruCache<string, Observed<Partial<Group>, Promise<void>>>; | ||
private _nameRE; | ||
private _prefixRE; | ||
private _getSelfIdsPromise; | ||
static defaultConfig: AppOptions; | ||
constructor(options?: AppOptions); | ||
createServer(): void; | ||
prepare(): void; | ||
getSelfIds(): Promise<number[]>; | ||
start(): Promise<void>; | ||
private _listen; | ||
stop(): Promise<void>; | ||
private _close; | ||
private _preprocess; | ||
@@ -58,0 +64,0 @@ private _receive; |
@@ -110,5 +110,5 @@ /// <reference types="node" /> | ||
stringify(args: readonly string[], options: any): string; | ||
execute(argv: ParsedArgv<U, G, O>): Promise<void>; | ||
execute(argv: ParsedArgv<U, G, O>): any; | ||
dispose(): void; | ||
} | ||
export {}; |
@@ -1,6 +0,5 @@ | ||
/// <reference types="koa-router" /> | ||
import { Logger } from 'koishi-utils'; | ||
import { Command, CommandConfig, ParsedArgv, ExecuteArgv } from './command'; | ||
import { PostType, Session } from './session'; | ||
import { User, Group } from './database'; | ||
import { EventType, Session } from './session'; | ||
import { User, Group, PlatformKind } from './database'; | ||
import { App } from './app'; | ||
@@ -16,3 +15,3 @@ export declare type NextFunction = (next?: NextFunction) => Promise<void>; | ||
export declare type Disposable = () => void; | ||
interface ScopeSet extends Array<number> { | ||
interface ScopeSet extends Array<string> { | ||
positive?: boolean; | ||
@@ -31,10 +30,8 @@ } | ||
constructor(scope: Scope, app?: App); | ||
get router(): import("koa-router")<any, {}>; | ||
get database(): import("./database").Database; | ||
set database(database: import("./database").Database); | ||
logger(name: string): Logger; | ||
get bots(): import("./server").Bot[]; | ||
group(...ids: number[]): Context; | ||
user(...ids: number[]): Context; | ||
private(...ids: number[]): Context; | ||
group(...ids: string[]): Context; | ||
user(...ids: string[]): Context; | ||
private(...ids: string[]): Context; | ||
match(session: Session): boolean; | ||
@@ -64,45 +61,44 @@ plugin<T extends PluginFunction<this>>(plugin: T, options?: T extends PluginFunction<this, infer U> ? U : never): this; | ||
command(rawName: string, description: string, config?: CommandConfig): Command; | ||
broadcast(message: string, forced?: boolean): Promise<number[]>; | ||
broadcast(groups: readonly number[], message: string, forced?: boolean): Promise<number[]>; | ||
broadcast(message: string, forced?: boolean): Promise<string[]>; | ||
broadcast(groups: readonly string[], message: string, forced?: boolean): Promise<string[]>; | ||
dispose(): void; | ||
} | ||
export declare type RawSession<P extends PostType = PostType> = Session<never, never, never, P>; | ||
export declare type RawSession<E extends EventType = EventType> = Session<never, never, never, PlatformKind, E>; | ||
export interface EventMap { | ||
[Context.MIDDLEWARE_EVENT]: Middleware; | ||
'message'(session: RawSession<'message'>): void; | ||
'message/normal'(session: RawSession<'message'>): void; | ||
'message/notice'(session: RawSession<'message'>): void; | ||
'message/anonymous'(session: RawSession<'message'>): void; | ||
'message/friend'(session: RawSession<'message'>): void; | ||
'message/group'(session: RawSession<'message'>): void; | ||
'message/other'(session: RawSession<'message'>): void; | ||
'friend-add'(session: RawSession<'notice'>): void; | ||
'group-increase'(session: RawSession<'notice'>): void; | ||
'group-increase/invite'(session: RawSession<'notice'>): void; | ||
'group-increase/approve'(session: RawSession<'notice'>): void; | ||
'group-decrease'(session: RawSession<'notice'>): void; | ||
'group-decrease/leave'(session: RawSession<'notice'>): void; | ||
'group-decrease/kick'(session: RawSession<'notice'>): void; | ||
'group-decrease/kick-me'(session: RawSession<'notice'>): void; | ||
'group-upload'(session: RawSession<'notice'>): void; | ||
'group-admin'(session: RawSession<'notice'>): void; | ||
'group-admin/set'(session: RawSession<'notice'>): void; | ||
'group-admin/unset'(session: RawSession<'notice'>): void; | ||
'group-ban'(session: RawSession<'notice'>): void; | ||
'group-ban/ban'(session: RawSession<'notice'>): void; | ||
'group-ban/lift-ban'(session: RawSession<'notice'>): void; | ||
'group_recall'(session: RawSession<'notice'>): void; | ||
'friend_recall'(session: RawSession<'notice'>): void; | ||
'notify'(session: RawSession<'notice'>): void; | ||
'notify/poke'(session: RawSession<'notice'>): void; | ||
'notify/lucky_king'(session: RawSession<'notice'>): void; | ||
'notify/honor'(session: RawSession<'notice'>): void; | ||
'request/friend'(session: RawSession<'request'>): void; | ||
'request/group/add'(session: RawSession<'request'>): void; | ||
'request/group/invite'(session: RawSession<'request'>): void; | ||
'heartbeat'(session: RawSession<'meta_event'>): void; | ||
'lifecycle'(session: RawSession<'meta_event'>): void; | ||
'lifecycle/enable'(session: RawSession<'meta_event'>): void; | ||
'lifecycle/disable'(session: RawSession<'meta_event'>): void; | ||
'lifecycle/connect'(session: RawSession<'meta_event'>): void; | ||
'message-edited'(session: RawSession<'message'>): void; | ||
'message-edited/friend'(session: RawSession<'message'>): void; | ||
'message-edited/group'(session: RawSession<'message'>): void; | ||
'message-deleted'(session: RawSession<'message'>): void; | ||
'message-deleted/friend'(session: RawSession<'message'>): void; | ||
'message-deleted/group'(session: RawSession<'message'>): void; | ||
'friend-added'(session: Session): void; | ||
'friend-deleted'(session: Session): void; | ||
'group'(session: Session): void; | ||
'group-added'(session: Session): void; | ||
'group-deleted'(session: Session): void; | ||
'group-member'(session: Session): void; | ||
'group-member-added'(session: Session): void; | ||
'group-member-deleted'(session: Session): void; | ||
'group-upload'(session: Session): void; | ||
'group-admin'(session: Session): void; | ||
'group-admin/set'(session: Session): void; | ||
'group-admin/unset'(session: Session): void; | ||
'group-ban'(session: Session): void; | ||
'group-ban/ban'(session: Session): void; | ||
'group-ban/lift-ban'(session: Session): void; | ||
'notify'(session: Session): void; | ||
'notify/poke'(session: Session): void; | ||
'notify/lucky_king'(session: Session): void; | ||
'notify/honor'(session: Session): void; | ||
'request/friend'(session: Session): void; | ||
'request/group/add'(session: Session): void; | ||
'request/group/invite'(session: Session): void; | ||
'lifecycle/enable'(session: RawSession<'lifecycle'>): void; | ||
'lifecycle/disable'(session: RawSession<'lifecycle'>): void; | ||
'lifecycle/connect'(session: RawSession<'lifecycle'>): void; | ||
'lifecycle/heartbeat'(session: RawSession<'lifecycle'>): void; | ||
'parse'(message: string, session: Session, builtin: boolean, terminator: string): void | ExecuteArgv; | ||
@@ -109,0 +105,0 @@ 'before-attach-user'(session: Session, fields: Set<User.Field>): void; |
@@ -7,3 +7,3 @@ import * as utils from 'koishi-utils'; | ||
} | ||
export interface User { | ||
export interface User extends Record<PlatformKind, string> { | ||
id: number; | ||
@@ -23,11 +23,15 @@ flag: number; | ||
export type Observed<K extends Field = Field> = utils.Observed<Pick<User, K>, Promise<void>>; | ||
type Getter = (id: number, authority: number) => Partial<User>; | ||
type Getter = (type: PlatformKind, id: string, authority: number) => Partial<User>; | ||
export function extend(getter: Getter): void; | ||
export function create(id: number, authority: number): User; | ||
export function create(type: PlatformKind, id: string, authority: number): User; | ||
export {}; | ||
} | ||
export interface Platforms { | ||
} | ||
export declare type PlatformKind = keyof Platforms; | ||
export interface Group { | ||
id: number; | ||
id: string; | ||
type: PlatformKind; | ||
flag: number; | ||
assignee: number; | ||
assignee: string; | ||
} | ||
@@ -37,3 +41,2 @@ export declare namespace Group { | ||
ignore = 1, | ||
noImage = 2, | ||
silent = 4 | ||
@@ -44,18 +47,14 @@ } | ||
export type Observed<K extends Field = Field> = utils.Observed<Pick<Group, K>, Promise<void>>; | ||
type Getter = (id: number, assignee: number) => Partial<Group>; | ||
type Getter = (type: PlatformKind, id: string, assignee: string) => Partial<Group>; | ||
export function extend(getter: Getter): void; | ||
export function create(id: number, assignee: number): Group; | ||
export function create(type: PlatformKind, id: string, assignee: string): Group; | ||
export {}; | ||
} | ||
export interface Database { | ||
getUser<K extends User.Field>(userId: number, fields?: readonly K[]): Promise<Pick<User, K | 'id'>>; | ||
getUser<K extends User.Field>(userId: number, defaultAuthority?: number, fields?: readonly K[]): Promise<Pick<User, K | 'id'>>; | ||
getUsers<K extends User.Field>(fields?: readonly K[]): Promise<Pick<User, K>[]>; | ||
getUsers<K extends User.Field>(ids: readonly number[], fields?: readonly K[]): Promise<Pick<User, K>[]>; | ||
setUser(userId: number, data: Partial<User>): Promise<void>; | ||
getGroup<K extends Group.Field>(groupId: number, fields?: readonly K[]): Promise<Pick<Group, K | 'id'>>; | ||
getGroup<K extends Group.Field>(groupId: number, selfId?: number, fields?: readonly K[]): Promise<Pick<Group, K | 'id'>>; | ||
getAllGroups<K extends Group.Field>(assignees?: readonly number[]): Promise<Pick<Group, K>[]>; | ||
getAllGroups<K extends Group.Field>(fields?: readonly K[], assignees?: readonly number[]): Promise<Pick<Group, K>[]>; | ||
setGroup(groupId: number, data: Partial<Group>): Promise<void>; | ||
getUser<K extends User.Field>(type: PlatformKind, id: string, fields?: readonly K[]): Promise<Pick<User, K>>; | ||
getUsers<K extends User.Field>(type: PlatformKind, ids?: readonly string[], fields?: readonly K[]): Promise<Pick<User, K>[]>; | ||
setUser(type: PlatformKind, id: string, data: Partial<User>): Promise<void>; | ||
getGroup<K extends Group.Field>(type: PlatformKind, id: string, fields?: readonly K[]): Promise<Pick<Group, K | 'id' | 'type'>>; | ||
getAllGroups<K extends Group.Field>(fields?: readonly K[], assignees?: readonly string[]): Promise<Pick<Group, K>[]>; | ||
setGroup(type: PlatformKind, id: string, data: Partial<Group>): Promise<void>; | ||
} | ||
@@ -62,0 +61,0 @@ declare type DatabaseExtensionMethods<I> = { |
1732
dist/index.js
@@ -1,24 +0,1710 @@ | ||
"use strict"; | ||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { | ||
if (k2 === undefined) k2 = k; | ||
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); | ||
}) : (function(o, m, k, k2) { | ||
if (k2 === undefined) k2 = k; | ||
o[k2] = m[k]; | ||
})); | ||
var __exportStar = (this && this.__exportStar) || function(m, exports) { | ||
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); | ||
var __create = Object.create; | ||
var __defProp = Object.defineProperty; | ||
var __getProtoOf = Object.getPrototypeOf; | ||
var __hasOwnProp = Object.prototype.hasOwnProperty; | ||
var __getOwnPropNames = Object.getOwnPropertyNames; | ||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor; | ||
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}); | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
__exportStar(require("./app"), exports); | ||
__exportStar(require("./command"), exports); | ||
__exportStar(require("./context"), exports); | ||
__exportStar(require("./database"), exports); | ||
__exportStar(require("./session"), exports); | ||
__exportStar(require("./server"), exports); | ||
__exportStar(require("./plugins/help"), exports); | ||
__exportStar(require("./plugins/message"), exports); | ||
__exportStar(require("./plugins/shortcut"), exports); | ||
__exportStar(require("./plugins/suggest"), exports); | ||
__exportStar(require("./plugins/validate"), exports); | ||
//# sourceMappingURL=index.js.map | ||
var __exportStar = (target, module2, desc) => { | ||
__markAsModule(target); | ||
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) => { | ||
if (module2 && module2.__esModule) | ||
return module2; | ||
return __exportStar(__defProp(module2 != null ? __create(__getProtoOf(module2)) : {}, "default", {value: module2, enumerable: true}), module2); | ||
}; | ||
// packages/koishi-core/src/index.ts | ||
__export(exports, { | ||
App: () => App2, | ||
AppStatus: () => AppStatus, | ||
Bot: () => Bot, | ||
BotStatusCode: () => BotStatusCode, | ||
Command: () => Command, | ||
Context: () => Context, | ||
Group: () => Group, | ||
Message: () => Message, | ||
Server: () => Server, | ||
Session: () => Session, | ||
User: () => User, | ||
checkTimer: () => checkTimer, | ||
checkUsage: () => checkUsage, | ||
extendDatabase: () => extendDatabase, | ||
getCommands: () => getCommands, | ||
getSessionId: () => getSessionId, | ||
getTargetId: () => getTargetId, | ||
getUsage: () => getUsage, | ||
getUsageName: () => getUsageName, | ||
parseArguments: () => parseArguments | ||
}); | ||
// packages/koishi-core/src/app.ts | ||
const koishi_utils7 = __toModule(require("koishi-utils")); | ||
// packages/koishi-core/src/context.ts | ||
const koishi_utils2 = __toModule(require("koishi-utils")); | ||
// packages/koishi-core/src/command.ts | ||
const koishi_utils = __toModule(require("koishi-utils")); | ||
const util = __toModule(require("util")); | ||
const logger = new koishi_utils.Logger("command"); | ||
const ANGLED_BRACKET_REGEXP = /<([^>]+)>/g; | ||
const SQUARE_BRACKET_REGEXP = /\[([^\]]+)\]/g; | ||
function parseBracket(name, required) { | ||
let variadic = false, greedy = false; | ||
if (name.startsWith("...")) { | ||
name = name.slice(3); | ||
variadic = true; | ||
} else if (name.endsWith("...")) { | ||
name = name.slice(0, -3); | ||
greedy = true; | ||
} | ||
return { | ||
name, | ||
required, | ||
variadic, | ||
greedy | ||
}; | ||
} | ||
function parseArguments(source) { | ||
let capture; | ||
const result = []; | ||
while (capture = ANGLED_BRACKET_REGEXP.exec(source)) { | ||
result.push(parseBracket(capture[1], true)); | ||
} | ||
while (capture = SQUARE_BRACKET_REGEXP.exec(source)) { | ||
result.push(parseBracket(capture[1], false)); | ||
} | ||
return result; | ||
} | ||
const supportedType = ["string", "number", "boolean"]; | ||
const quoteStart = `"'“‘`; | ||
const quoteEnd = `"'”’`; | ||
class Command { | ||
constructor(name, declaration, context2, config = {}) { | ||
this.name = name; | ||
this.declaration = declaration; | ||
this.context = context2; | ||
this.children = []; | ||
this.parent = null; | ||
this._aliases = []; | ||
this._options = {}; | ||
this._optionNameMap = {}; | ||
this._optionSymbolMap = {}; | ||
this._userFields = []; | ||
this._groupFields = []; | ||
if (!name) | ||
throw new Error("expect a command name"); | ||
this._arguments = parseArguments(declaration); | ||
this.config = {...Command.defaultConfig, ...config}; | ||
this._registerAlias(this.name); | ||
context2.app._commands.push(this); | ||
context2.app.emit("new-command", this); | ||
} | ||
static userFields(fields) { | ||
this._userFields.push(fields); | ||
return this; | ||
} | ||
static groupFields(fields) { | ||
this._groupFields.push(fields); | ||
return this; | ||
} | ||
static collect(argv, key, fields = new Set()) { | ||
if (!argv) | ||
return; | ||
const values = [ | ||
...this[`_${key}Fields`], | ||
...argv.command[`_${key}Fields`] | ||
]; | ||
for (const value of values) { | ||
if (typeof value === "function") { | ||
value(argv, fields); | ||
continue; | ||
} | ||
for (const field of value) { | ||
fields.add(field); | ||
} | ||
} | ||
return fields; | ||
} | ||
get app() { | ||
return this.context.app; | ||
} | ||
_registerAlias(name) { | ||
name = name.toLowerCase(); | ||
this._aliases.push(name); | ||
const previous = this.app._commandMap[name]; | ||
if (!previous) { | ||
this.app._commandMap[name] = this; | ||
} else if (previous !== this) { | ||
throw new Error(util.format('duplicate command names: "%s"', name)); | ||
} | ||
} | ||
[util.inspect.custom]() { | ||
return `Command <${this.name}>`; | ||
} | ||
userFields(fields) { | ||
this._userFields.push(fields); | ||
return this; | ||
} | ||
groupFields(fields) { | ||
this._groupFields.push(fields); | ||
return this; | ||
} | ||
alias(...names) { | ||
for (const name of names) { | ||
this._registerAlias(name); | ||
} | ||
return this; | ||
} | ||
subcommand(rawName, ...args) { | ||
rawName = this.name + (rawName.charCodeAt(0) === 46 ? "" : "/") + rawName; | ||
return this.context.command(rawName, ...args); | ||
} | ||
_registerOption(name, def, config) { | ||
const param = 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 option = this._options[name] || (this._options[name] = { | ||
...Command.defaultOptionConfig, | ||
...config, | ||
name, | ||
values: {}, | ||
description: syntax + " " + bracket + desc, | ||
greedy: bracket.includes("...") | ||
}); | ||
if ("value" in config) { | ||
names.forEach((name2) => option.values[name2] = config.value); | ||
} else if (!bracket.trim()) { | ||
option.type = "boolean"; | ||
} | ||
this._assignOption(option, names, this._optionNameMap); | ||
this._assignOption(option, symbols, this._optionSymbolMap); | ||
if (!this._optionNameMap[param]) { | ||
this._optionNameMap[param] = option; | ||
} | ||
return this; | ||
} | ||
_assignOption(option, names, optionMap) { | ||
for (const name of names) { | ||
if (name in optionMap) { | ||
throw new Error(util.format('duplicate option names: "%s"', name)); | ||
} | ||
optionMap[name] = option; | ||
} | ||
} | ||
option(name, desc, config = {}) { | ||
const fallbackType = typeof config.fallback; | ||
const type = config["type"] || supportedType.includes(fallbackType) && fallbackType; | ||
return this._registerOption(name, desc, {...config, type}); | ||
} | ||
removeOption(name) { | ||
if (!this._options[name]) | ||
return false; | ||
const option = this._options[name]; | ||
delete this._options[name]; | ||
for (const key in this._optionNameMap) { | ||
if (this._optionNameMap[key] === option) { | ||
delete this._optionNameMap[key]; | ||
} | ||
} | ||
for (const key in this._optionSymbolMap) { | ||
if (this._optionSymbolMap[key] === option) { | ||
delete this._optionSymbolMap[key]; | ||
} | ||
} | ||
return true; | ||
} | ||
action(callback) { | ||
this._action = callback; | ||
return this; | ||
} | ||
parseArg(source, terminator) { | ||
const index = quoteStart.indexOf(source[0]); | ||
if (index >= 0) { | ||
const capture = new RegExp(`${quoteEnd[index]}(?=[\\s${terminator}]|$)`).exec(source.slice(1)); | ||
if (capture) { | ||
return { | ||
quoted: true, | ||
content: source.slice(1, 1 + capture.index), | ||
rest: source.slice(2 + capture.index).trimLeft() | ||
}; | ||
} | ||
} | ||
const [content] = source.split(new RegExp(`[\\s${terminator}]`), 1); | ||
return { | ||
content, | ||
quoted: false, | ||
rest: source.slice(content.length).trimLeft() | ||
}; | ||
} | ||
parseRest(source, terminator) { | ||
const index = quoteStart.indexOf(source[0]); | ||
if (index >= 0) { | ||
const capture = terminator ? new RegExp(`${quoteEnd[index]}(?=[${terminator}]|$)`).exec(source.slice(1)) : new RegExp(`${quoteEnd[index]}(?=$)`).exec(source.slice(1)); | ||
if (capture) { | ||
return { | ||
quoted: true, | ||
content: source.slice(1, 1 + capture.index), | ||
rest: source.slice(2 + capture.index).trimLeft() | ||
}; | ||
} | ||
} | ||
const [content] = terminator ? source.split(new RegExp(`[${terminator}]`), 1) : [source]; | ||
return { | ||
content, | ||
quoted: false, | ||
rest: source.slice(content.length).trimLeft() | ||
}; | ||
} | ||
parseValue(source, quoted, {type, fallback} = {}) { | ||
if (source === "" && quoted) | ||
return ""; | ||
if (source === true || source === "") { | ||
if (fallback !== void 0) | ||
return fallback; | ||
if (type === "string") | ||
return ""; | ||
return true; | ||
} | ||
if (type === "number") | ||
return +source; | ||
if (type === "string") | ||
return source; | ||
const n = +source; | ||
return n * 0 === 0 ? n : source; | ||
} | ||
parse(message4, terminator = "") { | ||
var _a; | ||
let rest = ""; | ||
terminator = koishi_utils.escapeRegExp(terminator); | ||
const source = `${this.name} ${message4}`; | ||
const args = []; | ||
const options = {}; | ||
const handleOption = (name, value) => { | ||
const config = this._optionNameMap[name]; | ||
if (config) { | ||
options[config.name] = name in config.values ? config.values[name] : value; | ||
} else { | ||
options[koishi_utils.camelCase(name)] = value; | ||
} | ||
}; | ||
while (message4) { | ||
if (terminator.includes(message4[0])) { | ||
rest = message4; | ||
break; | ||
} | ||
if (message4[0] !== "-" && ((_a = this._arguments[args.length]) == null ? void 0 : _a.greedy)) { | ||
const arg02 = this.parseRest(message4, terminator); | ||
args.push(arg02.content); | ||
rest = arg02.rest; | ||
break; | ||
} | ||
let arg0 = this.parseArg(message4, terminator); | ||
const arg = arg0.content; | ||
message4 = arg0.rest; | ||
let option; | ||
let names; | ||
let param; | ||
if (!arg0.quoted && (option = this._optionSymbolMap[arg])) { | ||
names = [koishi_utils.paramCase(option.name)]; | ||
} else { | ||
if (arg[0] !== "-" || arg0.quoted) { | ||
args.push(arg); | ||
continue; | ||
} | ||
let i = 0; | ||
let name; | ||
for (; i < arg.length; ++i) { | ||
if (arg.charCodeAt(i) !== 45) | ||
break; | ||
} | ||
if (arg.slice(i, i + 3) === "no-" && !this._optionNameMap[arg.slice(i)]) { | ||
name = arg.slice(i + 3); | ||
handleOption(name, false); | ||
continue; | ||
} | ||
let j = i + 1; | ||
for (; j < arg.length; j++) { | ||
if (arg.charCodeAt(j) === 61) | ||
break; | ||
} | ||
name = arg.slice(i, j); | ||
names = i > 1 ? [name] : name; | ||
param = arg.slice(++j); | ||
option = this._optionNameMap[names[names.length - 1]]; | ||
} | ||
let quoted = false; | ||
if (!param) { | ||
const {greedy, type} = option || {}; | ||
if (greedy) { | ||
arg0 = this.parseRest(arg0.rest, terminator); | ||
param = arg0.content; | ||
quoted = arg0.quoted; | ||
rest = arg0.rest; | ||
message4 = ""; | ||
} else if (type !== "boolean" && (type || message4[0] !== "-")) { | ||
arg0 = this.parseArg(message4, terminator); | ||
param = arg0.content; | ||
quoted = arg0.quoted; | ||
message4 = arg0.rest; | ||
} | ||
} | ||
for (let j = 0; j < names.length; j++) { | ||
const name = names[j]; | ||
const config = this._optionNameMap[name]; | ||
const value = this.parseValue(j + 1 < names.length || param, quoted, config); | ||
handleOption(name, value); | ||
} | ||
} | ||
for (const {name, fallback} of Object.values(this._options)) { | ||
if (fallback !== void 0 && !(name in options)) { | ||
options[name] = fallback; | ||
} | ||
} | ||
return {rest, options, args, source}; | ||
} | ||
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; | ||
} | ||
async execute(argv) { | ||
argv.command = this; | ||
if (!argv.options) | ||
argv.options = {}; | ||
if (!argv.args) | ||
argv.args = []; | ||
if (!argv.rest) | ||
argv.rest = ""; | ||
let state = "before command"; | ||
const {next = koishi_utils.noop} = argv; | ||
argv.next = async (fallback) => { | ||
const oldState = state; | ||
state = ""; | ||
await next(fallback); | ||
state = oldState; | ||
}; | ||
let {args, options, session: session3, source} = argv; | ||
const getSource = () => source || (source = this.stringify(args, options)); | ||
if (logger.level >= 3) | ||
logger.debug(getSource()); | ||
const lastCall = this.app.options.prettyErrors && new Error().stack.split("\n", 4)[3]; | ||
try { | ||
const result = await this.app.serial(session3, "before-command", argv); | ||
if (typeof result === "string") | ||
return session3.$send(result); | ||
state = "executing command"; | ||
const message4 = await this._action(argv, ...args); | ||
if (message4) | ||
await session3.$send(message4); | ||
state = "after command"; | ||
await this.app.serial(session3, "command", argv); | ||
} catch (error) { | ||
if (!state) | ||
throw error; | ||
let stack = koishi_utils.coerce(error); | ||
if (lastCall) { | ||
const index = error.stack.indexOf(lastCall); | ||
stack = stack.slice(0, index - 1); | ||
} | ||
logger.warn(`${state}: ${getSource()} | ||
${stack}`); | ||
} | ||
} | ||
dispose() { | ||
for (const cmd of this.children) { | ||
cmd.dispose(); | ||
} | ||
this.context.emit("remove-command", this); | ||
this._aliases.forEach((name) => delete this.app._commandMap[name]); | ||
const index = this.app._commands.indexOf(this); | ||
this.app._commands.splice(index, 1); | ||
if (this.parent) { | ||
const index2 = this.parent.children.indexOf(this); | ||
this.parent.children.splice(index2, 1); | ||
} | ||
} | ||
} | ||
Command.defaultConfig = {}; | ||
Command.defaultOptionConfig = {}; | ||
Command._userFields = []; | ||
Command._groupFields = []; | ||
// packages/koishi-core/src/database.ts | ||
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", 0))); | ||
} | ||
User3.extend = extend; | ||
extend((type, id, authority) => ({ | ||
[type]: id, | ||
authority, | ||
flag: 0, | ||
usage: {}, | ||
timers: {} | ||
})); | ||
function create(type, id, authority) { | ||
const result = {}; | ||
for (const getter of getters) { | ||
Object.assign(result, getter(type, id, authority)); | ||
} | ||
return result; | ||
} | ||
User3.create = create; | ||
})(User || (User = {})); | ||
var Group; | ||
(function(Group2) { | ||
let Flag; | ||
(function(Flag2) { | ||
Flag2[Flag2["ignore"] = 1] = "ignore"; | ||
Flag2[Flag2["silent"] = 4] = "silent"; | ||
})(Flag = Group2.Flag || (Group2.Flag = {})); | ||
Group2.fields = []; | ||
const getters = []; | ||
function extend(getter) { | ||
getters.push(getter); | ||
Group2.fields.push(...Object.keys(getter(null, "0", "0"))); | ||
} | ||
Group2.extend = extend; | ||
function create(type, id, assignee) { | ||
const result = {}; | ||
for (const getter of getters) { | ||
Object.assign(result, getter(type, id, assignee)); | ||
} | ||
return result; | ||
} | ||
Group2.create = create; | ||
extend((id, assignee) => ({ | ||
id, | ||
assignee, | ||
flag: 0 | ||
})); | ||
})(Group || (Group = {})); | ||
function extendDatabase(module2, extension) { | ||
let Database2; | ||
try { | ||
Database2 = typeof module2 === "string" ? require(module2).default : module2; | ||
} catch (error) { | ||
} | ||
if (typeof extension === "function") { | ||
extension(Database2); | ||
} else { | ||
Object.assign(Database2.prototype, extension); | ||
} | ||
} | ||
// packages/koishi-core/src/context.ts | ||
function joinScope(base, ids) { | ||
const result = !ids.length ? [...base] : base.positive ? koishi_utils2.intersection(ids, base) : koishi_utils2.difference(ids, base); | ||
result.positive = !ids.length ? base.positive : true; | ||
return result; | ||
} | ||
function matchScope(base, id) { | ||
return !id || !(base.positive ^ base.includes(id)); | ||
} | ||
function isBailed(value) { | ||
return value !== null && value !== false && value !== void 0; | ||
} | ||
class Context { | ||
constructor(scope, app2) { | ||
this.scope = scope; | ||
this.app = app2; | ||
koishi_utils2.defineProperty(this, "_disposables", []); | ||
} | ||
get database() { | ||
return this.app._database; | ||
} | ||
set database(database4) { | ||
if (this.app._database && this.app._database !== database4) { | ||
this.logger("app").warn("ctx.database is overwritten"); | ||
} | ||
this.app._database = database4; | ||
} | ||
logger(name) { | ||
return new koishi_utils2.Logger(name); | ||
} | ||
group(...ids) { | ||
const scope = {...this.scope}; | ||
scope.groups = joinScope(scope.groups, ids); | ||
scope.private = false; | ||
return new Context(scope, this.app); | ||
} | ||
user(...ids) { | ||
const scope = {...this.scope}; | ||
scope.users = joinScope(scope.users, ids); | ||
return new Context(scope, this.app); | ||
} | ||
private(...ids) { | ||
const scope = {...this.scope}; | ||
scope.users = joinScope(scope.users, ids); | ||
scope.groups = []; | ||
scope.groups.positive = true; | ||
return new Context(scope, this.app); | ||
} | ||
match(session3) { | ||
if (!session3) | ||
return true; | ||
return matchScope(this.scope.groups, session3.groupId) && matchScope(this.scope.users, session3.userId) && (this.scope.private || session3.subType !== "private"); | ||
} | ||
plugin(plugin, options) { | ||
if (options === false) | ||
return; | ||
if (options === true) | ||
options = void 0; | ||
const ctx = Object.create(this); | ||
koishi_utils2.defineProperty(ctx, "_disposables", []); | ||
if (typeof plugin === "function") { | ||
plugin(ctx, options); | ||
} else if (plugin && typeof plugin === "object" && typeof plugin.apply === "function") { | ||
plugin.apply(ctx, options); | ||
} else { | ||
throw new Error('invalid plugin, expect function or object with an "apply" method'); | ||
} | ||
this._disposables.push(() => ctx.dispose()); | ||
return this; | ||
} | ||
async parallel(...args) { | ||
const tasks = []; | ||
const session3 = typeof args[0] === "object" ? args.shift() : null; | ||
const name = args.shift(); | ||
this.logger("dispatch").debug(name); | ||
for (const [context2, callback] of this.app._hooks[name] || []) { | ||
if (!context2.match(session3)) | ||
continue; | ||
tasks.push(callback.apply(this, args)); | ||
} | ||
await Promise.all(tasks); | ||
} | ||
emit(...args) { | ||
this.parallel(...args); | ||
} | ||
async serial(...args) { | ||
const session3 = typeof args[0] === "object" ? args.shift() : null; | ||
const name = args.shift(); | ||
this.logger("dispatch").debug(name); | ||
for (const [context2, callback] of this.app._hooks[name] || []) { | ||
if (!context2.match(session3)) | ||
continue; | ||
const result = await callback.apply(this, args); | ||
if (isBailed(result)) | ||
return result; | ||
} | ||
} | ||
bail(...args) { | ||
const session3 = typeof args[0] === "object" ? args.shift() : null; | ||
const name = args.shift(); | ||
this.logger("dispatch").debug(name); | ||
for (const [context2, callback] of this.app._hooks[name] || []) { | ||
if (!context2.match(session3)) | ||
continue; | ||
const result = callback.apply(this, args); | ||
if (isBailed(result)) | ||
return result; | ||
} | ||
} | ||
getHooks(name) { | ||
const hooks = this.app._hooks[name] || (this.app._hooks[name] = []); | ||
if (hooks.length >= this.app.options.maxListeners) { | ||
this.logger("app").warn('max listener count (%d) for event "%s" exceeded, which may be caused by a memory leak', this.app.options.maxListeners, name); | ||
} | ||
return hooks; | ||
} | ||
on(name, listener) { | ||
return this.addListener(name, listener); | ||
} | ||
addListener(name, listener) { | ||
this.getHooks(name).push([this, listener]); | ||
const dispose = () => this.removeListener(name, listener); | ||
this._disposables.push(name === "dispose" ? listener : dispose); | ||
return dispose; | ||
} | ||
prependListener(name, listener) { | ||
this.getHooks(name).unshift([this, listener]); | ||
const dispose = () => this.removeListener(name, listener); | ||
this._disposables.push(name === "dispose" ? listener : dispose); | ||
return dispose; | ||
} | ||
once(name, listener) { | ||
const dispose = this.addListener(name, (...args) => { | ||
dispose(); | ||
return listener.apply(this, args); | ||
}); | ||
return dispose; | ||
} | ||
off(name, listener) { | ||
return this.removeListener(name, listener); | ||
} | ||
removeListener(name, listener) { | ||
const index = (this.app._hooks[name] || []).findIndex(([context2, callback]) => context2 === this && callback === listener); | ||
if (index >= 0) { | ||
this.app._hooks[name].splice(index, 1); | ||
return true; | ||
} | ||
} | ||
middleware(middleware) { | ||
return this.addListener(Context.MIDDLEWARE_EVENT, middleware); | ||
} | ||
addMiddleware(middleware) { | ||
return this.addListener(Context.MIDDLEWARE_EVENT, middleware); | ||
} | ||
prependMiddleware(middleware) { | ||
return this.prependListener(Context.MIDDLEWARE_EVENT, middleware); | ||
} | ||
removeMiddleware(middleware) { | ||
return this.removeListener(Context.MIDDLEWARE_EVENT, middleware); | ||
} | ||
command(rawName, ...args) { | ||
const description = typeof args[0] === "string" ? args.shift() : void 0; | ||
const config = args[0] || {}; | ||
if (description !== void 0) | ||
config.description = description; | ||
const [path] = rawName.split(" ", 1); | ||
const declaration = rawName.slice(path.length); | ||
const segments = path.toLowerCase().split(/(?=[\\./])/); | ||
let parent = null; | ||
segments.forEach((segment) => { | ||
const code = segment.charCodeAt(0); | ||
const name = code === 46 ? parent.name + segment : code === 47 ? segment.slice(1) : segment; | ||
let command6 = this.app._commandMap[name]; | ||
if (command6) { | ||
if (parent) { | ||
if (command6 === parent) { | ||
throw new Error("cannot set a command as its own subcommand"); | ||
} | ||
if (command6.parent) { | ||
if (command6.parent !== parent) { | ||
throw new Error("already has subcommand"); | ||
} | ||
} else { | ||
command6.parent = parent; | ||
parent.children.push(command6); | ||
} | ||
} | ||
return parent = command6; | ||
} | ||
command6 = new Command(name, declaration, this); | ||
if (parent) { | ||
command6.parent = parent; | ||
parent.children.push(command6); | ||
} | ||
parent = command6; | ||
}); | ||
Object.assign(parent.config, config); | ||
this._disposables.push(() => parent.dispose()); | ||
return parent; | ||
} | ||
async broadcast(...args) { | ||
let groups; | ||
if (Array.isArray(args[0])) | ||
groups = args.shift(); | ||
const [message4, forced] = args; | ||
if (!message4) | ||
return []; | ||
const data = await this.database.getAllGroups(["id", "type", "assignee", "flag"]); | ||
const assignMap = {}; | ||
for (const {id, assignee, flag} of data) { | ||
if (groups && !groups.includes(id)) | ||
continue; | ||
if (!forced && flag & Group.Flag.silent) | ||
continue; | ||
if (assignMap[assignee]) { | ||
assignMap[assignee].push(id); | ||
} else { | ||
assignMap[assignee] = [id]; | ||
} | ||
} | ||
return (await Promise.all(Object.entries(assignMap).map(async ([id, groups2]) => { | ||
return await this.app.bots[+id].broadcast(groups2, message4); | ||
}))).flat(1); | ||
} | ||
dispose() { | ||
this._disposables.forEach((dispose) => dispose()); | ||
this._disposables = []; | ||
} | ||
} | ||
Context.MIDDLEWARE_EVENT = Symbol("mid"); | ||
// packages/koishi-core/src/server.ts | ||
const koishi_utils4 = __toModule(require("koishi-utils")); | ||
// packages/koishi-core/src/session.ts | ||
const koishi_utils3 = __toModule(require("koishi-utils")); | ||
const lru_cache = __toModule(require("lru-cache")); | ||
const logger2 = new koishi_utils3.Logger("session"); | ||
class Session { | ||
constructor(app2, session3) { | ||
this.$prefix = null; | ||
this.$uuid = koishi_utils3.Random.uuid(); | ||
koishi_utils3.defineProperty(this, "$app", app2); | ||
koishi_utils3.defineProperty(this, "_queued", Promise.resolve()); | ||
koishi_utils3.defineProperty(this, "_hooks", []); | ||
Object.assign(this, session3); | ||
} | ||
toJSON() { | ||
return Object.fromEntries(Object.entries(this).filter(([key]) => { | ||
return !key.startsWith("_") && !key.startsWith("$"); | ||
})); | ||
} | ||
get $bot() { | ||
return this.$app.bots[this.selfId]; | ||
} | ||
get $username() { | ||
const idString = "" + this.userId; | ||
return this.$user && this.$user["name"] && idString !== this.$user["name"] ? this.$user["name"] : this.anonymous ? this.anonymous.name : this.sender ? this.sender.card || this.sender.nickname : idString; | ||
} | ||
async $send(message4) { | ||
if (this.$bot[Bot.$send]) { | ||
return this.$bot[Bot.$send](this, message4); | ||
} | ||
if (!message4) | ||
return; | ||
await this.$bot.sendMessage(this.channelId, message4); | ||
} | ||
$cancelQueued(delay = 0) { | ||
this._hooks.forEach(Reflect.apply); | ||
this._delay = delay; | ||
} | ||
async $sendQueued(message4, delay) { | ||
if (!message4) | ||
return; | ||
if (typeof delay === "undefined") { | ||
const {queueDelay = 100} = this.$app.options; | ||
delay = typeof queueDelay === "function" ? queueDelay(message4, this) : queueDelay; | ||
} | ||
return this._queued = this._queued.then(() => new Promise((resolve) => { | ||
const hook = () => { | ||
resolve(); | ||
clearTimeout(timer); | ||
const index = this._hooks.indexOf(hook); | ||
if (index >= 0) | ||
this._hooks.splice(index, 1); | ||
}; | ||
this._hooks.push(hook); | ||
const timer = setTimeout(async () => { | ||
await this.$send(message4); | ||
this._delay = delay; | ||
hook(); | ||
}, this._delay || 0); | ||
})); | ||
} | ||
async $getGroup(id = this.groupId, fields = [], assignee) { | ||
const group = await this.$app.database.getGroup(this.kind, id, fields); | ||
if (group) | ||
return group; | ||
const fallback = Group.create(this.kind, id, assignee); | ||
if (assignee) { | ||
await this.$app.database.setGroup(this.kind, id, fallback); | ||
} | ||
return fallback; | ||
} | ||
async $observeGroup(fields = []) { | ||
const fieldSet = new Set(fields); | ||
const {kind, groupId, $argv, $group} = this; | ||
if ($argv) | ||
Command.collect($argv, "group", fieldSet); | ||
const identifier = `${kind}:${groupId}`; | ||
if ($group) { | ||
for (const key in $group) { | ||
fieldSet.delete(key); | ||
} | ||
if (fieldSet.size) { | ||
const data2 = await this.$getGroup(groupId, [...fieldSet]); | ||
this.$app._groupCache.set(identifier, $group._merge(data2)); | ||
} | ||
return $group; | ||
} | ||
const cache = this.$app._groupCache.get(identifier); | ||
const fieldArray = [...fieldSet]; | ||
const hasActiveCache = cache && koishi_utils3.contain(Object.keys(cache), fieldArray); | ||
if (hasActiveCache) | ||
return this.$group = cache; | ||
const data = await this.$getGroup(groupId, fieldArray); | ||
const group = koishi_utils3.observe(data, (diff) => this.$app.database.setGroup(kind, groupId, diff), `group ${groupId}`); | ||
this.$app._groupCache.set(identifier, group); | ||
return this.$group = group; | ||
} | ||
async $getUser(id = this.userId, fields = [], authority = 0) { | ||
const user = await this.$app.database.getUser(this.kind, id, fields); | ||
if (user) | ||
return user; | ||
const fallback = User.create(this.kind, id, authority); | ||
if (authority) { | ||
await this.$app.database.setUser(this.kind, id, fallback); | ||
} | ||
return fallback; | ||
} | ||
async $observeUser(fields = []) { | ||
const fieldSet = new Set(fields); | ||
const {userId, $argv, $user} = this; | ||
if ($argv) | ||
Command.collect($argv, "user", fieldSet); | ||
let userCache = this.$app._userCache[this.kind]; | ||
if (!userCache) { | ||
userCache = this.$app._userCache[this.kind] = new lru_cache.default({ | ||
max: this.$app.options.userCacheLength, | ||
maxAge: this.$app.options.userCacheAge | ||
}); | ||
} | ||
if ($user && !this.anonymous) { | ||
for (const key in $user) { | ||
fieldSet.delete(key); | ||
} | ||
if (fieldSet.size) { | ||
const data2 = await this.$getUser(userId, [...fieldSet]); | ||
userCache.set(userId, $user._merge(data2)); | ||
} | ||
} | ||
if ($user) | ||
return $user; | ||
const defaultAuthority = typeof this.$app.options.defaultAuthority === "function" ? this.$app.options.defaultAuthority(this) : this.$app.options.defaultAuthority || 0; | ||
if (this.anonymous) { | ||
const user2 = koishi_utils3.observe(User.create(this.kind, userId, defaultAuthority), () => Promise.resolve()); | ||
return this.$user = user2; | ||
} | ||
const cache = userCache.get(userId); | ||
const fieldArray = [...fieldSet]; | ||
const hasActiveCache = cache && koishi_utils3.contain(Object.keys(cache), fieldArray); | ||
if (hasActiveCache) | ||
return this.$user = cache; | ||
const data = await this.$getUser(userId, fieldArray, defaultAuthority); | ||
const user = koishi_utils3.observe(data, (diff) => this.$app.database.setUser(this.kind, userId, diff), `user ${userId}`); | ||
userCache.set(userId, user); | ||
return this.$user = user; | ||
} | ||
$resolve(argv) { | ||
if (typeof argv.command === "string") { | ||
argv.command = this.$app._commandMap[argv.command]; | ||
} | ||
if (!argv.command) { | ||
logger2.warn(new Error(`cannot find command ${argv}`)); | ||
return; | ||
} | ||
if (!argv.command.context.match(this)) | ||
return; | ||
return {session: this, ...argv}; | ||
} | ||
$parse(message4, terminator = "", builtin = false) { | ||
if (!message4) | ||
return; | ||
const argv = this.$app.bail(this, "parse", message4, this, builtin, terminator); | ||
return argv && this.$resolve(argv); | ||
} | ||
async $execute(...args) { | ||
let argv, next; | ||
if (typeof args[0] === "string") { | ||
next = args[1] || koishi_utils3.noop; | ||
argv = this.$parse(args[0]); | ||
} else { | ||
next = args[0].next || koishi_utils3.noop; | ||
argv = this.$resolve(args[0]); | ||
} | ||
if (!argv) | ||
return next(); | ||
argv.next = next; | ||
argv.session = this; | ||
this.$argv = argv; | ||
if (this.$app.database) { | ||
if (this.subType === "group") { | ||
await this.$observeGroup(); | ||
} | ||
await this.$observeUser(); | ||
} | ||
return argv.command.execute(argv); | ||
} | ||
} | ||
function getTargetId(target) { | ||
if (typeof target !== "string" && typeof target !== "number") | ||
return; | ||
let qq = +target; | ||
if (!qq) { | ||
const capture = /\[CQ:at,qq=(\d+)\]/.exec(target); | ||
if (capture) | ||
qq = +capture[1]; | ||
} | ||
if (!koishi_utils3.isInteger(qq)) | ||
return; | ||
return qq; | ||
} | ||
// packages/koishi-core/src/server.ts | ||
class Server { | ||
constructor(app2, BotStatic) { | ||
this.app = app2; | ||
this.BotStatic = BotStatic; | ||
this.bots = []; | ||
} | ||
create(options) { | ||
const bot = new this.BotStatic(this.app, options); | ||
this.bots.push(bot); | ||
this.app.bots[bot.selfId] = bot; | ||
} | ||
dispatch(session3) { | ||
if (this.app.status !== AppStatus.open) | ||
return; | ||
const events = []; | ||
events.push(session3.eventType); | ||
if (session3.subType) { | ||
events.unshift(events[0] + "/" + session3.subType); | ||
} | ||
for (const event of events) { | ||
this.app.emit(session3, koishi_utils4.paramCase(event), session3); | ||
} | ||
} | ||
} | ||
Server.types = {}; | ||
var BotStatusCode; | ||
(function(BotStatusCode2) { | ||
BotStatusCode2[BotStatusCode2["GOOD"] = 0] = "GOOD"; | ||
BotStatusCode2[BotStatusCode2["BOT_IDLE"] = 1] = "BOT_IDLE"; | ||
BotStatusCode2[BotStatusCode2["BOT_OFFLINE"] = 2] = "BOT_OFFLINE"; | ||
BotStatusCode2[BotStatusCode2["NET_ERROR"] = 3] = "NET_ERROR"; | ||
BotStatusCode2[BotStatusCode2["SERVER_ERROR"] = 4] = "SERVER_ERROR"; | ||
})(BotStatusCode || (BotStatusCode = {})); | ||
class Bot { | ||
constructor(app2, options) { | ||
this.app = app2; | ||
Object.assign(this, options); | ||
} | ||
createSession(subType, ctxType, ctxId, message4) { | ||
return new Session(this.app, { | ||
message: message4, | ||
subType, | ||
eventType: "send", | ||
selfId: this.selfId, | ||
[ctxType + "Id"]: ctxId, | ||
time: Math.round(Date.now() / 1e3) | ||
}); | ||
} | ||
async getGroupMemberMap(groupId) { | ||
const list = await this.getGroupMemberList(groupId); | ||
return Object.fromEntries(list.map((info) => [info.id, info.nick || info.name])); | ||
} | ||
async broadcast(channels, message4, delay = this.app.options.broadcastDelay) { | ||
const messageIds = []; | ||
for (let index = 0; index < channels.length; index++) { | ||
if (index && delay) | ||
await koishi_utils4.sleep(delay); | ||
try { | ||
messageIds.push(await this.sendMessage(channels[index], message4)); | ||
} catch (error) { | ||
this.app.logger("bot").warn(error); | ||
} | ||
} | ||
return messageIds; | ||
} | ||
} | ||
Bot.$send = Symbol.for("koishi.send"); | ||
// packages/koishi-core/src/plugins/validate.ts | ||
const util2 = __toModule(require("util")); | ||
const koishi_utils5 = __toModule(require("koishi-utils")); | ||
// packages/koishi-core/src/plugins/message.ts | ||
var Message; | ||
(function(Message2) { | ||
Message2.LOW_AUTHORITY = "权限不足。"; | ||
Message2.TOO_FREQUENT = "调用过于频繁,请稍后再试。"; | ||
Message2.INSUFFICIENT_ARGUMENTS = "缺少参数,请检查指令语法。"; | ||
Message2.REDUNANT_ARGUMENTS = "存在多余参数,请检查指令语法。"; | ||
Message2.INVALID_OPTION = "选项 %s 输入无效,%s"; | ||
Message2.UNKNOWN_OPTIONS = "存在未知选项 %s,请检查指令语法。"; | ||
Message2.CHECK_SYNTAX = "请检查指令语法。"; | ||
Message2.SHOW_THIS_MESSAGE = "显示本信息"; | ||
Message2.USAGE_EXHAUSTED = "调用次数已达上限。"; | ||
Message2.SUGGESTION = "你要找的是不是%s?"; | ||
Message2.COMMAND_SUGGEST_PREFIX = ""; | ||
Message2.COMMAND_SUGGEST_SUFFIX = "发送空行或句号以调用推测的指令。"; | ||
Message2.HELP_SUGGEST_PREFIX = "指令未找到。"; | ||
Message2.HELP_SUGGEST_SUFFIX = "发送空行或句号以调用推测的指令。"; | ||
Message2.GLOBAL_HELP_EPILOG = [ | ||
"群聊普通指令可以通过“@我+指令名”的方式进行触发。", | ||
"私聊或全局指令则不需要添加上述前缀,直接输入指令名即可触发。", | ||
"输入“帮助+指令名”查看特定指令的语法和使用示例。" | ||
].join("\n"); | ||
})(Message || (Message = {})); | ||
// packages/koishi-core/src/plugins/validate.ts | ||
function getUsageName(command6) { | ||
return command6.config.usageName || command6.name; | ||
} | ||
Object.assign(Command.defaultConfig, { | ||
authority: 1, | ||
showWarning: true, | ||
maxUsage: Infinity, | ||
minInterval: 0 | ||
}); | ||
Object.assign(Command.defaultOptionConfig, { | ||
authority: 0 | ||
}); | ||
Command.userFields(({command: command6, options = {}}, fields) => { | ||
const {maxUsage, minInterval, authority} = command6.config; | ||
let shouldFetchAuthority = !fields.has("authority") && authority > 0; | ||
let shouldFetchUsage = !!(maxUsage || minInterval); | ||
for (const {name, authority: authority2, notUsage} of Object.values(command6._options)) { | ||
if (name in options) { | ||
if (authority2 > 0) | ||
shouldFetchAuthority = true; | ||
if (notUsage) | ||
shouldFetchUsage = false; | ||
} | ||
} | ||
if (shouldFetchAuthority) | ||
fields.add("authority"); | ||
if (shouldFetchUsage) { | ||
if (maxUsage) | ||
fields.add("usage"); | ||
if (minInterval) | ||
fields.add("timers"); | ||
} | ||
}); | ||
Command.prototype.getConfig = function(key, session3) { | ||
const value = this.config[key]; | ||
return typeof value === "function" ? value(session3.$user) : value; | ||
}; | ||
Command.prototype.before = function(checker) { | ||
this._checkers.push(checker); | ||
return this; | ||
}; | ||
function apply(app2) { | ||
app2.on("new-command", (cmd) => { | ||
cmd._checkers = []; | ||
}); | ||
app2.on("before-command", ({session: session3, args, options, command: command6}) => { | ||
function sendHint(message4, ...param) { | ||
return command6.config.showWarning ? util2.format(message4, ...param) : ""; | ||
} | ||
for (const checker of command6._checkers) { | ||
const result = checker(session3); | ||
if (result) | ||
return sendHint(result === true ? "" : result); | ||
} | ||
if (command6.config.checkArgCount) { | ||
const nextArg = command6._arguments[args.length] || {}; | ||
if (nextArg.required) { | ||
return sendHint(Message.INSUFFICIENT_ARGUMENTS); | ||
} | ||
const finalArg = command6._arguments[command6._arguments.length - 1] || {}; | ||
if (args.length > command6._arguments.length && !finalArg.greedy && !finalArg.variadic) { | ||
return sendHint(Message.REDUNANT_ARGUMENTS); | ||
} | ||
} | ||
if (command6.config.checkUnknown) { | ||
const unknown = Object.keys(options).filter((key) => !command6._options[key]); | ||
if (unknown.length) { | ||
return sendHint(Message.UNKNOWN_OPTIONS, unknown.join(", ")); | ||
} | ||
} | ||
for (const {validate: validate3, name} of Object.values(command6._options)) { | ||
if (!validate3 || !(name in options)) | ||
continue; | ||
const result = typeof validate3 !== "function" ? !validate3.test(options[name]) : validate3(options[name]); | ||
if (result) { | ||
return sendHint(Message.INVALID_OPTION, name, result === true ? Message.CHECK_SYNTAX : result); | ||
} | ||
} | ||
if (!session3.$user) | ||
return; | ||
let isUsage = true; | ||
if (command6.config.authority > session3.$user.authority) { | ||
return sendHint(Message.LOW_AUTHORITY); | ||
} | ||
for (const option of Object.values(command6._options)) { | ||
if (option.name in options) { | ||
if (option.authority > session3.$user.authority) { | ||
return sendHint(Message.LOW_AUTHORITY); | ||
} | ||
if (option.notUsage) | ||
isUsage = false; | ||
} | ||
} | ||
if (isUsage) { | ||
const name = getUsageName(command6); | ||
const minInterval = command6.getConfig("minInterval", session3); | ||
const maxUsage = command6.getConfig("maxUsage", session3); | ||
if (maxUsage < Infinity && checkUsage(name, session3.$user, maxUsage)) { | ||
return sendHint(Message.USAGE_EXHAUSTED); | ||
} | ||
if (minInterval > 0 && checkTimer(name, session3.$user, minInterval)) { | ||
return sendHint(Message.TOO_FREQUENT); | ||
} | ||
} | ||
}); | ||
} | ||
function getUsage(name, user) { | ||
const $date = koishi_utils5.Time.getDateNumber(); | ||
if (user.usage.$date !== $date) { | ||
user.usage = {$date}; | ||
} | ||
return user.usage[name] || 0; | ||
} | ||
function checkUsage(name, user, maxUsage) { | ||
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 + koishi_utils5.Time.day; | ||
} | ||
if (now <= timers[name]) | ||
return true; | ||
if (offset !== void 0) { | ||
timers[name] = now + offset; | ||
} | ||
} | ||
// packages/koishi-core/src/plugins/help.ts | ||
Command.prototype.usage = function(text) { | ||
this._usage = text; | ||
return this; | ||
}; | ||
Command.prototype.example = function(example) { | ||
this._examples.push(example); | ||
return this; | ||
}; | ||
function apply2(app2) { | ||
app2.on("new-command", (cmd) => { | ||
cmd._examples = []; | ||
cmd.option("help", "-h 显示此信息", {hidden: true}); | ||
}); | ||
app2.prependListener("before-command", async ({command: command6, session: session3, options}) => { | ||
if (command6._action && !options["help"]) | ||
return; | ||
await session3.$execute({ | ||
command: "help", | ||
args: [command6.name] | ||
}); | ||
return ""; | ||
}); | ||
const createCollector = (key) => (argv, fields) => { | ||
const {args: [name]} = argv; | ||
const command6 = app2._commandMap[name] || app2._shortcutMap[name]; | ||
if (!command6) | ||
return; | ||
Command.collect({...argv, command: command6, args: [], options: {help: true}}, key, fields); | ||
}; | ||
app2.command("help [command]", "显示帮助信息", {authority: 0}).userFields(["authority"]).userFields(createCollector("user")).groupFields(createCollector("group")).shortcut("帮助", {fuzzy: true}).option("authority", "-a 显示权限设置").option("showHidden", "-H 查看隐藏的选项和指令").action(async ({session: session3, options}, target) => { | ||
if (!target) { | ||
const commands = session3.$app._commands.filter((cmd) => cmd.parent === null); | ||
const output = formatCommands("当前可用的指令有", session3, commands, options); | ||
if (Message.GLOBAL_HELP_EPILOG) | ||
output.push(Message.GLOBAL_HELP_EPILOG); | ||
return output.join("\n"); | ||
} | ||
const command6 = app2._commandMap[target] || app2._shortcutMap[target]; | ||
if (!(command6 == null ? void 0 : command6.context.match(session3))) { | ||
const items = getCommands(session3, app2._commands).flatMap((cmd) => cmd._aliases); | ||
return session3.$suggest({ | ||
target, | ||
items, | ||
prefix: Message.HELP_SUGGEST_PREFIX, | ||
suffix: Message.HELP_SUGGEST_SUFFIX, | ||
async apply(suggestion) { | ||
await this.$observeUser(["authority", "usage", "timers"]); | ||
const output = await showHelp(app2._commandMap[suggestion], this, options); | ||
return session3.$send(output); | ||
} | ||
}); | ||
} | ||
return showHelp(command6, session3, options); | ||
}); | ||
} | ||
function getCommands(session3, commands, showHidden = false) { | ||
const {authority} = session3.$user || {}; | ||
return commands.filter((cmd) => { | ||
return cmd.context.match(session3) && (authority === void 0 || cmd.config.authority <= authority) && (showHidden || !cmd.config.hidden); | ||
}).sort((a, b) => a.name > b.name ? 1 : -1); | ||
} | ||
function formatCommands(prefix, session3, source, options) { | ||
const commands = getCommands(session3, source, options.showHidden); | ||
if (!commands.length) | ||
return []; | ||
let hasSubcommand = false; | ||
const output = commands.map(({name, config, children}) => { | ||
let output2 = " " + name; | ||
if (options.authority) { | ||
output2 += ` (${config.authority}${children.length ? (hasSubcommand = true, "*") : ""})`; | ||
} | ||
output2 += " " + config.description; | ||
return output2; | ||
}); | ||
if (options.authority) { | ||
output.unshift(`${prefix}(括号内为对应的最低权限等级${hasSubcommand ? ",标有星号的表示含有子指令" : ""}):`); | ||
} else { | ||
output.unshift(`${prefix}:`); | ||
} | ||
return output; | ||
} | ||
function getOptions(command6, session3, maxUsage, config) { | ||
if (command6.config.hideOptions && !config.showHidden) | ||
return []; | ||
const options = config.showHidden ? Object.values(command6._options) : Object.values(command6._options).filter((option) => !option.hidden && (!session3.$user || option.authority <= session3.$user.authority)); | ||
if (!options.length) | ||
return []; | ||
const output = config.authority && options.some((o) => o.authority) ? ["可用的选项有(括号内为额外要求的权限等级):"] : ["可用的选项有:"]; | ||
options.forEach((option) => { | ||
const authority = option.authority && config.authority ? `(${option.authority}) ` : ""; | ||
let line = ` ${authority}${option.description}`; | ||
if (option.notUsage && maxUsage !== Infinity) { | ||
line += "(不计入总次数)"; | ||
} | ||
output.push(line); | ||
}); | ||
return output; | ||
} | ||
async function showHelp(command6, session3, config) { | ||
const output = [command6.name + command6.declaration, command6.config.description]; | ||
if (command6.context.database) { | ||
await session3.$observeUser(["authority", "timers", "usage"]); | ||
} | ||
const disabled = command6._checkers.some((checker) => checker(session3)); | ||
if (disabled) | ||
output[1] += "(指令已禁用)"; | ||
if (command6._aliases.length > 1) { | ||
output.push(`别名:${Array.from(command6._aliases.slice(1)).join(",")}。`); | ||
} | ||
const maxUsage = command6.getConfig("maxUsage", session3); | ||
if (!disabled && session3.$user) { | ||
const name = getUsageName(command6); | ||
const minInterval = command6.getConfig("minInterval", session3); | ||
const count = getUsage(name, session3.$user); | ||
if (maxUsage < Infinity) { | ||
output.push(`已调用次数:${Math.min(count, maxUsage)}/${maxUsage}。`); | ||
} | ||
const due = session3.$user.timers[name]; | ||
if (minInterval > 0) { | ||
const nextUsage = due ? (Math.max(0, due - Date.now()) / 1e3).toFixed() : 0; | ||
output.push(`距离下次调用还需:${nextUsage}/${minInterval / 1e3} 秒。`); | ||
} | ||
if (command6.config.authority > 1) { | ||
output.push(`最低权限:${command6.config.authority} 级。`); | ||
} | ||
} | ||
const usage = command6._usage; | ||
if (usage) { | ||
output.push(typeof usage === "string" ? usage : await usage.call(command6, session3)); | ||
} | ||
output.push(...getOptions(command6, session3, maxUsage, config)); | ||
if (command6._examples.length) { | ||
output.push("使用示例:", ...command6._examples.map((example) => " " + example)); | ||
} | ||
output.push(...formatCommands("可用的子指令有", session3, command6.children, config)); | ||
return output.join("\n"); | ||
} | ||
// packages/koishi-core/src/plugins/shortcut.ts | ||
const koishi_utils6 = __toModule(require("koishi-utils")); | ||
Command.prototype.shortcut = function(name, config) { | ||
config = this._shortcuts[name] = { | ||
name, | ||
command: this, | ||
authority: this.config.authority, | ||
...config | ||
}; | ||
this.app._shortcutMap[name] = this; | ||
this.app._shortcuts.push(config); | ||
return this; | ||
}; | ||
function apply3(ctx) { | ||
koishi_utils6.defineProperty(ctx.app, "_shortcuts", []); | ||
koishi_utils6.defineProperty(ctx.app, "_shortcutMap", {}); | ||
ctx.on("new-command", (cmd) => { | ||
cmd._shortcuts = {}; | ||
}); | ||
ctx.on("remove-command", (cmd) => { | ||
for (const name in cmd._shortcuts) { | ||
delete ctx.app._shortcutMap[name]; | ||
const index = ctx.app._shortcuts.indexOf(cmd._shortcuts[name]); | ||
ctx.app._shortcuts.splice(index, 1); | ||
} | ||
}); | ||
ctx.on("parse", (message4, {$reply, $prefix, $appel}, builtin) => { | ||
if (!builtin || $prefix || $reply) | ||
return; | ||
for (const shortcut2 of ctx.app._shortcuts) { | ||
const {name, fuzzy, command: command6, oneArg, prefix, options, args = []} = shortcut2; | ||
if (prefix && !$appel) | ||
continue; | ||
if (!fuzzy && message4 !== name) | ||
continue; | ||
if (message4.startsWith(name)) { | ||
const _message = message4.slice(name.length); | ||
if (fuzzy && !$appel && _message.match(/^\S/)) | ||
continue; | ||
const result = oneArg ? {options: {}, args: [_message.trim()], rest: ""} : command6.parse(_message.trim()); | ||
result.options = {...options, ...result.options}; | ||
result.args.unshift(...args); | ||
return {command: command6, ...result}; | ||
} | ||
} | ||
}); | ||
} | ||
// packages/koishi-core/src/plugins/suggest.ts | ||
const util3 = __toModule(require("util")); | ||
const fastest_levenshtein = __toModule(require("fastest-levenshtein")); | ||
function getSessionId(session3) { | ||
return "" + session3.userId + session3.groupId; | ||
} | ||
Session.prototype.$use = function $use(middleware) { | ||
const identifier = getSessionId(this); | ||
return this.$app.prependMiddleware(async (session3, next) => { | ||
if (identifier && getSessionId(session3) !== identifier) | ||
return next(); | ||
return middleware(session3, next); | ||
}); | ||
}; | ||
Session.prototype.$prompt = function $prompt(timeout = this.$app.options.promptTimeout) { | ||
return new Promise((resolve) => { | ||
const dispose = this.$use((session3) => { | ||
clearTimeout(timer); | ||
dispose(); | ||
resolve(session3.message); | ||
}); | ||
const timer = setTimeout(() => { | ||
dispose(); | ||
resolve(""); | ||
}, timeout); | ||
}); | ||
}; | ||
Session.prototype.$suggest = function $suggest(options) { | ||
const { | ||
target, | ||
items, | ||
prefix = "", | ||
suffix, | ||
apply: apply5, | ||
next = (callback) => callback(), | ||
coefficient = this.$app.options.similarityCoefficient | ||
} = options; | ||
let suggestions, minDistance = Infinity; | ||
for (const name of items) { | ||
const dist = fastest_levenshtein.distance(name, target); | ||
if (name.length <= 2 || dist > name.length * coefficient) | ||
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 message4 = prefix + util3.format(Message.SUGGESTION, suggestions.map((name) => `“${name}”`).join("或")); | ||
if (suggestions.length > 1) | ||
return this.$send(message4); | ||
const dispose = this.$use((session3, next2) => { | ||
dispose(); | ||
const message5 = session3.message.trim(); | ||
if (message5 && message5 !== "." && message5 !== "。") | ||
return next2(); | ||
return apply5.call(session3, suggestions[0], next2); | ||
}); | ||
return this.$send(message4 + suffix); | ||
}); | ||
}; | ||
function apply4(ctx) { | ||
ctx.middleware((session3, next) => { | ||
const {$argv, $reply, $parsed, $prefix, $appel, subType} = session3; | ||
if ($argv || subType !== "private" && $prefix === null && !$appel) | ||
return next(); | ||
const target = $parsed.split(/\s/, 1)[0].toLowerCase(); | ||
if (!target) | ||
return next(); | ||
const items = getCommands(session3, ctx.app._commands).flatMap((cmd) => cmd._aliases); | ||
return session3.$suggest({ | ||
target, | ||
next, | ||
items, | ||
prefix: Message.COMMAND_SUGGEST_PREFIX, | ||
suffix: Message.COMMAND_SUGGEST_SUFFIX, | ||
async apply(suggestion, next2) { | ||
const newMessage = suggestion + $parsed.slice(target.length) + ($reply ? " " + $reply.content : ""); | ||
return this.$execute(newMessage, next2); | ||
} | ||
}); | ||
}); | ||
} | ||
// packages/koishi-core/src/app.ts | ||
const lru_cache2 = __toModule(require("lru-cache")); | ||
const http = __toModule(require("http")); | ||
function createLeadingRE(patterns, prefix = "", suffix = "") { | ||
return patterns.length ? new RegExp(`^${prefix}(${patterns.map(koishi_utils7.escapeRegExp).join("|")})${suffix}`) : /$^/; | ||
} | ||
var AppStatus; | ||
(function(AppStatus2) { | ||
AppStatus2[AppStatus2["closed"] = 0] = "closed"; | ||
AppStatus2[AppStatus2["opening"] = 1] = "opening"; | ||
AppStatus2[AppStatus2["open"] = 2] = "open"; | ||
AppStatus2[AppStatus2["closing"] = 3] = "closing"; | ||
})(AppStatus || (AppStatus = {})); | ||
class App2 extends Context { | ||
constructor(options = {}) { | ||
super({groups: [], users: [], private: true}); | ||
this.app = this; | ||
this.bots = {}; | ||
this.servers = {}; | ||
this.status = 0; | ||
this._sessions = {}; | ||
options = this.options = {...App2.defaultConfig, ...options}; | ||
if (!options.bots) | ||
options.bots = [options]; | ||
koishi_utils7.defineProperty(this, "_hooks", {}); | ||
koishi_utils7.defineProperty(this, "_commands", []); | ||
koishi_utils7.defineProperty(this, "_commandMap", {}); | ||
koishi_utils7.defineProperty(this, "_userCache", {}); | ||
koishi_utils7.defineProperty(this, "_groupCache", new lru_cache2.default({ | ||
max: options.groupCacheLength, | ||
maxAge: options.groupCacheAge | ||
})); | ||
if (options.port) | ||
this.createServer(); | ||
for (const bot of options.bots) { | ||
let server3 = this.servers[bot.type]; | ||
if (!server3) { | ||
const constructor = Server.types[bot.type]; | ||
if (!constructor) { | ||
throw new Error(`unsupported type "${bot.type}", you should import the adapter yourself`); | ||
} | ||
server3 = this.servers[bot.type] = Reflect.construct(constructor, [this]); | ||
} | ||
server3.create(bot); | ||
} | ||
this.prepare(); | ||
this.middleware(this._preprocess.bind(this)); | ||
this.on("message", this._receive.bind(this)); | ||
this.on("parse", this._parse.bind(this)); | ||
this.on("before-connect", this._listen.bind(this)); | ||
this.on("before-disconnect", this._close.bind(this)); | ||
this.plugin(apply); | ||
this.plugin(apply4); | ||
this.plugin(apply3); | ||
this.plugin(apply2); | ||
} | ||
createServer() { | ||
const koa = new (require("koa"))(); | ||
this.router = new (require("koa-router"))(); | ||
koa.use(require("koa-bodyparser")()); | ||
koa.use(this.router.routes()); | ||
koa.use(this.router.allowedMethods()); | ||
this._httpServer = http.createServer(koa.callback()); | ||
} | ||
prepare() { | ||
const {nickname, prefix} = this.options; | ||
const nicknames = koishi_utils7.makeArray(nickname); | ||
const prefixes = Array.isArray(prefix) ? prefix : [prefix || ""]; | ||
this._nameRE = createLeadingRE(nicknames, "@?", "([,,]\\s*|\\s+)"); | ||
this._prefixRE = createLeadingRE(prefixes); | ||
} | ||
async start() { | ||
this.status = 1; | ||
await this.parallel("before-connect"); | ||
this.status = 2; | ||
this.logger("app").debug("started"); | ||
this.emit("connect"); | ||
} | ||
async _listen() { | ||
try { | ||
const {port} = this.app.options; | ||
if (port) { | ||
this._httpServer.listen(port); | ||
this.logger("server").info("server listening at %c", port); | ||
} | ||
await Promise.all(Object.values(this.servers).map((server3) => server3.listen())); | ||
} catch (error) { | ||
this._close(); | ||
throw error; | ||
} | ||
} | ||
async stop() { | ||
this.status = 3; | ||
await this.parallel("before-disconnect"); | ||
this.status = 0; | ||
this.logger("app").debug("stopped"); | ||
this.emit("disconnect"); | ||
} | ||
_close() { | ||
Object.values(this.servers).forEach((server3) => server3.close()); | ||
this.logger("server").debug("http server closing"); | ||
this._httpServer.close(); | ||
} | ||
async _preprocess(session3, next) { | ||
let message4 = this.options.processMessage(session3.message); | ||
let capture, atSelf = false; | ||
if (capture = message4.match(/^\[CQ:reply,id=(-?\d+)\]\s*/)) { | ||
session3.$reply = await session3.$bot.getMessage(session3.channelId, capture[1]).catch(koishi_utils7.noop); | ||
message4 = message4.slice(capture[0].length); | ||
if (session3.$reply) { | ||
const prefix = `[CQ:at,qq=${session3.$reply.sender.userId}]`; | ||
message4 = message4.slice(prefix.length).trimStart(); | ||
} | ||
} | ||
const at = `[CQ:at,qq=${session3.selfId}]`; | ||
if (session3.subType !== "private" && message4.startsWith(at)) { | ||
atSelf = session3.$appel = true; | ||
message4 = message4.slice(at.length).trimStart(); | ||
} else if (capture = message4.match(this._nameRE)) { | ||
session3.$appel = true; | ||
message4 = message4.slice(capture[0].length); | ||
} else if (capture = message4.match(this._prefixRE)) { | ||
session3.$prefix = capture[0]; | ||
message4 = message4.slice(capture[0].length); | ||
} | ||
session3.$parsed = message4; | ||
session3.$argv = session3.$parse(message4, "", true); | ||
if (this.database) { | ||
if (session3.subType === "group") { | ||
const groupFields = new Set(["flag", "assignee"]); | ||
this.emit("before-attach-group", session3, groupFields); | ||
const group = await session3.$observeGroup(groupFields); | ||
if (await this.serial(session3, "attach-group", session3)) | ||
return; | ||
if (group.flag & Group.Flag.ignore) | ||
return; | ||
if (group.assignee !== session3.selfId && !atSelf) | ||
return; | ||
} | ||
const userFields = new Set(["flag"]); | ||
this.emit("before-attach-user", session3, userFields); | ||
const user = await session3.$observeUser(userFields); | ||
if (await this.serial(session3, "attach-user", session3)) | ||
return; | ||
if (user.flag & User.Flag.ignore) | ||
return; | ||
} | ||
await this.parallel(session3, "attach", session3); | ||
if (!session3.$argv) | ||
return next(); | ||
session3.$argv.next = next; | ||
return session3.$argv.command.execute(session3.$argv); | ||
} | ||
async _receive(session3) { | ||
var _a, _b; | ||
this._sessions[session3.$uuid] = session3; | ||
const middlewares = this._hooks[Context.MIDDLEWARE_EVENT].filter(([context2]) => context2.match(session3)).map(([, middleware]) => middleware); | ||
let index = 0, midStack = "", lastCall = ""; | ||
const {prettyErrors} = this.options; | ||
const next = async (fallback) => { | ||
var _a2; | ||
if (prettyErrors) { | ||
lastCall = new Error().stack.split("\n", 3)[2]; | ||
if (index) { | ||
const capture = lastCall.match(/\((.+)\)/); | ||
midStack = ` | ||
- ${capture ? capture[1] : lastCall.slice(7)}${midStack}`; | ||
} | ||
} | ||
try { | ||
if (!this._sessions[session3.$uuid]) { | ||
throw new Error("isolated next function detected"); | ||
} | ||
if (fallback) | ||
middlewares.push((_, next2) => fallback(next2)); | ||
return (_a2 = middlewares[index++]) == null ? void 0 : _a2.call(middlewares, session3, next); | ||
} catch (error) { | ||
let stack = koishi_utils7.coerce(error); | ||
if (prettyErrors) { | ||
const index2 = stack.indexOf(lastCall); | ||
stack = `${stack.slice(0, index2)}Middleware stack:${midStack}`; | ||
} | ||
this.logger("middleware").warn(`${session3.message} | ||
${stack}`); | ||
} | ||
}; | ||
await next(); | ||
delete this._sessions[session3.$uuid]; | ||
this.emit(session3, "middleware", session3); | ||
await ((_a = session3.$user) == null ? void 0 : _a._update()); | ||
await ((_b = session3.$group) == null ? void 0 : _b._update()); | ||
} | ||
_parse(message4, session3, builtin, terminator = "") { | ||
const {$reply, $prefix, $appel, subType} = session3; | ||
if (builtin && subType !== "private" && $prefix === null && !$appel) | ||
return; | ||
terminator = koishi_utils7.escapeRegExp(terminator); | ||
const name = message4.split(new RegExp(`[\\s${terminator}]`), 1)[0]; | ||
const index = name.lastIndexOf("/"); | ||
const command6 = this.app._commandMap[name.slice(index + 1).toLowerCase()]; | ||
if (!command6) | ||
return; | ||
message4 = message4.slice(name.length).trim() + ($reply ? " " + $reply.content : ""); | ||
const result = command6.parse(message4, terminator); | ||
return {command: command6, ...result}; | ||
} | ||
} | ||
App2.defaultConfig = { | ||
maxListeners: 64, | ||
prettyErrors: true, | ||
queueDelay: 0.1 * koishi_utils7.Time.second, | ||
broadcastDelay: 0.5 * koishi_utils7.Time.second, | ||
promptTimeout: koishi_utils7.Time.minute, | ||
userCacheAge: koishi_utils7.Time.minute, | ||
groupCacheAge: 5 * koishi_utils7.Time.minute, | ||
similarityCoefficient: 0.4, | ||
processMessage: (message4) => koishi_utils7.simplify(message4.trim()) | ||
}; | ||
//# sourceMappingURL=index.js.map |
@@ -1,24 +0,18 @@ | ||
/// <reference types="node" /> | ||
import { Session, MessageType, MessageInfo } from './session'; | ||
import { Session, MessageInfo, EventTypeMap, GroupInfo, GroupMemberInfo, UserInfo } from './session'; | ||
import { App } from './app'; | ||
import * as http from 'http'; | ||
import type Router from 'koa-router'; | ||
export interface BotOptions { | ||
selfId?: number; | ||
type?: string; | ||
selfId?: string; | ||
} | ||
export declare abstract class Server { | ||
declare type BotStatic<T extends Bot = Bot> = new (app: App, options: BotOptions) => T; | ||
export declare abstract class Server<T extends Bot = Bot> { | ||
app: App; | ||
private BotStatic; | ||
static types: Record<string, new (app: App) => Server>; | ||
bots: Bot[]; | ||
router?: Router; | ||
server?: http.Server; | ||
protected _listening: boolean; | ||
protected abstract _listen(): Promise<void>; | ||
protected abstract _close(): void; | ||
constructor(app: App); | ||
createServer(): void; | ||
prepare(data: any): Session<never, never, {}, import("./session").PostType>; | ||
bots: T[]; | ||
abstract listen(): Promise<void>; | ||
abstract close(): void; | ||
constructor(app: App, BotStatic: BotStatic<T>); | ||
create(options: BotOptions): void; | ||
dispatch(session: Session): void; | ||
listen(): Promise<void>; | ||
close(): void; | ||
} | ||
@@ -38,16 +32,27 @@ export declare enum BotStatusCode { | ||
export interface Bot extends BotOptions { | ||
[Bot.$send](session: Session, message: string): Promise<void>; | ||
ready?: boolean; | ||
version?: string; | ||
getMsg(messageId: number): Promise<MessageInfo>; | ||
getSelfId(): Promise<number>; | ||
getSelfId(): Promise<string>; | ||
getStatusCode(): Promise<BotStatusCode>; | ||
getMemberMap(groupId: number): Promise<Record<number, string>>; | ||
sendGroupMsg(groupId: number, message: string, autoEscape?: boolean): Promise<number>; | ||
sendPrivateMsg(userId: number, message: string, autoEscape?: boolean): Promise<number>; | ||
sendMessage(channelId: string, message: string): Promise<string>; | ||
sendPrivateMessage(userId: string, message: string): Promise<string>; | ||
getMessage(channelId: string, messageId: string): Promise<MessageInfo>; | ||
deleteMessage(channelId: string, messageId: string): Promise<void>; | ||
getUser(userId: string): Promise<UserInfo>; | ||
getGroup(groupId: string): Promise<GroupInfo>; | ||
getGroupList(): Promise<GroupInfo[]>; | ||
getGroupMember(groupId: string, userId: string): Promise<GroupMemberInfo>; | ||
getGroupMemberList(groupId: string): Promise<GroupMemberInfo[]>; | ||
} | ||
export declare class Bot { | ||
app: App; | ||
static readonly $send: unique symbol; | ||
constructor(app: App, options: BotOptions); | ||
createSession(messageType: MessageType, ctxType: 'group' | 'user', ctxId: number, message: string): Session<never, never, {}, import("./session").PostType>; | ||
broadcast(groups: number[], message: string, delay?: number): Promise<number[]>; | ||
createSession(subType: EventTypeMap['message'], ctxType: 'group' | 'user', ctxId: string, message: string): Session<never, never, {}, never, "message" | "message-edited" | "message-deleted" | "group-added" | "group-deleted" | "lifecycle" | "send">; | ||
getGroupMemberMap(groupId: string): Promise<{ | ||
[k: string]: string; | ||
}>; | ||
broadcast(channels: string[], message: string, delay?: number): Promise<string[]>; | ||
} | ||
export {}; |
@@ -1,38 +0,28 @@ | ||
import { User, Group } from './database'; | ||
import { User, Group, Platforms, PlatformKind } from './database'; | ||
import { ExecuteArgv, ParsedArgv } from './command'; | ||
import { NextFunction } from './context'; | ||
import { App } from './app'; | ||
export declare type PostType = 'message' | 'notice' | 'request' | 'meta_event' | 'send'; | ||
import { Bot } from './server'; | ||
export declare type EventType = keyof EventTypeMap; | ||
export declare type MessageType = 'private' | 'group'; | ||
export declare type NoticeType = 'group_upload' | 'group_admin' | 'group_increase' | 'group_decrease' | 'group_ban' | 'friend_add' | 'group_recall' | 'friend_recall' | 'notify'; | ||
export declare type RequestType = 'friend' | 'group'; | ||
export declare type MetaEventType = 'lifecycle' | 'heartbeat'; | ||
export interface MetaTypeMap { | ||
message: MessageType; | ||
notice: NoticeType; | ||
request: RequestType; | ||
meta_event: MetaEventType; | ||
send: null; | ||
export interface EventTypeMap { | ||
'message': MessageType; | ||
'message-edited': MessageType; | ||
'message-deleted': MessageType; | ||
'group-added': null; | ||
'group-deleted': null; | ||
'lifecycle': 'heartbeat' | 'enable' | 'disable' | 'connect'; | ||
'send': MessageType; | ||
} | ||
export interface SubTypeMap { | ||
message: 'friend' | 'group' | 'other' | 'normal' | 'anonymous' | 'notice'; | ||
notice: 'set' | 'unset' | 'approve' | 'invite' | 'leave' | 'kick' | 'kick_me' | 'ban' | 'lift_ban' | 'poke' | 'lucky_king' | 'honor'; | ||
request: 'add' | 'invite'; | ||
meta_event: 'enable' | 'disable' | 'connect'; | ||
send: null; | ||
} | ||
/** CQHTTP Meta Information */ | ||
export interface Meta<P extends PostType = PostType> { | ||
postType?: P; | ||
messageType?: MetaTypeMap[P & 'message']; | ||
noticeType?: MetaTypeMap[P & 'notice']; | ||
requestType?: MetaTypeMap[P & 'request']; | ||
metaEventType?: MetaTypeMap[P & 'meta_event']; | ||
sendType?: MetaTypeMap[P & 'send']; | ||
subType?: SubTypeMap[P]; | ||
selfId?: number; | ||
userId?: number; | ||
groupId?: number; | ||
export interface Meta<E extends EventType = EventType> { | ||
kind?: PlatformKind; | ||
eventType?: E; | ||
subType?: EventTypeMap[E]; | ||
channelId?: string; | ||
selfId?: string; | ||
userId?: string; | ||
groupId?: string; | ||
time?: number; | ||
messageId?: number; | ||
messageId?: string; | ||
message?: string; | ||
@@ -53,5 +43,5 @@ rawMessage?: string; | ||
} | ||
export interface Session<U, G, O, P extends PostType = PostType> extends Meta<P> { | ||
export interface Session<U, G, O, K, E extends EventType = EventType> extends Meta<E> { | ||
} | ||
export declare class Session<U extends User.Field = never, G extends Group.Field = never, O extends {} = {}> { | ||
export declare class Session<U extends User.Field = never, G extends Group.Field = never, O extends {} = {}, K extends PlatformKind = never> { | ||
$user?: User.Observed<U>; | ||
@@ -73,9 +63,11 @@ $group?: Group.Observed<G>; | ||
}; | ||
get $bot(): import("./server").Bot; | ||
get $bot(): [K] extends [never] ? Bot : Platforms[K]; | ||
get $username(): string; | ||
$send(message: string): Promise<void>; | ||
$send(message: string): any; | ||
$cancelQueued(delay?: number): void; | ||
$sendQueued(message: string | void, delay?: number): Promise<void>; | ||
$getGroup<K extends Group.Field = never>(id?: string, fields?: readonly K[], assignee?: string): Promise<Pick<Group, "id" | "type" | K>>; | ||
/** 在元数据上绑定一个可观测群实例 */ | ||
$observeGroup<T extends Group.Field = never>(fields?: Iterable<T>): Promise<Group.Observed<T | G>>; | ||
$getUser<K extends User.Field = never>(id?: string, fields?: readonly K[], authority?: number): Promise<Pick<User, K>>; | ||
/** 在元数据上绑定一个可观测用户实例 */ | ||
@@ -100,3 +92,3 @@ $observeUser<T extends User.Field = never>(fields?: Iterable<T>): Promise<User.Observed<T | U>>; | ||
export interface AccountInfo { | ||
userId: number; | ||
userId: string; | ||
nickname: string; | ||
@@ -125,15 +117,20 @@ } | ||
export interface MessageInfo { | ||
time: number; | ||
messageType: MessageType; | ||
messageId: number; | ||
realId: number; | ||
id: string; | ||
type: EventTypeMap['message']; | ||
content: string; | ||
timestamp: number; | ||
sender: SenderInfo; | ||
message: string; | ||
} | ||
/** | ||
* get context unique id | ||
* @example | ||
* getContextId(session) // user123, group456 | ||
*/ | ||
export declare function getContextId(session: Session): string; | ||
export interface GroupInfo { | ||
id: string; | ||
name: string; | ||
} | ||
export interface UserInfo { | ||
id: string; | ||
name: string; | ||
} | ||
export interface GroupMemberInfo extends UserInfo { | ||
nick: string; | ||
roles: string[]; | ||
} | ||
export declare function getTargetId(target: string | number): number; |
{ | ||
"name": "koishi-core", | ||
"description": "Core features for Koishi", | ||
"version": "2.4.2", | ||
"version": "3.0.0-alpha.0", | ||
"main": "dist/index.js", | ||
@@ -48,5 +48,5 @@ "typings": "dist/index.d.ts", | ||
"koa-router": "^10.0.0", | ||
"koishi-utils": "^3.1.5", | ||
"koishi-utils": "^3.2.0", | ||
"lru-cache": "^6.0.0" | ||
} | ||
} |
Sorry, the diff of this file is not supported yet
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
251537
16
2383
2
2
Updatedkoishi-utils@^3.2.0