diff --git a/docs/refactor-v3/qqbot-architecture-convergence-inventory.md b/docs/refactor-v3/qqbot-architecture-convergence-inventory.md new file mode 100644 index 0000000..6196d55 --- /dev/null +++ b/docs/refactor-v3/qqbot-architecture-convergence-inventory.md @@ -0,0 +1,78 @@ +# QQBot Architecture Convergence Inventory + +This inventory freezes the current QQBot behavior before the strong convergence rewrite. It is a baseline for preserving behavior while deleting old structure. + +## Repository Baseline + +- API branch: `dev-api-architecture-convergence-v3`. +- Admin branch: `dev-admin-architecture-convergence-v3`. +- API package manager: `pnpm@9.15.9`. +- Admin package manager: `pnpm@10.28.2`, Node engine `>=20.19.0`. +- Current plugin source root: `src/modules/qqbot/plugins`. + +## Built-In Plugin Matrix + +| Current Dir | Platform Key | Legacy Key | Runtime Entry | Operations | Events | +| --- | --- | --- | --- | --- | --- | +| `bangDream` | `bangdream` | `bangDream` | `qqbot-bangdream.plugin.ts` | 15 | 0 | +| `ff14Market` | `ff14-market` | `ff14Market` | `qqbot-ff14-market.plugin.ts` | 2 | 0 | +| `fflogs` | `fflogs` | none | `qqbot-fflogs.plugin.ts` | 1 | 0 | +| `repeater` | `repeater` | none | `qqbot-repeater.plugin.ts` | 0 | 1 | + +## BangDream Operation Matrix + +| Operation Key | Name | Handler | Aliases | +| --- | --- | --- | --- | +| `bangdream.song.search` | 查曲 | `searchSong` | 查曲, bd, bangdream, bandori, 邦邦, 邦邦查歌 | +| `bangdream.song.chart` | 查谱面 | `getSongChart` | 查谱面, 谱面, bd谱面 | +| `bangdream.song.random` | 随机曲 | `randomSong` | 随机曲, 随机, bd随机 | +| `bangdream.song.meta` | 查询分数表 | `getSongMeta` | 查询分数表, 查分数表, 查询分数榜, 查分数榜, bd分数表 | +| `bangdream.card.search` | 查卡 | `searchCard` | 查卡, 查卡牌, bd查卡 | +| `bangdream.card.illustration` | 查卡面 | `getCardIllustration` | 查卡面, 查卡插画, 查插画, bd卡面 | +| `bangdream.character.search` | 查角色 | `searchCharacter` | 查角色, bd角色 | +| `bangdream.event.search` | 查活动 | `searchEvent` | 查活动, bd活动 | +| `bangdream.event.stage` | 查试炼 | `getEventStage` | 查试炼, 查stage, 查舞台, 查festival, 查5v5 | +| `bangdream.player.search` | 查玩家 | `searchPlayer` | 查玩家, 查询玩家, bd玩家 | +| `bangdream.gacha.search` | 查卡池 | `searchGacha` | 查卡池, bd卡池 | +| `bangdream.gacha.simulate` | 抽卡模拟 | `simulateGacha` | 抽卡模拟, bd抽卡 | +| `bangdream.cutoff.detail` | ycx | `getCutoffDetail` | ycx, 预测线, 查档线, bd档线 | +| `bangdream.cutoff.all` | ycxall | `getCutoffAll` | ycxall, myycx, 全部档线 | +| `bangdream.cutoff.recent` | lsycx | `getCutoffRecent` | lsycx, 历史档线, 近期档线 | + +## Other Plugin Capability Matrix + +| Operation/Event Key | Plugin | Type | Handler | Aliases | +| --- | --- | --- | --- | --- | +| `ff14.item.resolve` | `ff14-market` | operation | `resolveItem` | 物品, item, ff14item | +| `ff14.market.price` | `ff14-market` | operation | `getPrice` | 查价, price, ff14price | +| `fflogs.character.summary` | `fflogs` | operation | `getCharacterSummary` | fflogs, logs, 查logs, 查log | +| `repeater.message` | `repeater` | event | `handleMessage` | message event | + +## Current Route And Contract Surface + +- QQBot core compatibility routes remain under `/qqbot/account`, `/qqbot/command`, `/qqbot/rule`, `/qqbot/message`, `/qqbot/send`, and `/qqbot/dashboard`. +- Plugin compatibility routes remain under `/qqbot/plugin/*`, including list, operation list, operation page, and health. +- Plugin platform management routes remain under `/qqbot/plugin-platform/*`. +- NapCat login routes remain under QQBot account/login compatibility endpoints and must continue to expose scan create, refresh, status, captcha submit, SSE events, and runtime status. +- Admin current QQBot callers are concentrated in `apps/web-antdv-next/src/api/qqbot/index.ts`; the target split is `index.ts`, `plugin.ts`, and `napcat.ts`. + +## Command Seed Linkage + +- Current online command seed is `sql/qqbot-init.sql`. +- Current BangDream command rows use legacy plugin key `bangDream` and `bangdream.*` operation keys. +- Current FF14 market command row uses legacy plugin key `ff14Market` and operation key `ff14.market.price`. +- Current FFLogs command row uses plugin key `fflogs` and operation key `fflogs.character.summary`. +- Repeater is event-driven and has no command row. + +## Obsolete File Groups To Delete After Replacement + +- Core feature buckets that remain outside the approved layers: `core/account`, `core/command`, `core/config`, `core/connection`, `core/dashboard`, `core/dedupe`, `core/event`, `core/message`, `core/mqtt`, `core/permission`, `core/rule`, `core/send`. +- Plugin Platform buckets that remain outside the approved layers: `plugin-platform/manifest`, `plugin-platform/persistence`, `plugin-platform/registry`, `plugin-platform/runtime`, `plugin-platform/sdk`, root controller/service/module files. +- Plugin directories that must be replaced by package keys: `plugins/bangDream`, `plugins/ff14Market`. +- Plugin service files that must not survive the rewrite as runtime code: `qqbot-bangdream.plugin.ts`, `qqbot-ff14-market.plugin.ts`, `qqbot-fflogs.plugin.ts`, `qqbot-repeater.plugin.ts`, and companion Nest service files. +- NapCat buckets that remain outside the approved layers: `napcat/device`, `napcat/container`, `napcat/login`, `napcat/integration`, root service files, and `napcat/persistence.ts`. + +## Verification + +- `test/modules/qqbot/architecture/qqbot-current-operation-matrix.spec.ts` locks this matrix against current registry, manifests, and `sql/qqbot-init.sql`. +- Later rewrite batches must update the test only when the new package structure proves the same behavior through manifest-driven runtime contracts. diff --git a/test/modules/qqbot/architecture/qqbot-current-operation-matrix.spec.ts b/test/modules/qqbot/architecture/qqbot-current-operation-matrix.spec.ts new file mode 100644 index 0000000..5ee25ff --- /dev/null +++ b/test/modules/qqbot/architecture/qqbot-current-operation-matrix.spec.ts @@ -0,0 +1,221 @@ +import { readFileSync } from 'fs'; +import { join } from 'path'; + +import { parseQqbotPluginManifest } from '../../../../src/modules/qqbot/plugin-platform/manifest'; +import { BANGDREAM_OPERATION_REGISTRY } from '../../../../src/modules/qqbot/plugins/bangDream/registry/operation-registry'; + +const repoRoot = join(__dirname, '../../../..'); +const pluginRoot = join(repoRoot, 'src/modules/qqbot/plugins'); + +type ExpectedOperation = { + aliases: string[]; + handlerName: string; + key: string; + name: string; +}; + +const readJson = (filePath: string) => + JSON.parse(readFileSync(filePath, 'utf8')) as Record; + +const readManifest = (pluginDir: string) => + parseQqbotPluginManifest( + readJson(join(pluginRoot, pluginDir, 'plugin.json')), + { + pluginRoot: join(pluginRoot, pluginDir), + }, + ); + +const bangDreamOperations: ExpectedOperation[] = [ + { + aliases: ['查曲', 'bd', 'bangdream', 'bandori', '邦邦', '邦邦查歌'], + handlerName: 'searchSong', + key: 'bangdream.song.search', + name: '查曲', + }, + { + aliases: ['查谱面', '谱面', 'bd谱面'], + handlerName: 'getSongChart', + key: 'bangdream.song.chart', + name: '查谱面', + }, + { + aliases: ['随机曲', '随机', 'bd随机'], + handlerName: 'randomSong', + key: 'bangdream.song.random', + name: '随机曲', + }, + { + aliases: ['查询分数表', '查分数表', '查询分数榜', '查分数榜', 'bd分数表'], + handlerName: 'getSongMeta', + key: 'bangdream.song.meta', + name: '查询分数表', + }, + { + aliases: ['查卡', '查卡牌', 'bd查卡'], + handlerName: 'searchCard', + key: 'bangdream.card.search', + name: '查卡', + }, + { + aliases: ['查卡面', '查卡插画', '查插画', 'bd卡面'], + handlerName: 'getCardIllustration', + key: 'bangdream.card.illustration', + name: '查卡面', + }, + { + aliases: ['查角色', 'bd角色'], + handlerName: 'searchCharacter', + key: 'bangdream.character.search', + name: '查角色', + }, + { + aliases: ['查活动', 'bd活动'], + handlerName: 'searchEvent', + key: 'bangdream.event.search', + name: '查活动', + }, + { + aliases: ['查试炼', '查stage', '查舞台', '查festival', '查5v5'], + handlerName: 'getEventStage', + key: 'bangdream.event.stage', + name: '查试炼', + }, + { + aliases: ['查玩家', '查询玩家', 'bd玩家'], + handlerName: 'searchPlayer', + key: 'bangdream.player.search', + name: '查玩家', + }, + { + aliases: ['查卡池', 'bd卡池'], + handlerName: 'searchGacha', + key: 'bangdream.gacha.search', + name: '查卡池', + }, + { + aliases: ['抽卡模拟', 'bd抽卡'], + handlerName: 'simulateGacha', + key: 'bangdream.gacha.simulate', + name: '抽卡模拟', + }, + { + aliases: ['ycx', '预测线', '查档线', 'bd档线'], + handlerName: 'getCutoffDetail', + key: 'bangdream.cutoff.detail', + name: 'ycx', + }, + { + aliases: ['ycxall', 'myycx', '全部档线'], + handlerName: 'getCutoffAll', + key: 'bangdream.cutoff.all', + name: 'ycxall', + }, + { + aliases: ['lsycx', '历史档线', '近期档线'], + handlerName: 'getCutoffRecent', + key: 'bangdream.cutoff.recent', + name: 'lsycx', + }, +]; + +describe('QQBot current operation matrix', () => { + it('freezes the current BangDream operation registry before rewrite', () => { + expect( + BANGDREAM_OPERATION_REGISTRY.map((operation) => ({ + aliases: [...operation.onlineCommand.aliases], + handlerName: operation.handlerName, + key: operation.key, + name: operation.name, + })), + ).toEqual(bangDreamOperations); + }); + + it('freezes built-in plugin manifests and exposed capabilities', () => { + const manifests = { + bangDream: readManifest('bangDream'), + ff14Market: readManifest('ff14Market'), + fflogs: readManifest('fflogs'), + repeater: readManifest('repeater'), + }; + + expect(manifests.bangDream.pluginKey).toBe('bangdream'); + expect( + manifests.bangDream.operations.map((operation) => ({ + aliases: operation.aliases, + handlerName: operation.handlerName, + key: operation.key, + name: operation.name, + })), + ).toEqual(bangDreamOperations); + + expect( + manifests.ff14Market.operations.map((operation) => ({ + aliases: operation.aliases, + handlerName: operation.handlerName, + key: operation.key, + name: operation.name, + })), + ).toEqual([ + { + aliases: ['物品', 'item', 'ff14item'], + handlerName: 'resolveItem', + key: 'ff14.item.resolve', + name: '解析物品', + }, + { + aliases: ['查价', 'price', 'ff14price'], + handlerName: 'getPrice', + key: 'ff14.market.price', + name: '市场查价', + }, + ]); + + expect( + manifests.fflogs.operations.map((operation) => ({ + aliases: operation.aliases, + handlerName: operation.handlerName, + key: operation.key, + name: operation.name, + })), + ).toEqual([ + { + aliases: ['fflogs', 'logs', '查logs', '查log'], + handlerName: 'getCharacterSummary', + key: 'fflogs.character.summary', + name: '角色排名摘要', + }, + ]); + + expect(manifests.repeater.operations).toEqual([]); + expect( + manifests.repeater.events.map((event) => ({ + eventName: event.eventName, + handlerName: event.handlerName, + key: event.key, + name: event.name, + })), + ).toEqual([ + { + eventName: 'message', + handlerName: 'handleMessage', + key: 'repeater.message', + name: '消息事件', + }, + ]); + }); + + it('freezes current online command seed linkage for command plugins', () => { + const seedSql = readFileSync(join(repoRoot, 'sql/qqbot-init.sql'), 'utf8'); + + for (const operation of bangDreamOperations) { + expect(seedSql).toContain(`'${operation.key}'`); + expect(seedSql).toContain(JSON.stringify(operation.aliases)); + } + + expect(seedSql).toContain(`'ff14Market', 'ff14.market.price'`); + expect(seedSql).toContain(`'fflogs', 'fflogs.character.summary'`); + expect(seedSql).toContain( + `'bangDream', 'bangdream.event.stage', 'plain'`, + ); + }); +});