فهرست منبع

支持热重载

枫林 3 ماه پیش
والد
کامیت
40c3492a2a
6فایلهای تغییر یافته به همراه482 افزوده شده و 698 حذف شده
  1. 8 0
      nodemon.json
  2. 413 657
      package-lock.json
  3. 4 4
      package.json
  4. 5 5
      pnpm-lock.yaml
  5. 9 0
      src/app.ts
  6. 43 32
      src/lib/Plugins.ts

+ 8 - 0
nodemon.json

@@ -0,0 +1,8 @@
+{
+  "watch": ["src"],
+  "ext": "ts yml",
+  "exec": "node --no-warnings --loader=ts-node/esm --experimental-specifier-resolution=node --import=ts-node/esm src/app.ts",
+  "env": {
+    "NODE_OPTIONS": "--experimental-vm-modules"
+  }
+}

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 413 - 657
package-lock.json


+ 4 - 4
package.json

@@ -6,7 +6,7 @@
   "type": "module",
   "scripts": {
     "build": "cp -R ./src/config/ ./dist/ && cp -R ./src/resources/ ./dist/ && tsc",
-    "dev": "nodemon --exec \"node --no-warnings --loader ts-node/esm --import tsconfig-paths/register src/app.ts\"",
+    "dev": "nodemon --watch src --ext ts,yml --exec \"node --no-warnings --loader=ts-node/esm --import=ts-node/esm src/app.ts\"",
     "start": "npm run build && node dist/app.js"
   },
   "keywords": [],
@@ -21,7 +21,6 @@
     "node-napcat-ts": "^0.4.0",
     "puppeteer": "^23.9.0",
     "reflect-metadata": "^0.2.2",
-    "ts-node": "^10.9.2",
     "winston": "^3.17.0",
     "winston-daily-rotate-file": "^5.0.0",
     "yaml": "^2.6.1"
@@ -29,11 +28,12 @@
   "devDependencies": {
     "@types/js-yaml": "^4.0.9",
     "@types/minimist": "^1.2.5",
-    "@types/node": "^16.0.0",
+    "@types/node": "^16.18.126",
     "@types/node-cron": "^3.0.11",
     "@types/winston": "^2.4.4",
     "nodemon": "^3.1.9",
+    "ts-node": "^10.9.2",
     "tsconfig-paths": "^4.2.0",
-    "typescript": "^4.5.0"
+    "typescript": "^4.9.5"
   }
 }

+ 5 - 5
pnpm-lock.yaml

@@ -32,9 +32,6 @@ importers:
       reflect-metadata:
         specifier: ^0.2.2
         version: 0.2.2
-      ts-node:
-        specifier: ^10.9.2
-        version: 10.9.2(@types/node@16.18.126)(typescript@4.9.5)
       winston:
         specifier: ^3.17.0
         version: 3.17.0
@@ -52,7 +49,7 @@ importers:
         specifier: ^1.2.5
         version: 1.2.5
       '@types/node':
-        specifier: ^16.0.0
+        specifier: ^16.18.126
         version: 16.18.126
       '@types/node-cron':
         specifier: ^3.0.11
@@ -63,11 +60,14 @@ importers:
       nodemon:
         specifier: ^3.1.9
         version: 3.1.10
+      ts-node:
+        specifier: ^10.9.2
+        version: 10.9.2(@types/node@16.18.126)(typescript@4.9.5)
       tsconfig-paths:
         specifier: ^4.2.0
         version: 4.2.0
       typescript:
-        specifier: ^4.5.0
+        specifier: ^4.9.5
         version: 4.9.5
 
 packages:

+ 9 - 0
src/app.ts

@@ -24,4 +24,13 @@ process.on('SIGINT', () => {
   botlogger.info("正在关闭机器人...");
   qqBot.disconnect();
   process.exit(0);
+});
+
+process.on('uncaughtException', (error) => {
+    console.error('[致命错误]', error);
+    process.exit(1);
+});
+
+process.on('unhandledRejection', (reason, promise) => {
+    console.error('[未处理的Promise拒绝]', reason);
 });

+ 43 - 32
src/lib/Plugins.ts

@@ -2,20 +2,20 @@
 import botlogger from "./logger.js";
 import { promises as fsPromises } from 'fs';
 import { HtmlImg } from "./Puppeteer.js";
-import type {GroupMessage, EventHandleMap as MessageContext, PrivateFriendMessage, PrivateGroupMessage } from 'node-napcat-ts/dist/Interfaces.js';
+import type { GroupMessage, EventHandleMap as MessageContext, PrivateFriendMessage, PrivateGroupMessage } from 'node-napcat-ts/dist/Interfaces.js';
 import * as cron from 'node-cron';
 import 'reflect-metadata';
