n1cat-discord-script-manager
Version:
A Discord.js plugin for dynamic script management and execution
1,027 lines (915 loc) • 32.7 kB
JavaScript
const { Collection } = require("discord.js");
const path = require("path");
const fs = require("fs");
// Import Logger from utils
const Logger = require("./utils/logger");
const CoreScriptManager = require("./core/ScriptManager");
const MessageHandler = require("./core/MessageHandler");
const EventHandler = require("./core/EventHandler");
const {
installDependency,
loadModule,
checkDependencyVersion,
checkDependencyConflicts,
checkDependencySecurity,
getDependencyInfo,
} = require("./utils/moduleLoader");
// 用於追蹤函式調用的輔助函數
function getCallerInfo() {
const stack = new Error().stack;
const callerLine = stack.split("\n")[3]; // 跳過 Error 和 getCallerInfo 的堆疊
const match = callerLine.match(/at\s+(.+?)\s+\((.+?):(\d+):(\d+)\)/);
if (match) {
const [, functionName, file, line, column] = match;
return {
function: functionName,
file: file.split("/").pop(), // 只取檔案名稱
line,
column,
};
}
return {
function: "unknown",
file: "unknown",
line: "unknown",
column: "unknown",
};
}
// 日誌輸出函數
function log(message, isError = false) {
const caller = getCallerInfo();
const debugMode = this.options?.debug ?? false;
// 如果是錯誤或 debug 模式開啟,則輸出詳細日誌
if (isError || debugMode) {
console.log(`[${caller.file}:${caller.line}] ${message}`);
}
}
class ScriptManager {
constructor(client, options = {}) {
// Validate client object
if (!client) {
throw new Error("Discord client is required");
}
// Check for essential Discord.js client methods
const requiredMethods = ["on", "once"];
const missingMethods = requiredMethods.filter(
(method) => typeof client[method] !== "function"
);
if (missingMethods.length > 0) {
throw new TypeError(
`Invalid Discord client. Missing methods: ${missingMethods.join(", ")}`
);
}
this.client = client;
this.scripts = new Collection();
this.commands = new Collection();
// New flags for tracking initialization and readiness
this._isInitialized = false;
this._isReady = false;
// Store ScriptManager instance on client
client.scriptManager = this;
// Ensure script folder path is absolute
const scriptFolder = options.scriptFolder || "scripts";
this.options = {
scriptFolder: path.isAbsolute(scriptFolder)
? scriptFolder
: path.resolve(process.cwd(), scriptFolder),
allowedUsers: options.allowedUsers || [],
maxScriptSize: options.maxScriptSize || 1024 * 1024, // 1MB
debug: options.debug ?? false,
...options,
};
// Ensure script directory exists
if (!fs.existsSync(this.options.scriptFolder)) {
fs.mkdirSync(this.options.scriptFolder, { recursive: true });
}
// Add ready event listener for application initialization
this._setupReadyHandler();
}
/**
* Check if ScriptManager is ready
* @returns {boolean} Whether ScriptManager is fully initialized
*/
get isReady() {
return this._isReady;
}
/**
* Set up a ready handler to ensure application is properly initialized
* @private
*/
_setupReadyHandler() {
const logger = Logger.createContextLogger({
module: "ScriptManager",
debug: this.options.debug,
});
this.client.once("ready", async () => {
try {
// Attempt to fetch application if not already available
if (!this.client.application) {
logger.log("Fetching client application...");
await this.client.application?.fetch();
}
// Initialize commands and event handlers
await this._initCommands();
this._setupEventHandlers();
// Mark as initialized and ready
this._isInitialized = true;
this._isReady = true;
logger.log("ScriptManager initialized successfully");
// Emit a ready event for users to listen
this.client.emit("scriptManagerReady", this);
} catch (error) {
// Mark as not ready due to initialization failure
this._isReady = false;
logger.error("Failed to initialize ScriptManager", {
showStack: true,
additionalInfo: error.message,
});
// Emit an error event that users can listen to
this.client.emit("scriptManagerInitError", error);
}
});
}
/**
* Manually initialize ScriptManager if not using the ready event
* @param {boolean} [forceFetchApplication=true] - Whether to automatically fetch application
* @returns {Promise<void>}
*/
async init(forceFetchApplication = true) {
const logger = Logger.createContextLogger({
module: "ScriptManager",
debug: this.options.debug,
});
try {
// Ensure client is ready
if (this.client.status !== 0) {
throw new Error("Client is not connected. Call client.login() first.");
}
// Optionally fetch application
if (forceFetchApplication && !this.client.application) {
logger.log("Manually fetching client application...");
await this.client.application?.fetch();
}
// Initialize commands and event handlers
await this._initCommands();
this._setupEventHandlers();
// Mark as initialized and ready
this._isInitialized = true;
this._isReady = true;
logger.log("ScriptManager manually initialized successfully");
// Emit a ready event
this.client.emit("scriptManagerReady", this);
} catch (error) {
// Mark as not ready due to initialization failure
this._isReady = false;
logger.error("Failed to manually initialize ScriptManager", {
showStack: true,
additionalInfo: error.message,
});
// Emit an error event
this.client.emit("scriptManagerInitError", error);
throw error;
}
}
_setupEventHandlers() {
// 如果已經初始化過,先移除舊的事件監聽器
if (this._isInitialized) {
this.client.removeAllListeners("interactionCreate");
this.client.removeAllListeners("messageCreate");
}
// 用於追蹤正在處理的互動
const processingInteractions = new Set();
// 設置互動處理器
this.client.on("interactionCreate", async (interaction) => {
try {
// 檢查是否正在處理此互動
if (processingInteractions.has(interaction.id)) {
log.call(this, `跳過重複的互動處理: ${interaction.id}`);
return;
}
// 標記此互動為處理中
processingInteractions.add(interaction.id);
if (interaction.isButton()) {
log.call(this, `收到按鈕互動: ${interaction.customId}`);
const command = this.commands.get("interaction");
if (command) {
try {
log.call(this, "開始執行互動命令");
await command.execute(interaction);
log.call(this, "互動命令執行完成");
} catch (error) {
log.call(this, `處理按鈕互動時出錯: ${error.message}`, true);
console.error("詳細錯誤信息:", {
error: error.message,
code: error.code,
stack: error.stack,
interactionId: interaction.id,
interactionState: {
replied: interaction.replied,
deferred: interaction.deferred,
},
timestamp: new Date().toISOString(),
});
try {
if (!interaction.replied && !interaction.deferred) {
log.call(this, "嘗試延遲回應互動");
await interaction.deferUpdate();
log.call(this, "成功延遲回應互動");
}
// 嘗試更新消息以顯示錯誤
await interaction.message
.edit({
content: `${interaction.message.content}\n❌ 處理請求時發生錯誤:${error.message}`,
embeds: interaction.message.embeds,
components: [],
})
.catch((editError) => {
log.call(
this,
`更新消息時出錯: ${editError.message}`,
true
);
});
} catch (responseError) {
log.call(
this,
`回應互動時出錯: ${responseError.message}`,
true
);
}
}
} else {
log.call(this, "找不到互動命令處理器", true);
try {
await interaction.reply({
content: "❌ 找不到命令處理器",
ephemeral: true,
});
} catch (replyError) {
log.call(this, `發送錯誤消息時出錯: ${replyError.message}`, true);
}
}
} else if (interaction.isAutocomplete()) {
log.call(this, `收到自動完成請求: ${interaction.commandName}`);
const command = this.commands.get(interaction.commandName);
if (command && command.autocomplete) {
try {
await command.autocomplete(interaction);
} catch (error) {
log.call(this, `處理自動完成時出錯: ${error.message}`, true);
await interaction.respond([]).catch(console.error);
}
}
} else if (interaction.isCommand()) {
log.call(this, `收到命令: ${interaction.commandName}`);
log.call(
this,
`命令來源: ${JSON.stringify({
id: interaction.id,
token: interaction.token,
applicationId: interaction.applicationId,
guildId: interaction.guildId,
channelId: interaction.channelId,
userId: interaction.user.id,
timestamp: new Date().toISOString(),
stack: new Error().stack,
})}`
);
const command = this.commands.get(interaction.commandName);
if (command) {
try {
log.call(this, `開始執行命令: ${interaction.commandName}`);
log.call(
this,
`互動狀態: ${JSON.stringify({
id: interaction.id,
replied: interaction.replied,
deferred: interaction.deferred,
timestamp: new Date().toISOString(),
})}`
);
// 執行命令
await command.execute(interaction);
log.call(this, `命令執行完成: ${interaction.commandName}`);
log.call(
this,
`執行後互動狀態: ${JSON.stringify({
id: interaction.id,
replied: interaction.replied,
deferred: interaction.deferred,
timestamp: new Date().toISOString(),
})}`
);
} catch (error) {
log.call(this, `執行命令時出錯: ${error.message}`, true);
console.error("詳細錯誤信息:", {
command: interaction.commandName,
error: error.message,
code: error.code,
interactionId: interaction.id,
interactionState: {
replied: interaction.replied,
deferred: interaction.deferred,
},
timestamp: new Date().toISOString(),
stack: error.stack,
});
// 只在互動還沒有被回應時才發送錯誤消息
if (!interaction.replied && !interaction.deferred) {
try {
await interaction.reply({
content: `❌ 執行命令時發生錯誤:${error.message}`,
ephemeral: true,
});
log.call(this, "已發送錯誤回應");
} catch (replyError) {
log.call(
this,
`發送錯誤回應時出錯: ${replyError.message}`,
true
);
console.error("發送錯誤回應時的詳細錯誤:", {
error: replyError.message,
code: replyError.code,
interactionId: interaction.id,
interactionState: {
replied: interaction.replied,
deferred: interaction.deferred,
},
timestamp: new Date().toISOString(),
stack: replyError.stack,
});
}
} else {
log.call(this, "互動已經被回應,跳過錯誤回應");
}
}
}
} else if (interaction.isModalSubmit()) {
log.call(this, `收到 Modal Submit 互動: ${interaction.customId}`);
const command = this.commands.get("interaction");
if (command) {
try {
log.call(this, "開始執行互動命令");
await command.execute(interaction);
log.call(this, "互動命令執行完成");
} catch (error) {
log.call(
this,
`處理 Modal Submit 互動時出錯: ${error.message}`,
true
);
console.error("詳細錯誤信息:", {
error: error.message,
code: error.code,
stack: error.stack,
interactionId: interaction.id,
interactionState: {
replied: interaction.replied,
deferred: interaction.deferred,
},
timestamp: new Date().toISOString(),
});
try {
if (!interaction.replied && !interaction.deferred) {
log.call(this, "嘗試回應互動");
await interaction.reply({
content: `❌ 處理請求時發生錯誤:${error.message}`,
ephemeral: true,
});
}
} catch (responseError) {
log.call(
this,
`回應互動時出錯: ${responseError.message}`,
true
);
}
}
} else {
log.call(this, "找不到互動命令處理器", true);
try {
await interaction.reply({
content: "❌ 找不到命令處理器",
ephemeral: true,
});
} catch (replyError) {
log.call(this, `發送錯誤消息時出錯: ${replyError.message}`, true);
}
}
}
} catch (error) {
log.call(this, `處理互動時出錯: ${error.message}`, true);
} finally {
// 移除處理中的標記
processingInteractions.delete(interaction.id);
}
});
// 設置消息處理器
this.client.on("messageCreate", async (message) => {
if (message.author.bot) return;
for (const [name, script] of this.scripts) {
// 檢查腳本是否啟用
if (script.enabled === false) continue;
try {
const scriptModule = script.module;
if (typeof scriptModule === "function") {
// 如果是函數,直接調用
await scriptModule(message);
} else if (typeof scriptModule.execute === "function") {
// 如果是對象,調用 execute 方法
await scriptModule.execute({ client: this.client, message });
}
} catch (error) {
log.call(this, `執行腳本 ${name} 時出錯: ${error.message}`, true);
// 添加更詳細的錯誤日誌
this._logScriptError(name, error, message);
}
}
});
this._isInitialized = true; // 標記為已初始化
}
async _initCommands() {
const commandsPath = path.join(__dirname, "commands");
const commandFiles = fs
.readdirSync(commandsPath)
.filter((file) => file.endsWith(".js"));
log.call(this, "開始初始化命令...");
log.call(this, `找到的命令文件: ${JSON.stringify(commandFiles)}`);
// 清空現有命令集合
this.commands.clear();
for (const file of commandFiles) {
try {
const command = require(path.join(commandsPath, file));
// 載入所有命令
if ("data" in command && "execute" in command) {
if (this.commands.has(command.data.name)) {
log.call(
this,
`警告: 命令 ${command.data.name} 已存在,將被覆蓋`,
true
);
}
this.commands.set(command.data.name, command);
log.call(this, `已載入命令: ${command.data.name}`);
}
// 載入 interaction 處理器(不需要 data 屬性)
else if (file === "interaction.js" && "execute" in command) {
if (this.commands.has("interaction")) {
log.call(this, "警告: interaction 處理器已存在,將被覆蓋", true);
}
this.commands.set("interaction", command);
log.call(this, "已載入互動處理器");
} else {
log.call(
this,
`警告: ${file} 缺少必要的屬性 (data 或 execute)`,
true
);
}
} catch (error) {
log.call(this, `載入命令 ${file} 時出錯: ${error.message}`, true);
console.error("詳細錯誤信息:", {
error: error.message,
code: error.code,
file,
timestamp: new Date().toISOString(),
});
}
}
log.call(
this,
`命令初始化完成,已載入命令: ${JSON.stringify(
Array.from(this.commands.keys())
)}`
);
}
async loadScripts() {
log.call(this, `開始載入腳本,腳本目錄: ${this.options.scriptFolder}`);
// 確保腳本目錄存在
if (!fs.existsSync(this.options.scriptFolder)) {
log.call(this, `創建腳本目錄: ${this.options.scriptFolder}`);
fs.mkdirSync(this.options.scriptFolder, { recursive: true });
}
const scriptFiles = fs
.readdirSync(this.options.scriptFolder)
.filter((file) => file.endsWith(".js"));
log.call(this, `找到腳本文件: ${JSON.stringify(scriptFiles)}`);
let loadedCount = 0;
let errorCount = 0;
for (const file of scriptFiles) {
try {
// 使用絕對路徑
const scriptPath = path.resolve(this.options.scriptFolder, file);
log.call(this, `載入腳本: ${scriptPath}`);
// 檢查文件大小
const stats = fs.statSync(scriptPath);
if (stats.size > this.options.maxScriptSize) {
throw new Error(
`腳本大小超過限制 (${stats.size} > ${this.options.maxScriptSize})`
);
}
// 清除快取
if (require.cache[scriptPath]) {
delete require.cache[scriptPath];
}
// 使用 require.resolve 來獲取規範化的路徑
const resolvedPath = require.resolve(scriptPath);
const script = require(resolvedPath);
// 檢查是否已存在此腳本,如果存在則保留其啟用狀態
const existingScript = this.scripts.get(file);
const enabled = existingScript ? existingScript.enabled : true;
// 添加腳本元數據
const metadata = {
name: file,
path: scriptPath,
size: stats.size,
lastModified: stats.mtime,
enabled: enabled,
};
this.scripts.set(file, {
...metadata,
module: script,
});
log.call(this, `成功載入腳本: ${file}`);
loadedCount++;
} catch (error) {
log.call(this, `載入腳本 ${file} 時出錯: ${error.message}`, true);
console.error("詳細錯誤信息:", {
error: error.message,
code: error.code,
file,
timestamp: new Date().toISOString(),
});
errorCount++;
this._logScriptError(file, error);
}
}
log.call(this, `腳本載入完成: ${loadedCount} 成功, ${errorCount} 失敗`);
return { loaded: loadedCount, errors: errorCount };
}
async executeScript(scriptName, context = { client: this.client }) {
const script = this.scripts.get(scriptName);
if (!script) {
throw new Error(`腳本 ${scriptName} 不存在`);
}
try {
const scriptModule = script.module;
if (typeof scriptModule === "function") {
return await scriptModule(context);
} else if (typeof scriptModule.execute === "function") {
return await scriptModule.execute(context);
} else {
throw new Error("腳本必須導出函數或包含 execute 方法的對象");
}
} catch (error) {
console.error(`執行腳本 ${scriptName} 時出錯:`, error);
throw error;
}
}
isUserAllowed(userId) {
return (
this.options.allowedUsers.length === 0 ||
this.options.allowedUsers.includes(userId)
);
}
async registerCommands(guildId) {
try {
if (!this.client.application) {
throw new Error("Client application is not available");
}
// 優先使用傳入的 guildId,如果沒有則使用 options 中的 guildId
const targetGuildId = guildId || this.options.guildId;
log.call(this, "開始註冊命令...");
log.call(this, `目標伺服器: ${targetGuildId || "全局"}`);
// 獲取現有指令
const existingCommands = await this.client.application.commands.fetch({
guildId: targetGuildId,
});
log.call(this, `現有指令數量: ${existingCommands.size}`);
log.call(
this,
`現有指令: ${JSON.stringify(
Array.from(existingCommands.values()).map((cmd) => cmd.name)
)}`
);
// 修改刪除指令的邏輯,只刪除專案特定的命令
const projectCommands = new Set([
"uploadscript",
"managescript",
"listscripts",
"deletescript",
"previewscript",
]);
log.call(this, "開始清理現有指令...");
for (const [id, command] of existingCommands) {
if (projectCommands.has(command.name)) {
log.call(this, `正在刪除指令: ${command.name} (${id})`);
await command.delete();
log.call(this, `已刪除指令: ${command.name}`);
}
}
// 等待一小段時間確保刪除操作完成
await new Promise((resolve) => setTimeout(resolve, 2000));
// 驗證刪除結果
const afterDeleteCommands = await this.client.application.commands.fetch({
guildId: targetGuildId,
});
if (afterDeleteCommands.size > 0) {
log.call(
this,
`警告: 仍有指令未被刪除: ${JSON.stringify(
Array.from(afterDeleteCommands.values()).map((cmd) => cmd.name)
)}`,
true
);
} else {
log.call(this, "所有現有指令已成功刪除");
}
// 載入並註冊新的指令
log.call(this, "開始註冊新指令...");
const commandsPath = path.join(__dirname, "commands");
const commandsToRegister = [
"uploadscript.js",
"managescript.js",
"listscripts.js",
"deletescript.js",
"previewscript.js",
];
const registeredCommands = new Set();
for (const commandFile of commandsToRegister) {
const commandPath = path.join(commandsPath, commandFile);
if (fs.existsSync(commandPath)) {
const command = require(commandPath);
if ("data" in command) {
try {
if (registeredCommands.has(command.data.name)) {
log.call(
this,
`警告: 命令 ${command.data.name} 已註冊,跳過重複註冊`,
true
);
continue;
}
if (targetGuildId) {
await this.client.application.commands.create(
command.data.toJSON(),
targetGuildId
);
log.call(
this,
`成功註冊 ${command.data.name} 指令到伺服器 ${targetGuildId}`
);
} else {
await this.client.application.commands.create(
command.data.toJSON()
);
log.call(this, `成功註冊 ${command.data.name} 指令到全局`);
}
registeredCommands.add(command.data.name);
} catch (error) {
log.call(
this,
`註冊 ${command.data.name} 時出錯: ${error.message}`,
true
);
console.error("詳細錯誤信息:", {
error: error.message,
code: error.code,
command: command.data.name,
timestamp: new Date().toISOString(),
});
throw error;
}
} else {
log.call(this, `${commandFile} 缺少必要的 data 屬性`, true);
}
} else {
log.call(this, `找不到命令文件: ${commandFile}`, true);
}
}
// 驗證註冊結果
const finalCommands = await this.client.application.commands.fetch({
guildId: targetGuildId,
});
log.call(this, `最終指令數量: ${finalCommands.size}`);
log.call(
this,
`已註冊的指令: ${JSON.stringify(
Array.from(finalCommands.values()).map((cmd) => cmd.name)
)}`
);
// 驗證所有需要的指令都已註冊
const missingCommands = commandsToRegister
.map((file) => file.replace(".js", ""))
.filter(
(name) =>
!Array.from(finalCommands.values()).some((cmd) => cmd.name === name)
);
if (missingCommands.length > 0) {
log.call(
this,
`警告: 以下指令未能成功註冊: ${JSON.stringify(missingCommands)}`,
true
);
}
log.call(this, "命令註冊完成");
} catch (error) {
log.call(this, `註冊命令時出錯: ${error.message}`, true);
console.error("詳細錯誤信息:", {
error: error.message,
code: error.code,
guildId: targetGuildId,
timestamp: new Date().toISOString(),
});
throw error;
}
}
/**
* 記錄腳本錯誤
* @private
*/
_logScriptError(scriptName, error, message) {
const errorInfo = {
script: scriptName,
error: error.message,
stack: error.stack,
timestamp: new Date().toISOString(),
message: {
id: message.id,
content: message.content,
author: message.author.tag,
channel: message.channel.name,
},
};
console.error("腳本錯誤詳情:", JSON.stringify(errorInfo, null, 2));
// 如果配置了錯誤通知頻道,發送錯誤通知
if (this.options.errorChannel) {
this.options.errorChannel
.send({
content: `❌ 腳本 \`${scriptName}\` 執行出錯`,
embeds: [
{
title: "錯誤詳情",
description: `\`\`\`js\n${error.message}\n\`\`\``,
fields: [
{ name: "腳本", value: scriptName },
{ name: "頻道", value: message.channel.name },
{ name: "用戶", value: message.author.tag },
],
color: 0xff0000,
timestamp: new Date(),
},
],
})
.catch(console.error);
}
}
}
/**
* Discord Script Manager
* @module discord-script-manager
*/
/**
* Create a new ScriptManager instance
* @param {Object} options - Configuration options
* @param {Object} options.client - Discord.js client
* @param {string} [options.scriptFolder] - Path to script folder
* @returns {ScriptManager} ScriptManager instance
*/
function createScriptManager(options) {
// Validate options
if (!options) {
throw new Error(
"Options are required. Please provide a configuration object."
);
}
// Check client
if (!options.client) {
throw new Error(`
Discord client is required.
Example setup:
const { Client, GatewayIntentBits } = require('discord.js');
const { createScriptManager } = require('n1cat-discord-script-manager');
const client = new Client({
intents: [
GatewayIntentBits.Guilds,
GatewayIntentBits.GuildMessages,
GatewayIntentBits.MessageContent
]
});
const scriptManager = createScriptManager({
client: client,
scriptFolder: './scripts' // Optional: specify script folder
});`);
}
// Check script folder
if (!options.scriptFolder) {
options.scriptFolder = path.resolve(process.cwd(), "scripts");
console.warn(
`No script folder specified. Using default: ${options.scriptFolder}`
);
}
try {
// Ensure script folder exists
if (!fs.existsSync(options.scriptFolder)) {
fs.mkdirSync(options.scriptFolder, { recursive: true });
console.log(`Created script folder: ${options.scriptFolder}`);
}
// Validate Logger is available
if (typeof Logger === "undefined") {
throw new Error(`
Logger is not properly imported.
Troubleshooting steps:
1. Ensure 'n1cat-discord-script-manager' is installed correctly
2. Check that './utils/logger.js' exists in the package
3. Verify no conflicts in logger import
Example import:
const { Logger } = require('n1cat-discord-script-manager');
or
const Logger = require('n1cat-discord-script-manager/src/utils/logger');`);
}
return new ScriptManager(options.client, options);
} catch (error) {
console.error("Failed to create ScriptManager:", error);
throw new Error(`
ScriptManager initialization failed:
${error.message}
Troubleshooting tips:
1. Ensure you're using a recent version of discord.js
2. Verify client is properly initialized with required intents
3. Check script folder permissions
4. Verify logger is correctly imported`);
}
}
/**
* 創建一個新的消息處理器實例
* @param {Message} message - Discord.js 消息對象
* @param {Object} [options] - 配置選項
* @param {number} [options.timeout=5000] - 操作超時時間
* @param {number} [options.maxRetries=3] - 最大重試次數
* @param {number} [options.cacheSize=100] - 消息緩存大小
* @param {boolean} [options.debug=false] - 是否開啟調試模式
* @returns {MessageHandler} 消息處理器實例
*/
function createMessageHandler(message, options = {}) {
if (!message) {
throw new Error("Message is required");
}
const defaultOptions = {
timeout: 5000,
maxRetries: 3,
cacheSize: 100,
debug: false,
};
try {
return new MessageHandler(message, { ...defaultOptions, ...options });
} catch (error) {
console.error("Failed to create MessageHandler:", error);
throw error;
}
}
/**
* 創建一個新的事件處理器實例
* @param {Object} options - 配置選項
* @param {Object} options.client - Discord.js 客戶端
* @param {string} options.scriptFolder - 腳本資料夾路徑
* @returns {EventHandler} 事件處理器實例
*/
function createEventHandler(options) {
if (!options) {
throw new Error("Options are required");
}
if (!options.client) {
throw new Error("Discord client is required");
}
if (!options.scriptFolder) {
throw new Error("Script folder path is required");
}
try {
return new EventHandler(options);
} catch (error) {
console.error("Failed to create EventHandler:", error);
throw error;
}
}
// 導出所有工廠函數和工具函數
module.exports = {
// 工廠函數
createScriptManager,
createMessageHandler,
createEventHandler,
// 工具函數
installDependency,
loadModule,
checkDependencyVersion,
checkDependencyConflicts,
checkDependencySecurity,
getDependencyInfo,
// 為了向後兼容,也導出類
CoreScriptManager,
MessageHandler,
EventHandler,
// 導出 Logger
Logger,
};