#喵喵设置 中可设置禁用获取角色或面板原图功能

增加`#删除面板`命令,目前绑定CK用户使用,Bot主人可删除任意UID数据
This commit is contained in:
Kokomi 2023-02-18 04:42:05 +08:00
parent 036d61d698
commit 6b11badb66
24 changed files with 284 additions and 131 deletions

View File

@ -15,6 +15,8 @@
* 其他功能及界面优化,部分已知问题调整
* `#上传深渊` 界面与样式调整
* `#刷新排名`、`#禁用排名`、`#启用排名`可由群管理员进行管理
* `#喵喵设置` 中可设置禁用获取角色或面板原图功能
* 增加`#删除面板`命令目前绑定CK用户使用Bot主人可删除任意UID数据
# 2.2.1 ~ 2.2.2

View File

@ -88,7 +88,7 @@ let Avatar = {
}, { e, scale, retMsgId: true })
if (msgRes && msgRes.message_id) {
// 如果消息发送成功就将message_id和图片路径存起来3小时过期
await redis.set(`miao:original-picture:${msgRes.message_id}`, bg.img, { EX: 3600 * 3 })
await redis.set(`miao:original-picture:${msgRes.message_id}`, JSON.stringify({ type: 'character', img: bg.img }), { EX: 3600 * 3 })
}
return true
},

View File

