diff --git a/packages/core/src/context.ts b/packages/core/src/context.ts index 3b2577786..7c5dd67ea 100644 --- a/packages/core/src/context.ts +++ b/packages/core/src/context.ts @@ -12,6 +12,7 @@ import { Processor } from './middleware' import { Permissions } from './permission' import DatabaseMixin from './database' import BotMixin from './bot' +import { SchemaService } from './schema' export type EffectScope = cordis.EffectScope export type ForkScope = cordis.ForkScope @@ -58,6 +59,7 @@ export class Context extends satori.Context { ]) this.mixin('$commander', ['command']) this.provide('$filter', new FilterService(this), true) + this.provide('schema', new SchemaService(this), true) this.provide('$processor', new Processor(this), true) this.provide('i18n', new I18n(this, this.config.i18n), true) this.provide('permissions', new Permissions(this), true) diff --git a/packages/core/src/database.ts b/packages/core/src/database.ts index d84ad3fd4..63f502027 100644 --- a/packages/core/src/database.ts +++ b/packages/core/src/database.ts @@ -99,7 +99,6 @@ class KoishiDatabase { getAssignedChannels: 'database.getAssignedChannels', setChannel: 'database.setChannel', createChannel: 'database.createChannel', - getSelfIds: 'database.getSelfIds', broadcast: 'broadcast', }) diff --git a/packages/core/src/filter.ts b/packages/core/src/filter.ts index 3d13edc76..67aa9eb9c 100644 --- a/packages/core/src/filter.ts +++ b/packages/core/src/filter.ts @@ -1,6 +1,5 @@ import { defineProperty } from 'cosmokit' import { Eval } from 'minato' -import { Schema } from '@satorijs/core' import { Channel, User } from './database' import { Context } from './context' import { Session } from './session' @@ -40,11 +39,11 @@ function property(ctx: Context, key: K, ...values: Sess } 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 runtime.ctx.filter = (session) => { return runtime.children.some(p => p.ctx.filter(session)) @@ -52,118 +51,50 @@ export class FilterService { }) } - 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) - } - - private(...values: string[]) { - return property(this.caller.exclude(property(this.caller, 'guildId')), 'userId', ...values) + return property(this.ctx, 'platform', ...values) } -} -declare global { - interface Schemastery { - computed(options?: Computed.Options): Schema, Computed> - } - - namespace Schemastery { - interface Static { - path(options?: Path.Options): Schema - filter(): Schema> - computed(inner: X, options?: Computed.Options): Schema>, Computed>> - dynamic(name: string): Schema - } - - namespace Path { - interface Options { - filters?: Filter[] - allowCreate?: boolean - } - - type Filter = FileFilter | 'file' | 'directory' - - interface FileFilter { - name: string - extensions: string[] - } - } + private() { + return this.ctx.intersect((session) => session.isDirect) } } - -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) -} diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 8ac279724..960b9e3e8 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -8,6 +8,7 @@ export * from './database' export * from './filter' export * from './i18n' export * from './middleware' +export * from './schema' export * from './session' export * from './permission' export * from './command' diff --git a/packages/core/src/schema.ts b/packages/core/src/schema.ts new file mode 100644 index 000000000..c03a16141 --- /dev/null +++ b/packages/core/src/schema.ts @@ -0,0 +1,120 @@ +import { Dict, remove } from 'cosmokit' +import { Schema } from '@satorijs/core' +import { Computed } from './filter' +import { Context } from './context' + +declare global { + interface Schemastery { + computed(options?: Computed.Options): Schema, Computed> + } + + namespace Schemastery { + interface Static { + path(options?: Path.Options): Schema + filter(): Schema> + computed(inner: X, options?: Computed.Options): Schema>, Computed>> + 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) +} + +const kSchemaOrder = Symbol('schema-order') + +declare module '@satorijs/core' { + interface Context { + schema: SchemaService + } + + interface Events { + 'internal/schema'(name: string): void + } +} + +export class SchemaService { + _data: Dict = Object.create(null) + + constructor(public ctx: Context) { + this.extend('intercept.http', Schema.object({ + timeout: Schema.natural().role('ms').description('等待连接建立的最长时间。'), + proxyAgent: Schema.string().description('使用的代理服务器地址。'), + keepAlive: Schema.boolean().description('是否保持连接。'), + })) + } + + extend(name: string, schema: Schema, order = 0) { + const caller = this[Context.current] + const target = this.get(name) + const index = target.list.findIndex(a => a[kSchemaOrder] < order) + schema[kSchemaOrder] = order + if (index >= 0) { + target.list.splice(index, 0, schema) + } else { + target.list.push(schema) + } + this.ctx.emit('internal/schema', name) + caller?.on('dispose', () => { + remove(target.list, schema) + this.ctx.emit('internal/schema', name) + }) + } + + get(name: string) { + return this._data[name] ||= Schema.intersect([]) + } + + set(name: string, schema: Schema) { + const caller = this[Context.current] + this._data[name] = schema + this.ctx.emit('internal/schema', name) + caller?.on('dispose', () => { + delete this._data[name] + this.ctx.emit('internal/schema', name) + }) + } +}