diff --git a/CHANGELOG.md b/CHANGELOG.md index 243fb175..86f13a89 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,8 @@ * 其他功能及界面优化,部分已知问题调整 * `#上传深渊` 界面与样式调整 * `#刷新排名`、`#禁用排名`、`#启用排名`可由群管理员进行管理 + * `#喵喵设置` 中可设置禁用获取角色或面板原图功能 + * 增加`#删除面板`命令,目前绑定CK用户使用,Bot主人可删除任意UID数据 # 2.2.1 ~ 2.2.2 diff --git a/apps/character/AvatarCard.js b/apps/character/AvatarCard.js index e34dd87b..40d013cc 100644 --- a/apps/character/AvatarCard.js +++ b/apps/character/AvatarCard.js @@ -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 }, diff --git a/apps/character/AvatarWife.js b/apps/character/AvatarWife.js index 7c7aff0a..ec8900cb 100644 --- a/apps/character/AvatarWife.js +++ b/apps/character/AvatarWife.js @@ -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 } } diff --git a/apps/profile.js b/apps/profile.js index d0868326..8eadb3ea 100644 --- a/apps/profile.js +++ b/apps/profile.js @@ -115,6 +115,12 @@ app.reg({ rule: /^#?\s*(.+)(?:面板图列表)\s*$/, fn: profileImgList, describe: '【#刻晴面板图列表】 删除指定角色面板图(序号)' + }, + + profileDel: { + rule: /^#(删除全部面板|删除面板|删除面板数据)\s*(\d{9})?$/, + fn: ProfileList.del, + describe: '【#角色】 删除游戏橱窗详情数据' } }) diff --git a/apps/profile/ProfileChange.js b/apps/profile/ProfileChange.js index c757a705..7facb7a4 100644 --- a/apps/profile/ProfileChange.js +++ b/apps/profile/ProfileChange.js @@ -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: '圣遗物', diff --git a/apps/profile/ProfileCommon.js b/apps/profile/ProfileCommon.js index 6a17a34e..ae03bf4e 100644 --- a/apps/profile/ProfileCommon.js +++ b/apps/profile/ProfileCommon.js @@ -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 diff --git a/apps/profile/ProfileDetail.js b/apps/profile/ProfileDetail.js index 20e76be7..9caa3ee6 100644 --- a/apps/profile/ProfileDetail.js +++ b/apps/profile/ProfileDetail.js @@ -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 } diff --git a/apps/profile/ProfileList.js b/apps/profile/ProfileList.js index 59298f20..12d6ff38 100644 --- a/apps/profile/ProfileList.js +++ b/apps/profile/ProfileList.js @@ -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} + */ async refresh (e) { let uid = await getTargetUid(e) if (!uid) { @@ -34,6 +39,13 @@ const ProfileList = { return true }, + + /** + * 渲染面板 + * @param e + * @returns {Promise} + */ + 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} + */ + 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 diff --git a/apps/profile/ProfileStat.js b/apps/profile/ProfileStat.js index baffeb06..160f0f18 100644 --- a/apps/profile/ProfileStat.js +++ b/apps/profile/ProfileStat.js @@ -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 \ No newline at end of file +export default ProfileStat diff --git a/apps/profile/ProfileUtils.js b/apps/profile/ProfileUtils.js index 469f9872..ff0b9877 100644 --- a/apps/profile/ProfileUtils.js +++ b/apps/profile/ProfileUtils.js @@ -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) { diff --git a/apps/wiki/Calendar.js b/apps/wiki/Calendar.js index 69b34452..87e98284 100644 --- a/apps/wiki/Calendar.js +++ b/apps/wiki/Calendar.js @@ -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) diff --git a/components/Data.js b/components/Data.js index 722b1ba4..a4c70455 100644 --- a/components/Data.js +++ b/components/Data.js @@ -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}`) diff --git a/config/system/cfg_system.js b/config/system/cfg_system.js index 7d76ec99..cf0e52b7 100644 --- a/config/system/cfg_system.js +++ b/config/system/cfg_system.js @@ -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: '逗号', diff --git a/models/Base.js b/models/Base.js index ed4f69b0..4e17afb2 100644 --- a/models/Base.js +++ b/models/Base.js @@ -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] + } } diff --git a/models/Player.js b/models/Player.js index a888e480..0d408732 100644 --- a/models/Player.js +++ b/models/Player.js @@ -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 diff --git a/models/ProfileRank.js b/models/ProfileRank.js index 7dbea862..2208dcdd 100644 --- a/models/ProfileRank.js +++ b/models/ProfileRank.js @@ -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 + } } diff --git a/models/ProfileReq.js b/models/ProfileReq.js index 8770a18c..e59a13d8 100644 --- a/models/ProfileReq.js +++ b/models/ProfileReq.js @@ -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) { diff --git a/models/player/Profile.js b/models/player/Profile.js index 9cf4c10d..73de0f54 100644 --- a/models/player/Profile.js +++ b/models/player/Profile.js @@ -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 } }, diff --git a/resources/character/profile-list.html b/resources/character/profile-list.html index 1a6a687b..2cf5e4fe 100644 --- a/resources/character/profile-list.html +++ b/resources/character/profile-list.html @@ -57,9 +57,13 @@ {{else}} {{/if}} - 当前更新服务:{{servName}} - + + 当前更新服务:{{servName}} + {{if updateTime.profile }} + ,更新时间:{{updateTime.profile }} + {{/if}} + {{/block}} \ No newline at end of file diff --git a/resources/character/profile-stat.css b/resources/character/profile-stat.css index f1122b42..3409b88c 100644 --- a/resources/character/profile-stat.css +++ b/resources/character/profile-stat.css @@ -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 */ \ No newline at end of file diff --git a/resources/character/profile-stat.html b/resources/character/profile-stat.html index 94652f1c..9f109141 100644 --- a/resources/character/profile-stat.html +++ b/resources/character/profile-stat.html @@ -49,6 +49,7 @@ + {{set talentLvMap = [0,1,1,1,2,2,3,3,3,4,5] }} {{each tk talentKey}} {{set curr = (avatar.talent||{})[talentKey] || {original:1,level:'-'} }}
@@ -94,9 +95,22 @@
{{/each}} - {{if talentNotice}} -

{{@talentNotice}}

- {{/if}} + +
+ {{set ut = updateTime }} + {{if ut.profile || ut.mys}} + 数据更新时间 + {{if ut.profile}} + #更新面板: {{ut.profile}} + {{/if}} + {{if ut.mys}} + 米游社: {{ut.mys}} + {{/if}} + {{else}} + 未绑定CK或CK失效,信息可能不完全。发送#体力帮助查看CK绑定方法,发送#更新面板更新游戏内角色展柜信息 + {{/if}} +
+ diff --git a/resources/character/profile-stat.less b/resources/character/profile-stat.less index b0c54cbc..5e93d750 100644 --- a/resources/character/profile-stat.less +++ b/resources/character/profile-stat.less @@ -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; } } \ No newline at end of file diff --git a/resources/meta/character/流浪者/icons/cons-3.webp b/resources/meta/character/流浪者/icons/cons-3.webp index 0e56a279..6cfbeca8 100644 Binary files a/resources/meta/character/流浪者/icons/cons-3.webp and b/resources/meta/character/流浪者/icons/cons-3.webp differ diff --git a/resources/meta/character/流浪者/icons/cons-5.webp b/resources/meta/character/流浪者/icons/cons-5.webp index 6cfbeca8..0e56a279 100644 Binary files a/resources/meta/character/流浪者/icons/cons-5.webp and b/resources/meta/character/流浪者/icons/cons-5.webp differ