kt-template-online-api/src/qqbot/command/qqbot-command-engine.service.ts

223 lines
6.8 KiB
TypeScript

import { Injectable, Logger } from '@nestjs/common';
import { QqbotPluginRegistryService } from '../plugin/qqbot-plugin-registry.service';
import type { QqbotNormalizedMessage } from '../qqbot.types';
import { QqbotSendService } from '../send/qqbot-send.service';
import type { QqbotCommandTestDto } from './qqbot-command.dto';
import type { QqbotCommand } from './qqbot-command.entity';
import { QqbotCommandParserService } from './qqbot-command-parser.service';
import { QqbotCommandService } from './qqbot-command.service';
import { QqbotReplyTemplateService } from './qqbot-reply-template.service';
@Injectable()
export class QqbotCommandEngineService {
private readonly logger = new Logger(QqbotCommandEngineService.name);
constructor(
private readonly commandParser: QqbotCommandParserService,
private readonly commandService: QqbotCommandService,
private readonly pluginRegistry: QqbotPluginRegistryService,
private readonly replyTemplate: QqbotReplyTemplateService,
private readonly sendService: QqbotSendService,
) {}
async handleMessage(message: QqbotNormalizedMessage) {
const commands = await this.commandService.listEnabledForMessage(message);
for (const command of commands) {
const matched = this.commandParser.match(command, message);
if (!matched) continue;
if (this.commandService.isInCooldown(command)) return true;
await this.commandService.markHit(command);
const input = this.mergeInput(command, matched.input);
try {
const output = await this.pluginRegistry.execute(
command.pluginKey,
command.operationKey,
input,
{
args: matched.input,
command,
message,
},
);
const replyText = this.buildReplyText(command, input, output);
if (replyText) {
await this.sendService.sendText({
channelId: message.channelId,
guildId: message.rawEvent.guild_id
? `${message.rawEvent.guild_id}`
: undefined,
message: replyText,
selfId: message.selfId,
targetId: message.targetId,
targetType: message.messageType,
});
}
await this.commandService.logExecution({
command,
input,
message,
output,
status: 'success',
});
} catch (err) {
const errorMessage = err instanceof Error ? err.message : '命令执行失败';
await this.commandService.logExecution({
command,
errorMessage,
input,
message,
status: 'failed',
});
await this.sendErrorReply(command, input, message, errorMessage);
this.logger.warn(`QQBot 命令执行失败: ${errorMessage}`);
}
return true;
}
return false;
}
async preview(body: QqbotCommandTestDto) {
const message = this.buildPreviewMessage(body);
const command = body.commandId
? await this.commandService.findById(body.commandId)
: await this.findMatchedCommand(message);
const matched = this.commandParser.match(command, message);
if (!matched) {
return {
matched: false,
message: '未匹配到命令',
};
}
const input = this.mergeInput(command, matched.input);
try {
const output = await this.pluginRegistry.execute(
command.pluginKey,
command.operationKey,
input,
{
args: matched.input,
command,
message,
},
);
const replyText = this.buildReplyText(command, input, output);
return {
command: this.commandService.toResponse(command),
input,
matched: true,
output,
replyText,
status: 'success',
};
} catch (err) {
const errorMessage = err instanceof Error ? err.message : '命令执行失败';
return {
command: this.commandService.toResponse(command),
errorMessage,
input,
matched: true,
output: null,
replyText: this.buildErrorReplyText(command, input, errorMessage),
status: 'failed',
};
}
}
private async findMatchedCommand(message: QqbotNormalizedMessage) {
const commands = await this.commandService.listEnabledForMessage(message);
const command = commands.find((item) =>
this.commandParser.match(item, message),
);
if (!command) {
throw new Error('未匹配到命令');
}
return command;
}
private buildReplyText(
command: QqbotCommand,
input: Record<string, any>,
output: any,
) {
const data = { input, output, ...output };
return (
this.replyTemplate.render(command.replyTemplate, data) ||
this.replyTemplate.stringifyOutput(output)
);
}
private async sendErrorReply(
command: QqbotCommand,
input: Record<string, any>,
message: QqbotNormalizedMessage,
errorMessage: string,
) {
const reply = this.buildErrorReplyText(command, input, errorMessage);
try {
await this.sendService.sendText({
channelId: message.channelId,
guildId: message.rawEvent.guild_id
? `${message.rawEvent.guild_id}`
: undefined,
message: reply,
selfId: message.selfId,
targetId: message.targetId,
targetType: message.messageType,
});
} catch (err) {
const sendErr = err instanceof Error ? err.message : '错误回复发送失败';
this.logger.warn(`QQBot 命令错误回复发送失败: ${sendErr}`);
}
}
private buildErrorReplyText(
command: QqbotCommand,
input: Record<string, any>,
errorMessage: string,
) {
return (
this.replyTemplate.render(command.errorTemplate, {
error: errorMessage,
input,
}) || `命令执行失败:${errorMessage}`
);
}
private mergeInput(command: QqbotCommand, input: Record<string, any>) {
return {
...this.commandService.parseDefaultParams(command),
...this.removeUndefined(input),
};
}
private removeUndefined(input: Record<string, any>) {
return Object.entries(input).reduce<Record<string, any>>(
(result, [key, value]) => {
if (value !== undefined && value !== '') result[key] = value;
return result;
},
{},
);
}
private buildPreviewMessage(body: QqbotCommandTestDto): QqbotNormalizedMessage {
const targetType = body.targetType || 'private';
const targetId = body.targetId || body.userId || '10000';
const userId = body.userId || targetId;
return {
eventTime: new Date(),
groupId: targetType === 'group' ? targetId : undefined,
messageId: `preview-${Date.now()}`,
messageText: body.text,
messageType: targetType,
rawEvent: {},
rawMessage: body.text,
selfId: body.selfId || 'preview',
targetId,
userId,
};
}
}