refactor: 收敛QQBot核心模块边界

This commit is contained in:
sunlei 2026-06-15 15:52:26 +08:00
parent 8ada857f57
commit 379e5fb608
118 changed files with 701 additions and 468 deletions

View File

@ -4,7 +4,7 @@ import * as path from 'path';
import { import {
parseQqbotPluginManifest, parseQqbotPluginManifest,
type QqbotPluginManifest, type QqbotPluginManifest,
} from '../../src/modules/qqbot/plugin-platform/manifest'; } from '../../src/modules/qqbot/plugin-platform/domain/manifest';
export type QqbotPluginCliCommand = export type QqbotPluginCliCommand =
| 'create' | 'create'
@ -284,8 +284,8 @@ export const runQqbotPluginCli = async (
const command = argv[0] as QqbotPluginCliCommand | undefined; const command = argv[0] as QqbotPluginCliCommand | undefined;
const resolvedOptions: Required<QqbotPluginCliOptions> = { const resolvedOptions: Required<QqbotPluginCliOptions> = {
cwd: options.cwd || process.cwd(), cwd: options.cwd || process.cwd(),
stderr: options.stderr || ((message) => console.error(message)), stderr: options.stderr || ((message) => process.stderr.write(`${message}\n`)),
stdout: options.stdout || ((message) => console.log(message)), stdout: options.stdout || ((message) => process.stdout.write(`${message}\n`)),
}; };
switch (command) { switch (command) {

View File

@ -9,26 +9,26 @@ import {
ToolsService, ToolsService,
} from '@/common'; } from '@/common';
import { AdminPasswordCryptoService } from '@/modules/admin/identity/auth/admin-password-crypto.service'; import { AdminPasswordCryptoService } from '@/modules/admin/identity/auth/admin-password-crypto.service';
import { QqbotAccountAbility } from './qqbot-account-ability.entity'; import { QqbotAccountAbility } from '../../infrastructure/persistence/account/qqbot-account-ability.entity';
import { QqbotAccount } from './qqbot-account.entity'; import { QqbotAccount } from '../../infrastructure/persistence/account/qqbot-account.entity';
import type { import type {
QqbotAccountBodyDto, QqbotAccountBodyDto,
QqbotAccountQueryDto, QqbotAccountQueryDto,
QqbotAccountUpdateDto, QqbotAccountUpdateDto,
} from './qqbot-account.dto'; } from '../../contract/account/qqbot-account.dto';
import { QqbotAccountNapcat } from '../../napcat/infrastructure/persistence/qqbot-account-napcat.entity'; import { QqbotAccountNapcat } from '@/modules/qqbot/napcat/infrastructure/persistence/qqbot-account-napcat.entity';
import { QqbotNapcatContainer } from '../../napcat/infrastructure/persistence/qqbot-napcat-container.entity'; import { QqbotNapcatContainer } from '@/modules/qqbot/napcat/infrastructure/persistence/qqbot-napcat-container.entity';
import { QqbotNapcatContainerService } from '../../napcat/qqbot-napcat-container.service'; import { QqbotNapcatContainerService } from '@/modules/qqbot/napcat/infrastructure/integration/container/qqbot-napcat-container.service';
import { import {
QQBOT_DEFAULT_PAGE_NO, QQBOT_DEFAULT_PAGE_NO,
QQBOT_DEFAULT_PAGE_SIZE, QQBOT_DEFAULT_PAGE_SIZE,
} from '../contract/qqbot.constants'; } from '../../contract/qqbot.constants';
import type { import type {
QqbotAccountAbilityType, QqbotAccountAbilityType,
QqbotAccountListItem, QqbotAccountListItem,
QqbotConnectionRole, QqbotConnectionRole,
QqbotNapcatRuntimeStatusSnapshot, QqbotNapcatRuntimeStatusSnapshot,
} from '../contract/qqbot.types'; } from '../../contract/qqbot.types';
const NAPCAT_RUNTIME_CHECK_TTL_MS = 30_000; const NAPCAT_RUNTIME_CHECK_TTL_MS = 30_000;
const NAPCAT_AUTO_LOGIN_CLEANUP_FAILED_MESSAGE = const NAPCAT_AUTO_LOGIN_CLEANUP_FAILED_MESSAGE =

View File

@ -1,10 +1,13 @@
import { Injectable, Logger } from '@nestjs/common'; import { Inject, Injectable, Logger } from '@nestjs/common';
import { ToolsService } from '@/common'; import { ToolsService } from '@/common';
import { QqbotPluginRegistryService } from '@/modules/qqbot/plugin-platform/registry/qqbot-plugin-registry.service'; import {
import type { QqbotNormalizedMessage } from '../contract/qqbot.types'; QQBOT_PLUGIN_EXECUTION_PORT,
type QqbotPluginExecutionPort,
} from '../../domain/plugin-execution.port';
import type { QqbotNormalizedMessage } from '../../contract/qqbot.types';
import { QqbotSendService } from '../send/qqbot-send.service'; import { QqbotSendService } from '../send/qqbot-send.service';
import type { QqbotCommandTestDto } from './qqbot-command.dto'; import type { QqbotCommandTestDto } from '../../contract/command/qqbot-command.dto';
import type { QqbotCommand } from './qqbot-command.entity'; import type { QqbotCommand } from '../../infrastructure/persistence/command/qqbot-command.entity';
import { QqbotCommandParserService } from './qqbot-command-parser.service'; import { QqbotCommandParserService } from './qqbot-command-parser.service';
import { QqbotCommandService } from './qqbot-command.service'; import { QqbotCommandService } from './qqbot-command.service';
import { QqbotReplyTemplateService } from './qqbot-reply-template.service'; import { QqbotReplyTemplateService } from './qqbot-reply-template.service';
@ -16,7 +19,8 @@ export class QqbotCommandEngineService {
constructor( constructor(
private readonly commandParser: QqbotCommandParserService, private readonly commandParser: QqbotCommandParserService,
private readonly commandService: QqbotCommandService, private readonly commandService: QqbotCommandService,
private readonly pluginRegistry: QqbotPluginRegistryService, @Inject(QQBOT_PLUGIN_EXECUTION_PORT)
private readonly pluginExecution: QqbotPluginExecutionPort,
private readonly replyTemplate: QqbotReplyTemplateService, private readonly replyTemplate: QqbotReplyTemplateService,
private readonly sendService: QqbotSendService, private readonly sendService: QqbotSendService,
private readonly toolsService: ToolsService, private readonly toolsService: ToolsService,
@ -32,16 +36,16 @@ export class QqbotCommandEngineService {
await this.commandService.markHit(command); await this.commandService.markHit(command);
const input = this.mergeInput(command, matched.input); const input = this.mergeInput(command, matched.input);
try { try {
const output = await this.pluginRegistry.execute( const output = await this.pluginExecution.executeOperation({
command.pluginKey, context: {
command.operationKey,
input,
{
args: matched.input, args: matched.input,
command, command,
message, message,
}, },
); input,
operationKey: command.operationKey,
pluginKey: command.pluginKey,
});
const replyText = this.buildReplyText(command, input, output); const replyText = this.buildReplyText(command, input, output);
if (replyText) { if (replyText) {
await this.sendService.sendText({ await this.sendService.sendText({
@ -97,16 +101,16 @@ export class QqbotCommandEngineService {
const input = this.mergeInput(command, matched.input); const input = this.mergeInput(command, matched.input);
try { try {
const output = await this.pluginRegistry.execute( const output = await this.pluginExecution.executeOperation({
command.pluginKey, context: {
command.operationKey,
input,
{
args: matched.input, args: matched.input,
command, command,
message, message,
}, },
); input,
operationKey: command.operationKey,
pluginKey: command.pluginKey,
});
const replyText = this.buildReplyText(command, input, output); const replyText = this.buildReplyText(command, input, output);
return { return {
command: this.commandService.toResponse(command), command: this.commandService.toResponse(command),

View File

@ -0,0 +1,75 @@
import { Injectable } from '@nestjs/common';
import type { QqbotCommandMatchResult } from '../../contract/qqbot.types';
import type { QqbotNormalizedMessage } from '../../contract/qqbot.types';
import type { QqbotCommand } from '../../infrastructure/persistence/command/qqbot-command.entity';
@Injectable()
export class QqbotCommandParserService {
async match(command: QqbotCommand, message: QqbotNormalizedMessage) {
const source = `${message.messageText || ''}`.trim();
if (!source) return null;
const aliases = this.getAliases(command);
const prefixes = this.getPrefixes(command);
for (const alias of aliases) {
for (const prefix of prefixes) {
const commandText = `${prefix}${alias}`.trim();
const rawArgs = this.pickArgs(source, commandText);
if (rawArgs === null) continue;
return {
alias,
input: this.parseRawInput(rawArgs),
matched: true,
rawArgs,
} satisfies QqbotCommandMatchResult;
}
}
return null;
}
getAliases(command: QqbotCommand) {
return this.normalizeList(command.aliases, [command.code, command.name]);
}
getPrefixes(command: QqbotCommand) {
return this.normalizeList(command.prefixes, ['/', '!', '']);
}
private pickArgs(source: string, commandText: string) {
if (!commandText) return null;
if (source === commandText) return '';
if (source.startsWith(`${commandText} `)) {
return source.slice(commandText.length).trim();
}
return null;
}
private parseRawInput(rawArgs: string) {
const args = rawArgs ? rawArgs.split(/\s+/).filter(Boolean) : [];
return {
args,
raw: rawArgs,
text: rawArgs,
};
}
private normalizeList(value: string | undefined, fallback: string[]) {
const raw = `${value || ''}`.trim();
const parsed = this.tryParseJsonArray(raw);
const source = parsed.length > 0 ? parsed : raw.split(',');
const list = [...source, ...fallback]
.map((item) => `${item || ''}`.trim())
.filter(Boolean);
return [...new Set(list)];
}
private tryParseJsonArray(value: string) {
if (!value.startsWith('[')) return [];
try {
const parsed = JSON.parse(value);
return Array.isArray(parsed) ? parsed : [];
} catch {
return [];
}
}
}

View File

@ -1,27 +1,30 @@
import { Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config'; import { ConfigService } from '@nestjs/config';
import { InjectRepository } from '@nestjs/typeorm'; import { InjectRepository } from '@nestjs/typeorm';
import { Not, Repository } from 'typeorm'; import { Not, Repository } from 'typeorm';
import { throwVbenError, ToolsService } from '@/common'; import { throwVbenError, ToolsService } from '@/common';
import { QqbotAccountService } from '../account/qqbot-account.service'; import { QqbotAccountService } from '../account/qqbot-account.service';
import { QqbotPluginRegistryService } from '@/modules/qqbot/plugin-platform/registry/qqbot-plugin-registry.service'; import {
QQBOT_PLUGIN_EXECUTION_PORT,
type QqbotPluginExecutionPort,
} from '../../domain/plugin-execution.port';
import { import {
QQBOT_DEFAULT_PAGE_NO, QQBOT_DEFAULT_PAGE_NO,
QQBOT_DEFAULT_PAGE_SIZE, QQBOT_DEFAULT_PAGE_SIZE,
} from '../contract/qqbot.constants'; } from '../../contract/qqbot.constants';
import type { import type {
QqbotCommandParserType, QqbotCommandParserType,
QqbotNormalizedMessage, QqbotNormalizedMessage,
QqbotRuleTargetType, QqbotRuleTargetType,
} from '../contract/qqbot.types'; } from '../../contract/qqbot.types';
import type { import type {
QqbotCommandBodyDto, QqbotCommandBodyDto,
QqbotCommandQueryDto, QqbotCommandQueryDto,
QqbotCommandUpdateDto, QqbotCommandUpdateDto,
} from './qqbot-command.dto'; } from '../../contract/command/qqbot-command.dto';
import { isWithinCooldown } from '../domain/qqbot-cooldown.policy'; import { isWithinCooldown } from '../../domain/qqbot-cooldown.policy';
import { QqbotCommandLog } from './qqbot-command-log.entity'; import { QqbotCommandLog } from '../../infrastructure/persistence/command/qqbot-command-log.entity';
import { QqbotCommand } from './qqbot-command.entity'; import { QqbotCommand } from '../../infrastructure/persistence/command/qqbot-command.entity';
@Injectable() @Injectable()
export class QqbotCommandService { export class QqbotCommandService {
@ -31,7 +34,8 @@ export class QqbotCommandService {
@InjectRepository(QqbotCommandLog) @InjectRepository(QqbotCommandLog)
private readonly commandLogRepository: Repository<QqbotCommandLog>, private readonly commandLogRepository: Repository<QqbotCommandLog>,
private readonly accountService: QqbotAccountService, private readonly accountService: QqbotAccountService,
private readonly pluginRegistry: QqbotPluginRegistryService, @Inject(QQBOT_PLUGIN_EXECUTION_PORT)
private readonly pluginExecution: QqbotPluginExecutionPort,
private readonly toolsService: ToolsService, private readonly toolsService: ToolsService,
private readonly configService: ConfigService, private readonly configService: ConfigService,
) {} ) {}
@ -216,7 +220,7 @@ export class QqbotCommandService {
const operationKey = `${body.operationKey || ''}`.trim(); const operationKey = `${body.operationKey || ''}`.trim();
if (!code) throwVbenError('命令编码不能为空'); if (!code) throwVbenError('命令编码不能为空');
if (!body.name?.trim()) throwVbenError('命令名称不能为空'); if (!body.name?.trim()) throwVbenError('命令名称不能为空');
this.pluginRegistry.assertOperation(pluginKey, operationKey); await this.assertPluginOperation(pluginKey, operationKey);
return { return {
aliases: this.stringifyList(body.aliases), aliases: this.stringifyList(body.aliases),
@ -248,6 +252,22 @@ export class QqbotCommandService {
if (existed) throwVbenError(`命令编码已存在:${code}`); if (existed) throwVbenError(`命令编码已存在:${code}`);
} }
private async assertPluginOperation(
pluginKey?: string,
operationKey?: string,
) {
if (!pluginKey || !operationKey) {
throwVbenError('请选择插件和插件能力');
}
const operation = await this.pluginExecution.getOperationByCommand({
operationKey,
pluginKey,
});
if (!operation) {
throwVbenError(`QQBot 插件能力不存在:${pluginKey}.${operationKey}`);
}
}
private stringifyList(value: string[] | string | undefined, fallback = []) { private stringifyList(value: string[] | string | undefined, fallback = []) {
const list = Array.isArray(value) const list = Array.isArray(value)
? value ? value

View File

@ -1,8 +1,8 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm'; import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm'; import { Repository } from 'typeorm';
import { QqbotConfig } from './qqbot-config.entity'; import { QqbotConfig } from '../../infrastructure/persistence/config/qqbot-config.entity';
import type { QqbotPermissionConfig } from '../contract/qqbot.types'; import type { QqbotPermissionConfig } from '../../contract/qqbot.types';
const QQBOT_PERMISSION_CONFIG_KEYS = { const QQBOT_PERMISSION_CONFIG_KEYS = {
allowlistEnabled: 'permission.allowlistEnabled', allowlistEnabled: 'permission.allowlistEnabled',

View File

@ -1,13 +1,13 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm'; import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm'; import { Repository } from 'typeorm';
import { QqbotAccount } from '../account/qqbot-account.entity'; import { QqbotBusService } from '../../infrastructure/integration/bus/qqbot-bus.service';
import { QqbotConversation } from '../message/qqbot-conversation.entity'; import { QqbotReverseWsService } from '../../infrastructure/integration/connection/qqbot-reverse-ws.service';
import { QqbotMessage } from '../message/qqbot-message.entity'; import { QqbotAccount } from '../../infrastructure/persistence/account/qqbot-account.entity';
import { QqbotBusService } from '../mqtt/qqbot-bus.service'; import { QqbotConversation } from '../../infrastructure/persistence/message/qqbot-conversation.entity';
import { QqbotReverseWsService } from '../connection/qqbot-reverse-ws.service'; import { QqbotMessage } from '../../infrastructure/persistence/message/qqbot-message.entity';
import { QqbotRule } from '../rule/qqbot-rule.entity'; import { QqbotRule } from '../../infrastructure/persistence/rule/qqbot-rule.entity';
import { QqbotSendLog } from '../send/qqbot-send-log.entity'; import { QqbotSendLog } from '../../infrastructure/persistence/send/qqbot-send-log.entity';
@Injectable() @Injectable()
export class QqbotDashboardService { export class QqbotDashboardService {

View File

@ -1,7 +1,7 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm'; import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm'; import { Repository } from 'typeorm';
import { QqbotDedupe } from './qqbot-dedupe.entity'; import { QqbotDedupe } from '../../infrastructure/persistence/dedupe/qqbot-dedupe.entity';
@Injectable() @Injectable()
export class QqbotDedupeService { export class QqbotDedupeService {

View File

@ -4,17 +4,17 @@ import {
SystemNoticePublisher, SystemNoticePublisher,
ToolsService, ToolsService,
} from '@/common'; } from '@/common';
import { QQBOT_MQTT_TOPICS } from '../contract/qqbot.constants'; import { QQBOT_MQTT_TOPICS } from '../../contract/qqbot.constants';
import { QqbotDedupeService } from '../dedupe/qqbot-dedupe.service'; import { QqbotDedupeService } from '../dedupe/qqbot-dedupe.service';
import { QqbotMessageService } from '../message/qqbot-message.service'; import { QqbotMessageService } from '../message/qqbot-message.service';
import { QqbotBusService } from '../mqtt/qqbot-bus.service'; import { QqbotBusService } from '../../infrastructure/integration/bus/qqbot-bus.service';
import type { QqbotOneBotEvent } from '../contract/qqbot.types'; import type { QqbotOneBotEvent } from '../../contract/qqbot.types';
import { import {
buildDedupeKey, buildDedupeKey,
getOneBotOfflineReason, getOneBotOfflineReason,
isOneBotMessageEvent, isOneBotMessageEvent,
normalizeOneBotMessage, normalizeOneBotMessage,
} from './qqbot-event-normalizer'; } from '../../domain/event/qqbot-event-normalizer';
import { QqbotRuleEngineService } from '../rule/qqbot-rule-engine.service'; import { QqbotRuleEngineService } from '../rule/qqbot-rule-engine.service';
import { QqbotAccountService } from '../account/qqbot-account.service'; import { QqbotAccountService } from '../account/qqbot-account.service';

View File

@ -2,17 +2,20 @@ import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm'; import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm'; import { Repository } from 'typeorm';
import { ToolsService } from '@/common'; import { ToolsService } from '@/common';
import { QqbotConversation } from './qqbot-conversation.entity'; import { QqbotConversation } from '../../infrastructure/persistence/message/qqbot-conversation.entity';
import { QqbotMessage } from './qqbot-message.entity'; import { QqbotMessage } from '../../infrastructure/persistence/message/qqbot-message.entity';
import type { import type {
QqbotConversationQueryDto, QqbotConversationQueryDto,
QqbotMessageQueryDto, QqbotMessageQueryDto,
} from './qqbot-message.dto'; } from '../../contract/message/qqbot-message.dto';
import type { QqbotMessageType, QqbotNormalizedMessage } from '../contract/qqbot.types'; import type {
QqbotMessageType,
QqbotNormalizedMessage,
} from '../../contract/qqbot.types';
import { import {
QQBOT_DEFAULT_PAGE_NO, QQBOT_DEFAULT_PAGE_NO,
QQBOT_DEFAULT_PAGE_SIZE, QQBOT_DEFAULT_PAGE_SIZE,
} from '../contract/qqbot.constants'; } from '../../contract/qqbot.constants';
@Injectable() @Injectable()
export class QqbotMessageService { export class QqbotMessageService {

View File

@ -2,24 +2,24 @@ import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm'; import { InjectRepository } from '@nestjs/typeorm';
import { Brackets, Repository } from 'typeorm'; import { Brackets, Repository } from 'typeorm';
import { throwVbenError, ToolsService } from '@/common'; import { throwVbenError, ToolsService } from '@/common';
import { QqbotAllowlist } from './qqbot-allowlist.entity'; import { QqbotAllowlist } from '../../infrastructure/persistence/permission/qqbot-allowlist.entity';
import { QqbotBlocklist } from './qqbot-blocklist.entity'; import { QqbotBlocklist } from '../../infrastructure/persistence/permission/qqbot-blocklist.entity';
import type { import type {
QqbotPermissionBodyDto, QqbotPermissionBodyDto,
QqbotPermissionConfigDto, QqbotPermissionConfigDto,
QqbotPermissionQueryDto, QqbotPermissionQueryDto,
QqbotPermissionUpdateDto, QqbotPermissionUpdateDto,
} from './qqbot-permission.dto'; } from '../../contract/permission/qqbot-permission.dto';
import { QqbotConfigService } from '../config/qqbot-config.service'; import { QqbotConfigService } from '../config/qqbot-config.service';
import { import {
QQBOT_DEFAULT_PAGE_NO, QQBOT_DEFAULT_PAGE_NO,
QQBOT_DEFAULT_PAGE_SIZE, QQBOT_DEFAULT_PAGE_SIZE,
} from '../contract/qqbot.constants'; } from '../../contract/qqbot.constants';
import type { import type {
QqbotNormalizedMessage, QqbotNormalizedMessage,
QqbotPermissionEntity, QqbotPermissionEntity,
QqbotPermissionKind, QqbotPermissionKind,
} from '../contract/qqbot.types'; } from '../../contract/qqbot.types';
@Injectable() @Injectable()
export class QqbotPermissionService { export class QqbotPermissionService {

View File

@ -1,9 +1,12 @@
import { Injectable, Logger } from '@nestjs/common'; import { Inject, Injectable, Logger } from '@nestjs/common';
import { ToolsService } from '@/common'; import { ToolsService } from '@/common';
import type { QqbotNormalizedMessage } from '../contract/qqbot.types'; import {
QQBOT_PLUGIN_EXECUTION_PORT,
type QqbotPluginExecutionPort,
} from '../../domain/plugin-execution.port';
import type { QqbotNormalizedMessage } from '../../contract/qqbot.types';
import { QqbotCommandEngineService } from '../command/qqbot-command-engine.service'; import { QqbotCommandEngineService } from '../command/qqbot-command-engine.service';
import { QqbotPermissionService } from '../permission/qqbot-permission.service'; import { QqbotPermissionService } from '../permission/qqbot-permission.service';
import { QqbotRepeaterPluginService } from '@/modules/qqbot/plugins/repeater/qqbot-repeater.plugin';
import { QqbotSendService } from '../send/qqbot-send.service'; import { QqbotSendService } from '../send/qqbot-send.service';
import { QqbotRuleService } from './qqbot-rule.service'; import { QqbotRuleService } from './qqbot-rule.service';
@ -14,7 +17,8 @@ export class QqbotRuleEngineService {
constructor( constructor(
private readonly commandEngineService: QqbotCommandEngineService, private readonly commandEngineService: QqbotCommandEngineService,
private readonly permissionService: QqbotPermissionService, private readonly permissionService: QqbotPermissionService,
private readonly repeaterPluginService: QqbotRepeaterPluginService, @Inject(QQBOT_PLUGIN_EXECUTION_PORT)
private readonly pluginExecution: QqbotPluginExecutionPort,
private readonly ruleService: QqbotRuleService, private readonly ruleService: QqbotRuleService,
private readonly sendService: QqbotSendService, private readonly sendService: QqbotSendService,
private readonly toolsService: ToolsService, private readonly toolsService: ToolsService,
@ -49,6 +53,9 @@ export class QqbotRuleEngineService {
return; return;
} }
await this.repeaterPluginService.handleMessage(message); await this.pluginExecution.dispatchEvent({
eventKey: 'message',
message,
});
} }
} }

View File

@ -4,22 +4,22 @@ import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm'; import { Repository } from 'typeorm';
import { throwVbenError, ToolsService } from '@/common'; import { throwVbenError, ToolsService } from '@/common';
import { QqbotAccountService } from '../account/qqbot-account.service'; import { QqbotAccountService } from '../account/qqbot-account.service';
import { QqbotRule } from './qqbot-rule.entity'; import { QqbotRule } from '../../infrastructure/persistence/rule/qqbot-rule.entity';
import type { import type {
QqbotRuleBodyDto, QqbotRuleBodyDto,
QqbotRuleQueryDto, QqbotRuleQueryDto,
QqbotRuleUpdateDto, QqbotRuleUpdateDto,
} from './qqbot-rule.dto'; } from '../../contract/rule/qqbot-rule.dto';
import type { import type {
QqbotNormalizedMessage, QqbotNormalizedMessage,
QqbotRuleMatchType, QqbotRuleMatchType,
QqbotRuleTargetType, QqbotRuleTargetType,
} from '../contract/qqbot.types'; } from '../../contract/qqbot.types';
import { import {
QQBOT_DEFAULT_PAGE_NO, QQBOT_DEFAULT_PAGE_NO,
QQBOT_DEFAULT_PAGE_SIZE, QQBOT_DEFAULT_PAGE_SIZE,
} from '../contract/qqbot.constants'; } from '../../contract/qqbot.constants';
import { isWithinCooldown } from '../domain/qqbot-cooldown.policy'; import { isWithinCooldown } from '../../domain/qqbot-cooldown.policy';
@Injectable() @Injectable()
export class QqbotRuleService { export class QqbotRuleService {

View File

@ -4,24 +4,24 @@ import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm'; import { Repository } from 'typeorm';
import { throwVbenError, ToolsService } from '@/common'; import { throwVbenError, ToolsService } from '@/common';
import { QqbotAccountService } from '../account/qqbot-account.service'; import { QqbotAccountService } from '../account/qqbot-account.service';
import { QQBOT_MQTT_TOPICS } from '../contract/qqbot.constants'; import { QQBOT_MQTT_TOPICS } from '../../contract/qqbot.constants';
import { QqbotBusService } from '../mqtt/qqbot-bus.service'; import { QqbotBusService } from '../../infrastructure/integration/bus/qqbot-bus.service';
import { QqbotMessageService } from '../message/qqbot-message.service'; import { QqbotMessageService } from '../message/qqbot-message.service';
import type { import type {
QqbotMessageType, QqbotMessageType,
QqbotReverseActionSender, QqbotReverseActionSender,
} from '../contract/qqbot.types'; } from '../../contract/qqbot.types';
import { import {
QQBOT_DEFAULT_PAGE_NO, QQBOT_DEFAULT_PAGE_NO,
QQBOT_DEFAULT_PAGE_SIZE, QQBOT_DEFAULT_PAGE_SIZE,
} from '../contract/qqbot.constants'; } from '../../contract/qqbot.constants';
import { QqbotRateLimitService } from './qqbot-rate-limit.service'; import { QqbotRateLimitService } from './qqbot-rate-limit.service';
import { QqbotSendLog } from './qqbot-send-log.entity'; import { QqbotSendLog } from '../../infrastructure/persistence/send/qqbot-send-log.entity';
import type { import type {
QqbotSendGroupDto, QqbotSendGroupDto,
QqbotSendLogQueryDto, QqbotSendLogQueryDto,
QqbotSendPrivateDto, QqbotSendPrivateDto,
} from './qqbot-send.dto'; } from '../../contract/send/qqbot-send.dto';
@Injectable() @Injectable()
export class QqbotSendService { export class QqbotSendService {
@ -186,7 +186,7 @@ export class QqbotSendService {
private async getReverseWsService(): Promise<QqbotReverseActionSender> { private async getReverseWsService(): Promise<QqbotReverseActionSender> {
const { QqbotReverseWsService } = const { QqbotReverseWsService } =
await import('../connection/qqbot-reverse-ws.service'); await import('../../infrastructure/integration/connection/qqbot-reverse-ws.service');
return this.moduleRef.get<QqbotReverseActionSender>(QqbotReverseWsService, { return this.moduleRef.get<QqbotReverseActionSender>(QqbotReverseWsService, {
strict: false, strict: false,
}); });

View File

@ -19,9 +19,9 @@ import {
QqbotAccountScanStatusDto, QqbotAccountScanStatusDto,
QqbotAccountUpdateDto, QqbotAccountUpdateDto,
} from './qqbot-account.dto'; } from './qqbot-account.dto';
import { QqbotAccountService } from './qqbot-account.service'; import { QqbotAccountService } from '../../application/account/qqbot-account.service';
import { QqbotNapcatLoginService } from '../../napcat/login/qqbot-napcat-login.service'; import { QqbotNapcatLoginService } from '@/modules/qqbot/napcat/application/login/qqbot-napcat-login.service';
import { QqbotReverseWsService } from '../connection/qqbot-reverse-ws.service'; import { QqbotReverseWsService } from '../../infrastructure/integration/connection/qqbot-reverse-ws.service';
@ApiTags('QQBot - 账号连接') @ApiTags('QQBot - 账号连接')
@Controller('qqbot/account') @Controller('qqbot/account')

View File

@ -1,5 +1,5 @@
import { ApiProperty, ApiPropertyOptional, PartialType } from '@nestjs/swagger'; import { ApiProperty, ApiPropertyOptional, PartialType } from '@nestjs/swagger';
import type { QqbotConnectionMode } from '../contract/qqbot.types'; import type { QqbotConnectionMode } from '../qqbot.types';
export class QqbotAccountBodyDto { export class QqbotAccountBodyDto {
@ApiPropertyOptional({ default: 'reverse-ws' }) @ApiPropertyOptional({ default: 'reverse-ws' })

View File

@ -17,8 +17,8 @@ import {
QqbotCommandTestDto, QqbotCommandTestDto,
QqbotCommandUpdateDto, QqbotCommandUpdateDto,
} from './qqbot-command.dto'; } from './qqbot-command.dto';
import { QqbotCommandEngineService } from './qqbot-command-engine.service'; import { QqbotCommandEngineService } from '../../application/command/qqbot-command-engine.service';
import { QqbotCommandService } from './qqbot-command.service'; import { QqbotCommandService } from '../../application/command/qqbot-command.service';
@ApiTags('QQBot - 在线命令') @ApiTags('QQBot - 在线命令')
@Controller('qqbot/command') @Controller('qqbot/command')

View File

@ -5,7 +5,7 @@ import type {
QqbotCommandParserType, QqbotCommandParserType,
QqbotMessageType, QqbotMessageType,
QqbotRuleTargetType, QqbotRuleTargetType,
} from '../contract/qqbot.types'; } from '../qqbot.types';
export class QqbotCommandQueryDto implements KtPageQuery { export class QqbotCommandQueryDto implements KtPageQuery {
@ApiPropertyOptional() @ApiPropertyOptional()

View File

@ -2,7 +2,7 @@ import { Controller, Get, UseGuards } from '@nestjs/common';
import { ApiOperation, ApiTags } from '@nestjs/swagger'; import { ApiOperation, ApiTags } from '@nestjs/swagger';
import { JwtAuthGuard } from '@/modules/admin/identity/auth/jwt-auth.guard'; import { JwtAuthGuard } from '@/modules/admin/identity/auth/jwt-auth.guard';
import { vbenSuccess } from '@/common'; import { vbenSuccess } from '@/common';
import { QqbotDashboardService } from './qqbot-dashboard.service'; import { QqbotDashboardService } from '../../application/dashboard/qqbot-dashboard.service';
@ApiTags('QQBot - 工作台') @ApiTags('QQBot - 工作台')
@Controller('qqbot/dashboard') @Controller('qqbot/dashboard')

View File

@ -6,7 +6,7 @@ import {
QqbotConversationQueryDto, QqbotConversationQueryDto,
QqbotMessageQueryDto, QqbotMessageQueryDto,
} from './qqbot-message.dto'; } from './qqbot-message.dto';
import { QqbotMessageService } from './qqbot-message.service'; import { QqbotMessageService } from '../../application/message/qqbot-message.service';
@ApiTags('QQBot - 会话与消息') @ApiTags('QQBot - 会话与消息')
@Controller('qqbot') @Controller('qqbot')

View File

@ -1,5 +1,5 @@
import { ApiPropertyOptional } from '@nestjs/swagger'; import { ApiPropertyOptional } from '@nestjs/swagger';
import type { QqbotMessageType } from '../contract/qqbot.types'; import type { QqbotMessageType } from '../qqbot.types';
export class QqbotConversationQueryDto { export class QqbotConversationQueryDto {
@ApiPropertyOptional({ default: 1 }) @ApiPropertyOptional({ default: 1 })

View File

@ -17,7 +17,7 @@ import {
QqbotPermissionQueryDto, QqbotPermissionQueryDto,
QqbotPermissionUpdateDto, QqbotPermissionUpdateDto,
} from './qqbot-permission.dto'; } from './qqbot-permission.dto';
import { QqbotPermissionService } from './qqbot-permission.service'; import { QqbotPermissionService } from '../../application/permission/qqbot-permission.service';
@ApiTags('QQBot - 权限名单') @ApiTags('QQBot - 权限名单')
@Controller('qqbot/permission') @Controller('qqbot/permission')

View File

@ -1,5 +1,5 @@
import { ApiProperty, ApiPropertyOptional, PartialType } from '@nestjs/swagger'; import { ApiProperty, ApiPropertyOptional, PartialType } from '@nestjs/swagger';
import type { QqbotPermissionTargetType } from '../contract/qqbot.types'; import type { QqbotPermissionTargetType } from '../qqbot.types';
export class QqbotPermissionConfigDto { export class QqbotPermissionConfigDto {
@ApiPropertyOptional({ default: false }) @ApiPropertyOptional({ default: false })

View File

@ -1,8 +1,8 @@
import type { QrcodeLookupOptions } from '@/common/types'; import type { QrcodeLookupOptions } from '@/common/types';
import type { QqbotAccount } from '../account/qqbot-account.entity'; import type { QqbotAccount } from '../infrastructure/persistence/account/qqbot-account.entity';
import type { QqbotCommand } from '../command/qqbot-command.entity'; import type { QqbotCommand } from '../infrastructure/persistence/command/qqbot-command.entity';
import type { QqbotAllowlist } from '../permission/qqbot-allowlist.entity'; import type { QqbotAllowlist } from '../infrastructure/persistence/permission/qqbot-allowlist.entity';
import type { QqbotBlocklist } from '../permission/qqbot-blocklist.entity'; import type { QqbotBlocklist } from '../infrastructure/persistence/permission/qqbot-blocklist.entity';
export type { QrcodeLookupOptions } from '@/common/types'; export type { QrcodeLookupOptions } from '@/common/types';
@ -354,14 +354,6 @@ export type QqbotReverseActionSender = {
) => Promise<QqbotOneBotActionResponse>; ) => Promise<QqbotOneBotActionResponse>;
}; };
export type QqbotRepeaterConversationState = {
count: number;
lastRepeatedAt?: number;
lastText: string;
repeatedText: string;
updatedAt: number;
};
export type QrcodeRefreshOptions = QrcodeLookupOptions & { export type QrcodeRefreshOptions = QrcodeLookupOptions & {
fallbackStatus?: NapcatLoginStatus; fallbackStatus?: NapcatLoginStatus;
}; };

View File

@ -16,7 +16,7 @@ import {
QqbotRuleQueryDto, QqbotRuleQueryDto,
QqbotRuleUpdateDto, QqbotRuleUpdateDto,
} from './qqbot-rule.dto'; } from './qqbot-rule.dto';
import { QqbotRuleService } from './qqbot-rule.service'; import { QqbotRuleService } from '../../application/rule/qqbot-rule.service';
@ApiTags('QQBot - 自动回复规则') @ApiTags('QQBot - 自动回复规则')
@Controller('qqbot/rule') @Controller('qqbot/rule')

View File

@ -1,5 +1,5 @@
import { ApiProperty, ApiPropertyOptional, PartialType } from '@nestjs/swagger'; import { ApiProperty, ApiPropertyOptional, PartialType } from '@nestjs/swagger';
import type { QqbotRuleMatchType, QqbotRuleTargetType } from '../contract/qqbot.types'; import type { QqbotRuleMatchType, QqbotRuleTargetType } from '../qqbot.types';
export class QqbotRuleBodyDto { export class QqbotRuleBodyDto {
@ApiPropertyOptional() @ApiPropertyOptional()

View File

@ -16,7 +16,7 @@ import {
QqbotSendLogQueryDto, QqbotSendLogQueryDto,
QqbotSendPrivateDto, QqbotSendPrivateDto,
} from './qqbot-send.dto'; } from './qqbot-send.dto';
import { QqbotSendService } from './qqbot-send.service'; import { QqbotSendService } from '../../application/send/qqbot-send.service';
@ApiTags('QQBot - 发送日志') @ApiTags('QQBot - 发送日志')
@Controller('qqbot/send') @Controller('qqbot/send')

View File

@ -1,5 +1,5 @@
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import type { QqbotMessageType, QqbotSendStatus } from '../contract/qqbot.types'; import type { QqbotMessageType, QqbotSendStatus } from '../qqbot.types';
export class QqbotSendPrivateDto { export class QqbotSendPrivateDto {
@ApiPropertyOptional() @ApiPropertyOptional()

View File

@ -3,7 +3,7 @@ import type {
QqbotMessageType, QqbotMessageType,
QqbotNormalizedMessage, QqbotNormalizedMessage,
QqbotOneBotEvent, QqbotOneBotEvent,
} from '../contract/qqbot.types'; } from '../../contract/qqbot.types';
export function isOneBotMessageEvent( export function isOneBotMessageEvent(
payload: QqbotOneBotEvent, payload: QqbotOneBotEvent,

View File

@ -0,0 +1,40 @@
import type {
QqbotNormalizedMessage,
QqbotPluginOperationSummary,
} from '../contract/qqbot.types';
export const QQBOT_PLUGIN_EXECUTION_PORT = Symbol(
'QQBOT_PLUGIN_EXECUTION_PORT',
);
export type QqbotPluginOperationLookup = {
operationKey?: string;
pluginKey?: string;
};
export type QqbotPluginExecutionInput = {
context?: Record<string, any>;
input: Record<string, any>;
operationKey: string;
pluginKey: string;
};
export type QqbotPluginEventDispatchInput = {
eventKey: 'message';
message: QqbotNormalizedMessage;
};
export type QqbotPluginOperationListContext = {
selfId?: string;
};
export interface QqbotPluginExecutionPort {
dispatchEvent(input: QqbotPluginEventDispatchInput): Promise<boolean>;
executeOperation(input: QqbotPluginExecutionInput): Promise<any>;
getOperationByCommand(
command: QqbotPluginOperationLookup,
): Promise<null | QqbotPluginOperationSummary>;
listActiveOperations(
context?: QqbotPluginOperationListContext,
): Promise<QqbotPluginOperationSummary[]>;
}

View File

@ -8,7 +8,7 @@ import {
import { ConfigService } from '@nestjs/config'; import { ConfigService } from '@nestjs/config';
import * as mqtt from 'mqtt'; import * as mqtt from 'mqtt';
import type { MqttClient } from 'mqtt'; import type { MqttClient } from 'mqtt';
import type { QqbotBusHandler } from '../contract/qqbot.types'; import type { QqbotBusHandler } from '../../../contract/qqbot.types';
@Injectable() @Injectable()
export class QqbotBusService implements OnModuleInit, OnModuleDestroy { export class QqbotBusService implements OnModuleInit, OnModuleDestroy {

View File

@ -10,16 +10,19 @@ import { ConfigService } from '@nestjs/config';
import { HttpAdapterHost, ModuleRef } from '@nestjs/core'; import { HttpAdapterHost, ModuleRef } from '@nestjs/core';
import { WebSocket, WebSocketServer } from 'ws'; import { WebSocket, WebSocketServer } from 'ws';
import { ToolsService } from '@/common'; import { ToolsService } from '@/common';
import { QQBOT_MQTT_TOPICS, QQBOT_REVERSE_WS_PATH } from '../contract/qqbot.constants'; import {
QQBOT_MQTT_TOPICS,
QQBOT_REVERSE_WS_PATH,
} from '../../../contract/qqbot.constants';
import type { import type {
QqbotConnectionRole, QqbotConnectionRole,
QqbotOneBotActionResponse, QqbotOneBotActionResponse,
QqbotOneBotEvent, QqbotOneBotEvent,
QqbotPendingAction, QqbotPendingAction,
} from '../contract/qqbot.types'; } from '../../../contract/qqbot.types';
import { QqbotAccountService } from '../account/qqbot-account.service'; import { QqbotAccountService } from '../../../application/account/qqbot-account.service';
import { QqbotEventService } from '../event/qqbot-event.service'; import { QqbotEventService } from '../../../application/event/qqbot-event.service';
import { QqbotBusService } from '../mqtt/qqbot-bus.service'; import { QqbotBusService } from '../bus/qqbot-bus.service';
@Injectable() @Injectable()
export class QqbotReverseWsService export class QqbotReverseWsService

View File

@ -5,7 +5,7 @@ import {
KtDateTime, KtDateTime,
KtUpdateDateColumn, KtUpdateDateColumn,
} from '@/common'; } from '@/common';
import type { QqbotAccountAbilityType } from '../contract/qqbot.types'; import type { QqbotAccountAbilityType } from '../../../contract/qqbot.types';
@Entity('qqbot_account_ability') @Entity('qqbot_account_ability')
@Index('uk_qqbot_account_ability', ['accountId', 'abilityType', 'abilityKey'], { @Index('uk_qqbot_account_ability', ['accountId', 'abilityType', 'abilityKey'], {

View File

@ -10,7 +10,7 @@ import type {
QqbotConnectionMode, QqbotConnectionMode,
QqbotConnectionRole, QqbotConnectionRole,
QqbotConnectionStatus, QqbotConnectionStatus,
} from '../contract/qqbot.types'; } from '../../../contract/qqbot.types';
@Entity('qqbot_account') @Entity('qqbot_account')
export class QqbotAccount { export class QqbotAccount {

View File

@ -5,8 +5,8 @@ import {
KtDateTime, KtDateTime,
KtUpdateDateColumn, KtUpdateDateColumn,
} from '@/common'; } from '@/common';
import type { QqbotMessageType } from '../contract/qqbot.types'; import type { QqbotMessageType } from '../../../contract/qqbot.types';
import type { QqbotCommandLogStatus } from '../contract/qqbot.types'; import type { QqbotCommandLogStatus } from '../../../contract/qqbot.types';
@Entity('qqbot_command_log') @Entity('qqbot_command_log')
export class QqbotCommandLog { export class QqbotCommandLog {

View File

@ -9,7 +9,7 @@ import {
import type { import type {
QqbotCommandParserType, QqbotCommandParserType,
QqbotRuleTargetType, QqbotRuleTargetType,
} from '../contract/qqbot.types'; } from '../../../contract/qqbot.types';
@Entity('qqbot_command') @Entity('qqbot_command')
export class QqbotCommand { export class QqbotCommand {

View File

@ -6,7 +6,7 @@ import {
KtDateTimeColumn, KtDateTimeColumn,
KtUpdateDateColumn, KtUpdateDateColumn,
} from '@/common'; } from '@/common';
import type { QqbotMessageType } from '../contract/qqbot.types'; import type { QqbotMessageType } from '../../../contract/qqbot.types';
@Entity('qqbot_conversation') @Entity('qqbot_conversation')
@Index('idx_qqbot_conversation_target', ['selfId', 'targetType', 'targetId']) @Index('idx_qqbot_conversation_target', ['selfId', 'targetType', 'targetId'])

View File

@ -6,7 +6,7 @@ import {
KtDateTimeColumn, KtDateTimeColumn,
KtUpdateDateColumn, KtUpdateDateColumn,
} from '@/common'; } from '@/common';
import type { QqbotMessageDirection, QqbotMessageType } from '../contract/qqbot.types'; import type { QqbotMessageDirection, QqbotMessageType } from '../../../contract/qqbot.types';
@Entity('qqbot_message') @Entity('qqbot_message')
@Index('idx_qqbot_message_self_message', ['selfId', 'messageId']) @Index('idx_qqbot_message_self_message', ['selfId', 'messageId'])

View File

@ -5,7 +5,7 @@ import {
KtDateTime, KtDateTime,
KtUpdateDateColumn, KtUpdateDateColumn,
} from '@/common'; } from '@/common';
import type { QqbotPermissionTargetType } from '../contract/qqbot.types'; import type { QqbotPermissionTargetType } from '../../../contract/qqbot.types';
@Entity('qqbot_allowlist') @Entity('qqbot_allowlist')
export class QqbotAllowlist { export class QqbotAllowlist {

View File

@ -5,7 +5,7 @@ import {
KtDateTime, KtDateTime,
KtUpdateDateColumn, KtUpdateDateColumn,
} from '@/common'; } from '@/common';
import type { QqbotPermissionTargetType } from '../contract/qqbot.types'; import type { QqbotPermissionTargetType } from '../../../contract/qqbot.types';
@Entity('qqbot_blocklist') @Entity('qqbot_blocklist')
export class QqbotBlocklist { export class QqbotBlocklist {

View File

@ -6,7 +6,7 @@ import {
KtDateTimeColumn, KtDateTimeColumn,
KtUpdateDateColumn, KtUpdateDateColumn,
} from '@/common'; } from '@/common';
import type { QqbotRuleMatchType, QqbotRuleTargetType } from '../contract/qqbot.types'; import type { QqbotRuleMatchType, QqbotRuleTargetType } from '../../../contract/qqbot.types';
@Entity('qqbot_rule') @Entity('qqbot_rule')
export class QqbotRule { export class QqbotRule {

View File

@ -5,7 +5,7 @@ import {
KtDateTime, KtDateTime,
KtUpdateDateColumn, KtUpdateDateColumn,
} from '@/common'; } from '@/common';
import type { QqbotMessageType, QqbotSendStatus } from '../contract/qqbot.types'; import type { QqbotMessageType, QqbotSendStatus } from '../../../contract/qqbot.types';
@Entity('qqbot_send_log') @Entity('qqbot_send_log')
@Index('idx_qqbot_send_log_target', ['selfId', 'targetType', 'targetId']) @Index('idx_qqbot_send_log_target', ['selfId', 'targetType', 'targetId'])

View File

@ -1,66 +1,54 @@
import { Module } from '@nestjs/common'; import { forwardRef, Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config'; import { ConfigModule } from '@nestjs/config';
import { TypeOrmModule } from '@nestjs/typeorm'; import { TypeOrmModule } from '@nestjs/typeorm';
import { AdminAuthGuardModule } from '@/modules/admin/identity/auth/admin-auth-guard.module'; import { AdminAuthGuardModule } from '@/modules/admin/identity/auth/admin-auth-guard.module';
import { DictModule } from '@/modules/admin/platform-config/dict/dict.module'; import { DictModule } from '@/modules/admin/platform-config/dict/dict.module';
import { QqbotAccountAbility } from '@/modules/qqbot/core/account/qqbot-account-ability.entity'; import { QqbotPluginPlatformModule } from '@/modules/qqbot/plugin-platform/plugin-platform.module';
import { QqbotAccountController } from '@/modules/qqbot/core/account/qqbot-account.controller'; import { QqbotAccountAbility } from '@/modules/qqbot/core/infrastructure/persistence/account/qqbot-account-ability.entity';
import { QqbotAccount } from '@/modules/qqbot/core/account/qqbot-account.entity'; import { QqbotAccountController } from '@/modules/qqbot/core/contract/account/qqbot-account.controller';
import { QqbotAccountService } from '@/modules/qqbot/core/account/qqbot-account.service'; import { QqbotAccount } from '@/modules/qqbot/core/infrastructure/persistence/account/qqbot-account.entity';
import { QqbotNapcatLoginService } from '@/modules/qqbot/napcat/login/qqbot-napcat-login.service'; import { QqbotAccountService } from '@/modules/qqbot/core/application/account/qqbot-account.service';
import { QqbotNapcatWatchdogService } from '@/modules/qqbot/napcat/login/qqbot-napcat-watchdog.service'; import { QqbotNapcatLoginService } from '@/modules/qqbot/napcat/application/login/qqbot-napcat-login.service';
import { QqbotCommandController } from '@/modules/qqbot/core/command/qqbot-command.controller'; import { QqbotNapcatWatchdogService } from '@/modules/qqbot/napcat/application/login/qqbot-napcat-watchdog.service';
import { QqbotCommand } from '@/modules/qqbot/core/command/qqbot-command.entity'; import { QqbotCommandController } from '@/modules/qqbot/core/contract/command/qqbot-command.controller';
import { QqbotCommandEngineService } from '@/modules/qqbot/core/command/qqbot-command-engine.service'; import { QqbotCommand } from '@/modules/qqbot/core/infrastructure/persistence/command/qqbot-command.entity';
import { QqbotCommandLog } from '@/modules/qqbot/core/command/qqbot-command-log.entity'; import { QqbotCommandEngineService } from '@/modules/qqbot/core/application/command/qqbot-command-engine.service';
import { QqbotCommandParserService } from '@/modules/qqbot/core/command/qqbot-command-parser.service'; import { QqbotCommandLog } from '@/modules/qqbot/core/infrastructure/persistence/command/qqbot-command-log.entity';
import { QqbotCommandService } from '@/modules/qqbot/core/command/qqbot-command.service'; import { QqbotCommandParserService } from '@/modules/qqbot/core/application/command/qqbot-command-parser.service';
import { QqbotReplyTemplateService } from '@/modules/qqbot/core/command/qqbot-reply-template.service'; import { QqbotCommandService } from '@/modules/qqbot/core/application/command/qqbot-command.service';
import { QqbotReverseWsService } from '@/modules/qqbot/core/connection/qqbot-reverse-ws.service'; import { QqbotReplyTemplateService } from '@/modules/qqbot/core/application/command/qqbot-reply-template.service';
import { QqbotConfig } from '@/modules/qqbot/core/config/qqbot-config.entity'; import { QqbotReverseWsService } from '@/modules/qqbot/core/infrastructure/integration/connection/qqbot-reverse-ws.service';
import { QqbotConfigService } from '@/modules/qqbot/core/config/qqbot-config.service'; import { QqbotConfig } from '@/modules/qqbot/core/infrastructure/persistence/config/qqbot-config.entity';
import { QqbotDashboardController } from '@/modules/qqbot/core/dashboard/qqbot-dashboard.controller'; import { QqbotConfigService } from '@/modules/qqbot/core/application/config/qqbot-config.service';
import { QqbotDashboardService } from '@/modules/qqbot/core/dashboard/qqbot-dashboard.service'; import { QqbotDashboardController } from '@/modules/qqbot/core/contract/dashboard/qqbot-dashboard.controller';
import { QqbotDedupe } from '@/modules/qqbot/core/dedupe/qqbot-dedupe.entity'; import { QqbotDashboardService } from '@/modules/qqbot/core/application/dashboard/qqbot-dashboard.service';
import { QqbotDedupeService } from '@/modules/qqbot/core/dedupe/qqbot-dedupe.service'; import { QqbotDedupe } from '@/modules/qqbot/core/infrastructure/persistence/dedupe/qqbot-dedupe.entity';
import { QqbotEventService } from '@/modules/qqbot/core/event/qqbot-event.service'; import { QqbotDedupeService } from '@/modules/qqbot/core/application/dedupe/qqbot-dedupe.service';
import { QqbotPluginHttpClientService } from '@/modules/qqbot/plugin-platform/sdk'; import { QqbotEventService } from '@/modules/qqbot/core/application/event/qqbot-event.service';
import { NapcatDeviceIdentity } from '@/modules/qqbot/napcat/device/napcat-device-identity.entity'; import { NapcatDeviceIdentity } from '@/modules/qqbot/napcat/infrastructure/persistence/napcat-device-identity.entity';
import { NapcatDeviceIdentityService } from '@/modules/qqbot/napcat/device/napcat-device-identity.service'; import { NapcatDeviceIdentityService } from '@/modules/qqbot/napcat/infrastructure/integration/device/napcat-device-identity.service';
import { QqbotConversation } from '@/modules/qqbot/core/message/qqbot-conversation.entity'; import { QqbotConversation } from '@/modules/qqbot/core/infrastructure/persistence/message/qqbot-conversation.entity';
import { QqbotMessageController } from '@/modules/qqbot/core/message/qqbot-message.controller'; import { QqbotMessageController } from '@/modules/qqbot/core/contract/message/qqbot-message.controller';
import { QqbotMessage } from '@/modules/qqbot/core/message/qqbot-message.entity'; import { QqbotMessage } from '@/modules/qqbot/core/infrastructure/persistence/message/qqbot-message.entity';
import { QqbotMessageService } from '@/modules/qqbot/core/message/qqbot-message.service'; import { QqbotMessageService } from '@/modules/qqbot/core/application/message/qqbot-message.service';
import { QqbotBusService } from '@/modules/qqbot/core/mqtt/qqbot-bus.service'; import { QqbotBusService } from '@/modules/qqbot/core/infrastructure/integration/bus/qqbot-bus.service';
import { QqbotAccountNapcat } from '@/modules/qqbot/napcat/infrastructure/persistence/qqbot-account-napcat.entity'; import { QqbotAccountNapcat } from '@/modules/qqbot/napcat/infrastructure/persistence/qqbot-account-napcat.entity';
import { QqbotNapcatContainer } from '@/modules/qqbot/napcat/infrastructure/persistence/qqbot-napcat-container.entity'; import { QqbotNapcatContainer } from '@/modules/qqbot/napcat/infrastructure/persistence/qqbot-napcat-container.entity';
import { QqbotNapcatContainerService } from '@/modules/qqbot/napcat/qqbot-napcat-container.service'; import { QqbotNapcatContainerService } from '@/modules/qqbot/napcat/infrastructure/integration/container/qqbot-napcat-container.service';
import { QqbotAllowlist } from '@/modules/qqbot/core/permission/qqbot-allowlist.entity'; import { QqbotAllowlist } from '@/modules/qqbot/core/infrastructure/persistence/permission/qqbot-allowlist.entity';
import { QqbotBlocklist } from '@/modules/qqbot/core/permission/qqbot-blocklist.entity'; import { QqbotBlocklist } from '@/modules/qqbot/core/infrastructure/persistence/permission/qqbot-blocklist.entity';
import { QqbotPermissionController } from '@/modules/qqbot/core/permission/qqbot-permission.controller'; import { QqbotPermissionController } from '@/modules/qqbot/core/contract/permission/qqbot-permission.controller';
import { QqbotPermissionService } from '@/modules/qqbot/core/permission/qqbot-permission.service'; import { QqbotPermissionService } from '@/modules/qqbot/core/application/permission/qqbot-permission.service';
import { QqbotPluginController } from '@/modules/qqbot/plugin-platform/qqbot-plugin.controller'; import { QqbotRuleController } from '@/modules/qqbot/core/contract/rule/qqbot-rule.controller';
import { QqbotEventPluginRegistryService } from '@/modules/qqbot/plugin-platform/registry/qqbot-event-plugin-registry.service'; import { QqbotRule } from '@/modules/qqbot/core/infrastructure/persistence/rule/qqbot-rule.entity';
import { QqbotPluginRegistryService } from '@/modules/qqbot/plugin-platform/registry/qqbot-plugin-registry.service'; import { QqbotRuleEngineService } from '@/modules/qqbot/core/application/rule/qqbot-rule-engine.service';
import { QqbotBangDreamClientService } from '@/modules/qqbot/plugins/bangDream/application/bangdream-client.service'; import { QqbotRuleService } from '@/modules/qqbot/core/application/rule/qqbot-rule.service';
import { TsuguApplicationService } from '@/modules/qqbot/plugins/bangDream/application/bangdream-application.service'; import { QqbotRateLimitService } from '@/modules/qqbot/core/application/send/qqbot-rate-limit.service';
import { QqbotBangDreamRendererService } from '@/modules/qqbot/plugins/bangDream/application/bangdream-renderer.facade'; import { QqbotSendController } from '@/modules/qqbot/core/contract/send/qqbot-send.controller';
import { QqbotBangDreamPluginService } from '@/modules/qqbot/plugins/bangDream/qqbot-bangdream.plugin'; import { QqbotSendLog } from '@/modules/qqbot/core/infrastructure/persistence/send/qqbot-send-log.entity';
import { QqbotFf14ClientService } from '@/modules/qqbot/plugins/ff14Market/qqbot-ff14-client.service'; import { QqbotSendService } from '@/modules/qqbot/core/application/send/qqbot-send.service';
import { QqbotFf14MarketPluginService } from '@/modules/qqbot/plugins/ff14Market/qqbot-ff14-market.plugin';
import { QqbotFflogsClientService } from '@/modules/qqbot/plugins/fflogs/qqbot-fflogs-client.service';
import { QqbotFflogsPluginService } from '@/modules/qqbot/plugins/fflogs/qqbot-fflogs.plugin';
import { QqbotRepeaterPluginService } from '@/modules/qqbot/plugins/repeater/qqbot-repeater.plugin';
import { QqbotRuleController } from '@/modules/qqbot/core/rule/qqbot-rule.controller';
import { QqbotRule } from '@/modules/qqbot/core/rule/qqbot-rule.entity';
import { QqbotRuleEngineService } from '@/modules/qqbot/core/rule/qqbot-rule-engine.service';
import { QqbotRuleService } from '@/modules/qqbot/core/rule/qqbot-rule.service';
import { QqbotRateLimitService } from '@/modules/qqbot/core/send/qqbot-rate-limit.service';
import { QqbotSendController } from '@/modules/qqbot/core/send/qqbot-send.controller';
import { QqbotSendLog } from '@/modules/qqbot/core/send/qqbot-send-log.entity';
import { QqbotSendService } from '@/modules/qqbot/core/send/qqbot-send.service';
export { QQBOT_CORE_DOMAIN_CONTRACT } from './qqbot-core.contract'; export { QQBOT_CORE_DOMAIN_CONTRACT } from './contract/qqbot-core.contract';
export const QQBOT_CORE_ENTITIES = [ export const QQBOT_CORE_ENTITIES = [
QqbotAccount, QqbotAccount,
@ -86,7 +74,6 @@ export const QQBOT_CORE_CONTROLLERS = [
QqbotDashboardController, QqbotDashboardController,
QqbotMessageController, QqbotMessageController,
QqbotPermissionController, QqbotPermissionController,
QqbotPluginController,
QqbotRuleController, QqbotRuleController,
QqbotSendController, QqbotSendController,
]; ];
@ -101,24 +88,12 @@ export const QQBOT_CORE_PROVIDERS = [
QqbotDashboardService, QqbotDashboardService,
QqbotDedupeService, QqbotDedupeService,
QqbotEventService, QqbotEventService,
QqbotPluginHttpClientService,
QqbotBangDreamClientService,
QqbotBangDreamPluginService,
QqbotBangDreamRendererService,
TsuguApplicationService,
QqbotFf14ClientService,
QqbotFf14MarketPluginService,
QqbotFflogsClientService,
QqbotFflogsPluginService,
QqbotMessageService, QqbotMessageService,
NapcatDeviceIdentityService, NapcatDeviceIdentityService,
QqbotNapcatLoginService, QqbotNapcatLoginService,
QqbotNapcatWatchdogService, QqbotNapcatWatchdogService,
QqbotNapcatContainerService, QqbotNapcatContainerService,
QqbotPermissionService, QqbotPermissionService,
QqbotEventPluginRegistryService,
QqbotPluginRegistryService,
QqbotRepeaterPluginService,
QqbotRateLimitService, QqbotRateLimitService,
QqbotReplyTemplateService, QqbotReplyTemplateService,
QqbotReverseWsService, QqbotReverseWsService,
@ -129,6 +104,7 @@ export const QQBOT_CORE_PROVIDERS = [
export const QQBOT_CORE_EXPORTS = [ export const QQBOT_CORE_EXPORTS = [
QqbotAccountService, QqbotAccountService,
QqbotSendService,
NapcatDeviceIdentityService, NapcatDeviceIdentityService,
QqbotNapcatLoginService, QqbotNapcatLoginService,
QqbotNapcatContainerService, QqbotNapcatContainerService,
@ -140,6 +116,7 @@ export const QQBOT_CORE_EXPORTS = [
ConfigModule, ConfigModule,
AdminAuthGuardModule, AdminAuthGuardModule,
DictModule, DictModule,
forwardRef(() => QqbotPluginPlatformModule),
TypeOrmModule.forFeature(QQBOT_CORE_ENTITIES), TypeOrmModule.forFeature(QQBOT_CORE_ENTITIES),
], ],
controllers: QQBOT_CORE_CONTROLLERS, controllers: QQBOT_CORE_CONTROLLERS,

View File

@ -0,0 +1,33 @@
# QQBot Core Schema
Core owns account, command, rule, permission, message, conversation, config,
dedupe, and send-log persistence.
Primary tables:
- `qqbot_account`
- `qqbot_account_ability`
- `qqbot_command`
- `qqbot_command_log`
- `qqbot_config`
- `qqbot_conversation`
- `qqbot_dedupe`
- `qqbot_message`
- `qqbot_allowlist`
- `qqbot_blocklist`
- `qqbot_rule`
- `qqbot_send_log`
Seed linkage:
- Online command rows bind to accounts through `qqbot_account_ability`.
- Core command rows refer to plugin capabilities by `plugin_key` and
`operation_key`; execution is delegated through `QQBOT_PLUGIN_EXECUTION_PORT`.
Verification SQL:
```sql
SELECT COUNT(*) FROM qqbot_account WHERE is_deleted = 0;
SELECT COUNT(*) FROM qqbot_command WHERE is_deleted = 0;
SELECT COUNT(*) FROM qqbot_account_ability WHERE is_deleted = 0;
```

View File

@ -5,7 +5,7 @@ import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config'; import { ConfigService } from '@nestjs/config';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { throwVbenError, ToolsService } from '@/common'; import { throwVbenError, ToolsService } from '@/common';
import { NapcatLoginApiClient } from '../integration/napcat-login-api.client'; import { NapcatLoginApiClient } from '../../infrastructure/integration/napcat-login-api.client';
import type { import type {
NapcatApiResponse, NapcatApiResponse,
NapcatCaptchaLoginResult, NapcatCaptchaLoginResult,
@ -23,9 +23,9 @@ import type {
QqbotNapcatRuntime, QqbotNapcatRuntime,
QrcodeLookupOptions, QrcodeLookupOptions,
QrcodeRefreshOptions, QrcodeRefreshOptions,
} from '../../core/contract/qqbot.types'; } from '@/modules/qqbot/core/contract/qqbot.types';
import { QqbotAccountService } from '../../core/account/qqbot-account.service'; import { QqbotAccountService } from '@/modules/qqbot/core/application/account/qqbot-account.service';
import { QqbotNapcatContainerService } from '../qqbot-napcat-container.service'; import { QqbotNapcatContainerService } from '../../infrastructure/integration/container/qqbot-napcat-container.service';
@Injectable() @Injectable()
export class QqbotNapcatLoginService { export class QqbotNapcatLoginService {

View File

@ -5,7 +5,7 @@ import {
OnModuleInit, OnModuleInit,
} from '@nestjs/common'; } from '@nestjs/common';
import { ConfigService } from '@nestjs/config'; import { ConfigService } from '@nestjs/config';
import { QqbotAccountService } from '../../core/account/qqbot-account.service'; import { QqbotAccountService } from '@/modules/qqbot/core/application/account/qqbot-account.service';
const DEFAULT_INTERVAL_MS = 120_000; const DEFAULT_INTERVAL_MS = 120_000;
const MIN_INTERVAL_MS = 30_000; const MIN_INTERVAL_MS = 30_000;
@ -13,7 +13,7 @@ const MIN_INTERVAL_MS = 30_000;
/** /**
* NapCat 线线线/ * NapCat 线线线/
* @nestjs/schedule OnModuleInit + setInterval * @nestjs/schedule OnModuleInit + setInterval
* + ACCOUNT -q+ docker restart * +
*/ */
@Injectable() @Injectable()
export class QqbotNapcatWatchdogService export class QqbotNapcatWatchdogService

View File

@ -1,11 +1,10 @@
export * from './container/napcat-docker-device-options'; export * from './infrastructure/persistence/napcat-device-identity.entity';
export * from './device/napcat-device-identity.entity'; export * from './infrastructure/integration/device/napcat-device-identity.service';
export * from './device/napcat-device-identity.service';
export * from './infrastructure/persistence/qqbot-account-napcat.entity'; export * from './infrastructure/persistence/qqbot-account-napcat.entity';
export * from './infrastructure/persistence/qqbot-napcat-container.entity'; export * from './infrastructure/persistence/qqbot-napcat-container.entity';
export * from './integration/napcat-login-api.client'; export * from './infrastructure/integration/napcat-login-api.client';
export * from './login/napcat-login-state-machine'; export * from './domain/login/napcat-login-state-machine';
export * from './login/qqbot-napcat-login.service'; export * from './application/login/qqbot-napcat-login.service';
export * from './login/qqbot-napcat-watchdog.service'; export * from './application/login/qqbot-napcat-watchdog.service';
export * from './persistence'; export * from './infrastructure/persistence';
export * from './qqbot-napcat-container.service'; export * from './infrastructure/integration/container/qqbot-napcat-container.service';

View File

@ -1,4 +1,4 @@
import type { NapcatDeviceIdentity } from '../device/napcat-device-identity.entity'; import type { NapcatDeviceIdentity } from '../../persistence/napcat-device-identity.entity';
export type NapcatDockerDeviceOptions = { export type NapcatDockerDeviceOptions = {
dataDir: string; dataDir: string;

View File

@ -10,11 +10,11 @@ import { throwVbenError, ToolsService } from '@/common';
import { import {
toNapcatDockerDeviceOptions, toNapcatDockerDeviceOptions,
type NapcatDockerDeviceOptions, type NapcatDockerDeviceOptions,
} from './container/napcat-docker-device-options'; } from './napcat-docker-device-options';
import { NapcatDeviceIdentityService } from './device/napcat-device-identity.service'; import { NapcatDeviceIdentityService } from '../device/napcat-device-identity.service';
import { QqbotAccount } from '../core/account/qqbot-account.entity'; import { QqbotAccount } from '@/modules/qqbot/core/infrastructure/persistence/account/qqbot-account.entity';
import { QqbotAccountNapcat } from './infrastructure/persistence/qqbot-account-napcat.entity'; import { QqbotAccountNapcat } from '../../persistence/qqbot-account-napcat.entity';
import { QqbotNapcatContainer } from './infrastructure/persistence/qqbot-napcat-container.entity'; import { QqbotNapcatContainer } from '../../persistence/qqbot-napcat-container.entity';
import type { import type {
NapcatApiResponse, NapcatApiResponse,
NapcatCredential, NapcatCredential,
@ -22,7 +22,7 @@ import type {
QqbotNapcatRuntime, QqbotNapcatRuntime,
QqbotNapcatRuntimeLoginStatus, QqbotNapcatRuntimeLoginStatus,
QqbotNapcatRuntimeStatusSnapshot, QqbotNapcatRuntimeStatusSnapshot,
} from '../core/contract/qqbot.types'; } from '@/modules/qqbot/core/contract/qqbot.types';
type NapcatLoginLogState = 'offline' | 'online' | 'unknown'; type NapcatLoginLogState = 'offline' | 'online' | 'unknown';

View File

@ -4,7 +4,7 @@ import { ConfigService } from '@nestjs/config';
import { InjectRepository } from '@nestjs/typeorm'; import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm'; import { Repository } from 'typeorm';
import { ensureSnowflakeId } from '@/common'; import { ensureSnowflakeId } from '@/common';
import { NapcatDeviceIdentity } from './napcat-device-identity.entity'; import { NapcatDeviceIdentity } from '../../persistence/napcat-device-identity.entity';
type ResolveNapcatDeviceIdentityInput = { type ResolveNapcatDeviceIdentityInput = {
accountId: string; accountId: string;

View File

@ -1,6 +1,6 @@
import { NapcatDeviceIdentity } from './device/napcat-device-identity.entity'; import { NapcatDeviceIdentity } from './napcat-device-identity.entity';
import { QqbotAccountNapcat } from './infrastructure/persistence/qqbot-account-napcat.entity'; import { QqbotAccountNapcat } from './qqbot-account-napcat.entity';
import { QqbotNapcatContainer } from './infrastructure/persistence/qqbot-napcat-container.entity'; import { QqbotNapcatContainer } from './qqbot-napcat-container.entity';
export const NAPCAT_RUNTIME_DOMAIN_CONTRACT = { export const NAPCAT_RUNTIME_DOMAIN_CONTRACT = {
tables: [ tables: [

View File

@ -6,7 +6,7 @@ import {
KtDateTimeColumn, KtDateTimeColumn,
KtUpdateDateColumn, KtUpdateDateColumn,
} from '@/common'; } from '@/common';
import type { QqbotAccountNapcatBindStatus } from '../../../core/contract/qqbot.types'; import type { QqbotAccountNapcatBindStatus } from '@/modules/qqbot/core/contract/qqbot.types';
@Entity('qqbot_account_napcat') @Entity('qqbot_account_napcat')
@Index('idx_qqbot_account_napcat_account', ['accountId', 'isDeleted']) @Index('idx_qqbot_account_napcat_account', ['accountId', 'isDeleted'])

View File

@ -6,7 +6,7 @@ import {
KtDateTimeColumn, KtDateTimeColumn,
KtUpdateDateColumn, KtUpdateDateColumn,
} from '@/common'; } from '@/common';
import type { QqbotNapcatContainerStatus } from '../../../core/contract/qqbot.types'; import type { QqbotNapcatContainerStatus } from '@/modules/qqbot/core/contract/qqbot.types';
@Entity('qqbot_napcat_container') @Entity('qqbot_napcat_container')
@Index('uk_qqbot_napcat_container_name', ['name'], { unique: true }) @Index('uk_qqbot_napcat_container_name', ['name'], { unique: true })

View File

@ -0,0 +1,27 @@
# QQBot NapCat Schema
NapCat owns container runtime, account binding, device identity, login session,
challenge, and cleanup persistence.
Current compatibility tables:
- `qqbot_account_napcat`
- `qqbot_napcat_container`
- `napcat_device_identity`
Target v3 tables:
- `napcat_container`
- `napcat_device_identity`
- `napcat_account_binding`
- `napcat_login_session`
- `napcat_login_challenge`
- `napcat_runtime_cleanup`
Verification SQL:
```sql
SELECT COUNT(*) FROM napcat_device_identity;
SELECT COUNT(*) FROM qqbot_account_napcat WHERE is_deleted = 0;
SELECT COUNT(*) FROM qqbot_napcat_container WHERE is_deleted = 0;
```

View File

@ -1,76 +1,38 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { DictService } from '@/modules/admin/platform-config/dict/dict.service'; import { DictService } from '@/modules/admin/platform-config/dict/dict.service';
import type { QqbotCommand } from './qqbot-command.entity';
import type { QqbotNormalizedMessage } from '../contract/qqbot.types';
import { import {
buildQqbotFf14MarketCatalog, buildQqbotFf14MarketCatalog,
buildQqbotFf14MarketCatalogFromTree, buildQqbotFf14MarketCatalogFromTree,
QQBOT_FF14_MARKET_DICT_CODES,
isQqbotFf14DataCenterName, isQqbotFf14DataCenterName,
isQqbotFf14LocationName, isQqbotFf14LocationName,
isQqbotFf14RegionName, isQqbotFf14RegionName,
isQqbotFf14WorldName, isQqbotFf14WorldName,
QQBOT_FF14_MARKET_DICT_CODES,
splitQqbotFf14WorldPath, splitQqbotFf14WorldPath,
} from '@/modules/qqbot/plugins/ff14Market/qqbot-ff14-worlds'; } from '@/modules/qqbot/plugins/ff14Market/qqbot-ff14-worlds';
import type { QqbotFf14MarketCatalog } from '@/modules/qqbot/plugins/ff14Market/qqbot-ff14-market.types'; import type { QqbotFf14MarketCatalog } from '@/modules/qqbot/plugins/ff14Market/qqbot-ff14-market.types';
import type { QqbotCommandMatchResult } from '../contract/qqbot.types'; import type { QqbotPluginExecutionInput } from '@/modules/qqbot/core/domain/plugin-execution.port';
@Injectable() @Injectable()
export class QqbotCommandParserService { export class QqbotPluginArgumentParserService {
constructor(private readonly dictService: DictService) {} constructor(private readonly dictService: DictService) {}
async match(command: QqbotCommand, message: QqbotNormalizedMessage) { async normalizeInput(input: QqbotPluginExecutionInput) {
const source = `${message.messageText || ''}`.trim(); const parserKey = `${input.context?.command?.parserKey || 'plain'}`.trim();
if (!source) return null; const rawArgs = `${input.input?.raw ?? input.input?.text ?? ''}`.trim();
if (parserKey === 'ff14Price') {
const aliases = this.getAliases(command); return {
const prefixes = this.getPrefixes(command); ...input.input,
for (const alias of aliases) { ...this.removeEmpty(await this.parseFf14PriceInput(rawArgs)),
for (const prefix of prefixes) { };
const commandText = `${prefix}${alias}`.trim();
const rawArgs = this.pickArgs(source, commandText);
if (rawArgs === null) continue;
return {
alias,
input: await this.parseInput(command, rawArgs),
matched: true,
rawArgs,
} satisfies QqbotCommandMatchResult;
}
} }
return null; if (parserKey === 'fflogsCharacter') {
} return {
...input.input,
getAliases(command: QqbotCommand) { ...this.removeEmpty(await this.parseFflogsCharacterInput(rawArgs)),
return this.normalizeList(command.aliases, [command.code, command.name]); };
}
getPrefixes(command: QqbotCommand) {
return this.normalizeList(command.prefixes, ['/', '!', '']);
}
private pickArgs(source: string, commandText: string) {
if (!commandText) return null;
if (source === commandText) return '';
if (source.startsWith(`${commandText} `)) {
return source.slice(commandText.length).trim();
} }
return null; return input.input;
}
private async parseInput(command: QqbotCommand, rawArgs: string) {
if (command.parserKey === 'ff14Price') {
return this.parseFf14PriceInput(rawArgs);
}
if (command.parserKey === 'fflogsCharacter') {
return this.parseFflogsCharacterInput(rawArgs);
}
const args = rawArgs ? rawArgs.split(/\s+/).filter(Boolean) : [];
return {
args,
raw: rawArgs,
text: rawArgs,
};
} }
private async parseFf14PriceInput(rawArgs: string) { private async parseFf14PriceInput(rawArgs: string) {
@ -240,11 +202,11 @@ export class QqbotCommandParserService {
), ),
encounter: encounterName, encounter: encounterName,
encounterName, encounterName,
limit: this.normalizeString(flags.get('limit') || flags.get('数量')),
metric: this.normalizeString(flags.get('metric') || flags.get('指标')), metric: this.normalizeString(flags.get('metric') || flags.get('指标')),
partition: this.normalizeString( partition: this.normalizeString(
flags.get('partition') || flags.get('分区'), flags.get('partition') || flags.get('分区'),
), ),
limit: this.normalizeString(flags.get('limit') || flags.get('数量')),
raw: rawArgs, raw: rawArgs,
role: this.normalizeString(flags.get('role') || flags.get('职责')), role: this.normalizeString(flags.get('role') || flags.get('职责')),
serverRegion: this.normalizeString( serverRegion: this.normalizeString(
@ -318,7 +280,7 @@ export class QqbotCommandParserService {
private async pickFflogsPositionalsByKnownWorld(positional: string[]) { private async pickFflogsPositionalsByKnownWorld(positional: string[]) {
const catalog = await this.getFf14MarketCatalog(); const catalog = await this.getFf14MarketCatalog();
for (let index = positional.length - 2; index > 0; index--) { for (let index = positional.length - 2; index > 0; index -= 1) {
const candidate = positional[index]; const candidate = positional[index];
if (!isQqbotFf14LocationName(catalog, candidate)) continue; if (!isQqbotFf14LocationName(catalog, candidate)) continue;
const characterName = positional.slice(0, index).join(' ').trim(); const characterName = positional.slice(0, index).join(' ').trim();
@ -366,28 +328,18 @@ export class QqbotCommandParserService {
return ['1', 'true', 'yes', 'hq'].includes(`${value}`.toLowerCase()); return ['1', 'true', 'yes', 'hq'].includes(`${value}`.toLowerCase());
} }
private normalizeList(value: string | undefined, fallback: string[]) {
const raw = `${value || ''}`.trim();
const parsed = this.tryParseJsonArray(raw);
const source = parsed.length > 0 ? parsed : raw.split(',');
const list = [...source, ...fallback]
.map((item) => `${item || ''}`.trim())
.filter(Boolean);
return [...new Set(list)];
}
private tryParseJsonArray(value: string) {
if (!value.startsWith('[')) return [];
try {
const parsed = JSON.parse(value);
return Array.isArray(parsed) ? parsed : [];
} catch {
return [];
}
}
private normalizeString(value?: string | true) { private normalizeString(value?: string | true) {
if (value === true) return ''; if (value === true) return '';
return `${value || ''}`.trim(); return `${value || ''}`.trim();
} }
private removeEmpty(input: Record<string, any>) {
return Object.entries(input).reduce<Record<string, any>>(
(result, [key, value]) => {
if (value !== undefined && value !== '') result[key] = value;
return result;
},
{},
);
}
} }

View File

@ -0,0 +1,53 @@
import { Injectable } from '@nestjs/common';
import type {
QqbotPluginEventDispatchInput,
QqbotPluginExecutionInput,
QqbotPluginExecutionPort,
QqbotPluginOperationLookup,
} from '@/modules/qqbot/core/domain/plugin-execution.port';
import { QqbotEventPluginRegistryService } from './registry/qqbot-event-plugin-registry.service';
import { QqbotPluginRegistryService } from './registry/qqbot-plugin-registry.service';
import { QqbotPluginArgumentParserService } from './argument/qqbot-plugin-argument-parser.service';
@Injectable()
export class QqbotPluginExecutionAdapter implements QqbotPluginExecutionPort {
constructor(
private readonly argumentParser: QqbotPluginArgumentParserService,
private readonly eventPluginRegistry: QqbotEventPluginRegistryService,
private readonly pluginRegistry: QqbotPluginRegistryService,
) {}
async executeOperation(input: QqbotPluginExecutionInput) {
const normalizedInput = await this.argumentParser.normalizeInput(input);
return this.pluginRegistry.execute(
input.pluginKey,
input.operationKey,
normalizedInput,
{
...(input.context || {}),
args: normalizedInput,
},
);
}
async dispatchEvent(input: QqbotPluginEventDispatchInput) {
if (input.eventKey !== 'message') return false;
return this.eventPluginRegistry.dispatchMessage(input.message);
}
async listActiveOperations() {
return [
...this.pluginRegistry.listOperations(),
...this.eventPluginRegistry.listOperations(),
];
}
async getOperationByCommand(command: QqbotPluginOperationLookup) {
if (!command.pluginKey || !command.operationKey) return null;
return (
this.pluginRegistry
.listOperations(command.pluginKey)
.find((operation) => operation.key === command.operationKey) || null
);
}
}

View File

@ -2,7 +2,10 @@ import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm'; import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm'; import { Repository } from 'typeorm';
import { throwVbenError } from '@/common'; import { throwVbenError } from '@/common';
import { parseQqbotPluginManifest, type QqbotPluginManifest } from './manifest'; import {
parseQqbotPluginManifest,
type QqbotPluginManifest,
} from '../domain/manifest';
import { import {
QqbotPlugin, QqbotPlugin,
QqbotPluginAccountBinding, QqbotPluginAccountBinding,
@ -14,7 +17,7 @@ import {
QqbotPluginRuntimeEvent, QqbotPluginRuntimeEvent,
QqbotPluginVersion, QqbotPluginVersion,
type QqbotPluginInstallStatus, type QqbotPluginInstallStatus,
} from './persistence'; } from '../infrastructure/persistence';
type ValidateManifestBody = { type ValidateManifestBody = {
manifest?: unknown; manifest?: unknown;

View File

@ -1,9 +1,10 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { formatKtDateTime, throwVbenError } from '@/common'; import { formatKtDateTime, throwVbenError } from '@/common';
import { QqbotAccountService } from '@/modules/qqbot/core/account/qqbot-account.service'; import { QqbotAccountService } from '@/modules/qqbot/core/application/account/qqbot-account.service';
import { QqbotRepeaterPluginService } from '@/modules/qqbot/plugins/repeater/qqbot-repeater.plugin'; import { QqbotRepeaterPluginService } from '@/modules/qqbot/plugins/repeater/qqbot-repeater.plugin';
import type { import type {
QqbotEventPluginDefinition, QqbotEventPluginDefinition,
QqbotNormalizedMessage,
QqbotPluginHealth, QqbotPluginHealth,
QqbotPluginOperationSummary, QqbotPluginOperationSummary,
} from '@/modules/qqbot/core/contract/qqbot.types'; } from '@/modules/qqbot/core/contract/qqbot.types';
@ -61,6 +62,10 @@ export class QqbotEventPluginRegistryService {
})); }));
} }
async dispatchMessage(message: QqbotNormalizedMessage) {
return this.repeaterPlugin.handleMessage(message);
}
async bind(pluginKey: string, selfId: string) { async bind(pluginKey: string, selfId: string) {
this.assertPlugin(pluginKey); this.assertPlugin(pluginKey);
if (pluginKey === 'repeater') { if (pluginKey === 'repeater') {

View File

@ -11,7 +11,7 @@ import {
import { ApiOperation, ApiQuery, ApiTags } from '@nestjs/swagger'; import { ApiOperation, ApiQuery, ApiTags } from '@nestjs/swagger';
import { JwtAuthGuard } from '@/modules/admin/identity/auth/jwt-auth.guard'; import { JwtAuthGuard } from '@/modules/admin/identity/auth/jwt-auth.guard';
import { vbenSuccess } from '@/common'; import { vbenSuccess } from '@/common';
import { QqbotPluginPlatformService } from './plugin-platform.service'; import { QqbotPluginPlatformService } from '../application/plugin-platform.service';
@ApiTags('QQBot - 插件平台') @ApiTags('QQBot - 插件平台')
@Controller('qqbot/plugin-platform') @Controller('qqbot/plugin-platform')

View File

@ -10,8 +10,8 @@ import {
import { ApiOperation, ApiQuery, ApiTags } from '@nestjs/swagger'; import { ApiOperation, ApiQuery, ApiTags } from '@nestjs/swagger';
import { JwtAuthGuard } from '@/modules/admin/identity/auth/jwt-auth.guard'; import { JwtAuthGuard } from '@/modules/admin/identity/auth/jwt-auth.guard';
import { ToolsService, vbenSuccess } from '@/common'; import { ToolsService, vbenSuccess } from '@/common';
import { QqbotEventPluginRegistryService } from './registry/qqbot-event-plugin-registry.service'; import { QqbotEventPluginRegistryService } from '../application/registry/qqbot-event-plugin-registry.service';
import { QqbotPluginRegistryService } from './registry/qqbot-plugin-registry.service'; import { QqbotPluginRegistryService } from '../application/registry/qqbot-plugin-registry.service';
import type { import type {
QqbotPluginOperationSummary, QqbotPluginOperationSummary,
QqbotPluginTriggerMode, QqbotPluginTriggerMode,

View File

@ -1,18 +1,63 @@
import { Module } from '@nestjs/common'; import { forwardRef, Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { TypeOrmModule } from '@nestjs/typeorm'; import { TypeOrmModule } from '@nestjs/typeorm';
import { AdminAuthGuardModule } from '@/modules/admin/identity/auth/admin-auth-guard.module'; import { AdminAuthGuardModule } from '@/modules/admin/identity/auth/admin-auth-guard.module';
import { QqbotPluginPlatformController } from './plugin-platform.controller'; import { DictModule } from '@/modules/admin/platform-config/dict/dict.module';
import { QqbotPluginPlatformService } from './plugin-platform.service'; import { QQBOT_PLUGIN_EXECUTION_PORT } from '@/modules/qqbot/core/domain/plugin-execution.port';
import { QQBOT_PLUGIN_PLATFORM_ENTITIES } from './persistence'; import { QqbotCoreModule } from '@/modules/qqbot/core/qqbot-core.module';
import { QqbotPluginHttpClientService } from './sdk'; import { QqbotBangDreamClientService } from '@/modules/qqbot/plugins/bangDream/application/bangdream-client.service';
import { TsuguApplicationService } from '@/modules/qqbot/plugins/bangDream/application/bangdream-application.service';
import { QqbotBangDreamRendererService } from '@/modules/qqbot/plugins/bangDream/application/bangdream-renderer.facade';
import { QqbotBangDreamPluginService } from '@/modules/qqbot/plugins/bangDream/qqbot-bangdream.plugin';
import { QqbotFf14ClientService } from '@/modules/qqbot/plugins/ff14Market/qqbot-ff14-client.service';
import { QqbotFf14MarketPluginService } from '@/modules/qqbot/plugins/ff14Market/qqbot-ff14-market.plugin';
import { QqbotFflogsClientService } from '@/modules/qqbot/plugins/fflogs/qqbot-fflogs-client.service';
import { QqbotFflogsPluginService } from '@/modules/qqbot/plugins/fflogs/qqbot-fflogs.plugin';
import { QqbotRepeaterPluginService } from '@/modules/qqbot/plugins/repeater/qqbot-repeater.plugin';
import { QqbotPluginArgumentParserService } from './application/argument/qqbot-plugin-argument-parser.service';
import { QqbotEventPluginRegistryService } from './application/registry/qqbot-event-plugin-registry.service';
import { QqbotPluginRegistryService } from './application/registry/qqbot-plugin-registry.service';
import { QqbotPluginExecutionAdapter } from './application/plugin-execution.adapter';
import { QqbotPluginPlatformService } from './application/plugin-platform.service';
import { QqbotPluginPlatformController } from './contract/plugin-platform.controller';
import { QqbotPluginController } from './contract/qqbot-plugin.controller';
import { QQBOT_PLUGIN_PLATFORM_ENTITIES } from './infrastructure/persistence';
import { QqbotPluginHttpClientService } from './infrastructure/integration/sdk';
@Module({ @Module({
controllers: [QqbotPluginPlatformController], controllers: [QqbotPluginController, QqbotPluginPlatformController],
exports: [QqbotPluginHttpClientService, QqbotPluginPlatformService], exports: [
QQBOT_PLUGIN_EXECUTION_PORT,
QqbotPluginHttpClientService,
QqbotPluginPlatformService,
],
imports: [ imports: [
ConfigModule,
AdminAuthGuardModule, AdminAuthGuardModule,
DictModule,
forwardRef(() => QqbotCoreModule),
TypeOrmModule.forFeature([...QQBOT_PLUGIN_PLATFORM_ENTITIES]), TypeOrmModule.forFeature([...QQBOT_PLUGIN_PLATFORM_ENTITIES]),
], ],
providers: [QqbotPluginHttpClientService, QqbotPluginPlatformService], providers: [
QqbotBangDreamClientService,
QqbotBangDreamPluginService,
QqbotBangDreamRendererService,
QqbotEventPluginRegistryService,
QqbotFf14ClientService,
QqbotFf14MarketPluginService,
QqbotFflogsClientService,
QqbotFflogsPluginService,
QqbotPluginArgumentParserService,
QqbotPluginExecutionAdapter,
{
provide: QQBOT_PLUGIN_EXECUTION_PORT,
useExisting: QqbotPluginExecutionAdapter,
},
QqbotPluginHttpClientService,
QqbotPluginPlatformService,
QqbotPluginRegistryService,
QqbotRepeaterPluginService,
TsuguApplicationService,
],
}) })
export class QqbotPluginPlatformModule {} export class QqbotPluginPlatformModule {}

View File

@ -0,0 +1,27 @@
# QQBot Plugin Platform Schema
Plugin Platform owns plugin package, installation, lifecycle, runtime event,
operation registry, event dispatch, SDK host adapter, and account binding
persistence.
Current compatibility tables:
- `qqbot_plugin_package`
- `qqbot_plugin_installation`
- `qqbot_plugin_runtime_event`
- `qqbot_plugin_account_binding`
- `qqbot_plugin_config`
Seed linkage:
- Built-in command capabilities are exposed through the platform registry.
- Legacy `/qqbot/plugin/*` routes are contract compatibility endpoints owned by
Plugin Platform.
Verification SQL:
```sql
SELECT COUNT(*) FROM qqbot_plugin_package WHERE is_deleted = 0;
SELECT COUNT(*) FROM qqbot_plugin_installation WHERE is_deleted = 0;
SELECT COUNT(*) FROM qqbot_plugin_runtime_event;
```

View File

@ -2,7 +2,7 @@ import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config'; import { ConfigService } from '@nestjs/config';
import { formatKtDateTime } from '@/common'; import { formatKtDateTime } from '@/common';
import { DictService } from '@/modules/admin/platform-config/dict/dict.service'; import { DictService } from '@/modules/admin/platform-config/dict/dict.service';
import { QqbotPluginHttpClientService } from '@/modules/qqbot/plugin-platform/sdk'; import { QqbotPluginHttpClientService } from '@/modules/qqbot/plugin-platform/infrastructure/integration/sdk';
import { import {
buildQqbotFf14MarketCatalog, buildQqbotFf14MarketCatalog,
buildQqbotFf14MarketCatalogFromTree, buildQqbotFf14MarketCatalogFromTree,

View File

@ -1,7 +1,7 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config'; import { ConfigService } from '@nestjs/config';
import { DictService } from '@/modules/admin/platform-config/dict/dict.service'; import { DictService } from '@/modules/admin/platform-config/dict/dict.service';
import { QqbotPluginHttpClientService } from '@/modules/qqbot/plugin-platform/sdk'; import { QqbotPluginHttpClientService } from '@/modules/qqbot/plugin-platform/infrastructure/integration/sdk';
import type { import type {
FflogsCharacterEncounterRankingsResponse, FflogsCharacterEncounterRankingsResponse,
FflogsCharacterSummaryResponse, FflogsCharacterSummaryResponse,

View File

@ -1,18 +1,25 @@
import { Injectable, Logger } from '@nestjs/common'; import { Injectable, Logger } from '@nestjs/common';
import { ConfigService } from '@nestjs/config'; import { ConfigService } from '@nestjs/config';
import { ToolsService } from '@/common'; import { ToolsService } from '@/common';
import { QqbotAccountService } from '@/modules/qqbot/core/account/qqbot-account.service'; import { QqbotAccountService } from '@/modules/qqbot/core/application/account/qqbot-account.service';
import type { import type {
QqbotEventPluginDefinition, QqbotEventPluginDefinition,
QqbotEventPluginSummary, QqbotEventPluginSummary,
QqbotNormalizedMessage, QqbotNormalizedMessage,
QqbotRepeaterConversationState,
} from '@/modules/qqbot/core/contract/qqbot.types'; } from '@/modules/qqbot/core/contract/qqbot.types';
import { QqbotSendService } from '@/modules/qqbot/core/send/qqbot-send.service'; import { QqbotSendService } from '@/modules/qqbot/core/application/send/qqbot-send.service';
const QQBOT_REPEATER_VERSION = '1.0.0'; const QQBOT_REPEATER_VERSION = '1.0.0';
const QQBOT_REPEATER_PLUGIN_KEY = 'repeater'; const QQBOT_REPEATER_PLUGIN_KEY = 'repeater';
type QqbotRepeaterConversationState = {
count: number;
lastRepeatedAt?: number;
lastText: string;
repeatedText: string;
updatedAt: number;
};
@Injectable() @Injectable()
export class QqbotRepeaterPluginService { export class QqbotRepeaterPluginService {
private readonly logger = new Logger(QqbotRepeaterPluginService.name); private readonly logger = new Logger(QqbotRepeaterPluginService.name);

View File

@ -1,7 +1,7 @@
import { readFileSync } from 'fs'; import { readFileSync } from 'fs';
import { join } from 'path'; import { join } from 'path';
import { parseQqbotPluginManifest } from '../../../../src/modules/qqbot/plugin-platform/manifest'; import { parseQqbotPluginManifest } from '../../../../src/modules/qqbot/plugin-platform/domain/manifest';
import { BANGDREAM_OPERATION_REGISTRY } from '../../../../src/modules/qqbot/plugins/bangDream/registry/operation-registry'; import { BANGDREAM_OPERATION_REGISTRY } from '../../../../src/modules/qqbot/plugins/bangDream/registry/operation-registry';
const repoRoot = join(__dirname, '../../../..'); const repoRoot = join(__dirname, '../../../..');

View File

@ -1,4 +1,4 @@
import { QQBOT_CORE_DOMAIN_CONTRACT } from '../../../../src/modules/qqbot/core/qqbot-core.contract'; import { QQBOT_CORE_DOMAIN_CONTRACT } from '../../../../src/modules/qqbot/core/contract/qqbot-core.contract';
import { readRefactorV3SqlSchema } from '../../../helpers/sql-schema.helper'; import { readRefactorV3SqlSchema } from '../../../helpers/sql-schema.helper';
describe('QQBot core command contract', () => { describe('QQBot core command contract', () => {

View File

@ -13,24 +13,19 @@ import { Test } from '@nestjs/testing';
import * as request from 'supertest'; import * as request from 'supertest';
import { DictService } from '../../../../src/modules/admin/platform-config/dict/dict.service'; import { DictService } from '../../../../src/modules/admin/platform-config/dict/dict.service';
import { ToolsService } from '../../../../src/common'; import { ToolsService } from '../../../../src/common';
import { QqbotAccountService } from '../../../../src/modules/qqbot/core/account/qqbot-account.service'; import { QqbotAccountService } from '../../../../src/modules/qqbot/core/application/account/qqbot-account.service';
import { QqbotCommandController } from '../../../../src/modules/qqbot/core/command/qqbot-command.controller'; import { QqbotCommandController } from '../../../../src/modules/qqbot/core/contract/command/qqbot-command.controller';
import { QqbotCommand } from '../../../../src/modules/qqbot/core/command/qqbot-command.entity'; import { QqbotCommand } from '../../../../src/modules/qqbot/core/infrastructure/persistence/command/qqbot-command.entity';
import { QqbotCommandEngineService } from '../../../../src/modules/qqbot/core/command/qqbot-command-engine.service'; import { QqbotCommandEngineService } from '../../../../src/modules/qqbot/core/application/command/qqbot-command-engine.service';
import { QqbotCommandLog } from '../../../../src/modules/qqbot/core/command/qqbot-command-log.entity'; import { QqbotCommandLog } from '../../../../src/modules/qqbot/core/infrastructure/persistence/command/qqbot-command-log.entity';
import { QqbotCommandParserService } from '../../../../src/modules/qqbot/core/command/qqbot-command-parser.service'; import { QqbotCommandParserService } from '../../../../src/modules/qqbot/core/application/command/qqbot-command-parser.service';
import { QqbotCommandService } from '../../../../src/modules/qqbot/core/command/qqbot-command.service'; import { QqbotCommandService } from '../../../../src/modules/qqbot/core/application/command/qqbot-command.service';
import { QqbotReplyTemplateService } from '../../../../src/modules/qqbot/core/command/qqbot-reply-template.service'; import { QqbotReplyTemplateService } from '../../../../src/modules/qqbot/core/application/command/qqbot-reply-template.service';
import { QQBOT_CORE_PROVIDERS } from '../../../../src/modules/qqbot/core/qqbot-core.module'; import { QQBOT_PLUGIN_EXECUTION_PORT } from '../../../../src/modules/qqbot/core/domain/plugin-execution.port';
import { QqbotSendService } from '../../../../src/modules/qqbot/core/send/qqbot-send.service'; import { QqbotSendService } from '../../../../src/modules/qqbot/core/application/send/qqbot-send.service';
describe('QQBot core command local smoke', () => { describe('QQBot core command local smoke', () => {
let app: INestApplication; let app: INestApplication;
type ProviderClass = new (...args: never[]) => unknown;
const qqbotPluginRegistryProvider = QQBOT_CORE_PROVIDERS.find(
(provider) =>
(provider as { name?: string }).name === 'QqbotPluginRegistryService',
) as ProviderClass;
const command = { const command = {
aliases: '["查歌"]', aliases: '["查歌"]',
@ -72,11 +67,15 @@ describe('QQBot core command local smoke', () => {
save: jest.fn(), save: jest.fn(),
}; };
const pluginRegistry = { const pluginExecution = {
execute: jest.fn().mockResolvedValue({ executeOperation: jest.fn().mockResolvedValue({
title: 'FIRE BIRD', title: 'FIRE BIRD',
type: 'text', type: 'text',
}), }),
getOperationByCommand: jest.fn().mockResolvedValue({
key: command.operationKey,
pluginKey: command.pluginKey,
}),
}; };
beforeAll(async () => { beforeAll(async () => {
@ -103,8 +102,8 @@ describe('QQBot core command local smoke', () => {
}, },
}, },
{ {
provide: qqbotPluginRegistryProvider, provide: QQBOT_PLUGIN_EXECUTION_PORT,
useValue: pluginRegistry, useValue: pluginExecution,
}, },
{ {
provide: QqbotSendService, provide: QqbotSendService,
@ -190,14 +189,16 @@ describe('QQBot core command local smoke', () => {
expect(commandRepository.findOne).toHaveBeenCalledWith({ expect(commandRepository.findOne).toHaveBeenCalledWith({
where: { id: command.id, isDeleted: false }, where: { id: command.id, isDeleted: false },
}); });
expect(pluginRegistry.execute).toHaveBeenCalledWith( expect(pluginExecution.executeOperation).toHaveBeenCalledWith(
command.pluginKey,
command.operationKey,
expect.objectContaining({ expect.objectContaining({
text: 'FIRE BIRD', context: expect.objectContaining({
}), command,
expect.objectContaining({ }),
command, input: expect.objectContaining({
text: 'FIRE BIRD',
}),
operationKey: command.operationKey,
pluginKey: command.pluginKey,
}), }),
); );
}); });

View File

@ -50,13 +50,13 @@ import { getMetadataArgsStorage } from 'typeorm';
import { AdminAuthGuardModule } from '../../../../src/modules/admin/identity/auth/admin-auth-guard.module'; import { AdminAuthGuardModule } from '../../../../src/modules/admin/identity/auth/admin-auth-guard.module';
import { DictModule } from '../../../../src/modules/admin/platform-config/dict/dict.module'; import { DictModule } from '../../../../src/modules/admin/platform-config/dict/dict.module';
import { AppModule } from '../../../../src/app.module'; import { AppModule } from '../../../../src/app.module';
import { QqbotAccountController } from '../../../../src/modules/qqbot/core/account/qqbot-account.controller'; import { QqbotAccountController } from '../../../../src/modules/qqbot/core/contract/account/qqbot-account.controller';
import { QqbotCommandController } from '../../../../src/modules/qqbot/core/command/qqbot-command.controller'; import { QqbotCommandController } from '../../../../src/modules/qqbot/core/contract/command/qqbot-command.controller';
import { QqbotDashboardController } from '../../../../src/modules/qqbot/core/dashboard/qqbot-dashboard.controller'; import { QqbotDashboardController } from '../../../../src/modules/qqbot/core/contract/dashboard/qqbot-dashboard.controller';
import { QqbotMessageController } from '../../../../src/modules/qqbot/core/message/qqbot-message.controller'; import { QqbotMessageController } from '../../../../src/modules/qqbot/core/contract/message/qqbot-message.controller';
import { QqbotPermissionController } from '../../../../src/modules/qqbot/core/permission/qqbot-permission.controller'; import { QqbotPermissionController } from '../../../../src/modules/qqbot/core/contract/permission/qqbot-permission.controller';
import { QqbotRuleController } from '../../../../src/modules/qqbot/core/rule/qqbot-rule.controller'; import { QqbotRuleController } from '../../../../src/modules/qqbot/core/contract/rule/qqbot-rule.controller';
import { QqbotSendController } from '../../../../src/modules/qqbot/core/send/qqbot-send.controller'; import { QqbotSendController } from '../../../../src/modules/qqbot/core/contract/send/qqbot-send.controller';
import { import {
QQBOT_CORE_CONTROLLERS, QQBOT_CORE_CONTROLLERS,
QQBOT_CORE_ENTITIES, QQBOT_CORE_ENTITIES,
@ -128,7 +128,6 @@ describe('QQBot core module contract', () => {
'GET /qqbot/permission/config', 'GET /qqbot/permission/config',
'GET /qqbot/permission/allowlist', 'GET /qqbot/permission/allowlist',
'GET /qqbot/permission/blocklist', 'GET /qqbot/permission/blocklist',
'GET /qqbot/plugin/list',
'GET /qqbot/rule/list', 'GET /qqbot/rule/list',
'GET /qqbot/send/log/list', 'GET /qqbot/send/log/list',
'POST /qqbot/send/private', 'POST /qqbot/send/private',
@ -191,9 +190,6 @@ describe('QQBot core module contract', () => {
QqbotSendController, QqbotSendController,
]), ]),
); );
expect(getNames(QQBOT_CORE_CONTROLLERS)).toEqual(
expect.arrayContaining(['QqbotPluginController']),
);
expect(getNames(QQBOT_CORE_PROVIDERS)).toEqual( expect(getNames(QQBOT_CORE_PROVIDERS)).toEqual(
expect.arrayContaining([ expect.arrayContaining([
'QqbotAccountService', 'QqbotAccountService',
@ -204,7 +200,6 @@ describe('QQBot core module contract', () => {
'QqbotConfigService', 'QqbotConfigService',
'QqbotDashboardService', 'QqbotDashboardService',
'QqbotDedupeService', 'QqbotDedupeService',
'QqbotEventPluginRegistryService',
'QqbotEventService', 'QqbotEventService',
'QqbotMessageService', 'QqbotMessageService',
'NapcatDeviceIdentityService', 'NapcatDeviceIdentityService',
@ -212,8 +207,6 @@ describe('QQBot core module contract', () => {
'QqbotNapcatLoginService', 'QqbotNapcatLoginService',
'QqbotNapcatWatchdogService', 'QqbotNapcatWatchdogService',
'QqbotPermissionService', 'QqbotPermissionService',
'QqbotPluginHttpClientService',
'QqbotPluginRegistryService',
'QqbotRateLimitService', 'QqbotRateLimitService',
'QqbotReplyTemplateService', 'QqbotReplyTemplateService',
'QqbotReverseWsService', 'QqbotReverseWsService',

View File

@ -25,7 +25,7 @@ describe('QQBot core plugin execution ports', () => {
it('keeps command parser generic and leaves plugin-specific parsing to plugins', () => { it('keeps command parser generic and leaves plugin-specific parsing to plugins', () => {
const source = readSource( const source = readSource(
'src/modules/qqbot/core/command/qqbot-command-parser.service.ts', 'src/modules/qqbot/core/application/command/qqbot-command-parser.service.ts',
); );
const bannedParserSignals = [ const bannedParserSignals = [
@ -41,7 +41,7 @@ describe('QQBot core plugin execution ports', () => {
it('dispatches unconsumed events through Plugin Platform instead of directly invoking Repeater', () => { it('dispatches unconsumed events through Plugin Platform instead of directly invoking Repeater', () => {
const source = readSource( const source = readSource(
'src/modules/qqbot/core/rule/qqbot-rule-engine.service.ts', 'src/modules/qqbot/core/application/rule/qqbot-rule-engine.service.ts',
); );
const bannedRepeaterSignals = [ const bannedRepeaterSignals = [

View File

@ -1,4 +1,4 @@
import { QQBOT_CORE_DOMAIN_CONTRACT } from '../../../../src/modules/qqbot/core/qqbot-core.contract'; import { QQBOT_CORE_DOMAIN_CONTRACT } from '../../../../src/modules/qqbot/core/contract/qqbot-core.contract';
import { readRefactorV3SqlSchema } from '../../../helpers/sql-schema.helper'; import { readRefactorV3SqlSchema } from '../../../helpers/sql-schema.helper';
describe('QQBot core send contract', () => { describe('QQBot core send contract', () => {

View File

@ -1,4 +1,4 @@
import { QQBOT_CORE_DOMAIN_CONTRACT } from '../../../../src/modules/qqbot/core/qqbot-core.contract'; import { QQBOT_CORE_DOMAIN_CONTRACT } from '../../../../src/modules/qqbot/core/contract/qqbot-core.contract';
import { readRefactorV3SqlSchema } from '../../../helpers/sql-schema.helper'; import { readRefactorV3SqlSchema } from '../../../helpers/sql-schema.helper';
describe('QQBot core status contract', () => { describe('QQBot core status contract', () => {

View File

@ -5,9 +5,9 @@ import {
NapcatDeviceIdentityService, NapcatDeviceIdentityService,
NAPCAT_RUNTIME_DOMAIN_CONTRACT, NAPCAT_RUNTIME_DOMAIN_CONTRACT,
NAPCAT_RUNTIME_ENTITIES, NAPCAT_RUNTIME_ENTITIES,
toNapcatDockerDeviceOptions,
} from '../../../../src/modules/qqbot/napcat'; } from '../../../../src/modules/qqbot/napcat';
import { QqbotNapcatContainerService } from '../../../../src/modules/qqbot/napcat/qqbot-napcat-container.service'; import { toNapcatDockerDeviceOptions } from '../../../../src/modules/qqbot/napcat/infrastructure/integration/container/napcat-docker-device-options';
import { QqbotNapcatContainerService } from '../../../../src/modules/qqbot/napcat/infrastructure/integration/container/qqbot-napcat-container.service';
import { readRefactorV3SqlSchema } from '../../../helpers/sql-schema.helper'; import { readRefactorV3SqlSchema } from '../../../helpers/sql-schema.helper';
type EntityClass = new (...args: never[]) => unknown; type EntityClass = new (...args: never[]) => unknown;

View File

@ -38,7 +38,7 @@ describe('NapCat persistent login state contract', () => {
it('keeps login truth in persistence rather than in-memory scan sessions', () => { it('keeps login truth in persistence rather than in-memory scan sessions', () => {
const loginSource = readSource( const loginSource = readSource(
'src/modules/qqbot/napcat/login/qqbot-napcat-login.service.ts', 'src/modules/qqbot/napcat/application/login/qqbot-napcat-login.service.ts',
); );
const bannedMemorySignals = ['new Map', 'scanSessions'].filter((signal) => const bannedMemorySignals = ['new Map', 'scanSessions'].filter((signal) =>
@ -56,7 +56,7 @@ describe('NapCat persistent login state contract', () => {
it('keeps captcha and new-device challenges recoverable and separate', () => { it('keeps captcha and new-device challenges recoverable and separate', () => {
const stateMachine = readSource( const stateMachine = readSource(
'src/modules/qqbot/napcat/login/napcat-login-state-machine.ts', 'src/modules/qqbot/napcat/domain/login/napcat-login-state-machine.ts',
); );
expect(stateMachine).toEqual(expect.stringContaining("type: 'captcha'")); expect(stateMachine).toEqual(expect.stringContaining("type: 'captcha'"));

View File

@ -1,7 +1,7 @@
import * as fs from 'fs'; import * as fs from 'fs';
import * as path from 'path'; import * as path from 'path';
import { runQqbotPluginCli } from '../../../../scripts/qqbot-plugin/cli'; import { runQqbotPluginCli } from '../../../../scripts/qqbot-plugin/cli';
import { parseQqbotPluginManifest } from '../../../../src/modules/qqbot/plugin-platform/manifest'; import { parseQqbotPluginManifest } from '../../../../src/modules/qqbot/plugin-platform/domain/manifest';
const projectRoot = path.resolve(__dirname, '..', '..', '..', '..'); const projectRoot = path.resolve(__dirname, '..', '..', '..', '..');
const workspaceTmpRoot = path.resolve( const workspaceTmpRoot = path.resolve(

View File

@ -2,7 +2,7 @@ import {
QQBOT_PLUGIN_ALLOWED_PERMISSIONS, QQBOT_PLUGIN_ALLOWED_PERMISSIONS,
parseQqbotPluginManifest, parseQqbotPluginManifest,
QqbotPluginManifestValidationError, QqbotPluginManifestValidationError,
} from '../../../../src/modules/qqbot/plugin-platform/manifest'; } from '../../../../src/modules/qqbot/plugin-platform/domain/manifest';
const createValidManifest = () => ({ const createValidManifest = () => ({
assets: [ assets: [

View File

@ -2,7 +2,7 @@ import { getMetadataArgsStorage } from 'typeorm';
import { import {
QQBOT_PLUGIN_PLATFORM_DOMAIN_CONTRACT, QQBOT_PLUGIN_PLATFORM_DOMAIN_CONTRACT,
QQBOT_PLUGIN_PLATFORM_ENTITIES, QQBOT_PLUGIN_PLATFORM_ENTITIES,
} from '../../../../src/modules/qqbot/plugin-platform/persistence'; } from '../../../../src/modules/qqbot/plugin-platform/infrastructure/persistence';
import { readRefactorV3SqlSchema } from '../../../helpers/sql-schema.helper'; import { readRefactorV3SqlSchema } from '../../../helpers/sql-schema.helper';
type EntityClass = new (...args: never[]) => unknown; type EntityClass = new (...args: never[]) => unknown;

View File

@ -9,10 +9,10 @@ const readSource = (relativePath: string) =>
describe('QQBot plugin platform lifecycle runtime contract', () => { describe('QQBot plugin platform lifecycle runtime contract', () => {
it('uses dedicated lifecycle use cases instead of direct status flips', () => { it('uses dedicated lifecycle use cases instead of direct status flips', () => {
const controller = readSource( const controller = readSource(
'src/modules/qqbot/plugin-platform/plugin-platform.controller.ts', 'src/modules/qqbot/plugin-platform/contract/plugin-platform.controller.ts',
); );
const service = readSource( const service = readSource(
'src/modules/qqbot/plugin-platform/plugin-platform.service.ts', 'src/modules/qqbot/plugin-platform/application/plugin-platform.service.ts',
); );
const bannedDirectStatusFlips = [ const bannedDirectStatusFlips = [
@ -36,8 +36,8 @@ describe('QQBot plugin platform lifecycle runtime contract', () => {
it('activates workers and refreshes active registries during lifecycle transitions', () => { it('activates workers and refreshes active registries during lifecycle transitions', () => {
const source = [ const source = [
readSource('src/modules/qqbot/plugin-platform/plugin-platform.service.ts'), readSource('src/modules/qqbot/plugin-platform/application/plugin-platform.service.ts'),
readSource('src/modules/qqbot/plugin-platform/runtime/worker-runtime.ts'), readSource('src/modules/qqbot/plugin-platform/infrastructure/integration/runtime/worker-runtime.ts'),
].join('\n'); ].join('\n');
const missingRuntimeSignals = [ const missingRuntimeSignals = [
@ -55,7 +55,7 @@ describe('QQBot plugin platform lifecycle runtime contract', () => {
it('exposes operation executor and event dispatcher through the platform', () => { it('exposes operation executor and event dispatcher through the platform', () => {
const source = readSource( const source = readSource(
'src/modules/qqbot/plugin-platform/plugin-platform.service.ts', 'src/modules/qqbot/plugin-platform/application/plugin-platform.service.ts',
); );
const missingExecutorSignals = [ const missingExecutorSignals = [

View File

@ -5,9 +5,9 @@ import { Test } from '@nestjs/testing';
import { getRepositoryToken } from '@nestjs/typeorm'; import { getRepositoryToken } from '@nestjs/typeorm';
import * as request from 'supertest'; import * as request from 'supertest';
import { JwtAuthGuard } from '../../../../src/modules/admin/identity/auth/jwt-auth.guard'; import { JwtAuthGuard } from '../../../../src/modules/admin/identity/auth/jwt-auth.guard';
import { QqbotPluginPlatformController } from '../../../../src/modules/qqbot/plugin-platform/plugin-platform.controller'; import { QqbotPluginPlatformController } from '../../../../src/modules/qqbot/plugin-platform/contract/plugin-platform.controller';
import { QqbotPluginPlatformModule } from '../../../../src/modules/qqbot/plugin-platform/plugin-platform.module'; import { QqbotPluginPlatformModule } from '../../../../src/modules/qqbot/plugin-platform/plugin-platform.module';
import { QqbotPluginPlatformService } from '../../../../src/modules/qqbot/plugin-platform/plugin-platform.service'; import { QqbotPluginPlatformService } from '../../../../src/modules/qqbot/plugin-platform/application/plugin-platform.service';
import { import {
QQBOT_PLUGIN_PLATFORM_ENTITIES, QQBOT_PLUGIN_PLATFORM_ENTITIES,
QqbotPlugin, QqbotPlugin,
@ -19,7 +19,7 @@ import {
QqbotPluginOperation, QqbotPluginOperation,
QqbotPluginRuntimeEvent, QqbotPluginRuntimeEvent,
QqbotPluginVersion, QqbotPluginVersion,
} from '../../../../src/modules/qqbot/plugin-platform/persistence'; } from '../../../../src/modules/qqbot/plugin-platform/infrastructure/persistence';
import { import {
collectControllerRoutes, collectControllerRoutes,
routeKey, routeKey,

Some files were not shown because too many files have changed in this diff Show More