@koishijs/core
Advanced tools
Comparing version 4.16.2 to 4.16.3
@@ -8,4 +8,4 @@ /// <reference types="node" /> | ||
import { Fragment, Schema, h, Universal, Bot, Quester } from '@satorijs/core'; | ||
import { LocaleTree } from '@koishijs/i18n-utils'; | ||
import { Disposable, GetEvents, Parameters, ReturnType, ThisType } from 'cordis'; | ||
import { LocaleTree } from '@koishijs/i18n-utils'; | ||
import { version } from '../package.json'; | ||
@@ -158,2 +158,57 @@ export interface Events { | ||
} | ||
declare const kTemplate: unique symbol; | ||
export interface Context { | ||
i18n: I18n; | ||
} | ||
export interface Events { | ||
'internal/i18n'(): void; | ||
} | ||
type GroupNames<P extends string, K extends string = never> = P extends `${string}(${infer R})${infer S}` ? GroupNames<S, K | R> : K; | ||
export type MatchResult<P extends string = never> = Record<GroupNames<P>, string>; | ||
export function createMatch<P extends string>(pattern: P): (string: string) => undefined | MatchResult<P>; | ||
export interface CompareOptions { | ||
minSimilarity?: number; | ||
} | ||
export namespace I18n { | ||
type Node = string | Store; | ||
interface Store { | ||
[kTemplate]?: string; | ||
[K: string]: Node; | ||
} | ||
type Formatter = (value: any, args: string[], locale: string) => string; | ||
type Renderer = (dict: Dict, params: any, locale: string) => string; | ||
interface FindOptions extends CompareOptions { | ||
} | ||
interface FindResult<P extends string> { | ||
locale: string; | ||
data: MatchResult<P>; | ||
similarity: number; | ||
} | ||
} | ||
export class I18n { | ||
ctx: Context; | ||
_data: Dict<Dict<string>>; | ||
_presets: Dict<I18n.Renderer>; | ||
locales: LocaleTree; | ||
constructor(ctx: Context, config: I18n.Config); | ||
fallback(locales: string[]): string[]; | ||
compare(expect: string, actual: string, options?: CompareOptions): number; | ||
get(key: string, locales?: string[]): Dict<string>; | ||
private set; | ||
define(locale: string, dict: I18n.Store): () => void; | ||
define(locale: string, key: string, value: I18n.Node): () => void; | ||
find<P extends string>(pattern: P, actual: string, options?: I18n.FindOptions): I18n.FindResult<P>[]; | ||
_render(value: I18n.Node, params: any, locale: string): h[]; | ||
/** @deprecated */ | ||
text(locales: string[], paths: string[], params: object): string; | ||
render(locales: string[], paths: string[], params: object): h[]; | ||
} | ||
export namespace I18n { | ||
interface Config { | ||
locales?: string[]; | ||
output?: 'prefer-user' | 'prefer-channel'; | ||
match?: 'strict' | 'prefer-input' | 'prefer-output'; | ||
} | ||
const Config: Schema<Config>; | ||
} | ||
declare module '@satorijs/core' { | ||
@@ -167,32 +222,32 @@ interface Context { | ||
} | ||
export interface PermissionConfig { | ||
authority?: number; | ||
permissions?: string[]; | ||
dependencies?: string[]; | ||
} | ||
declare class DAG { | ||
store: Map<string, Map<string, Computed<boolean>[]>>; | ||
define(name: string): void; | ||
delete(name: string): void; | ||
link(source: string, target: string, condition: Computed<boolean>): void; | ||
unlink(source: string, target: string, condition: Computed<boolean>): void; | ||
subgraph(parents: Iterable<string>, session: Partial<Session>, result?: Set<string>): Set<string>; | ||
} | ||
export namespace Permissions { | ||
type ProvideCallback = (name: string, session: Partial<Session>) => Awaitable<boolean>; | ||
type Links<P extends string> = undefined | string[] | ((data: MatchResult<P>) => undefined | string[]); | ||
type Check<P extends string> = (data: MatchResult<P>, session: Partial<Session>) => Awaitable<boolean>; | ||
interface Options<P extends string = string> { | ||
list?: () => string[]; | ||
check?: Check<P>; | ||
depends?: Links<P>; | ||
inherits?: Links<P>; | ||
} | ||
interface Entry extends Options { | ||
match: (string: string) => undefined | MatchResult; | ||
} | ||
interface Config { | ||
authority?: number; | ||
permissions?: string[]; | ||
dependencies?: string[]; | ||
} | ||
} | ||
export class Permissions { | ||
ctx: Context; | ||
_inherits: DAG; | ||
_depends: DAG; | ||
_providers: Dict<Permissions.ProvideCallback>; | ||
store: Permissions.Entry[]; | ||
constructor(ctx: Context); | ||
private get caller(); | ||
provide(name: string, callback: Permissions.ProvideCallback): () => void; | ||
define<P extends string>(pattern: P, options: Permissions.Options<P>): () => void; | ||
provide<P extends string>(pattern: P, check: Permissions.Check<P>): () => void; | ||
inherit<P extends string>(pattern: P, inherits: Permissions.Links<P>): () => void; | ||
depend<P extends string>(pattern: P, depends: Permissions.Links<P>): () => void; | ||
list(result?: Set<string>): string[]; | ||
check(name: string, session: Partial<Session>): Promise<boolean>; | ||
config(name: string, config?: PermissionConfig, defaultAuthority?: number): () => void; | ||
define(name: string, inherits: string[]): () => void; | ||
inherit(child: string, parent: string, condition?: Computed<boolean>): () => void; | ||
depend(dependent: string, dependency: string, condition?: Computed<boolean>): () => void; | ||
list(): string[]; | ||
subgraph(type: 'inherits' | 'depends', parents: Iterable<string>, result?: Set<string>): Set<string>; | ||
test(names: Iterable<string>, session?: Partial<Session>, cache?: Map<string, Promise<boolean>>): Promise<boolean>; | ||
@@ -283,3 +338,3 @@ } | ||
export function parseValue(source: string, kind: string, argv: Argv, decl?: Declaration): any; | ||
export interface OptionConfig<T extends Type = Type> extends PermissionConfig { | ||
export interface OptionConfig<T extends Type = Type> extends Permissions.Config { | ||
aliases?: string[]; | ||
@@ -359,4 +414,2 @@ symbols?: string[]; | ||
private _checkers; | ||
static defaultConfig: Command.Config; | ||
static defaultOptionConfig: Argv.OptionConfig; | ||
private static _userFields; | ||
@@ -368,3 +421,3 @@ private static _channelFields; | ||
static channelFields(fields: FieldCollector<'channel'>): typeof Command; | ||
constructor(name: string, decl: string, ctx: Context); | ||
constructor(name: string, decl: string, ctx: Context, config: Command.Config); | ||
get caller(): Context; | ||
@@ -406,3 +459,3 @@ get displayName(): string; | ||
export namespace Command { | ||
interface Config extends Argv.CommandBase.Config, PermissionConfig { | ||
interface Config extends Argv.CommandBase.Config, Permissions.Config { | ||
/** disallow unknown options */ | ||
@@ -461,54 +514,2 @@ checkUnknown?: boolean; | ||
} | ||
declare const kTemplate: unique symbol; | ||
export interface Context { | ||
i18n: I18n; | ||
} | ||
export interface Events { | ||
'internal/i18n'(): void; | ||
} | ||
export interface CompareOptions { | ||
minSimilarity?: number; | ||
} | ||
export namespace I18n { | ||
type Node = string | Store; | ||
interface Store { | ||
[kTemplate]?: string; | ||
[K: string]: Node; | ||
} | ||
type Formatter = (value: any, args: string[], locale: string) => string; | ||
type Renderer = (dict: Dict, params: any, locale: string) => string; | ||
interface FindOptions extends CompareOptions { | ||
} | ||
interface FindResult { | ||
locale: string; | ||
data: Dict; | ||
similarity: number; | ||
} | ||
} | ||
export class I18n { | ||
ctx: Context; | ||
_data: Dict<Dict<string>>; | ||
_presets: Dict<I18n.Renderer>; | ||
locales: LocaleTree; | ||
constructor(ctx: Context, config: I18n.Config); | ||
fallback(locales: string[]): string[]; | ||
compare(expect: string, actual: string, options?: CompareOptions): number; | ||
get(key: string, locales?: string[]): Dict<string>; | ||
private set; | ||
define(locale: string, dict: I18n.Store): () => void; | ||
define(locale: string, key: string, value: I18n.Node): () => void; | ||
find(path: string, actual: string, options?: I18n.FindOptions): I18n.FindResult[]; | ||
_render(value: I18n.Node, params: any, locale: string): h[]; | ||
/** @deprecated */ | ||
text(locales: string[], paths: string[], params: object): string; | ||
render(locales: string[], paths: string[], params: object): h[]; | ||
} | ||
export namespace I18n { | ||
interface Config { | ||
locales?: string[]; | ||
output?: 'prefer-user' | 'prefer-channel'; | ||
match?: 'strict' | 'prefer-input' | 'prefer-output'; | ||
} | ||
const Config: Schema<Config>; | ||
} | ||
export interface PromptOptions { | ||
@@ -515,0 +516,0 @@ timeout?: number; |
{ | ||
"name": "@koishijs/core", | ||
"description": "Core Features for Koishi", | ||
"version": "4.16.2", | ||
"version": "4.16.3", | ||
"main": "lib/index.cjs", | ||
@@ -35,9 +35,9 @@ "module": "lib/index.mjs", | ||
"@koishijs/i18n-utils": "^1.0.0", | ||
"@koishijs/utils": "^7.1.1", | ||
"@koishijs/utils": "^7.1.2", | ||
"@minatojs/core": "^2.8.1", | ||
"@satorijs/core": "^3.3.2", | ||
"@satorijs/core": "^3.3.4", | ||
"cordis": "^3.4.1", | ||
"cosmokit": "^1.5.1", | ||
"cosmokit": "^1.5.2", | ||
"fastest-levenshtein": "^1.0.16" | ||
} | ||
} |
@@ -8,3 +8,3 @@ import { Awaitable, camelize, Dict, isNullable, remove } from 'cosmokit' | ||
import { FieldCollector, Session } from '../session' | ||
import { PermissionConfig } from '../permission' | ||
import { Permissions } from '../permission' | ||
import { Context } from '../context' | ||
@@ -61,10 +61,2 @@ | ||
static defaultConfig: Command.Config = { | ||
showWarning: true, | ||
handleError: true, | ||
slash: true, | ||
} | ||
static defaultOptionConfig: Argv.OptionConfig = {} | ||
private static _userFields: FieldCollector<'user'>[] = [] | ||
@@ -85,4 +77,10 @@ private static _channelFields: FieldCollector<'channel'>[] = [] | ||
constructor(name: string, decl: string, ctx: Context) { | ||
super(name, decl, ctx, { ...Command.defaultConfig }) | ||
constructor(name: string, decl: string, ctx: Context, config: Command.Config) { | ||
super(name, decl, ctx, { | ||
showWarning: true, | ||
handleError: true, | ||
slash: true, | ||
...config, | ||
}) | ||
this.config.permissions ??= [`authority:${config?.authority ?? 1}`] | ||
this._registerAlias(name) | ||
@@ -109,8 +107,5 @@ ctx.$commander._commandList.push(this) | ||
set parent(parent: Command) { | ||
// We do not use `ctx.permissions.depend()` here | ||
// because the permission `command.${name}` itself is disposable. | ||
if (this._parent === parent) return | ||
if (this._parent) { | ||
remove(this._parent.children, this) | ||
this.ctx.permissions._depends.unlink(`command.${this.name}`, `command.${this._parent.name}`, true) | ||
} | ||
@@ -120,3 +115,2 @@ this._parent = parent | ||
parent.children.push(this) | ||
this.ctx.permissions._depends.link(`command.${this.name}`, `command.${parent.name}`, true) | ||
} | ||
@@ -250,9 +244,6 @@ } | ||
} | ||
const config = { | ||
...Command.defaultOptionConfig, | ||
...args[0] as Argv.OptionConfig, | ||
} | ||
const config = { ...args[0] as Argv.OptionConfig } | ||
config.permissions ??= [`authority:${config.authority ?? 0}`] | ||
this._createOption(name, desc, config) | ||
this.caller.collect('command.option', () => this.removeOption(name)) | ||
this.caller.permissions.config(`command.${this.name}.option.${name}`, config, 0) | ||
this.caller.collect('option', () => this.removeOption(name)) | ||
return this | ||
@@ -392,3 +383,3 @@ } | ||
export namespace Command { | ||
export interface Config extends Argv.CommandBase.Config, PermissionConfig { | ||
export interface Config extends Argv.CommandBase.Config, Permissions.Config { | ||
/** disallow unknown options */ | ||
@@ -407,3 +398,4 @@ checkUnknown?: boolean | ||
export const Config: Schema<Config> = Schema.object({ | ||
authority: Schema.natural().description('指令的权限等级。').default(1).hidden(), | ||
permissions: Schema.array(String).role('perms').default(['authority:1']).description('权限继承。'), | ||
dependencies: Schema.array(String).role('perms').description('权限依赖。'), | ||
slash: Schema.boolean().description('启用斜线指令功能。').default(true), | ||
@@ -410,0 +402,0 @@ checkUnknown: Schema.boolean().description('是否检查未知选项。').default(false).hidden(), |
@@ -125,3 +125,3 @@ import { Awaitable, defineProperty } from 'cosmokit' | ||
name = this.resolve(name)!.name | ||
return ctx.permissions.test(`command.${name}`, session, cache) | ||
return ctx.permissions.test(`command:${name}`, session, cache) | ||
}, | ||
@@ -137,3 +137,4 @@ }) | ||
ctx.schema.extend('command-option', Schema.object({ | ||
authority: Schema.computed(Schema.natural()).description('选项的权限等级。').default(0).hidden(), | ||
permissions: Schema.array(String).role('perms').default(['authority:0']).description('权限继承。'), | ||
dependencies: Schema.array(String).role('perms').description('权限依赖。'), | ||
}), 1000) | ||
@@ -251,3 +252,3 @@ | ||
const isLast = index === segments.length - 1 | ||
command = new Command(name, isLast ? decl : '', caller) | ||
command = new Command(name, isLast ? decl : '', caller, isLast ? config : {}) | ||
command._disposables.push(caller.i18n.define('', { | ||
@@ -257,3 +258,2 @@ [`commands.${command.name}.$`]: '', | ||
})) | ||
command._disposables.push(caller.permissions.config(`command.${name}`, isLast ? config : {}, 1)) | ||
created.push(command) | ||
@@ -260,0 +260,0 @@ root ||= command |
@@ -7,3 +7,3 @@ import { camelCase, Dict, paramCase, Time } from 'cosmokit' | ||
import { Next } from '../middleware' | ||
import { PermissionConfig } from '../permission' | ||
import { Permissions } from '../permission' | ||
import { Disposable } from 'cordis' | ||
@@ -373,3 +373,3 @@ import { Session } from '../session' | ||
export interface OptionConfig<T extends Type = Type> extends PermissionConfig { | ||
export interface OptionConfig<T extends Type = Type> extends Permissions.Config { | ||
aliases?: string[] | ||
@@ -376,0 +376,0 @@ symbols?: string[] |
@@ -6,2 +6,32 @@ import { isNullable } from 'cosmokit' | ||
export default function validate(ctx: Context) { | ||
ctx.permissions.define('command:(name)', { | ||
depends: ({ name }) => { | ||
const command = ctx.$commander.get(name) | ||
if (!command) return | ||
const depends = [...command.config.dependencies ?? []] | ||
if (command.parent) depends.push(`command:${command.parent.name}`) | ||
return depends | ||
}, | ||
inherits: ({ name }) => { | ||
return ctx.$commander.get(name)?.config.permissions | ||
}, | ||
list: () => { | ||
return ctx.$commander._commandList.map(command => `command:${command.name}`) | ||
}, | ||
}) | ||
ctx.permissions.define('command:(name):option:(name2)', { | ||
depends: ({ name, name2 }) => { | ||
return ctx.$commander.get(name)?._options[name2]?.dependencies | ||
}, | ||
inherits: ({ name, name2 }) => { | ||
return ctx.$commander.get(name)?._options[name2]?.permissions | ||
}, | ||
list: () => { | ||
return ctx.$commander._commandList.flatMap(command => { | ||
return Object.keys(command._options).map(name => `command:${command.name}:option:${name}`) | ||
}) | ||
}, | ||
}) | ||
// check user | ||
@@ -17,9 +47,9 @@ ctx.before('command/execute', async (argv: Argv<'authority'>) => { | ||
// check permissions | ||
const permissions = [`command.${command.name}`] | ||
const permissions = [`command:${command.name}`] | ||
for (const option of Object.values(command._options)) { | ||
if (option.name in options) { | ||
permissions.push(`command.${command.name}.option.${option.name}`) | ||
permissions.push(`command:${command.name}:option:${option.name}`) | ||
} | ||
} | ||
if (!await ctx.permissions.test(permissions, session as any)) { | ||
if (!await ctx.permissions.test(permissions, session)) { | ||
return sendHint('internal.low-authority') | ||
@@ -26,0 +56,0 @@ } |
@@ -22,2 +22,27 @@ import { distance } from 'fastest-levenshtein' | ||
type GroupNames<P extends string, K extends string = never> = | ||
| P extends `${string}(${infer R})${infer S}` | ||
? GroupNames<S, K | R> | ||
: K | ||
export type MatchResult<P extends string = never> = Record<GroupNames<P>, string> | ||
export function createMatch<P extends string>(pattern: P): (string: string) => undefined | MatchResult<P> { | ||
const groups: string[] = [] | ||
const source = pattern.replace(/\(([^)]+)\)/g, (_, name) => { | ||
groups.push(name) | ||
return '(.+)' | ||
}) | ||
const regexp = new RegExp(`^${source}$`) | ||
return (string: string) => { | ||
const capture = regexp.exec(string) | ||
if (!capture) return | ||
const data: any = {} | ||
for (let i = 0; i < groups.length; i++) { | ||
data[groups[i]] = capture[i + 1] | ||
} | ||
return data | ||
} | ||
} | ||
export interface CompareOptions { | ||
@@ -40,5 +65,5 @@ minSimilarity?: number | ||
export interface FindResult { | ||
export interface FindResult<P extends string> { | ||
locale: string | ||
data: Dict | ||
data: MatchResult<P> | ||
similarity: number | ||
@@ -119,15 +144,10 @@ } | ||
find(path: string, actual: string, options: I18n.FindOptions = {}): I18n.FindResult[] { | ||
find<P extends string>(pattern: P, actual: string, options: I18n.FindOptions = {}): I18n.FindResult<P>[] { | ||
if (!actual) return [] | ||
const groups: string[] = [] | ||
path = path.replace(/\(([^)]+)\)/g, (_, name) => { | ||
groups.push(name) | ||
return '([^.]+)' | ||
}) | ||
const pattern = new RegExp(`^${path}$`) | ||
const results: I18n.FindResult[] = [] | ||
const match = createMatch(pattern) | ||
const results: I18n.FindResult<P>[] = [] | ||
for (const locale in this._data) { | ||
for (const path in this._data[locale]) { | ||
const capture = pattern.exec(path) | ||
if (!capture) continue | ||
const data = match(path) | ||
if (!data) continue | ||
const expect = this._data[locale][path] | ||
@@ -137,6 +157,2 @@ if (typeof expect !== 'string') continue | ||
if (!similarity) continue | ||
const data = {} | ||
for (let i = 0; i < groups.length; i++) { | ||
data[groups[i]] = capture[i + 1] | ||
} | ||
results.push({ locale, data, similarity }) | ||
@@ -143,0 +159,0 @@ } |
import { Logger } from '@satorijs/core' | ||
import { Awaitable, Dict, remove } from 'cosmokit' | ||
import { Computed } from './filter' | ||
import { Awaitable, defineProperty, remove } from 'cosmokit' | ||
import { Session } from './session' | ||
import { Context } from './context' | ||
import { createMatch, MatchResult } from './i18n' | ||
@@ -19,72 +19,43 @@ const logger = new Logger('app') | ||
export interface PermissionConfig { | ||
authority?: number | ||
permissions?: string[] | ||
dependencies?: string[] | ||
} | ||
export namespace Permissions { | ||
export type Links<P extends string> = undefined | string[] | ((data: MatchResult<P>) => undefined | string[]) | ||
export type Check<P extends string> = (data: MatchResult<P>, session: Partial<Session>) => Awaitable<boolean> | ||
class DAG { | ||
store: Map<string, Map<string, Computed<boolean>[]>> = new Map() | ||
define(name: string) { | ||
this.delete(name) | ||
this.store.set(name, new Map()) | ||
export interface Options<P extends string = string> { | ||
list?: () => string[] | ||
check?: Check<P> | ||
depends?: Links<P> | ||
inherits?: Links<P> | ||
} | ||
delete(name: string) { | ||
this.store.delete(name) | ||
for (const map of this.store.values()) { | ||
map.delete(name) | ||
} | ||
export interface Entry extends Options { | ||
match: (string: string) => undefined | MatchResult | ||
} | ||
link(source: string, target: string, condition: Computed<boolean>) { | ||
if (!this.store.has(source)) this.store.set(source, new Map()) | ||
const map = this.store.get(source) | ||
if (!map.has(target)) map.set(target, []) | ||
map.get(target).push(condition) | ||
export interface Config { | ||
authority?: number | ||
permissions?: string[] | ||
dependencies?: string[] | ||
} | ||
unlink(source: string, target: string, condition: Computed<boolean>) { | ||
const list = this.store.get(source)?.get(target) | ||
if (list) remove(list, condition) | ||
} | ||
subgraph(parents: Iterable<string>, session: Partial<Session>, result = new Set<string>()): Set<string> { | ||
let node: string | ||
const queue = [...parents] | ||
while ((node = queue.shift())) { | ||
if (result.has(node)) continue | ||
result.add(node) | ||
const map = this.store.get(node) | ||
if (!map) continue | ||
for (const [key, conditions] of map) { | ||
if (conditions.every(value => !session.resolve(value))) continue | ||
queue.push(key) | ||
} | ||
} | ||
return result | ||
} | ||
} | ||
export namespace Permissions { | ||
export type ProvideCallback = (name: string, session: Partial<Session>) => Awaitable<boolean> | ||
} | ||
export class Permissions { | ||
_inherits = new DAG() | ||
_depends = new DAG() | ||
_providers: Dict<Permissions.ProvideCallback> = Object.create(null) | ||
public store: Permissions.Entry[] = [] | ||
constructor(public ctx: Context) { | ||
this.provide('authority.*', (name, { user }: Partial<Session<'authority'>>) => { | ||
const value = +name.slice(10) | ||
return !user || user.authority >= value | ||
defineProperty(this, Context.current, ctx) | ||
ctx.alias('permissions', ['perms']) | ||
this.define('authority:(value)', { | ||
check: ({ value }, { user }: Partial<Session<'authority'>>) => { | ||
return !user || user.authority >= +value | ||
}, | ||
list: () => Array(5).fill(0).map((_, i) => `authority:${i}`), | ||
}) | ||
this.provide('*', (name, session) => { | ||
this.provide('(name)', ({ name }, session) => { | ||
return session.bot?.checkPermission(name, session) | ||
}) | ||
this.provide('*', (name, session: Partial<Session<'permissions', 'permissions'>>) => { | ||
this.provide('(name)', ({ name }, session: Partial<Session<'permissions', 'permissions'>>) => { | ||
return session.permissions?.includes(name) | ||
@@ -100,71 +71,66 @@ || session.user?.permissions?.includes(name) | ||
provide(name: string, callback: Permissions.ProvideCallback) { | ||
this._providers[name] = callback | ||
return this.caller?.collect('permission-provide', () => { | ||
return delete this._providers[name] | ||
define<P extends string>(pattern: P, options: Permissions.Options<P>) { | ||
const entry: Permissions.Entry = { | ||
...options, | ||
match: createMatch(pattern), | ||
} | ||
if (!pattern.includes('(')) entry.list ||= () => [pattern] | ||
return this.caller.effect(() => { | ||
this.store.push(entry) | ||
return () => remove(this.store, entry) | ||
}) | ||
} | ||
async check(name: string, session: Partial<Session>) { | ||
try { | ||
const callbacks = Object.entries(this._providers) | ||
.filter(([key]) => name === key || key.endsWith('*') && name.startsWith(key.slice(0, -1))) | ||
.map(([key, value]) => value) | ||
if (!callbacks.length) return false | ||
for (const callback of callbacks) { | ||
if (await callback(name, session)) return true | ||
} | ||
return false | ||
} catch (error) { | ||
logger.warn(error) | ||
return false | ||
} | ||
provide<P extends string>(pattern: P, check: Permissions.Check<P>) { | ||
return this.define(pattern, { check }) | ||
} | ||
config(name: string, config: PermissionConfig = {}, defaultAuthority = 0) { | ||
for (const dep of config.dependencies || []) { | ||
this._depends.link(name, dep, true) | ||
} | ||
const children = config.permissions || [] | ||
if (!config.permissions || typeof config.authority === 'number') { | ||
children.push(`authority.${config.authority ?? defaultAuthority}`) | ||
} | ||
for (const child of children) { | ||
this._inherits.link(name, child, true) | ||
} | ||
return this.caller?.collect('permission-config', () => { | ||
this._depends.delete(name) | ||
this._inherits.delete(name) | ||
this.ctx.emit('internal/permission') | ||
}) | ||
inherit<P extends string>(pattern: P, inherits: Permissions.Links<P>) { | ||
return this.define(pattern, { inherits }) | ||
} | ||
define(name: string, inherits: string[]) { | ||
this._inherits.define(name) | ||
this.ctx.emit('internal/permission') | ||
for (const permission of inherits) { | ||
this.inherit(name, permission) | ||
} | ||
return this.caller?.collect('permission-define', () => { | ||
this._inherits.delete(name) | ||
this.ctx.emit('internal/permission') | ||
}) | ||
depend<P extends string>(pattern: P, depends: Permissions.Links<P>) { | ||
return this.define(pattern, { depends }) | ||
} | ||
inherit(child: string, parent: string, condition: Computed<boolean> = true) { | ||
this._inherits.link(parent, child, condition) | ||
return this.caller?.collect('permission-inherit', () => { | ||
this._inherits.unlink(parent, child, condition) | ||
}) | ||
list(result = new Set<string>()) { | ||
for (const { list } of this.store) { | ||
if (!list) continue | ||
for (const name of list()) { | ||
result.add(name) | ||
} | ||
} | ||
return [...result] | ||
} | ||
depend(dependent: string, dependency: string, condition: Computed<boolean> = true) { | ||
this._depends.link(dependent, dependency, condition) | ||
return this.caller?.collect('permission-depend', () => { | ||
this._depends.unlink(dependent, dependency, condition) | ||
}) | ||
async check(name: string, session: Partial<Session>) { | ||
const results = await Promise.all(this.store.map(async ({ match, check }) => { | ||
if (!check) return false | ||
const data = match(name) | ||
if (!data) return false | ||
try { | ||
return await check(data, session) | ||
} catch (error) { | ||
logger.warn(error) | ||
return false | ||
} | ||
})) | ||
return results.some(Boolean) | ||
} | ||
list() { | ||
return [...this._inherits.store.keys()] | ||
subgraph(type: 'inherits' | 'depends', parents: Iterable<string>, result = new Set<string>()): Set<string> { | ||
let name: string | ||
const queue = [...parents] | ||
while ((name = queue.shift())) { | ||
if (result.has(name)) continue | ||
result.add(name) | ||
for (const entry of this.store) { | ||
const data = entry.match(name) | ||
if (!data) continue | ||
let links = entry[type] | ||
if (typeof links === 'function') links = links(data) | ||
if (Array.isArray(links)) queue.push(...links) | ||
} | ||
} | ||
return result | ||
} | ||
@@ -175,4 +141,4 @@ | ||
if (typeof names === 'string') names = [names] | ||
for (const name of this._depends.subgraph(names, session)) { | ||
const parents = [...this._inherits.subgraph([name], session)] | ||
for (const name of this.subgraph('depends', names)) { | ||
const parents = [...this.subgraph('inherits', [name])] | ||
const results = await Promise.all(parents.map(parent => { | ||
@@ -179,0 +145,0 @@ let result = cache.get(parent) |
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
469096
8856
Updated@koishijs/utils@^7.1.2
Updated@satorijs/core@^3.3.4
Updatedcosmokit@^1.5.2