Refactor plugin identification to use package name and dirname

Updated plugin manager and API to distinguish between plugin package name and directory name (dirname) for more robust plugin identification and path resolution. Adjusted context creation, status management, and API handlers to use package name for identification and dirname for filesystem operations. Also replaced console.error with console.log in builtin plugin for consistency.
This commit is contained in:
手瓜一十雪 2026-01-28 15:02:47 +08:00
parent 93126e514e
commit 0aa0c44634
4 changed files with 46 additions and 68 deletions

View File

@ -1,24 +0,0 @@
{
"name": "napcat-types",
"version": "0.0.1",
"type": "module",
"types": "./napcat-types/index.d.ts",
"files": [
"**/*"
],
"dependencies": {
"@types/node": "^22.10.7",
"@types/express": "^4.17.21",
"@types/ws": "^8.5.12",
"@types/cors": "^2.8.17",
"@types/multer": "^1.4.12",
"@types/winston": "^2.4.4",
"@types/yaml": "^1.9.7",
"@types/ip": "^1.1.3"
},
"publishConfig": {
"registry": "https://registry.npmjs.org/",
"access": "public",
"tag": "latest"
}
}

View File

@ -88,6 +88,7 @@ export interface PluginModule<T extends OB11EmitEventContent = OB11EmitEventCont
export interface LoadedPlugin {
name: string;
dirname: string; // Actual directory name for path resolution
version?: string;
pluginPath: string;
entryPath: string;
@ -245,7 +246,8 @@ export class OB11PluginMangerAdapter extends IOB11NetworkAdapter<PluginConfig> {
}
const plugin: LoadedPlugin = {
name: pluginId, // Use Directory Name as Internal Plugin Name/ID
name: packageJson?.name || pluginId, // Use package.json name for API lookups, fallback to dir name
dirname: pluginId, // Keep track of actual directory name for path resolution
version: packageJson?.version,
pluginPath: pluginDir,
entryPath,
@ -325,15 +327,15 @@ export class OB11PluginMangerAdapter extends IOB11NetworkAdapter<PluginConfig> {
}
// Create Context
const id = plugin.name; // directory name as ID
const dataPath = path.join(this.pluginPath, id, 'data');
// Use dirname for path resolution, name for identification
const dataPath = path.join(this.pluginPath, plugin.dirname, 'data');
const configPath = path.join(dataPath, 'config.json');
const context: NapCatPluginContext = {
core: this.core,
oneBot: this.obContext,
actions: this.actions,
pluginName: id,
pluginName: plugin.name, // Use package name for identification
pluginPath: plugin.pluginPath,
dataPath: dataPath,
configPath: configPath,
@ -404,20 +406,18 @@ export class OB11PluginMangerAdapter extends IOB11NetworkAdapter<PluginConfig> {
}
public setPluginStatus (pluginName: string, enable: boolean): void {
// Try to find plugin by package name first
const plugin = this.loadedPlugins.get(pluginName);
// Use dirname for config storage (if plugin is loaded), otherwise assume pluginName is dirname
const configKey = plugin?.dirname || pluginName;
const config = this.loadPluginConfig();
config[pluginName] = enable;
config[configKey] = enable;
this.savePluginConfig(config);
if (!enable) {
for (const [_, loaded] of this.loadedPlugins.entries()) {
const dirOrFile = path.basename(loaded.pluginPath === this.pluginPath ? loaded.entryPath : loaded.pluginPath);
const ext = path.extname(dirOrFile);
const simpleName = ext ? path.parse(dirOrFile).name : dirOrFile;
if (pluginName === simpleName) {
this.unloadPlugin(loaded.name).catch(e => this.logger.logError('Error unloading', e));
}
}
if (!enable && plugin) {
// Unload by plugin.name (package name, which is the key in loadedPlugins)
this.unloadPlugin(plugin.name).catch(e => this.logger.logError('Error unloading', e));
}
}
@ -539,13 +539,14 @@ export class OB11PluginMangerAdapter extends IOB11NetworkAdapter<PluginConfig> {
return false;
}
const dirname = plugin.dirname;
try {
// 卸载插件
await this.unloadPlugin(pluginName);
// 重新加载插件
// We assume pluginName IS the directory name (the ID)
await this.loadDirectoryPlugin(pluginName);
// 重新加载插件 - use dirname for directory loading
await this.loadDirectoryPlugin(dirname);
this.logger.log(
`[Plugin Adapter] Plugin ${pluginName} reloaded successfully`
@ -560,7 +561,10 @@ export class OB11PluginMangerAdapter extends IOB11NetworkAdapter<PluginConfig> {
}
}
public getPluginDataPath (pluginName: string): string {
return path.join(this.pluginPath, pluginName, 'data');
// Lookup plugin by name (package name) and use dirname for path
const plugin = this.loadedPlugins.get(pluginName);
const dirname = plugin?.dirname || pluginName; // fallback to pluginName if not found
return path.join(this.pluginPath, dirname, 'data');
}
public getPluginConfigPath (pluginName: string): string {

View File

@ -97,7 +97,7 @@ const plugin_onmessage: PluginModule['plugin_onmessage'] = async (_ctx, event) =
console.log('[Plugin: builtin] 已回复版本信息');
} catch (error) {
console.error('[Plugin: builtin] 处理消息时发生错误:', error);
console.log('[Plugin: builtin] 处理消息时发生错误:', error);
}
};
@ -150,7 +150,7 @@ async function sendMessage (actions: ActionMap, event: OB11Message, message: str
try {
await actions.call('send_msg', params, adapter, config);
} catch (error) {
console.error('[Plugin: builtin] 发送消息失败:', error);
console.log('[Plugin: builtin] 发送消息失败:', error);
}
}

View File

@ -33,21 +33,19 @@ export const GetPluginListHandler: RequestHandler = async (_req, res) => {
// 1. 整理已加载的插件
for (const p of loadedPlugins) {
// 计算 ID需要回溯到加载时的入口信息
// 对于已加载的插件,我们通过判断 pluginPath 是否等于根 pluginPath 来判断它是单文件还是目录
const isFilePlugin = p.pluginPath === pluginManager.getPluginPath();
const fsName = isFilePlugin ? path.basename(p.entryPath) : path.basename(p.pluginPath);
const id = getPluginId(fsName, isFilePlugin);
// Use dirname for map key (matches filesystem scan)
const id = p.dirname;
const fsName = p.dirname; // dirname is the actual filesystem directory name
loadedPluginMap.set(id, {
name: p.packageJson?.name || p.name, // 优先使用 package.json 的 name
name: p.name, // This is now package name (from packageJson.name || dirname)
id: id,
version: p.version || '0.0.0',
description: p.packageJson?.description || '',
author: p.packageJson?.author || '',
status: 'active',
filename: fsName, // 真实文件/目录名
loadedName: p.name, // 运行时注册的名称,用于重载/卸载
loadedName: p.name, // 运行时注册的名称,用于重载/卸载 (package name)
hasConfig: !!(p.module.plugin_config_schema || p.module.plugin_config_ui)
});
}
@ -121,40 +119,40 @@ export const GetPluginListHandler: RequestHandler = async (_req, res) => {
// ReloadPluginHandler removed
export const SetPluginStatusHandler: RequestHandler = async (req, res) => {
const { enable, filename } = req.body;
// We Use filename / id to control config
// Front-end should pass the 'filename' or 'id' as the key identifier
const { enable, filename, name } = req.body;
// filename is the directory name (used for fs checks)
// name is the package name (used for plugin manager API, if provided)
// We need to determine: which to use for setPluginStatus call
if (!filename) return sendError(res, 'Plugin Filename/ID is required');
if (!filename && !name) return sendError(res, 'Plugin Filename or Name is required');
const pluginManager = getPluginManager();
if (!pluginManager) {
return sendError(res, 'Plugin Manager not found');
}
// ID IS the filename (directory name) now
const id = filename;
// Determine which ID to use
// If 'name' (package name) is provided, use it for pluginManager calls
// But 'filename' (dirname) is needed for filesystem operations
const dirname = filename || name; // fallback
const pluginName = name || filename; // fallback
try {
pluginManager.setPluginStatus(id, enable);
// setPluginStatus now handles both package name and dirname lookup internally
pluginManager.setPluginStatus(pluginName, enable);
// If enabling, trigger load
if (enable) {
const pluginPath = pluginManager.getPluginPath();
const fullPath = path.join(pluginPath, filename);
const fullPath = path.join(pluginPath, dirname);
if (fs.existsSync(fullPath) && fs.statSync(fullPath).isDirectory()) {
await pluginManager.loadDirectoryPlugin(filename);
await pluginManager.loadDirectoryPlugin(dirname);
} else {
return sendError(res, 'Plugin directory not found: ' + filename);
return sendError(res, 'Plugin directory not found: ' + dirname);
}
} else {
// Disabling behavior is managed by setPluginStatus which updates config
// If we want to unload immediately?
// The config update will prevent load on next startup.
// Does pluginManager.setPluginStatus unload it?
// Inspecting pluginManager.setPluginStatus... it iterates and unloads if match found.
}
// Disabling is handled by setPluginStatus
return sendSuccess(res, { message: 'Status updated successfully' });
} catch (e: any) {