NapCatQQ/packages/napcat-onebot/network/plugin-manger.ts
手瓜一十雪 805c1d5ea2 Default plugins disabled; skip loading disabled
Change plugin loader to treat plugins as disabled by default (unless the id/dir is 'napcat-plugin-builtin') by using nullish coalescing when reading statusConfig. Add an early-return guard in the plugin manager/adapter that logs and skips loading when entry.enable is false. This prevents disabled plugins from being loaded automatically and provides a clear log message when skipped.
2026-01-31 15:26:56 +08:00

537 lines
15 KiB
TypeScript

import fs from 'fs';
import path from 'path';
import { ActionMap } from '@/napcat-onebot/action';
import { NapCatCore } from 'napcat-core';
import { NapCatOneBot11Adapter, OB11Message } from '@/napcat-onebot/index';
import { OB11EmitEventContent, OB11NetworkReloadType } from '@/napcat-onebot/network/index';
import { IOB11NetworkAdapter } from '@/napcat-onebot/network/adapter';
import { PluginConfig } from '@/napcat-onebot/config/config';
import { NapCatConfig } from './plugin/config';
import { PluginLoader } from './plugin/loader';
import {
PluginEntry,
PluginLogger,
PluginStatusConfig,
NapCatPluginContext,
IPluginManager,
} from './plugin/types';
import { PluginRouterRegistryImpl } from './plugin/router-registry';
export { PluginPackageJson } from './plugin/types';
export { PluginConfigItem } from './plugin/types';
export { PluginConfigUIController } from './plugin/types';
export { NapCatConfig } from './plugin/config';
export { PluginConfigSchema } from './plugin/types';
export { PluginLogger } from './plugin/types';
export { NapCatPluginContext } from './plugin/types';
export { PluginModule } from './plugin/types';
export { PluginStatusConfig } from './plugin/types';
export { PluginRouterRegistry, PluginRequestHandler, PluginApiRouteDefinition, PluginPageDefinition, HttpMethod } from './plugin/types';
export { PluginHttpRequest, PluginHttpResponse, PluginNextFunction } from './plugin/types';
export { PluginRouterRegistryImpl } from './plugin/router-registry';
export class OB11PluginMangerAdapter extends IOB11NetworkAdapter<PluginConfig> implements IPluginManager {
private readonly pluginPath: string;
private readonly configPath: string;
private readonly loader: PluginLoader;
/** 插件注册表: ID -> 插件条目 */
private plugins: Map<string, PluginEntry> = new Map();
/** 插件路由注册表: ID -> 路由注册器 */
private pluginRouters: Map<string, PluginRouterRegistryImpl> = new Map();
declare config: PluginConfig;
public NapCatConfig = NapCatConfig;
override get isActive (): boolean {
return this.isEnable && this.getLoadedPlugins().length > 0;
}
constructor (
name: string,
core: NapCatCore,
obContext: NapCatOneBot11Adapter,
actions: ActionMap
) {
const config = {
name,
messagePostFormat: 'array',
reportSelfMessage: true,
enable: true,
debug: true,
};
super(name, config, core, obContext, actions);
this.pluginPath = this.core.context.pathWrapper.pluginPath;
this.configPath = path.join(this.core.context.pathWrapper.configPath, 'plugins.json');
this.loader = new PluginLoader(this.pluginPath, this.configPath, this.logger);
}
// ==================== 插件状态配置 ====================
public getPluginConfig (): PluginStatusConfig {
return this.loader.loadPluginStatusConfig();
}
private savePluginConfig (config: PluginStatusConfig): void {
this.loader.savePluginStatusConfig(config);
}
// ==================== 插件扫描与加载 ====================
/**
* 扫描并加载所有插件
*/
private async scanAndLoadPlugins (): Promise<void> {
// 扫描所有插件目录
const entries = await this.loader.scanPlugins();
// 清空现有注册表
this.plugins.clear();
// 注册所有插件条目
for (const entry of entries) {
this.plugins.set(entry.id, entry);
}
this.logger.log(`[PluginManager] Scanned ${this.plugins.size} plugins`);
// 加载启用的插件
for (const entry of this.plugins.values()) {
if (entry.enable && entry.runtime.status !== 'error') {
await this.loadPlugin(entry);
}
}
const loadedCount = this.getLoadedPlugins().length;
this.logger.log(`[PluginManager] Loaded ${loadedCount} plugins`);
}
/**
* 加载单个插件
*/
private async loadPlugin (entry: PluginEntry): Promise<boolean> {
if (entry.loaded) {
return true;
}
if (entry.runtime.status === 'error') {
return false;
}
// 加载模块
const module = await this.loader.loadPluginModule(entry);
if (!module) {
return false;
}
// 创建上下文
const context = this.createPluginContext(entry);
// 初始化插件
try {
await module.plugin_init(context);
entry.loaded = true;
entry.runtime = {
status: 'loaded',
module,
context,
};
this.logger.log(`[PluginManager] Initialized plugin: ${entry.id}${entry.version ? ` v${entry.version}` : ''}`);
return true;
} catch (error: any) {
entry.loaded = false;
entry.runtime = {
status: 'error',
error: error.message || 'Initialization failed',
};
this.logger.logError(`[PluginManager] Error initializing plugin ${entry.id}:`, error);
return false;
}
}
/**
* 卸载单个插件
*/
private async unloadPlugin (entry: PluginEntry): Promise<void> {
if (!entry.loaded || entry.runtime.status !== 'loaded') {
return;
}
const { module, context } = entry.runtime;
// 调用清理方法
if (module && context && typeof module.plugin_cleanup === 'function') {
try {
await module.plugin_cleanup(context);
this.logger.log(`[PluginManager] Cleaned up plugin: ${entry.id}`);
} catch (error) {
this.logger.logError(`[PluginManager] Error cleaning up plugin ${entry.id}:`, error);
}
}
// 清理插件路由
const routerRegistry = this.pluginRouters.get(entry.id);
if (routerRegistry) {
routerRegistry.clear();
this.pluginRouters.delete(entry.id);
}
// 重置状态
entry.loaded = false;
entry.runtime = {
status: 'unloaded',
};
this.logger.log(`[PluginManager] Unloaded plugin: ${entry.id}`);
}
/**
* 创建插件上下文
*/
private createPluginContext (entry: PluginEntry): NapCatPluginContext {
const dataPath = path.join(entry.pluginPath, 'data');
const configPath = path.join(dataPath, 'config.json');
// 创建插件专用日志器
const pluginPrefix = `[Plugin: ${entry.id}]`;
const coreLogger = this.logger;
const pluginLogger: PluginLogger = {
log: (...args: any[]) => coreLogger.log(pluginPrefix, ...args),
debug: (...args: any[]) => coreLogger.logDebug(pluginPrefix, ...args),
info: (...args: any[]) => coreLogger.log(pluginPrefix, ...args),
warn: (...args: any[]) => coreLogger.logWarn(pluginPrefix, ...args),
error: (...args: any[]) => coreLogger.logError(pluginPrefix, ...args),
};
// 创建插件路由注册器
const routerRegistry = new PluginRouterRegistryImpl(entry.id, entry.pluginPath);
// 保存到路由注册表
this.pluginRouters.set(entry.id, routerRegistry);
return {
core: this.core,
oneBot: this.obContext,
actions: this.actions,
pluginName: entry.id,
pluginPath: entry.pluginPath,
dataPath,
configPath,
NapCatConfig,
adapterName: this.name,
pluginManager: this,
logger: pluginLogger,
router: routerRegistry,
};
}
// ==================== 公共 API ====================
/**
* 获取插件目录路径
*/
public getPluginPath (): string {
return this.pluginPath;
}
/**
* 获取所有插件条目
*/
public getAllPlugins (): PluginEntry[] {
return Array.from(this.plugins.values());
}
/**
* 获取已加载的插件列表
*/
public getLoadedPlugins (): PluginEntry[] {
return Array.from(this.plugins.values()).filter(p => p.loaded);
}
/**
* 通过 ID 获取插件信息
*/
public getPluginInfo (pluginId: string): PluginEntry | undefined {
return this.plugins.get(pluginId);
}
/**
* 获取插件路由注册器
*/
public getPluginRouter (pluginId: string): PluginRouterRegistryImpl | undefined {
return this.pluginRouters.get(pluginId);
}
/**
* 获取所有插件路由注册器
*/
public getAllPluginRouters (): Map<string, PluginRouterRegistryImpl> {
return this.pluginRouters;
}
/**
* 设置插件状态(启用/禁用)
*/
public async setPluginStatus (pluginId: string, enable: boolean): Promise<void> {
const config = this.getPluginConfig();
config[pluginId] = enable;
this.savePluginConfig(config);
const entry = this.plugins.get(pluginId);
if (entry) {
entry.enable = enable;
if (enable && !entry.loaded) {
// 启用插件
await this.loadPlugin(entry);
} else if (!enable && entry.loaded) {
// 禁用插件
await this.unloadPlugin(entry);
}
}
}
/**
* 通过 ID 加载插件
*/
public async loadPluginById (pluginId: string): Promise<boolean> {
let entry = this.plugins.get(pluginId);
if (!entry) {
// 尝试查找并扫描
const dirname = this.loader.findPluginDirById(pluginId);
if (!dirname) {
this.logger.logWarn(`[PluginManager] Plugin ${pluginId} not found in filesystem`);
return false;
}
const newEntry = this.loader.rescanPlugin(dirname);
if (!newEntry) {
return false;
}
this.plugins.set(newEntry.id, newEntry);
entry = newEntry;
}
if (!entry.enable) {
this.logger.log(`[PluginManager] Skipping loading disabled plugin: ${pluginId}`);
return false;
}
return await this.loadPlugin(entry);
}
/**
* 卸载插件(仅从内存卸载)
*/
public async unregisterPlugin (pluginId: string): Promise<void> {
const entry = this.plugins.get(pluginId);
if (entry) {
await this.unloadPlugin(entry);
}
}
/**
* 卸载并删除插件
*/
public async uninstallPlugin (pluginId: string, cleanData: boolean = false): Promise<void> {
const entry = this.plugins.get(pluginId);
if (!entry) {
throw new Error(`Plugin ${pluginId} not found`);
}
const pluginPath = entry.pluginPath;
const dataPath = path.join(pluginPath, 'data');
if (entry.loaded) {
await this.unloadPlugin(entry);
}
// 从注册表移除
this.plugins.delete(pluginId);
// 删除插件目录
if (fs.existsSync(pluginPath)) {
fs.rmSync(pluginPath, { recursive: true, force: true });
}
// 清理数据
if (cleanData && fs.existsSync(dataPath)) {
fs.rmSync(dataPath, { recursive: true, force: true });
}
}
/**
* 重载指定插件
*/
public async reloadPlugin (pluginId: string): Promise<boolean> {
const entry = this.plugins.get(pluginId);
if (!entry) {
this.logger.logWarn(`[PluginManager] Plugin ${pluginId} not found`);
return false;
}
try {
// 卸载插件
await this.unloadPlugin(entry);
// 重新扫描
const newEntry = this.loader.rescanPlugin(entry.fileId);
if (!newEntry) {
return false;
}
// 更新注册表
this.plugins.set(newEntry.id, newEntry);
// 重新加载
if (newEntry.enable) {
await this.loadPlugin(newEntry);
}
this.logger.log(`[PluginManager] Plugin ${pluginId} reloaded successfully`);
return true;
} catch (error) {
this.logger.logError(`[PluginManager] Error reloading plugin ${pluginId}:`, error);
return false;
}
}
/**
* 加载目录插件(用于新安装的插件)
*/
public async loadDirectoryPlugin (dirname: string): Promise<void> {
const entry = this.loader.rescanPlugin(dirname);
if (!entry) {
return;
}
// 检查是否已存在
if (this.plugins.has(entry.id)) {
this.logger.logWarn(`[PluginManager] Plugin ${entry.id} already exists`);
return;
}
this.plugins.set(entry.id, entry);
if (entry.enable && entry.runtime.status !== 'error') {
await this.loadPlugin(entry);
}
}
/**
* 获取插件数据目录路径
*/
public getPluginDataPath (pluginId: string): string {
const entry = this.plugins.get(pluginId);
if (!entry) {
throw new Error(`Plugin ${pluginId} not found`);
}
return path.join(entry.pluginPath, 'data');
}
/**
* 获取插件配置文件路径
*/
public getPluginConfigPath (pluginId: string): string {
return path.join(this.getPluginDataPath(pluginId), 'config.json');
}
// ==================== 事件处理 ====================
async onEvent<T extends OB11EmitEventContent> (event: T): Promise<void> {
if (!this.isEnable) {
return;
}
try {
await Promise.allSettled(
this.getLoadedPlugins().map((entry) =>
this.callPluginEventHandler(entry, event)
)
);
} catch (error) {
this.logger.logError('[PluginManager] Error handling event:', error);
}
}
/**
* 调用插件的事件处理方法
*/
private async callPluginEventHandler (
entry: PluginEntry,
event: OB11EmitEventContent
): Promise<void> {
if (entry.runtime.status !== 'loaded' || !entry.runtime.module || !entry.runtime.context) {
return;
}
const { module, context } = entry.runtime;
try {
// 优先使用 plugin_onevent 方法
if (typeof module.plugin_onevent === 'function') {
await module.plugin_onevent(context, event);
}
// 如果是消息事件并且插件有 plugin_onmessage 方法,也调用
if (
(event as any).message_type &&
typeof module.plugin_onmessage === 'function'
) {
await module.plugin_onmessage(context, event as OB11Message);
}
} catch (error) {
this.logger.logError(`[PluginManager] Error calling plugin ${entry.id} event handler:`, error);
}
}
// ==================== 生命周期 ====================
async open (): Promise<void> {
if (this.isEnable) {
return;
}
this.logger.log('[PluginManager] Opening plugin manager...');
this.isEnable = true;
// 扫描并加载所有插件
await this.scanAndLoadPlugins();
this.logger.log(`[PluginManager] Plugin manager opened with ${this.getLoadedPlugins().length} plugins loaded`);
}
async close (): Promise<void> {
if (!this.isEnable) {
return;
}
this.logger.log('[PluginManager] Closing plugin manager...');
this.isEnable = false;
// 卸载所有已加载的插件
for (const entry of this.plugins.values()) {
if (entry.loaded) {
await this.unloadPlugin(entry);
}
}
this.logger.log('[PluginManager] Plugin manager closed');
}
async reload (): Promise<OB11NetworkReloadType> {
this.logger.log('[PluginManager] Reloading plugin manager...');
// 先关闭然后重新打开
await this.close();
await this.open();
this.logger.log('[PluginManager] Plugin manager reloaded');
return OB11NetworkReloadType.Normal;
}
}