@ -39,7 +39,7 @@ async function getAvatarList (player, type) {
let list = []
player.forEachAvatar((avatar) => {
if (type !== false) {
if (!avatar.char.checkWifeType(avatar.id, type)) {
if (!avatar.char.checkWifeType(type)) {
return true
}
}

View File

@ -115,6 +115,12 @@ app.reg({
rule: /^#?\s*(.+)(?:面板图列表)\s*$/,
fn: profileImgList,
describe: '【#刻晴面板图列表】 删除指定角色面板图(序号)'
},
profileDel: {
rule: /^#(删除全部面板|删除面板|删除面板数据)\s*(\d{9})?$/,
fn: ProfileList.del,
describe: '【#角色】 删除游戏橱窗详情数据'
}
})

View File

@ -3,7 +3,7 @@
*/
import lodash from 'lodash'
import { Data } from '../../components/index.js'
import { Character, ProfileData, Weapon,Player } from '../../models/index.js'
import { Character, ProfileData, Weapon, Player } from '../../models/index.js'
const keyMap = {
artis: '圣遗物',

View File

@ -87,7 +87,9 @@ export async function getProfileRefresh (e, avatar) {
profile = player.getProfile(char.id)
}
if (!profile || !profile.hasData) {
e.reply(`请确认${char.name}已展示在【游戏内】的角色展柜中,并打开了“显示角色详情”。然后请使用 #更新面板\n命令来获取${char.name}的面板详情`)
if (!e._isReplyed) {
e.reply(`请确认${char.name}已展示在【游戏内】的角色展柜中,并打开了“显示角色详情”。然后请使用 #更新面板\n命令来获取${char.name}的面板详情`)
}
return false
}
return profile

View File

@ -205,7 +205,7 @@ export async function renderProfile (e, char, mode = 'profile', params = {}) {
}, { e, scale: 1.6, retMsgId: true })
if (msgRes && msgRes.message_id) {
// 如果消息发送成功就将message_id和图片路径存起来3小时过期
await redis.set(`miao:original-picture:${msgRes.message_id}`, costumeSplash, { EX: 3600 * 3 })
await redis.set(`miao:original-picture:${msgRes.message_id}`, JSON.stringify({ type: 'profile', img: costumeSplash }), { EX: 3600 * 3 })
}
return true
}

View File

@ -4,6 +4,11 @@ import { ProfileRank, Player, Character } from '../../models/index.js'
import { Common, Data } from '../../components/index.js'
const ProfileList = {
/**
* 刷新面板
* @param e
* @returns {Promise<boolean|*>}
*/
async refresh (e) {
let uid = await getTargetUid(e)
if (!uid) {
@ -34,6 +39,13 @@ const ProfileList = {
return true
},
/**
* 渲染面板
* @param e
* @returns {Promise<boolean|*>}
*/
async render (e) {
let uid = await getTargetUid(e)
if (!uid) {
@ -113,9 +125,44 @@ const ProfileList = {
hasNew,
msg,
groupRank,
updateTime: player.getUpdateTime(),
allowRank: rank && rank.allowRank,
rankCfg
}, { e, scale: 1.6 })
},
/**
* 删除面板数据
* @param e
* @returns {Promise<boolean>}
*/
async del (e) {
let ret = /^#(删除全部面板|删除面板|删除面板数据)\s*(\d{9})?$/.exec(e.msg)
let uid = await getTargetUid(e)
if (!uid) {
return true
}
let targetUid = ret[2]
let user = e?.runtime?.user || {}
if (!user.hasCk && !e.isMaster) {
e.reply('为确保数据安全目前仅允许绑定CK用户删除自己UID的面板数据请联系Bot主人删除...')
return true
}
if (!targetUid) {
e.reply(`你确认要删除面板数据吗? 请回复 #删除面板${uid} 以删除面板数据`)
return true
}
let ckUids = (user?.ckUids || []).join(',').split(',')
if (!ckUids.includes(targetUid) && !e.isMaster) {
e.reply(`仅允许删除自己的UID数据[${ckUids.join(',')}]`)
return true
}
Player.delByUid(targetUid)
e.reply(`UID${targetUid}的本地数据已删除,排名数据已清除...`)
return true
}
}
export default ProfileList

View File

@ -76,11 +76,10 @@ const ProfileStat = {
info,
updateTime: player.getUpdateTime(),
isSelfCookie: e.isSelfCookie,
talentLvMap: '0,1,1,1,2,2,3,3,3,4,5'.split(','),
face,
avatars: avatarRet,
talentNotice
}, { e, scale: 1.4 })
}
}
export default ProfileStat
export default ProfileStat

View File

@ -1,5 +1,6 @@
import { segment } from 'oicq'
import { MysApi } from '../../models/index.js'
import { Cfg } from '../../components/index.js'
/** 获取角色卡片的原图 */
export async function getOriginalPicture (e) {
@ -14,6 +15,7 @@ export async function getOriginalPicture (e) {
if (!/^\[图片]$/.test(e.source.message)) {
return true
}
let originalPic = Cfg.get('originalPic') * 1
// 获取原消息
let source
if (e.isGroup) {
@ -24,7 +26,25 @@ export async function getOriginalPicture (e) {
if (source) {
let imgPath = await redis.get(`miao:original-picture:${source.message_id}`)
if (imgPath) {
e.reply([segment.image(process.cwd() + '/plugins/miao-plugin/resources/' + decodeURIComponent(imgPath))], false, { recallMsg: 30 })
try {
if (imgPath[0] === '{') {
imgPath = JSON.parse(imgPath)
} else {
imgPath = { img: imgPath, type: '' }
}
} catch (e) {
}
if (imgPath.type === 'character' && [2, 0].includes(originalPic)) {
e.reply('已禁止获取角色原图...')
return true
}
if (imgPath.type === 'profile' && [1, 0].includes(originalPic)) {
e.reply('已禁止获取面板原图...')
return true
}
if (imgPath && imgPath.img) {
e.reply([segment.image(process.cwd() + '/plugins/miao-plugin/resources/' + decodeURIComponent(imgPath.img))], false, { recallMsg: 30 })
}
return true
}
if (source.time) {

View File

@ -1,7 +1,7 @@
import fetch from 'node-fetch'
import moment from 'moment'
import { Character, Material } from '../../models/index.js'
import { Common, Data } from '../../components/index.js'
import { Common, Data, Cfg } from '../../components/index.js'
import lodash from 'lodash'
const ignoreIds = [495, // 有奖问卷调查开启!
@ -227,7 +227,7 @@ let Cal = {
data.weekly = char.getMaterials('weekly')?.icon
charTalent[t].chars.push(data)
}
}, 'official')
}, Cfg.get('notReleasedData') ? 'official' : 'release')
let charNum = 0
lodash.forEach(charBirth, (charList) => {
charNum = Math.max(charNum, charList.length)

View File

@ -58,9 +58,12 @@ let Data = {
delete data._res
return fs.writeFileSync(`${root}/${file}`, JSON.stringify(data, null, space))
},
delfile (file) {
delFile (file, root = '') {
root = getRoot(root)
try {
fs.unlinkSync(`${_path}/${file}`)
if (fs.existsSync(`${root}/${file}`)) {
fs.unlinkSync(`${root}/${file}`)
}
return true
} catch (error) {
logger.error(`文件删除失败:${error}`)

View File

@ -138,6 +138,14 @@ export const cfgSchema = {
input: (n) => Math.min(200, Math.max(50, (n * 1 || 100))),
desc: '可选值50~200建议100。设置高精度会提高图片的精细度但因图片较大可能会影响渲染与发送速度'
},
originalPic: {
title: '原图',
key: '原图',
type: 'num',
def: 3,
input: (n) => Math.min(3, Math.max(n * 1 || 0, 0)),
desc: '允许获取原图0:不允许, 1:仅允许角色图, 2:仅允许面板图, 3:开启'
},
commaGroup: {
title: '数字逗号分组',
key: '逗号',

View File

@ -73,17 +73,23 @@ export default class Base {
// 设置超时时间
_expire (time = 10 * 60) {
let id = this._uuid
let self = this
reFn[id] && clearTimeout(reFn[id])
if (time > 0) {
if (id) {
reFn[id] = setTimeout(() => {
reFn[id] && clearTimeout(reFn[id])
delete reFn[id]
delete cacheMap[id]
delete metaMap[id]
self._delCache()
}, time * 1000)
}
return cacheMap[id]
}
}
_delCache () {
let id = this._uuid
reFn[id] && clearTimeout(reFn[id])
delete reFn[id]
delete cacheMap[id]
delete metaMap[id]
}
}

View File

@ -58,6 +58,13 @@ export default class Player extends Base {
return Serv.name
}
static delByUid (uid) {
let player = Player.create(uid)
if (player) {
player.del()
}
}
/**
* 重新加载json文件
*/
@ -89,6 +96,18 @@ export default class Player extends Base {
Data.writeJSON(`/data/UserData/${this.uid}.json`, ret, '', 'root')
}
del () {
try {
Data.delFile(`/data/UserData/${this.uid}.json`, 'root')
ProfileRank.delUidInfo(this.uid)
this._delCache()
Bot.logger.mark(`【面板数据删除】${this.uid}本地文件数据已删除...`)
} catch (e) {
console.log('del error', e)
}
return true
}
/**
* 设置玩家基础数据
* @param ds

View File

@ -19,99 +19,6 @@ export default class ProfileRank {
return rank
}
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 = {}
for (let typeKey of ['mark', 'dmg']) {
let typeRank = await this.getTypeRank(profile, typeKey, force)
ret[typeKey] = typeRank
if (!ret.rank || ret.rank >= typeRank.rank) {
ret.rank = typeRank.rank
ret.rankType = typeKey
}
}
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 {
score: mark._mark * 1,
data: mark
}
}
}
if (type === 'dmg' && profile.hasDmg) {
let dmg = await profile.calcDmg({ mode: 'single' })
if (dmg && dmg.avg) {
return {
score: dmg.avg,
data: dmg
}
}
}
return false
}
/**
* 获取群排行UID
* @param groupId
@ -258,6 +165,20 @@ export default class ProfileRank {
await redis.set(`miao:rank:uid-info:${uid}`, JSON.stringify(data), { EX: 3600 * 24 * 365 })
}
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) {
let charRet = /^miao:rank:\d+:(?:mark|dmg):(\d{8})$/.exec(key)
if (charRet) {
await redis.zRem(key, uid)
}
}
}
static async getUidInfo (uid) {
try {
let data = await redis.get(`miao:rank:uid-info:${uid}`)
@ -304,4 +225,97 @@ export default class ProfileRank {
return false
}
}
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 = {}
for (let typeKey of ['mark', 'dmg']) {
let typeRank = await this.getTypeRank(profile, typeKey, force)
ret[typeKey] = typeRank
if (!ret.rank || ret.rank >= typeRank.rank) {
ret.rank = typeRank.rank
ret.rankType = typeKey
}
}
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 {
score: mark._mark * 1,
data: mark
}
}
}
if (type === 'dmg' && profile.hasDmg) {
let dmg = await profile.calcDmg({ mode: 'single' })
if (dmg && dmg.avg) {
return {
score: dmg.avg,
data: dmg
}
}
}
return false
}
}

View File

@ -40,9 +40,7 @@ export default class ProfileReq extends Base {
empty: '请将角色放置在【游戏内】角色展柜并打开【显示详情】等待5分钟重新获取面板'
}
msg = msgs[msg] || msg
if (msg) {
this.e.reply(msg)
}
this.msg(msg)
// 设置CD
if (cd) {
this.setCd(cd)
@ -51,7 +49,11 @@ export default class ProfileReq extends Base {
}
msg (msg) {
this.e.reply(msg)
let e = this.e
if (msg && !e._isReplyed) {
e.reply(msg)
e._isReplyed = true
}
}
async requestProfile (player, serv) {

View File

@ -55,7 +55,9 @@ const Profile = {
player.save()
return player._update.length
} catch (err) {
e.reply(`UID:${uid}更新面板失败,更新服务:${serv.name}`)
if (!e._isReplyed) {
e.reply(`UID:${uid}更新面板失败,更新服务:${serv.name}`)
}
return false
}
},

View File

@ -57,9 +57,13 @@
{{else}}
<span></span>
{{/if}}
<span class="serv">当前更新服务:{{servName}}</span>
<span class="serv">
当前更新服务:{{servName}}
{{if updateTime.profile }}
<span>,更新时间:{{updateTime.profile }}</span>
{{/if}}
</span>
</div>
</div>
{{/block}}

View File

@ -358,14 +358,16 @@ body {
margin-right: -60px;
}
.cont-notice {
color: #888;
background: rgba(255, 255, 255, 0.4);
background: rgba(0, 0, 0, 0.7);
font-size: 13px;
text-align: center;
padding: 8px;
text-align: right;
padding: 8px 12px 8px 8px;
}
.cont-notice strong {
color: #d3bc8e;
font-weight: normal;
}
.cont-notice span {
padding: 0 3px;
color: #555;
margin-left: 5px;
}
/*# sourceMappingURL=profile-stat.css.map */

View File

@ -49,6 +49,7 @@
<span class="fetter fetter{{['空','荧','旅行者'].includes(avatar.name)?10:avatar.fetter}}"></span>
</div>
{{set talentLvMap = [0,1,1,1,2,2,3,3,3,4,5] }}
{{each tk talentKey}}
{{set curr = (avatar.talent||{})[talentKey] || {original:1,level:'-'} }}
<div class="td-talent lv{{talentLvMap[curr.original]}} {{curr.level>curr.original?'talent-plus':''}}">
@ -94,9 +95,22 @@
</div>
{{/each}}
</div>
{{if talentNotice}}
<p class="cont-notice">{{@talentNotice}}</p>
{{/if}}
<div class="cont-notice">
{{set ut = updateTime }}
{{if ut.profile || ut.mys}}
<strong>数据更新时间</strong>
{{if ut.profile}}
<span>#更新面板: {{ut.profile}}</span>
{{/if}}
{{if ut.mys}}
<span>米游社: {{ut.mys}}</span>
{{/if}}
{{else}}
未绑定CK或CK失效信息可能不完全。发送<strong>#体力帮助</strong>查看CK绑定方法发送<strong>#更新面板</strong>更新游戏内角色展柜信息
{{/if}}
</div>
</div>
</div>

View File

@ -402,14 +402,17 @@ body {
}
.cont-notice {
color: #888;
background: rgba(255, 255, 255, .4);
background: rgba(0, 0, 0, .7);
font-size: 13px;
text-align: center;
padding: 8px;
text-align: right;
padding: 8px 12px 8px 8px;
strong {
color: #d3bc8e;
font-weight: normal;
}
span {
padding: 0 3px;
color: #555;
margin-left: 5px;
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.5 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 8.5 KiB