新增`#绑定用户`命令,用于打通多个平台间的用户

This commit is contained in:
Kokomi 2023-11-04 04:29:34 +08:00
parent 669e331d98
commit e3d789fc91
6 changed files with 172 additions and 103 deletions

View File

@ -1,3 +1,10 @@
# 3.1.2
* 新增`#绑定用户`命令
* 可将其他QQ绑定至当前用户以打通多个用户子用户使用主用户的CK与UID等信息
* 同时也可绑定其他平台的用户例如频道、微信等。如需链接至其他平台需使用TRSS-Yunzai或Lain-plugin
* 部分命令可能无法识别绑定后的主用户,如遇问题可反馈
# 3.1.1 # 3.1.1
* 初步适配原神4.0版本,增加对应资源及信息展示,感谢**Ca(HCO₃)₂**、**@touchscale**、**@teriri7** * 初步适配原神4.0版本,增加对应资源及信息展示,感谢**Ca(HCO₃)₂**、**@touchscale**、**@teriri7**
@ -7,10 +14,10 @@
# 3.1.0 # 3.1.0
* 重构CK与UID管理逻辑 * 重构CK与UID管理逻辑
* 支持多UID绑定可绑定多个UID并进行切换 * 支持多UID绑定可绑定多个UID并进行切换
* 支持原神与星铁UID共存可针对查询命令分配对应UID * 支持原神与星铁UID共存可针对查询命令分配对应UID
* 新增`#删除uid1`命令,可对`#uid`列表内的绑定UID进行删除 * 新增`#删除uid1`命令,可对`#uid`列表内的绑定UID进行删除
* 使用sqlite进行ck与uid存储 * 使用sqlite进行ck与uid存储
* 底层对星铁查询进行支持 **@cvs** * 底层对星铁查询进行支持 **@cvs**
# 3.0.2 # 3.0.2
@ -29,4 +36,4 @@
# 3.0.0 # 3.0.0
* Yunzai-Bot * Yunzai-Bot

View File

@ -1,6 +1,6 @@
{ {
"name": "miao-yunzai", "name": "miao-yunzai",
"version": "3.1.1", "version": "3.1.2",
"author": "Yoimiya-Kokomi, Le-niao", "author": "Yoimiya-Kokomi, Le-niao",
"description": "QQ group Bot", "description": "QQ group Bot",
"main": "app.js", "main": "app.js",
@ -52,4 +52,4 @@
"#miao": "./plugins/miao-plugin/components/index.js", "#miao": "./plugins/miao-plugin/components/index.js",
"#miao.models": "./plugins/miao-plugin/models/index.js" "#miao.models": "./plugins/miao-plugin/models/index.js"
} }
} }

View File

@ -52,6 +52,14 @@ export class user extends plugin {
{ {
reg: '^#\\s*(检查|我的)*ck(状态)*$', reg: '^#\\s*(检查|我的)*ck(状态)*$',
fnc: 'checkCkStatus' fnc: 'checkCkStatus'
},
{
reg: '^#(接受)?绑定(主|子)?用户(\\[\\w+\\]){0,2}$',
fnc: 'bindNoteUser'
},
{
reg: '^#(删除绑定|取消绑定|解除绑定|解绑|删除|取消)(主|子)用户$',
fnc: 'bindNoteUser'
} }
] ]
}) })
@ -175,4 +183,8 @@ export class user extends plugin {
async checkCkStatus () { async checkCkStatus () {
await this.User.checkCkStatus() await this.User.checkCkStatus()
} }
async bindNoteUser () {
await this.User.bindNoteUser()
}
} }

View File

