Commit 33ad7ee2 authored by nanahira's avatar nanahira

first

parent 6192f216
import {
BeforeEventName,
CommandConfigExtended,
CommandDefinitionFun,
EventName,
KoishiCommandDefinition,
KoishiCommandPutDef,
KoishiOnContextScope,
OnContextFunction,
} from './def';
import 'reflect-metadata';
import { Argv, Command, FieldCollector, Selection, Session } from 'koishi';
import { Metadata } from './meta/metadata.decorators';
import { CommandPut, DoRegister } from './registry';
// Register method
export const UseMiddleware = DoRegister.middleware;
export const UseEvent = (name: EventName, prepend?: boolean): MethodDecorator =>
DoRegister.event({ name, prepend });
export const UseBeforeEvent = (
name: BeforeEventName,
prepend?: boolean,
): MethodDecorator => DoRegister.beforeEvent({ name, prepend });
export const UsePlugin = (): MethodDecorator => DoRegister.plugin();
export const Get = (path: string) => DoRegister.route({ path, method: 'get' });
export const Post = (path: string) =>
DoRegister.route({ path, method: 'post' });
export const Put = (path: string) => DoRegister.route({ path, method: 'put' });
export const Delete = (path: string) =>
DoRegister.route({ path, method: 'delete' });
export const Patch = (path: string) =>
DoRegister.route({ path, method: 'patch' });
export const Options = (path: string) =>
DoRegister.route({ path, method: 'options' });
export const Head = (path: string) =>
DoRegister.route({ path, method: 'head' });
export const All = (path: string) => DoRegister.route({ path, method: 'all' });
export const Ws = DoRegister.ws;
export function UseCommand<D extends string>(
def: D,
config?: CommandConfigExtended,
): MethodDecorator;
export function UseCommand<D extends string>(
def: D,
desc: string,
config?: CommandConfigExtended,
): MethodDecorator;
export function UseCommand(
def: string,
...args: [CommandConfigExtended?] | [string, CommandConfigExtended?]
): MethodDecorator {
const desc = typeof args[0] === 'string' ? (args.shift() as string) : '';
const config = args[0] as CommandConfigExtended;
return (obj, key: string, des) => {
const putOptions: CommandPut.Config<keyof CommandPut.ConfigMap>[] =
Reflect.getMetadata(KoishiCommandPutDef, obj.constructor, key) ||
undefined;
const metadataDec = DoRegister.command({
def,
desc,
config,
putOptions,
});
return metadataDec(obj, key, des);
};
}
// Context scopes
export const OnContext = (
ctxFun: OnContextFunction,
): MethodDecorator & ClassDecorator =>
Metadata.append(KoishiOnContextScope, ctxFun);
export const OnAnywhere = () => OnContext((ctx) => ctx.any());
export const OnNowhere = () => OnContext((ctx) => ctx.never());
export const OnUser = (...values: string[]) =>
OnContext((ctx) => ctx.user(...values));
export const OnSelf = (...values: string[]) =>
OnContext((ctx) => ctx.self(...values));
export const OnGuild = (...values: string[]) =>
OnContext((ctx) => ctx.guild(...values));
export const OnChannel = (...values: string[]) =>
OnContext((ctx) => ctx.channel(...values));
export const OnPlatform = (...values: string[]) =>
OnContext((ctx) => ctx.platform(...values));
export const OnPrivate = (...values: string[]) =>
OnContext((ctx) => ctx.private(...values));
export const OnSelection = (selection: Selection) =>
OnContext((ctx) => ctx.select(selection));
// Command definition
export const CommandDef = (
def: CommandDefinitionFun,
): MethodDecorator & ClassDecorator =>
Metadata.append(KoishiCommandDefinition, def);
export const CommandUse = <T extends Command, R extends any[]>(
callback: (command: Command, ...args: R) => T,
...args: R
) => CommandDef((cmd) => callback(cmd, ...args));
export const CommandDescription = (desc: string) =>
CommandDef((cmd) => {
cmd.description = desc;
return cmd;
});
export const CommandAlias = (...names: string[]) =>
CommandDef((cmd) => cmd.alias(...names));
export const CommandShortcut = (
name: string | RegExp,
config: Command.Shortcut = {},
) => CommandDef((cmd) => cmd.shortcut(name, config));
export const CommandUsage = (text: Command.Usage) =>
CommandDef((cmd) => cmd.usage(text));
export const CommandExample = (text: string) =>
CommandDef((cmd) => cmd.example(text));
export const CommandOption = (
name: string,
desc: string,
config: Argv.OptionConfig = {},
) => CommandDef((cmd) => cmd.option(name, desc, config));
export const CommandUserFields = (fields: FieldCollector<'user'>) =>
CommandDef((cmd) => cmd.userFields(fields));
export const CommandChannelFields = (fields: FieldCollector<'channel'>) =>
CommandDef((cmd) => cmd.channelFields(fields));
export const CommandBefore = (callback: Command.Action, append = false) =>
CommandDef((cmd) => cmd.before(callback, append));
export const CommandAction = (callback: Command.Action, prepend = false) =>
CommandDef((cmd) => cmd.action(callback, prepend));
// Command put config
export const PutArgv = (field?: keyof Argv) =>
field ? CommandPut.decorate('argvField', field) : CommandPut.decorate('argv');
export const PutSession = (field?: keyof Session) =>
field ? CommandPut.decorate('sessionField', field) : PutArgv('session');
export const PutArg = (i: number) => CommandPut.decorate('arg', i);
export const PutArgs = () => CommandPut.decorate('args');
export const PutOption = (
name: string,
desc: string,
config: Argv.OptionConfig = {},
) => CommandPut.decorate('option', { name, desc, config });
export const PutUser = (field: FieldCollector<'user'>) =>
CommandPut.decorate('user', field);
export const PutChannel = (field: FieldCollector<'channel'>) =>
CommandPut.decorate('channel', field);
export const PutUserName = (useDatabase = true) =>
CommandPut.decorate('username', useDatabase);
export const PutUserId = () => PutSession('userId');
export const PutGuildId = () => PutSession('guildId');
export const PutGuildName = () => PutSession('guildName');
export const PutChannelId = () => PutSession('channelId');
export const PutChannelName = () => PutSession('channelName');
export const PutSelfId = () => PutSession('selfId');
export const PutBot = () => PutSession('bot');
export const PutNext = () => PutArgv('next');
// metadatas
import { CommandDefinitionFun, OnContextFunction } from './interfaces';
import { DoRegister } from '../registry';
export const KoishiOnContextScope = 'KoishiOnContextScope';
export const KoishiDoRegister = 'KoishiDoRegister';
export const KoishiDoRegisterKeys = 'KoishiDoRegisterKeys';
export const KoishiCommandDefinition = 'KoishiCommandDefinition';
export const KoishiCommandPutDef = 'KoishiCommandPutDef';
// metadata map
export interface MetadataArrayMap {
KoishiOnContextScope: OnContextFunction;
KoishiCommandDefinition: CommandDefinitionFun;
KoishiDoRegisterKeys: string;
}
export interface MetadataMap {
KoishiDoRegister: DoRegister.Config;
}
export * from './interfaces';
export * from './constants';
import { Argv, Command, Context, EventMap, Plugin, Selection } from 'koishi';
import type { DefaultContext, DefaultState, ParameterizedContext } from 'koa';
import type { RouterParamContext } from '@koa/router';
import { CommandPut } from '../registry/registries/command-put';
export interface Type<T = any> extends Function {
new (...args: any[]): T;
}
export interface ContextSelector {
select?: Selection;
useSelector?: OnContextFunction;
}
export type PluginOptions<T extends Plugin> = boolean | Plugin.Config<T>;
export interface PluginDefinitionExact<T extends Plugin>
extends ContextSelector {
plugin: T;
options?: boolean | PluginOptions<T>;
}
export interface PluginDefinitionName extends ContextSelector {
plugin: string;
options?: any;
}
export type PluginDefinition<T extends Plugin = any> =
| PluginDefinitionExact<T>
| PluginDefinitionName;
export function PluginDef(
name: string,
options?: any,
select?: Selection,
): PluginDefinitionName;
export function PluginDef<T extends Plugin>(
plugin: T,
options?: PluginOptions<T>,
select?: Selection,
): PluginDefinitionExact<T>;
export function PluginDef<T extends Plugin>(
plugin: T,
options?: PluginOptions<T>,
select?: Selection,
): PluginDefinition<T> {
return { plugin, options, select };
}
export interface CommonEventNameAndPrepend<T extends keyof any> {
name: T;
prepend?: boolean;
}
export type EventName = keyof EventMap;
export type EventNameAndPrepend = CommonEventNameAndPrepend<EventName>;
type OmitSubstring<
S extends string,
T extends string,
> = S extends `${infer L}${T}${infer R}` ? `${L}${R}` : never;
export type BeforeEventName = OmitSubstring<EventName & string, 'before-'>;
export type BeforeEventNameAndPrepend =
CommonEventNameAndPrepend<BeforeEventName>;
export type ContextFunction<T> = (ctx: Context) => T;
export type OnContextFunction = ContextFunction<Context>;
export interface MappingStruct<
T extends Record<string | number | symbol, any>,
K extends keyof T,
> {
type: K;
data?: T[K];
}
export function GenerateMappingStruct<
T extends Record<string | number | symbol, any>,
K extends keyof T,
>(type: K, data?: T[K]): MappingStruct<T, K> {
return {
type,
data,
};
}
// Command stuff
export interface CommandRegisterConfig<D extends string = string> {
def: D;
desc?: string;
config?: CommandConfigExtended;
putOptions?: CommandPut.Config[];
}
export interface CommandConfigExtended extends Command.Config {
empty?: boolean;
}
export interface CommandOptionConfig {
name: string;
desc: string;
config?: Argv.OptionConfig;
}
export type CommandDefinitionFun = (cmd: Command) => Command;
export interface KoishiRouteDef {
path: string;
method:
| 'get'
| 'post'
| 'put'
| 'delete'
| 'patch'
| 'options'
| 'head'
| 'all';
}
export type KoaContext = ParameterizedContext<
DefaultState,
DefaultContext & RouterParamContext<DefaultState, DefaultContext>,
any
>;
import 'reflect-metadata';
import { Reflector } from 'typed-reflector';
import { MetadataArrayMap, MetadataMap } from '../def';
export const reflector = new Reflector<MetadataMap, MetadataArrayMap>();
import 'reflect-metadata';
import { MetadataSetter } from 'typed-reflector';
import { MetadataArrayMap, MetadataMap } from '../def';
export const Metadata = new MetadataSetter<MetadataMap, MetadataArrayMap>();
import {
KoishiDoRegister,
KoishiDoRegisterKeys,
KoishiOnContextScope,
} from './def';
import { reflector } from './meta/meta-fetch';
import { Context } from 'koishi';
import { getContextFromFilters } from './utility';
import { DoRegister } from './registry';
export interface DoRegisterResult extends DoRegister.Config {
key: string;
result: any;
}
export class DeclarationRegistrar<T = any> {
constructor(private obj: T, private alternativeObject?: any) {}
getAllFieldsToRegister(): (keyof T)[] {
const arr = reflector.getArray(KoishiDoRegisterKeys, this.obj);
return arr as (keyof T)[];
}
getScopeContext(ctx: Context, key?: keyof T & string) {
let contextFilters = reflector.getArray(
KoishiOnContextScope,
this.obj,
key,
);
if (this.alternativeObject) {
contextFilters = contextFilters.concat(
reflector.getArray(KoishiOnContextScope, this.alternativeObject, key),
);
}
return getContextFromFilters(ctx, contextFilters);
}
register(ctx: Context, key: keyof T & string): DoRegisterResult {
const data = reflector.get(KoishiDoRegister, this, key);
if (!data) return;
const result = DoRegister.registry.execute(
data,
ctx,
this.obj,
key,
this.alternativeObject,
);
return { ...data, key, result };
}
registerAll(ctx: Context, autoScope = false) {
if (autoScope) {
ctx = this.getScopeContext(ctx);
}
return this.getAllFieldsToRegister().map((_key) => {
const key = _key as keyof T & string;
return this.register(
this.getScopeContext(ctx, key),
key as keyof T & string,
);
});
}
}
import { MappingStruct } from '../def';
export class AbstractRegistry<M extends Record<string | number | symbol, any>> {
private map = new Map<keyof M, any>();
extend<K extends keyof M>(name: K, value: M[K]) {
this.map.set(name, value);
return;
}
get<K extends keyof M>(name: K): M[K] {
return this.map.get(name) as M[K];
}
}
export type MethodType<T, R = any, EXT extends any[] = []> = (
data: T,
...ext: EXT
) => R;
export type MethodMap<
M extends Record<string | number | symbol, any>,
R = any,
EXT extends any[] = [],
> = {
[K in keyof M]: MethodType<M[K], R, EXT>;
};
export class MethodRegistry<
M extends Record<string | number | symbol, any>,
R = any,
EXT extends any[] = [],
> extends AbstractRegistry<MethodMap<M, R, EXT>> {
execute<K extends keyof M>(info: MappingStruct<M, K>, ...ext: EXT) {
const fun = this.get(info.type);
if (!fun) {
return;
}
return fun(info.data, ...ext);
}
}
export * from './registries/command-put';
export * from './registries/do-register';
import { Argv, Command, FieldCollector, Session, User } from 'koishi';
import {
CommandOptionConfig,
GenerateMappingStruct,
KoishiCommandPutDef,
MappingStruct,
} from '../../def';
import { MethodRegistry } from '../abstract-registry';
// eslint-disable-next-line @typescript-eslint/no-namespace
export namespace CommandPut {
export interface ConfigMap {
args: void;
arg: number;
argv: void;
argvField: keyof Argv;
option: CommandOptionConfig;
user: FieldCollector<'user'>;
channel: FieldCollector<'channel'>;
username: boolean;
sessionField: keyof Session;
}
export type Config<K extends keyof ConfigMap = keyof ConfigMap> =
MappingStruct<ConfigMap, K>;
export const preRegistry = new MethodRegistry<ConfigMap, void, [Command]>();
preRegistry.extend('option', (data, cmd) =>
cmd.option(data.name, data.desc, data.config),
);
preRegistry.extend('user', (data, cmd) => {
if (data) {
cmd.userFields(data);
}
});
preRegistry.extend('channel', (data, cmd) => {
if (data) {
cmd.channelFields(data);
}
});
preRegistry.extend('username', (data, cmd) => {
if (data) {
cmd.userFields(['name']);
}
});
export const registry = new MethodRegistry<ConfigMap, any, [Argv, any[]]>();
registry.extend('args', (data, argv, args) => args);
registry.extend('arg', (data, argv, args) => args[data]);
registry.extend('argv', (data, argv, args) => argv);
registry.extend('argvField', (data, argv, args) => argv[data]);
registry.extend('option', (data, argv, args) => argv.options[data.name]);
registry.extend('user', (data, argv, args) => argv.session.user);
registry.extend('channel', (data, argv, args) => argv.session.channel);
registry.extend('username', (useDatabase, argv, args) => {
if (useDatabase) {
const user = argv.session.user as User.Observed<'name'>;
if (user?.name) {
return user?.name;
}
}
return (
argv.session.author?.nickname ||
argv.session.author?.username ||
argv.session.userId
);
});
registry.extend('sessionField', (data, argv, args) => argv.session[data]);
export function decorate<T extends keyof ConfigMap>(
type: T,
data?: ConfigMap[T],
): ParameterDecorator {
return (obj, key: string, index) => {
const objClass = obj.constructor;
const list: Config<T>[] =
Reflect.getMetadata(KoishiCommandPutDef, objClass, key) || [];
list[index] = GenerateMappingStruct(type, data);
Reflect.defineMetadata(KoishiCommandPutDef, list, objClass, key);
};
}
}
import { MethodMap, MethodRegistry } from '../abstract-registry';
import { Argv, Awaitable, Context, MaybeArray } from 'koishi';
import {
BeforeEventNameAndPrepend,
CommandRegisterConfig,
EventNameAndPrepend,
KoishiCommandDefinition,
KoishiDoRegister,
KoishiDoRegisterKeys,
KoishiRouteDef,
MappingStruct,
PluginDefinition,
} from '../../def';
import { Metadata } from '../../meta/metadata.decorators';
import { reflector } from '../../meta/meta-fetch';
import { CommandPut } from './command-put';
import { applySelector } from '../../utility';
// eslint-disable-next-line @typescript-eslint/no-namespace
export namespace DoRegister {
export function decorate<K extends keyof ConfigMap>(
config: Config<K>,
): MethodDecorator {
return Metadata.set(KoishiDoRegister, config, KoishiDoRegisterKeys);
}
class SpecificRegistry extends MethodRegistry<
ConfigMap,
any,
[Context, any, string, any | undefined]
> {
define<K extends keyof ConfigMap>(
name: K,
method: MethodMap<ConfigMap, any, [Context, any, string, ...any[]]>[K],
) {
this.extend(name, method);
return (config: ConfigMap[K]) => decorate({ type: name, data: config });
}
}
export interface ConfigMap {
middleware: boolean; // prepend
onEvent: EventNameAndPrepend;
beforeEvent: BeforeEventNameAndPrepend;
plugin: void;
command: CommandRegisterConfig;
route: KoishiRouteDef;
ws: MaybeArray<string | RegExp>;
}
export type Config<K extends keyof ConfigMap = keyof ConfigMap> =
MappingStruct<ConfigMap, K>;
export const registry = new SpecificRegistry();
export const middleware = registry.define(
'middleware',
(prepend, ctx, obj, key) =>
ctx.middleware((session, next) => obj[key](session, next), true),
);
export const event = registry.define('onEvent', (data, ctx, obj, key) =>
ctx.on(
data.name,
(session, ...args) => obj[key](session, ...args),
data.prepend,
),
);
export const beforeEvent = registry.define(
'beforeEvent',
(data, ctx, obj, key) =>
ctx.before(
data.name,
(session, ...args) => obj[key](session, ...args),
data.prepend,
),
);
export const route = registry.define('route', (data, ctx, obj, key) =>
ctx.router[data.method](data.path, (koaCtx, next) =>
obj[key](koaCtx, next),
),
);
export const ws = registry.define('ws', (data, ctx, obj, key) =>
ctx.router.ws(data, (socket, request) => obj[key](socket, request)),
);
function applyInnerPluginDef(
ctx: Context,
key: string,
pluginDef: PluginDefinition<any>,
) {
const pluginCtx = applySelector(ctx, pluginDef);
if (pluginDef == null) {
return;
}
if (!pluginDef || !pluginDef.plugin) {
throw new Error(`Invalid plugin from method ${key}.`);
}
pluginCtx.plugin(pluginDef.plugin, pluginDef.options);
}
export const plugin = registry.define('plugin', (_, ctx, obj, key) => {
const pluginDescMayBeProm: Awaitable<PluginDefinition<any>> = obj[key]();
if (pluginDescMayBeProm instanceof Promise) {
pluginDescMayBeProm.then((pluginDef) => {
applyInnerPluginDef(ctx, key, pluginDef);
});
} else {
applyInnerPluginDef(ctx, key, pluginDescMayBeProm);
}
});
export const command = registry.define(
'command',
(data, ctx, obj, key, extraObj) => {
let command = ctx.command(data.def, data.desc, data.config);
const commandDefs = reflector.getProperty(
KoishiCommandDefinition,
obj,
key,
extraObj,
);
for (const commandDef of commandDefs) {
command = commandDef(command) || command;
}
if (!data.config?.empty) {
if (!data.putOptions) {
command.action((argv: Argv, ...args: any[]) =>
obj[key](argv, ...args),
);
} else {
for (const putOption of data.putOptions) {
CommandPut.preRegistry.execute(putOption, command);
}
command.action((argv: Argv, ...args: any[]) => {
const params = data.putOptions.map((o) =>
CommandPut.registry.execute(o, argv, args),
);
return obj[key](...params);
});
}
}
},
);
}
export * from './utility';
import { Context } from 'koishi';
import { ContextSelector, OnContextFunction } from '../def';
export function applySelector(
ctx: Context,
selector: ContextSelector,
): Context {
if (!selector) {
return ctx;
}
let targetCtx = ctx;
if (selector.select) {
targetCtx = targetCtx.select(selector.select);
}
if (selector.useSelector) {
targetCtx = selector.useSelector(targetCtx) || targetCtx;
}
return targetCtx;
}
export function getContextFromFilters(
ctx: Context,
filters: OnContextFunction[],
) {
let targetCtx = ctx;
for (const fun of filters) {
targetCtx = fun(targetCtx) || targetCtx;
}
return targetCtx;
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment