@koishijs/core
Advanced tools
Comparing version 4.17.8 to 4.17.9
@@ -7,3 +7,3 @@ import * as utils from '@koishijs/utils'; | ||
import { Driver, FlatKeys, FlatPick, Update, Eval } from 'minato'; | ||
import { Fragment, EventOptions, h, Schema, Universal, Bot, HTTP } from '@satorijs/core'; | ||
import { Fragment, EventOptions, Hook, h, Schema, Universal, Bot, HTTP } from '@satorijs/core'; | ||
import { LocaleTree } from '@koishijs/i18n-utils'; | ||
@@ -80,3 +80,5 @@ import { Disposable, GetEvents, Parameters, ReturnType, ThisType } from 'cordis'; | ||
} | ||
export class KoishiDatabase { | ||
declare interface KoishiDatabase extends minato.Database<Tables, Types, Context> { | ||
} | ||
declare class KoishiDatabase { | ||
ctx: Context; | ||
@@ -143,3 +145,3 @@ constructor(ctx: Context); | ||
private ctx; | ||
_hooks: [Context, Middleware, EventOptions][]; | ||
_hooks: Hook[]; | ||
_sessions: Dict<Session>; | ||
@@ -150,4 +152,3 @@ _userCache: SharedCache<User.Observed<keyof User>>; | ||
constructor(ctx: Context); | ||
protected get caller(): Context; | ||
middleware(middleware: Middleware, options?: boolean | EventOptions): any; | ||
middleware(middleware: Middleware, options?: boolean | EventOptions): () => any; | ||
match(pattern: string | RegExp, response: Matcher.Response, options: Matcher.Options): () => void; | ||
@@ -254,3 +255,2 @@ private _executeMatcher; | ||
constructor(ctx: Context); | ||
private get caller(); | ||
define<P extends string>(pattern: P, options: Permissions.Options<P>): () => boolean; | ||
@@ -322,2 +322,3 @@ provide<P extends string>(pattern: P, check: Permissions.Check<P>): () => boolean; | ||
natural: number; | ||
bigint: bigint; | ||
date: Date; | ||
@@ -550,5 +551,10 @@ img: JSX.IntrinsicElements['img']; | ||
} | ||
declare interface Task { | ||
delay: number; | ||
content: Fragment; | ||
resolve(ids: string[]): void; | ||
reject(reason: any): void; | ||
} | ||
export type FieldCollector<T extends keyof Tables, K = keyof Tables[T], A extends any[] = any[], O extends {} = {}> = Iterable<K> | ((argv: Argv<never, never, A, O>, fields: Set<keyof Tables[T]>) => void); | ||
export class Session<U extends User.Field = never, G extends Channel.Field = never, C extends Context = Context> extends satori.Session<C> { | ||
static shadow: symbol; | ||
export interface Session<U extends User.Field = never, G extends Channel.Field = never, C extends Context = Context> extends satori.Session<C> { | ||
argv?: Argv<U, G>; | ||
@@ -561,6 +567,32 @@ user?: User.Observed<U>; | ||
response?: () => Promise<Fragment>; | ||
private _stripped; | ||
private _queuedTasks; | ||
private _queuedTimeout; | ||
resolve<T, R extends any[]>(source: T | Eval.Expr | ((session: this, ...args: R) => T), ...args: R): T extends Eval.Expr ? Eval<T> : T extends (...args: any[]) => any ? ReturnType<T> : T; | ||
stripped: Stripped; | ||
username: string; | ||
send(fragment: Fragment, options?: Universal.SendOptions): Promise<string[]>; | ||
cancelQueued(delay?: number): void; | ||
sendQueued(content: Fragment, delay?: number): Promise<string[]>; | ||
getChannel<K extends Channel.Field = never>(id?: string, fields?: K[]): Promise<Channel>; | ||
observeChannel<T extends Channel.Field = never>(fields: Iterable<T>): Promise<Channel.Observed<T | G>>; | ||
getUser<K extends User.Field = never>(userId?: string, fields?: K[]): Promise<User>; | ||
observeUser<T extends User.Field = never>(fields: Iterable<T>): Promise<User.Observed<T | U>>; | ||
withScope(scope: string, callback: () => Awaitable<string>): Promise<string>; | ||
resolveScope(path: string): string; | ||
text(path: string | string[], params?: object): string; | ||
i18n(path: string | string[], params?: object): satori.Element[]; | ||
collect<T extends 'user' | 'channel'>(key: T, argv: Argv, fields?: Set<keyof Tables[T]>): Set<keyof Tables[T]>; | ||
execute(content: string, next?: true | Next): Promise<string>; | ||
execute(argv: Argv, next?: true | Next): Promise<string>; | ||
middleware(middleware: Middleware<this>): () => boolean; | ||
prompt(timeout?: number): Promise<string>; | ||
prompt<T>(callback: (session: this) => Awaitable<T>, options?: PromptOptions): Promise<T>; | ||
suggest(options: SuggestOptions): Promise<string>; | ||
} | ||
declare interface KoishiSession<U extends User.Field, G extends Channel.Field, C extends Context> extends Session<U, G, C> { | ||
_stripped: Stripped; | ||
_queuedTasks: Task[]; | ||
_queuedTimeout: NodeJS.Timeout; | ||
} | ||
declare class KoishiSession<U, G, C> { | ||
constructor(ctx: C); | ||
resolve<T, R extends any[]>(source: T | Eval.Expr | ((session: this, ...args: R) => T), ...args: R): T extends Eval.Expr ? Eval<T> : T extends (...args: any[]) => any ? ReturnType<T> : T; | ||
_stripNickname(content: string): string; | ||
@@ -572,9 +604,9 @@ /** @deprecated */ | ||
send(fragment: Fragment, options?: Universal.SendOptions): Promise<string[]>; | ||
cancelQueued(delay?: any): void; | ||
cancelQueued(delay?: number): void; | ||
_next(): void; | ||
sendQueued(content: Fragment, delay?: number): Promise<string[]>; | ||
getChannel<K extends Channel.Field = never>(id?: string, fields?: K[]): Promise<any>; | ||
getChannel<K extends Channel.Field = never>(id?: string, fields?: K[]): any; | ||
_observeChannelLike<K extends Channel.Field = never>(channelId: string, fields?: Iterable<K>): Promise<Channel.Observed<keyof Channel>>; | ||
observeChannel<T extends Channel.Field = never>(fields: Iterable<T>): Promise<Channel.Observed<T | G>>; | ||
getUser<K extends User.Field = never>(userId?: string, fields?: K[]): Promise<any>; | ||
getUser<K extends User.Field = never>(userId?: string, fields?: K[]): any; | ||
observeUser<T extends User.Field = never>(fields: Iterable<T>): Promise<User.Observed<T | U>>; | ||
@@ -588,3 +620,3 @@ withScope(scope: string, callback: () => Awaitable<string>): Promise<string>; | ||
execute(argv: Argv, next?: true | Next): Promise<string>; | ||
middleware(middleware: Middleware<this>): () => boolean; | ||
middleware(middleware: Middleware<this>): any; | ||
prompt(timeout?: number): Promise<string>; | ||
@@ -618,5 +650,4 @@ prompt<T>(callback: (session: this) => Awaitable<T>, options?: PromptOptions): Promise<T>; | ||
export class FilterService { | ||
private app; | ||
constructor(app: Context); | ||
protected get caller(): Context; | ||
private ctx; | ||
constructor(ctx: Context); | ||
any(): Context; | ||
@@ -632,3 +663,3 @@ never(): Context; | ||
platform(...values: string[]): Context; | ||
private(...values: string[]): Context; | ||
private(): Context; | ||
} | ||
@@ -659,2 +690,18 @@ declare global { | ||
} | ||
declare module '@satorijs/core' { | ||
interface Context { | ||
schema: SchemaService; | ||
} | ||
interface Events { | ||
'internal/schema'(name: string): void; | ||
} | ||
} | ||
export class SchemaService { | ||
ctx: Context; | ||
_data: Dict<Schema>; | ||
constructor(ctx: Context); | ||
extend(name: string, schema: Schema, order?: number): void; | ||
get(name: string): Schema; | ||
set(name: string, schema: Schema): void; | ||
} | ||
export type EffectScope = cordis.EffectScope<Context>; | ||
@@ -686,9 +733,10 @@ export type ForkScope = cordis.ForkScope<Context>; | ||
[Context.session]: Session<never, never, this>; | ||
koishi: Koishi; | ||
} | ||
export class Context extends satori.Context { | ||
static readonly Session: typeof Session; | ||
static shadow: symbol; | ||
constructor(config?: Context.Config); | ||
/** @deprecated use `ctx.root` instead */ | ||
get app(): this; | ||
/** @deprecated use `root.config` instead */ | ||
/** @deprecated use `koishi.config` instead */ | ||
get options(): any; | ||
@@ -747,9 +795,8 @@ /** @deprecated */ | ||
} | ||
export interface KoishiBot extends Bot<Context> { | ||
declare interface KoishiBot extends Bot<Context> { | ||
} | ||
export class KoishiBot { | ||
declare class KoishiBot { | ||
constructor(ctx: Context); | ||
getGuildMemberMap(guildId: string): Promise<Dict<string>>; | ||
broadcast(channels: (string | [string, string] | Session)[], content: Fragment, delay?: any): Promise<string[]>; | ||
session(event?: Partial<Event>): any; | ||
} | ||
@@ -756,0 +803,0 @@ export * from '@koishijs/utils'; |
{ | ||
"name": "@koishijs/core", | ||
"description": "Core Features for Koishi", | ||
"version": "4.17.8", | ||
"version": "4.17.9", | ||
"main": "lib/index.cjs", | ||
@@ -36,8 +36,8 @@ "module": "lib/index.mjs", | ||
"@koishijs/utils": "^7.2.1", | ||
"@satorijs/core": "^4.1.1", | ||
"cordis": "^3.16.1", | ||
"@satorijs/core": "^4.1.2", | ||
"cordis": "^3.17.1", | ||
"cosmokit": "^1.6.2", | ||
"fastest-levenshtein": "^1.0.16", | ||
"minato": "^3.4.0" | ||
"minato": "^3.4.1" | ||
} | ||
} |
@@ -19,9 +19,10 @@ import { sleep } from '@koishijs/utils' | ||
export interface KoishiBot extends Bot<Context> {} | ||
interface KoishiBot extends Bot<Context> {} | ||
export class KoishiBot { | ||
class KoishiBot { | ||
constructor(ctx: Context) { | ||
ctx.accessor('bot.getGuildMemberMap', { get: () => this.getGuildMemberMap }) | ||
ctx.accessor('bot.broadcast', { get: () => this.broadcast }) | ||
ctx.accessor('bot.session', { get: () => this.session }) | ||
ctx.mixin(this, { | ||
getGuildMemberMap: 'bot.getGuildMemberMap', | ||
broadcast: 'bot.broadcast', | ||
}) | ||
} | ||
@@ -54,6 +55,4 @@ | ||
} | ||
} | ||
session(event: Partial<Event> = {}) { | ||
return new Session(this, event) | ||
} | ||
} | ||
export default KoishiBot |
@@ -193,2 +193,10 @@ import { Awaitable, defineProperty, Time } from 'cosmokit' | ||
this.domain('bigint', (source, session) => { | ||
try { | ||
return BigInt(source) | ||
} catch { | ||
throw new Error('internal.invalid-integer') | ||
} | ||
}, { numeric: true }) | ||
this.domain('date', (source, session) => { | ||
@@ -323,3 +331,2 @@ const timestamp = Time.parseDate(source) | ||
const segments = path.split(/(?=[./])/g) | ||
const caller: Context = this[Context.current] | ||
@@ -351,4 +358,4 @@ /** parent command in the chain */ | ||
const isLast = index === segments.length - 1 | ||
command = new Command(name, isLast ? decl : '', caller, isLast ? config : {}) | ||
command._disposables.push(caller.i18n.define('', { | ||
command = new Command(name, isLast ? decl : '', this.ctx, isLast ? config : {}) | ||
command._disposables.push(this.ctx.i18n.define('', { | ||
[`commands.${command.name}.$`]: '', | ||
@@ -367,5 +374,5 @@ [`commands.${command.name}.description`]: isLast ? desc : '', | ||
// Make sure `command.config` is set before emitting any events | ||
created.forEach(command => caller.emit('command-added', command)) | ||
parent[Context.current] = caller | ||
if (root) caller.collect(`command <${root.name}>`, () => root.dispose()) | ||
created.forEach(command => this.ctx.emit('command-added', command)) | ||
parent[Context.current] = this.ctx | ||
if (root) this.ctx.collect(`command <${root.name}>`, () => root.dispose()) | ||
return parent | ||
@@ -377,6 +384,5 @@ } | ||
domain<K extends keyof Argv.Domain>(name: K, transform?: Argv.Transform<Argv.Domain[K]>, options?: Argv.DomainConfig<Argv.Domain[K]>) { | ||
const caller = this[Context.current] as Context | ||
const service = 'domain:' + name | ||
if (!transform) return caller.get(service) | ||
return caller.set(service, { transform, ...options }) | ||
if (!transform) return this.ctx.get(service) | ||
return this.ctx.set(service, { transform, ...options }) | ||
} | ||
@@ -383,0 +389,0 @@ |
@@ -185,2 +185,3 @@ import { camelCase, Dict, paramCase } from 'cosmokit' | ||
natural: number | ||
bigint: bigint | ||
date: Date | ||
@@ -187,0 +188,0 @@ img: JSX.IntrinsicElements['img'] |
@@ -6,10 +6,12 @@ import { defineProperty, Promisify, Time } from 'cosmokit' | ||
import * as cordis from 'cordis' | ||
import * as minato from 'minato' | ||
import { Computed, FilterService } from './filter' | ||
import { Commander } from './command' | ||
import { I18n } from './i18n' | ||
import { Session } from './session' | ||
import SessionMixin, { Session } from './session' | ||
import { Processor } from './middleware' | ||
import { Permissions } from './permission' | ||
import { KoishiDatabase } from './database' | ||
import { KoishiBot } from './bot' | ||
import DatabaseMixin from './database' | ||
import BotMixin from './bot' | ||
import { SchemaService } from './schema' | ||
@@ -46,6 +48,7 @@ export type EffectScope = cordis.EffectScope<Context> | ||
[Context.session]: Session<never, never, this> | ||
koishi: Koishi | ||
} | ||
export class Context extends satori.Context { | ||
static readonly Session = Session | ||
static shadow = Symbol.for('session.shadow') | ||
@@ -61,2 +64,3 @@ constructor(config: Context.Config = {}) { | ||
this.provide('$filter', new FilterService(this), true) | ||
this.provide('schema', new SchemaService(this), true) | ||
this.provide('$processor', new Processor(this), true) | ||
@@ -68,4 +72,4 @@ this.provide('i18n', new I18n(this, this.config.i18n), true) | ||
this.provide('$commander', new Commander(this, this.config), true) | ||
this.provide('koishi.database', new KoishiDatabase(this), true) | ||
this.provide('koishi.bot', new KoishiBot(this), true) | ||
this.plugin(minato.Database) | ||
this.plugin(Koishi, this.config) | ||
} | ||
@@ -78,3 +82,3 @@ | ||
/** @deprecated use `root.config` instead */ | ||
/** @deprecated use `koishi.config` instead */ | ||
get options() { | ||
@@ -89,6 +93,6 @@ return this.root.config | ||
async waterfall(...args: [any, ...any[]]) { | ||
const thisArg = typeof args[0] === 'object' ? args.shift() : null | ||
const thisArg = typeof args[0] === 'object' || typeof args[0] === 'function' ? args.shift() : null | ||
const name = args.shift() | ||
for (const callback of this.lifecycle.getHooks(name, thisArg)) { | ||
const result = await callback.apply(thisArg, args) | ||
for (const hook of this.lifecycle.filterHooks(this.lifecycle._hooks[name] || [], thisArg)) { | ||
const result = await hook.callback.apply(thisArg, args) | ||
args[0] = result | ||
@@ -103,6 +107,6 @@ } | ||
chain(...args: [any, ...any[]]) { | ||
const thisArg = typeof args[0] === 'object' ? args.shift() : null | ||
const thisArg = typeof args[0] === 'object' || typeof args[0] === 'function' ? args.shift() : null | ||
const name = args.shift() | ||
for (const callback of this.lifecycle.getHooks(name, thisArg)) { | ||
const result = callback.apply(thisArg, args) | ||
for (const hook of this.lifecycle.filterHooks(this.lifecycle._hooks[name] || [], thisArg)) { | ||
const result = hook.callback.apply(thisArg, args) | ||
args[0] = result | ||
@@ -121,3 +125,13 @@ } | ||
Session.prototype[Context.filter] = function (this: Session, ctx: Context) { | ||
export default class Koishi extends cordis.Service<Context.Config, Context> { | ||
bot = new BotMixin(this.ctx) | ||
database = new DatabaseMixin(this.ctx) | ||
session = new SessionMixin(this.ctx) | ||
constructor(ctx: Context, public config: Context.Config) { | ||
super(ctx, 'koishi', true) | ||
} | ||
} | ||
satori.Session.prototype[Context.filter] = function (this: Session, ctx: Context) { | ||
return ctx.filter(this) | ||
@@ -124,0 +138,0 @@ } |
@@ -90,8 +90,7 @@ import * as utils from '@koishijs/utils' | ||
// Do not set "database" inject for this service. | ||
export class KoishiDatabase { | ||
interface KoishiDatabase extends minato.Database<Tables, Types, Context> {} | ||
class KoishiDatabase { | ||
constructor(public ctx: Context) { | ||
ctx.plugin(minato.Database) | ||
ctx.mixin('koishi.database', { | ||
ctx.mixin(this, { | ||
getUser: 'database.getUser', | ||
@@ -156,5 +155,5 @@ setUser: 'database.setUser', | ||
async getUser<K extends FlatKeys<User>>(platform: string, pid: string, modifier?: Driver.Cursor<K>): Promise<FlatPick<User, K>> { | ||
const [binding] = await this.ctx.database.get('binding', { platform, pid }, ['aid']) | ||
const [binding] = await this.get('binding', { platform, pid }, ['aid']) | ||
if (!binding) return | ||
const [user] = await this.ctx.database.get('user', { id: binding.aid }, modifier) | ||
const [user] = await this.get('user', { id: binding.aid }, modifier) | ||
return user | ||
@@ -164,10 +163,10 @@ } | ||
async setUser(platform: string, pid: string, data: Update<User>) { | ||
const [binding] = await this.ctx.database.get('binding', { platform, pid }, ['aid']) | ||
const [binding] = await this.get('binding', { platform, pid }, ['aid']) | ||
if (!binding) throw new Error('user not found') | ||
return this.ctx.database.set('user', binding.aid, data) | ||
return this.set('user', binding.aid, data) | ||
} | ||
async createUser(platform: string, pid: string, data: Partial<User>) { | ||
const user = await this.ctx.database.create('user', data) | ||
await this.ctx.database.create('binding', { aid: user.id, bid: user.id, pid, platform }) | ||
const user = await this.create('user', data) | ||
await this.create('binding', { aid: user.id, bid: user.id, pid, platform }) | ||
return user | ||
@@ -178,4 +177,4 @@ } | ||
getChannel<K extends FlatKeys<Channel>>(platform: string, ids: string[], modifier?: Driver.Cursor<K>): Promise<FlatPick<Channel, K>[]> | ||
async getChannel(platform: string, id: MaybeArray<string>, modifier?: Driver.Cursor<Channel.Field>) { | ||
const data = await this.ctx.database.get('channel', { platform, id }, modifier) | ||
async getChannel(platform: string, id: MaybeArray<string>, modifier?: any) { | ||
const data = await this.get('channel', { platform, id }, modifier) | ||
if (Array.isArray(id)) return data | ||
@@ -197,3 +196,3 @@ if (data[0]) Object.assign(data[0], { platform, id }) | ||
async getAssignedChannels(fields?: Channel.Field[], selfIdMap: Dict<string[]> = this.getSelfIds()) { | ||
return this.ctx.database.get('channel', { | ||
return this.get('channel', { | ||
$or: Object.entries(selfIdMap).map(([platform, assignee]) => ({ platform, assignee })), | ||
@@ -204,7 +203,7 @@ }, fields) | ||
setChannel(platform: string, id: string, data: Update<Channel>) { | ||
return this.ctx.database.set('channel', { platform, id }, data) | ||
return this.set('channel', { platform, id }, data) | ||
} | ||
createChannel(platform: string, id: string, data: Partial<Channel>) { | ||
return this.ctx.database.create('channel', { platform, id, ...data }) | ||
return this.create('channel', { platform, id, ...data }) | ||
} | ||
@@ -236,3 +235,3 @@ | ||
if (channels?.length) { | ||
this[Context.current].logger('app').warn('broadcast', 'channel not found: ', channels.join(', ')) | ||
this.ctx.logger('app').warn('broadcast', 'channel not found: ', channels.join(', ')) | ||
} | ||
@@ -256,1 +255,3 @@ | ||
} | ||
export default KoishiDatabase |
import { defineProperty } from 'cosmokit' | ||
import { Eval } from 'minato' | ||
import { Schema } from '@satorijs/core' | ||
import { Channel, User } from './database' | ||
@@ -43,7 +42,7 @@ import { Context } from './context' | ||
export class FilterService { | ||
constructor(private app: Context) { | ||
defineProperty(this, Context.current, app) | ||
constructor(private ctx: Context) { | ||
defineProperty(this, Context.current, ctx) | ||
app.filter = () => true | ||
app.on('internal/runtime', (runtime) => { | ||
ctx.filter = () => true | ||
ctx.on('internal/runtime', (runtime) => { | ||
if (!runtime.uid) return | ||
@@ -56,116 +55,48 @@ runtime.ctx.filter = (session) => { | ||
protected get caller() { | ||
return this[Context.current] as Context | ||
} | ||
any() { | ||
return this.caller.extend({ filter: () => true }) | ||
return this.ctx.extend({ filter: () => true }) | ||
} | ||
never() { | ||
return this.caller.extend({ filter: () => false }) | ||
return this.ctx.extend({ filter: () => false }) | ||
} | ||
union(arg: Filter | Context) { | ||
const caller = this.caller | ||
const filter = typeof arg === 'function' ? arg : arg.filter | ||
return caller.extend({ filter: s => caller.filter(s) || filter(s) }) | ||
return this.ctx.extend({ filter: s => this.ctx.filter(s) || filter(s) }) | ||
} | ||
intersect(arg: Filter | Context) { | ||
const caller = this.caller | ||
const filter = typeof arg === 'function' ? arg : arg.filter | ||
return caller.extend({ filter: s => caller.filter(s) && filter(s) }) | ||
return this.ctx.extend({ filter: s => this.ctx.filter(s) && filter(s) }) | ||
} | ||
exclude(arg: Filter | Context) { | ||
const caller = this.caller | ||
const filter = typeof arg === 'function' ? arg : arg.filter | ||
return caller.extend({ filter: s => caller.filter(s) && !filter(s) }) | ||
return this.ctx.extend({ filter: s => this.ctx.filter(s) && !filter(s) }) | ||
} | ||
user(...values: string[]) { | ||
return property(this.caller, 'userId', ...values) | ||
return property(this.ctx, 'userId', ...values) | ||
} | ||
self(...values: string[]) { | ||
return property(this.caller, 'selfId', ...values) | ||
return property(this.ctx, 'selfId', ...values) | ||
} | ||
guild(...values: string[]) { | ||
return property(this.caller, 'guildId', ...values) | ||
return property(this.ctx, 'guildId', ...values) | ||
} | ||
channel(...values: string[]) { | ||
return property(this.caller, 'channelId', ...values) | ||
return property(this.ctx, 'channelId', ...values) | ||
} | ||
platform(...values: string[]) { | ||
return property(this.caller, 'platform', ...values) | ||
return property(this.ctx, 'platform', ...values) | ||
} | ||
private(...values: string[]) { | ||
return property(this.caller.exclude(property(this.caller, 'guildId')), 'userId', ...values) | ||
private() { | ||
return this.ctx.intersect((session) => session.isDirect) | ||
} | ||
} | ||
declare global { | ||
interface Schemastery<S, T> { | ||
computed(options?: Computed.Options): Schema<Computed<S>, Computed<T>> | ||
} | ||
namespace Schemastery { | ||
interface Static { | ||
path(options?: Path.Options): Schema<string> | ||
filter(): Schema<Computed<boolean>> | ||
computed<X>(inner: X, options?: Computed.Options): Schema<Computed<TypeS<X>>, Computed<TypeT<X>>> | ||
dynamic(name: string): Schema | ||
} | ||
namespace Path { | ||
interface Options { | ||
filters?: Filter[] | ||
allowCreate?: boolean | ||
} | ||
type Filter = FileFilter | 'file' | 'directory' | ||
interface FileFilter { | ||
name: string | ||
extensions: string[] | ||
} | ||
} | ||
} | ||
} | ||
Schema.dynamic = function dynamic(name) { | ||
return Schema.any().role('dynamic', { name }) as never | ||
} | ||
Schema.filter = function filter() { | ||
return Schema.any().role('filter') | ||
} | ||
Schema.computed = function computed(inner, options = {}) { | ||
return Schema.union([ | ||
Schema.from(inner), | ||
Schema.object({ | ||
$switch: Schema.object({ | ||
branches: Schema.array(Schema.object({ | ||
case: Schema.any(), | ||
then: Schema.from(inner), | ||
})), | ||
default: Schema.from(inner), | ||
}), | ||
}).hidden(), | ||
Schema.any().hidden(), | ||
]).role('computed', options) | ||
} | ||
Schema.path = function path(options = {}) { | ||
return Schema.string().role('path', options) | ||
} | ||
Schema.prototype.computed = function computed(this: Schema, options = {}) { | ||
return Schema.computed(this, options).default(this.meta.default) | ||
} |
@@ -128,3 +128,2 @@ import { distance } from 'fastest-levenshtein' | ||
define(locale: string, ...args: [I18n.Store] | [string, I18n.Node]) { | ||
const caller = this[Context.current] || this.ctx | ||
const dict = this._data[locale] ||= {} | ||
@@ -135,3 +134,3 @@ const paths = [...typeof args[0] === 'string' | ||
this.ctx.emit('internal/i18n') | ||
return caller.collect('i18n', () => { | ||
return this.ctx.collect('i18n', () => { | ||
for (const path of paths) { | ||
@@ -138,0 +137,0 @@ delete dict[path] |
@@ -11,2 +11,3 @@ // @ts-ignore | ||
export * from './middleware' | ||
export * from './schema' | ||
export * from './session' | ||
@@ -13,0 +14,0 @@ export * from './permission' |
import { coerce, makeArray, Random } from '@koishijs/utils' | ||
import { Awaitable, defineProperty, Dict, Time } from 'cosmokit' | ||
import { EventOptions, Fragment, h } from '@satorijs/core' | ||
import { EventOptions, Fragment, h, Hook } from '@satorijs/core' | ||
import { Session } from './session' | ||
import { Context } from './context' | ||
import { Channel, User } from './database' | ||
import { Session } from './session' | ||
@@ -65,3 +65,3 @@ declare module './context' { | ||
export class Processor { | ||
_hooks: [Context, Middleware, EventOptions][] = [] | ||
_hooks: Hook[] = [] | ||
_sessions: Dict<Session> = Object.create(null) | ||
@@ -136,6 +136,2 @@ _userCache = new SharedCache<User.Observed<keyof User>>() | ||
protected get caller() { | ||
return this[Context.current] as Context | ||
} | ||
middleware(middleware: Middleware, options?: boolean | EventOptions) { | ||
@@ -145,9 +141,9 @@ if (typeof options !== 'object') { | ||
} | ||
return this.caller.lifecycle.register('middleware', this._hooks, middleware, options) | ||
return this.ctx.lifecycle.register('middleware', this._hooks, middleware, options) | ||
} | ||
match(pattern: string | RegExp, response: Matcher.Response, options: Matcher.Options) { | ||
const matcher: Matcher = { ...options, context: this.caller, pattern, response } | ||
const matcher: Matcher = { ...options, context: this.ctx, pattern, response } | ||
this._matchers.add(matcher) | ||
return this.caller.collect('shortcut', () => { | ||
return this.ctx.collect('shortcut', () => { | ||
return this._matchers.delete(matcher) | ||
@@ -248,5 +244,5 @@ }) | ||
this._sessions[session.id] = session | ||
const queue: Next.Queue = this._hooks | ||
.filter(([context]) => context.filter(session)) | ||
.map(([, middleware]) => middleware.bind(null, session)) | ||
const queue: Next.Queue = this.ctx.lifecycle | ||
.filterHooks(this._hooks, session) | ||
.map(({ callback }) => callback.bind(null, session)) | ||
@@ -253,0 +249,0 @@ // execute middlewares |
import { Logger } from '@satorijs/core' | ||
import { Awaitable, defineProperty, remove } from 'cosmokit' | ||
import { Session } from './session' | ||
import { Context } from './context' | ||
import { createMatch, MatchResult } from './i18n' | ||
import { Session } from './session' | ||
@@ -66,6 +66,2 @@ const logger = new Logger('app') | ||
private get caller(): Context { | ||
return this[Context.current] | ||
} | ||
define<P extends string>(pattern: P, options: Permissions.Options<P>) { | ||
@@ -77,3 +73,3 @@ const entry: Permissions.Entry = { | ||
if (!pattern.includes('(')) entry.list ||= () => [pattern] | ||
return this.caller.effect(() => { | ||
return this.ctx.effect(() => { | ||
this.store.push(entry) | ||
@@ -139,3 +135,3 @@ return () => remove(this.store, entry) | ||
async test(names: Iterable<string>, session: Partial<Session> = {}, cache: Map<string, Promise<boolean>> = new Map()) { | ||
session = session[Session.shadow] || session | ||
session = session[Context.shadow] || session | ||
if (typeof names === 'string') names = [names] | ||
@@ -142,0 +138,0 @@ for (const name of this.subgraph('depends', names)) { |
@@ -59,5 +59,3 @@ import { observe } from '@koishijs/utils' | ||
export class Session<U extends User.Field = never, G extends Channel.Field = never, C extends Context = Context> extends satori.Session<C> { | ||
static shadow = Symbol.for('session.shadow') | ||
export interface Session<U extends User.Field = never, G extends Channel.Field = never, C extends Context = Context> extends satori.Session<C> { | ||
argv?: Argv<U, G> | ||
@@ -67,10 +65,64 @@ user?: User.Observed<U> | ||
guild?: Channel.Observed<G> | ||
permissions: string[] = [] | ||
permissions: string[] | ||
scope?: string | ||
response?: () => Promise<Fragment> | ||
resolve<T, R extends any[]>(source: T | Eval.Expr | ((session: this, ...args: R) => T), ...args: R): | ||
| T extends Eval.Expr ? Eval<T> | ||
: T extends (...args: any[]) => any ? ReturnType<T> | ||
: T | ||
stripped: Stripped | ||
username: string | ||
send(fragment: Fragment, options?: Universal.SendOptions): Promise<string[]> | ||
cancelQueued(delay?: number): void | ||
sendQueued(content: Fragment, delay?: number): Promise<string[]> | ||
getChannel<K extends Channel.Field = never>(id?: string, fields?: K[]): Promise<Channel> | ||
observeChannel<T extends Channel.Field = never>(fields: Iterable<T>): Promise<Channel.Observed<T | G>> | ||
getUser<K extends User.Field = never>(userId?: string, fields?: K[]): Promise<User> | ||
observeUser<T extends User.Field = never>(fields: Iterable<T>): Promise<User.Observed<T | U>> | ||
withScope(scope: string, callback: () => Awaitable<string>): Promise<string> | ||
resolveScope(path: string): string | ||
text(path: string | string[], params?: object): string | ||
i18n(path: string | string[], params?: object): satori.Element[] | ||
collect<T extends 'user' | 'channel'>(key: T, argv: Argv, fields?: Set<keyof Tables[T]>): Set<keyof Tables[T]> | ||
execute(content: string, next?: true | Next): Promise<string> | ||
execute(argv: Argv, next?: true | Next): Promise<string> | ||
middleware(middleware: Middleware<this>): () => boolean | ||
prompt(timeout?: number): Promise<string> | ||
prompt<T>(callback: (session: this) => Awaitable<T>, options?: PromptOptions): Promise<T> | ||
suggest(options: SuggestOptions): Promise<string> | ||
} | ||
private _stripped: Stripped | ||
private _queuedTasks: Task[] = [] | ||
private _queuedTimeout: NodeJS.Timeout | ||
interface KoishiSession<U extends User.Field, G extends Channel.Field, C extends Context> extends Session<U, G, C> { | ||
// DO NOT set class properties here, | ||
// because they will override the actual properties in the instance. | ||
_stripped: Stripped | ||
_queuedTasks: Task[] | ||
_queuedTimeout: NodeJS.Timeout | ||
} | ||
class KoishiSession<U, G, C> { | ||
constructor(ctx: C) { | ||
ctx.mixin(this, { | ||
resolve: 'session.resolve', | ||
stripped: 'session.stripped', | ||
username: 'session.username', | ||
send: 'session.send', | ||
cancelQueued: 'session.cancelQueued', | ||
sendQueued: 'session.sendQueued', | ||
getChannel: 'session.getChannel', | ||
observeChannel: 'session.observeChannel', | ||
getUser: 'session.getUser', | ||
observeUser: 'session.observeUser', | ||
withScope: 'session.withScope', | ||
resolveScope: 'session.resolveScope', | ||
text: 'session.text', | ||
i18n: 'session.i18n', | ||
collect: 'session.collect', | ||
execute: 'session.execute', | ||
middleware: 'session.middleware', | ||
prompt: 'session.prompt', | ||
suggest: 'session.suggest', | ||
}) | ||
} | ||
resolve<T, R extends any[]>(source: T | Eval.Expr | ((session: this, ...args: R) => T), ...args: R): | ||
@@ -91,3 +143,3 @@ | T extends Eval.Expr ? Eval<T> | ||
if (content.startsWith('@')) content = content.slice(1) | ||
for (const nickname of this.resolve(this.app.config.nickname) ?? []) { | ||
for (const nickname of this.resolve(this.app.koishi.config.nickname) ?? []) { | ||
if (!content.startsWith(nickname)) continue | ||
@@ -157,3 +209,3 @@ const rest = content.slice(nickname.length) | ||
cancelQueued(delay = this.app.config.delay.cancel) { | ||
cancelQueued(delay = this.app.koishi.config.delay.cancel) { | ||
clearTimeout(this._queuedTimeout) | ||
@@ -165,3 +217,3 @@ this._queuedTasks = [] | ||
_next() { | ||
const task = this._queuedTasks.shift() | ||
const task = this._queuedTasks?.shift() | ||
if (!task) { | ||
@@ -179,7 +231,7 @@ this._queuedTimeout = null | ||
if (isNullable(delay)) { | ||
const { message, character } = this.app.config.delay | ||
const { message, character } = this.app.koishi.config.delay | ||
delay = Math.max(message, character * text.length) | ||
} | ||
return new Promise<string[]>((resolve, reject) => { | ||
this._queuedTasks.push({ content, delay, resolve, reject }) | ||
(this._queuedTasks ??= []).push({ content, delay, resolve, reject }) | ||
if (!this._queuedTimeout) this._next() | ||
@@ -194,3 +246,3 @@ }) | ||
if (channel) return channel | ||
const assignee = this.resolve(app.config.autoAssign) ? this.selfId : '' | ||
const assignee = this.resolve(app.koishi.config.autoAssign) ? this.selfId : '' | ||
if (assignee) { | ||
@@ -249,3 +301,3 @@ return app.database.createChannel(platform, id, { assignee, guildId, createdAt: new Date() }) | ||
if (user) return user | ||
const authority = this.resolve(app.config.autoAuthorize) | ||
const authority = this.resolve(app.koishi.config.autoAuthorize) | ||
const data = { locales: this.locales, authority, createdAt: new Date() } | ||
@@ -275,3 +327,3 @@ if (authority) { | ||
const fallback = this.app.model.tables.user.create() | ||
fallback.authority = this.resolve(this.app.config.autoAuthorize) | ||
fallback.authority = this.resolve(this.app.koishi.config.autoAuthorize) | ||
const user = observe(fallback, () => Promise.resolve()) | ||
@@ -330,3 +382,3 @@ return this.user = user | ||
] | ||
if (this.app.config.i18n.output === 'prefer-user') { | ||
if (this.app.koishi.config.i18n.output === 'prefer-user') { | ||
locales.unshift(...(this.user as User.Observed)?.locales || []) | ||
@@ -449,3 +501,3 @@ } else { | ||
resolve(undefined) | ||
}, options.timeout ?? this.app.config.delay.prompt) | ||
}, options.timeout ?? this.app.koishi.config.delay.prompt) | ||
}) | ||
@@ -490,1 +542,3 @@ } | ||
} | ||
export default KoishiSession |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
476860
27
8963
Updated@satorijs/core@^4.1.2
Updatedcordis@^3.17.1
Updatedminato@^3.4.1