mirror of
https://github.com/ctrlcvs/xiaoyao-cvs-plugin.git
synced 2024-12-23 11:40:51 +08:00
651 lines
16 KiB
JavaScript
651 lines
16 KiB
JavaScript
import MysApi from './mysApi.js'
|
||
import GsCfg from '../gsCfg.js'
|
||
import lodash from 'lodash'
|
||
import moment from 'moment'
|
||
|
||
/** 公共ck */
|
||
let pubCk = {}
|
||
/** 绑定ck */
|
||
let bingCkUid = {}
|
||
let bingCkQQ = {}
|
||
let bingCkLtuid = {}
|
||
|
||
export default class MysInfo {
|
||
/** redis key */
|
||
static keyPre = 'Yz:genshin:mys:'
|
||
static key = {
|
||
/** ck使用次数统计 */
|
||
count: `${MysInfo.keyPre}ck:count`,
|
||
/** ck使用详情 */
|
||
detail: `${MysInfo.keyPre}ck:detail`,
|
||
/** 单个ck使用次数 */
|
||
ckNum: `${MysInfo.keyPre}ckNum:`,
|
||
/** 已失效的ck使用详情 */
|
||
delDetail: `${MysInfo.keyPre}ck:delDetail`,
|
||
/** qq-uid */
|
||
qqUid: `${MysInfo.keyPre}qq-uid:`
|
||
}
|
||
|
||
static tips = '请先#绑定cookie\n发送【体力帮助】查看配置教程'
|
||
|
||
constructor (e) {
|
||
if (e) {
|
||
this.e = e
|
||
this.userId = String(e.user_id)
|
||
}
|
||
/** 当前查询原神uid */
|
||
this.uid = ''
|
||
/** 当前ck信息 */
|
||
this.ckInfo = {
|
||
ck: '',
|
||
uid: '',
|
||
qq: '',
|
||
ltuid: '',
|
||
type: ''
|
||
}
|
||
|
||
this.auth = ['dailyNote', 'bbs_sign_info', 'bbs_sign_home', 'bbs_sign']
|
||
}
|
||
|
||
static async init (e, api) {
|
||
let mysInfo = new MysInfo(e)
|
||
|
||
/** 检查时间 */
|
||
if (!mysInfo.checkTime()) return false
|
||
|
||
/** 初始化绑定ck */
|
||
await mysInfo.initBingCk()
|
||
|
||
/** 初始化公共ck */
|
||
await mysInfo.initPubCk()
|
||
|
||
if (mysInfo.checkAuth(api)) {
|
||
/** 获取ck绑定uid */
|
||
mysInfo.uid = (await MysInfo.getSelfUid(e)).uid
|
||
} else {
|
||
/** 获取uid */
|
||
mysInfo.uid = await MysInfo.getUid(e)
|
||
}
|
||
|
||
if (!mysInfo.uid) return false
|
||
|
||
mysInfo.e.uid = mysInfo.uid
|
||
|
||
/** 获取ck */
|
||
await mysInfo.getCookie()
|
||
|
||
/** 判断回复 */
|
||
await mysInfo.checkReply()
|
||
|
||
return mysInfo
|
||
}
|
||
|
||
/** 获取uid */
|
||
static async getUid (e) {
|
||
if (e.uid) return e.uid
|
||
|
||
let { msg = '', at = '' } = e
|
||
|
||
if (!msg) return false
|
||
|
||
let uid = false
|
||
/** at用户 */
|
||
if (at) {
|
||
uid = await redis.get(`${MysInfo.key.qqUid}${at}`)
|
||
if (uid) return String(uid)
|
||
e.reply('尚未绑定uid', false, { at })
|
||
return false
|
||
}
|
||
|
||
let matchUid = (msg = '') => {
|
||
let ret = /[1|2|5][0-9]{8}/g.exec(msg)
|
||
if (!ret) return false
|
||
return ret[0]
|
||
}
|
||
|
||
/** 命令消息携带 */
|
||
uid = matchUid(msg)
|
||
if (uid) return String(uid)
|
||
|
||
/** 绑定的uid */
|
||
uid = await redis.get(`${MysInfo.key.qqUid}${e.user_id}`)
|
||
if (uid) return String(uid)
|
||
|
||
/** 群名片 */
|
||
uid = matchUid(e.sender.card)
|
||
if (uid) return String(uid)
|
||
|
||
e.reply('请先#绑定uid', false, { at })
|
||
|
||
return false
|
||
}
|
||
|
||
/** 获取ck绑定uid */
|
||
static async getSelfUid (e) {
|
||
if (e.uid) return e.uid
|
||
|
||
let { msg = '', at = '' } = e
|
||
|
||
if (!msg) return false
|
||
|
||
/** at用户 */
|
||
if (at && (!bingCkQQ[at] || !bingCkQQ[at].uid)) {
|
||
e.reply('尚未绑定cookie', false, { at })
|
||
return false
|
||
}
|
||
|
||
if (!e.user_id || !bingCkQQ[e.user_id] || !bingCkQQ[e.user_id].uid) {
|
||
e.reply(MysInfo.tips, false, { at })
|
||
return false
|
||
}
|
||
|
||
return bingCkQQ[e.user_id]
|
||
}
|
||
|
||
/** 判断绑定ck才能查询 */
|
||
checkAuth (api) {
|
||
if (lodash.isObject(api)) {
|
||
for (let i in api) {
|
||
if (this.auth.includes(i)) {
|
||
return true
|
||
}
|
||
}
|
||
} else if (this.auth.includes(api)) {
|
||
return true
|
||
}
|
||
return false
|
||
}
|
||
|
||
/**
|
||
* @param api
|
||
* * `index` 米游社原神首页宝箱等数据
|
||
* * `spiralAbyss` 原神深渊
|
||
* * `character` 原神角色详情
|
||
* * `dailyNote` 原神树脂
|
||
* * `bbs_sign` 米游社原神签到
|
||
* * `detail` 详情
|
||
* * `ys_ledger` 札记
|
||
* * `compute` 养成计算器
|
||
* * `avatarSkill` 角色技能
|
||
*/
|
||
static async get (e, api, data = {}) {
|
||
let mysInfo = await MysInfo.init(e, api)
|
||
|
||
if (!mysInfo.uid || !mysInfo.ckInfo.ck) return false
|
||
e.uid = mysInfo.uid
|
||
|
||
let mysApi = new MysApi(mysInfo.uid, mysInfo.ckInfo.ck)
|
||
|
||
let res
|
||
if (lodash.isObject(api)) {
|
||
let all = []
|
||
lodash.forEach(api, (v, i) => {
|
||
all.push(mysApi.getData(i, v))
|
||
})
|
||
res = await Promise.all(all)
|
||
|
||
for (let i in res) {
|
||
res[i] = await mysInfo.checkCode(res[i], res[i].api)
|
||
if (res[i].retcode === 0) continue
|
||
break
|
||
}
|
||
} else {
|
||
res = await mysApi.getData(api, data)
|
||
if (!res) return false
|
||
|
||
res = await mysInfo.checkCode(res, api)
|
||
}
|
||
|
||
return res
|
||
}
|
||
|
||
async checkReply () {
|
||
if (!this.uid) {
|
||
this.e.reply('请先#绑定uid')
|
||
}
|
||
|
||
if (!this.ckInfo.ck) {
|
||
if (lodash.isEmpty(pubCk)) {
|
||
this.e.reply('请先配置公共查询ck')
|
||
} else {
|
||
this.e.reply('公共ck查询次数已用完,暂无法查询新uid')
|
||
}
|
||
}
|
||
}
|
||
|
||
async getCookie () {
|
||
if (this.ckInfo.ck) return this.ckInfo.ck
|
||
// 使用用户uid绑定的ck
|
||
await this.getBingCK() ||
|
||
// 使用uid已查询的ck
|
||
await this.getCheckCK() ||
|
||
// 使用用户绑定的ck
|
||
await this.getBingCKqq() ||
|
||
// 使用公共ck
|
||
await this.getPublicCK()
|
||
|
||
return this.ckInfo.ck
|
||
}
|
||
|
||
async getBingCK () {
|
||
if (!bingCkUid[this.uid]) return false
|
||
|
||
this.isSelf = true
|
||
|
||
let ck = bingCkUid[this.uid]
|
||
|
||
this.ckInfo = ck
|
||
this.ckInfo.type = 'self'
|
||
|
||
logger.mark(`[米游社查询][uid:${this.uid}]${logger.green(`[使用已绑定ck:${ck.ltuid}]`)}`)
|
||
|
||
return ck.ck
|
||
}
|
||
|
||
async getCheckCK () {
|
||
let ltuid = await redis.zScore(MysInfo.key.detail, this.uid)
|
||
|
||
if (!ltuid) return false
|
||
|
||
this.ckInfo.ltuid = ltuid
|
||
this.ckInfo.type = 'public'
|
||
|
||
/** 使用用户绑定ck */
|
||
if (bingCkLtuid[ltuid]) {
|
||
logger.mark(`[米游社查询][uid:${this.uid}]${logger.blue(`[已查询][使用用户ck:${ltuid}]`)}`)
|
||
|
||
this.ckInfo = bingCkLtuid[ltuid]
|
||
this.ckInfo.type = 'self'
|
||
|
||
return this.ckInfo.ck
|
||
}
|
||
|
||
/** 公共ck */
|
||
if (pubCk[ltuid]) {
|
||
logger.mark(`[米游社查询][uid:${this.uid}]${logger.cyan(`[已查询][使用公共ck:${ltuid}]`)}`)
|
||
|
||
this.ckInfo.ck = pubCk[ltuid]
|
||
return this.ckInfo.ck
|
||
}
|
||
|
||
return false
|
||
}
|
||
|
||
/** 使用用户绑定的ck */
|
||
async getBingCKqq () {
|
||
/** 用户没有绑定ck */
|
||
if (!bingCkQQ[this.userId]) return false
|
||
|
||
let ck = bingCkQQ[this.userId]
|
||
|
||
/** 判断用户ck使用次数 */
|
||
let num = await redis.get(`${MysInfo.key.ckNum}${ck.ltuid}`)
|
||
if (num && num >= 27) {
|
||
logger.mark(`[米游社查询][uid:${this.uid}] 绑定用户ck次数已用完`)
|
||
return
|
||
}
|
||
|
||
if (!num) num = 0
|
||
|
||
this.ckInfo = ck
|
||
this.ckInfo.type = 'bing'
|
||
|
||
/** 插入查询详情 */
|
||
await redis.zAdd(MysInfo.key.detail, { score: ck.ltuid, value: this.uid })
|
||
/** 获取ck查询详情 */
|
||
let count = await redis.zRangeByScore(MysInfo.key.detail, ck.ltuid, ck.ltuid)
|
||
|
||
/** 用户ck也配置公共ck */
|
||
if (pubCk[ck.ltuid]) {
|
||
/** 统计ck查询次数 */
|
||
redis.zAdd(MysInfo.key.count, { score: count.length || 1, value: String(ck.ltuid) })
|
||
}
|
||
this.expire(MysInfo.key.detail)
|
||
|
||
/** 插入单个查询次数 */
|
||
redis.setEx(`${MysInfo.key.ckNum}${ck.ltuid}`, this.getEnd(), String(count.length))
|
||
|
||
logger.mark(`[米游社查询][uid:${this.uid}]${logger.blue(`[使用用户ck:${ck.ltuid}]`)}[次数:${++num}次]`)
|
||
|
||
return ck.ck
|
||
}
|
||
|
||
async getPublicCK () {
|
||
if (lodash.isEmpty(pubCk)) {
|
||
logger.mark('请先配置公共查询ck')
|
||
return false
|
||
}
|
||
|
||
/** 获取使用次数最少的ck */
|
||
let list = await redis.zRangeByScore(MysInfo.key.count, 0, 27, true)
|
||
|
||
if (lodash.isEmpty(list)) {
|
||
logger.mark('公共查询ck已用完')
|
||
return false
|
||
}
|
||
|
||
let ltuid = list[0]
|
||
|
||
if (!pubCk[ltuid]) {
|
||
logger.mark(`公共查询ck错误[ltuid:${ltuid}]`)
|
||
await redis.zAdd(MysInfo.key.count, { score: 99, value: ltuid })
|
||
return false
|
||
}
|
||
|
||
this.ckInfo.ck = pubCk[ltuid]
|
||
this.ckInfo.ltuid = ltuid
|
||
this.ckInfo.type = 'public'
|
||
|
||
/** 非原子操作,可能存在误差 */
|
||
|
||
/** 插入查询详情 */
|
||
await redis.zAdd(MysInfo.key.detail, { score: ltuid, value: this.uid })
|
||
/** 获取ck查询详情 */
|
||
let count = await redis.zRangeByScore(MysInfo.key.detail, ltuid, ltuid)
|
||
/** 统计ck查询次数 */
|
||
redis.zAdd(MysInfo.key.count, { score: count.length, value: ltuid })
|
||
/** 插入单个查询次数 */
|
||
redis.setEx(`${MysInfo.key.ckNum}${ltuid}`, this.getEnd(), String(count.length))
|
||
|
||
this.expire(MysInfo.key.detail)
|
||
|
||
logger.mark(`[米游社查询][uid:${this.uid}]${logger.yellow(`[使用公共ck:${ltuid}][次数:${count.length}]`)}`)
|
||
|
||
return pubCk[ltuid]
|
||
}
|
||
|
||
/** 初始化公共查询ck */
|
||
async initPubCk () {
|
||
/** 没配置每次都会初始化 */
|
||
if (!lodash.isEmpty(pubCk)) return
|
||
|
||
let ckList = await redis.zRangeByScore(MysInfo.key.count, 0, 100)
|
||
|
||
await this.addPubCk(ckList)
|
||
|
||
/** 使用用户ck当公共查询 */
|
||
let set = GsCfg.getConfig('mys', 'set')
|
||
let userNum = 0
|
||
if (set.allowUseCookie == 1) {
|
||
lodash.forEach(bingCkUid, async v => {
|
||
if (pubCk[v.ltuid]) return
|
||
pubCk[v.ltuid] = v.ck
|
||
|
||
userNum++
|
||
/** 加入redis统计 */
|
||
if (!ckList.includes(v.ltuid)) {
|
||
await redis.zAdd(MysInfo.key.count, { score: 0, value: String(v.ltuid) })
|
||
}
|
||
})
|
||
}
|
||
|
||
this.expire(MysInfo.key.count)
|
||
|
||
if (userNum > 0) logger.mark(`加载用户ck:${userNum}个`)
|
||
}
|
||
|
||
/** 加入公共ck池 */
|
||
async addPubCk (ckList = '') {
|
||
let ckArr = GsCfg.getConfig('mys', 'pubCk')
|
||
|
||
if (!ckList) {
|
||
ckList = await redis.zRangeByScore(MysInfo.key.count, 0, 100)
|
||
}
|
||
|
||
let pubNum = 0
|
||
for (let v of ckArr) {
|
||
let [ltuid = ''] = v.match(/ltuid=(\w{0,9})/g)
|
||
if (!ltuid) return
|
||
|
||
ltuid = String(lodash.trim(ltuid, 'ltuid='))
|
||
|
||
if (isNaN(ltuid)) return
|
||
|
||
pubCk[ltuid] = v
|
||
|
||
pubNum++
|
||
|
||
/** 加入redis统计 */
|
||
if (!ckList.includes(ltuid)) {
|
||
await redis.zAdd(MysInfo.key.count, { score: 0, value: ltuid })
|
||
}
|
||
}
|
||
if (pubNum > 0) logger.mark(`加载公共ck:${pubNum}个`)
|
||
}
|
||
|
||
async initBingCk () {
|
||
if (!lodash.isEmpty(bingCkUid)) return
|
||
|
||
let res = await GsCfg.getBingCk()
|
||
bingCkUid = res.ck
|
||
bingCkQQ = res.ckQQ
|
||
bingCkLtuid = lodash.keyBy(bingCkUid, 'ltuid')
|
||
}
|
||
|
||
async checkCode (res, type) {
|
||
res.retcode = Number(res.retcode)
|
||
if (type == 'bbs_sign') {
|
||
if ([-5003].includes(res.retcode)) {
|
||
res.retcode = 0
|
||
}
|
||
}
|
||
switch (res.retcode) {
|
||
case 0:break
|
||
case -1:
|
||
case -100:
|
||
case 1001:
|
||
case 10001:
|
||
case 10103:
|
||
if (/(登录|login)/i.test(res.message)) {
|
||
if (this.ckInfo.uid) {
|
||
this.e.reply(`UID:${this.ckInfo.uid},米游社cookie已失效,请重新绑定cookie`)
|
||
} else {
|
||
this.e.reply(`ltuid:${this.ckInfo.ltuid},米游社cookie已失效`)
|
||
}
|
||
await this.delCk()
|
||
} else {
|
||
this.e.reply(`米游社接口报错,暂时无法查询:${res.message}`)
|
||
}
|
||
break
|
||
case 1008:
|
||
this.e.reply('\n请先去米游社绑定角色', false, { at: this.userId })
|
||
break
|
||
case 10101:
|
||
this.disableToday()
|
||
this.e.reply('查询已达今日上限')
|
||
break
|
||
case 10102:
|
||
if (res.message == 'Data is not public for the user') {
|
||
this.e.reply(`\nUID:${this.ckInfo.uid},米游社数据未公开`, false, { at: this.userId })
|
||
} else {
|
||
this.e.reply(`uid:${this.uid},请先去米游社绑定角色`)
|
||
}
|
||
break
|
||
// 伙伴不存在~
|
||
case -1002:
|
||
if (res.api == 'detail') res.retcode = 0
|
||
break
|
||
default:
|
||
this.e.reply(`米游社接口报错,暂时无法查询:${res.message || 'error'}`)
|
||
break
|
||
}
|
||
|
||
if (res.retcode !== 0) {
|
||
logger.mark(`mys接口报错:${JSON.stringify(res)},uid:${this.uid}`)
|
||
}
|
||
|
||
return res
|
||
}
|
||
|
||
/** 删除失效ck */
|
||
async delCk () {
|
||
let ltuid = this.ckInfo.ltuid
|
||
|
||
/** 记录公共ck失效 */
|
||
if (this.ckInfo.type == 'public') {
|
||
if (bingCkLtuid[ltuid]) {
|
||
this.ckInfo = bingCkLtuid[ltuid]
|
||
this.ckInfo.type = 'self'
|
||
} else {
|
||
logger.mark(`删除失效ck[ltuid:${ltuid}]`)
|
||
}
|
||
}
|
||
|
||
if (this.ckInfo.type == 'self' || this.ckInfo.type == 'bing') {
|
||
/** 获取用户绑定ck */
|
||
let ck = GsCfg.getBingCkSingle(this.userId)
|
||
let tmp = ck[this.ckInfo.uid]
|
||
if (tmp) {
|
||
ltuid = tmp.ltuid
|
||
|
||
logger.mark(`删除失效绑定ck[qq:${this.userId}]`)
|
||
/** 删除文件保存ck */
|
||
delete ck[this.ckInfo.uid]
|
||
GsCfg.saveBingCk(this.userId, ck)
|
||
|
||
this.redisDel(ltuid)
|
||
|
||
delete pubCk[ltuid]
|
||
delete bingCkUid[tmp.uid]
|
||
delete bingCkQQ[tmp.qq]
|
||
}
|
||
}
|
||
|
||
delete pubCk[ltuid]
|
||
|
||
await this.redisDel(ltuid)
|
||
}
|
||
|
||
async redisDel (ltuid) {
|
||
/** 统计次数设为超限 */
|
||
await redis.zRem(MysInfo.key.count, String(ltuid))
|
||
// await redis.setEx(`${MysInfo.key.ckNum}${ltuid}`, this.getEnd(), '99')
|
||
|
||
/** 将当前查询记录移入回收站 */
|
||
await this.detailDel(ltuid)
|
||
}
|
||
|
||
/** 将当前查询记录移入回收站 */
|
||
async detailDel (ltuid) {
|
||
let detail = await redis.zRangeByScore(MysInfo.key.detail, ltuid, ltuid)
|
||
if (!lodash.isEmpty(detail)) {
|
||
let delDetail = []
|
||
detail.forEach((v) => {
|
||
delDetail.push({ score: ltuid, value: String(v) })
|
||
})
|
||
await redis.zAdd(MysInfo.key.delDetail, delDetail)
|
||
this.expire(MysInfo.key.delDetail)
|
||
}
|
||
/** 删除当前ck查询记录 */
|
||
await redis.zRemRangeByScore(MysInfo.key.detail, ltuid, ltuid)
|
||
}
|
||
|
||
async disableToday () {
|
||
/** 统计次数设为超限 */
|
||
await redis.zAdd(MysInfo.key.count, { score: 99, value: String(this.ckInfo.ltuid) })
|
||
await redis.setEx(`${MysInfo.key.ckNum}${this.ckInfo.ltuid}`, this.getEnd(), '99')
|
||
}
|
||
|
||
async expire (key) {
|
||
return await redis.expire(key, this.getEnd())
|
||
}
|
||
|
||
getEnd () {
|
||
let end = moment().endOf('day').format('X')
|
||
return end - moment().format('X')
|
||
}
|
||
|
||
/** 处理用户绑定ck */
|
||
async addBingCk (ck) {
|
||
/** 加入缓存 */
|
||
bingCkUid[ck.uid] = ck
|
||
bingCkQQ[ck.qq] = ck
|
||
bingCkLtuid[ck.ltuid] = ck
|
||
|
||
let set = GsCfg.getConfig('mys', 'set')
|
||
|
||
/** qq-uid */
|
||
await redis.setEx(`${MysInfo.key.qqUid}${ck.qq}`, 3600 * 24 * 30, String(ck.uid))
|
||
|
||
/** 恢复回收站查询记录,会覆盖原来记录 */
|
||
let detail = await redis.zRangeByScore(MysInfo.key.delDetail, ck.ltuid, ck.ltuid)
|
||
if (!lodash.isEmpty(detail)) {
|
||
let delDetail = []
|
||
detail.forEach((v) => {
|
||
delDetail.push({ score: ck.ltuid, value: String(v) })
|
||
})
|
||
await redis.zAdd(MysInfo.key.detail, delDetail)
|
||
this.expire(MysInfo.key.detail)
|
||
}
|
||
/** 删除回收站记录 */
|
||
await redis.zRemRangeByScore(MysInfo.key.delDetail, ck.ltuid, ck.ltuid)
|
||
|
||
/** 获取ck查询详情 */
|
||
let count = await redis.zRangeByScore(MysInfo.key.detail, ck.ltuid, ck.ltuid)
|
||
|
||
/** 开启了用户ck查询 */
|
||
if (set.allowUseCookie == 1) {
|
||
pubCk[ck.ltuid] = ck
|
||
let ckList = await redis.zRangeByScore(MysInfo.key.count, 0, 100)
|
||
if (!ckList.includes(ck.ltuid)) {
|
||
await redis.zAdd(MysInfo.key.count, { score: count.length, value: String(ck.ltuid) })
|
||
}
|
||
}
|
||
}
|
||
|
||
async delBingCk (ck) {
|
||
delete bingCkUid[ck.uid]
|
||
delete bingCkQQ[ck.qq]
|
||
delete bingCkLtuid[ck.ltuid]
|
||
|
||
this.detailDel(ck.ltuid)
|
||
}
|
||
|
||
async resetCk () {
|
||
return await redis.del(MysInfo.key.count)
|
||
}
|
||
|
||
static async initCk () {
|
||
if (lodash.isEmpty(bingCkUid)) {
|
||
let mysInfo = new MysInfo()
|
||
await mysInfo.initBingCk()
|
||
}
|
||
}
|
||
|
||
static async getBingCkUid () {
|
||
await MysInfo.initCk()
|
||
|
||
return bingCkUid
|
||
}
|
||
|
||
/** 切换uid */
|
||
static toggleUid (qq, ck) {
|
||
bingCkQQ[qq] = ck
|
||
}
|
||
|
||
static async checkUidBing (uid) {
|
||
await MysInfo.initCk()
|
||
|
||
if (bingCkUid[uid]) return true
|
||
|
||
return false
|
||
}
|
||
|
||
/** 数据更新中,请稍后再试 */
|
||
checkTime () {
|
||
let hour = moment().hour()
|
||
let min = moment().minute()
|
||
let second = moment().second()
|
||
|
||
if (hour == 23 && min == 59 && second >= 58) {
|
||
this.e.reply('数据更新中,请稍后再试')
|
||
return false
|
||
}
|
||
if (hour == 0 && min == 0 && second <= 3) {
|
||
this.e.reply('数据更新中,请稍后再试')
|
||
return false
|
||
}
|
||
return true
|
||
}
|
||
}
|