Loading... ## 一、前言 盖亚迭代到这个版本,应该算是3.0了,这个版本把原来的结构进行了解构,最核心的部分仅负责用户数据的维护,接入方式也从原来的硬接入(代码)改成了软(接口)接入,这意味着,aut插件支持的所有语言都能完美的接入盖亚。 ## 二、更新内容 * [X] 新增一个用户积分属性`giftCoin`:这个属性相当于某东平台的某豆,这能让用户充值的余额积分与激励活动(例如签到等活动)发放的积分区分开来,并且在进行积分消费时,支持自定义这个积分(`giftCoin`)的抵扣比例。 * [X] 更改接入方式:原来的接入方式只适用与es5插件,这个版本为了提供全语言支持,使用了aut的微服务功能,所有aut插件支持的语言都能通过网络请求来调用盖亚的服务。 * [X] 系统的解构:新版本把原来的集用户管理、充值卡密管理、基础交互一体的插件进行了解构,你可以根据你的需要来进行安装。 ## 三、注意事项 1. autMan版本:`>=3.3.5` 2. 这个版本并不适配以前版本的盖亚数据,如需使用,请先完成数据转移再使用。 ## 四、系列插件介绍 ### 1.盖亚RT(必装) #### 介绍 核心文件,提供盖亚的服务接口给其他插件调用。插件开发者,可查看下面的接口文档,把盖亚接入到你的插件中。 #### 接口文档 <button class=" btn m-b-xs btn-info " onclick="window.open('https://apifox.com/apidoc/shared-842f4085-e82e-4ede-be47-f2ac39c1c751','_blank')">盖亚微服务 接口文档</button>你可以结合下面的演示代码来进行开发 --- ### 2.盖亚_卡密(自行决定) #### 介绍 以前盖亚版本内置的充值卡密系统,如今你可以通过上面`盖亚_微服务`中的接口写插件实现你自己的充值卡密系统,文章的底部有演示代码。非必装插件,你如果并不需要使用充值卡密,你完全可以不装。 #### 功能(指令) 1. 管理员生成卡密:`sm:gaiaCDK:generateCDK` 3. 用户使用卡密:`(GAIA)[A-Z0-9]{11}` --- ### 4.盖亚_交互(自行决定) #### 介绍 这是我根据日常使用,写的较为简易的机器人交互逻辑,一样的你可以通过上面`盖亚_微服务`中的接口写插件实现你自己的交互系统,文章的底部有开源代码供你参考。 #### 功能(指令) 1. 查询账号积分情况:`sm:gaiaInteraction:query` 3. 每日签到:`sm:gaiaInteraction:signIn` 4. 赞赏码充值:`sm:gaiaInteraction:WXQRRecharge` ## 五、接入Demo 下面是`盖亚_卡密`和`盖亚_交互`的初稿代码,均通过调用盖亚_微服务接入盖亚。 ```javascript //#region ==================云市场元数据================== //[title: 盖亚_卡密] //[author: sirhexs] //[language: nodejs] //[class: 工具类] //[service: 46883409] 售后联系方式 //[version: 1.0.0]版本号 //[public:false] 是否发布?值为true或false,不设置则上传aut云时会自动设置为true,false时上传后不显示在市场中,但是搜索能搜索到,方便开发者测试 //[price: 99999999] 上架价格 //[platform: qq,qb,wx,tb,tg,web] 适用的平台 //[description: 全新版本盖亚] 使用方法尽量写具体 //[open_source: false]是否开源 //#endregion //#region ==================功能元数据================== //[disable: false] 禁用开关,true表示禁用,false表示可用 //[priority: 10] 优先级,数字越大表示优先级越高 //[admin: false] 是否为管理员指令 //[rule: ^更新命令_盖亚_卡密$] //[rule: ^sm:gaiaCDK:generateCDK$] //[rule: ^(GAIA)[A-Z0-9]{11}$] //#endregion //#region ==================依赖部分================== // 引入中间件模块 const { Sender, getSenderID, bucketSet, bucketGet, port } = require('./middleware.js'); // 获取发送者ID const senderID = getSenderID(); // 从中间件中获取autMan的内置函数 const { getMessage, reply, getImtype, getUserID, listen, getUserName, isAdmin } = new Sender(senderID); // 引入依赖 const axios = require('axios'); //#endregion // 全局变量 var CDKDB = 'GAIA_CDKS' // 定义插件命令的注册数据 const registerData = { "name": "盖亚_卡密", // 一般为插件的名称 "identifier": "sm:gaiaCDK", // 插件标识,为了避免插件名重复,建议使用 author:pluginName 的命名方式 "version": 1, // 版本,阿拉伯数字 "keys": [ // 命令数据 { "title": "生成卡密命令", // 命令的标题 "rule": "sm:gaiaCDK:generateCDK", // 命令在插件代码中固定的指令,建议使用 author:pluginName:rule 的命名方式,避免指令与其他作者重复 "description": "管理员触发生成卡密的指令", // 命令的说明,能让用户更容易理解命令的作用 "data": [ // 默认的触发指令 "生成卡密", ], "request": true // 是否为必填项,如为必填项 data中至少要有一个元素,否则会注册失败 } ] }; // 匿名函数启动主线程 (async () => { const msg = await getMessage() // 命令判断 // 初始化命令 在【自定义指令】插件中完成注册 if (msg === '更新命令_盖亚_卡密' && await isAdmin()) { // 获取端口 const localPort = await port() // 二、完成数据的注册 使用网络请求来完成 const customCommandToken = await bucketGet('CustomCommand_Config', 'token') // 获取鉴权token const url = `http://localhost:${localPort}/customCommand?fun=register&token=${customCommandToken}` console.log(url) const res = await axios({ // 网络请求函数 url: url, // 调用【自定义命令mod】中写好的注册微服务 headers: undefined, method: "post", // 一定是post请求 data: registerData, // 请求体,就传上面写好的注册数据就行 }) if (res) { await reply(res.data.message)// 给会话用户发送信息 return } reply('未安装【自定义命令mod】插件')// 给会话用户发送信息 return } // 生成卡密 if (msg === 'sm:gaiaCDK:generateCDK' && await isAdmin()) { await reply('请选择生成的积分类型\n[1] 余额\n[2] 礼币\n输入[]中的阿拉伯数字即可') const choics = await listen(60000) if (choics == 'q') { await reply('退出成功') return } if (choics == '') { await reply('输入超时') return } if (choics != '1' && choics != '2') { await reply('输入错误') return } await reply('请输入单张卡密的额度') const amount = await listen(60000) if (amount == 'q') { await reply('退出成功') return } if (amount == '') { await reply('输入超时') return } if (parseInt(amount) < 0) { await reply('输入错误') return } await reply('请输入要生成的张数') const num = await listen(60000) if (num == 'q') { await reply('退出成功') return } if (num == '') { await reply('输入超时') return } if (parseInt(num) < 0) { await reply('输入错误') return } const gaia_cdk = new GAIA_CDK() const cdks = await gaia_cdk.generateCDKs(choics == '1' ? 'balance' : 'giftCoin', num, amount) await reply(cdks.join('\n')) } // 卡密充值 if (/^(GAIA)[A-Z0-9]{11}$/.test(msg)) { const gaia_cdk = new GAIA_CDK() const res = await gaia_cdk.useCDK(await getImtype(), await getUserID(), msg, await getUserName()) console.log(res) if (res.code != 200) { await reply(res.msg) return } await reply(`充值${res.data.cdkData.value}${res.data.cdkData.type == 'balance' ? '余额' : '礼币'}成功`) // 接下来可以写管理员通知,自行添加即可 return } })() /** * @description: cdk类 */ class GAIA_CDK { /** * @description: 生成卡密号 * @return {string} 卡密号 */ generateCDKNumber() { //字符数组 let arr = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z' ]; //GAIA用于识别卡密 let str = 'GAIA' //随机字符 for (let i = 0; i < 11; i++) { let pos = Math.round(Math.random() * (arr.length - 1)); str += arr[pos]; } return str; } /** * @description: * @param {*} type 积分类型 'balance' | 'giftCoin' * @param {number} num 数量 * @param {number} amount 单卡额度 * @return {*} */ async generateCDKs(type, num, amount) { let CDKs = [] for (let i = 0; i < num; i++) { let cdkNumber = this.generateCDKNumber(); let keyData = { "isUsed": false, "value": parseInt(amount), "type": type, "creationTime": getCurrentTime(), "usedTime": '', "userName": '', "userID": '' }; //存进桶 await bucketSet(CDKDB, cdkNumber, JSON.stringify(keyData)) CDKs.push(cdkNumber) } return CDKs } /** * @description: 卡密检测 */ async checkCDK(cdkNumber) { // 返回 let msg = { "code": -1, "msg": '未知错误', "data": {} } // 获取cdk数据 let cdkDataStr = await bucketGet(CDKDB, cdkNumber) let cdkData = cdkDataStr ? JSON.parse(cdkDataStr) : { "isUsed": true, "value": -1, "type": '', "creationTime": '', "usedTime": '', "userName": '', "userID": '' } // 不存在卡密 if (cdkData.value == -1) { msg.code = 100 msg.msg = '该卡密不存在' return msg } msg.data = cdkData // 卡密可用 if (!cdkData.isUsed) { msg.code = 200 msg.msg = '可用' return msg } // 卡密已被使用 if (cdkData.isUsed && cdkData.usedTime != '') { msg.code = 101 msg.msg = '该卡密已被使用' return msg } return msg } /** * @description: 使用卡密 * @param {*} imType 社交渠道 * @param {*} uid 用户名 * @param {*} cdkNumber 卡密号 * @param {*} name 网名 * @return {*} */ async useCDK(imType, uid, cdkNumber, name = "") { let res = await this.checkCDK(cdkNumber) if (res.code != 200) { return res } let cdkData = res.data cdkData['isUsed'] = true cdkData['usedTime'] = getCurrentTime() cdkData['userName'] = name cdkData['userID'] = uid const gaia_request = new GAIA_Request(imType, uid) let result = await gaia_request.request(cdkData.type == 'balance' ? 'addBalance' : "addGiftCoin", 'post', { number: cdkData.value, description: `使用卡密-${cdkNumber}` }) if (!result) { let msg = { "code": 102, "msg": '充值失败', "data": null } return msg } //修改卡密详细 const order = result.data await bucketSet(CDKDB, cdkNumber, JSON.stringify(cdkData)) let msg = { "code": 200, "msg": '充值成功', "data": { cdkData: cdkData, order: order } } return msg } } /** * @description: 盖亚微服务请求封装 */ class GAIA_Request { constructor(imType, uid) { this.imType = imType this.uid = uid } /** * @description: 盖亚微服务的封装 * @param {*} fun 调用的方法 * @param {*} method 请求类型 get|post * @param {*} body 请求体 * @return {*} */ async request(fun, method, body) { const { bucketGet, port } = require('./middleware.js'); // 获取参数 const localPort = await port() const token = await bucketGet('GAIA_Config', 'token') const config = { url: `http://localhost:${localPort}/gaia?fun=${fun}&imType=${this.imType}&uid=${this.uid}&token=${token}`,//地址 method: method ? method : "get",//网络请求方法get,post,put,delete data: body ? body : "", timeout: 30000//单位为毫秒ms,也可以都小写timeout } try { let res = await axios(config) return res.data } catch (error) { console.log(error); return false } } } /** * @description: 以yyyy-MM-dd hh:mm:ss格式返回当前的时间 */ function getCurrentTime() { const now = new Date(); const year = now.getFullYear(); const month = String(now.getMonth() + 1).padStart(2, '0'); // 月份从0开始,所以要加1 const day = String(now.getDate()).padStart(2, '0'); const hours = String(now.getHours()).padStart(2, '0'); const minutes = String(now.getMinutes()).padStart(2, '0'); const seconds = String(now.getSeconds()).padStart(2, '0'); return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; } ``` ```javascript //#region ==================云市场元数据================== //[title: 盖亚_交互] //[author: sirhexs] //[language: nodejs] //[class: 工具类] //[service: 46883409] 售后联系方式 //[version: 1.0.0]版本号 //[public:false] 是否发布?值为true或false,不设置则上传aut云时会自动设置为true,false时上传后不显示在市场中,但是搜索能搜索到,方便开发者测试 //[price: 99999999] 上架价格 //[platform: qq,qb,wx,tb,tg,web] 适用的平台 //[description: 全新版本盖亚] 使用方法尽量写具体 //[open_source: false]是否开源 // #endregion //#region ==================功能元数据================== //[disable: false] 禁用开关,true表示禁用,false表示可用 //[priority: 10] 优先级,数字越大表示优先级越高 //[admin: false] 是否为管理员指令 //[rule: ^更新命令_盖亚_交互$] //[rule: ^sm:gaiaInteraction:query$] //[rule: ^sm:gaiaInteraction:signIn$] //[rule: ^sm:gaiaInteraction:WXQRRecharge$] //#endregion //#region ==================配参数据================== // [param: {"spliter":true}] // [param: {"required":false,"key":"sm_gaia_basis_config.WXQRURL","placeholder":"","name":"赞赏码图片直链","desc":""}] // [param: {"spliter":true}] // [param: {"required":false,"key":"sm_gaia_basis_config.prohibitPrivateChatCheckIn","placeholder":"","bool":true,"name":"禁止私聊签到","desc":""}] // [param: {"required":false,"key":"sm_gaia_basis_config.signInGroupChatWhitelist","placeholder":"","name":"签到群聊白名单","desc":"不填则不进行限制,使用英文逗号分割"}] // [param: {"required":false,"key":"sm_gaia_basis_config.lowerLimitOfIntegration","placeholder":"","name":"积分下限","desc":"签到随机赠送积分的最低值(包含)"}] // [param: {"required":false,"key":"sm_gaia_basis_config.upperLimitOfIntegration","placeholder":"","name":"积分上限","desc":"签到随机赠送积分的最高值(包含)"}] // [param: {"spliter":true}] // [param: {"required":false,"key":"sm_gaia_basis_config.debug","placeholder":"","bool":true,"name":"开启调试","desc":"开启调试后,日志会打印插件的调试内容。如非排查bug等情况,不建议开启,会增加无用的日志内容。"}] // [event: wx-received-sent-pay]这个是微信收到或发送支付操作,当使用waitPay()函数时会优先waitPay()函数捕获支付事件,此处将无法捕获此事件 //#endregion //#region ==================依赖部分================== // 检测axios安装情况 let dtest = testDependency("axios"); if (!dtest) { // 安装axios let res = installDependency("axios"); console.log(res); } // 引入中间件模块 const { Sender, getSenderID, port, bucketGet, bucketSet, notifyMasters } = require('./middleware.js'); // 获取发送者ID const senderID = getSenderID(); // 从中间件中获取autMan的内置函数 const { getMessage, reply, getImtype, getUserID, atWaitPay, replyImage, waitPay,isAdmin } = new Sender(senderID); // 引入依赖 const axios = require('axios'); //#endregion // 定义插件命令的注册数据 const registerData = { "name": "盖亚_交互", // 一般为插件的名称 "identifier": "sm:gaiaInteraction", // 插件标识,为了避免插件名重复,建议使用 author:pluginName 的命名方式 "version": 1, // 版本,阿拉伯数字,用于判断是否需要更新指令注册数据 "keys": [ // 命令数据 { "title": "查余额的指令", // 命令的标题 "rule": "sm:gaiaInteraction:query", // 命令在插件代码中固定的指令,建议使用 author:pluginName:rule 的命名方式,避免指令与其他作者重复 "description": "", // 命令的说明,能让用户更容易理解命令的作用 "data": [ // 默认的触发指令 "查余额", "查询" ], "request": false // 是否为必填项,如为必填项 data中至少要有一个元素,否则会注册失败 }, { "title": "签到的指令", "rule": "sm:gaiaInteraction:signIn", "description": "", "data": [ "签到", "打卡" ], "request": false }, { "title": "充值的指令", "rule": "sm:gaiaInteraction:WXQRRecharge", "description": "设置触发赞赏码充值流程的指令", "data": [ "充值", ], "request": false } ] }; var imType, uid // 匿名函数启动主线程 (async () => { const msg = await getMessage() imType = await getImtype() uid = await getUserID() // 命令初始化 if (msg === '更新命令_盖亚_交互' && await isAdmin()) { // 获取端口 const localPort = await port() // 二、完成数据的注册 使用网络请求来完成 const customCommandToken = await bucketGet('CustomCommand_Config', 'token') // 获取鉴权token const url = `http://localhost:${localPort}/customCommand?fun=register&token=${customCommandToken}` console.log(url) const res = await axios({ // 网络请求函数 url: url, // 调用【自定义命令mod】中写好的注册微服务 headers: undefined, method: "post", // 一定是post请求 data: registerData, // 请求体,就传上面写好的注册数据就行 }) if (res) { reply(res.data.message)// 给会话用户发送信息 return } reply('未安装【自定义命令mod】插件')// 给会话用户发送信息 return } // 查余额 if (msg === 'sm:gaiaInteraction:query') { const res = await request('getUserData') console.log(res) if (res) { reply(`余额:${res.data.balance}\n积分:${res.data.giftCoin}`) return } reply('系统错误,请联系管理员') return } // 签到 if (msg === 'sm:gaiaInteraction:signIn') { // 记录数据库的读写 async function read() { let attendanceRecordData = await bucketGet('sm_gaia_basis_attendanceRecord', timeFmt()) || `{"time":0,"data":[]}` let attendanceRecord = JSON.parse(attendanceRecordData) return attendanceRecord } async function write(data) { let attendanceRecord = await read() if (attendanceRecord.time == data.time) { data.time = Date.now() await bucketSet('sm_gaia_basis_attendanceRecord', timeFmt(), JSON.stringify(data)) return true } else { return false } } let attendanceRecord = await read() let attendanceRecord_bucket = attendanceRecord let data = attendanceRecord.data // 查询是否签到 // for (let i = 0; i < data.length; i++) { // if (data[i].type == await getImtype() && data[i].uid == await getUserID()) { // await reply("抱歉,请勿重复签到") // return // } // } // 获取配置 let min = await bucketGet("sm_gaia_basis_config", "lowerLimitOfIntegration") || 0 let max = await bucketGet("sm_gaia_basis_config", "upperLimitOfIntegration") || 0 let integral = getRandomInt(min, max) let record = { type: await getImtype(), uid: await getUserID(), integral: integral, time: Date.now() } attendanceRecord.data.unshift(record) let recordStatus = await write(attendanceRecord) let addSatus = await request('addGiftCoin', 'post', { number: integral, description: '签到' }) if (recordStatus && addSatus) { await reply(`签到成功,+${addSatus.data.giftCoin}🌸`) return } await reply('系统错误,请联系管理员') return } // 充值(赞赏码) if (msg === 'sm:gaiaInteraction:WXQRRecharge') { let WXQRURL = await bucketGet("sm_gaia_basis_config", "WXQRURL") if (await atWaitPay()) { await reply('赞赏系统正在运行,请稍等再试') return } if (WXQRURL == '') { await reply('获取配置失败') await notifyMasters("未设置赞赏二维码直链,请前往插件配参页面进行填写") return } await replyImage(WXQRURL)//发送图片 await reply('请在2分钟内使用【微信APP】完成打赏') let pay = await waitPay("q", 120000) if (pay == 'timeout') { await reply('超时了,自动退出') return } if (pay == 'q') { await reply('退出成功') return } print(JSON.stringify(pay)) // {"Time":"2025-01-13T17:21:24.170470152+08:00","Type":"微信赞赏","FromWxid":"","FromName":"Sirhexs","Money":1} // {"time":"2025-01-15T15:12:41.314858606+08:00","type":"微信赞赏","from_wxid":"","from_name":"Sirhexs","money":1} 最新版本 const res = await request('addBalance', 'post', { number: pay.money * 100, description: `微信赞赏¥${pay.money}`, }) if (res && res.code == 200) { await reply(`充值${pay.money * 100}余额成功`) return } await reply('充值失败,请联系管理员') return } return })() /** * @description: 检查依赖情况 * @param {*} Dependency 依赖名 * @return {*} */ function testDependency(Dependency) { try { if (Dependency.includes("@")) { Dependency = Dependency.match(/(.*?)@/)[1] } console.log(Dependency); require(Dependency) return true } catch (e) { return false } } /** * @description: 安装依赖 * @param {*} Dependency 依赖名 * @return {*} */ function installDependency(Dependency) { const { spawnSync } = require('child_process') try { let installRes = spawnSync("npm", ["install", Dependency, "--legacy-peer-deps", "--registry=https://registry.npmmirror.com"]) console.log(Buffer.from(installRes.stdout).toString("utf-8")); if (Buffer.from(installRes.stdout).toString("utf-8").includes("ERR!")) { return false } return true } catch (e) { console.log(e); return false } } /** * @description: 格式化时间 */ function timeFmt() { const date = new Date(); const year = date.getFullYear(); const month = String(date.getMonth() + 1).padStart(2, '0'); // 月份从0开始 const day = String(date.getDate()).padStart(2, '0'); return `${year}-${month}-${day}`; } /** * @description: 生成范围内的随机整数 * @param {*} min 最小值 * @param {*} max 最大值 * @return {*} */ function getRandomInt(min, max) { min = Math.ceil(min); max = Math.floor(max); var range = max - min + 1; var random = Math.pow(Math.random(), 2) * range; return Math.floor(random) + min; } /** * @description: 盖亚微服务的封装 * @param {*} fun 调用的方法 * @param {*} method 请求类型 get|post * @param {*} body 请求体 * @return {*} */ async function request(fun, method, body) { // 获取参数 const localPort = await port() const token = await bucketGet('GAIA_Config', 'token') const config = { url: `http://localhost:${localPort}/gaia?fun=${fun}&imType=${imType}&uid=${uid}&token=${token}`,//地址 method: method ? method : "get",//网络请求方法get,post,put,delete data: body ? body : "", timeout: 30000//单位为毫秒ms,也可以都小写timeout } try { let res = await axios(config) return res.data } catch (error) { console.log(error); return false } } /** * @description: 打印调试日志 * @param {*} text 日志 * @return {*} */ function print(...arg) { if (bucketGet('sm_gaia_basis_config', 'debug') === "true") { console.log(arg) } } ``` 最后修改:2025 年 02 月 24 日 © 允许规范转载 赞 1 如果觉得我的文章对你有用,请随意赞赏
3 条评论
文章紧扣主题,观点鲜明,展现出深刻的思考维度。
以终为始的思考方式为行业指明方向。
文章结构紧凑,层次分明,逻辑严密,让人一读即懂。