diff --git a/CHANGELOG.md b/CHANGELOG.md index eeaebe5..6b8fbd8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# 3.1.2 + +* 新增`#绑定用户`命令 + * 可将其他QQ绑定至当前用户,以打通多个用户,子用户使用主用户的CK与UID等信息 + * 同时也可绑定其他平台的用户,例如频道、微信等。如需链接至其他平台需使用TRSS-Yunzai或Lain-plugin + * 部分命令可能无法识别绑定后的主用户,如遇问题可反馈 + # 3.1.1 * 初步适配原神4.0版本,增加对应资源及信息展示,感谢**Ca(HCO₃)₂**、**@touchscale**、**@teriri7** @@ -7,10 +14,10 @@ # 3.1.0 * 重构CK与UID管理逻辑 - * 支持多UID绑定,可绑定多个UID并进行切换 - * 支持原神与星铁UID共存,可针对查询命令分配对应UID - * 新增`#删除uid1`命令,可对`#uid`列表内的绑定UID进行删除 - * 使用sqlite进行ck与uid存储 + * 支持多UID绑定,可绑定多个UID并进行切换 + * 支持原神与星铁UID共存,可针对查询命令分配对应UID + * 新增`#删除uid1`命令,可对`#uid`列表内的绑定UID进行删除 + * 使用sqlite进行ck与uid存储 * 底层对星铁查询进行支持 **@cvs** # 3.0.2 @@ -29,4 +36,4 @@ # 3.0.0 -* Yunzai-Bot \ No newline at end of file +* Yunzai-Bot diff --git a/package.json b/package.json index efb1812..7fa554e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "miao-yunzai", - "version": "3.1.1", + "version": "3.1.2", "author": "Yoimiya-Kokomi, Le-niao", "description": "QQ group Bot", "main": "app.js", @@ -52,4 +52,4 @@ "#miao": "./plugins/miao-plugin/components/index.js", "#miao.models": "./plugins/miao-plugin/models/index.js" } -} \ No newline at end of file +} diff --git a/plugins/genshin/apps/user.js b/plugins/genshin/apps/user.js index 4d85984..72dd4a0 100644 --- a/plugins/genshin/apps/user.js +++ b/plugins/genshin/apps/user.js @@ -52,6 +52,14 @@ export class user extends plugin { { reg: '^#\\s*(检查|我的)*ck(状态)*$', fnc: 'checkCkStatus' + }, + { + reg: '^#(接受)?绑定(主|子)?用户(\\[\\w+\\]){0,2}$', + fnc: 'bindNoteUser' + }, + { + reg: '^#(删除绑定|取消绑定|解除绑定|解绑|删除|取消)(主|子)用户$', + fnc: 'bindNoteUser' } ] }) @@ -175,4 +183,8 @@ export class user extends plugin { async checkCkStatus () { await this.User.checkCkStatus() } + + async bindNoteUser () { + await this.User.bindNoteUser() + } } diff --git a/plugins/genshin/model/gsCfg.js b/plugins/genshin/model/gsCfg.js index facd6d7..17939d3 100644 --- a/plugins/genshin/model/gsCfg.js +++ b/plugins/genshin/model/gsCfg.js @@ -1,15 +1,13 @@ import YAML from 'yaml' import chokidar from 'chokidar' import fs from 'node:fs' -import { promisify } from 'node:util' import lodash from 'lodash' import MysInfo from './mys/mysInfo.js' import NoteUser from './mys/NoteUser.js' -import MysUser from './mys/MysUser.js' /** 配置文件 */ class GsCfg { - constructor() { + constructor () { this.isSr = false /** 默认设置 */ this.defSetPath = './plugins/genshin/defSet/' @@ -25,7 +23,7 @@ class GsCfg { this.ignore = ['mys.pubCk', 'gacha.set', 'bot.help', 'role.name'] } - get element() { + get element () { return { ...this.getdefSet('element', 'role'), ...this.getdefSet('element', 'weapon') } } @@ -33,12 +31,12 @@ class GsCfg { * @param app 功能 * @param name 配置文件名称 */ - getdefSet(app, name) { + getdefSet (app, name) { return this.getYaml(app, name, 'defSet') } /** 用户配置 */ - getConfig(app, name) { + getConfig (app, name) { if (this.ignore.includes(`${app}.${name}`)) { return this.getYaml(app, name, 'config') } @@ -52,7 +50,7 @@ class GsCfg { * @param name 名称 * @param type 默认跑配置-defSet,用户配置-config */ - getYaml(app, name, type) { + getYaml (app, name, type) { let file = this.getFilePath(app, name, type) let key = `${app}.${name}` @@ -72,13 +70,16 @@ class GsCfg { return this[type][key] } - getFilePath(app, name, type) { - if (type == 'defSet') return `${this.defSetPath}${app}/${name}.yaml` - else return `${this.configPath}${app}.${name}.yaml` + getFilePath (app, name, type) { + if (type == 'defSet') { + return `${this.defSetPath}${app}/${name}.yaml` + } else { + return `${this.configPath}${app}.${name}.yaml` + } } /** 监听配置文件 */ - watch(file, app, name, type = 'defSet') { + watch (file, app, name, type = 'defSet') { let key = `${app}.${name}` if (this.watcher[type][key]) return @@ -96,7 +97,7 @@ class GsCfg { } /** 读取所有用户绑定的ck */ - async getBingCk(game = 'gs') { + async getBingCk (game = 'gs') { let ck = {} let ckQQ = {} let noteCk = {} @@ -121,20 +122,10 @@ class GsCfg { return { ck, ckQQ, noteCk } } - /** 获取qq号绑定ck */ - getBingCkSingle(userId) { - console.log('gsCfg.getBingCkSingle() 即将废弃') - return {} - } - - saveBingCk(userId, data) { - console.log('gsCfg.saveBingCk() 即将废弃') - } - /** * 原神角色id转换角色名字 */ - roleIdToName(id) { + roleIdToName (id) { let name = this.getdefSet('role', this.isSr ? 'sr_name' : 'name') if (name[id]) { return name[id][0] @@ -143,20 +134,8 @@ class GsCfg { return '' } - /** - * 原神武器id转换成武器名字 - */ - getWeaponDataByWeaponHash(hash) { - let data = this.getdefSet('weapon', 'data') - let weaponData = {} - weaponData.name = data.Name[hash] - weaponData.type = data.Type[weaponData.name] - weaponData.icon = data.Icon[weaponData.name] - return weaponData - } - /** 原神角色别名转id */ - roleNameToID(keyword, isSr) { + roleNameToID (keyword, isSr) { if (isSr) this.isSr = isSr if (!isNaN(keyword)) keyword = Number(keyword) this.getAbbr() @@ -165,7 +144,7 @@ class GsCfg { } /** 获取角色别名 */ - getAbbr() { + getAbbr () { if (this[this.isSr ? 'sr_nameID' : 'nameID']) return this.nameID = new Map() @@ -197,25 +176,12 @@ class GsCfg { } } - /** 返回所有别名,包括用户自定义的 */ - getAllAbbr() { - let nameArr = this.getdefSet('role', this.isSr ? 'sr_name' : 'name') - let nameArrUser = this.getConfig('role', 'name') - - for (let i in nameArrUser) { - let id = this.roleNameToID(i) - nameArr[id] = nameArr[id].concat(nameArrUser[i]) - } - - return nameArr - } - /** * 原神角色武器长名称缩写 * @param name 名称 * @param isWeapon 是否武器 */ - shortName(name, isWeapon = false) { + shortName (name, isWeapon = false) { let other = {} if (isWeapon) { other = this.getdefSet('weapon', 'other') @@ -226,12 +192,12 @@ class GsCfg { } /** 公共配置ck文件修改hook */ - async change_myspubCk() { + async change_myspubCk () { await MysInfo.initCache() await MysInfo.initPubCk() } - getGachaSet(groupId = '') { + getGachaSet (groupId = '') { let config = this.getYaml('gacha', 'set', 'config') let def = config.default if (config[groupId]) { @@ -240,7 +206,7 @@ class GsCfg { return def } - getMsgUid(msg) { + getMsgUid (msg) { let ret = /[1|2|5-9][0-9]{8}/g.exec(msg) if (!ret) return false return ret[0] @@ -255,7 +221,7 @@ class GsCfg { * @return alias 当前别名 * @return uid 游戏uid */ - getRole(msg, filterMsg = '', isSr = false) { + getRole (msg, filterMsg = '', isSr = false) { let alias = msg.replace(/#|老婆|老公|[1|2|5-9][0-9]{8}/g, '').trim() if (filterMsg) { alias = alias.replace(new RegExp(filterMsg, 'g'), '').trim() @@ -276,7 +242,7 @@ class GsCfg { } } - cpCfg(app, name) { + cpCfg (app, name) { if (!fs.existsSync('./plugins/genshin/config')) { fs.mkdirSync('./plugins/genshin/config') } @@ -287,52 +253,43 @@ class GsCfg { } } - /** - * 根据角色名获取对应的元素类型 - */ - getElementByRoleName(roleName) { - let element = this.getdefSet('element', 'role') - if (element[roleName]) { - return element[roleName] - } + getWeaponDataByWeaponHash (hash) { + console.log('gsCfg.getWeaponDataByWeaponHash() 已废弃') + return {} } - /** - * 根据技能id获取对应的技能数据,角色名用于命座加成的技能等级 - */ - getSkillDataByskillId(skillId, roleName) { - let skillMap = this.getdefSet('skill', 'data') - let skillData = {} - if (skillMap.Name[skillId]) { - skillData.name = skillMap.Name[skillId] - } - if (skillMap.Icon[skillId]) { - skillData.icon = skillMap.Icon[skillId] - } - if (skillMap.Talent[roleName]) { - skillData.talent = skillMap.Talent[roleName] - } - return skillData + getAllAbbr () { + console.log('gsCfg.getAllAbbr() 已废弃') + return {} } - fightPropIdToName(propId) { - let propMap = this.getdefSet('prop', 'prop') - if (propMap[propId]) { - return propMap[propId] - } + getBingCkSingle (userId) { + console.log('gsCfg.getBingCkSingle() 已废弃') + return {} + } + + saveBingCk (userId, data) { + console.log('gsCfg.saveBingCk() 已废弃') + } + + getElementByRoleName (roleName) { + console.log('gsCfg.getElementByRoleName() 已废弃') return '' } - getRoleTalentByTalentId(talentId) { - let talentMap = this.getdefSet('role', 'talent') - let talent = {} - if (talentMap.Name[talentId]) { - talent.name = talentMap.Name[talentId] - } - if (talentMap.Icon[talentId]) { - talent.icon = talentMap.Icon[talentId] - } - return talent + getSkillDataByskillId (skillId, roleName) { + console.log('gsCfg.getSkillDataByskillId() 已废弃') + return {} + } + + fightPropIdToName (propId) { + console.log('gsCfg.fightPropIdToName() 已废弃') + return '' + } + + getRoleTalentByTalentId (talentId) { + console.log('gsCfg.getRoleTalentByTalentId 已废弃') + return {} } } diff --git a/plugins/genshin/model/mys/NoteUser.js b/plugins/genshin/model/mys/NoteUser.js index 6c2a082..6a6cc94 100644 --- a/plugins/genshin/model/mys/NoteUser.js +++ b/plugins/genshin/model/mys/NoteUser.js @@ -86,7 +86,14 @@ export default class NoteUser extends BaseModel { // 兼容处理传入e if (qq && qq.user_id) { let e = qq - let user = await NoteUser.create(e.user_id) + let id = e.originalUserId || e.user_id + let mainId = await redis.get(`Yz:NoteUser:mainId:${e.user_id}`) + if (mainId) { + id = mainId + e.mainUserId = mainId + e.originalUserId = e.originalUserId || e.user_id + } + let user = await NoteUser.create(id) e.user = user return user } diff --git a/plugins/genshin/model/user.js b/plugins/genshin/model/user.js index 26a416f..1b5f8af 100644 --- a/plugins/genshin/model/user.js +++ b/plugins/genshin/model/user.js @@ -499,4 +499,90 @@ export default class User extends base { ...this.screenData } } + + async bindNoteUser () { + let user = await this.user() + let id = user.qq + let { e } = this + let { msg } = e + if (!id) { + return true + } + if (/(删除绑定|取消绑定|解除绑定|解绑|删除|取消)/.test(msg)) { + // 删除用户 + id = e.originalUserId || id + if (/主/.test(msg)) { + let mainId = await redis.get(`Yz:NoteUser:mainId:${id}`) + if (!mainId) { + e.reply('当前用户没有主用户,在主用户中通过【#绑定用户】可进行绑定...') + return true + } + let subIds = await Data.getCacheJSON(`Yz:NoteUser:subIds:${mainId}`) + delete subIds[id] + await redis.del(`Yz:NoteUser:mainId:${id}`) + await Data.setCacheJSON(`Yz:NoteUser:subIds:${mainId}`, subIds) + e.reply('已经解除与主用户的绑定...') + } else if (/子/.test(msg)) { + let subIds = await Data.getCacheJSON(`Yz:NoteUser:subIds:${id}`) + let count = 0 + for (let key in subIds) { + await redis.del(`Yz:NoteUser:mainId:${key}`) + count++ + } + if (count > 0) { + e.reply(`已删除${count}个子用户...`) + await redis.del(`Yz:NoteUser:subIds:${id}`) + } else { + e.reply(`当前用户没有子用户,通过【#绑定用户】可绑定子用户...`) + } + } + return true + } + msg = msg.replace(/^#\s*(接受)?绑定(主|子)?用户/, '') + let idRet = /^\[(\w{5,})](?:\[(\w+)])?$/.exec(msg) + if (idRet && idRet[1]) { + let mainId = idRet[1] + let currId = id.toString() + if (!idRet[2]) { + // 子用户绑定 + if (currId === mainId) { + e.reply('请切换到需要绑定的子用户并发送绑定命令...') + return true + } + let verify = (Math.floor(100000000 + Math.random() * 100000000)).toString() + await redis.set(`Yz:NoteUser:verify:${mainId}`, verify + '||' + currId, { EX: 300 }) + e.reply(['此账号即将作为子用户,绑定至主用户:${mainId}', + '成功绑定后,此用户输入的命令,将视作主用户命令,使用主用户的CK与UID等信息', + '如需继续绑定,请在5分钟内,使用主账户发送以下命令:', '', + `#接受绑定子用户[${mainId}][${verify}]` + ].join('\n')) + return true + } else { + // 接受绑定 + if (currId !== mainId) { + e.reply('请切换到主用户并发送接受绑定的命令...') + return true + } + let verify = await redis.get(`Yz:NoteUser:verify:${mainId}`) || '' + verify = verify.split('||') + if (!verify || verify[0] !== idRet[2] || !verify[1]) { + e.reply('校验失败,请发送【#绑定用户】重新开始绑定流程') + return true + } + let subId = verify[1] + await redis.del(`Yz:NoteUser:verify:${mainId}`) + await redis.set(`Yz:NoteUser:mainId:${subId}`, mainId, { EX: 3600 * 24 * 365 }) + let subIds = await Data.getCacheJSON(`Yz:NoteUser:subIds:${mainId}`) + subIds[subId] = 'true' + await Data.setCacheJSON(`Yz:NoteUser:subIds:${mainId}`, subIds) + e.reply('绑定成功,绑定的子用户可使用主用户的UID/CK等信息\n请勿接受不是自己用户的绑定,如需解绑可通过【#解绑子用户】进行解绑') + return true + } + } else { + this.e.reply(['将此账号作为主用户,同Bot已绑定的子用户可使用主用户的CK及UID信息等信息。', + '可在多个QQ或频道间打通用户信息,推荐使用QQ账户作为主用户。', + '请【切换至需要绑定的子用户】,输入以下命令,获得绑定验证码', '请勿接受不是自己用户的绑定!', '', + `#绑定主用户[${id}]`].join('\n')) + } + } }