@ -1,15 +1,13 @@
import YAML from 'yaml' import YAML from 'yaml'
import chokidar from 'chokidar' import chokidar from 'chokidar'
import fs from 'node:fs' import fs from 'node:fs'
import { promisify } from 'node:util'
import lodash from 'lodash' import lodash from 'lodash'
import MysInfo from './mys/mysInfo.js' import MysInfo from './mys/mysInfo.js'
import NoteUser from './mys/NoteUser.js' import NoteUser from './mys/NoteUser.js'
import MysUser from './mys/MysUser.js'
/** 配置文件 */ /** 配置文件 */
class GsCfg { class GsCfg {
constructor() { constructor () {
this.isSr = false this.isSr = false
/** 默认设置 */ /** 默认设置 */
this.defSetPath = './plugins/genshin/defSet/' this.defSetPath = './plugins/genshin/defSet/'
@ -25,7 +23,7 @@ class GsCfg {
this.ignore = ['mys.pubCk', 'gacha.set', 'bot.help', 'role.name'] this.ignore = ['mys.pubCk', 'gacha.set', 'bot.help', 'role.name']
} }
get element() { get element () {
return { ...this.getdefSet('element', 'role'), ...this.getdefSet('element', 'weapon') } return { ...this.getdefSet('element', 'role'), ...this.getdefSet('element', 'weapon') }
} }
@ -33,12 +31,12 @@ class GsCfg {
* @param app 功能 * @param app 功能
* @param name 配置文件名称 * @param name 配置文件名称
*/ */
getdefSet(app, name) { getdefSet (app, name) {
return this.getYaml(app, name, 'defSet') return this.getYaml(app, name, 'defSet')
} }
/** 用户配置 */ /** 用户配置 */
getConfig(app, name) { getConfig (app, name) {
if (this.ignore.includes(`${app}.${name}`)) { if (this.ignore.includes(`${app}.${name}`)) {
return this.getYaml(app, name, 'config') return this.getYaml(app, name, 'config')
} }
@ -52,7 +50,7 @@ class GsCfg {
* @param name 名称 * @param name 名称
* @param type 默认跑配置-defSet用户配置-config * @param type 默认跑配置-defSet用户配置-config
*/ */
getYaml(app, name, type) { getYaml (app, name, type) {
let file = this.getFilePath(app, name, type) let file = this.getFilePath(app, name, type)
let key = `${app}.${name}` let key = `${app}.${name}`
@ -72,13 +70,16 @@ class GsCfg {
return this[type][key] return this[type][key]
} }
getFilePath(app, name, type) { getFilePath (app, name, type) {
if (type == 'defSet') return `${this.defSetPath}${app}/${name}.yaml` if (type == 'defSet') {
else return `${this.configPath}${app}.${name}.yaml` 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}` let key = `${app}.${name}`
if (this.watcher[type][key]) return if (this.watcher[type][key]) return
@ -96,7 +97,7 @@ class GsCfg {
} }
/** 读取所有用户绑定的ck */ /** 读取所有用户绑定的ck */
async getBingCk(game = 'gs') { async getBingCk (game = 'gs') {
let ck = {} let ck = {}
let ckQQ = {} let ckQQ = {}
let noteCk = {} let noteCk = {}
@ -121,20 +122,10 @@ class GsCfg {
return { ck, ckQQ, noteCk } return { ck, ckQQ, noteCk }
} }
/** 获取qq号绑定ck */
getBingCkSingle(userId) {
console.log('gsCfg.getBingCkSingle() 即将废弃')
return {}
}
saveBingCk(userId, data) {
console.log('gsCfg.saveBingCk() 即将废弃')
}
/** /**
* 原神角色id转换角色名字 * 原神角色id转换角色名字
*/ */
roleIdToName(id) { roleIdToName (id) {
let name = this.getdefSet('role', this.isSr ? 'sr_name' : 'name') let name = this.getdefSet('role', this.isSr ? 'sr_name' : 'name')
if (name[id]) { if (name[id]) {
return name[id][0] return name[id][0]
@ -143,20 +134,8 @@ class GsCfg {
return '' 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 */ /** 原神角色别名转id */
roleNameToID(keyword, isSr) { roleNameToID (keyword, isSr) {
if (isSr) this.isSr = isSr if (isSr) this.isSr = isSr
if (!isNaN(keyword)) keyword = Number(keyword) if (!isNaN(keyword)) keyword = Number(keyword)
this.getAbbr() this.getAbbr()
@ -165,7 +144,7 @@ class GsCfg {
} }
/** 获取角色别名 */ /** 获取角色别名 */
getAbbr() { getAbbr () {
if (this[this.isSr ? 'sr_nameID' : 'nameID']) return if (this[this.isSr ? 'sr_nameID' : 'nameID']) return
this.nameID = new Map() 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 name 名称
* @param isWeapon 是否武器 * @param isWeapon 是否武器
*/ */
shortName(name, isWeapon = false) { shortName (name, isWeapon = false) {
let other = {} let other = {}
if (isWeapon) { if (isWeapon) {
other = this.getdefSet('weapon', 'other') other = this.getdefSet('weapon', 'other')
@ -226,12 +192,12 @@ class GsCfg {
} }
/** 公共配置ck文件修改hook */ /** 公共配置ck文件修改hook */
async change_myspubCk() { async change_myspubCk () {
await MysInfo.initCache() await MysInfo.initCache()
await MysInfo.initPubCk() await MysInfo.initPubCk()
} }
getGachaSet(groupId = '') { getGachaSet (groupId = '') {
let config = this.getYaml('gacha', 'set', 'config') let config = this.getYaml('gacha', 'set', 'config')
let def = config.default let def = config.default
if (config[groupId]) { if (config[groupId]) {
@ -240,7 +206,7 @@ class GsCfg {
return def return def
} }
getMsgUid(msg) { getMsgUid (msg) {
let ret = /[1|2|5-9][0-9]{8}/g.exec(msg) let ret = /[1|2|5-9][0-9]{8}/g.exec(msg)
if (!ret) return false if (!ret) return false
return ret[0] return ret[0]
@ -255,7 +221,7 @@ class GsCfg {
* @return alias 当前别名 * @return alias 当前别名
* @return uid 游戏uid * @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() let alias = msg.replace(/#|老婆|老公|[1|2|5-9][0-9]{8}/g, '').trim()
if (filterMsg) { if (filterMsg) {
alias = alias.replace(new RegExp(filterMsg, 'g'), '').trim() 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')) { if (!fs.existsSync('./plugins/genshin/config')) {
fs.mkdirSync('./plugins/genshin/config') fs.mkdirSync('./plugins/genshin/config')
} }
@ -287,52 +253,43 @@ class GsCfg {
} }
} }
/** getWeaponDataByWeaponHash (hash) {
* 根据角色名获取对应的元素类型 console.log('gsCfg.getWeaponDataByWeaponHash() 已废弃')
*/ return {}
getElementByRoleName(roleName) {
let element = this.getdefSet('element', 'role')
if (element[roleName]) {
return element[roleName]
}
} }
/** getAllAbbr () {
* 根据技能id获取对应的技能数据,角色名用于命座加成的技能等级 console.log('gsCfg.getAllAbbr() 已废弃')
*/ return {}
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
} }
fightPropIdToName(propId) { getBingCkSingle (userId) {
let propMap = this.getdefSet('prop', 'prop') console.log('gsCfg.getBingCkSingle() 已废弃')
if (propMap[propId]) { return {}
return propMap[propId] }
}
saveBingCk (userId, data) {
console.log('gsCfg.saveBingCk() 已废弃')
}
getElementByRoleName (roleName) {
console.log('gsCfg.getElementByRoleName() 已废弃')
return '' return ''
} }
getRoleTalentByTalentId(talentId) { getSkillDataByskillId (skillId, roleName) {
let talentMap = this.getdefSet('role', 'talent') console.log('gsCfg.getSkillDataByskillId() 已废弃')
let talent = {} return {}
if (talentMap.Name[talentId]) { }
talent.name = talentMap.Name[talentId]
} fightPropIdToName (propId) {
if (talentMap.Icon[talentId]) { console.log('gsCfg.fightPropIdToName() 已废弃')
talent.icon = talentMap.Icon[talentId] return ''
} }
return talent
getRoleTalentByTalentId (talentId) {
console.log('gsCfg.getRoleTalentByTalentId 已废弃')
return {}
} }
} }

View File

@ -86,7 +86,14 @@ export default class NoteUser extends BaseModel {
// 兼容处理传入e // 兼容处理传入e
if (qq && qq.user_id) { if (qq && qq.user_id) {
let e = qq 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 e.user = user
return user return user
} }

View File

@ -499,4 +499,90 @@ export default class User extends base {
...this.screenData ...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'))
}
}
} }