-import { 
-    ParamType, 
-    commandList, 
+import {
+    ParamType,
+    commandList,
     Command,
     Plugin,
 } from './decorators.js';
 import * as fs from 'fs'
 import * as path from 'path'
 // 获取指令前缀
-import {Botconfig as config, PermissionConfig} from './config.js'
-import { ImageSegment, ReplySegment,  TextSegment } from "node-napcat-ts/dist/Structs.js";
+import { Botconfig as config, PermissionConfig } from './config.js'
+import { ImageSegment, ReplySegment, TextSegment } from "node-napcat-ts/dist/Structs.js";
 import { fileURLToPath } from 'node:url';
 import { qqBot } from "../app.js";
 import { count } from "node:console";
@@ -23,7 +23,7 @@ import { IsPermission } from "./Permission.js";
 
 //WSSendParam
 
-const CMD_PREFIX = config?.cmd?.prefix??'#'; 
+const CMD_PREFIX = config?.cmd?.prefix ?? '#';
 
 // 导出装饰器
 // export { param, ParamType };
@@ -68,13 +68,13 @@ function findCommand(plugin: Plugin, cmdName: string): Command | undefined {
         // 从完整命令中提取命令名
         const cmdParts = cmd.cmd.split(/\s+/);
         const matchCmd = cmdParts[cmdParts.length - 1] === cmdName;
-        
+
         // 检查别名
         const matchAlias = cmd.aliases?.some((alias: string) => {
             const aliasParts = alias.split(/\s+/);
             return aliasParts[aliasParts.length - 1] === cmdName;
         });
-        
+
         return matchCmd || matchAlias;
     });
 }
