Explorar o código

refactor(权限管理): 重构权限管理系统以支持更灵活的权限控制

重构权限管理系统,引入新的权限类型(Admin、User、Group),并优化权限检查逻辑。移除旧的权限配置方式,改为基于用户数据的权限管理,提升系统的可维护性和扩展性。同时修复了权限检查中的潜在问题,确保权限控制的准确性。
枫林 hai 3 meses
pai
achega
5c8204aa5a

+ 3 - 4
src/config/load.yml

@@ -1,6 +1,5 @@
-# 热重载插件配置文件
 enable: true
 isuplad: false
-name: sakulin.ts
-id: 2180323481
-isGroupMessage: false
+name: jixin.ts
+id: 773847213
+isGroupMessage: true

+ 4 - 0
src/config/permission.yml

@@ -0,0 +1,4 @@
+enable: true #是否启用权限管理
+admins: #管理员QQ号
+  - '2180323481'
+  - '1814872986'

+ 4 - 0
src/interface/Permission.ts

@@ -0,0 +1,4 @@
+export interface PermissionCommands {
+    name: string; 
+    type: 'Admin'| 'User' | 'Group';
+}

+ 3 - 2
src/interface/economy.ts

@@ -1,4 +1,4 @@
-import { Prop } from "./prop.js";
+import { UserProp } from "./prop.js";
 
 export interface UserData {
     userId: string; 
@@ -6,7 +6,8 @@ export interface UserData {
         coins: number;
         logs: Economylogs[];
     },
-    props:Prop[]
+    props:UserProp[]
+    Permission:string[]
 }
 export interface Economylogs {
     type: 'add' | 'remove';

+ 4 - 0
src/interface/prop.ts

@@ -9,3 +9,7 @@ export interface Prop{
     price: number; // 道具价格
     classConstructor:any;
 }
+export interface UserProp{
+    propId: string;
+    Num: number;
+}

+ 76 - 68
src/lib/Permission.ts

@@ -1,81 +1,89 @@
-import { PermissionConfig, saveConfig, savePermission } from "./config.js";
+import { PermissionConfig } from "./config.js";
 import botlogger from "./logger.js";
+import { PermissionCommands } from "../interface/Permission.js";
+import { getUserData, saveUserData } from "./economy.js";
 export const IsAdmin = async function (id:number){return await PermissionConfig.admins.some((admin: string) => admin === String(id)) }
-export async function IsPermission(id: number, plugin: string, command: string): Promise<boolean> {
-    try {
-        // 检查用户是否在管理员
-        if (await IsAdmin(id)) {
-            return true;
-        }
-        // 获取用户权限配置(带默认回退)
-        const userPermission = getUserPermission(id);
-        
-        // 获取插件配置(带默认回退)
-        const pluginConfig = getPluginConfig(userPermission, plugin);
-        
-        // 检查插件总开关
-        if (pluginConfig?.enable === false) return false;
+export const permissionCommands:PermissionCommands[] = [];
 
-        // 获取命令权限配置
-        const commandPermission = getCommandPermission(pluginConfig, command);
-        
-        // 自动保存新配置
-        if (commandPermission === undefined) {
-            await saveNewCommandConfig(id, plugin, command);
-            return true; // 默认允许新命令
-        }
-        
-        return Boolean(commandPermission);
-    } catch (error) {
-        if (error instanceof Error) {
-            botlogger.error(`权限检查失败 [${id}/${plugin}/${command}]:${error.message}`);
-        } else {
-            botlogger.error(`权限检查失败 [${id}/${plugin}/${command}]:未知错误`);
+export function Permission(type: 'Admin'| 'User' | 'Group'): MethodDecorator {
+    return function (target: any, propertyKey: string | symbol | undefined): void {
+        const actualPropertyKey = propertyKey!;
+        const fnName = `${target.constructor.name}.${actualPropertyKey.toString()}`;
+        const EconomyCommand: PermissionCommands = {
+            name: fnName,
+            type: type,
+        };
+        permissionCommands.push(EconomyCommand)
+    };
+}
+
+
+export async function IsPermission(user_id: number, plugin: string, command: string,IsGroup:boolean): Promise<boolean> {
+    const permission = await permissionCommands.find((permission: PermissionCommands) => permission.name === `${plugin}.${command}`);
+    if (permission) {
+        switch (permission.type) {
+            case 'Admin':
+                return await IsAdmin(user_id);
+            case 'User':
+                return await IsuserPermission(user_id, plugin, command);
+            case 'Group':
+                if(IsGroup){
+                    return await IsuserPermission(user_id, plugin, command);
+                }
+                return false
+            default:
+                botlogger.error(`未知权限类型 ${permission.type}`);
+            return false
         }
-        return false; // 出错时默认拒绝
     }
+    return true;
 }
-
-// 新增辅助函数
-function getUserPermission(id: number) {
-    // 确保默认配置层级存在
-    if (!PermissionConfig.users.default) {
-        PermissionConfig.users.default = { plugins: {} };
+async function IsuserPermission(user_id: number, plugin: string, command: string): Promise<boolean> {
+    if (await IsAdmin(user_id)) {
+        return true;
     }
-    if (!PermissionConfig.users.default.plugins) {
-        PermissionConfig.users.default.plugins = {};
+    if (!await getUserData(user_id.toString())) {
+        return false;
     }
-    
-    // 深度合并用户配置与默认配置
-    return {
-        plugins: {
-            ...PermissionConfig.users.default.plugins,
-            ...(PermissionConfig.users[id]?.plugins || {})
-        }
-    };
+    const permission = (await getuserPermissions(user_id.toString())).find((permission) => permission === `${plugin}.${command}`);
+    if (permission) {
+        return true;
+    } 
+    return false;
 }
-
-async function saveNewCommandConfig(id: number, plugin: string, command: string) {
-    try {
-        // 初始化用户配置树
-        PermissionConfig.users[id] = PermissionConfig.users[id] || { plugins: {} };
-        PermissionConfig.users[id].plugins[plugin] = PermissionConfig.users[id].plugins[plugin] || { commands: {} };
-        PermissionConfig.users[id].plugins[plugin].commands = PermissionConfig.users[id].plugins[plugin].commands || {};
-        
-        // 设置新命令默认权限
-        PermissionConfig.users[id].plugins[plugin].commands[command] = true;
-        
-        savePermission('permission', PermissionConfig);
-        botlogger.info(`自动创建 [${id}] 的 ${plugin}.${command} 命令权限`);
-    } catch (error) {
-        botlogger.error(`配置保存失败:${error instanceof Error ? error.stack : error}`);
+export async function getuserPermissions(userId: string): Promise<string[]> {
+    const userData = await getUserData(userId);
+    if (!userData) {
+        return [];
+    }else{
+        return userData.Permission;
     }
 }
-function getPluginConfig(userPermission: any, plugin: string) {
-    const PluginConfig = userPermission?.plugins[plugin]
-    return PluginConfig ?? PermissionConfig.users.default.plugins[plugin];
+export async function addPermission(userId: string, plugin: string, command: string) : Promise<boolean>{
+    const userPermission = await getuserPermissions(userId) || [];
+    const permission = permissionCommands.find((permission) => permission.name === plugin + '.' + command);
+    if (permission && !userPermission.includes(plugin + '.' + command)) {
+        userPermission.push(plugin + '.' + command);
+    }
+    const userData = await getUserData(userId)
+    if (!userData) {
+        return false;
+    }
+    userData.Permission = userPermission;
+    await saveUserData(userId,userData);
+    return true;
 }
-
-function getCommandPermission(pluginConfig: any, command: string) {
-    return pluginConfig?.commands[command];
+export async function removePermission(userId: string, plugin: string, command: string) : Promise<boolean>{
+    const userPermission = await getuserPermissions(userId) || [];
+    const permission = permissionCommands.find((permission) => permission.name === plugin + '.' + command);
+    if (permission && userPermission.includes(plugin + '.' + command)) {
+        userPermission.splice(userPermission.indexOf(plugin + '.' + command), 1);
+    }
+    const userData = await getUserData(userId)
+    if (!userData) {
+        return false;
+    }
+    userData.Permission = userPermission;
+    await saveUserData(userId,userData);
+    return true;
 }

+ 17 - 9
src/lib/Plugins.ts

@@ -21,6 +21,7 @@ import { IsPermission } from "./Permission.js";
 import { download } from "./download.js";
 import { commandList, economyCommands, paramMetadata } from "./decorators.js";
 import { addCoins, removeCoins } from "./economy.js";
+import { url } from "node:inspector";
 
 //WSSendParam
 const CMD_PREFIX = config?.cmd?.prefix ?? '#';
@@ -256,7 +257,7 @@ export async function runplugins() {
                     }
                     //指令权限检查
                     if (context.message_type === 'private') {
-                        if (!await IsPermission(context.user_id, easyplugin.id, command.cmd)) {
+                        if (!await IsPermission(context.user_id, easyplugin.id, command.fnName, false)) {
                             botlogger.info(`[${context.user_id}]无权限执行命令: ${CMD_PREFIX}${easyplugin.id} ${command.cmd}`);
                             context.quick_action([{
                                 type: 'text',
@@ -266,7 +267,7 @@ export async function runplugins() {
                         }
                     }
                     if (context.message_type === 'group') {
-                        if (!await IsPermission(context.group_id, easyplugin.id, command.cmd)) {
+                        if (!await IsPermission(context.sender.user_id, easyplugin.id, command.fnName,true)) {
                             botlogger.info(`[${context.group_id}]无权限执行命令: ${CMD_PREFIX}${easyplugin.id} ${command.cmd}`);
                             context.quick_action([{
                                 type: 'text',
@@ -306,7 +307,7 @@ 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 (!await IsPermission(context.user_id, plugin.id, command.fnName,false)) {
                         botlogger.info(`[${context.user_id}]无权限执行命令: ${CMD_PREFIX}${plugin.id} ${command.cmd}`);
                         context.quick_action([{
                             type: 'text',
@@ -316,7 +317,7 @@ export async function runplugins() {
                     }
                 }
                 if (context.message_type === 'group') {
-                    if (!await IsPermission(context.group_id, plugin.id, command.cmd)) {
+                    if (!await IsPermission(context.sender.user_id, plugin.id, command.fnName, true)) {
                         botlogger.info(`[${context.group_id}]无权限执行命令: ${CMD_PREFIX}${plugin.id} ${command.cmd}`);
                         context.quick_action([{
                             type: 'text',
@@ -382,9 +383,13 @@ async function handleCommand(context: PrivateFriendMessage | PrivateGroupMessage
                 let templateIsPath: boolean = true;
                 const templateHtml = result.template.html;
                 const templatePath = result.template.path;
+                const url= result.template.render.url;
+                
                 if (templateHtml) {
                     templateIsPath = false;
-                } else if (!templatePath || !fs.existsSync(templatePath)) {
+                } else if (url) {
+                    templateIsPath = false;
+                }else if (!templatePath || !fs.existsSync(templatePath)) {
                     throw new Error(`Template not found: ${templatePath}`);
                 }
 
@@ -395,6 +400,7 @@ async function handleCommand(context: PrivateFriendMessage | PrivateGroupMessage
                         template: templateIsPath ? templatePath : templateHtml,
                         templateIsPath,
                         data: result,
+                        url: url,
                         width: result.template.render?.width || 800,
                         height: result.template.render?.height || 600,
                         type: result.template.render?.type || 'png',
@@ -660,16 +666,18 @@ async function parseCommandParams(message: string, context: PrivateFriendMessage
                 throw new Error(`参数 <${name}> 是必需的,${msg}`);
             }
             // 检查参数类型
-            let msgtype = paramArgs[index].type
+            if (optional && paramArgs[index] === undefined) {
+                params[index] = paramData.defaultValue;
+            }
+            
+            let msgtype = paramArgs[index]?.type ?? paramData.defaultValue?.type
             if (type === msgtype) {
                 params[index] = paramArgs[index];
             }else{
                 throw new Error(`参数 <${name}> 类型错误,${msg}`);
             }
 
-            if (optional && paramArgs[index] === undefined) {
-                params[index] = paramData.defaultValue;
-            }
+            
         }
     }
     // 添加 context 参数

+ 14 - 4
src/lib/Puppeteer.ts

@@ -9,7 +9,7 @@ export class HtmlImg {
     async init() {
         if (!this.browser) {
             const options: PuppeteerLaunchOptions = {
-                headless: true,
+                headless: true, // 无头模式,可根据需要设置为false,
                 args: [
                     '--no-sandbox',
                     '--disable-setuid-sandbox',
@@ -21,6 +21,7 @@ export class HtmlImg {
     }
 
     async render(options: {
+        url?: string;
         template: string;
         templateIsPath?: boolean;
         data: any;
@@ -34,6 +35,7 @@ export class HtmlImg {
         try {
             await this.init();
             const {
+                url,
                 template,
                 templateIsPath = true,
                 data,
@@ -49,14 +51,22 @@ export class HtmlImg {
             const templateContent = templateIsPath ? fs.readFileSync(template, 'utf-8') : template;
 
             // 渲染HTML
-
-            const html = art.render(templateContent, data);
+            let html = '';
+            if (templateContent) {
+                html = art.render(templateContent, data);
+            }
             // 计算高度
 
             // 创建页面
             const page = await this.browser!.newPage();
             await page.setViewport({ width, height });
-            await page.setContent(html, { waitUntil: 'networkidle0' });
+            // 设置页面内容
+            if (url) {
+                await page.goto(url);
+                await page.waitForNetworkIdle();
+            }else{
+                await page.setContent(html, { waitUntil: 'networkidle0' });
+            }
             // 获取document.body.scrollHeight
             
             const bodyheight = await page.evaluate(() => document.body.scrollHeight)  as number;

+ 2 - 9
src/lib/config.ts

@@ -13,15 +13,8 @@ export function saveConfig(file: string, data: any): void {
     const configPath = path.join(__dirname, `../config/${file}.yml`);  // 保持源码与编译后一致
     fs.writeFileSync(configPath, yaml.dump(data));
 }
-export async function loadPermission(){
-    const configPath = path.join(__dirname, `../../data/permission.yml`);  // 保持源码与编译后一致
-    return await yaml.load(fs.readFileSync(configPath, 'utf8')) as any;
-}
-export function savePermission(file: string, data: any): void {
-    const configPath = path.join(__dirname, `../../data/permission.yml`);  // 保持源码与编译后一致
-    fs.writeFileSync(configPath, yaml.dump(data));
-}
+
 export const Botconfig = await loadConfig('bot');
-export const PermissionConfig = await loadPermission();
+export const PermissionConfig = await loadConfig('permission');
 export const load = await loadConfig('load')
 export const economy = await loadConfig('economy')

+ 18 - 19
src/lib/economy.ts

@@ -6,17 +6,22 @@ import { economy } from "./config.js";
 
 export function addCoins(userId: string, amount: number, reason: string): void {
     const userData = getUserData(userId);
-    userData.economy.coins += amount;
-    userData.economy.logs.unshift({
-        type: 'add',
-        amount: amount,
-        reason: reason,
-        date: formatDate(new Date())
-    });
-    saveUserData(userId, userData);
+    if(userData){
+        userData.economy.coins += amount;
+        userData.economy.logs.unshift({
+            type: 'add',
+            amount: amount,
+            reason: reason,
+            date: formatDate(new Date())
+        });
+        saveUserData(userId, userData);
+    }
 }
 export function removeCoins(userId: string, amount: number, reason: string): void {
     const userData = getUserData(userId);
+    if (!userData) {
+        throw new Error(`未找到用户数据`);
+    }
     if (userData.economy.coins < amount) {
         throw new Error(`${economy.name}不足,需要${amount}${economy.currency},拥有${userData.economy.coins}${economy.currency}`);
     }
@@ -29,19 +34,12 @@ export function removeCoins(userId: string, amount: number, reason: string): voi
     });
      saveUserData(userId, userData);
 }
-export function getUserData(userId: string): UserData {
+export function getUserData(userId: string): UserData|null {
     if (!fs.existsSync(`${economy.data.path}`)) {
         throw new Error(`未找到用户数据目录,请检查配置文件`);
     }
     if (!userId) {
-        return {
-            userId: '',
-            economy: {
-                coins: 0,
-                logs: []
-            },
-            props: []
-        }
+        return null
     }
     if (!fs.existsSync(`${economy.data.path}/${userId}.json`)) {
         const newUserData: UserData = {
@@ -51,7 +49,8 @@ export function getUserData(userId: string): UserData {
                 logs: [],
                 
             },
-            props: []
+            props: [],
+            Permission: []
         };
         fs.writeFileSync(`${economy.data.path}/${userId}.json`, JSON.stringify(newUserData, null, 4));
         return newUserData;
@@ -59,7 +58,7 @@ export function getUserData(userId: string): UserData {
     const userData = JSON.parse(fs.readFileSync(`${economy.data.path}/${userId}.json`, 'utf-8')) as UserData;
     return userData;
 }
-export function  saveUserData(userId: string, userData: UserData): void {
+export function saveUserData(userId: string, userData: UserData): void {
     if (!fs.existsSync(`${economy.data.path}`)) {
         throw new Error(`未找到用户数据目录,请检查配置文件`);
     }

+ 14 - 3
src/lib/prop.ts

@@ -1,6 +1,6 @@
 // 添加金币相关的fn装饰器
 
-import { Prop } from "../interface/prop.js";
+import { Prop, UserProp } from "../interface/prop.js";
 import { getUserData, saveUserData } from "./economy.js";
 import botlogger from "./logger.js";
 
@@ -38,8 +38,13 @@ export function getProp(fnName: string): Prop | undefined {
     return Props.get(fnName);
 }
 
-export async function getuserProp(userId: string): Promise<Prop[]> {
-    return await getUserData(userId).props;
+export async function getuserProp(userId: string): Promise<UserProp[]> {
+    const userData = await getUserData(userId);
+    if (!userData) {
+        return [];
+    }else{
+        return userData.props;
+    }
 }
 // 减少道具数量
 export async function reduceProp(userId: string, propId: string, Num: number = 1): Promise<boolean> {
@@ -52,6 +57,9 @@ export async function reduceProp(userId: string, propId: string, Num: number = 1
     }
     // 保存道具数据
     const userData = await getUserData(userId)
+    if (!userData) {
+        return false;
+    }
     userData.props = userProp;
     await saveUserData(userId,userData);
     return true;
@@ -72,6 +80,9 @@ export async function addProp(userId: string, propId: string, Num: number = 1):
     }
     // 保存道具数据
     const userData = await getUserData(userId)
+    if (!userData) {
+        return false;
+    }
     userData.props = userProp;
     await saveUserData(userId,userData);
     return true;

+ 2 - 2
src/plugins/PluginsFile.ts

@@ -141,14 +141,14 @@ export class PluginsFile {
                 await qqBot.upload_group_file({
                     group_id: Number(context.group_id),
                     file: 'data:file;base64,' + file,
-                    name: pluName + '.ts'
+                    name: pluName?.data?.text + '.ts'
                 })
 
             } else {
                 await qqBot.upload_private_file({
                     user_id: Number(context.sender.user_id),
                     file: 'data:file;base64,' + file,
-                    name: pluName + '.ts'
+                    name: pluName?.data?.text + '.ts'
                 })
             }
 

+ 2 - 2
src/plugins/log.ts

@@ -73,14 +73,14 @@ export class Botlog {
                 await qqBot.upload_group_file({
                     group_id: Number(context.group_id),
                     file: 'data:file;base64,' + file,
-                    name: logName+'.log'
+                    name: logName.data.text+'.log'
                 })
 
             } else {
                 await qqBot.upload_private_file({
                     user_id: Number(context.sender.user_id),
                     file: 'data:file;base64,' + file,
-                    name: logName+'.log'
+                    name: logName.data.text+'.log'
                 })
             }
 

+ 1 - 1
src/plugins/prop.ts

@@ -111,9 +111,9 @@ export class Propplu {
                 if(Allprop.propId === prop.propId){
                     Allprop.Num=prop.Num
                     p.push(Allprop) 
+                    s += `名称:${Allprop.propName}---描述:${Allprop.describe}---数量:${prop.Num}\n`;
                 }
             })
-            s += `名称:${prop.propName}---描述:${prop.describe}---数量:${prop.Num}\n`;
         })
         const __dirname = path.dirname(fileURLToPath(import.meta.url)); //获取当前文件的目录名
         return {

+ 11 - 0
src/plugins/test.ts

@@ -11,6 +11,7 @@ import { prop } from '../lib/prop.js';
 import * as fs from 'fs'
 import { GroupMessage, PrivateFriendMessage, PrivateGroupMessage } from 'node-napcat-ts/dist/Interfaces.js';
 import { Receive } from 'node-napcat-ts';
+import { Permission } from '../lib/Permission.js';
 async function convertImageToBase64(filePath: string): Promise<string> {
     try {
       const fileData = await fs.promises.readFile(filePath);
@@ -85,6 +86,14 @@ export class test {
         };
     }
 
+    @Permission('Group')
+    @runcod(['tp',"权限测试"],"权限测试,执行此指令返回对应权限")//命令装饰器,用于注册命令
+    async Permission(): Promise<any> {
+        return 'Permission测试'
+    }
+
+
+
     @runcod(["remove"], "移除金币")//命令装饰器,用于注册命令
     @coins(
         10,//金币数量
@@ -93,10 +102,12 @@ export class test {
     async remove(){
         return `移除成功`;
     }
+
     @schedule('* */30 * * * *') // 每30分钟执行一次
     async testschedule() {
         // botlogger.info("定时任务测试")
     }
+
     @prop(
         "testProp",//道具id
         "称号卡",//道具名称