Commit 84735852 authored by nanahira's avatar nanahira

add command option decorator

parent 908560ea
......@@ -2,6 +2,7 @@ import { CustomDecorator, Inject, SetMetadata } from '@nestjs/common';
import {
KOISHI_CONTEXT,
KoishiCommandDefinition,
KoishiCommandPutDef,
KoishiDoRegister,
KoishiOnContextScope,
KoishiServiceProvideSym,
......@@ -10,10 +11,14 @@ import {
} from './utility/koishi.constants';
import {
CommandDefinitionFun,
CommandPutConfig,
CommandPutConfigMap,
ContextFunction,
DoRegisterConfig,
DoRegisterConfigDataMap,
EventName,
EventNameAndPrepend,
GenerateMappingStruct,
OnContextFunction,
Selection,
} from './koishi.interfaces';
......@@ -66,33 +71,54 @@ export const SetExtraMetadata = <K = string, V = any>(
// Register methods
export const UseMiddleware = (prepend?: boolean): MethodDecorator =>
SetMetadata<string, DoRegisterConfig<boolean>>(KoishiDoRegister, {
type: 'middleware',
data: prepend,
});
SetMetadata<string, DoRegisterConfig<'middleware'>>(
KoishiDoRegister,
GenerateMappingStruct('middleware', prepend),
);
export const UseEvent = (name: EventName, prepend?: boolean): MethodDecorator =>
SetMetadata<string, DoRegisterConfig<EventNameAndPrepend>>(KoishiDoRegister, {
type: 'onevent',
data: { name, prepend },
});
SetMetadata<string, DoRegisterConfig<'onevent'>>(
KoishiDoRegister,
GenerateMappingStruct('onevent', { name, prepend }),
);
export const UsePlugin = (): MethodDecorator =>
SetMetadata<string, DoRegisterConfig>(KoishiDoRegister, {
type: 'plugin',
});
SetMetadata<string, DoRegisterConfig<'plugin'>>(
KoishiDoRegister,
GenerateMappingStruct('plugin'),
);
export function UseCommand<D extends string>(
def: D,
config?: Command.Config,
): MethodDecorator;
export function UseCommand<D extends string>(
def: D,
desc: string,
config?: Command.Config,
): MethodDecorator;
export function UseCommand(
def: string,
...args: [Command.Config?] | [string, Command.Config?]
): MethodDecorator {
return SetMetadata<
string,
DoRegisterConfig<
ContextFunction<Command<never, never, Argv.ArgumentType<D>>>
>
>(KoishiDoRegister, {
type: 'command',
data: (ctx) => ctx.command(def, config),
});
const desc = typeof args[0] === 'string' ? (args.shift() as string) : '';
const config = args[0] as Command.Config;
return (obj, key: string, des) => {
const putOptions: CommandPutConfig<keyof CommandPutConfigMap>[] =
Reflect.getMetadata(KoishiCommandPutDef, obj.constructor, key) ||
undefined;
const metadataDec = SetMetadata<string, DoRegisterConfig<'command'>>(
KoishiDoRegister,
{
type: 'command',
data: {
def,
desc,
config,
putOptions,
},
},
);
return metadataDec(obj, key, des);
};
}
// Context scopes
......@@ -154,6 +180,30 @@ export const CommandOption = (
config: Argv.OptionConfig = {},
) => CommandDef((cmd) => cmd.option(name, desc, config));
// Command put config
function PutCommandParam<T extends keyof CommandPutConfigMap>(
type: T,
data?: CommandPutConfigMap[T],
): ParameterDecorator {
return (obj, key: string, index) => {
const objClass = obj.constructor;
const list: CommandPutConfig<T>[] =
Reflect.getMetadata(KoishiCommandPutDef, objClass, key) || [];
list[index] = GenerateMappingStruct(type, data);
Reflect.defineMetadata(KoishiCommandPutDef, list, objClass, key);
};
}
export const PutArgv = () => PutCommandParam('argv');
export const PutSession = () => PutCommandParam('session');
export const PutArg = (i: number) => PutCommandParam('arg', i);
export const PutOption = (
name: string,
desc: string,
config: Argv.OptionConfig = {},
) => PutCommandParam('option', { name, desc, config });
// Service
export function WireContextService(name?: string): PropertyDecorator {
......
import { ModuleMetadata, Provider, Type } from '@nestjs/common';
import { App, Command, Context, EventMap, MaybeArray, Plugin } from 'koishi';
import {
App,
Argv,
Command,
Context,
EventMap,
MaybeArray,
Plugin,
} from 'koishi';
const selectors = [
'user',
......@@ -74,18 +82,62 @@ export interface EventNameAndPrepend {
name: EventName;
prepend?: boolean;
}
export type Promisify<T> = T extends Promise<unknown> ? T : Promise<T>;
export type ContextFunction<T> = (ctx: Context) => T;
export type OnContextFunction = ContextFunction<Context>;
export type DoRegisterType = 'middleware' | 'command' | 'onevent' | 'plugin';
export interface DoRegisterConfig<T = any> {
type: DoRegisterType;
data?: T;
export interface DoRegisterConfigDataMap {
middleware: boolean; // prepend
onevent: EventNameAndPrepend;
plugin: never;
command: CommandRegisterConfig;
}
export interface CommandConfigWIthDescription extends Command.Config {
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,
};
}
export type DoRegisterConfig<
K extends keyof DoRegisterConfigDataMap = keyof DoRegisterConfigDataMap
> = MappingStruct<DoRegisterConfigDataMap, K>;
// Command stuff
export interface CommandRegisterConfig<D extends string = string> {
def: D;
desc?: string;
config?: Command.Config;
putOptions?: CommandPutConfig<keyof CommandPutConfigMap>[];
}
export interface CommandOptionConfig {
name: string;
desc: string;
config?: Argv.OptionConfig;
}
export interface CommandPutConfigMap {
arg: number;
argv: never;
session: never;
option: CommandOptionConfig;
}
export type CommandPutConfig<
K extends keyof CommandPutConfigMap = keyof CommandPutConfigMap
> = MappingStruct<CommandPutConfigMap, K>;
export type CommandDefinitionFun = (cmd: Command) => Command;
......@@ -19,6 +19,7 @@ import {
} from '../utility/koishi.constants';
import {
CommandDefinitionFun,
CommandPutConfig,
ContextFunction,
DoRegisterConfig,
EventName,
......@@ -50,6 +51,27 @@ export class KoishiMetascanService {
}
}
private getCommandActionArg(
config: CommandPutConfig,
argv: Argv,
args: any[],
) {
switch (config.type) {
case 'arg':
const { data: index } = config as CommandPutConfig<'arg'>;
return args[index];
case 'argv':
return argv;
case 'session':
return argv.session;
case 'option':
const { data: optionData } = config as CommandPutConfig<'option'>;
return argv.options[optionData.name];
default:
return undefined;
}
}
private async handleInstanceRegistration(
ctx: Context,
instance: Record<string, any>,
......@@ -84,15 +106,14 @@ export class KoishiMetascanService {
}
switch (regData.type) {
case 'middleware':
const { data: midPrepend } = regData as DoRegisterConfig<'middleware'>;
baseContext.middleware(
(session, next) => methodFun.call(instance, session, next),
regData.data,
midPrepend,
);
break;
case 'onevent':
const {
data: eventData,
} = regData as DoRegisterConfig<EventNameAndPrepend>;
const { data: eventData } = regData as DoRegisterConfig<'onevent'>;
const eventName = eventData.name;
baseContext.on(eventData.name, (...args: any[]) =>
methodFun.call(instance, ...args),
......@@ -116,10 +137,12 @@ export class KoishiMetascanService {
pluginCtx.plugin(pluginDesc.plugin, pluginDesc.options);
break;
case 'command':
const { data: commandData } = regData as DoRegisterConfig<
ContextFunction<Command>
>;
let command = commandData(baseContext);
const { data: commandData } = regData as DoRegisterConfig<'command'>;
let command = baseContext.command(
commandData.def,
commandData.desc,
commandData.config,
);
const commandDefs: CommandDefinitionFun[] = this.reflector.get(
KoishiCommandDefinition,
methodFun,
......@@ -129,9 +152,29 @@ export class KoishiMetascanService {
command = commandDef(command) || command;
}
}
command.action((argv: Argv, ...args: any[]) =>
methodFun.call(instance, argv, ...args),
);
if (!commandData.putOptions) {
command.action((argv: Argv, ...args: any[]) =>
methodFun.call(instance, argv, ...args),
);
} else {
for (const _optionToRegister of commandData.putOptions) {
if (_optionToRegister.type !== 'option') {
continue;
}
const optionToRegister = _optionToRegister as CommandPutConfig<'option'>;
command.option(
optionToRegister.data.name,
optionToRegister.data.desc,
optionToRegister.data.config,
);
}
command.action((argv: Argv, ...args: any[]) => {
const params = commandData.putOptions.map((o) =>
this.getCommandActionArg(o, argv, args),
);
return methodFun.call(instance, ...params);
});
}
break;
default:
throw new Error(`Unknown operaton type ${regData.type}`);
......
......@@ -6,6 +6,7 @@ export const KOISHI_CONTEXT = 'KOISHI_CONTEXT';
export const KoishiOnContextScope = 'KoishiOnContextScope';
export const KoishiDoRegister = 'KoishiDoRegister';
export const KoishiCommandDefinition = 'KoishiCommandDefinition';
export const KoishiCommandPutDef = Symbol('KoishiCommandPutDef');
export const KoishiServiceWireProperty = Symbol('KoishiServiceWireProperty');
export const KoishiServiceWireKeys = Symbol('KoishiServiceWireKeys');
......
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