浏览代码

支持动态上传下载插件

枫林 3 月之前
父节点
当前提交
26ecba3c5e
共有 6 个文件被更改,包括 187 次插入3 次删除
  1. 18 2
      src/config/permission.yml
  2. 4 0
      src/lib/Permission.ts
  3. 29 1
      src/lib/Plugins.ts
  4. 18 0
      src/lib/download.ts
  5. 21 0
      src/plugins/sakulin.ts
  6. 97 0
      src/plugins/upload.ts

+ 18 - 2
src/config/permission.yml

@@ -1,13 +1,29 @@
 enable: true
+admins:
+  - '2180323481'
+  - '1814872986'
 users:
-  default:
+  '211249983':
     plugins:
       test:
         enable: true
         commands:
           help: true
           param: false
-  '211249983':
+      testupload:
+        commands:
+          help: true
+          param: true
+      downloadPlugins:
+        commands:
+          download: true
+          help: true
+          plugins: true
+      saku:
+        commands:
+          ping: true
+          help: true
+  default:
     plugins:
       test:
         enable: true

+ 4 - 0
src/lib/Permission.ts

@@ -3,6 +3,10 @@ import botlogger from "./logger.js";
 
 export async function IsPermission(id: number, plugin: string, command: string): Promise<boolean> {
     try {
+        // 检查用户是否在白名单中
+        if (PermissionConfig.admins.some((admin: string) => admin === String(id))) {
+            return true;
+        }
         // 获取用户权限配置(带默认回退)
         const userPermission = getUserPermission(id);
         

+ 29 - 1
src/lib/Plugins.ts

@@ -20,6 +20,7 @@ import { fileURLToPath } from 'node:url';
 import { qqBot } from "../app.js";
 import { count } from "node:console";
 import { IsPermission } from "./Permission.js";
+import { download } from "./download.js";
 
 //WSSendParam
 
@@ -136,7 +137,7 @@ async function loadPlugins(): Promise<void> {
 async function initializePluginCommands(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];
         try {
@@ -201,6 +202,33 @@ export async function runplugins() {
         // 设置消息处理器
         qqBot.on('message', async (context) => {
             try {
+                // 检查消息类型和内容
+                if (context.message[0].type === "file") {
+                    const file = context.message[0].data;
+                    botlogger.info("收到文件消息:" + JSON.stringify(file));
+                    if (file.file.includes(".ts")) {
+                        let isAdmin = false
+                        // 使用 some 方法简化循环逻辑,只要数组中有一个元素满足条件就返回 true
+                        isAdmin = PermissionConfig.admins.some((admin: string) => admin === String(context.sender.user_id));
+                        if (!isAdmin) {
+                            context.quick_action([{
+                                type: 'text',
+                                data: { text: `无权限,无法加载插件` }
+                            }]);
+                            return;
+                        }
+                        const url = (file as any).url; // 文件URL
+                        await download(url, `../plugins/${file.file}`);
+                        botlogger.info("下载完成:" + JSON.stringify(file));
+                        context.quick_action([{
+                            type: 'text',
+                            data: { text: `插件下载完成,开始重载` }
+                        }]);
+                    }
+                    return;
+                }
+
+
                 if (context.message[0].type !== 'text') {
                     return;
                 }

+ 18 - 0
src/lib/download.ts

@@ -0,0 +1,18 @@
+import axios from 'axios';
+import fs from 'fs';
+import path from 'path';
+import { fileURLToPath } from 'node:url';
+async function downloadFile(url: string, savePath: string) {
+     const response = await axios.get(url, { responseType: 'stream' });
+     const writer = fs.createWriteStream(savePath);
+     response.data.pipe(writer);
+     return new Promise((resolve, reject) => {
+         writer.on('finish', resolve);
+         writer.on('error', reject);
+     });
+}
+export async function download(url: string, savePath: string) {
+    const __dirname = path.dirname(fileURLToPath(import.meta.url));
+    const fullSavePath = path.join(__dirname, savePath);
+    await downloadFile(url, fullSavePath);
+}

+ 21 - 0
src/plugins/sakulin.ts

@@ -0,0 +1,21 @@
+import { plugins, runcod } from '../lib/decorators.js';
+import 'reflect-metadata';
+
+@plugins({
+    id: "saku",
+    name: "Sakulin Helper",
+    version: "1.0.0",
+    describe: "This is a plugin by Sakulin",
+    author: "活性红磷",
+    help: {
+        enabled: true,
+        description: "显示帮助信息"
+    }
+})
+export class sakulass {
+    @runcod(["ping", "test"], "测试接口")
+    async test() {
+        await ((ms) => new Promise((resolve) => {setTimeout(resolve, ms);}))(500);
+        return "pong";
+    }
+}

+ 97 - 0
src/plugins/upload.ts

@@ -0,0 +1,97 @@
+import { param, ParamType, plugins, runcod } from '../lib/decorators.js';
+import path from 'path';
+import 'reflect-metadata';
+import { fileURLToPath } from 'node:url';
+import { GroupMessage, PrivateFriendMessage, PrivateGroupMessage } from 'node-napcat-ts';
+import botlogger from '../lib/logger.js';
+import fs from 'fs/promises';
+import { qqBot } from '../app.js';
+
+@plugins({
+    id: "downloadPlugins", //插件ID,必须唯一,不能重复
+    name: "下载插件", //插件名称,用于显示在菜单中
+    version: "1.0.0", //插件版本号,用于显示在菜单中
+    describe: "在服务器下载一个插件", //插件描述,用于显示在菜单中
+    author: "枫叶秋林",//插件作者,用于显示在菜单中
+    help: { //插件帮助信息,用于显示在菜单中
+        enabled: true, //是否启用帮助信息
+        description: "显示帮助信息" //帮助信息描述
+    }
+})
+export class downloadPlugins {
+    @runcod(["download","下载插件"], "下载插件")//命令装饰器,用于注册命令
+    async param(
+        @param("插件名称", ParamType.String) pluName: string,
+        context: PrivateFriendMessage | PrivateGroupMessage | GroupMessage
+    ): Promise<any> {
+        // pluName += ".ts"
+        const __dirname = path.dirname(fileURLToPath(import.meta.url));
+        // 查找插件目录下的文件
+        const pluginsDir = path.join(__dirname, '..', 'plugins');
+        try {
+            const files = await fs.readdir(pluginsDir);
+            const foundFiles = files.filter(file => 
+                (file.endsWith('.ts') || file.endsWith('.js')) && 
+                file !== 'index.ts'
+            );
+        
+            // 在服务器日志中输出找到的文件列表
+            botlogger.info(`找到插件文件:${foundFiles.join(', ')}`);
+            
+            // 根据文件名查找具体插件
+            const targetFile = foundFiles.find((file: string) => 
+                path.parse(file).name.toLowerCase() === pluName.toLowerCase()
+            );
+        
+            if (!targetFile) {
+                return `未找到名为 ${pluName} 的插件`;
+            }
+        
+            // 返回文件完整路径
+            const fullPath = path.join(pluginsDir, targetFile);
+            //.toString('base64'
+
+            const file = Buffer.from(await fs.readFile(fullPath, { encoding: "utf-8" })).toString('base64')
+            const isGroupMessage = context.message_type === 'group';
+            if (isGroupMessage && context.group_id) {
+                await qqBot.upload_group_file({
+                    group_id: Number(context.group_id),
+                    file: 'data:file;base64,'+file,
+                    name: pluName
+                })
+                
+            } else {
+                await qqBot.upload_private_file({
+                    user_id: Number(context.sender.user_id),
+                    file: 'data:file;base64,'+file,
+                    name: pluName
+                })
+            }
+           
+            return'上传成功';
+        } catch (error) {
+            botlogger.error('文件查找失败:', error);
+            return '插件查找服务暂不可用';
+        }
+    }
+    @runcod(["plugins","插件列表"], "查看插件文件")//命令装饰器,用于注册命令
+    async plugins(
+        context: PrivateFriendMessage | PrivateGroupMessage | GroupMessage
+    ): Promise<any> {
+        // pluName += ".ts"
+        const __dirname = path.dirname(fileURLToPath(import.meta.url));
+        // 查找插件目录下的文件
+        const pluginsDir = path.join(__dirname, '..', 'plugins');
+        try {
+            const files = await fs.readdir(pluginsDir);
+            const foundFiles = files.filter(file => 
+                (file.endsWith('.ts') || file.endsWith('.js')) && 
+                file !== 'index.ts'
+            );
+            return `找到插件文件:${foundFiles.join(', ')}`;
+        } catch (error) {
+            botlogger.error('文件查找失败:', error);
+            return '插件查找服务暂不可用';
+        }
+    }
+}