@@ -86,14 +86,16 @@ async function loadPlugins(): Promise<void> {
     try {
         const __dirname = path.dirname(fileURLToPath(import.meta.url));
         const pluginsDir = path.join(__dirname, '..', 'plugins');
+
+        // 删除require缓存相关代码
         const files = await fsPromises.readdir(pluginsDir);
-        
+
         for (const file of files) {
-            if (file.endsWith('.js') && file !== 'index.ts') {
+            if (file.endsWith('.ts') && file !== 'index.ts') {
                 const filePath = path.join(pluginsDir, file);
                 try {
-                    // 动态导入插件
-                    const module = await import(filePath);
+                    // 使用ESM动态导入并添加时间戳防止缓存
+                    const module = await import(`${filePath}?t=${Date.now()}`);
                     const pluginClasses = Object.values(module).filter(
                         value => typeof value === 'function' && value.prototype?.plugincfg
                     );
@@ -101,7 +103,7 @@ async function loadPlugins(): Promise<void> {
                     for (const PluginClass of pluginClasses) {
                         const instance = new (PluginClass as any)();
                         const pluginConfig = instance.constructor.prototype.plugincfg;
-                        
+
                         if (pluginConfig) {
                             const plugin: Plugin = {
                                 id: pluginConfig.id,
@@ -154,7 +156,7 @@ async function initializePluginCommands(instance: any): Promise<void> {
 async function initializeScheduledTasks(instance: any): Promise<void> {
     const methods = Object.getOwnPropertyNames(instance.constructor.prototype)
         .filter(name => name !== 'constructor');
-    
+
     for (const methodName of methods) {
         const method = instance.constructor.prototype[methodName];
         if (method.isScheduled) {
@@ -180,6 +182,15 @@ async function initializeScheduledTasks(instance: any): Promise<void> {
 // 修改 runplugins 函数
 export async function runplugins() {
     try {
+        // 清理旧实例
+        commandList.forEach(plugin => {
+            plugin.commands.forEach(cmd => {
+                // 检查 cmd.fn 是否存在,并且是否有 close 方法
+                if (cmd.fn && typeof (cmd.fn as any).close === 'function') {
+                    (cmd.fn as any).close();
+                }
+            });
+        });
 
         // 清空现有命令列表
         commandList.length = 0;
@@ -194,7 +205,7 @@ export async function runplugins() {
                     return;
                 }
                 const msg = context.message[0].data.text || '';
-                botlogger.info('收到消息:'+ context.message[0].data.text);
+                botlogger.info('收到消息:' + context.message[0].data.text);
                 // 检查是否是命令
                 if (!msg.startsWith(CMD_PREFIX)) {
                     return;
@@ -238,8 +249,8 @@ export async function runplugins() {
 
                 botlogger.info(`找到命令: ${CMD_PREFIX}${plugin.id} ${command.cmd}`);
                 //指令权限检查
-                if(context.message_type === 'private'){
-                    if(!await IsPermission(context.user_id,plugin.id,command.cmd)){
+                if (context.message_type === 'private') {
+                    if (!await IsPermission(context.user_id, plugin.id, command.cmd)) {
                         botlogger.info(`[${context.user_id}]无权限执行命令: ${CMD_PREFIX}${plugin.id} ${command.cmd}`);
                         context.quick_action([{
                             type: 'text',
@@ -248,13 +259,13 @@ export async function runplugins() {
                         return;
                     }
                 }
-                if(context.message_type === 'group'){
-                    if(!await IsPermission(context.group_id,plugin.id,command.cmd)){
+                if (context.message_type === 'group') {
+                    if (!await IsPermission(context.group_id, plugin.id, command.cmd)) {
                         botlogger.info(`[${context.group_id}]无权限执行命令: ${CMD_PREFIX}${plugin.id} ${command.cmd}`);
                         context.quick_action([{
                             type: 'text',
                             data: { text: `你没有权限执行此命令` }
-                       }])
+                        }])
                         return;
                     }
                 }
@@ -278,7 +289,7 @@ export async function runplugins() {
                 botlogger.info(`  ${CMD_PREFIX}${plugin.id} ${cmd.cmd}`);
             }
         }
-        
+
     } catch (error) {
         botlogger.error("注册插件时出错:", error);
     }
@@ -294,7 +305,7 @@ async function handleCommand(context: PrivateFriendMessage | PrivateGroupMessage
         const message = context.message[0].data.text || '';
         const parsedArgs = await parseCommandParams(message, context);
 
-        botlogger.info('命令参数解析完成:'+ JSON.stringify({
+        botlogger.info('命令参数解析完成:' + JSON.stringify({
             command: command.cmd,
             args: parsedArgs.slice(0, -1) // 不显示 context 对象
         }));
@@ -305,7 +316,7 @@ async function handleCommand(context: PrivateFriendMessage | PrivateGroupMessage
 
         // 检查是否是群消息
         const isGroupMessage = context.message_type === 'group';
-        const baseMessage = isGroupMessage && context.message_id 
+        const baseMessage = isGroupMessage && context.message_id
             ? [createReplyMessage(context.message_id)]
             : [];
 
@@ -433,10 +444,10 @@ export function runcod(cmd: string | string[], desc: string): MethodDecorator {
             botlogger.error(`未找到插件配置: ${target.constructor.name}`);
             return descriptor;
         }
-        
+
         const pluginId = pluginConfig.id;
         const pluginName = pluginConfig.name;
-        
+
         // 获或创建插件的命令列表
         let plugin = commandList.find((p: Plugin) => p.class === target.constructor);
         if (!plugin) {
@@ -453,7 +464,7 @@ export function runcod(cmd: string | string[], desc: string): MethodDecorator {
         // 使用新的命令格式
         const cmdList = Array.isArray(cmd) ? cmd : [cmd];
         const [mainCmd, ...aliases] = cmdList;
-        
+
         // 修改命令创建
         const command: Command = {
             cmd: mainCmd,
@@ -468,10 +479,10 @@ export function runcod(cmd: string | string[], desc: string): MethodDecorator {
                 sendText: true
             }
         };
-        
+
         plugin.commands.push(command);
         botlogger.info(`注册命令[${pluginId}]: ${CMD_PREFIX}${pluginId} ${mainCmd}`);
-        
+
         return descriptor;
     };
 }
@@ -480,7 +491,7 @@ export function runcod(cmd: string | string[], desc: string): MethodDecorator {
 // 修改参数解析函数
 async function parseCommandParams(message: string, context: PrivateFriendMessage | PrivateGroupMessage | GroupMessage): Promise<any[]> {
     const cmdArgs = message.split(/\s+/).filter(Boolean);
-    
+
     // 移除命令前缀和命令名
     const cmdPrefix = '#';
     const parts = message.split(/\s+/);
@@ -495,12 +506,12 @@ async function parseCommandParams(message: string, context: PrivateFriendMessage
     }));
 
     const params: any[] = [];
-    
+
     // 添加参数
     if (paramArgs.length > 0) {
         // 第一个参数作为字符串
         params.push(paramArgs[0]);
-        
+
         // 第二个参数尝试转换为数字
         if (paramArgs.length > 1) {
             const num = Number(paramArgs[1]);

برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است