cyberbot-v2
Version:
cyberbot, 基于napcat-ts, nodejs,轻量qq机器人框架。
310 lines (309 loc) • 12.3 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.PluginManager = void 0;
const chokidar_1 = require("chokidar");
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const ts_node_1 = require("ts-node");
const index_1 = require("./index.js");
// 注册TypeScript编译器(只执行一次)
let tsNodeRegistered = false;
const registerTSNode = () => {
if (!tsNodeRegistered) {
(0, ts_node_1.register)({
transpileOnly: true,
skipProject: true,
compilerOptions: {
module: 'CommonJS',
esModuleInterop: true,
}
});
tsNodeRegistered = true;
}
};
class PluginManager {
bot;
plugins = new Map();
pluginDir;
config;
configPath;
builtInPlugins; // 添加内置插件集合
constructor(bot) {
this.bot = bot;
this.pluginDir = bot.pluginDir;
this.configPath = bot.configPath;
this.builtInPlugins = bot.builtInPlugins; // 初始化内置插件集合
// 注册TypeScript编译器
registerTSNode();
this.config = bot.config_json;
// 监听 config.json 文件变化
this.startWatchingConfig();
}
loadConfig() {
try {
return JSON.parse(fs.readFileSync(this.configPath, 'utf8'));
}
catch (error) {
index_1.logger.error('❌ Failed to load config', error);
return { plugins: [] };
}
}
saveConfig() {
try {
fs.writeFileSync(this.configPath, JSON.stringify(this.config, null, 2));
}
catch (error) {
index_1.logger.error('❌ Failed to save config:', error);
}
}
async loadAllPlugins() {
await Promise.all(this.config.plugins.map(async (pluginName) => {
const pluginEntry = this.findPluginEntry(pluginName);
if (!pluginEntry) {
index_1.logger.error(`❌ Plugin ${pluginName} not found in ${this.pluginDir}`);
return;
}
await this.loadPlugin(pluginEntry.dirPath, pluginEntry.entryFile);
}));
}
findPluginEntry(pluginName) {
const pluginDir = path.join(this.pluginDir, pluginName);
if (!fs.existsSync(pluginDir))
return null;
// const entryFile = ['index.ts', 'index.js']
// .find(file => fs.existsSync(path.join(pluginDir, file)));
// return entryFile ? {
// dirPath: pluginDir,
// entryFile: path.join(pluginDir, entryFile)
// } : null;
// 支持直接加载TS文件
const entryFile = ['index.ts', 'index.js']
.map(file => path.join(pluginDir, file))
.find(fs.existsSync);
return entryFile ? {
dirPath: pluginDir,
entryFile: entryFile
} : null;
}
async loadPlugin(pluginDir, entryFile) {
try {
const resolvedPath = require.resolve(entryFile);
delete require.cache[resolvedPath];
// const pluginModule: PluginModule = await import(entryFile);
const pluginModule = require(entryFile);
if (!pluginModule.name) {
index_1.logger.warn(`⚠️ Plugin must export 'name' constant: ${entryFile}`);
this.bot.client.send_private_msg({
user_id: this.bot.master,
message: [index_1.Structs.text(`⚠️ Plugin must export 'name' constant: ${entryFile}`)]
});
}
const pluginName = pluginModule.name;
if (this.plugins.has(pluginName)) {
await this.unloadPlugin(pluginName);
}
// 验证插件名与目录名的一致性
const expectedDirName = path.basename(pluginDir);
if (path.basename(pluginDir) !== pluginName) {
index_1.logger.warn(`⚠️ Plugin name '${pluginName}' does not match directory name '${expectedDirName}'`);
}
let handlers = {};
if (pluginModule.handlers) {
handlers = pluginModule.handlers;
}
else if (pluginModule.setup) {
handlers = pluginModule.setup(this.bot);
}
else {
index_1.logger.error(`Plugin ${pluginName} must export "handlers" or "setup"`);
this.bot.client.send_private_msg({
user_id: this.bot.master,
message: [index_1.Structs.text(`Plugin ${pluginName} must export "handlers" or "setup"`)]
});
}
const registeredHandlers = [];
for (const [event, eventHandlers] of Object.entries(handlers)) {
for (const handler of eventHandlers) {
this.bot.on(event, handler);
registeredHandlers.push({ event, handler });
}
}
this.plugins.set(pluginName, {
dirPath: pluginDir,
entryFile,
handlers: registeredHandlers
});
index_1.logger.info(`✅ Plugin ${pluginName} loaded from ${path.relative(this.pluginDir, pluginDir)}`);
}
catch (error) {
index_1.logger.error(`❌ Failed to load plugin from ${entryFile}:`, error);
}
}
async unloadPlugin(pluginName) {
const plugin = this.plugins.get(pluginName);
if (plugin) {
for (const { event, handler } of plugin.handlers) {
this.bot.off(event, handler);
}
this.plugins.delete(pluginName);
index_1.logger.info(`✅ Plugin ${pluginName} unloaded`);
}
}
async enablePlugin(pluginName) {
if (this.builtInPlugins.has(pluginName)) {
index_1.logger.warn(`❌ Built-in plugin ${pluginName} cannot be enabled.`);
return;
}
const pluginEntry = this.findPluginEntry(pluginName);
if (!pluginEntry) {
index_1.logger.error(`❌ Plugin ${pluginName} not found in ${this.pluginDir}`);
this.bot.client.send_private_msg({
user_id: this.bot.master,
message: [index_1.Structs.text(`❌ Plugin ${pluginName} not found in ${this.pluginDir}`)]
});
return;
}
const wasEnabled = this.config.plugins.includes(pluginName);
if (!wasEnabled) {
this.config.plugins.push(pluginName);
this.saveConfig();
}
try {
await this.loadPlugin(pluginEntry.dirPath, pluginEntry.entryFile);
}
catch (error) {
if (!wasEnabled) {
const index = this.config.plugins.indexOf(pluginName);
if (index !== -1) {
this.config.plugins.splice(index, 1);
this.saveConfig();
}
}
index_1.logger.error(`❌ Failed to enable plugin ${pluginName}:`, error);
this.bot.client.send_private_msg({
user_id: this.bot.master,
message: [index_1.Structs.text(`❌ Failed to enable plugin ${pluginName}:${error}`)]
});
}
}
async disablePlugin(pluginName) {
if (this.builtInPlugins.has(pluginName)) {
index_1.logger.warn(`❌ Built-in plugin ${pluginName} cannot be disabled.`);
return;
}
const index = this.config.plugins.indexOf(pluginName);
if (index !== -1) {
this.config.plugins.splice(index, 1);
this.saveConfig();
}
await this.unloadPlugin(pluginName);
}
async reloadPlugin(pluginName) {
if (this.builtInPlugins.has(pluginName)) {
index_1.logger.warn(`❌ Built-in plugin ${pluginName} cannot be reloaded.`);
return;
}
const plugin = this.plugins.get(pluginName);
if (!plugin) {
index_1.logger.error(`Plugin ${pluginName} not loaded`);
this.bot.client.send_private_msg({
user_id: this.bot.master,
message: [index_1.Structs.text(`Plugin ${pluginName} not loaded`)]
});
return;
}
await this.unloadPlugin(pluginName);
await this.loadPlugin(plugin.dirPath, plugin.entryFile);
}
startWatching() {
const watcher = (0, chokidar_1.watch)(this.pluginDir, {
ignored: /(^|[\/\\])\../,
persistent: true,
ignoreInitial: true,
depth: 2
});
watcher.on('change', async (changedPath) => {
// 从文件路径推导插件目录
const relativePath = path.relative(this.pluginDir, changedPath);
const pluginDirName = relativePath.split(path.sep)[0];
const pluginEntry = this.findPluginEntry(pluginDirName);
if (pluginEntry && this.config.plugins.includes(pluginDirName)) {
index_1.logger.info(`🔄 Detected changes in ${pluginDirName}`);
await this.reloadPlugin(pluginDirName);
}
});
}
// 监听 config.json 文件变化
startWatchingConfig() {
const configWatcher = (0, chokidar_1.watch)(this.configPath, {
persistent: true,
ignoreInitial: true,
});
configWatcher.on('change', async () => {
index_1.logger.info('🔄 Detected changes in bot.config.json');
const newConfig = this.loadConfig();
const oldPlugins = new Set(this.config.plugins);
const newPlugins = new Set(newConfig.plugins);
// 找出新增的插件
const addedPlugins = [...newPlugins].filter(plugin => !oldPlugins.has(plugin));
// 找出移除的插件
const removedPlugins = [...oldPlugins].filter(plugin => !newPlugins.has(plugin));
// 启用新增的插件
for (const plugin of addedPlugins) {
try {
await this.enablePlugin(plugin);
index_1.logger.info(`✅ Enabled plugin: ${plugin}`);
}
catch (error) {
index_1.logger.error(`❌ Failed to enable plugin ${plugin}:`, error);
}
}
// 禁用移除的插件
for (const plugin of removedPlugins) {
try {
await this.disablePlugin(plugin);
index_1.logger.info(`✅ Disabled plugin: ${plugin}`);
}
catch (error) {
index_1.logger.error(`❌ Failed to disable plugin ${plugin}:`, error);
}
}
// 更新当前配置
this.config = newConfig;
});
}
}
exports.PluginManager = PluginManager;