diff --git a/packages/napcat-adapter/index.ts b/packages/napcat-adapter/index.ts new file mode 100644 index 00000000..e9ed47ee --- /dev/null +++ b/packages/napcat-adapter/index.ts @@ -0,0 +1,176 @@ +import { InstanceContext, NapCatCore } from 'napcat-core'; +import { NapCatPathWrapper } from 'napcat-common/src/path'; +import { NapCatOneBot11Adapter } from 'napcat-onebot'; +import { NapCatProtocolAdapter } from 'napcat-protocol'; + +// 协议适配器类型 +export type ProtocolAdapterType = 'onebot11' | 'napcat-protocol'; + +// 协议适配器接口 +export interface IProtocolAdapter { + readonly name: string; + readonly enabled: boolean; + init (): Promise; + close (): Promise; +} + +// 协议适配器包装器 +class OneBotAdapterWrapper implements IProtocolAdapter { + readonly name = 'onebot11'; + private adapter: NapCatOneBot11Adapter; + + constructor (adapter: NapCatOneBot11Adapter) { + this.adapter = adapter; + } + + get enabled (): boolean { + return true; // OneBot11 默认启用 + } + + async init (): Promise { + await this.adapter.InitOneBot(); + } + + async close (): Promise { + await this.adapter.networkManager.closeAllAdapters(); + } + + getAdapter (): NapCatOneBot11Adapter { + return this.adapter; + } +} + +// NapCat Protocol 适配器包装器 +class NapCatProtocolAdapterWrapper implements IProtocolAdapter { + readonly name = 'napcat-protocol'; + private adapter: NapCatProtocolAdapter; + + constructor (adapter: NapCatProtocolAdapter) { + this.adapter = adapter; + } + + get enabled (): boolean { + return this.adapter.isEnabled(); + } + + async init (): Promise { + await this.adapter.initProtocol(); + } + + async close (): Promise { + await this.adapter.close(); + } + + getAdapter (): NapCatProtocolAdapter { + return this.adapter; + } +} + +// 协议适配器管理器 +export class NapCatAdapterManager { + private core: NapCatCore; + private context: InstanceContext; + private pathWrapper: NapCatPathWrapper; + + // 协议适配器实例 + private onebotAdapter: OneBotAdapterWrapper | null = null; + private napcatProtocolAdapter: NapCatProtocolAdapterWrapper | null = null; + + // 所有已注册的适配器 + private adapters: Map = new Map(); + + constructor (core: NapCatCore, context: InstanceContext, pathWrapper: NapCatPathWrapper) { + this.core = core; + this.context = context; + this.pathWrapper = pathWrapper; + } + + // 初始化所有协议适配器 + async initAdapters (): Promise { + this.context.logger.log('[AdapterManager] 开始初始化协议适配器...'); + + // 初始化 OneBot11 适配器 (默认启用) + try { + const onebot = new NapCatOneBot11Adapter(this.core, this.context, this.pathWrapper); + this.onebotAdapter = new OneBotAdapterWrapper(onebot); + this.adapters.set('onebot11', this.onebotAdapter); + await this.onebotAdapter.init(); + this.context.logger.log('[AdapterManager] OneBot11 适配器初始化完成'); + } catch (e) { + this.context.logger.logError('[AdapterManager] OneBot11 适配器初始化失败:', e); + } + + // 初始化 NapCat Protocol 适配器 (默认关闭,需要配置启用) + try { + const napcatProtocol = new NapCatProtocolAdapter(this.core, this.context, this.pathWrapper); + this.napcatProtocolAdapter = new NapCatProtocolAdapterWrapper(napcatProtocol); + this.adapters.set('napcat-protocol', this.napcatProtocolAdapter); + + if (this.napcatProtocolAdapter.enabled) { + await this.napcatProtocolAdapter.init(); + this.context.logger.log('[AdapterManager] NapCat Protocol 适配器初始化完成'); + } else { + this.context.logger.log('[AdapterManager] NapCat Protocol 适配器未启用,跳过初始化'); + } + } catch (e) { + this.context.logger.logError('[AdapterManager] NapCat Protocol 适配器初始化失败:', e); + } + + this.context.logger.log(`[AdapterManager] 协议适配器初始化完成,已加载 ${this.adapters.size} 个适配器`); + } + + // 获取 OneBot11 适配器 + getOneBotAdapter (): NapCatOneBot11Adapter | null { + return this.onebotAdapter?.getAdapter() ?? null; + } + + // 获取 NapCat Protocol 适配器 + getNapCatProtocolAdapter (): NapCatProtocolAdapter | null { + return this.napcatProtocolAdapter?.getAdapter() ?? null; + } + + // 获取指定适配器 + getAdapter (name: ProtocolAdapterType): IProtocolAdapter | undefined { + return this.adapters.get(name); + } + + // 获取所有已启用的适配器 + getEnabledAdapters (): IProtocolAdapter[] { + return Array.from(this.adapters.values()).filter(adapter => adapter.enabled); + } + + // 获取所有适配器 + getAllAdapters (): IProtocolAdapter[] { + return Array.from(this.adapters.values()); + } + + // 关闭所有适配器 + async closeAllAdapters (): Promise { + this.context.logger.log('[AdapterManager] 开始关闭所有协议适配器...'); + + for (const [name, adapter] of this.adapters) { + try { + await adapter.close(); + this.context.logger.log(`[AdapterManager] ${name} 适配器已关闭`); + } catch (e) { + this.context.logger.logError(`[AdapterManager] 关闭 ${name} 适配器失败:`, e); + } + } + + this.adapters.clear(); + this.context.logger.log('[AdapterManager] 所有协议适配器已关闭'); + } + + // 重新加载指定适配器 + async reloadAdapter (name: ProtocolAdapterType): Promise { + const adapter = this.adapters.get(name); + if (adapter) { + await adapter.close(); + await adapter.init(); + this.context.logger.log(`[AdapterManager] ${name} 适配器已重新加载`); + } + } +} + +export { NapCatOneBot11Adapter } from 'napcat-onebot'; +export { NapCatProtocolAdapter } from 'napcat-protocol'; diff --git a/packages/napcat-adapter/package.json b/packages/napcat-adapter/package.json new file mode 100644 index 00000000..324d3455 --- /dev/null +++ b/packages/napcat-adapter/package.json @@ -0,0 +1,30 @@ +{ + "name": "napcat-adapter", + "version": "0.0.1", + "private": true, + "type": "module", + "main": "index.ts", + "scripts": { + "typecheck": "tsc --noEmit --skipLibCheck -p tsconfig.json" + }, + "exports": { + ".": { + "import": "./index.ts" + }, + "./*": { + "import": "./*" + } + }, + "dependencies": { + "napcat-core": "workspace:*", + "napcat-common": "workspace:*", + "napcat-onebot": "workspace:*", + "napcat-protocol": "workspace:*" + }, + "devDependencies": { + "@types/node": "^22.0.1" + }, + "engines": { + "node": ">=18.0.0" + } +} \ No newline at end of file diff --git a/packages/napcat-adapter/tsconfig.json b/packages/napcat-adapter/tsconfig.json new file mode 100644 index 00000000..48d3a03b --- /dev/null +++ b/packages/napcat-adapter/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig.base.json", + "include": [ + "*.ts", + "**/*.ts" + ], + "exclude": [ + "node_modules", + "dist" + ] +} \ No newline at end of file diff --git a/packages/napcat-core/index.ts b/packages/napcat-core/index.ts index 3fe915c6..5058b35e 100644 --- a/packages/napcat-core/index.ts +++ b/packages/napcat-core/index.ts @@ -45,6 +45,7 @@ export * from './helper/log'; export * from './helper/qq-basic-info'; export * from './helper/event'; export * from './helper/config'; +export * from './helper/config-base'; export * from './helper/proxy-handler'; export * from './helper/session-proxy'; diff --git a/packages/napcat-framework/napcat.ts b/packages/napcat-framework/napcat.ts index e9974aa6..882c64bc 100644 --- a/packages/napcat-framework/napcat.ts +++ b/packages/napcat-framework/napcat.ts @@ -1,6 +1,6 @@ import { NapCatPathWrapper } from 'napcat-common/src/path'; import { InitWebUi, WebUiConfig, webUiRuntimePort } from 'napcat-webui-backend/index'; -import { NapCatOneBot11Adapter } from 'napcat-onebot/index'; +import { NapCatAdapterManager } from 'napcat-adapter'; import { NativePacketHandler } from 'napcat-core/packet/handler/client'; import { FFmpegService } from 'napcat-core/helper/ffmpeg/ffmpeg'; import { logSubscription, LogWrapper } from 'napcat-core/helper/log'; @@ -79,11 +79,14 @@ export async function NCoreInitFramework ( // 启动WebUi WebUiDataRuntime.setWorkingEnv(NapCatCoreWorkingEnv.Framework); InitWebUi(logger, pathWrapper, logSubscription, statusHelperSubscription).then().catch(e => logger.logError(e)); - // 初始化LLNC的Onebot实现 - const oneBotAdapter = new NapCatOneBot11Adapter(loaderObject.core, loaderObject.context, pathWrapper); - // 注册到 WebUiDataRuntime,供调试功能使用 - WebUiDataRuntime.setOneBotContext(oneBotAdapter); - await oneBotAdapter.InitOneBot(); + // 使用 NapCatAdapterManager 统一管理协议适配器 + const adapterManager = new NapCatAdapterManager(loaderObject.core, loaderObject.context, pathWrapper); + await adapterManager.initAdapters(); + // 注册 OneBot 适配器到 WebUiDataRuntime,供调试功能使用 + const oneBotAdapter = adapterManager.getOneBotAdapter(); + if (oneBotAdapter) { + WebUiDataRuntime.setOneBotContext(oneBotAdapter); + } } export class NapCatFramework { diff --git a/packages/napcat-framework/package.json b/packages/napcat-framework/package.json index 90debc9f..382efebd 100644 --- a/packages/napcat-framework/package.json +++ b/packages/napcat-framework/package.json @@ -1,33 +1,33 @@ { - "name": "napcat-framework", - "version": "0.0.1", - "private": true, - "type": "module", - "main": "index.ts", - "scripts": { - "build": "vite build", - "typecheck": "tsc --noEmit --skipLibCheck -p tsconfig.json" + "name": "napcat-framework", + "version": "0.0.1", + "private": true, + "type": "module", + "main": "index.ts", + "scripts": { + "build": "vite build", + "typecheck": "tsc --noEmit --skipLibCheck -p tsconfig.json" + }, + "exports": { + ".": { + "import": "./index.ts" }, - "exports": { - ".": { - "import": "./index.ts" - }, - "./*": { - "import": "./*" - } - }, - "dependencies": { - "napcat-core": "workspace:*", - "napcat-common": "workspace:*", - "napcat-onebot": "workspace:*", - "napcat-webui-backend": "workspace:*", - "napcat-vite": "workspace:*", - "napcat-qrcode": "workspace:*" - }, - "devDependencies": { - "@types/node": "^22.0.1" - }, - "engines": { - "node": ">=18.0.0" + "./*": { + "import": "./*" } + }, + "dependencies": { + "napcat-core": "workspace:*", + "napcat-common": "workspace:*", + "napcat-adapter": "workspace:*", + "napcat-webui-backend": "workspace:*", + "napcat-vite": "workspace:*", + "napcat-qrcode": "workspace:*" + }, + "devDependencies": { + "@types/node": "^22.0.1" + }, + "engines": { + "node": ">=18.0.0" + } } \ No newline at end of file diff --git a/packages/napcat-framework/vite.config.ts b/packages/napcat-framework/vite.config.ts index 3e15c1e3..9cae7a4d 100644 --- a/packages/napcat-framework/vite.config.ts +++ b/packages/napcat-framework/vite.config.ts @@ -50,6 +50,8 @@ const FrameworkBaseConfig = () => '@/napcat-pty': resolve(__dirname, '../napcat-pty'), '@/napcat-webui-backend': resolve(__dirname, '../napcat-webui-backend'), '@/image-size': resolve(__dirname, '../image-size'), + '@/napcat-protocol': resolve(__dirname, '../napcat-protocol'), + '@/napcat-adapter': resolve(__dirname, '../napcat-adapter'), }, }, build: { diff --git a/packages/napcat-protocol/action/index.ts b/packages/napcat-protocol/action/index.ts new file mode 100644 index 00000000..bd87c17c --- /dev/null +++ b/packages/napcat-protocol/action/index.ts @@ -0,0 +1,51 @@ +import { NapCatCore } from 'napcat-core'; +import { NapCatProtocolResponse } from '@/napcat-protocol/types'; + +// 前向声明类型,避免循环依赖 +import type { NapCatProtocolAdapter } from '@/napcat-protocol/index'; + +// Action 基类 +export abstract class BaseAction { + abstract actionName: string; + protected core: NapCatCore; + protected adapter: NapCatProtocolAdapter; + + constructor (adapter: NapCatProtocolAdapter, core: NapCatCore) { + this.adapter = adapter; + this.core = core; + } + + protected abstract _handle (payload: PayloadType): Promise; + + async handle (payload: PayloadType): Promise> { + try { + const result = await this._handle(payload); + return { + status: 'ok', + retcode: 0, + data: result, + }; + } catch (e) { + return { + status: 'failed', + retcode: -1, + data: null, + message: e instanceof Error ? e.message : String(e), + }; + } + } +} + +// Action 映射类型 +export type ActionMap = Map>; + +// 创建 Action 映射 +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export function createActionMap (_adapter: NapCatProtocolAdapter, _core: NapCatCore): ActionMap { + const actionMap = new Map>(); + + // 这里可以注册各种 Action + // 例如: actionMap.set('send_msg', new SendMsgAction(adapter, core)); + + return actionMap; +} diff --git a/packages/napcat-protocol/api/index.ts b/packages/napcat-protocol/api/index.ts new file mode 100644 index 00000000..5c578c58 --- /dev/null +++ b/packages/napcat-protocol/api/index.ts @@ -0,0 +1,49 @@ +import { NapCatCore } from 'napcat-core'; +import { NapCatProtocolAdapter } from '@/napcat-protocol/index'; + +// NapCat Protocol API 基类 +export abstract class NapCatProtocolApiBase { + protected adapter: NapCatProtocolAdapter; + protected core: NapCatCore; + + constructor (adapter: NapCatProtocolAdapter, core: NapCatCore) { + this.adapter = adapter; + this.core = core; + } +} + +// 消息 API +export class NapCatProtocolMsgApi extends NapCatProtocolApiBase { + constructor (adapter: NapCatProtocolAdapter, core: NapCatCore) { + super(adapter, core); + } + + // 消息相关 API 方法可以在这里实现 +} + +// 用户 API +export class NapCatProtocolUserApi extends NapCatProtocolApiBase { + constructor (adapter: NapCatProtocolAdapter, core: NapCatCore) { + super(adapter, core); + } + + // 用户相关 API 方法可以在这里实现 +} + +// 群组 API +export class NapCatProtocolGroupApi extends NapCatProtocolApiBase { + constructor (adapter: NapCatProtocolAdapter, core: NapCatCore) { + super(adapter, core); + } + + // 群组相关 API 方法可以在这里实现 +} + +// 好友 API +export class NapCatProtocolFriendApi extends NapCatProtocolApiBase { + constructor (adapter: NapCatProtocolAdapter, core: NapCatCore) { + super(adapter, core); + } + + // 好友相关 API 方法可以在这里实现 +} diff --git a/packages/napcat-protocol/config/config.ts b/packages/napcat-protocol/config/config.ts new file mode 100644 index 00000000..0f0b60e2 --- /dev/null +++ b/packages/napcat-protocol/config/config.ts @@ -0,0 +1,66 @@ +import { Type, Static } from '@sinclair/typebox'; +import Ajv from 'ajv'; + +// WebSocket 服务端配置 +const WebsocketServerConfigSchema = Type.Object({ + name: Type.String({ default: 'napcat-ws-server' }), + enable: Type.Boolean({ default: false }), + host: Type.String({ default: '127.0.0.1' }), + port: Type.Number({ default: 6700 }), + token: Type.String({ default: '' }), + heartInterval: Type.Number({ default: 30000 }), + debug: Type.Boolean({ default: false }), +}); + +// WebSocket 客户端配置 +const WebsocketClientConfigSchema = Type.Object({ + name: Type.String({ default: 'napcat-ws-client' }), + enable: Type.Boolean({ default: false }), + url: Type.String({ default: 'ws://localhost:6701' }), + token: Type.String({ default: '' }), + reconnectInterval: Type.Number({ default: 5000 }), + heartInterval: Type.Number({ default: 30000 }), + debug: Type.Boolean({ default: false }), +}); + +// HTTP 服务端配置 +const HttpServerConfigSchema = Type.Object({ + name: Type.String({ default: 'napcat-http-server' }), + enable: Type.Boolean({ default: false }), + host: Type.String({ default: '127.0.0.1' }), + port: Type.Number({ default: 6702 }), + token: Type.String({ default: '' }), + enableCors: Type.Boolean({ default: true }), + debug: Type.Boolean({ default: false }), +}); + +// 网络配置 +const NetworkConfigSchema = Type.Object({ + httpServers: Type.Array(HttpServerConfigSchema, { default: [] }), + websocketServers: Type.Array(WebsocketServerConfigSchema, { default: [] }), + websocketClients: Type.Array(WebsocketClientConfigSchema, { default: [] }), +}, { default: {} }); + +// NapCat Protocol 主配置 - 默认关闭 +export const NapCatProtocolConfigSchema = Type.Object({ + enable: Type.Boolean({ default: false }), // 默认关闭 + network: NetworkConfigSchema, +}); + +export type NapCatProtocolConfig = Static; +export type HttpServerConfig = Static; +export type WebsocketServerConfig = Static; +export type WebsocketClientConfig = Static; + +export type NetworkAdapterConfig = HttpServerConfig | WebsocketServerConfig | WebsocketClientConfig; +export type NetworkConfigKey = keyof NapCatProtocolConfig['network']; + +export function loadConfig (config: Partial): NapCatProtocolConfig { + const ajv = new Ajv({ useDefaults: true, coerceTypes: true }); + const validate = ajv.compile(NapCatProtocolConfigSchema); + const valid = validate(config); + if (!valid) { + throw new Error(ajv.errorsText(validate.errors)); + } + return config as NapCatProtocolConfig; +} diff --git a/packages/napcat-protocol/config/index.ts b/packages/napcat-protocol/config/index.ts new file mode 100644 index 00000000..646834f3 --- /dev/null +++ b/packages/napcat-protocol/config/index.ts @@ -0,0 +1,11 @@ +import { ConfigBase } from 'napcat-core'; +import { NapCatCore } from 'napcat-core'; +import { NapCatProtocolConfig, NapCatProtocolConfigSchema } from './config'; + +export class NapCatProtocolConfigLoader extends ConfigBase { + constructor (core: NapCatCore, configPath: string) { + super('napcat_protocol', core, configPath, NapCatProtocolConfigSchema); + } +} + +export * from './config'; diff --git a/packages/napcat-protocol/event/NapCatProtocolEvent.ts b/packages/napcat-protocol/event/NapCatProtocolEvent.ts new file mode 100644 index 00000000..2452df31 --- /dev/null +++ b/packages/napcat-protocol/event/NapCatProtocolEvent.ts @@ -0,0 +1,66 @@ +import { NapCatCore } from 'napcat-core'; + +// NapCat Protocol 事件基类 +export abstract class NapCatProtocolEvent { + protected core: NapCatCore; + public time: number; + public self_id: number; + public post_type: string; + + constructor (core: NapCatCore) { + this.core = core; + this.time = Math.floor(Date.now() / 1000); + this.self_id = parseInt(core.selfInfo.uin); + this.post_type = 'event'; + } + + abstract toJSON (): Record; +} + +// 消息事件基类 +export abstract class NapCatProtocolMessageEvent extends NapCatProtocolEvent { + public message_type: 'private' | 'group'; + public message_id: number; + public user_id: number; + + constructor (core: NapCatCore, messageType: 'private' | 'group', messageId: number, userId: number) { + super(core); + this.post_type = 'message'; + this.message_type = messageType; + this.message_id = messageId; + this.user_id = userId; + } +} + +// 通知事件基类 +export abstract class NapCatProtocolNoticeEvent extends NapCatProtocolEvent { + public notice_type: string; + + constructor (core: NapCatCore, noticeType: string) { + super(core); + this.post_type = 'notice'; + this.notice_type = noticeType; + } +} + +// 请求事件基类 +export abstract class NapCatProtocolRequestEvent extends NapCatProtocolEvent { + public request_type: string; + + constructor (core: NapCatCore, requestType: string) { + super(core); + this.post_type = 'request'; + this.request_type = requestType; + } +} + +// 元事件基类 +export abstract class NapCatProtocolMetaEvent extends NapCatProtocolEvent { + public meta_event_type: string; + + constructor (core: NapCatCore, metaEventType: string) { + super(core); + this.post_type = 'meta_event'; + this.meta_event_type = metaEventType; + } +} diff --git a/packages/napcat-protocol/event/index.ts b/packages/napcat-protocol/event/index.ts new file mode 100644 index 00000000..a225d946 --- /dev/null +++ b/packages/napcat-protocol/event/index.ts @@ -0,0 +1 @@ +export * from './NapCatProtocolEvent'; diff --git a/packages/napcat-protocol/index.ts b/packages/napcat-protocol/index.ts new file mode 100644 index 00000000..d0426130 --- /dev/null +++ b/packages/napcat-protocol/index.ts @@ -0,0 +1,104 @@ +import { + InstanceContext, + NapCatCore, +} from 'napcat-core'; +import { NapCatProtocolConfigLoader, NapCatProtocolConfig } from '@/napcat-protocol/config'; +import { NapCatPathWrapper } from 'napcat-common/src/path'; +import { + NapCatProtocolNetworkManager, +} from '@/napcat-protocol/network'; +import { + NapCatProtocolMsgApi, + NapCatProtocolUserApi, + NapCatProtocolGroupApi, + NapCatProtocolFriendApi, +} from '@/napcat-protocol/api'; +import { ActionMap, createActionMap } from '@/napcat-protocol/action'; + +interface ApiListType { + MsgApi: NapCatProtocolMsgApi; + UserApi: NapCatProtocolUserApi; + GroupApi: NapCatProtocolGroupApi; + FriendApi: NapCatProtocolFriendApi; +} + +// NapCat Protocol 适配器 - NapCat 私有 Bot 协议实现 +export class NapCatProtocolAdapter { + readonly core: NapCatCore; + readonly context: InstanceContext; + + configLoader: NapCatProtocolConfigLoader; + public apis: ApiListType; + networkManager: NapCatProtocolNetworkManager; + actions: ActionMap; + + constructor (core: NapCatCore, context: InstanceContext, pathWrapper: NapCatPathWrapper) { + this.core = core; + this.context = context; + this.configLoader = new NapCatProtocolConfigLoader(core, pathWrapper.configPath); + this.apis = { + MsgApi: new NapCatProtocolMsgApi(this, core), + UserApi: new NapCatProtocolUserApi(this, core), + GroupApi: new NapCatProtocolGroupApi(this, core), + FriendApi: new NapCatProtocolFriendApi(this, core), + } as const; + this.actions = createActionMap(this, core); + this.networkManager = new NapCatProtocolNetworkManager(); + } + + // 检查协议是否启用 + isEnabled (): boolean { + return this.configLoader.configData.enable; + } + + async createProtocolLog (config: NapCatProtocolConfig) { + let log = '[NapCat Protocol] 配置加载\n'; + log += `协议状态: ${config.enable ? '已启用' : '已禁用'}\n`; + + if (config.enable) { + for (const key of config.network.httpServers) { + log += `HTTP服务: ${key.host}:${key.port}, : ${key.enable ? '已启动' : '未启动'}\n`; + } + for (const key of config.network.websocketServers) { + log += `WebSocket服务: ${key.host}:${key.port}, : ${key.enable ? '已启动' : '未启动'}\n`; + } + for (const key of config.network.websocketClients) { + log += `WebSocket客户端: ${key.url}, : ${key.enable ? '已启动' : '未启动'}\n`; + } + } + return log; + } + + async initProtocol () { + const config = this.configLoader.configData; + + // 如果协议未启用,直接返回 + if (!config.enable) { + this.context.logger.log('[NapCat Protocol] 协议未启用,跳过初始化'); + return; + } + + const selfInfo = this.core.selfInfo; + const serviceInfo = await this.createProtocolLog(config); + this.context.logger.log(`[Notice] ${serviceInfo}`); + + // 注册网络适配器 + // 这里可以根据配置注册不同的网络适配器 + // 例如: WebSocket Server, WebSocket Client, HTTP Server 等 + + await this.networkManager.openAllAdapters(); + + this.context.logger.log(`[NapCat Protocol] 初始化完成,Bot: ${selfInfo.uin}`); + } + + async close () { + await this.networkManager.closeAllAdapters(); + this.context.logger.log('[NapCat Protocol] 已关闭所有网络适配器'); + } +} + +export * from './types/index'; +export * from './api/index'; +export * from './event/index'; +export * from './config/index'; +export * from './network/index'; diff --git a/packages/napcat-protocol/network/adapter.ts b/packages/napcat-protocol/network/adapter.ts new file mode 100644 index 00000000..37dd1723 --- /dev/null +++ b/packages/napcat-protocol/network/adapter.ts @@ -0,0 +1,37 @@ +import { NetworkAdapterConfig } from '@/napcat-protocol/config/config'; +import { LogWrapper } from 'napcat-core/helper/log'; +import { NapCatCore } from 'napcat-core'; +import { NapCatProtocolAdapter } from '@/napcat-protocol/index'; +import { ActionMap } from '@/napcat-protocol/action'; +import { NapCatProtocolEmitEventContent, NapCatProtocolNetworkReloadType } from '@/napcat-protocol/network/index'; + +export abstract class INapCatProtocolNetworkAdapter { + name: string; + isEnable: boolean = false; + config: CT; + readonly logger: LogWrapper; + readonly core: NapCatCore; + readonly protocolContext: NapCatProtocolAdapter; + readonly actions: ActionMap; + + constructor (name: string, config: CT, core: NapCatCore, protocolContext: NapCatProtocolAdapter, actions: ActionMap) { + this.name = name; + this.config = structuredClone(config); + this.core = core; + this.protocolContext = protocolContext; + this.actions = actions; + this.logger = core.context.logger; + } + + abstract onEvent (event: T): Promise; + + abstract open (): void | Promise; + + abstract close (): void | Promise; + + abstract reload (config: unknown): NapCatProtocolNetworkReloadType | Promise; + + get isActive (): boolean { + return this.isEnable; + } +} diff --git a/packages/napcat-protocol/network/index.ts b/packages/napcat-protocol/network/index.ts new file mode 100644 index 00000000..466940ad --- /dev/null +++ b/packages/napcat-protocol/network/index.ts @@ -0,0 +1,112 @@ +import { NapCatProtocolEvent } from '@/napcat-protocol/event/NapCatProtocolEvent'; +import { NapCatProtocolMessage } from '@/napcat-protocol/types'; +import { NetworkAdapterConfig } from '@/napcat-protocol/config/config'; +import { INapCatProtocolNetworkAdapter } from '@/napcat-protocol/network/adapter'; + +export type NapCatProtocolEmitEventContent = NapCatProtocolEvent | NapCatProtocolMessage; + +export enum NapCatProtocolNetworkReloadType { + Normal = 0, + ConfigChange = 1, + NetWorkReload = 2, + NetWorkClose = 3, + NetWorkOpen = 4, +} + +export class NapCatProtocolNetworkManager { + adapters: Map> = new Map(); + + async openAllAdapters () { + return Promise.all(Array.from(this.adapters.values()).map(adapter => adapter.open())); + } + + async emitEvent (event: NapCatProtocolEmitEventContent) { + return Promise.all(Array.from(this.adapters.values()).map(async adapter => { + if (adapter.isActive) { + return await adapter.onEvent(event); + } + })); + } + + async emitEvents (events: NapCatProtocolEmitEventContent[]) { + return Promise.all(events.map(event => this.emitEvent(event))); + } + + async emitEventByName (names: string[], event: NapCatProtocolEmitEventContent) { + return Promise.all(names.map(async name => { + const adapter = this.adapters.get(name); + if (adapter && adapter.isActive) { + return await adapter.onEvent(event); + } + })); + } + + async emitEventByNames (map: Map) { + return Promise.all(Array.from(map.entries()).map(async ([name, event]) => { + const adapter = this.adapters.get(name); + if (adapter && adapter.isActive) { + return await adapter.onEvent(event); + } + })); + } + + registerAdapter (adapter: INapCatProtocolNetworkAdapter) { + this.adapters.set(adapter.name, adapter); + } + + async registerAdapterAndOpen (adapter: INapCatProtocolNetworkAdapter) { + this.registerAdapter(adapter); + await adapter.open(); + } + + async closeSomeAdapters (adaptersToClose: INapCatProtocolNetworkAdapter[]) { + for (const adapter of adaptersToClose) { + this.adapters.delete(adapter.name); + await adapter.close(); + } + } + + async closeSomeAdapterWhenOpen (adaptersToClose: INapCatProtocolNetworkAdapter[]) { + for (const adapter of adaptersToClose) { + this.adapters.delete(adapter.name); + if (adapter.isEnable) { + await adapter.close(); + } + } + } + + findSomeAdapter (name: string) { + return this.adapters.get(name); + } + + async closeAdapterByPredicate (closeFilter: (adapter: INapCatProtocolNetworkAdapter) => boolean) { + const adaptersToClose = Array.from(this.adapters.values()).filter(closeFilter); + await this.closeSomeAdapters(adaptersToClose); + } + + async closeAllAdapters () { + await Promise.all(Array.from(this.adapters.values()).map(adapter => adapter.close())); + this.adapters.clear(); + } + + async reloadAdapter (name: string, config: T) { + const adapter = this.adapters.get(name); + if (adapter) { + await adapter.reload(config); + } + } + + async reloadSomeAdapters (configMap: Map) { + await Promise.all(Array.from(configMap.entries()).map(([name, config]) => this.reloadAdapter(name, config))); + } + + hasActiveAdapters (): boolean { + return Array.from(this.adapters.values()).some(adapter => adapter.isActive); + } + + async getAllConfig () { + return Array.from(this.adapters.values()).map(adapter => adapter.config); + } +} + +export * from './adapter'; diff --git a/packages/napcat-protocol/package.json b/packages/napcat-protocol/package.json new file mode 100644 index 00000000..79c9ef3f --- /dev/null +++ b/packages/napcat-protocol/package.json @@ -0,0 +1,36 @@ +{ + "name": "napcat-protocol", + "version": "0.0.1", + "private": true, + "type": "module", + "main": "index.ts", + "scripts": { + "typecheck": "tsc --noEmit --skipLibCheck -p tsconfig.json" + }, + "exports": { + ".": { + "import": "./index.ts" + }, + "./*": { + "import": "./*" + } + }, + "dependencies": { + "ajv": "^8.13.0", + "@sinclair/typebox": "^0.34.38", + "cors": "^2.8.5", + "express": "^5.0.0", + "ws": "^8.18.3", + "json5": "^2.2.3", + "napcat-core": "workspace:*", + "napcat-common": "workspace:*" + }, + "devDependencies": { + "@types/cors": "^2.8.17", + "@types/express": "^5.0.0", + "@types/node": "^22.0.1" + }, + "engines": { + "node": ">=18.0.0" + } +} \ No newline at end of file diff --git a/packages/napcat-protocol/tsconfig.json b/packages/napcat-protocol/tsconfig.json new file mode 100644 index 00000000..48d3a03b --- /dev/null +++ b/packages/napcat-protocol/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig.base.json", + "include": [ + "*.ts", + "**/*.ts" + ], + "exclude": [ + "node_modules", + "dist" + ] +} \ No newline at end of file diff --git a/packages/napcat-protocol/types/index.ts b/packages/napcat-protocol/types/index.ts new file mode 100644 index 00000000..ff3653bd --- /dev/null +++ b/packages/napcat-protocol/types/index.ts @@ -0,0 +1,70 @@ +// NapCat Protocol 消息类型定义 + +export interface NapCatProtocolMessage { + post_type: 'message' | 'notice' | 'request' | 'meta_event'; + time: number; + self_id: number; + message_type?: 'private' | 'group'; + sub_type?: string; + message_id?: number; + user_id?: number; + group_id?: number; + message?: NapCatProtocolMessageSegment[] | string; + raw_message?: string; + sender?: NapCatProtocolSender; +} + +export interface NapCatProtocolMessageSegment { + type: string; + data: Record; +} + +export interface NapCatProtocolSender { + user_id: number; + nickname: string; + card?: string; + sex?: 'male' | 'female' | 'unknown'; + age?: number; + area?: string; + level?: string; + role?: 'owner' | 'admin' | 'member'; + title?: string; +} + +// API 请求类型 +export interface NapCatProtocolRequest { + action: string; + params?: Record; + echo?: string | number; +} + +// API 响应类型 +export interface NapCatProtocolResponse { + status: 'ok' | 'failed'; + retcode: number; + data: T | null; + message?: string; + echo?: string | number; +} + +// 心跳事件 +export interface NapCatProtocolHeartbeat { + post_type: 'meta_event'; + meta_event_type: 'heartbeat'; + time: number; + self_id: number; + status: { + online: boolean; + good: boolean; + }; + interval: number; +} + +// 生命周期事件 +export interface NapCatProtocolLifecycle { + post_type: 'meta_event'; + meta_event_type: 'lifecycle'; + time: number; + self_id: number; + sub_type: 'connect' | 'enable' | 'disable'; +} diff --git a/packages/napcat-schema/vite.config.ts b/packages/napcat-schema/vite.config.ts index e77ccdd4..b2b35cbb 100644 --- a/packages/napcat-schema/vite.config.ts +++ b/packages/napcat-schema/vite.config.ts @@ -15,12 +15,7 @@ export default defineConfig({ resolve: { conditions: ['node', 'default'], alias: { - '@/napcat-core': resolve(__dirname, '../napcat-core'), - '@/napcat-common': resolve(__dirname, '../napcat-common'), - '@/napcat-onebot': resolve(__dirname, '../napcat-onebot'), - '@/napcat-pty': resolve(__dirname, '../napcat-pty'), - '@/napcat-webui-backend': resolve(__dirname, '../napcat-webui-backend'), - '@/image-size': resolve(__dirname, '../image-size'), + '@/': resolve(__dirname, '../'), }, }, plugins: [ diff --git a/packages/napcat-shell/base.ts b/packages/napcat-shell/base.ts index b192f6bc..7bce0fac 100644 --- a/packages/napcat-shell/base.ts +++ b/packages/napcat-shell/base.ts @@ -22,7 +22,7 @@ import fs from 'fs'; import os from 'os'; import { LoginListItem, NodeIKernelLoginService } from 'napcat-core/services'; import qrcode from 'napcat-qrcode/lib/main'; -import { NapCatOneBot11Adapter } from 'napcat-onebot/index'; +import { NapCatAdapterManager } from 'napcat-adapter'; import { InitWebUi } from 'napcat-webui-backend/index'; import { WebUiDataRuntime } from 'napcat-webui-backend/src/helper/Data'; import { napCatVersion } from 'napcat-common/src/version'; @@ -475,10 +475,14 @@ export class NapCatShell { this.core.event.on('KickedOffLine', (tips: string) => { WebUiDataRuntime.setQQLoginError(tips); }); - const oneBotAdapter = new NapCatOneBot11Adapter(this.core, this.context, this.context.pathWrapper); - // 注册到 WebUiDataRuntime,供调试功能使用 - WebUiDataRuntime.setOneBotContext(oneBotAdapter); - oneBotAdapter.InitOneBot() - .catch(e => this.context.logger.logError('初始化OneBot失败', e)); + // 使用 NapCatAdapterManager 统一管理协议适配器 + const adapterManager = new NapCatAdapterManager(this.core, this.context, this.context.pathWrapper); + await adapterManager.initAdapters() + .catch(e => this.context.logger.logError('初始化协议适配器失败', e)); + // 注册 OneBot 适配器到 WebUiDataRuntime,供调试功能使用 + const oneBotAdapter = adapterManager.getOneBotAdapter(); + if (oneBotAdapter) { + WebUiDataRuntime.setOneBotContext(oneBotAdapter); + } } } diff --git a/packages/napcat-shell/package.json b/packages/napcat-shell/package.json index 61bd712d..47ebd68a 100644 --- a/packages/napcat-shell/package.json +++ b/packages/napcat-shell/package.json @@ -1,34 +1,34 @@ { - "name": "napcat-shell", - "version": "0.0.1", - "private": true, - "type": "module", - "main": "index.ts", - "scripts": { - "build": "vite build", - "build:dev": "vite build --mode development", - "typecheck": "tsc --noEmit --skipLibCheck -p tsconfig.json" + "name": "napcat-shell", + "version": "0.0.1", + "private": true, + "type": "module", + "main": "index.ts", + "scripts": { + "build": "vite build", + "build:dev": "vite build --mode development", + "typecheck": "tsc --noEmit --skipLibCheck -p tsconfig.json" + }, + "exports": { + ".": { + "import": "./index.ts" }, - "exports": { - ".": { - "import": "./index.ts" - }, - "./*": { - "import": "./*" - } - }, - "dependencies": { - "napcat-core": "workspace:*", - "napcat-common": "workspace:*", - "napcat-onebot": "workspace:*", - "napcat-webui-backend": "workspace:*", - "napcat-qrcode": "workspace:*" - }, - "devDependencies": { - "@types/node": "^22.0.1", - "napcat-vite": "workspace:*" - }, - "engines": { - "node": ">=18.0.0" + "./*": { + "import": "./*" } + }, + "dependencies": { + "napcat-core": "workspace:*", + "napcat-common": "workspace:*", + "napcat-adapter": "workspace:*", + "napcat-webui-backend": "workspace:*", + "napcat-qrcode": "workspace:*" + }, + "devDependencies": { + "@types/node": "^22.0.1", + "napcat-vite": "workspace:*" + }, + "engines": { + "node": ">=18.0.0" + } } \ No newline at end of file diff --git a/packages/napcat-shell/vite.config.ts b/packages/napcat-shell/vite.config.ts index e46f6b0e..caca10ee 100644 --- a/packages/napcat-shell/vite.config.ts +++ b/packages/napcat-shell/vite.config.ts @@ -47,6 +47,7 @@ const ShellBaseConfig = (source_map: boolean = false) => '@/napcat-pty': resolve(__dirname, '../napcat-pty'), '@/napcat-webui-backend': resolve(__dirname, '../napcat-webui-backend'), '@/image-size': resolve(__dirname, '../image-size'), + '@/napcat-protocol': resolve(__dirname, '../napcat-protocol'), }, }, build: { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8dd0aac3..20d04fc3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -43,6 +43,25 @@ importers: specifier: ^4.0.9 version: 4.0.9(@types/debug@4.1.12)(@types/node@22.19.1)(@vitest/ui@4.0.9)(jiti@1.21.7)(tsx@4.21.0)(yaml@2.8.2) + packages/napcat-adapter: + dependencies: + napcat-common: + specifier: workspace:* + version: link:../napcat-common + napcat-core: + specifier: workspace:* + version: link:../napcat-core + napcat-onebot: + specifier: workspace:* + version: link:../napcat-onebot + napcat-protocol: + specifier: workspace:* + version: link:../napcat-protocol + devDependencies: + '@types/node': + specifier: ^22.0.1 + version: 22.19.1 + packages/napcat-common: dependencies: ajv: @@ -111,15 +130,15 @@ importers: packages/napcat-framework: dependencies: + napcat-adapter: + specifier: workspace:* + version: link:../napcat-adapter napcat-common: specifier: workspace:* version: link:../napcat-common napcat-core: specifier: workspace:* version: link:../napcat-core - napcat-onebot: - specifier: workspace:* - version: link:../napcat-onebot napcat-qrcode: specifier: workspace:* version: link:../napcat-qrcode @@ -230,6 +249,43 @@ importers: specifier: ^5.6 version: 5.9.3 + packages/napcat-protocol: + dependencies: + '@sinclair/typebox': + specifier: ^0.34.38 + version: 0.34.41 + ajv: + specifier: ^8.13.0 + version: 8.17.1 + cors: + specifier: ^2.8.5 + version: 2.8.5 + express: + specifier: ^5.0.0 + version: 5.1.0 + json5: + specifier: ^2.2.3 + version: 2.2.3 + napcat-common: + specifier: workspace:* + version: link:../napcat-common + napcat-core: + specifier: workspace:* + version: link:../napcat-core + ws: + specifier: ^8.18.3 + version: 8.18.3 + devDependencies: + '@types/cors': + specifier: ^2.8.17 + version: 2.8.19 + '@types/express': + specifier: ^5.0.0 + version: 5.0.5 + '@types/node': + specifier: ^22.0.1 + version: 22.19.1 + packages/napcat-pty: dependencies: '@homebridge/node-pty-prebuilt-multiarch': @@ -270,15 +326,15 @@ importers: packages/napcat-shell: dependencies: + napcat-adapter: + specifier: workspace:* + version: link:../napcat-adapter napcat-common: specifier: workspace:* version: link:../napcat-common napcat-core: specifier: workspace:* version: link:../napcat-core - napcat-onebot: - specifier: workspace:* - version: link:../napcat-onebot napcat-qrcode: specifier: workspace:* version: link:../napcat-qrcode