2022-11-07 20:08:24 +00:00
|
|
|
|
import lodash from 'lodash'
|
2022-11-09 19:48:13 +00:00
|
|
|
|
import moment from 'moment'
|
2023-06-03 12:31:13 +00:00
|
|
|
|
import { Cfg, Common, Data, Version } from '#miao'
|
2022-11-07 20:08:24 +00:00
|
|
|
|
|
|
|
|
|
export default class ProfileRank {
|
|
|
|
|
constructor (data) {
|
2022-11-13 19:42:24 +00:00
|
|
|
|
this.groupId = data.groupId || data.groupId || ''
|
|
|
|
|
if (!this.groupId || this.groupId === 'undefined') {
|
|
|
|
|
return false
|
|
|
|
|
}
|
2022-11-07 20:08:24 +00:00
|
|
|
|
this.qq = data.qq
|
|
|
|
|
this.uid = data.uid + ''
|
2022-11-13 19:42:24 +00:00
|
|
|
|
this.allowRank = false
|
2022-11-07 20:08:24 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static async create (data) {
|
2022-11-13 19:42:24 +00:00
|
|
|
|
let rank = new ProfileRank(data)
|
|
|
|
|
rank.allowRank = await ProfileRank.checkRankLimit(rank.uid)
|
|
|
|
|
return rank
|
2022-11-07 20:08:24 +00:00
|
|
|
|
}
|
|
|
|
|
|
2022-11-09 19:48:13 +00:00
|
|
|
|
/**
|
|
|
|
|
* 获取群排行UID
|
|
|
|
|
* @param groupId
|
|
|
|
|
* @param charId
|
|
|
|
|
* @param type
|
|
|
|
|
* @returns {Promise<string|boolean>}
|
|
|
|
|
*/
|
2022-11-08 22:04:08 +00:00
|
|
|
|
static async getGroupMaxUid (groupId, charId, type = 'mark') {
|
2022-11-09 19:48:13 +00:00
|
|
|
|
let uids = await redis.zRange(`miao:rank:${groupId}:${type}:${charId}`, -1, -1)
|
|
|
|
|
return uids ? uids[0] : false
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-17 18:25:32 +00:00
|
|
|
|
static async getGroupMaxUidList (groupId, type = 'mark') {
|
|
|
|
|
let keys = await redis.keys(`miao:rank:${groupId}:${type}:*`)
|
|
|
|
|
let ret = []
|
|
|
|
|
for (let key of keys) {
|
2023-09-01 18:22:27 +00:00
|
|
|
|
let keyRet = /^miao:rank:[\w-]+:(?:mark|dmg|crit|valid):(\d{8})$/.exec(key)
|
2022-11-17 18:25:32 +00:00
|
|
|
|
if (keyRet && keyRet[1]) {
|
|
|
|
|
let charId = keyRet[1]
|
|
|
|
|
let uid = await ProfileRank.getGroupMaxUid(groupId, charId, type)
|
|
|
|
|
if (uid) {
|
|
|
|
|
ret.push({
|
|
|
|
|
uid,
|
|
|
|
|
charId
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return ret
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-10 20:56:15 +00:00
|
|
|
|
/**
|
|
|
|
|
* 获取排行榜
|
|
|
|
|
* @param groupId
|
|
|
|
|
* @param charId
|
|
|
|
|
* @param type
|
|
|
|
|
* @returns {Promise<ConvertArgumentType<ZMember, string>[]|boolean>}
|
|
|
|
|
*/
|
|
|
|
|
static async getGroupUidList (groupId, charId, type = 'mark') {
|
2023-02-03 15:05:43 +00:00
|
|
|
|
let number = Cfg.get('rankNumber', 15)
|
|
|
|
|
let uids = await redis.zRangeWithScores(`miao:rank:${groupId}:${type}:${charId}`, -`${number}`, -1)
|
2022-11-10 20:56:15 +00:00
|
|
|
|
return uids ? uids.reverse() : false
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-09 19:48:13 +00:00
|
|
|
|
/**
|
|
|
|
|
* 重置群排行
|
|
|
|
|
* @param groupId
|
|
|
|
|
* @param charId
|
|
|
|
|
* @returns {Promise<void>}
|
|
|
|
|
*/
|
2024-01-27 21:59:31 +00:00
|
|
|
|
static async resetRank (groupId, charId = '', game = 'gs') {
|
2022-11-09 19:48:13 +00:00
|
|
|
|
let keys = await redis.keys(`miao:rank:${groupId}:*`)
|
|
|
|
|
for (let key of keys) {
|
2024-01-27 21:59:31 +00:00
|
|
|
|
let charRet = game === 'gs' ? /^miao:rank:\d+:(?:mark|dmg|crit|valid):(\d{8})$/.exec(key) : /^miao:rank:\d+:(?:mark|dmg|crit|valid):(\d{4})$/.exec(key)
|
2022-11-09 19:48:13 +00:00
|
|
|
|
if (charRet) {
|
|
|
|
|
if (charId === '' || charId * 1 === charRet[1] * 1) {
|
|
|
|
|
await redis.del(key)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (charId === '') {
|
|
|
|
|
await redis.del(`miao:rank:${groupId}:cfg`)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static async getGroupCfg (groupId) {
|
2022-11-13 19:42:24 +00:00
|
|
|
|
const rankLimitTxt = {
|
|
|
|
|
1: '无限制',
|
2022-11-13 20:43:59 +00:00
|
|
|
|
2: '绑定有CK的用户',
|
|
|
|
|
3: '绑定CK,或列表有16个角色数据',
|
|
|
|
|
4: '绑定CK,或列表有安柏&凯亚&丽莎的数据',
|
|
|
|
|
5: '绑定CK,或列表有16个角色数据且包含安柏&凯亚&丽莎'
|
2022-11-13 19:42:24 +00:00
|
|
|
|
}
|
|
|
|
|
let rankLimit = Common.cfg('groupRankLimit') * 1 || 1
|
2023-01-17 18:40:17 +00:00
|
|
|
|
let ret = await Data.redisGet(`miao:rank:${groupId}:cfg`, {
|
2022-11-09 19:48:13 +00:00
|
|
|
|
timestamp: (new Date()) * 1,
|
|
|
|
|
status: 0
|
2023-01-17 18:40:17 +00:00
|
|
|
|
})
|
|
|
|
|
await Data.redisSet(`miao:rank:${groupId}:cfg`, ret, 3600 * 24 * 365)
|
2022-11-13 20:43:59 +00:00
|
|
|
|
ret.limitTxt = rankLimitTxt[rankLimit]
|
2022-11-09 19:48:13 +00:00
|
|
|
|
ret.time = moment(new Date(ret.timestamp)).format('MM-DD HH:mm')
|
2023-02-04 21:11:18 +00:00
|
|
|
|
ret.number = Cfg.get('rankNumber', 15)
|
2022-11-09 19:48:13 +00:00
|
|
|
|
return ret
|
2022-11-07 20:08:24 +00:00
|
|
|
|
}
|
2022-11-13 19:42:24 +00:00
|
|
|
|
|
2023-01-17 18:40:17 +00:00
|
|
|
|
/**
|
|
|
|
|
* 设置群开关状态
|
|
|
|
|
* @param groupId
|
|
|
|
|
* @param status:0开启,1关闭
|
|
|
|
|
* @returns {Promise<void>}
|
|
|
|
|
*/
|
|
|
|
|
static async setGroupStatus (groupId, status = 0) {
|
|
|
|
|
let cfg = await Data.redisGet(`miao:rank:${groupId}:cfg`, {
|
|
|
|
|
timestamp: (new Date()) * 1,
|
|
|
|
|
status
|
|
|
|
|
})
|
|
|
|
|
cfg.status = status
|
|
|
|
|
await Data.redisSet(`miao:rank:${groupId}:cfg`, cfg, 3600 * 24 * 365)
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-15 20:58:19 +00:00
|
|
|
|
static async setUidInfo ({ uid, qq, profiles, uidType = 'bind' }) {
|
2022-11-13 19:42:24 +00:00
|
|
|
|
if (!uid) {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
let basicCount = 0
|
|
|
|
|
let totalCount = 0
|
|
|
|
|
for (let charId in profiles) {
|
|
|
|
|
let profile = profiles[charId]
|
|
|
|
|
if (!profile || !profile.hasData) {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
if (['安柏', '凯亚', '丽莎'].includes(profile.name)) {
|
|
|
|
|
basicCount++
|
|
|
|
|
}
|
|
|
|
|
totalCount++
|
|
|
|
|
}
|
2022-11-13 20:43:59 +00:00
|
|
|
|
let data = {}
|
|
|
|
|
try {
|
2022-11-13 23:10:29 +00:00
|
|
|
|
let uData = await redis.get(`miao:rank:uid-info:${uid}`)
|
|
|
|
|
if (uData) {
|
|
|
|
|
data = JSON.parse(uData)
|
2022-11-13 20:43:59 +00:00
|
|
|
|
}
|
|
|
|
|
} catch (e) {
|
|
|
|
|
data = {}
|
|
|
|
|
}
|
2022-11-15 20:07:21 +00:00
|
|
|
|
data.totalCount = totalCount
|
|
|
|
|
data.basicCount = basicCount
|
|
|
|
|
if (data.isSelfUid) {
|
|
|
|
|
delete data.isSelfUid
|
|
|
|
|
data.uidType = 'ck'
|
|
|
|
|
}
|
2022-11-17 19:40:32 +00:00
|
|
|
|
|
2022-11-15 20:07:21 +00:00
|
|
|
|
if (uidType === 'ck') {
|
|
|
|
|
data.uidType = 'ck'
|
2022-11-17 19:40:32 +00:00
|
|
|
|
data.qq = qq || data.qq || ''
|
2022-11-15 20:07:21 +00:00
|
|
|
|
} else {
|
|
|
|
|
data.uidType = data.uidType || 'bind'
|
2022-11-17 19:40:32 +00:00
|
|
|
|
if (data.uidType === 'bind') {
|
|
|
|
|
data.qq = data.qq || qq || ''
|
|
|
|
|
} else {
|
|
|
|
|
data.qq = qq || data.qq || ''
|
|
|
|
|
}
|
2022-11-15 20:07:21 +00:00
|
|
|
|
}
|
|
|
|
|
await redis.set(`miao:rank:uid-info:${uid}`, JSON.stringify(data), { EX: 3600 * 24 * 365 })
|
2022-11-13 19:42:24 +00:00
|
|
|
|
}
|
|
|
|
|
|
2023-02-17 20:42:05 +00:00
|
|
|
|
static async delUidInfo (uid) {
|
|
|
|
|
let keys = await redis.keys('miao:rank:*')
|
|
|
|
|
uid = uid + ''
|
|
|
|
|
if (!/\d{9}/.test(uid)) {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
for (let key of keys) {
|
2023-03-27 19:42:50 +00:00
|
|
|
|
let charRet = /^miao:rank:\d+:(?:mark|dmg|crit|valid):(\d{8})$/.exec(key)
|
2023-02-17 20:42:05 +00:00
|
|
|
|
if (charRet) {
|
|
|
|
|
await redis.zRem(key, uid)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-15 20:58:19 +00:00
|
|
|
|
static async getUidInfo (uid) {
|
|
|
|
|
try {
|
|
|
|
|
let data = await redis.get(`miao:rank:uid-info:${uid}`)
|
|
|
|
|
return JSON.parse(data)
|
|
|
|
|
} catch (e) {
|
|
|
|
|
}
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-03 12:31:13 +00:00
|
|
|
|
static async getUserUidMap (e, game = 'gs') {
|
|
|
|
|
let rn = e.runtime
|
|
|
|
|
let groupMemMap = await e.group?.getMemberMap() || []
|
|
|
|
|
let users = {}
|
|
|
|
|
for (let [qq] of groupMemMap) {
|
|
|
|
|
users[qq] = true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let uidMap = {}
|
|
|
|
|
let qqMap = {}
|
|
|
|
|
let add = (qq, uid, type) => {
|
2023-09-18 19:54:42 +00:00
|
|
|
|
uidMap[uid] = { uid, qq, type: type === 'ck' ? 'ck' : 'bind' }
|
2023-06-03 12:31:13 +00:00
|
|
|
|
qqMap[qq] = true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let keys = await redis.keys('miao:rank:uid-info:*')
|
|
|
|
|
for (let key of keys) {
|
|
|
|
|
let data = await Data.redisGet(key)
|
|
|
|
|
let { qq, uidType } = data
|
|
|
|
|
if (!users[qq]) continue
|
|
|
|
|
let uidRet = /miao:rank:uid-info:(\d{9})/.exec(key)
|
|
|
|
|
if (qq && uidType && uidRet?.[1]) {
|
|
|
|
|
add(qq, uidRet[1], uidType === 'ck' ? 'ck' : 'bind')
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (rn.NoteUser) {
|
|
|
|
|
// Miao-Yunzai
|
|
|
|
|
await rn.NoteUser.forEach(async (user) => {
|
|
|
|
|
if (!users[user.qq]) return true
|
|
|
|
|
let uids = user.getUidList(game)
|
|
|
|
|
lodash.forEach(uids, (ds) => {
|
|
|
|
|
let { uid, type } = ds
|
|
|
|
|
add(user.qq, uid, type)
|
|
|
|
|
})
|
|
|
|
|
})
|
2023-10-10 18:29:04 +00:00
|
|
|
|
} else {
|
2023-06-03 12:31:13 +00:00
|
|
|
|
if (rn?.gsCfg?.getBingCk) {
|
|
|
|
|
// Yunzai-V3
|
|
|
|
|
let noteCks = await rn.gsCfg.getBingCk(game) || {}
|
|
|
|
|
lodash.forEach(noteCks.ck, (ck, _qq) => {
|
|
|
|
|
let qq = ck.qq || _qq
|
|
|
|
|
let uid = ck.uid
|
|
|
|
|
if (!users[qq]) return true
|
|
|
|
|
add(qq, uid, 'ck')
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (let qq in users) {
|
|
|
|
|
if (qqMap[qq]) continue
|
|
|
|
|
let uid = await redis.get(Version.isV3 ? `Yz:genshin:mys:qq-uid:${qq}` : `genshin:id-uid:${qq}`)
|
|
|
|
|
if (uid) {
|
|
|
|
|
add(qq, uid, 'bind')
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return uidMap
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-13 20:43:59 +00:00
|
|
|
|
/**
|
|
|
|
|
* 1: '无限制',
|
|
|
|
|
* 2: '绑定有CK的用户',
|
|
|
|
|
* 3: '面板列表有16个角色数据,或绑定CK',
|
|
|
|
|
* 4: '面板列表有安柏&凯亚&丽莎的数据,或绑定CK',
|
|
|
|
|
* 5: '面板列表有16个角色数据且包含安柏&凯亚&丽莎,或绑定CK'
|
|
|
|
|
* @param uid
|
|
|
|
|
* @returns {Promise<boolean>}
|
|
|
|
|
*/
|
2022-11-13 19:42:24 +00:00
|
|
|
|
static async checkRankLimit (uid) {
|
|
|
|
|
if (!uid) {
|
|
|
|
|
return false
|
|
|
|
|
}
|
2023-09-16 15:43:07 +00:00
|
|
|
|
// 预设面板不参与排名
|
|
|
|
|
if (uid * 1 < 100000006) {
|
2023-02-19 04:05:42 +00:00
|
|
|
|
return false
|
|
|
|
|
}
|
2022-11-13 19:42:24 +00:00
|
|
|
|
try {
|
|
|
|
|
let rankLimit = Common.cfg('groupRankLimit') * 1 || 1
|
|
|
|
|
if (rankLimit === 1) {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
let data = await redis.get(`miao:rank:uid-info:${uid}`)
|
|
|
|
|
data = JSON.parse(data)
|
2022-11-15 20:07:21 +00:00
|
|
|
|
if (data.isSelfUid || data.uidType === 'ck') {
|
2022-11-13 20:43:59 +00:00
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
if (rankLimit === 2) {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
if ((data.totalCount || 0) < 16 && [3, 5].includes(rankLimit)) {
|
2022-11-13 19:42:24 +00:00
|
|
|
|
return false
|
|
|
|
|
}
|
2022-11-13 20:43:59 +00:00
|
|
|
|
if ((data.basicCount || 0) < 3 && [4, 5].includes(rankLimit)) {
|
2022-11-13 19:42:24 +00:00
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
return true
|
|
|
|
|
} catch (e) {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-02-17 20:42:05 +00:00
|
|
|
|
|
|
|
|
|
key (profile, type) {
|
|
|
|
|
return `miao:rank:${this.groupId}:${type}:${profile.id}`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 获取排行信息
|
|
|
|
|
* @param profile
|
|
|
|
|
* @param force
|
|
|
|
|
* @returns {Promise<{}|boolean>}
|
|
|
|
|
*/
|
|
|
|
|
async getRank (profile, force = false) {
|
|
|
|
|
if (!profile || !this.groupId || !this.allowRank || !profile.hasData) {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
let ret = {}
|
2023-03-29 19:27:46 +00:00
|
|
|
|
for (let typeKey of ['mark', 'dmg', 'crit', 'valid']) {
|
2023-02-17 20:42:05 +00:00
|
|
|
|
let typeRank = await this.getTypeRank(profile, typeKey, force)
|
2023-03-29 19:27:46 +00:00
|
|
|
|
if (['mark', 'dmg'].includes(typeKey)) {
|
|
|
|
|
ret[typeKey] = typeRank
|
|
|
|
|
if (!ret.rank || ret.rank >= typeRank.rank) {
|
|
|
|
|
ret.rank = typeRank.rank
|
|
|
|
|
ret.rankType = typeKey
|
|
|
|
|
}
|
2023-02-17 20:42:05 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return ret
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async getTypeRank (profile, type, force) {
|
|
|
|
|
if (!profile || !profile.hasData || !type) {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
if (type === 'dmg' && !profile.hasDmg) {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
const typeKey = this.key(profile, type)
|
|
|
|
|
let value
|
|
|
|
|
let rank
|
|
|
|
|
if (force) {
|
|
|
|
|
value = await this.getTypeValue(profile, type)
|
|
|
|
|
} else {
|
|
|
|
|
rank = await redis.zRevRank(typeKey, this.uid)
|
|
|
|
|
if (!lodash.isNumber(rank)) {
|
|
|
|
|
value = await this.getTypeValue(profile, type)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (value && !lodash.isUndefined(value.score)) {
|
|
|
|
|
await redis.zAdd(typeKey, { score: value.score, value: this.uid })
|
|
|
|
|
}
|
|
|
|
|
if (!lodash.isNumber(rank)) {
|
|
|
|
|
rank = await redis.zRevRank(typeKey, this.uid)
|
|
|
|
|
}
|
|
|
|
|
if (rank === null) {
|
|
|
|
|
rank = 99
|
|
|
|
|
}
|
|
|
|
|
if (force) {
|
|
|
|
|
return {
|
|
|
|
|
rank: rank + 1,
|
|
|
|
|
value: value.score,
|
|
|
|
|
data: value.data
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return {
|
|
|
|
|
rank: rank + 1
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async getTypeValue (profile, type) {
|
|
|
|
|
if (!profile || !profile.hasData) {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
if (type === 'mark') {
|
|
|
|
|
if (!profile?.artis?.hasArtis) {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
let mark = profile.getArtisMark(false)
|
|
|
|
|
if (mark && mark._mark) {
|
|
|
|
|
return {
|
2023-03-27 19:42:50 +00:00
|
|
|
|
score: mark.mark * 1,
|
|
|
|
|
data: mark
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (type === 'crit') {
|
|
|
|
|
if (!profile?.artis?.hasArtis) {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
let mark = profile.getArtisMark(false)
|
|
|
|
|
if (mark && mark._crit) {
|
|
|
|
|
return {
|
|
|
|
|
score: mark._crit * 1,
|
|
|
|
|
data: mark
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (type === 'valid') {
|
|
|
|
|
if (!profile?.artis?.hasArtis) {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
let mark = profile.getArtisMark(false)
|
|
|
|
|
if (mark && mark._valid) {
|
|
|
|
|
return {
|
|
|
|
|
score: mark._valid * 1,
|
2023-02-17 20:42:05 +00:00
|
|
|
|
data: mark
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (type === 'dmg' && profile.hasDmg) {
|
2024-01-27 21:59:31 +00:00
|
|
|
|
let enemyLv = profile.game === 'gs' ? 91 : 80
|
|
|
|
|
let dmg = await profile.calcDmg({ enemyLv, mode: 'single' })
|
2023-02-17 20:42:05 +00:00
|
|
|
|
if (dmg && dmg.avg) {
|
|
|
|
|
return {
|
|
|
|
|
score: dmg.avg,
|
|
|
|
|
data: dmg
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return false
|
|
|
|
|
}
|
2022-11-07 20:08:24 +00:00
|
|
|
|
}
|