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