Merge pull request #469 from yoimiya-kokomi/dev

v2.3.0 发布
This commit is contained in:
Kokomi 2023-02-19 02:14:28 +08:00 committed by GitHub
commit b8d1db30cc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
139 changed files with 5078 additions and 4128 deletions

View File

@ -1,18 +1,27 @@
# 2.2.2
# 2.3.0
* 重写底层面板、角色数据获取与保存逻辑
* 底层完全兼容面板及Mys数据对于miao-plugin的大部分场景可做到数据通用
* 角色数据及天赋增加缓存逻辑有缓存数据情况下可在ck失效/验证码等情况下正常使用大部分功能
* 全量使用通过圣遗物属性计算得到的面板数据
* 面板底层数据结构及存储逻辑优化,兼容老版本数据
* Enka服务下使用statsIds存储圣遗物数据能够更精确的计算角色属性
* 增加`#角色`功能查询并展示Mys角色信息
* Yunzai需要跟随游戏版本升级的功能会逐步在miao-plugin中提供以保障基础功能相对长期可用
* 大部分功能目前默认关闭,可在`#喵喵设置`中设置并开启
* 为`#喵喵设置`增加更多配置项
* 允许禁用面板替换功能
* 允许禁用非实装角色资料,关闭可禁用非实装角色资料及面板替换
* 允许禁用面板替换功能
* 允许禁用获取角色或面板原图功能
* 可选择面板服务可选喵喵Api优先需具备Token或Enka优先
* 更新迪希雅、米卡的最新天赋与命座数据
* 全量使用通过属性计算得到的面板数据,移除相关配置项
# 2.2.1
* 增加瑶瑶伤害计算
* 增加丽莎的皮肤数据,需要重新更新面板数据以获得信息
* 为群排名人数、圣遗物列表展示数增加`#喵喵设置`的配置 **@SmallK111407**
* 可通过`#喵喵设置`配置`#面板练度统计`替换Yunzai`#练度统计`功能 **@SmallK111407**
* 可设置群排名人数、圣遗物列表展示数 **@SmallK111407**
* 角色信息及伤害计算更新
* 更新迪希雅、米卡的最新天赋与命座数据
* 增加瑶瑶伤害计算
* 其他功能及界面优化,部分已知问题调整
* `#上传深渊` 界面与样式调整
* `#刷新排名`、`#禁用排名`、`#启用排名`可由群管理员进行管理
* 增加`#删除面板`命令目前绑定CK用户使用Bot主人可删除任意UID数据
# 2.2.0
@ -154,11 +163,11 @@
* 部分依赖MysApi查询的功能在V3下暂时只支持查自己
* 增加提纳里、柯莱、多莉的资料及角色图像
* 可通过 `#柯莱天赋`、`#柯莱命座`查看资料
* 增加 `#深渊使用率`命令,数据源自SG团队胡桃API
* 增加 `#深渊使用率`命令,数据源自DGP-Studio胡桃API
* 新增 `#上传深渊数据`命令
* 上传自己角色的深渊挑战数据及角色列表,并展示在本期深渊中伤害与承伤排名
* 上传数据用于 `#角色持有率 #深渊出场率`等统计,可使统计更加及时准确
* 数据统计及服务来自SG团队胡桃API
* 数据统计及服务来自DGP-Studio胡桃API
* 增加 `#添加刻晴图像`命令,感谢 **@叶**
* 可通过命令上传添加指定角色图片,上传至 **resources/character-img/刻晴/upload**
* 请将图像与命令一同发送后续会支持at图像及命令后发送图像
@ -267,7 +276,7 @@
* 增加 `#深渊配队` 功能
* 根据当前账号的角色练度及本期深渊出场数据,推荐较匹配的配队方案
* 深渊出场数据来自胡桃API为Snap Genshin用户自主上传的深渊挑战记录感谢SG团队
* 深渊出场数据来自DGP-Studio胡桃API
* 配队方案仅供参考
* `#角色面板` 伤害计算新增部分角色
* 目前支持:雷神、胡桃、魈、神子、甘雨、宵宫、公子、绫人、绫华、心海、钟离

View File

@ -12,17 +12,22 @@ let app = App.init({
let sysCfgReg = new RegExp(`^#喵喵设置\\s*(${keys.join('|')})?\\s*(.*)$`)
app.reg('update-res', updateRes, {
rule: /^#喵喵(强制)?(更新图像|图像更新)$/,
desc: '【#管理】更新素材'
})
app.reg('update', updateMiaoPlugin, {
rule: /^#喵喵(强制)?更新/,
desc: '【#管理】喵喵更新'
})
app.reg('sys-cfg', sysCfg, {
rule: sysCfgReg,
desc: '【#管理】系统设置'
app.reg({
updateRes: {
rule: /^#喵喵(强制)?(更新图像|图像更新)$/,
fn: updateRes,
desc: '【#管理】更新素材'
},
update: {
rule: /^#喵喵(强制)?更新/,
fn: updateMiaoPlugin,
desc: '【#管理】喵喵更新'
},
sysCfg: {
rule: sysCfgReg,
fn: sysCfg,
desc: '【#管理】系统设置'
}
})
export default app

View File

@ -1,71 +1,36 @@
import { Common, App } from '../components/index.js'
import { Character } from '../models/index.js'
import { renderAvatar } from './character/AvatarCard.js'
import { App } from '../components/index.js'
import { uploadCharacterImg } from './character/ImgUpload.js'
import { wife, wifeReg } from './character/AvatarWife.js'
import { getOriginalPicture } from './profile/ProfileUtils.js'
import Avatar from './character/AvatarCard.js'
import Wife from './character/AvatarWife.js'
let app = App.init({
id: 'character',
name: '角色查询'
})
app.reg('character', character, {
rule: /^#喵喵角色卡片$/,
check: checkCharacter,
name: '角色卡片'
})
app.reg('upload-img', uploadCharacterImg, {
rule: /^#*(喵喵)?(上传|添加)(.+)(照片|写真|图片|图像)\s*$/,
name: '上传角色写真'
})
app.reg('wife', wife, {
rule: wifeReg,
describe: '#老公 #老婆 查询'
})
app.reg('original-pic', getOriginalPicture, {
rule: /^#?(获取|给我|我要|求|发|发下|发个|发一下)?原图(吧|呗)?$/,
describe: '【#原图】 回复角色卡片,可获取原图'
app.reg({
character: {
rule: /^#喵喵角色卡片$/,
fn: Avatar.render,
check: Avatar.check,
name: '角色卡片'
},
uploadImg: {
rule: /^#*(喵喵)?(上传|添加)(.+)(照片|写真|图片|图像)\s*$/,
fn: uploadCharacterImg,
name: '上传角色写真'
},
wife: {
rule: Wife.reg,
fn: Wife.render,
describe: '#老公 #老婆 查询'
},
originalPic: {
rule: /^#?(获取|给我|我要|求|发|发下|发个|发一下)?原图(吧|呗)?$/,
fn: getOriginalPicture,
describe: '【#原图】 回复角色卡片,可获取原图'
}
})
export default app
// 查看当前角色
export async function character (e) {
if (!e.char) {
return false
}
return renderAvatar(e, e.char?.name)
}
function checkCharacter (e) {
let msg = e.original_msg || e.msg
if (!msg || !/^#/.exec(msg)) {
return false
}
if (!Common.cfg('avatarCard')) {
return false
}
let uidRet = /[0-9]{9}/.exec(msg)
if (uidRet) {
e.uid = uidRet[0]
msg = msg.replace(uidRet[0], '')
}
let name = msg.replace(/#|老婆|老公|卡片/g, '').trim()
// cache gsCfg
Character.gsCfg = Character.gsCfg || e?.runtime?.gsCfg
let char = Character.get(name.trim())
if (!char) {
return false
}
e.msg = '#喵喵角色卡片'
e.char = char
return true
}

View File

@ -1,124 +1,125 @@
import { Character, Avatar, MysApi } from '../../models/index.js'
import { Cfg, Common, Profile } from '../../components/index.js'
import { Character, MysApi, Player } from '../../models/index.js'
import { Cfg, Common } from '../../components/index.js'
import lodash from 'lodash'
import { segment } from 'oicq'
import moment from 'moment'
export async function renderAvatar (e, avatar, renderType = 'card') {
// 如果传递的是名字,则获取
let uid = e.uid
if (typeof (avatar) === 'string') {
let char = Character.get(avatar)
let Avatar = {
render (e) {
if (!e.char) {
return false
}
return Avatar.renderAvatar(e, e.char?.name)
},
async renderAvatar (e, avatar, renderType = 'card') {
// 如果传递的是名字,则获取
if (typeof (avatar) === 'string') {
// 检查角色
let char = Character.get(avatar)
if (!char) {
return false
}
let mys = await MysApi.init(e)
if (!mys) return true
if (!char.isRelease) {
avatar = { id: char.id, name: char.name, detail: false }
} else {
let player = Player.create(e)
await player.refreshMysDetail()
await player.refreshTalent(char.id)
avatar = player.getAvatar(char.id)
if (!avatar) {
avatar = { id: char.id, name: char.name, detail: false }
}
}
}
return await Avatar.renderCard(e, avatar, renderType)
},
async renderCard (e, avatar, renderType = 'card') {
let char = Character.get(avatar.id)
if (!char) {
return false
}
let mys = await MysApi.init(e)
if (!mys) return true
uid = mys.uid
if (!char.isRelease) {
avatar = { id: char.id, name: char.name, detail: false }
} else {
let profile = Profile.get(uid, char.id, true)
if (profile && profile.hasData) {
// 优先使用Profile数据
avatar = profile
} else {
// 使用Mys数据兜底
let charData = await mys.getCharacter()
if (!charData) return true
let avatars = charData.avatars
if (char.isTraveler) {
char = await char.checkAvatars(avatars, uid)
}
avatars = lodash.keyBy(avatars, 'id')
avatar = avatars[char.id] || { id: char.id, name: char.name, detail: false }
}
let bg = char.getCardImg(Cfg.get('charPicSe', false))
if (renderType === 'photo') {
e.reply(segment.image(process.cwd() + '/plugins/miao-plugin/resources/' + bg.img))
return true
}
let uid = e.uid || (e.targetUser && e.targetUser.uid)
let data = {}
let custom = char.isCustom
let isRelease = char.isRelease
if (isRelease) {
data = avatar.getDetail()
data.imgs = char.imgs
data.source = avatar._source
data.artis = avatar.getArtisDetail()
data.updateTime = moment(new Date(avatar._time)).format('MM-DD HH:mm')
if (data.hasTalent) {
data.talent = avatar.talent
data.talentMap = ['a', 'e', 'q']
// 计算皇冠个数
data.crownNum = lodash.filter(lodash.map(data.talent, (d) => d.original), (d) => d >= 10).length
}
} else {
data = char.getData('id,name,sName')
}
}
return await renderCard(e, avatar, renderType)
}
// 渲染角色卡片
async function renderCard (e, ds, renderType = 'card') {
let char = Character.get(ds)
if (!char) {
return false
}
let bg = char.getCardImg(Cfg.get('charPicSe', false))
if (renderType === 'photo') {
e.reply(segment.image(process.cwd() + '/plugins/miao-plugin/resources/' + bg.img))
let width = 600
let imgCss = ''
let scale = 1.2
if (bg.mode === 'left') {
const height = 480
width = height * bg.width / bg.height
imgCss = `img.bg{width:auto;height:${height}px;}`
scale = 1.45
}
// 渲染图像
let msgRes = await Common.render('character/character-card', {
saveId: uid,
uid,
bg,
widthStyle: `<style>html,body,#container{width:${width}px} ${imgCss}</style>`,
mode: bg.mode,
custom,
isRelease,
data
}, { e, scale, retMsgId: true })
if (msgRes && msgRes.message_id) {
// 如果消息发送成功就将message_id和图片路径存起来3小时过期
await redis.set(`miao:original-picture:${msgRes.message_id}`, JSON.stringify({ type: 'character', img: bg.img }), { EX: 3600 * 3 })
}
return true
},
check (e) {
let msg = e.original_msg || e.msg
if (!msg || !/^#/.exec(msg)) {
return false
}
if (!Common.cfg('avatarCard')) {
return false
}
let uidRet = /[0-9]{9}/.exec(msg)
if (uidRet) {
e.uid = uidRet[0]
msg = msg.replace(uidRet[0], '')
}
let name = msg.replace(/#|老婆|老公|卡片/g, '').trim()
// cache gsCfg
Character.gsCfg = Character.gsCfg || e?.runtime?.gsCfg
let char = Character.get(name.trim())
if (!char) {
return false
}
e.msg = '#喵喵角色卡片'
e.char = char
return true
}
let uid = e.uid || (e.targetUser && e.targetUser.uid)
let data = {}
let custom = char.isCustom
let isRelease = char.isRelease
if (isRelease) {
let mys = await MysApi.init(e)
let avatar = new Avatar(ds, uid, mys.isSelfCookie)
data = avatar.getData('id,name,sName,level,fetter,cons,weapon,elem,artis,artisSet,imgs,dataSourceName,updateTime')
data.talent = await avatar.getTalent(mys)
if (data.talent) {
data.talentMap = ['a', 'e', 'q']
// 计算皇冠个数
data.crownNum = lodash.filter(lodash.map(data.talent, (d) => d.original), (d) => d >= 10).length
}
} else {
data = char.getData('id,name,sName')
}
let width = 600
let imgCss = ''
let scale = 1.2
if (bg.mode === 'left') {
const height = 480
width = height * bg.width / bg.height
imgCss = `img.bg{width:auto;height:${height}px;}`
scale = 1.45
}
// 渲染图像
let msgRes = await Common.render('character/character-card', {
saveId: uid,
uid,
bg,
widthStyle: `<style>html,body,#container{width:${width}px} ${imgCss}</style>`,
mode: bg.mode,
custom,
isRelease,
data
}, { 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 })
}
return true
}
export async function getAvatarList (e, type, mys) {
let data = await mys.getCharacter()
if (!data) return false
let avatars = data.avatars
if (!avatars || avatars.length <= 0) {
return false
}
let list = []
for (let val of avatars) {
if (type !== false) {
if (!Character.checkWifeType(val.id, type)) {
continue
}
}
if (val.rarity > 5) {
val.rarity = 5
}
list.push(val)
}
if (list.length <= 0) {
return false
}
let sortKey = 'level,fetter,weapon_level,rarity,weapon_rarity,cons,weapon_affix_level'
list = lodash.orderBy(list, sortKey, lodash.repeat('desc,', sortKey.length).split(','))
return list
}
export default Avatar

View File

@ -1,8 +1,8 @@
// #老婆
import lodash from 'lodash'
import { Common } from '../../components/index.js'
import { Character, MysApi, AvatarList } from '../../models/index.js'
import { getAvatarList, renderAvatar } from './AvatarCard.js'
import { Character, MysApi, Player } from '../../models/index.js'
import Avatar from './AvatarCard.js'
const relationMap = {
wife: {
@ -32,140 +32,157 @@ const relationMap = {
}
const relation = lodash.flatMap(relationMap, (d) => d.keyword)
export const wifeReg = `^#?\\s*(${relation.join('|')})\\s*(设置|选择|指定|列表|查询|列表|是|是谁|照片|相片|图片|写真|图像)?\\s*([^\\d]*)\\s*(\\d*)$`
const wifeReg = `^#?\\s*(${relation.join('|')})\\s*(设置|选择|指定|列表|查询|列表|是|是谁|照片|相片|图片|写真|图像)?\\s*([^\\d]*)\\s*(\\d*)$`
export async function wife (e) {
let msg = e.msg || ''
if (!msg && !e.isPoke) return false
async function getAvatarList (player, type) {
await player.refreshMysDetail()
let list = []
player.forEachAvatar((avatar) => {
if (type !== false) {
if (!avatar.char.checkWifeType(type)) {
return true
}
}
list.push(avatar)
})
if (e.isPoke) {
if (!Common.cfg('avatarPoke')) {
if (list.length <= 0) {
return false
}
let sortKey = 'level,fetter,weapon_level,rarity,weapon_rarity,cons,weapon_affix_level'
list = lodash.orderBy(list, sortKey, lodash.repeat('desc,', sortKey.length).split(','))
return list
}
const Wife = {
reg: wifeReg,
async render (e) {
let msg = e.msg || ''
if (!msg && !e.isPoke) return false
if (e.isPoke) {
if (!Common.cfg('avatarPoke')) {
return false
}
} else if (!Common.cfg('avatarCard')) {
return false
}
} else if (!Common.cfg('avatarWife')) {
return false
}
let msgRet = (new RegExp(wifeReg)).exec(msg)
if (e.isPoke) {
msgRet = []
} else if (!msgRet) {
return false
}
let target = msgRet[1]
let action = msgRet[2] || '卡片'
let actionParam = msgRet[3] || ''
let msgRet = (new RegExp(wifeReg)).exec(msg)
if (e.isPoke) {
msgRet = []
} else if (!msgRet) {
return false
}
let target = msgRet[1]
let action = msgRet[2] || '卡片'
let actionParam = msgRet[3] || ''
if (!'设置,选择,挑选,指定'.split(',').includes(action) && actionParam) {
return false
}
if (!'设置,选择,挑选,指定'.split(',').includes(action) && actionParam) {
return false
}
let targetCfg = lodash.find(relationMap, (cfg, key) => {
cfg.key = key
return cfg.keyword.includes(target)
})
if (!targetCfg && !e.isPoke) return true
let targetCfg = lodash.find(relationMap, (cfg, key) => {
cfg.key = key
return cfg.keyword.includes(target)
})
if (!targetCfg && !e.isPoke) return true
let avatarList = []
let avatar = {}
let wifeList = []
let avatarList = []
let avatar = {}
let wifeList = []
let mys = await MysApi.init(e)
if (!mys || !mys.uid) {
return true
}
let selfUser = mys.selfUser
let isSelf = true
let renderType = (action === '卡片' ? 'card' : 'photo')
let addRet = []
switch (action) {
case '卡片':
case '照片':
case '相片':
case '图片':
case '写真':
// 展示老婆卡片
// 如果选择过,则进行展示
if (!e.isPoke) {
wifeList = await selfUser.getCfg(`wife.${targetCfg.key}`, [])
// 存在设置
if (wifeList && wifeList.length > 0 && isSelf && !e.isPoke) {
if (wifeList[0] === '随机') {
// 如果选择为全部,则从列表中随机选择一个
avatarList = await getAvatarList(e, targetCfg.type, mys)
let avatar = lodash.sample(avatarList)
return renderAvatar(e, avatar, renderType)
} else {
// 如果指定过,则展示指定角色
return renderAvatar(e, lodash.sample(wifeList), renderType)
let mys = await MysApi.init(e)
if (!mys || !mys.uid) {
return true
}
let player = Player.create(e)
let selfUser = mys.selfUser
let isSelf = true
let renderType = (action === '卡片' ? 'card' : 'photo')
let addRet = []
switch (action) {
case '卡片':
case '照片':
case '相片':
case '图片':
case '写真':
// 展示老婆卡片
// 如果选择过,则进行展示
if (!e.isPoke) {
wifeList = await selfUser.getCfg(`wife.${targetCfg.key}`, [])
// 存在设置
if (wifeList && wifeList.length > 0 && isSelf && !e.isPoke) {
if (wifeList[0] === '随机') {
// 如果选择为全部,则从列表中随机选择一个
avatarList = await getAvatarList(player, targetCfg.type, mys)
let avatar = lodash.sample(avatarList)
return Avatar.renderAvatar(e, avatar, renderType)
} else {
// 如果指定过,则展示指定角色
return Avatar.renderAvatar(e, lodash.sample(wifeList), renderType)
}
}
}
}
// 如果未指定过则从列表中排序并随机选择前5个
if (e.isPoke) {
avatarList = await getAvatarList(e, false, mys)
// 如果未指定过,则从列表中排序并随机选择
avatarList = await getAvatarList(player, e.isPoke ? false : targetCfg.type, mys)
if (avatarList && avatarList.length > 0) {
avatar = lodash.sample(avatarList)
return await renderAvatar(e, avatar, renderType)
return await Avatar.renderAvatar(e, avatar, renderType)
}
} else {
avatarList = await getAvatarList(e, targetCfg.type, mys)
if (avatarList && avatarList.length > 0) {
avatar = lodash.sample(avatarList.slice(0, 5))
return await renderAvatar(e, avatar, renderType)
}
}
e.reply('在当前米游社公开展示的角色中未能找到适合展示的角色..')
return true
case '设置':
case '选择':
case '挑选':
case '指定':
if (!isSelf) {
e.reply('只能指定自己的哦~')
e.reply('在当前米游社公开展示的角色中未能找到适合展示的角色..')
return true
}
// 选择老婆
actionParam = actionParam.replace(/(|、|;|)/g, ',')
wifeList = actionParam.split(',')
if (lodash.intersection(['全部', '任意', '随机', '全都要'], wifeList).length > 0) {
addRet = ['随机']
} else {
wifeList = lodash.map(wifeList, (name) => {
let char = Character.get(name)
if (char && char.checkWifeType(targetCfg.type)) {
return char.name
}
})
wifeList = lodash.filter(lodash.uniq(wifeList), (d) => !!d)
addRet = wifeList
if (addRet.length === 0) {
e.reply(`在可选的${targetCfg.keyword[0]}列表中未能找到 ${actionParam} ~`)
case '设置':
case '选择':
case '挑选':
case '指定':
if (!isSelf) {
e.reply('只能指定自己的哦~')
return true
}
}
await selfUser.setCfg(`wife.${targetCfg.key}`, addRet)
e.reply(`${targetCfg.keyword[0]}已经设置:${addRet.join('')}`)
return true
case '列表':
case '是':
case '是谁':
// 查看当前选择老婆
if (!isSelf) {
e.reply('只能查看自己的哦~')
// 选择老婆
actionParam = actionParam.replace(/(|、|;|)/g, ',')
wifeList = actionParam.split(',')
if (lodash.intersection(['全部', '任意', '随机', '全都要'], wifeList).length > 0) {
addRet = ['随机']
} else {
wifeList = lodash.map(wifeList, (name) => {
let char = Character.get(name)
if (char && char.checkWifeType(targetCfg.type)) {
return char.name
}
})
wifeList = lodash.filter(lodash.uniq(wifeList), (d) => !!d)
addRet = wifeList
if (addRet.length === 0) {
e.reply(`在可选的${targetCfg.keyword[0]}列表中未能找到 ${actionParam} ~`)
return true
}
}
await selfUser.setCfg(`wife.${targetCfg.key}`, addRet)
e.reply(`${targetCfg.keyword[0]}已经设置:${addRet.join('')}`)
return true
}
wifeList = await selfUser.getCfg(`wife.${targetCfg.key}`, [])
if (wifeList && wifeList.length > 0) {
e.reply(`你的${targetCfg.keyword[0]}是:${wifeList.join('')}`)
} else {
e.reply(`尚未设置,回复#${targetCfg.keyword[0]}设置+角色名 来设置,如果设置多位请用逗号间隔`)
}
break
case '列表':
case '是':
case '是谁':
// 查看当前选择老婆
if (!isSelf) {
e.reply('只能查看自己的哦~')
return true
}
wifeList = await selfUser.getCfg(`wife.${targetCfg.key}`, [])
if (wifeList && wifeList.length > 0) {
e.reply(`你的${targetCfg.keyword[0]}是:${wifeList.join('')}`)
} else {
e.reply(`尚未设置,回复#${targetCfg.keyword[0]}设置+角色名 来设置,如果设置多位请用逗号间隔`)
}
break
}
return true
},
async poke (e) {
return await Wife.render(e)
}
return true
}
export async function pokeWife (e, components) {
return await wife(e, components)
}
export default Wife

View File

@ -1,7 +1,5 @@
import lodash from 'lodash'
import fs from 'fs'
import { Cfg, Version, Common, Data, App } from '../components/index.js'
import HelpTheme from './help/HelpTheme.js'
import { App } from '../components/index.js'
import Help from './help/Help.js'
let app = App.init({
id: 'help',
@ -9,84 +7,17 @@ let app = App.init({
desc: '喵喵帮助'
})
app.reg('help', help, {
rule: /^#?(喵喵)?(命令|帮助|菜单|help|说明|功能|指令|使用说明)$/,
desc: '【#帮助】 #喵喵帮助'
})
app.reg('version', versionInfo, {
rule: /^#?喵喵版本$/,
desc: '【#帮助】 喵喵版本介绍'
app.reg({
help: {
rule: /^#?(喵喵)?(命令|帮助|菜单|help|说明|功能|指令|使用说明)$/,
fn: Help.render,
desc: '【#帮助】 #喵喵帮助'
},
version: {
rule: /^#?喵喵版本$/,
fn: Help.version,
desc: '【#帮助】 喵喵版本介绍'
}
})
export default app
const _path = process.cwd()
const helpPath = `${_path}/plugins/miao-plugin/resources/help`
async function help (e) {
if (!/喵喵/.test(e.msg) && !Cfg.get('help', false)) {
return false
}
let custom = {}
let help = {}
if (fs.existsSync(`${helpPath}/help-cfg.js`)) {
console.log('miao-plugin: 检测到存在help-cfg.js配置\n建议将help-cfg.js移为config/help.js或重新复制config/help_default.js进行配置~')
help = await import(`file://${helpPath}/help-cfg.js?version=${new Date().getTime()}`)
} else if (fs.existsSync(`${helpPath}/help-list.js`)) {
console.log('miao-plugin: 检测到存在help-list.js配置建议将help-list.js移为config/help.js或重新复制config/help_default.js进行配置~')
help = await import(`file://${helpPath}/help-list.js?version=${new Date().getTime()}`)
}
let { diyCfg, sysCfg } = await Data.importCfg('help')
// 兼容一下旧字段
if (lodash.isArray(help.helpCfg)) {
custom = {
helpList: help.helpCfg,
helpCfg: {}
}
} else {
custom = help
}
let helpConfig = lodash.defaults(diyCfg.helpCfg || {}, custom.helpCfg, sysCfg.helpCfg)
let helpList = diyCfg.helpList || custom.helpList || sysCfg.helpList
let helpGroup = []
lodash.forEach(helpList, (group) => {
if (group.auth && group.auth === 'master' && !e.isMaster) {
return true
}
lodash.forEach(group.list, (help) => {
let icon = help.icon * 1
if (!icon) {
help.css = 'display:none'
} else {
let x = (icon - 1) % 10
let y = (icon - x - 1) / 10
help.css = `background-position:-${x * 50}px -${y * 50}px`
}
})
helpGroup.push(group)
})
let themeData = await HelpTheme.getThemeData(diyCfg.helpCfg || {}, sysCfg.helpCfg || {})
return await Common.render('help/index', {
helpCfg: helpConfig,
helpGroup,
...themeData,
element: 'default'
}, { e, scale: 1.2 })
}
async function versionInfo (e) {
return await Common.render('help/version-info', {
currentVersion: Version.version,
changelogs: Version.changelogs,
elem: 'cryo'
}, { e, scale: 1.2 })
}

77
apps/help/Help.js Normal file
View File

@ -0,0 +1,77 @@
import { Cfg, Common, Data, Version } from '../../components/index.js'
import fs from 'fs'
import lodash from 'lodash'
import HelpTheme from './HelpTheme.js'
const _path = process.cwd()
const helpPath = `${_path}/plugins/miao-plugin/resources/help`
const Help = {
async render (e) {
if (!/喵喵/.test(e.msg) && !Cfg.get('help', false)) {
return false
}
let custom = {}
let help = {}
if (fs.existsSync(`${helpPath}/help-cfg.js`)) {
console.log('miao-plugin: 检测到存在help-cfg.js配置\n建议将help-cfg.js移为config/help.js或重新复制config/help_default.js进行配置~')
help = await import(`file://${helpPath}/help-cfg.js?version=${new Date().getTime()}`)
} else if (fs.existsSync(`${helpPath}/help-list.js`)) {
console.log('miao-plugin: 检测到存在help-list.js配置建议将help-list.js移为config/help.js或重新复制config/help_default.js进行配置~')
help = await import(`file://${helpPath}/help-list.js?version=${new Date().getTime()}`)
}
let { diyCfg, sysCfg } = await Data.importCfg('help')
// 兼容一下旧字段
if (lodash.isArray(help.helpCfg)) {
custom = {
helpList: help.helpCfg,
helpCfg: {}
}
} else {
custom = help
}
let helpConfig = lodash.defaults(diyCfg.helpCfg || {}, custom.helpCfg, sysCfg.helpCfg)
let helpList = diyCfg.helpList || custom.helpList || sysCfg.helpList
let helpGroup = []
lodash.forEach(helpList, (group) => {
if (group.auth && group.auth === 'master' && !e.isMaster) {
return true
}
lodash.forEach(group.list, (help) => {
let icon = help.icon * 1
if (!icon) {
help.css = 'display:none'
} else {
let x = (icon - 1) % 10
let y = (icon - x - 1) / 10
help.css = `background-position:-${x * 50}px -${y * 50}px`
}
})
helpGroup.push(group)
})
let themeData = await HelpTheme.getThemeData(diyCfg.helpCfg || {}, sysCfg.helpCfg || {})
return await Common.render('help/index', {
helpCfg: helpConfig,
helpGroup,
...themeData,
element: 'default'
}, { e, scale: 1.2 })
},
async version (e) {
return await Common.render('help/version-info', {
currentVersion: Version.version,
changelogs: Version.changelogs,
elem: 'cryo'
}, { e, scale: 1.2 })
}
}
export default Help

View File

@ -1,5 +1,5 @@
import { App } from '../components/index.js'
import { pokeWife } from './character/AvatarWife.js'
import Wife from './character/AvatarWife.js'
let app = App.init({
id: 'poke',
@ -7,8 +7,11 @@ let app = App.init({
event: 'poke'
})
app.reg('pock-wife', pokeWife, {
describe: '#老公 #老婆 查询'
app.reg({
pockWife: {
fn: Wife.poke,
describe: '#老公 #老婆 查询'
}
})
export default app

View File

@ -1,209 +1,137 @@
import { Common, App, Data, Cfg } from '../components/index.js'
import { Character } from '../models/index.js'
import { getTargetUid, getProfile, profileHelp } from './profile/ProfileCommon.js'
import { profileArtis, profileArtisList } from './profile/ProfileArtis.js'
import { renderProfile } from './profile/ProfileDetail.js'
import { profileStat } from './profile/ProfileStat.js'
import { profileList } from './profile/ProfileList.js'
import { App, Cfg } from '../components/index.js'
import { profileHelp } from './profile/ProfileCommon.js'
import { profileArtisList } from './profile/ProfileArtis.js'
import { profileDetail } from './profile/ProfileDetail.js'
import ProfileStat from './profile/ProfileStat.js'
import ProfileList from './profile/ProfileList.js'
import { uploadCharacterImg, delProfileImg, profileImgList } from './character/ImgUpload.js'
import { enemyLv } from './profile/ProfileUtils.js'
import ProfileChange from './profile/ProfileChange.js'
import { groupRank, resetRank, refreshRank, manageRank } from './profile/ProfileRank.js'
let app = App.init({
id: 'profile',
name: '角色面板'
})
app.reg('profile-detail', profileDetail, {
rule: /^#*([^#]+)\s*(详细|详情|面板|面版|圣遗物|伤害[1-7]?)\s*(\d{9})*(.*[换变改].*)?$/,
name: '角色面板'
})
app.reg('profile-change', profileDetail, {
rule: /^#.+换.+$/,
name: '角色面板计算'
})
app.reg('group-profile', groupRank, {
rule: /^#(群|群内)?(排名|排行)?(最强|最高|最高分|最牛|第一)+.+/,
name: '群内最强'
})
app.reg({
profileDetail: {
name: '角色面板',
fn: profileDetail,
rule: /^#*([^#]+)\s*(详细|详情|面板|面版|圣遗物|伤害[1-7]?)\s*(\d{9})*(.*[换变改].*)?$/
},
app.reg('reset-rank', resetRank, {
rule: /^#(重置|重设)(.*)(排名|排行)$/,
name: '重置排名'
})
profileChange: {
name: '角色面板计算',
fn: profileDetail,
rule: /^#.+换.+$/
},
app.reg('refresh-rank', refreshRank, {
rule: /^#(刷新|更新|重新加载)(群内|群|全部)*(排名|排行)$/,
name: '重置排名'
})
groupProfile: {
name: '群内最强',
fn: groupRank,
rule: /^#(群|群内)?(排名|排行)?(最强|最高|最高分|最牛|第一)+.+/
},
app.reg('manage-rank', manageRank, {
rule: /^#(开启|打开|启用|关闭|禁用)(群内|群|全部)*(排名|排行)$/,
name: '打开关闭'
})
resetRank: {
name: '重置排名',
fn: resetRank,
rule: /^#(重置|重设)(.*)(排名|排行)$/
},
app.reg('rank-list', groupRank, {
rule: /^#(群|群内)?.+(排名|排行)(榜)?$/,
name: '面板排名榜'
})
refreshRank: {
name: '重置排名',
fn: refreshRank,
rule: /^#(刷新|更新|重新加载)(群内|群|全部)*(排名|排行)$/
},
app.reg('artis-list', profileArtisList, {
rule: /^#圣遗物列表\s*(\d{9})?$/,
name: '面板圣遗物列表'
})
manageRank: {
name: '打开关闭',
fn: manageRank,
rule: /^#(开启|打开|启用|关闭|禁用)(群内|群|全部)*(排名|排行)$/
},
app.reg('profile-list', profileList, {
rule: /^#(面板角色|角色面板|面板)(列表)?\s*(\d{9})?$/,
name: '面板角色列表',
desc: '查看当前已获取面板数据的角色列表'
})
rankList: {
name: '面板排名榜',
fn: groupRank,
rule: /^#(群|群内)?.+(排名|排行)(榜)?$/
},
app.reg('profile-stat', profileStat, {
rule: /^#(面板|喵喵|角色|武器|天赋|技能|圣遗物)?练度统计$/,
name: '面板练度统计$'
})
artisList: {
name: '面板圣遗物列表',
fn: profileArtisList,
rule: /^#圣遗物列表\s*(\d{9})?$/
},
app.reg('profile-help', profileHelp, {
rule: /^#(角色|换|更换)?面[板版]帮助$/,
name: '角色面板帮助'
})
profileList: {
name: '面板角色列表',
desc: '查看当前已获取面板数据的角色列表',
fn: ProfileList.render,
rule: /^#(面板角色|角色面板|面板)(列表)?\s*(\d{9})?$/
},
app.reg('enemy-lv', enemyLv, {
rule: /^#(敌人|怪物)等级\s*\d{1,3}\s*$/,
describe: '【#角色】 设置伤害计算中目标敌人的等级'
})
profileStat: {
name: '面板练度统计',
fn: ProfileStat.stat,
rule: /^#(面板|喵喵)练度统计$/,
yzRule: /^#*(我的)*(技能|天赋|武器|角色|练度|五|四|5|4|星)+(汇总|统计|列表)(force|五|四|5|4|星)*[ |0-9]*$/,
yzCheck: () => Cfg.get('profileStat', false)
},
app.reg('profile-refresh', getProfile, {
rule: /^#(全部面板更新|更新全部面板|获取游戏角色详情|更新面板|面板更新)\s*(\d{9})?$/,
describe: '【#角色】 获取游戏橱窗详情数据'
})
avatarList: {
name: '角色查询',
fn: ProfileStat.avatarList,
rule: /^#喵喵(角色|查询)[ |0-9]*$/,
yzRule: /^(#(角色|查询|查询角色|角色查询|人物)[ |0-9]*$)|(^(#*uid|#*UID)\+*[1|2|5-9][0-9]{8}$)|(^#[\+|]*[1|2|5-9][0-9]{8})/,
yzCheck: () => Cfg.get('avatarList', false)
},
app.reg('upload-img', uploadCharacterImg, {
rule: /^#?\s*(?:上传|添加)(.+)(?:面板图)\s*$/,
describe: '【#上传刻晴面板图】 上传角色面板图'
profileHelp: {
name: '角色面板帮助',
fn: profileHelp,
rule: /^#(角色|换|更换)?面[板版]帮助$/
},
enemyLv: {
name: '敌人等级',
fn: enemyLv,
describe: '【#角色】 设置伤害计算中目标敌人的等级',
rule: /^#(敌人|怪物)等级\s*\d{1,3}\s*$/
},
profileRefresh: {
name: '面板更新',
describe: '【#角色】 获取游戏橱窗详情数据',
fn: ProfileList.refresh,
rule: /^#(全部面板更新|更新全部面板|获取游戏角色详情|更新面板|面板更新)\s*(\d{9})?$/
},
uploadImg: {
name: '上传面板图',
describe: '【#上传刻晴面板图】 上传角色面板图',
fn: uploadCharacterImg,
rule: /^#?\s*(?:上传|添加)(.+)(?:面板图)\s*$/
},
delProfile: {
name: '删除面板图',
describe: '【#删除刻晴面板图1】 删除指定角色面板图(序号)',
fn: delProfileImg,
rule: /^#?\s*(?:移除|清除|删除)(.+)(?:面板图)(\d){1,}\s*$/
},
profileImgList: {
name: '面板图列表',
describe: '【#刻晴面板图列表】 删除指定角色面板图(序号)',
fn: profileImgList,
rule: /^#?\s*(.+)(?:面板图列表)\s*$/
},
profileDel: {
name: '删除面板',
describe: '【#角色】 删除游戏橱窗详情数据',
fn: ProfileList.del,
rule: /^#(删除全部面板|删除面板|删除面板数据)\s*(\d{9})?$/
}
})
app.reg('del-profile', delProfileImg, {
rule: /^#?\s*(?:移除|清除|删除)(.+)(?:面板图)(\d){1,}\s*$/,
describe: '【#删除刻晴面板图1】 删除指定角色面板图(序号)'
})
app.reg('profile-img-list', profileImgList, {
rule: /^#?\s*(.+)(?:面板图列表)\s*$/,
describe: '【#刻晴面板图列表】 删除指定角色面板图(序号)'
})
/**
app.reg('del-uidflie', delProfile, {
rule: /^#?\s*(?:移除|清除|删除)面板数据$/,
describe: '【#删除面板数据】 删除面板数据'
})
*/
export default app
export async function delProfile (e) {
let uid = await getTargetUid(e)
if (!uid) {
return true
}
if (Data.delfile(`data/UserData/${uid}.json`)) {
e.reply(`uid:${uid}缓存面板数据已删除~`)
}
return true
}
// 查看当前角色
export async function profileDetail (e) {
let msg = e.original_msg || e.msg
if (!msg) {
return false
}
if (!/详细|详情|面板|面版|圣遗物|伤害|换/.test(msg)) {
return false
}
let mode = 'profile'
let profileChange = false
let changeMsg = msg
let pc = ProfileChange.matchMsg(msg)
if (pc && pc.char && pc.change) {
if (!Cfg.get('profileChange')) {
e.reply('面板替换功能已禁用...')
return true
}
e.uid = pc.uid || e.runtime.uid
profileChange = ProfileChange.getProfile(e.uid, pc.char, pc.change)
if (profileChange && profileChange.char) {
msg = `#${profileChange.char?.name}${pc.mode || '面板'}`
e._profile = profileChange
e._profileMsg = changeMsg
}
}
let uidRet = /[0-9]{9}/.exec(msg)
if (uidRet) {
e.uid = uidRet[0]
msg = msg.replace(uidRet[0], '')
}
let name = msg.replace(/#|老婆|老公/g, '').trim()
msg = msg.replace('面版', '面板')
let dmgRet = /伤害(\d?)$/.exec(name)
let dmgIdx = 0
if (/(最强|最高|最高分|最牛|第一)/.test(msg)) {
mode = /(分|圣遗物|评分|ACE)/.test(msg) ? 'rank-mark' : 'rank-dmg'
name = name.replace(/(最强|最高分|第一|最高|最牛|圣遗物|评分|群)/g, '')
}
if (/(详情|详细|面板|面版)\s*$/.test(msg) && !/更新|录入|输入/.test(msg)) {
mode = 'profile'
name = name.replace(/(详情|详细|面板)/, '').trim()
} else if (dmgRet) {
mode = 'dmg'
name = name.replace(/伤害[0-5]?/, '').trim()
if (dmgRet[1]) {
dmgIdx = dmgRet[1] * 1
}
} else if (/(详情|详细|面板)更新$/.test(msg) || (/更新/.test(msg) && /(详情|详细|面板)$/.test(msg))) {
mode = 'refresh'
name = name.replace(/详情|详细|面板|更新/g, '').trim()
} else if (/圣遗物/.test(msg)) {
mode = 'artis'
name = name.replace('圣遗物', '').trim()
}
if (!Common.cfg('avatarProfile')) {
// 面板开关关闭
return false
}
let char = Character.get(name.trim())
if (!char) {
return false
}
let uid = e.uid || await getTargetUid(e)
if (!uid) {
return true
}
e.uid = uid
e.avatar = char.id
if (char.isCustom) {
e.reply('自定义角色暂不支持此功能')
return true
}
if (!char.isRelease) {
if (!profileChange) {
e.reply('角色尚未实装')
return true
} else if (Cfg.get('notReleasedData') === false) {
e.reply('未实装角色面板已禁用...')
return true
}
}
if (mode === 'profile' || mode === 'dmg') {
return renderProfile(e, char, mode, { dmgIdx })
} else if (mode === 'refresh') {
await getProfile(e)
return true
} else if (mode === 'artis') {
return profileArtis(e)
}
return true
}

View File

@ -3,35 +3,24 @@
*
* */
import lodash from 'lodash'
import { Cfg, Profile, Common } from '../../components/index.js'
import { getTargetUid, profileHelp, autoGetProfile } from './ProfileCommon.js'
import { Artifact, Character, ProfileArtis } from '../../models/index.js'
import { Cfg, Common } from '../../components/index.js'
import { getTargetUid, profileHelp, getProfileRefresh } from './ProfileCommon.js'
import { Artifact, Character, ProfileArtis, Player } from '../../models/index.js'
/*
* 角色圣遗物面板
* */
export async function profileArtis (e) {
let { uid, avatar } = e
let profile
if (e._profile) {
profile = e._profile
} else {
let autoRet = await autoGetProfile(e, uid, avatar, async () => {
await profileArtis(e)
})
if (autoRet.err) {
return false
}
profile = autoRet.profile
let profile = e._profile || await getProfileRefresh(e, avatar)
if (!profile) {
return true
}
let char = profile.char
if (!profile.hasArtis()) {
e.reply('未能获得圣遗物详情,请重新获取面板信息后查看')
return true
}
let char = profile.char
let charCfg = profile.artis.getCharCfg()
let { attrMap } = Artifact.getMeta()
@ -43,7 +32,7 @@ export async function profileArtis (e) {
return await Common.render('character/artis-mark', {
uid,
elem: char.elem,
splash: char.getImgs(profile.costume).splash0,
splash: profile.costumeSplash,
data: profile,
costume: profile.costume ? '2' : '',
artisDetail,
@ -64,14 +53,12 @@ export async function profileArtisList (e) {
}
let artis = []
let profiles = Profile.getAll(uid) || {}
if (!profiles || profiles.length === 0) {
e.reply('暂无角色圣遗物详情')
return true
}
lodash.forEach(profiles || [], (profile) => {
let player = Player.create(uid)
player.forEachAvatar((avatar) => {
let profile = avatar.getProfile()
if (!profile) {
return true
}
let name = profile.name
let char = Character.get(name)
if (!profile.hasData || !profile.hasArtis()) {

View File

@ -2,8 +2,8 @@
* 面板数据替换相关逻辑
*/
import lodash from 'lodash'
import { Profile, Data } from '../../components/index.js'
import { Character, ProfileData, Weapon } from '../../models/index.js'
import { Data } from '../../components/index.js'
import { Character, ProfileData, Weapon, Player } from '../../models/index.js'
const keyMap = {
artis: '圣遗物',
@ -153,8 +153,9 @@ const ProfileChange = {
if (!charid) {
return false
}
let player = Player.create(uid)
let source = Profile.get(uid, charid)
let source = player.getProfile(charid)
let dc = ds.char || {}
if (!source || !source.hasData) {
source = {}
@ -168,7 +169,7 @@ const ProfileChange = {
let profiles = {}
if (source && source.id) {
profiles[`${source.uid}:${source.id}`] = source
profiles[`${player.uid}:${source.id}`] = source
}
// 获取source
let getSource = function (cfg) {
@ -179,7 +180,8 @@ const ProfileChange = {
let id = cfg.char || source.id
let key = cuid + ':' + id
if (!profiles[key]) {
profiles[key] = Profile.get(cuid, id) || {}
let cPlayer = Player.create(cuid)
profiles[key] = cPlayer.getProfile(id) || {}
}
return profiles[key]?.id ? profiles[key] : source
}
@ -193,7 +195,7 @@ const ProfileChange = {
elem: char.elem,
dataSource: 'change',
promote
}, uid, false)
}, false)
// 设置武器
let wCfg = ds.weapon || {}

View File

@ -1,16 +1,14 @@
/*
* 面板公共方法及处理
* */
import lodash from 'lodash'
import { segment } from 'oicq'
import { profileList } from './ProfileList.js'
import { Profile, Version } from '../../components/index.js'
import { Character, MysApi } from '../../models/index.js'
import { Version } from '../../components/index.js'
import { Character, MysApi, Player } from '../../models/index.js'
/*
* 获取面板查询的 目标uid
* */
export async function getTargetUid (e) {
const _getTargetUid = async function (e) {
let uidReg = /[1-9][0-9]{8}/
if (e.uid && uidReg.test(e.uid)) {
@ -57,7 +55,7 @@ export async function getTargetUid (e) {
return false
}
uid = user.uid
if (!uid || !uidReg.test(uid)) {
if ((!uid || !uidReg.test(uid)) && !e._replyNeedUid) {
e.reply('请先发送【#绑定+你的UID】来绑定查询目标')
return false
}
@ -67,152 +65,34 @@ export async function getTargetUid (e) {
return uid || false
}
/*
* 自动更新面板数据
* */
export async function autoRefresh (e) {
let uid = await getTargetUid(e)
if (!uid || e.isRefreshed) {
return false
}
let refreshMark = await redis.get(`miao:profile-refresh-cd:${uid}`)
let inCd = await redis.get(`miao:role-all:${uid}`)
if (refreshMark || inCd) {
return false
}
await redis.set(`miao:profile-refresh-cd:${uid}`, 'TRUE', { EX: 3600 * 12 })
e.isRefreshed = true
// 数据更新
let data = await Profile.request(uid, e)
if (!data) {
return false
}
if (!data.chars) {
e.reply('请确认角色已在【游戏内】橱窗展示并开放了查看详情。请在设置完毕5分钟后使用 #面板更新 重新获取')
return false
} else {
let ret = []
lodash.forEach(data.chars, (ds) => {
let char = Character.get(ds.id)
if (char) {
ret.push(char.name)
}
})
if (ret.length === 0) {
e.reply('请确认角色已在【游戏内】橱窗展示并开放了查看详情。请在设置完毕5分钟后使用 #面板更新 重新获取')
return false
} else {
// e.reply(`本次获取成功角色: ${ret.join(", ")} `)
return true
}
export async function getTargetUid (e) {
let uid = await _getTargetUid(e)
if (uid) {
e.uid = uid
}
return uid
}
export async function autoGetProfile (e, uid, avatar, callback) {
let refresh = async () => {
let refreshRet = await autoRefresh(e)
if (refreshRet) {
await callback()
}
return refreshRet
}
export async function getProfileRefresh (e, avatar) {
let char = Character.get(avatar)
if (!char) {
return { err: true }
return false
}
let profile = Profile.get(uid, char.id)
let player = Player.create(e)
let profile = player.getProfile(char.id)
if (!profile || !profile.hasData) {
if (await refresh()) {
return { err: true }
} else {
logger.mark(`本地无UID:${player.uid}${char.name}面板数据,尝试自动请求...`)
await player.refresh({ profile: true })
profile = player.getProfile(char.id)
}
if (!profile || !profile.hasData) {
if (!e._isReplyed) {
e.reply(`请确认${char.name}已展示在【游戏内】的角色展柜中,并打开了“显示角色详情”。然后请使用 #更新面板\n命令来获取${char.name}的面板详情`)
}
return { err: true }
} else if (!['enka', 'miao'].includes(profile.dataSource)) {
if (!await refresh()) {
e.reply('缓存数据错误,请重新获取面板信息后查看')
}
return { err: true }
return false
}
return { profile, char, refresh }
}
/*
* 面板数据更新
* */
export async function getProfile (e) {
let uid = await getTargetUid(e)
if (!uid) {
return true
}
// 数据更新
let data = await Profile.request(uid, e)
if (!data) {
return true
}
if (!data.chars) {
e.reply('获取角色面板数据失败请确认角色已在游戏内橱窗展示并开放了查看详情。设置完毕后请5分钟后再进行请求~')
} else {
let ret = {}
lodash.forEach(data.chars, (ds) => {
let char = Character.get(ds.id)
if (char) {
ret[char.name] = true
}
})
if (ret.length === 0) {
e.reply('获取角色面板数据失败未能请求到角色数据。请确认角色已在游戏内橱窗展示并开放了查看详情。设置完毕后请5分钟后再进行请求~')
} else {
e.newChar = ret
return await profileList(e)
}
}
return true
}
/*
* 获取面板列表
* */
export async function getProfileAll (e) {
let uid = await getTargetUid(e)
if (!uid) {
return true
}
let profiles = Profile.getAll(uid) || {}
let chars = []
lodash.forEach(profiles || [], (ds) => {
if (!['enka', 'miao'].includes(ds.dataSource)) {
return true
}
ds.name && chars.push(ds.name)
})
if (chars.length === 0) {
if (await autoRefresh(e)) {
await getProfileAll(e)
return true
} else {
e.reply('尚未获取任何角色数据')
await profileHelp(e)
}
return true
}
e.reply(`uid${uid} 已获取面板角色: ` + chars.join(', '))
return true
return profile
}
/*

View File

@ -1,7 +1,106 @@
import lodash from 'lodash'
import { autoRefresh } from './ProfileCommon.js'
import { Common, Format, Profile } from '../../components/index.js'
import { MysApi, Avatar, ProfileRank, ProfileArtis } from '../../models/index.js'
import { getTargetUid, getProfileRefresh } from './ProfileCommon.js'
import ProfileList from './ProfileList.js'
import { Cfg, Common, Format } from '../../components/index.js'
import { MysApi, ProfileRank, ProfileArtis, Player, Character } from '../../models/index.js'
import ProfileChange from './ProfileChange.js'
import { profileArtis } from './ProfileArtis.js'
// 查看当前角色
export async function profileDetail (e) {
let msg = e.original_msg || e.msg
if (!msg) {
return false
}
if (!/详细|详情|面板|面版|圣遗物|伤害|换/.test(msg)) {
return false
}
let mode = 'profile'
let profileChange = false
let changeMsg = msg
let pc = ProfileChange.matchMsg(msg)
if (pc && pc.char && pc.change) {
if (!Cfg.get('profileChange')) {
e.reply('面板替换功能已禁用...')
return true
}
e.uid = pc.uid || e.runtime.uid
profileChange = ProfileChange.getProfile(e.uid, pc.char, pc.change)
if (profileChange && profileChange.char) {
msg = `#${profileChange.char?.name}${pc.mode || '面板'}`
e._profile = profileChange
e._profileMsg = changeMsg
}
}
let uidRet = /[0-9]{9}/.exec(msg)
if (uidRet) {
e.uid = uidRet[0]
msg = msg.replace(uidRet[0], '')
}
let name = msg.replace(/#|老婆|老公/g, '').trim()
msg = msg.replace('面版', '面板')
let dmgRet = /伤害(\d?)$/.exec(name)
let dmgIdx = 0
if (/(最强|最高|最高分|最牛|第一)/.test(msg)) {
mode = /(分|圣遗物|评分|ACE)/.test(msg) ? 'rank-mark' : 'rank-dmg'
name = name.replace(/(最强|最高分|第一|最高|最牛|圣遗物|评分|群)/g, '')
}
if (/(详情|详细|面板|面版)\s*$/.test(msg) && !/更新|录入|输入/.test(msg)) {
mode = 'profile'
name = name.replace(/(详情|详细|面板)/, '').trim()
} else if (dmgRet) {
mode = 'dmg'
name = name.replace(/伤害[0-5]?/, '').trim()
if (dmgRet[1]) {
dmgIdx = dmgRet[1] * 1
}
} else if (/(详情|详细|面板)更新$/.test(msg) || (/更新/.test(msg) && /(详情|详细|面板)$/.test(msg))) {
mode = 'refresh'
name = name.replace(/详情|详细|面板|更新/g, '').trim()
} else if (/圣遗物/.test(msg)) {
mode = 'artis'
name = name.replace('圣遗物', '').trim()
}
if (!Common.cfg('avatarProfile')) {
return false // 面板开关关闭
}
let char = Character.get(name.trim())
if (!char) {
return false
}
let uid = e.uid || await getTargetUid(e)
if (!uid) {
return true
}
e.uid = uid
e.avatar = char.id
if (char.isCustom) {
e.reply('自定义角色暂不支持此功能')
return true
}
if (!char.isRelease) {
if (!profileChange) {
e.reply('角色尚未实装')
return true
} else if (Cfg.get('notReleasedData') === false) {
e.reply('未实装角色面板已禁用...')
return true
}
}
if (mode === 'profile' || mode === 'dmg') {
return renderProfile(e, char, mode, { dmgIdx })
} else if (mode === 'refresh') {
await ProfileList.refresh(e)
return true
} else if (mode === 'artis') {
return profileArtis(e)
}
return true
}
export async function renderProfile (e, char, mode = 'profile', params = {}) {
let selfUser = await MysApi.initUser(e)
@ -18,25 +117,10 @@ export async function renderProfile (e, char, mode = 'profile', params = {}) {
return true
}
let profile = e._profile || Profile.get(uid, char.id)
let refresh = async () => {
let refreshRet = await autoRefresh(e)
if (refreshRet) {
await renderProfile(e, char, mode, params)
}
return refreshRet
}
if (!profile || !profile.hasData) {
if (await refresh()) {
return true
} else {
e.reply(`请确认${char.name}已展示在【游戏内】的角色展柜中,并打开了“显示角色详情”。然后请使用 #更新面板\n命令来获取${char.name}的面板详情`)
}
let profile = e._profile || await getProfileRefresh(e, char.id)
if (!profile) {
return true
}
let avatar = new Avatar(profile)
char = profile.char || char
let a = profile.attr
let c = Format.comma
@ -96,11 +180,12 @@ export async function renderProfile (e, char, mode = 'profile', params = {}) {
let artisDetail = profile.getArtisMark()
let artisKeyTitle = ProfileArtis.getArtisKeyTitle()
let imgs = char.getImgs(profile.costume)
let costumeSplash = profile.costumeSplash
// 渲染图像
let msgRes = await Common.render('character/profile-detail', {
save_id: uid,
uid,
data: avatar.getData('name,abbr,cons,level,weapon,talent,dataSource,updateTime'),
data: profile.getData('name,abbr,cons,level,weapon,talent,dataSource,updateTime'),
attr,
elem: char.elem,
dmgData,
@ -111,15 +196,16 @@ export async function renderProfile (e, char, mode = 'profile', params = {}) {
artisKeyTitle,
enemyLv,
imgs,
costumeSplash,
enemyName: dmgCalc.enemyName || '小宝',
talentMap: { a: '普攻', e: '战技', q: '爆发' },
bodyClass: `char-${char.name}`,
mode,
changeProfile: e._profileMsg
}, { e, scale: 1.6,retMsgId: true })
}, { e, scale: 1.6, retMsgId: true })
if (msgRes && msgRes.message_id) {
// 如果消息发送成功就将message_id和图片路径存起来3小时过期
await redis.set(`miao:original-picture:${msgRes.message_id}`, imgs.splash0, { 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

@ -1,91 +1,168 @@
import lodash from 'lodash'
import { autoRefresh, getTargetUid } from './ProfileCommon.js'
import { ProfileRank } from '../../models/index.js'
import { Common, Profile, Data } from '../../components/index.js'
import { getTargetUid } from './ProfileCommon.js'
import { ProfileRank, Player, Character } from '../../models/index.js'
import { Common, Data } from '../../components/index.js'
export async function profileList (e) {
let uid = await getTargetUid(e)
if (!uid) {
return true
}
let isSelfUid = false
if (e.runtime) {
let uids = e.runtime?.user?.ckUids || []
isSelfUid = uids.join(',').split(',').includes(uid + '')
}
let rank = false
let servName = Profile.getServName(uid)
let hasNew = false
let newCount = 0
let chars = []
let msg = ''
let newChar = {}
if (e.newChar) {
msg = '获取角色面板数据成功'
newChar = e.newChar
}
const cfg = await Data.importCfg('cfg')
// 获取面板数据
let profiles = Profile.getAll(uid)
// 检测标志位
let qq = (e.at && !e.atBot) ? e.at : e.qq
await ProfileRank.setUidInfo({ uid, profiles, qq, uidType: isSelfUid ? 'ck' : 'bind' })
let groupId = e.group_id
if (groupId) {
rank = await ProfileRank.create({ groupId, uid, qq: e.user_id })
}
const rankCfg = await ProfileRank.getGroupCfg(groupId)
const groupRank = rank && (cfg?.diyCfg?.groupRank || false) && rankCfg.status !== 1
await Profile.forEach(uid, async function (profile) {
if (!profile.hasData) {
const ProfileList = {
/**
* 刷新面板
* @param e
* @returns {Promise<boolean|*>}
*/
async refresh (e) {
let uid = await getTargetUid(e)
if (!uid) {
return true
}
let char = profile.char
let tmp = char.getData('id,face,name,abbr,element,star')
tmp.face = char.getImgs(profile.costume).face
tmp.source = profile.dataSource
tmp.level = profile.level || 1
tmp.cons = profile.cons
tmp.isNew = 0
if (newChar[char.name]) {
tmp.isNew = 1
newCount++
}
if (rank) {
tmp.groupRank = await rank.getRank(profile, !!tmp.isNew)
}
chars.push(tmp)
})
if (chars.length === 0) {
if (await autoRefresh(e)) {
await profileList(e)
return true
// 数据更新
let player = Player.create(e)
await player.refreshProfile(2)
if (!player?._update?.length) {
e.reply('获取角色面板数据失败请确认角色已在游戏内橱窗展示并开放了查看详情。设置完毕后请5分钟后再进行请求~')
} else {
e.reply(`本地暂无uid${uid}的面板数据...`)
let ret = {}
lodash.forEach(player._update, (id) => {
let char = Character.get(id)
if (char) {
ret[char.name] = true
}
})
if (ret.length === 0) {
e.reply('获取角色面板数据失败未能请求到角色数据。请确认角色已在游戏内橱窗展示并开放了查看详情。设置完毕后请5分钟后再进行请求~')
} else {
e.newChar = ret
return await ProfileList.render(e)
}
}
return true
},
/**
* 渲染面板
* @param e
* @returns {Promise<boolean|*>}
*/
async render (e) {
let uid = await getTargetUid(e)
if (!uid) {
return true
}
let isSelfUid = false
if (e.runtime) {
let uids = e.runtime?.user?.ckUids || []
isSelfUid = uids.join(',').split(',').includes(uid + '')
}
let rank = false
let servName = Player.getProfileServName(uid)
let hasNew = false
let newCount = 0
let chars = []
let msg = ''
let newChar = {}
if (e.newChar) {
msg = '获取角色面板数据成功'
newChar = e.newChar
}
const cfg = await Data.importCfg('cfg')
// 获取面板数据
let player = Player.create(e)
if (!player.hasProfile) {
await player.refresh({ profile: true })
}
if (!player.hasProfile) {
e.reply(`本地暂无uid${uid}的面板数据...`)
return true
}
let profiles = player.getProfiles()
// 检测标志位
let qq = (e.at && !e.atBot) ? e.at : e.qq
await ProfileRank.setUidInfo({ uid, profiles, qq, uidType: isSelfUid ? 'ck' : 'bind' })
let groupId = e.group_id
if (groupId) {
rank = await ProfileRank.create({ groupId, uid, qq: e.user_id })
}
const rankCfg = await ProfileRank.getGroupCfg(groupId)
const groupRank = rank && (cfg?.diyCfg?.groupRank || false) && rankCfg.status !== 1
for (let id in profiles) {
let profile = profiles[id]
let char = profile.char
let tmp = char.getData('id,face,name,abbr,element,star')
tmp.face = char.getImgs(profile.costume).face
tmp.level = profile.level || 1
tmp.cons = profile.cons
tmp.isNew = 0
if (newChar[char.name]) {
tmp.isNew = 1
newCount++
}
if (rank) {
tmp.groupRank = await rank.getRank(profile, !!tmp.isNew)
}
chars.push(tmp)
}
if (newCount > 0) {
hasNew = newCount <= 8
}
chars = lodash.sortBy(chars, ['isNew', 'star', 'level', 'id'])
chars = chars.reverse()
player.save()
// 渲染图像
return await Common.render('character/profile-list', {
save_id: uid,
uid,
chars,
servName,
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
}
if (newCount > 0) {
hasNew = newCount <= 8
}
chars = lodash.sortBy(chars, ['isNew', 'star', 'level', 'id'])
chars = chars.reverse()
// 渲染图像
return await Common.render('character/profile-list', {
save_id: uid,
uid,
chars,
servName,
hasNew,
msg,
groupRank,
allowRank: rank && rank.allowRank,
rankCfg
}, { e, scale: 1.6 })
}
export default ProfileList

View File

@ -1,6 +1,6 @@
import { Character, ProfileRank, ProfileDmg, Avatar } from '../../models/index.js'
import { Character, ProfileRank, ProfileDmg, Player } from '../../models/index.js'
import { renderProfile } from './ProfileDetail.js'
import { Data, Profile, Common, Format } from '../../components/index.js'
import { Data, Common, Format } from '../../components/index.js'
import lodash from 'lodash'
export async function groupRank (e) {
@ -30,11 +30,11 @@ export async function groupRank (e) {
}
let groupCfg = await ProfileRank.getGroupCfg(groupId)
if (!groupRank) {
e.reply('群面板排名功能已禁用,主人可通过【#喵喵设置】启用...')
e.reply('群面板排名功能已禁用,Bot主人可通过【#喵喵设置】启用...')
return true
}
if (groupCfg.status === 1) {
e.reply('本群已关闭群排名,主人可通过【#启用排名】启用...')
e.reply('本群已关闭群排名,群管理员或Bot主人可通过【#启用排名】启用...')
return true
}
if (type === 'detail') {
@ -105,8 +105,8 @@ export async function refreshRank (e) {
if (!groupId) {
return true
}
if (!e.isMaster) {
e.reply('只有管理员可刷新排名...')
if (!e.isMaster && !this.e.member?.is_admin) {
e.reply('只有主人及群管理员可刷新排名...')
return true
}
e.reply('面板数据刷新中,等待时间可能较长,请耐心等待...')
@ -115,7 +115,8 @@ export async function refreshRank (e) {
let count = 0
for (let qq in groupUids) {
for (let { uid, type } of groupUids[qq]) {
let profiles = Profile.getAll(uid)
let player = new Player(uid)
let profiles = player.getProfiles()
// 刷新rankLimit
await ProfileRank.setUidInfo({ uid, profiles, qq, uidType: type })
let rank = await ProfileRank.create({ groupId, uid, qq })
@ -140,8 +141,8 @@ export async function manageRank (e) {
return true
}
let isClose = /(关闭|禁用)/.test(e.msg)
if (!e.isMaster) {
e.reply(`只有管理员可${isClose ? '禁用' : '启用'}排名...`)
if (!e.isMaster && !this.e.member?.is_admin) {
e.reply(`只有主人及群管理员可${isClose ? '禁用' : '启用'}排名...`)
return true
}
await ProfileRank.setGroupStatus(groupId, isClose ? 1 : 0)
@ -156,13 +157,17 @@ async function renderCharRankList ({ e, uids, char, mode, groupId }) {
let list = []
for (let ds of uids) {
let uid = ds.uid || ds.value
let profile = Profile.get(uid, ds.charId || char.id)
let player = Player.create(uid)
let avatar = player.getAvatar(ds.charId || char.id)
if (!avatar) {
continue
}
let profile = avatar.getProfile()
if (profile) {
let profileRank = await ProfileRank.create({ groupId, uid })
let data = await profileRank.getRank(profile, true)
let mark = data?.mark?.data
let avatar = new Avatar(profile, uid)
let tmp = {
uid,
isMax: !char,

View File

@ -1,62 +1,77 @@
import lodash from 'lodash'
import {Cfg, Common, Data } from '../../components/index.js'
import { AvatarList, ProfileRank } from '../../models/index.js'
import { Common } from '../../components/index.js'
import { MysApi, Player, Character } from '../../models/index.js'
export async function profileStat (e) {
let isMatch = /^#(面板|喵喵|角色|武器|天赋|技能|圣遗物)练度统计?$/.test(e.original_msg || e.msg || '')
if (!Cfg.get('profileStat', false) && !isMatch) {
return false
}
// 缓存时间,单位小时
const ProfileStat = {
async stat (e) {
return ProfileStat.render(e, false)
},
let msg = e.msg.replace('#', '').trim()
if (msg === '角色统计' || msg === '武器统计') {
// 暂时避让一下抽卡分析的关键词
return false
}
let avatars = await AvatarList.getAll(e)
if (!avatars) {
return true
}
let uid = avatars.uid
let rank = false
if (e.group_id) {
rank = await ProfileRank.create({ group: e.group_id, uid, qq: e.user_id })
}
let talentData = await avatars.getTalentData()
// 天赋等级背景
let avatarRet = []
lodash.forEach(talentData, (avatar) => {
let { talent, id } = avatar
avatar.aeq = talent?.a?.original + talent?.e?.original + talent?.q?.original || 3
avatarRet.push(avatar)
let profile = avatars.getProfile(id)
if (profile) {
if (profile.hasData) {
let mark = profile.getArtisMark(false)
avatar.artisMark = Data.getData(mark, 'mark,markClass,names')
if (rank) {
rank.getRank(profile)
}
}
async avatarList (e) {
return ProfileStat.render(e, true)
},
async render (e, isAvatarList = false) {
// 缓存时间,单位小时
let msg = e.msg.replace('#', '').trim()
if (msg === '角色统计' || msg === '武器统计') {
// 暂时避让一下抽卡分析的关键词
return false
}
})
let sortKey = 'level,star,aeq,cons,weapon.level,weapon.star,weapon.affix,fetter'.split(',')
avatarRet = lodash.orderBy(avatarRet, sortKey)
avatarRet = avatarRet.reverse()
let talentNotice = ''
if (!avatars.isSelfCookie || avatarRet.length <= 8) {
talentNotice = '未绑定CK信息可能展示不完全。回复<span>#体力帮助</span>获取CK配置帮助'
let mys = await MysApi.init(e)
if (!mys || !mys.uid) return false
const uid = mys.uid
let player = Player.create(e)
let avatarRet = await player.refreshAndGetAvatarData({
detail: 1,
talent: isAvatarList ? 0 : 1,
rank: true,
retType: 'array',
sort: true
})
if (avatarRet.length === 0) {
e.reply(`暂未获得#${uid}角色数据请绑定CK或 #更新面板`)
return true
}
let talentNotice = []
if (!mys.isSelfCookie) {
talentNotice.push('未绑定CK信息可能展示不完全')
}
let faceChar = Character.get(player.face || avatarRet[0]?.id)
let face = {
banner: faceChar.imgs?.banner,
face: faceChar.imgs?.face,
name: player.name || `#${uid}`,
sign: player.sign,
level: player.level
}
let info = player.getInfo()
info.stats = info.stats || {}
info.statMap = {
achievement: '成就',
wayPoint: '锚点',
avatar: '角色',
avatar5: '五星角色',
goldCount: '金卡总数'
}
return await Common.render(isAvatarList ? 'character/avatar-list' : 'character/profile-stat', {
save_id: uid,
uid,
info,
updateTime: player.getUpdateTime(),
isSelfCookie: e.isSelfCookie,
face,
avatars: avatarRet,
talentNotice
}, { e, scale: 1.4 })
}
return await Common.render('character/profile-stat', {
save_id: uid,
uid,
talentLvMap: '0,1,1,1,2,2,3,3,3,4,5'.split(','),
avatars: avatarRet,
isSelf: e.isSelf,
talentNotice
}, { e, scale: 1.8 })
}
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

@ -2,503 +2,36 @@
* 胡桃数据库的统计
*
* */
import lodash from 'lodash'
import { Cfg, Common, App, Data } from '../components/index.js'
import { Abyss, AvatarList, Character, MysApi } from '../models/index.js'
import HutaoApi from './wiki/HutaoApi.js'
import { App } from '../components/index.js'
import { ConsStat, AbyssPct } from './stat/AbyssStat.js'
import { AbyssTeam } from './stat/AbyssTeam.js'
import { AbyssSummary } from './stat/AbyssSummary.js'
let app = App.init({
id: 'stat',
name: '深渊统计'
})
app.reg('cons-stat', consStat, {
rule: /^#(喵喵)?角色(持有|持有率|命座|命之座|.命)(分布|统计|持有|持有率)?$/,
desc: '【#统计】 #角色持有率 #角色5命统计'
})
app.reg('abyss-pct', abyssPct, {
rule: /^#(喵喵)?深渊(第?.{1,2}层)?(角色)?(出场|使用)(率|统计)*$/,
desc: '【#统计】 #深渊出场率 #深渊12层出场率'
})
app.reg('abyss-team', abyssTeam, {
rule: /#深渊(组队|配队)/,
describe: '【#角色】 #深渊组队'
})
app.reg('upload-data', uploadData, {
rule: /^#*(喵喵|上传|本期)*(深渊|深境|深境螺旋)[ |0-9]*(数据)?$/,
desc: '上传深渊'
app.reg({
consStat: {
rule: /^#(喵喵)?角色(持有|持有率|命座|命之座|.命)(分布|统计|持有|持有率)?$/,
fn: ConsStat,
desc: '【#统计】 #角色持有率 #角色5命统计'
},
abyssPct: {
rule: /^#(喵喵)?深渊(第?.{1,2}层)?(角色)?(出场|使用)(率|统计)*$/,
fn: AbyssPct,
desc: '【#统计】 #深渊出场率 #深渊12层出场率'
},
abyssTeam: {
rule: /#深渊(组队|配队)/,
fn: AbyssTeam,
describe: '【#角色】 #深渊组队'
},
abyssSummary: {
rule: /^#*(喵喵|上传|本期)*(深渊|深境|深境螺旋)[ |0-9]*(数据)?$/,
fn: AbyssSummary,
desc: '上传深渊'
}
})
export default app
async function consStat (e) {
let consData = await HutaoApi.getCons()
let overview = await HutaoApi.getOverview()
if (!consData) {
e.reply('角色持有数据获取失败,请稍后重试~')
return true
}
let msg = e.msg
let mode = /持有/.test(msg) ? 'char' : 'cons'
let conNum = -1
if (mode === 'cons') {
lodash.forEach([/0|零/, /1|一/, /2|二/, /3|三/, /4|四/, /5|五/, /6|六|满/], (reg, idx) => {
if (reg.test(msg)) {
conNum = idx
return false
}
})
}
if (!consData && !consData.data) {
return true
}
let data = consData.data
let Lumine = lodash.filter(data, (ds) => ds.avatar === 10000007)[0] || {}
let Aether = lodash.filter(data, (ds) => ds.avatar === 10000005)[0] || {}
Lumine.holdingRate = (1 - Aether.holdingRate) || Lumine.holdingRate
let ret = []
lodash.forEach(data, (ds) => {
let char = Character.get(ds.avatar)
let data = {
name: char.name || ds.avatar,
abbr: char.abbr,
star: char.star || 3,
side: char.side,
hold: ds.holdingRate
}
if (mode === 'char') {
data.cons = lodash.map(ds.rate, (c) => {
c.value = c.value * ds.holdingRate
return c
})
} else {
data.cons = ds.rate
}
data.cons = lodash.sortBy(data.cons, ['id'])
ret.push(data)
})
if (conNum > -1) {
ret = lodash.sortBy(ret, [`cons[${conNum}].value`])
ret.reverse()
} else {
ret = lodash.sortBy(ret, ['hold'])
}
// 渲染图像
return await Common.render('stat/character', {
chars: ret,
mode,
conNum,
totalCount: overview?.data?.totalPlayerCount || 0,
lastUpdate: consData.lastUpdate,
pct: function (num) {
return (num * 100).toFixed(2)
}
}, { e, scale: 1.5 })
}
async function abyssPct (e) {
let mode = /使用/.test(e.msg) ? 'use' : 'pct'
let modeName
let abyssData
let modeMulti = 1
if (mode === 'use') {
modeName = '使用率'
abyssData = await HutaoApi.getAbyssUse()
} else {
modeName = '出场率'
abyssData = await HutaoApi.getAbyssPct()
modeMulti = 8
}
let overview = await HutaoApi.getOverview()
if (!abyssData) {
e.reply(`深渊${modeName}数据获取失败,请稍后重试~`)
return true
}
let ret = []
let chooseFloor = -1
let msg = e.msg
const floorName = {
12: '十二层',
11: '十一层',
10: '十层',
9: '九层'
}
// 匹配深渊楼层信息
lodash.forEach(floorName, (cn, num) => {
let reg = new RegExp(`${cn}|${num}`)
if (reg.test(msg)) {
chooseFloor = num
return false
}
})
let data = abyssData.data
data = lodash.sortBy(data, 'floor')
data = data.reverse()
lodash.forEach(data, (floorData) => {
let avatars = []
lodash.forEach(floorData.avatarUsage, (ds) => {
let char = Character.get(ds.id)
if (char) {
avatars.push({
name: char.name,
star: char.star,
value: ds.value * modeMulti,
face: char.face
})
}
})
avatars = lodash.sortBy(avatars, 'value', ['asc'])
avatars.reverse()
if (chooseFloor === -1) {
avatars = avatars.slice(0, 14)
}
ret.push({
floor: floorData.floor,
avatars
})
})
return await Common.render('stat/abyss-pct', {
abyss: ret,
floorName,
chooseFloor,
mode,
modeName,
totalCount: overview?.data?.collectedPlayerCount || 0,
lastUpdate: abyssData.lastUpdate
}, { e, scale: 1.5 })
}
async function abyssTeam (e) {
let mys = await MysApi.init(e, 'cookie')
if (!mys || !mys.uid || !mys.isSelfCookie) {
return true
}
let abyssData = await HutaoApi.getAbyssTeam()
if (!abyssData || !abyssData.data) {
e.reply('深渊组队数据获取失败,请稍后重试~')
return true
}
abyssData = abyssData.data
let avatars
try {
avatars = await AvatarList.getAll(e, mys)
// resDetail = await mys.getCharacter()
if (!avatars) {
e.reply('角色信息获取失败')
return true
}
} catch (err) {
// console.log(err);
}
let avatarData = await avatars.getTalentData()
let avatarRet = {}
let data = {}
let noAvatar = {}
lodash.forEach(avatarData, (avatar) => {
let t = avatar.talent
avatarRet[avatar.id] = Math.min(avatar.level, avatar.weapon?.level || 1) * 100 + Math.max(t.a?.original, t.e?.original, t.q?.original) * 1000
})
let getTeamCfg = (str) => {
let teams = str.split(',')
teams.sort()
let teamMark = 0
lodash.forEach(teams, (a) => {
if (!avatarRet[a]) {
teamMark = -1
noAvatar[a] = true
}
if (teamMark !== -1) {
teamMark += avatarRet[a] * 1
}
})
if (teamMark === -1) {
teamMark = 1
}
return {
key: teams.join(','),
mark: teamMark
}
}
let hasSame = function (team1, team2) {
for (let idx = 0; idx < team1.length; idx++) {
if (team2.includes(team1[idx])) {
return true
}
}
return false
}
lodash.forEach(abyssData, (ds) => {
let floor = ds.floor
if (!data[floor]) {
data[floor] = {
up: {},
down: {},
teams: []
}
}
lodash.forEach(['up', 'down'], (halfKey) => {
lodash.forEach(ds[halfKey], (ds) => {
let teamCfg = getTeamCfg(ds.item)
if (teamCfg) {
if (!data[floor][halfKey][teamCfg.key]) {
data[floor][halfKey][teamCfg.key] = {
count: 0,
mark: 0,
hasTeam: teamCfg.mark > 1
}
}
data[floor][halfKey][teamCfg.key].count += ds.rate
data[floor][halfKey][teamCfg.key].mark += ds.rate * teamCfg.mark
}
})
})
let temp = []
lodash.forEach(['up', 'down'], (halfKey) => {
lodash.forEach(data[floor][halfKey], (ds, team) => {
temp.push({
team,
teamArr: team.split(','),
half: halfKey,
count: ds.count,
mark: ds.mark,
mark2: 1,
hasTeam: ds.hasTeam
})
})
temp = lodash.sortBy(temp, 'mark')
data[floor].teams = temp.reverse()
})
})
let ret = {}
lodash.forEach(data, (floorData, floor) => {
ret[floor] = {}
let ds = ret[floor]
lodash.forEach(floorData.teams, (t1) => {
if (t1.mark2 <= 0) {
return true
}
lodash.forEach(floorData.teams, (t2) => {
if (t1.mark2 <= 0) {
return true
}
if (t1.half === t2.half || t2.mark2 <= 0) {
return true
}
let teamKey = t1.half === 'up' ? (t1.team + '+' + t2.team) : (t2.team + '+' + t1.team)
if (ds[teamKey]) {
return true
}
if (hasSame(t1.teamArr, t2.teamArr)) {
return true
}
ds[teamKey] = {
up: t1.half === 'up' ? t1 : t2,
down: t1.half === 'up' ? t2 : t1,
count: Math.min(t1.count, t2.count),
mark: t1.hasTeam && t2.hasTeam ? t1.mark + t2.mark : t1.count + t2.count // 如果不存在组队则进行评分惩罚
}
t1.mark2--
t2.mark2--
return false
})
if (lodash.keys(ds).length >= 20) {
return false
}
})
})
lodash.forEach(ret, (ds, floor) => {
ds = lodash.sortBy(lodash.values(ds), 'mark')
ds = ds.reverse()
ds = ds.slice(0, 4)
lodash.forEach(ds, (team) => {
team.up.teamArr = Character.sortIds(team.up.teamArr)
team.down.teamArr = Character.sortIds(team.down.teamArr)
})
ret[floor] = ds
})
let avatarMap = {}
lodash.forEach(avatarData, (ds) => {
let char = Character.get(ds.id)
avatarMap[ds.id] = {
id: ds.id,
name: ds.name,
star: ds.star,
level: ds.level,
cons: ds.cons,
face: char.face
}
})
lodash.forEach(noAvatar, (d, id) => {
let char = Character.get(id)
avatarMap[id] = {
id,
name: char.name,
face: char.face,
star: char.star,
level: 0,
cons: 0
}
})
return await Common.render('stat/abyss-team', {
teams: ret,
avatars: avatarMap
}, { e, scale: 1.5 })
}
async function uploadData (e) {
let isMatch = /^#(喵喵|上传)深渊(数据)?$/.test(e.original_msg || e.msg || '')
if (!Cfg.get('uploadAbyssData', false) && !isMatch) {
return false
}
let mys = await MysApi.init(e, 'all')
if (!mys || !mys.uid) {
if (isMatch) {
e.reply(`请绑定ck后再使用${e.original_msg || e.msg}`)
}
return false
}
let ret = {}
let uid = mys.uid
let resDetail, resAbyss
try {
resAbyss = await mys.getSpiralAbyss(1)
let lvs = Data.getVal(resAbyss, 'floors.0.levels.0')
// 检查是否查询到了深渊信息
if (!lvs || !lvs.battles) {
e.reply('暂未获得本期深渊挑战数据...')
return true
} else if (lvs && lvs.battles && lvs.battles.length === 0) {
if (!mys.isSelfCookie) {
if (isMatch) {
e.reply(`请绑定ck后再使用${e.original_msg || e.msg}`)
}
return false
}
}
if (resAbyss.floors.length > 0 && !await AvatarList.hasTalentCache(uid)) {
e.reply('正在获取用户信息,请稍候...')
}
resDetail = await mys.getCharacter()
if (!resDetail || !resAbyss || !resDetail.avatars || resDetail.avatars.length <= 3) {
e.reply('角色信息获取失败')
return true
}
delete resDetail._res
delete resAbyss._res
ret = await HutaoApi.uploadData({
uid,
resDetail,
resAbyss
})
} catch (err) {
// console.log(err);
}
if (ret && ret.retcode === 0) {
let stat = []
if (ret.data) {
if (resAbyss.floors.length === 0) {
e.reply('暂未获得本期深渊挑战数据...')
return true
}
let abyss = new Abyss(resAbyss)
let abyssData = abyss.getData()
let avatars = new AvatarList(uid, resDetail.avatars)
let avatarIds = abyss.getAvatars()
let overview = ret.info || (await HutaoApi.getOverview())?.data || {}
let addMsg = function (title, ds) {
let tmp = {}
if (!ds) {
return false
}
if (!ds.avatarId && !ds.id) {
return false
}
let char = Character.get(ds.avatarId || ds.id)
tmp.title = title
tmp.id = char.id
tmp.value = `${(ds.value / 10000).toFixed(1)} W`
let msg = []
tmp.msg = msg
let pct = (percent, name) => {
if (percent < 0.2) {
msg.push({
title: '少于',
value: (Math.max(0.1, 100 - percent * 100)).toFixed(1),
name: name
})
} else {
msg.push({
title: '超过',
value: (Math.min(99.9, percent * 100)).toFixed(1),
name: name
})
}
}
if (ds.percent) {
pct(ds.percent, char.name)
pct(ds.percentTotal, '总记录')
} else {
msg.push({
txt: '暂无统计信息'
})
}
stat.push(tmp)
}
addMsg('最强一击', ret.data?.damage || abyssData?.stat?.dmg || {})
addMsg('最高承伤', ret.data?.takeDamage || abyssData?.stat.takeDmg || {})
let avatarData = await avatars.getTalentData(avatarIds, mys)
return await Common.render('stat/abyss-summary', {
abyss: abyssData,
avatars: avatarData,
stat,
save_id: uid,
totalCount: overview?.collectedPlayerCount || 0,
uid
}, { e, scale: 1.8 })
} else {
e.reply('暂未获得本期深渊挑战数据...')
return true
}
} else {
e.reply(`${ret.message || '上传失败'},请稍后重试...`)
}
return true
}

164
apps/stat/AbyssStat.js Normal file
View File

@ -0,0 +1,164 @@
import HutaoApi from './HutaoApi.js'
import lodash from 'lodash'
import { Character } from '../../models/index.js'
import { Common } from '../../components/index.js'
export async function ConsStat (e) {
let consData = await HutaoApi.getCons()
let overview = await HutaoApi.getOverview()
if (!consData) {
e.reply('角色持有数据获取失败,请稍后重试~')
return true
}
let msg = e.msg
let mode = /持有/.test(msg) ? 'char' : 'cons'
let conNum = -1
if (mode === 'cons') {
lodash.forEach([/0|零/, /1|一/, /2|二/, /3|三/, /4|四/, /5|五/, /6|六|满/], (reg, idx) => {
if (reg.test(msg)) {
conNum = idx
return false
}
})
}
if (!consData && !consData.data) {
return true
}
let data = consData.data
let Lumine = lodash.filter(data, (ds) => ds.avatar === 10000007)[0] || {}
let Aether = lodash.filter(data, (ds) => ds.avatar === 10000005)[0] || {}
Lumine.holdingRate = (1 - Aether.holdingRate) || Lumine.holdingRate
let ret = []
lodash.forEach(data, (ds) => {
let char = Character.get(ds.avatar)
let data = {
name: char.name || ds.avatar,
abbr: char.abbr,
star: char.star || 3,
side: char.side,
hold: ds.holdingRate
}
if (mode === 'char') {
data.cons = lodash.map(ds.rate, (c) => {
c.value = c.value * ds.holdingRate
return c
})
} else {
data.cons = ds.rate
}
data.cons = lodash.sortBy(data.cons, ['id'])
ret.push(data)
})
if (conNum > -1) {
ret = lodash.sortBy(ret, [`cons[${conNum}].value`])
ret.reverse()
} else {
ret = lodash.sortBy(ret, ['hold'])
}
// 渲染图像
return await Common.render('stat/character', {
chars: ret,
mode,
conNum,
totalCount: overview?.data?.totalPlayerCount || 0,
lastUpdate: consData.lastUpdate,
pct: function (num) {
return (num * 100).toFixed(2)
}
}, { e, scale: 1.5 })
}
export async function AbyssPct (e) {
let mode = /使用/.test(e.msg) ? 'use' : 'pct'
let modeName
let abyssData
let modeMulti = 1
if (mode === 'use') {
modeName = '使用率'
abyssData = await HutaoApi.getAbyssUse()
} else {
modeName = '出场率'
abyssData = await HutaoApi.getAbyssPct()
modeMulti = 8
}
let overview = await HutaoApi.getOverview()
if (!abyssData) {
e.reply(`深渊${modeName}数据获取失败,请稍后重试~`)
return true
}
let ret = []
let chooseFloor = -1
let msg = e.msg
const floorName = {
12: '十二层',
11: '十一层',
10: '十层',
9: '九层'
}
// 匹配深渊楼层信息
lodash.forEach(floorName, (cn, num) => {
let reg = new RegExp(`${cn}|${num}`)
if (reg.test(msg)) {
chooseFloor = num
return false
}
})
let data = abyssData.data
data = lodash.sortBy(data, 'floor')
data = data.reverse()
lodash.forEach(data, (floorData) => {
let avatars = []
lodash.forEach(floorData.avatarUsage, (ds) => {
let char = Character.get(ds.id)
if (char) {
avatars.push({
name: char.name,
star: char.star,
value: ds.value * modeMulti,
face: char.face
})
}
})
avatars = lodash.sortBy(avatars, 'value', ['asc'])
avatars.reverse()
if (chooseFloor === -1) {
avatars = avatars.slice(0, 14)
}
ret.push({
floor: floorData.floor,
avatars
})
})
return await Common.render('stat/abyss-pct', {
abyss: ret,
floorName,
chooseFloor,
mode,
modeName,
totalCount: overview?.data?.collectedPlayerCount || 0,
lastUpdate: abyssData.lastUpdate
}, { e, scale: 1.5 })
}

137
apps/stat/AbyssSummary.js Normal file
View File

@ -0,0 +1,137 @@
import { Cfg, Common, Data } from '../../components/index.js'
import { Abyss, Character, MysApi, Player } from '../../models/index.js'
import HutaoApi from './HutaoApi.js'
import lodash from 'lodash'
export async function AbyssSummary (e) {
let isMatch = /^#(喵喵|上传)深渊(数据)?$/.test(e.original_msg || e.msg || '')
if (!Cfg.get('uploadAbyssData', false) && !isMatch) {
return false
}
let mys = await MysApi.init(e, 'all')
if (!mys || !mys.uid) {
if (isMatch) {
e.reply(`请绑定ck后再使用${e.original_msg || e.msg}`)
}
return false
}
let ret = {}
let uid = mys.uid
let player = Player.create(e)
let resDetail, resAbyss
try {
resAbyss = await mys.getSpiralAbyss(1)
let lvs = Data.getVal(resAbyss, 'floors.0.levels.0')
// 检查是否查询到了深渊信息
if (!lvs || !lvs.battles) {
e.reply('暂未获得本期深渊挑战数据...')
return true
} else if (lvs && lvs.battles && lvs.battles.length === 0) {
if (!mys.isSelfCookie) {
if (isMatch) {
e.reply(`请绑定ck后再使用${e.original_msg || e.msg}`)
}
return false
}
}
resDetail = await mys.getCharacter()
if (!resDetail || !resAbyss || !resDetail.avatars || resDetail.avatars.length <= 3) {
e.reply('角色信息获取失败')
return true
}
delete resDetail._res
delete resAbyss._res
ret = await HutaoApi.uploadData({
uid,
resDetail,
resAbyss
})
} catch (err) {
// console.log(err);
}
// 更新player信息
player.setMysCharData(resDetail)
if (ret && ret.retcode === 0) {
let stat = []
if (ret.data) {
if (resAbyss.floors.length === 0) {
e.reply('暂未获得本期深渊挑战数据...')
return true
}
let abyss = new Abyss(resAbyss)
let abyssData = abyss.getData()
let avatarIds = abyss.getAvatars()
let overview = ret.info || (await HutaoApi.getOverview())?.data || {}
let addMsg = function (title, ds) {
let tmp = {}
if (!ds) {
return false
}
if (!ds.avatarId && !ds.id) {
return false
}
let char = Character.get(ds.avatarId || ds.id)
tmp.title = title
tmp.id = char.id
tmp.value = `${(ds.value / 10000).toFixed(1)} W`
let msg = []
tmp.msg = msg
let pct = (percent, name) => {
if (percent < 0.2) {
msg.push({
title: '少于',
value: (Math.max(0.1, 100 - percent * 100)).toFixed(1),
name: name
})
} else {
msg.push({
title: '超过',
value: (Math.min(99.9, percent * 100)).toFixed(1),
name: name
})
}
}
if (ds.percent) {
pct(ds.percent, char.abbr)
pct(ds.percentTotal, '总记录')
} else {
msg.push({
txt: '暂无统计信息'
})
}
stat.push(tmp)
}
addMsg('最强一击', ret.data?.damage || abyssData?.stat?.dmg || {})
addMsg('最高承伤', ret.data?.takeDamage || abyssData?.stat.takeDmg || {})
let abyssStat = abyssData?.stat || {}
lodash.forEach({ defeat: '最多击破', e: '元素战技', q: '元素爆发' }, (title, key) => {
if (abyssStat[key]) {
stat.push({
title,
id: abyssStat[key]?.id || 0,
value: `${abyssStat[key]?.value}`
})
} else {
stat.push({})
}
})
await player.refreshTalent(avatarIds)
let avatarData = player.getAvatarData(avatarIds)
return await Common.render('stat/abyss-summary', {
abyss: abyssData,
avatars: avatarData,
stat,
save_id: uid,
totalCount: overview?.collectedPlayerCount || 0,
uid
}, { e, scale: 1.2 })
} else {
e.reply('暂未获得本期深渊挑战数据...')
return true
}
} else {
e.reply(`${ret.message || '上传失败'},请稍后重试...`)
}
return true
}

189
apps/stat/AbyssTeam.js Normal file
View File

@ -0,0 +1,189 @@
import { Character, MysApi, Player } from '../../models/index.js'
import HutaoApi from './HutaoApi.js'
import lodash from 'lodash'
import { Common } from '../../components/index.js'
export async function AbyssTeam (e) {
let mys = await MysApi.init(e, 'cookie')
if (!mys || !mys.uid || !mys.isSelfCookie) {
return true
}
let player = Player.create(e)
await player.refreshMysDetail()
await player.refreshTalent()
let abyssData = await HutaoApi.getAbyssTeam()
if (!abyssData || !abyssData.data) {
e.reply('深渊组队数据获取失败,请稍后重试~')
return true
}
abyssData = abyssData.data
let avatarData = player.getAvatarData()
let avatarRet = {}
let data = {}
let noAvatar = {}
lodash.forEach(avatarData, (avatar) => {
let t = avatar.originalTalent
avatarRet[avatar.id] = Math.min(avatar.level, avatar.weapon?.level || 1) * 100 + Math.max(t?.a, t?.e, t?.q) * 1000
})
let getTeamCfg = (str) => {
let teams = str.split(',')
teams.sort()
let teamMark = 0
lodash.forEach(teams, (a) => {
if (!avatarRet[a]) {
teamMark = -1
noAvatar[a] = true
}
if (teamMark !== -1) {
teamMark += avatarRet[a] * 1
}
})
if (teamMark === -1) {
teamMark = 1
}
return {
key: teams.join(','),
mark: teamMark
}
}
let hasSame = function (team1, team2) {
for (let idx = 0; idx < team1.length; idx++) {
if (team2.includes(team1[idx])) {
return true
}
}
return false
}
lodash.forEach(abyssData, (ds) => {
let floor = ds.floor
if (!data[floor]) {
data[floor] = {
up: {},
down: {},
teams: []
}
}
lodash.forEach(['up', 'down'], (halfKey) => {
lodash.forEach(ds[halfKey], (ds) => {
let teamCfg = getTeamCfg(ds.item)
if (teamCfg) {
if (!data[floor][halfKey][teamCfg.key]) {
data[floor][halfKey][teamCfg.key] = {
count: 0,
mark: 0,
hasTeam: teamCfg.mark > 1
}
}
data[floor][halfKey][teamCfg.key].count += ds.rate
data[floor][halfKey][teamCfg.key].mark += ds.rate * teamCfg.mark
}
})
})
let temp = []
lodash.forEach(['up', 'down'], (halfKey) => {
lodash.forEach(data[floor][halfKey], (ds, team) => {
temp.push({
team,
teamArr: team.split(','),
half: halfKey,
count: ds.count,
mark: ds.mark,
mark2: 1,
hasTeam: ds.hasTeam
})
})
temp = lodash.sortBy(temp, 'mark')
data[floor].teams = temp.reverse()
})
})
let ret = {}
lodash.forEach(data, (floorData, floor) => {
ret[floor] = {}
let ds = ret[floor]
lodash.forEach(floorData.teams, (t1) => {
if (t1.mark2 <= 0) {
return true
}
lodash.forEach(floorData.teams, (t2) => {
if (t1.mark2 <= 0) {
return true
}
if (t1.half === t2.half || t2.mark2 <= 0) {
return true
}
let teamKey = t1.half === 'up' ? (t1.team + '+' + t2.team) : (t2.team + '+' + t1.team)
if (ds[teamKey]) {
return true
}
if (hasSame(t1.teamArr, t2.teamArr)) {
return true
}
ds[teamKey] = {
up: t1.half === 'up' ? t1 : t2,
down: t1.half === 'up' ? t2 : t1,
count: Math.min(t1.count, t2.count),
mark: t1.hasTeam && t2.hasTeam ? t1.mark + t2.mark : t1.count + t2.count // 如果不存在组队则进行评分惩罚
}
t1.mark2--
t2.mark2--
return false
})
if (lodash.keys(ds).length >= 20) {
return false
}
})
})
lodash.forEach(ret, (ds, floor) => {
ds = lodash.sortBy(lodash.values(ds), 'mark')
ds = ds.reverse()
ds = ds.slice(0, 4)
lodash.forEach(ds, (team) => {
team.up.teamArr = Character.sortIds(team.up.teamArr)
team.down.teamArr = Character.sortIds(team.down.teamArr)
})
ret[floor] = ds
})
let avatarMap = {}
lodash.forEach(avatarData, (ds) => {
let char = Character.get(ds.id)
avatarMap[ds.id] = {
id: ds.id,
name: ds.name,
star: ds.star,
level: ds.level,
cons: ds.cons,
face: char.face
}
})
lodash.forEach(noAvatar, (d, id) => {
let char = Character.get(id)
avatarMap[id] = {
id,
name: char.name,
face: char.face,
star: char.star,
level: 0,
cons: 0
}
})
return await Common.render('stat/abyss-team', {
teams: ret,
avatars: avatarMap
}, { e, scale: 1.5 })
}

View File

@ -1,183 +1,23 @@
import { segment } from 'oicq'
import lodash from 'lodash'
import { App } from '../components/index.js'
import Calendar from './wiki/Calendar.js'
import { Format, Cfg, Common, App } from '../components/index.js'
import { Character } from '../models/index.js'
import CharWiki from './wiki/CharWiki.js'
let wikiReg = /^(?:#|喵喵)?(.*)(天赋|技能|命座|命之座|资料|图鉴|照片|写真|图片|图像)$/
let app = App.init({
id: 'wiki',
name: '角色资料'
})
app.reg('wiki', wiki, {
rule: '^#喵喵WIKI$',
check: checkCharacter,
desc: '【#资料】 #神里天赋 #夜兰命座'
})
app.reg('calendar', calendar, {
rule: /^(#|喵喵)+(日历|日历列表)$/,
desc: '【#日历】 活动日历'
app.reg({
wiki: {
rule: '^#喵喵WIKI$',
check: CharWiki.check,
fn: CharWiki.wiki,
desc: '【#资料】 #神里天赋 #夜兰命座'
},
calendar: {
rule: /^(#|喵喵)+(日历|日历列表)$/,
fn: Calendar.render,
desc: '【#日历】 活动日历'
}
})
export default app
function checkCharacter (e) {
let msg = e.original_msg || e.msg
if (!e.msg) {
return false
}
let ret = wikiReg.exec(msg)
if (!ret || !ret[1] || !ret[2]) {
return false
}
let mode = 'talent'
if (/命/.test(ret[2])) {
mode = 'cons'
} else if (/(图鉴|资料)/.test(ret[2])) {
mode = 'wiki'
if (!Common.cfg('charWiki')) {
return false
}
} else if (/图|画|写真|照片/.test(ret[2])) {
mode = 'pic'
if (!Common.cfg('charPic')) {
return false
}
} else if (/(材料|养成|成长)/.test(ret[2])) {
mode = 'material'
}
if (['cons', 'talent'].includes(mode) && !Common.cfg('charWikiTalent')) {
return false
}
let char = Character.get(ret[1])
if (!char) {
return false
}
e.wikiMode = mode
e.msg = '#喵喵WIKI'
e.char = char
return true
}
async function wiki (e) {
let mode = e.wikiMode
let char = e.char
if (mode === 'pic') {
let img = char.getCardImg(Cfg.get('charPicSe', false), false)
if (img && img.img) {
e.reply(segment.image(process.cwd() + '/plugins/miao-plugin/resources/' + img.img))
} else {
e.reply('暂无图片')
}
return true
}
if (char.isCustom) {
if (mode === 'wiki') {
return false
}
e.reply('暂不支持自定义角色')
return true
}
if (!char.isRelease && Cfg.get('notReleasedData') === false) {
e.reply('未实装角色资料已禁用...')
return true
}
let lvs = []
for (let i = 1; i <= 15; i++) {
lvs.push('Lv' + i)
}
if (mode === 'wiki') {
if (char.source === 'amber') {
e.reply('暂不支持该角色图鉴展示')
return true
}
return await renderWiki({ e, char })
} else if (mode === 'material') {
return await renderCharMaterial({ e, char })
}
return await Common.render('wiki/character-talent', {
saveId: `${mode}-${char.id}`,
...char.getData(),
detail: char.getDetail(),
imgs: char.getImgs(),
mode,
lvs,
line: getLineData(char)
}, { e, scale: 1.1 })
}
async function renderWiki ({ e, char }) {
let data = char.getData()
lodash.extend(data, char.getData('weaponTypeName,elemName'))
// 命座持有
let holding = await CharWiki.getHolding(char.id)
let usage = await CharWiki.getUsage(char.id)
return await Common.render('wiki/character-wiki', {
data,
attr: char.getAttrList(),
detail: char.getDetail(),
imgs: char.getImgs(),
holding,
usage,
materials: char.getMaterials(),
elem: char.elem
}, { e, scale: 1.4 })
}
async function renderCharMaterial ({ e, char }) {
let data = char.getData()
return await Common.render('wiki/character-material', {
// saveId: `info-${char.id}`,
data,
attr: char.getAttrList(),
detail: char.getDetail(),
imgs: char.getImgs(),
materials: char.getMaterials(),
elem: char.elem
}, { e, scale: 1.4 })
}
const getLineData = function (char) {
let ret = []
const attrMap = {
atkPct: '大攻击',
hpPct: '大生命',
defPct: '大防御',
cpct: '暴击',
cdmg: '爆伤',
recharge: '充能',
mastery: '精通',
heal: '治疗',
dmg: char.elemName + '伤',
phy: '物伤'
}
lodash.forEach({ hp: '基础生命', atk: '基础攻击', def: '基础防御' }, (label, key) => {
ret.push({
num: Format.comma(char.baseAttr[key], 1),
label
})
})
let ga = char.growAttr
ret.push({
num: ga.key === 'mastery' ? Format.comma(ga.value, 1) : ga.value,
label: `成长·${attrMap[ga.key]}`
})
return ret
}
async function calendar (e) {
let calData = await Calendar.get()
let mode = 'calendar'
if (/(日历列表|活动)$/.test(e.msg)) {
mode = 'list'
}
return await Common.render('wiki/calendar', {
...calData,
displayMode: mode
}, { e, scale: 1.1 })
}

View File

@ -1,7 +1,7 @@
import fetch from 'node-fetch'
import moment from 'moment'
import { Character, Material } from '../../models/index.js'
import { 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)
@ -397,6 +397,19 @@ let Cal = {
nowTime: now.format('YYYY-MM-DD HH:mm'),
nowDate: now.date()
}
},
async render (e) {
let calData = await Cal.get()
let mode = 'calendar'
if (/(日历列表|活动)$/.test(e.msg)) {
mode = 'list'
}
return await Common.render('wiki/calendar', {
...calData,
displayMode: mode
}, { e, scale: 1.1 })
}
}

18
apps/wiki/CharMaterial.js Normal file
View File

@ -0,0 +1,18 @@
import { Common } from '../../components/index.js'
const CharMaterial = {
async render ({ e, char }) {
let data = char.getData()
return await Common.render('wiki/character-material', {
// saveId: `info-${char.id}`,
data,
attr: char.getAttrList(),
detail: char.getDetail(),
imgs: char.getImgs(),
materials: char.getMaterials(),
elem: char.elem
}, { e, scale: 1.4 })
}
}
export default CharMaterial

49
apps/wiki/CharTalent.js Normal file
View File

@ -0,0 +1,49 @@
import { Common, Format } from '../../components/index.js'
import lodash from 'lodash'
const CharTalent = {
async render (e, mode, char) {
let lvs = []
for (let i = 1; i <= 15; i++) {
lvs.push('Lv' + i)
}
return await Common.render('wiki/character-talent', {
saveId: `${mode}-${char.id}`,
...char.getData(),
detail: char.getDetail(),
imgs: char.getImgs(),
mode,
lvs,
line: CharTalent.getLineData(char)
}, { e, scale: 1.1 })
},
getLineData (char) {
let ret = []
const attrMap = {
atkPct: '大攻击',
hpPct: '大生命',
defPct: '大防御',
cpct: '暴击',
cdmg: '爆伤',
recharge: '充能',
mastery: '精通',
heal: '治疗',
dmg: char.elemName + '伤',
phy: '物伤'
}
lodash.forEach({ hp: '基础生命', atk: '基础攻击', def: '基础防御' }, (label, key) => {
ret.push({
num: Format.comma(char.baseAttr[key], 1),
label
})
})
let ga = char.growAttr
ret.push({
num: ga.key === 'mastery' ? Format.comma(ga.value, 1) : ga.value,
label: `成长·${attrMap[ga.key]}`
})
return ret
}
}
export default CharTalent

View File

@ -1,108 +1,107 @@
import HutaoApi from './HutaoApi.js'
import { Cfg, Common } from '../../components/index.js'
import { Character } from '../../models/index.js'
import { segment } from 'oicq'
import CharTalent from './CharTalent.js'
import lodash from 'lodash'
import { Format } from '../../components/index.js'
import { ArtifactSet, Weapon } from '../../models/index.js'
import CharWikiData from './CharWikiData.js'
import CharMaterial from './CharMaterial.js'
let CharWiki = {
/**
* 角色命座持有
* @param id
* @returns {Promise<{}>}
*/
async getHolding (id) {
let consData = (await HutaoApi.getCons()).data || {}
consData = lodash.find(consData, (ds) => ds.avatar === id)
let holding = {}
if (consData) {
let { holdingRate, rate } = consData
rate = lodash.sortBy(rate, 'id')
holding.num = Format.percent(holdingRate)
holding.cons = []
lodash.forEach(rate, (ds) => {
holding.cons.push({
cons: ds.id,
num: Format.percent(ds.value)
})
})
const wikiReg = /^(?:#|喵喵)?(.*)(天赋|技能|命座|命之座|资料|图鉴|照片|写真|图片|图像)$/
const CharWiki = {
check (e) {
let msg = e.original_msg || e.msg
if (!e.msg) {
return false
}
return holding
let ret = wikiReg.exec(msg)
if (!ret || !ret[1] || !ret[2]) {
return false
}
let mode = 'talent'
if (/命/.test(ret[2])) {
mode = 'cons'
} else if (/(图鉴|资料)/.test(ret[2])) {
mode = 'wiki'
if (!Common.cfg('charWiki')) {
return false
}
} else if (/图|画|写真|照片/.test(ret[2])) {
mode = 'pic'
if (!Common.cfg('charPic')) {
return false
}
} else if (/(材料|养成|成长)/.test(ret[2])) {
mode = 'material'
}
if (['cons', 'talent'].includes(mode) && !Common.cfg('charWikiTalent')) {
return false
}
let char = Character.get(ret[1])
if (!char) {
return false
}
e.wikiMode = mode
e.msg = '#喵喵WIKI'
e.char = char
return true
},
/**
* 角色武器圣遗物使用
* @param id
* @returns {Promise<{}|{artis: *[], weapons: *[]}>}
*/
async getUsage (id) {
let ud = (await HutaoApi.getUsage()).data || {}
if (!ud[id]) {
return {}
async wiki (e) {
let mode = e.wikiMode
let char = e.char
if (mode === 'pic') {
let img = char.getCardImg(Cfg.get('charPicSe', false), false)
if (img && img.img) {
e.reply(segment.image(process.cwd() + '/plugins/miao-plugin/resources/' + img.img))
} else {
e.reply('暂无图片')
}
return true
}
ud = ud[id]
return {
weapons: CharWiki.getWeaponsData(ud.weapons),
artis: CharWiki.getArtisData(ud.artis)
if (char.isCustom) {
if (mode === 'wiki') {
return false
}
e.reply('暂不支持自定义角色')
return true
}
if (!char.isRelease && Cfg.get('notReleasedData') === false) {
e.reply('未实装角色资料已禁用...')
return true
}
if (mode === 'wiki') {
if (char.source === 'amber') {
e.reply('暂不支持该角色图鉴展示')
return true
}
return await CharWiki.render({ e, char })
} else if (mode === 'material') {
return CharMaterial.render({ e, char })
}
return await CharTalent.render(e, mode, char)
},
/**
* 武器使用
* @param data
* @returns {*[]}
*/
getWeaponsData (data = []) {
let weapons = []
lodash.forEach(data, (ds) => {
let weapon = Weapon.get(ds.item) || {}
weapons.push({
...weapon.getData('name,abbr,img,star'),
value: ds.rate
})
})
weapons = lodash.sortBy(weapons, 'value')
weapons = weapons.reverse()
lodash.forEach(weapons, (ds) => {
ds.value = Format.percent(ds.value, 1)
})
return weapons
},
/**
* 圣遗物使用
* @param data
* @returns {*[]}
*/
getArtisData (data = []) {
let artis = []
lodash.forEach(data, (ds) => {
let imgs = []
let abbrs = []
let ss = ds.item.split(',')
lodash.forEach(ss, (t) => {
t = t.split(':')
let artiSet = ArtifactSet.get(t[0])
if (artiSet) {
imgs.push(artiSet.img)
abbrs.push(artiSet.abbr + (ss.length === 1 ? t[1] : ''))
}
})
artis.push({
imgs,
title: abbrs.join('+'),
value: ds.rate
})
})
artis = lodash.sortBy(artis, 'value')
artis = artis.reverse()
artis.forEach((ds) => {
ds.value = Format.percent(ds.value)
})
return artis
async render ({ e, char }) {
let data = char.getData()
lodash.extend(data, char.getData('weaponTypeName,elemName'))
// 命座持有
let holding = await CharWikiData.getHolding(char.id)
let usage = await CharWikiData.getUsage(char.id)
return await Common.render('wiki/character-wiki', {
data,
attr: char.getAttrList(),
detail: char.getDetail(),
imgs: char.getImgs(),
holding,
usage,
materials: char.getMaterials(),
elem: char.elem
}, { e, scale: 1.4 })
}
}
export default CharWiki

108
apps/wiki/CharWikiData.js Normal file
View File

@ -0,0 +1,108 @@
import HutaoApi from '../stat/HutaoApi.js'
import lodash from 'lodash'
import { Format } from '../../components/index.js'
import { ArtifactSet, Weapon } from '../../models/index.js'
let CharWikiData = {
/**
* 角色命座持有
* @param id
* @returns {Promise<{}>}
*/
async getHolding (id) {
let consData = (await HutaoApi.getCons()).data || {}
consData = lodash.find(consData, (ds) => ds.avatar === id)
let holding = {}
if (consData) {
let { holdingRate, rate } = consData
rate = lodash.sortBy(rate, 'id')
holding.num = Format.percent(holdingRate)
holding.cons = []
lodash.forEach(rate, (ds) => {
holding.cons.push({
cons: ds.id,
num: Format.percent(ds.value)
})
})
}
return holding
},
/**
* 角色武器圣遗物使用
* @param id
* @returns {Promise<{}|{artis: *[], weapons: *[]}>}
*/
async getUsage (id) {
let ud = (await HutaoApi.getUsage()).data || {}
if (!ud[id]) {
return {}
}
ud = ud[id]
return {
weapons: CharWikiData.getWeaponsData(ud.weapons),
artis: CharWikiData.getArtisData(ud.artis)
}
},
/**
* 武器使用
* @param data
* @returns {*[]}
*/
getWeaponsData (data = []) {
let weapons = []
lodash.forEach(data, (ds) => {
let weapon = Weapon.get(ds.item) || {}
weapons.push({
...weapon.getData('name,abbr,img,star'),
value: ds.rate
})
})
weapons = lodash.sortBy(weapons, 'value')
weapons = weapons.reverse()
lodash.forEach(weapons, (ds) => {
ds.value = Format.percent(ds.value, 1)
})
return weapons
},
/**
* 圣遗物使用
* @param data
* @returns {*[]}
*/
getArtisData (data = []) {
let artis = []
lodash.forEach(data, (ds) => {
let imgs = []
let abbrs = []
let ss = ds.item.split(',')
lodash.forEach(ss, (t) => {
t = t.split(':')
let artiSet = ArtifactSet.get(t[0])
if (artiSet) {
imgs.push(artiSet.img)
abbrs.push(artiSet.abbr + (ss.length === 1 ? t[1] : ''))
}
})
artis.push({
imgs,
title: abbrs.join('+'),
value: ds.rate
})
})
artis = lodash.sortBy(artis, 'value')
artis = artis.reverse()
artis.forEach((ds) => {
ds.value = Format.percent(ds.value)
})
return artis
}
}
export default CharWikiData

View File

@ -1,5 +1,5 @@
import lodash from 'lodash'
import plugin from './common-lib/plugin.js'
import Plugin from './common/Plugin.js'
class App {
constructor (cfg) {
@ -9,9 +9,15 @@ class App {
}
reg (key, fn, cfg = {}) {
this.apps[key] = {
fn,
...cfg
if (lodash.isPlainObject(key)) {
lodash.forEach(key, (cfg, k) => {
this.reg(k, cfg.fn, cfg)
})
} else {
this.apps[key] = {
fn,
...cfg
}
}
}
@ -21,7 +27,7 @@ class App {
let rules = []
let check = []
let event = cfg.event
let cls = class extends plugin {
let cls = class extends Plugin {
constructor () {
super({
name: `喵喵:${cfg.name || cfg.id}`,
@ -63,6 +69,8 @@ class App {
fnc: key
})
console.log('rule', rule)
if (app.check) {
check.push(app.check)
}
@ -86,6 +94,24 @@ class App {
e.original_msg = e.original_msg || e.msg
return await app.fn.call(this, e)
}
if (app.yzRule && app.yzCheck) {
let yzKey = `Yz${key}`
let yzRule = lodash.trim(app.yzRule.toString(), '/')
rules.push({
reg: yzRule,
fnc: yzKey
})
cls.prototype[yzKey] = async function () {
if (!app.yzCheck()) {
return false
}
let e = this.e
e.original_msg = e.original_msg || e.msg
return await app.fn.call(this, e)
}
}
}
return cls
}
@ -133,6 +159,14 @@ class App {
if (ret === true) {
return true
}
} else if (app.yzRule && app.yzCheck()) {
rule = new RegExp(app.yzRule)
if (rule.test(msg)) {
let ret = await app.fn(e, {})
if (ret === true) {
return true
}
}
}
} else if (event === 'poke' && msg === '#poke#') {
let ret = await app.fn(e, {})

View File

@ -1,6 +1,6 @@
import fs from 'fs'
import lodash from 'lodash'
import cfgData from './cfg-lib/cfg-data.js'
import cfgData from './cfg/CfgData.js'
const _path = process.cwd()
const _cfgPath = `${_path}/plugins/miao-plugin/components/`

View File

@ -1,5 +1,5 @@
import Cfg from './Cfg.js'
import render from './common-lib/render.js'
import render from './common/Render.js'
import { Version } from './index.js'
import lodash from 'lodash'

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

@ -1,5 +1,5 @@
import lodash from 'lodash'
import Elem from './common-lib/elem.js'
import Elem from './common/Elem.js'
import { Cfg } from '../components/index.js'
let Format = {

View File

@ -1,129 +0,0 @@
import fs from 'fs'
import lodash from 'lodash'
import { Character, ProfileReq, ProfileData } from '../models/index.js'
import Miao from './profile-data/miao.js'
import Enka from './profile-data/enka.js'
const _path = process.cwd()
const userPath = `${_path}/data/UserData/`
if (!fs.existsSync(userPath)) {
fs.mkdirSync(userPath)
}
ProfileReq.regServ({ Miao, Enka })
let Profile = {
async request (uid, e) {
if (uid.toString().length !== 9) {
return false
}
let req = new ProfileReq({ e, uid })
let data
try {
data = await req.request()
if (!data) {
return false
}
return Profile.save(uid, data)
} catch (err) {
console.log(err)
e.reply('请求失败')
return false
}
},
save (uid, data) {
let userData = {}
const userFile = `${userPath}/${uid}.json`
if (fs.existsSync(userFile)) {
try {
userData = JSON.parse(fs.readFileSync(userFile, 'utf8')) || {}
} catch (e) {
userData = {}
}
}
lodash.assignIn(userData, lodash.pick(data, 'uid,name,lv,avatar'.split(',')))
userData.chars = userData.chars || {}
lodash.forEach(data.chars, (char, charId) => {
userData.chars[charId] = char
})
fs.writeFileSync(userFile, JSON.stringify(userData), '', ' ')
return data
},
_get (uid, charId) {
const userFile = `${userPath}/${uid}.json`
let userData = {}
if (fs.existsSync(userFile)) {
try {
userData = JSON.parse(fs.readFileSync(userFile, 'utf8')) || {}
} catch (e) {
}
}
if (userData && userData.chars) {
let char = Character.get(charId)
if (char.isTraveler) {
let charData = userData.chars['10000005'] || userData.chars['10000007'] || false
if (charData) {
char.checkAvatars(charData, uid)
}
return charData
} else {
return userData.chars[charId]
}
}
return false
},
get (uid, charId, onlyHasData = false) {
let data = Profile._get(uid, charId)
if (data) {
let profile = new ProfileData(data, uid)
if (onlyHasData && !profile.hasData) {
return false
}
return profile
} else {
return false
}
},
getAll (uid) {
const userFile = `${userPath}/${uid}.json`
let userData = {}
if (fs.existsSync(userFile)) {
try {
userData = JSON.parse(fs.readFileSync(userFile, 'utf8')) || {}
} catch (e) {
userData = {}
}
}
if (userData && userData.chars) {
let ret = {}
lodash.forEach(userData.chars, (ds, id) => {
let profile = new ProfileData(ds, uid)
if (profile.hasData) {
ret[id] = profile
}
})
return ret
}
return false
},
async forEach (uid, fn) {
let profiles = Profile.getAll(uid)
for (let id in profiles) {
let ret = await fn(profiles[id], id)
if (ret === false) {
return false
}
}
},
getServName (uid) {
let Serv = ProfileReq.getServ(uid)
return Serv.name
}
}
export default Profile

View File

@ -3,7 +3,6 @@ import Format from './Format.js'
import Common from './Common.js'
import Cfg from './Cfg.js'
import Version from './Version.js'
import Profile from './Profile.js'
import App from './App.js'
export { Data, Cfg, Format, Common, Version, Profile, App }
export { Data, Cfg, Format, Common, Version, App }

View File

@ -1,227 +0,0 @@
import lodash from 'lodash'
import enkaMeta from './enka-meta.js'
import { Character, ArtifactSet, ProfileData } from '../../models/index.js'
const artiIdx = {
EQUIP_BRACER: 1,
EQUIP_NECKLACE: 2,
EQUIP_SHOES: 3,
EQUIP_RING: 4,
EQUIP_DRESS: 5
}
const attrMap = {
HP: '小生命',
HP_PERCENT: '大生命',
ATTACK: '小攻击',
ATTACK_PERCENT: '大攻击',
DEFENSE: '小防御',
DEFENSE_PERCENT: '大防御',
FIRE_ADD_HURT: '火元素伤害加成',
ICE_ADD_HURT: '冰元素伤害加成',
ROCK_ADD_HURT: '岩元素伤害加成',
ELEC_ADD_HURT: '雷元素伤害加成',
WIND_ADD_HURT: '风元素伤害加成',
WATER_ADD_HURT: '水元素伤害加成',
PHYSICAL_ADD_HURT: '物理伤害加成',
GRASS_ADD_HURT: '草元素伤害加成',
HEAL_ADD: '治疗加成',
ELEMENT_MASTERY: '元素精通',
CRITICAL: '暴击率',
CRITICAL_HURT: '暴击伤害',
CHARGE_EFFICIENCY: '充能效率'
}
let EnkaData = {
getProfile (data) {
let char = Character.get(data.avatarId)
let profile = new ProfileData({ id: char.id })
profile.setBasic({
level: data.propMap['4001'].val * 1,
promote: data.propMap['1002'].val * 1,
cons: data.talentIdList ? data.talentIdList.length : 0,
fetter: data.fetterInfo.expLevel,
costume: char.checkCostume(data.costumeId) ? data.costumeId : 0,
dataSource: 'enka'
})
profile.setAttr(EnkaData.getAttr(data.fightPropMap))
profile.setWeapon(EnkaData.getWeapon(data.equipList))
profile.setArtis(EnkaData.getArtifact(data.equipList))
let talentRet = EnkaData.getTalent(char.id, data.skillLevelMap)
profile.setTalent(talentRet.talent, 'original')
// 为旅行者增加elem
if (talentRet.elem) {
profile.elem = talentRet.elem
}
return EnkaData.dataFix(profile)
},
getAttr (data) {
let ret = {}
let attrKey = {
// atk: 2001,
atkBase: 4,
def: 2002,
defBase: 7,
hp: 2000,
hpBase: 1,
mastery: 28,
cpct: {
src: 20,
pct: true
},
cdmg: {
src: 22,
pct: true
},
heal: {
src: 26,
pct: true
},
recharge: {
src: 23,
pct: true
}
}
lodash.forEach(attrKey, (cfg, key) => {
if (!lodash.isObject(cfg)) {
cfg = { src: cfg }
}
let val = data[cfg.src] || 0
if (cfg.pct) {
val = val * 100
}
ret[key] = val
})
ret.atk = data['4'] * (1 + (data['6'] || 0)) + (data['5'] || 0)
let maxDmg = 0
// 火40 水42 风44 岩45 冰46 雷46
// 41 雷
lodash.forEach('40,41,42,43,44,45,45,46'.split(','), (key) => {
maxDmg = Math.max(data[key] * 1, maxDmg)
})
// phy 30
ret.dmg = maxDmg * 100
ret.phy = data['30'] * 100
return ret
},
getArtifact (data) {
let ret = {}
let get = function (d) {
if (!d) {
return []
}
let id = d.appendPropId || d.mainPropId || ''
id = id.replace('FIGHT_PROP_', '')
if (!attrMap[id]) {
return []
}
return [attrMap[id], d.statValue]
}
lodash.forEach(data, (ds) => {
let flat = ds.flat || {}
let sub = flat.reliquarySubstats || []
let idx = artiIdx[flat.equipType]
if (!idx) {
return
}
let setName = enkaMeta[flat.setNameTextMapHash] || ''
ret[idx] = {
name: ArtifactSet.getArtiNameBySet(setName, idx),
set: setName,
level: Math.min(20, ((ds.reliquary && ds.reliquary.level) || 1) - 1),
main: get(flat.reliquaryMainstat),
attrs: [
get(sub[0]),
get(sub[1]),
get(sub[2]),
get(sub[3])
]
}
})
return ret
},
getWeapon (data) {
let ds = {}
lodash.forEach(data, (temp) => {
if (temp.flat && temp.flat.itemType === 'ITEM_WEAPON') {
ds = temp
return false
}
})
let { weapon, flat } = ds
return {
name: enkaMeta[flat.nameTextMapHash],
star: flat.rankLevel,
level: weapon.level,
promote: weapon.promoteLevel,
affix: (lodash.values(weapon.affixMap)[0] || 0) + 1
}
},
getTalent (charid, ds = {}) {
let char = Character.get(charid)
let { talentId = {}, talentElem = {}, talentKey = {} } = char.meta
let elem = ''
let idx = 0
let ret = {}
lodash.forEach(ds, (lv, id) => {
let key
if (talentId[id]) {
let tid = talentId[id]
key = talentKey[tid]
elem = elem || talentElem[tid]
ret[key] = lv
} else {
key = ['a', 'e', 'q'][idx++]
ret[key] = ret[key] || lv
}
})
return {
elem: elem,
talent: ret
}
},
dataFix (ret) {
if (ret._fix) {
return ret
}
let { attr, id, weapon } = ret
let count = 0
id = id * 1
switch (id) {
case 10000052:
// 雷神被动加成fix
attr.dmg = Math.max(0, attr.dmg - (attr.recharge - 100) * 0.4)
break
case 10000041:
// 莫娜被动fix
attr.dmg = Math.max(0, attr.dmg - attr.recharge * 0.2)
break
case 10000070:
// 妮露满命效果fix
if (ret.cons === 6) {
count = Math.floor(attr.hp / 1000)
attr.cpct = Math.max(5, attr.cpct - Math.min(30, count * 0.6))
attr.cdmg = Math.max(50, attr.cdmg - Math.min(60, count * 1.2))
}
break
}
let wDmg = {
息灾: 12,
波乱月白经津: 12,
雾切之回光: 12,
猎人之径: 12
}
let { name, affix } = weapon
// 修正武器的加伤
if (wDmg[name]) {
attr.dmg = Math.max(0, attr.dmg - wDmg[name] - wDmg[name] * (affix - 1) / 4)
}
ret._fix = true
return ret
}
}
export default EnkaData

View File

@ -1,172 +0,0 @@
import { Character, ProfileData } from '../../models/index.js'
import lodash from 'lodash'
import { artiIdx, artiSetMap, attrMap } from './miao-meta.js'
let MiaoData = {
key: 'miao',
name: '喵喵Api',
getData (uid, data) {
let ret = {
uid,
chars: {}
}
if (data.cacheExpireAt) {
let exp = Math.max(0, Math.round(data.cacheExpireAt - (new Date() / 1000)))
ret.ttl = Math.max(60, exp)
}
return ret
},
getAvatar (ds) {
let char = Character.get(ds.id)
return {
id: ds.id,
name: char ? char.name : '',
dataSource: 'miao',
level: ds.level
}
},
getProfile (ds) {
let char = Character.get(ds.id)
let profile = new ProfileData({ id: char.id })
profile.setBasic({
level: ds.level,
cons: ds.constellationNum || 0,
fetter: ds.fetterLevel,
costume: char.checkCostume(ds.costumeID) ? ds.costumeID : 0,
dataSource: 'miao'
})
profile.setAttr(MiaoData.getAttr(ds.combatValue))
profile.setWeapon(MiaoData.getWeapon(ds.weapon))
profile.setArtis(MiaoData.getArtifact(ds.reliquary))
let talentRet = MiaoData.getTalent(char.id, ds.skill)
profile.setTalent(talentRet.talent, 'level')
if (talentRet.elem) {
profile.elem = talentRet.elem
}
return profile
},
getAttr (data) {
let ret = {}
lodash.forEach({
atk: 'attack',
atkBase: 'baseATK',
hp: 'health',
hpBase: 'baseHP',
def: 'defense',
defBase: 'baseDEF',
mastery: 'elementMastery',
cpct: {
src: 'critRate',
pct: true
},
cdmg: {
src: 'critDamage',
pct: true
},
heal: {
src: 'heal',
pct: true
},
recharge: {
src: 'recharge',
pct: true
}
}, (cfg, key) => {
if (!lodash.isObject(cfg)) {
cfg = { src: cfg }
}
let val = data[cfg.src] || 0
if (cfg.pct) {
val = val * 100
}
ret[key] = val
})
let maxDmg = 0
let hurt = data.addHurt || {}
lodash.forEach('fire,elec,water,grass,wind,rock,ice'.split(','), (key) => {
maxDmg = Math.max(hurt[key] * 100, maxDmg)
})
ret.dmg = maxDmg
ret.phy = hurt.physical * 100
return ret
},
getWeapon (weapon) {
return {
name: weapon.name,
star: weapon.rank,
level: weapon.level,
promote: weapon.promoteLevel,
affix: (weapon.affixLevel || 0) + 1
}
},
getArtifact (data) {
let ret = {}
let get = function (d) {
if (!d) {
return []
}
let name = d.name
name = name.replace('FIGHT_PROP_', '')
if (!attrMap[name]) {
return []
}
let value = d.value
if (value && value < 1) {
value = value * 100
}
return [attrMap[name], value]
}
lodash.forEach(data, (ds) => {
let sub = ds.appendAffix || []
let idx = artiIdx[ds.type]
if (!idx) {
return
}
ret[`arti${idx}`] = {
name: ds.name,
set: artiSetMap[ds.name] || '',
level: ds.level,
main: get(ds.mainAffix),
attrs: [
get(sub[0]),
get(sub[1]),
get(sub[2]),
get(sub[3])
]
}
})
return ret
},
getTalent (charid, data = {}) {
let char = Character.get(charid)
let { talentId = {}, talentElem = {}, talentKey = {} } = char.meta
let elem = ''
let idx = 0
let ret = {}
lodash.forEach(data, (ds) => {
let key
if (talentId[ds.id]) {
let tid = talentId[ds.id]
key = talentKey[tid]
elem = elem || talentElem[tid]
ret[key] = {
level: ds.level
}
} else {
key = ['a', 'e', 'q'][idx++]
ret[key] = ret[key] || {
level: ds.level
}
}
})
return {
talent: ret,
elem
}
}
}
export default MiaoData

View File

@ -1,48 +0,0 @@
import { Data } from '../index.js'
import lodash from 'lodash'
const _path = process.cwd()
export const artiIdx = {
生之花: 1,
死之羽: 2,
时之沙: 3,
空之杯: 4,
理之冠: 5
}
let relis = Data.readJSON('resources/meta/artifact/data.json')
let setMap = {}
lodash.forEach(relis, (ds) => {
if (ds.sets) {
lodash.forEach(ds.sets, (tmp) => {
if (tmp.name) {
setMap[tmp.name] = ds.name
}
})
}
})
export const artiSetMap = setMap
export const attrMap = {
HP: '小生命',
HP_PERCENT: '大生命',
ATTACK: '小攻击',
ATTACK_PERCENT: '大攻击',
DEFENSE: '小防御',
DEFENSE_PERCENT: '大防御',
FIRE_ADD_HURT: '火元素伤害加成',
ICE_ADD_HURT: '冰元素伤害加成',
ROCK_ADD_HURT: '岩元素伤害加成',
ELEC_ADD_HURT: '雷元素伤害加成',
WIND_ADD_HURT: '风元素伤害加成',
WATER_ADD_HURT: '水元素伤害加成',
PHYSICAL_ADD_HURT: '物理伤害加成',
GRASS_ADD_HURT: '草元素伤害加成',
HEAL_ADD: '治疗加成',
ELEMENT_MASTERY: '元素精通',
CRITICAL: '暴击率',
CRITICAL_HURT: '暴击伤害',
CHARGE_EFFICIENCY: '充能效率'
}

View File

@ -1,203 +0,0 @@
import lodash from 'lodash'
import Character from '../models/Character.js'
const artifactMap = {
生命值: {
title: '小生命'
},
生命值_百分比: {
title: '大生命',
pct: true
},
暴击率: {
title: '暴击率',
pct: true
},
暴击伤害: {
title: '暴击伤害',
pct: true
},
防御力: {
title: '小防御'
},
防御力_百分比: {
title: '大防御',
pct: true
},
攻击力: {
title: '小攻击'
},
攻击力_百分比: {
title: '大攻击',
pct: true
},
元素精通: {
title: '元素精通'
},
元素充能效率: {
title: '充能效率',
pct: true
},
治疗加成: {
title: '治疗加成',
pct: true
}
}
let posIdx = {
生之花: {
idx: 1
},
死之羽: {
idx: 2
},
时之沙: {
idx: 3
},
空之杯: {
idx: 4
},
理之冠: {
idx: 5
}
}
let Data = {
getData (uid, data) {
let ret = {
uid,
chars: {}
}
lodash.forEach({
name: '角色名称',
avatar: '头像ID',
level: '冒险等阶'
}, (title, key) => {
ret[key] = data[title] || ''
})
lodash.forEach(data.items, (ds) => {
let char = Data.getAvatar(ds)
ret.chars[char.id] = char
})
return ret
},
getAvatar (data) {
let char = Character.get(data['英雄Id'])
return {
id: data['英雄Id'],
name: char ? char.name : '',
level: data['等级'],
attr: Data.getAttr(data),
// weapon: Data.getWeapon(data),
artis: Data.getArtifact(data)
// cons: data["命之座数量"] * 1 || 0,
// talent: Data.getTalent(data)
}
},
getAttr (data) {
let ret = {}
let attrKey = {
atk: '攻击力_总',
atkBase: '属性攻击力',
def: '防御力_总',
defBase: '属性防御力',
hp: '生命值上限_总',
hpBase: '属性生命值上限',
mastery: '属性元素精通',
cRate: {
title: '属性暴击率',
pct: true
},
cDmg: {
title: '属性暴击伤害',
pct: true
},
hInc: {
title: '属性治疗加成',
pct: true
},
recharge: {
title: '属性元素充能效率',
pct: true
}
}
lodash.forEach(attrKey, (cfg, key) => {
if (typeof (cfg) === 'string') {
cfg = { title: cfg }
}
let val = data[cfg.title] || ''
if (cfg.pct) {
val = (val * 100).toFixed(2)
}
ret[key] = val
})
let maxDmg = 0
lodash.forEach('火水草雷风冰岩'.split(''), (key) => {
maxDmg = Math.max(data[`属性${key}元素伤害加成`] * 1, maxDmg)
})
ret.dmgBonus = (maxDmg * 100).toFixed(2)
ret.phyBonus = (data['属性物理伤害加成'] * 100).toFixed(2)
return ret
},
getWeapon (data) {
return {
name: data['武器名称'],
level: data['武器等级'],
refine: data['武器精炼']
}
},
getArtifact (data) {
let ret = {}
let get = function (idx, key) {
let v = data[`圣遗物${idx}${key}`]
let ret = /^([^\d]*)([\d\.\-]*)$/.exec(v)
if (ret && ret[1]) {
let title = ret[1]; let val = ret[2]
if (artifactMap[title]) {
if (artifactMap[title].pct) {
val = (val * 100).toFixed(2)
}
title = artifactMap[title].title
}
return [title, val]
}
return []
}
for (let idx = 1; idx <= 5; idx++) {
ret[`arti${idx}`] = {
name: data[`圣遗物${idx}名称`],
type: data[`圣遗物${idx}类型`],
main: get(idx, '主词条'),
attrs: [
get(idx, '副词条1'),
get(idx, '副词条2'),
get(idx, '副词条3'),
get(idx, '副词条4')
]
}
}
return ret
},
getTalent (data) {
let ret = {}
lodash.forEach({
a: 1,
e: 2,
q: 3
}, (idx, key) => {
let val = data[`天赋主动名称${idx}`]
let regRet = /等级(\d*)$/.exec(val)
if (regRet && regRet[1]) {
ret[key] = regRet[1] * 1 || 1
} else {
ret[key] = 1
}
})
return ret
}
}

View File

@ -57,6 +57,10 @@ export const helpList = [{
icon: 65,
title: '#圣遗物列表 #雷神圣遗物',
desc: '查看圣遗物列表 / 评分详情'
}, {
icon: 79,
title: '#面板帮助',
desc: '面板替换及其他帮助信息'
}, {
icon: 64,
title: '#深渊 #深渊12层',

View File

@ -1,4 +1,39 @@
export const cfgSchema = {
apps: {
title: 'Yunzai功能开启使用喵喵版功能',
cfg: {
avatarList: {
title: '#角色 #UID',
key: '角色列表',
def: false
},
avatarCard: {
title: '#刻晴 #老婆',
key: '角色卡片',
def: true
},
uploadAbyssData: {
title: '#深渊',
key: '深渊',
def: false
},
profileStat: {
title: '#练度统计',
key: '练度统计',
def: false
},
help: {
title: '#帮助 #菜单',
key: '帮助',
def: false
},
avatarPoke: {
title: '戳一戳展示角色卡片',
key: '戳一戳',
def: true
}
}
},
profile: {
title: '角色面板相关设置',
cfg: {
@ -12,12 +47,6 @@ export const cfgSchema = {
key: '面板替换',
def: true
},
profileStat: {
title: '面板练度统计',
key: '练度统计',
def: false,
desc: '使用【#面板练度统计】功能取代【#练度统计】功能,默认关闭'
},
groupRank: {
title: '群面板排名',
key: '排名',
@ -63,34 +92,6 @@ export const cfgSchema = {
}
}
},
char: {
title: '玩家&老婆卡片展示',
cfg: {
avatarCard: {
title: '角色查询',
key: '角色',
def: true,
desc: '使用喵喵版角色卡片作为默认角色卡片功能',
showDesc: false
},
uploadAbyssData: {
title: '上传深渊',
key: '深渊',
def: false,
desc: '使用【#上传深渊】功能取代【#深渊】功能,默认关闭'
},
avatarWife: {
title: '老婆查询',
key: '老婆',
def: true
},
avatarPoke: {
title: '戳一戳卡片',
key: '戳一戳',
def: true
}
}
},
wiki: {
title: '角色资料与信息查询',
cfg: {
@ -137,11 +138,13 @@ export const cfgSchema = {
input: (n) => Math.min(200, Math.max(50, (n * 1 || 100))),
desc: '可选值50~200建议100。设置高精度会提高图片的精细度但因图片较大可能会影响渲染与发送速度'
},
help: {
title: '喵喵作为默认帮助',
key: '帮助',
def: false,
desc: '开启后将使用喵喵版帮助作为Yunzai的默认帮助默认关闭'
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: '数字逗号分组',

View File

@ -41,6 +41,10 @@ export const helpList = [{
icon: 65,
title: '#圣遗物列表 #雷神圣遗物',
desc: '查看圣遗物列表 / 评分详情'
}, {
icon: 79,
title: '#面板帮助',
desc: '面板替换及其他帮助信息'
}, {
icon: 64,
title: '#深渊 #深渊12层',

View File

@ -2,10 +2,14 @@
* 圣遗物
* */
import Base from './Base.js'
import { Format } from '../components/index.js'
import { ArtifactSet } from './index.js'
import { artiMap, attrMap } from '../resources/meta/artifact/index.js'
import { artiMap, attrMap, mainIdMap, attrIdMap } from '../resources/meta/artifact/index.js'
import lodash from 'lodash'
class Artifact extends Base {
static getAttrs
constructor (name) {
super()
let cache = this._getCache(`arti:${name}`)
@ -29,6 +33,10 @@ class Artifact extends Base {
return this.set
}
get img () {
return `meta/artifact/imgs/${this.setName}/${this.idx}.webp`
}
static get (name) {
if (artiMap[name]) {
return new Artifact(name)
@ -36,10 +44,6 @@ class Artifact extends Base {
return false
}
get img () {
return `meta/artifact/imgs/${this.setName}/${this.idx}.webp`
}
static getSetNameByArti (name) {
let arti = Artifact.get(name)
if (arti) {
@ -53,6 +57,46 @@ class Artifact extends Base {
attrMap
}
}
static getMainById (id, level = 20, star = 5) {
let key = mainIdMap[id]
if (!key) {
return false
}
let attrCfg = attrMap[Format.isElem(key) ? 'dmg' : key]
let posEff = ['hpPlus', 'atkPlus', 'defPlus'].includes(key) ? 2 : 1
let starEff = { 1: 0.21, 2: 0.36, 3: 0.6, 4: 0.9, 5: 1 }
return {
key,
value: attrCfg.value * (1.2 + 0.34 * level) * posEff * (starEff[star || 5])
}
}
static getAttrsByIds (ids, star = 5) {
let ret = []
let tmp = {}
let starEff = { 1: 0.21, 2: 0.36, 3: 0.6, 4: 0.8, 5: 1 }
lodash.forEach(ids, (id) => {
let cfg = attrIdMap[id]
if (!cfg) {
return true
}
let { key, value } = cfg
if (!tmp[key]) {
tmp[key] = {
key,
eff: 0,
upNum: 0,
value: 0
}
ret.push(tmp[key])
}
tmp[key].eff += value / attrMap[key].value * starEff[star]
tmp[key].value += value * (attrMap[key].format === 'pct' ? 100 : 1) * starEff[star]
tmp[key].upNum++
})
return ret
}
}
export default Artifact

View File

@ -1,211 +0,0 @@
/*
* 用户角色封装
* 兼容处理面板 Profile Data Mys Avatar 数据
* */
import Base from './Base.js'
import lodash from 'lodash'
import { Profile } from '../components/index.js'
import { Artifact, Character, Weapon, ArtifactSet } from './index.js'
import moment from 'moment'
const charKey = 'name,abbr,sName,star,imgs,face,side,gacha,weaponTypeName,elem'.split(',')
export default class Avatar extends Base {
constructor (data = {}, pd = false, hasCk = true) {
super()
if (!data.name) {
return false
}
let char = Character.get(data.name)
if (!char || char.isCustom) {
return false
}
this.meta = data
this.char = char
let isProfile = data.isProfile
this.dataType = isProfile ? 'profile' : 'avatar'
this.hasCk = hasCk
let profile
let uid
if (isProfile) {
profile = data
} else if (pd) {
if (pd.isProfile) {
profile = pd
} else if (/\d{9}/.test(pd)) {
uid = pd
profile = Profile.get(pd, char.id, true)
}
}
if (profile && profile.isProfile && profile.hasData) {
this.profile = profile
}
this.elem = ((profile && profile.elem) || data.element || data.elem || char.elem || 'anemo').toLowerCase()
if (char.isTraveler) {
this.char = Character.get({ id: data.id || char.id, elem: this.elem })
uid && char.setTraveler(uid)
}
}
_get (key) {
if (charKey.includes(key)) {
return this.char[key]
}
return this.meta[key]
}
get dataSourceName () {
if (!this.hasCk && this.profile) {
return this.profile.dataSourceName
}
return this.meta.dataSourceName || '米游社'
}
get updateTime () {
if ((!this.hasCk || this.isProfile) && this.profile) {
return this.profile.updateTime
}
return moment(new Date()).format('MM-DD HH:mm')
}
get isProfile () {
return this.dataType === 'profile'
}
get isAvatar () {
return this.dataType === 'avatar'
}
get artis () {
let ret = {}
if (!this.isProfile) {
lodash.forEach(this.meta.reliquaries, (ds) => {
let arti = Artifact.get(ds.name)
ret[arti.idx] = {
name: arti.name,
set: arti.setName,
img: arti.img,
level: ds.level
}
})
return ret
}
if (this.profile && this.profile?.artis) {
return this.profile.artis.getArtisData()
}
return false
}
get cons () {
let data = this.meta
let profile = this.profile
return data?.cons || data?.actived_constellation_num || profile?.cons || 0
}
get weapon () {
let wd = this.meta?.weapon || this.profile?.weapon
if (!wd || !wd.name) {
return {}
}
let weapon = Weapon.get(wd.name)
return {
name: wd.name,
abbr: weapon.abbr,
star: weapon.star,
level: wd.level || 1,
affix: wd.affix || wd.affix_level || 0,
type: weapon.type,
img: weapon.img
}
}
async getTalent (mys) {
if (!this.isProfile && mys && mys.isSelfCookie) {
let char = this.char
let id = char.id
let talent = {}
let talentRes = await mys.getDetail(id)
// { data: null, message: '请先登录', retcode: -100, api: 'detail' }
let avatar = this.meta
if (!char || !avatar) {
return false
}
if (talentRes && talentRes.skill_list) {
let talentList = lodash.orderBy(talentRes.skill_list, ['id'], ['asc'])
for (let val of talentList) {
let { max_level: maxLv, level_current: lv } = val
if (val.name.includes('普通攻击')) {
talent.a = lv
continue
}
if (maxLv >= 10 && !talent.e) {
talent.e = lv
continue
}
if (maxLv >= 10 && !talent.q) {
talent.q = lv
continue
}
}
}
let ret = char.getAvatarTalent(talent, this.cons, 'original')
if (ret) {
ret.id = id
}
return ret
}
if (this.profile) {
let profile = this.profile
let talent = profile.talent
talent.id = profile.id
return talent
}
return false
}
get artisSet () {
if (this._artisSet) {
return this._artisSet
}
this._artisSet = false
if (!this.isProfile) {
let artis = this.artis
let setCount = {}
lodash.forEach(artis, (arti, idx) => {
let set = arti?.set
if (set) {
setCount[set] = (setCount[set] || 0) + 1
}
})
let sets = {}
let names = []
let abbrs = []
let abbrs2 = []
let imgs = []
for (let set in setCount) {
if (setCount[set] >= 2) {
let value = setCount[set] >= 4 ? 4 : 2
sets[set] = value
let artiSet = ArtifactSet.get(set)
names.push(artiSet.name)
abbrs.push(artiSet.abbr + value)
abbrs2.push(artiSet.name + value)
imgs.push(artiSet.img)
}
}
this._artisSet = {
sets,
names,
abbrs: [...abbrs, ...abbrs2],
imgs,
name: (abbrs.length > 1 || abbrs2[0]?.length > 7) ? abbrs.join('+') : abbrs2[0],
sName: abbrs.join('+')
}
}
if (this.profile) {
let profile = this.profile
this._artisSet = profile.artis ? profile.artis.getSetData() : false
}
return this._artisSet || {}
}
}

268
models/AvatarArtis.js Normal file
View File

@ -0,0 +1,268 @@
/**
* 面板圣遗物
*/
import lodash from 'lodash'
import Base from './Base.js'
import { Artifact, ArtifactSet } from './index.js'
import { Format, Data } from '../components/index.js'
import ArtisMark from './profile/ArtisMark.js'
export default class AvatarArtis extends Base {
constructor (charid = 0) {
super()
this.charid = charid
this.artis = {}
}
get sets () {
return this.getSetData().sets || {}
}
get names () {
return this.getSetData().names || []
}
get hasArtis () {
return !lodash.isEmpty(this.artis)
}
get hasAttr () {
return ArtisMark.hasAttr(this.artis)
}
static _eachArtisSet (sets, fn) {
lodash.forEach(sets || [], (v, k) => {
let artisSet = ArtifactSet.get(k)
if (artisSet) {
if (v >= 4) {
fn(artisSet, 2)
}
fn(artisSet, v)
}
})
}
static getArtisKeyTitle () {
return ArtisMark.getKeyTitleMap()
}
setArtisData (ds = {}, profile = false) {
// let force = !this.hasArtis || ArtisMark.hasAttr(ds) || !ArtisMark.hasAttr(this.artis)
if (!profile || (profile && ArtisMark.hasAttr(ds))) {
for (let idx = 1; idx <= 5; idx++) {
if (ds[idx] || ds[`arti${idx}`]) {
this.setArtis(idx, ds[idx] || ds[`arti${idx}`], profile)
}
}
}
}
setArtis (idx = 1, ds = {}, profile = false) {
idx = idx.toString().replace('arti', '')
this.artis[idx] = this.artis[idx] || {}
let arti = this.artis[idx]
if (profile) {
arti.name = ds._name || ds.name || arti.name || ''
arti.set = ds._set || Artifact.getSetNameByArti(arti._name) || ds.set || ''
arti.level = ds._level || ds.level || 1
arti.star = ds._star || ds.star || 5
arti.main = ds.main
arti.attrs = ds.attrs
return true
}
arti.name = ds.name || arti.name || ''
arti.set = ds.set || Artifact.getSetNameByArti(arti.name) || ''
arti.level = ds.level || 1
arti.star = ds.star || 5
if (ds.mainId || ds.main) {
arti._name = ds.name || arti.name
arti._set = ds.set || Artifact.getSetNameByArti(arti.name) || arti.set || ''
arti._level = ds.level || arti.level
arti._star = ds.star || arti.star || 5
}
// 存在面板数据,更新面板数据
if (ds.mainId && ds.attrIds) {
arti.mainId = ds.mainId
arti.attrIds = ds.attrIds
arti.main = Artifact.getMainById(ds.mainId, arti._level, arti._star)
arti.attrs = Artifact.getAttrsByIds(ds.attrIds, arti._star)
} else if (ds.main && ds.attrs) {
arti.main = ArtisMark.formatAttr(ds.main || {})
arti.attrs = []
for (let attrIdx in ds.attrs || []) {
if (ds.attrs[attrIdx]) {
arti.attrs.push(ArtisMark.formatAttr(ds.attrs[attrIdx]))
}
}
}
}
forEach (fn) {
lodash.forEach(this.artis, (ds, idx) => {
if (ds.name) {
fn(ds, idx)
}
})
}
_get (key) {
let artis = this.artis
switch (key) {
case 'length':
return lodash.keys(artis).length
}
if (artis[key]) {
return artis[key]
}
}
toJSON () {
let ret = {}
for (let idx = 1; idx <= 5; idx++) {
let ds = this.artis[idx]
if (ds) {
let tmp = {
name: ds.name || '',
level: ds.level || 1,
star: ds.star || 5
}
if ((ds.mainId && ds.attrIds) || (ds.main && ds.attrs)) {
if ((ds._name && ds._name !== ds.name) || (ds._level && ds._level !== ds.level) || (ds._star && ds._star !== ds.star)) {
tmp._name = ds._name || null
tmp._level = ds._level || null
tmp._star = ds._star || null
}
}
if (ds.mainId && ds.attrIds) {
tmp.mainId = ds.mainId || null
tmp.attrIds = ds.attrIds
} else if (ds.main && ds.attrs) {
tmp.main = ds.main || null
tmp.attrs = []
for (let attrIdx in ds.attrs || []) {
if (ds.attrs[attrIdx]) {
tmp.attrs.push(ArtisMark.formatAttr(ds.attrs[attrIdx]))
}
}
}
ret[idx] = tmp
}
}
return ret
}
getDetail (profile = false) {
let ret = {}
for (let idx = 1; idx <= 5; idx++) {
let ds = this.artis[idx]
if (ds) {
let artis = Artifact.get(profile ? ds._name : ds.name)
let tmp = {
...artis?.getData('img,name,set'),
level: (profile ? ds._level : ds.level) || 1
}
if (ds.main && ds.attrs) {
tmp.main = ds.main || null
tmp.attrs = []
for (let attrIdx in ds.attrs || []) {
if (ds.attrs[attrIdx]) {
tmp.attrs.push(ArtisMark.formatAttr(ds.attrs[attrIdx]))
}
}
}
ret[idx] = tmp
}
}
return ret
}
mainAttr (idx = '') {
if (!idx) {
let ret = {}
for (let i = 1; i <= 5; i++) {
ret[i] = this.mainAttr(i)
}
return ret
}
let main = this.artis[idx]?.main
if (!main) {
return ''
}
return main.key || ''
}
is (check, pos = '') {
if (pos) {
return this.isAttr(check, pos)
}
let sets = this.getSetData()?.abbrs || []
let ret = false
Data.eachStr(check, (s) => {
if (sets.includes(s)) {
ret = true
return false
}
})
return ret
}
isAttr (attr, pos = '3,4,5') {
let mainAttr = this.mainAttr()
let check = true
Data.eachStr(pos.toString(), (p) => {
let attrs = attr.split(',')
if (!attrs.includes(mainAttr[p]) && (p === '4' && !attrs.includes('dmg') && Format.isElem(mainAttr[p]))) {
check = false
return false
}
})
return check
}
/**
* 获取圣遗物套装数据
* @returns {*|{imgs: *[], names: *[], sets: {}, abbrs: *[], sName: string, name: (string|*)}}
* sets: 套装名:2/4
* names: [套装名]
* imgs: [img]
* abbrs[别名]
* name: '组合名字' 若为4件套会使用套装完整名
* sName: '简写名字'若为4件套也会使用简写
*/
getSetData (profile = false) {
let setCount = {}
this.forEach((arti, idx) => {
setCount[profile ? arti._set : arti.set] = (setCount[profile ? arti._set : arti.set] || 0) + 1
})
let sets = {}
let names = []
let imgs = []
let abbrs = []
let abbrs2 = []
for (let set in setCount) {
if (setCount[set] >= 2) {
let count = setCount[set] >= 4 ? 4 : 2
sets[set] = count
let artiSet = ArtifactSet.get(set)
names.push(artiSet.name)
imgs.push(artiSet.img)
abbrs.push(artiSet.abbr + count)
abbrs2.push(artiSet.name + count)
}
}
return {
sets,
names,
imgs,
abbrs: [...abbrs, ...abbrs2],
name: (abbrs.length > 1 || abbrs2[0]?.length > 7) ? abbrs.join('+') : abbrs2[0],
sName: abbrs.join('+')
}
}
eachArtisSet (fn) {
AvatarArtis._eachArtisSet(this.sets, fn)
}
}

205
models/AvatarData.js Normal file
View File

@ -0,0 +1,205 @@
import lodash from 'lodash'
import Base from './Base.js'
import moment from 'moment'
import { Character, AvatarArtis, ProfileData, Weapon } from './index.js'
import { Data } from '../components/index.js'
import AttrCalc from './profile/AttrCalc.js'
import Profile from './player/Profile.js'
const charKey = 'name,abbr,sName,star,imgs,face,side,gacha,weaponTypeName'.split(',')
export default class AvatarData extends Base {
constructor (ds = {}, source) {
super()
let char = Character.get({ id: ds.id, elem: ds.elem })
if (!char) {
return
}
this.id = char.id
this.char = char
this.initArtis()
this.setAvatar(ds, source)
}
get hasTalent () {
return this.talent && !lodash.isEmpty(this.talent) && !!this._talent
}
get name () {
return this.char?.name || ''
}
get hasData () {
return !!(this.level > 1 || this?.weapon?.name || this?.talent?.a)
}
// 是否是合法面板数据
get isProfile () {
return Profile.isProfile(this)
}
get costume () {
let costume = this._costume
if (lodash.isArray(costume)) {
costume = costume[0]
}
return costume
}
get originalTalent () {
return lodash.mapValues(this.talent, (ds) => ds.original)
}
/**
* 获取圣遗物套装属性
* @returns {boolean|*|{imgs: *[], names: *[], sets: {}, abbrs: *[], sName: string, name: (string|*)}|{}}
*/
get artisSet () {
return this.artis.getSetData()
}
get dataSource () {
return {
enka: 'Enka.Network',
miao: '喵喵Api',
mys: '米游社'
}[this._source] || this._source
}
get updateTime () {
let time = this._time
if (!time) {
return ''
}
if (lodash.isString(time)) {
return moment(time).format('MM-DD HH:mm')
}
if (lodash.isNumber(time)) {
return moment(new Date(time)).format('MM-DD HH:mm')
}
return ''
}
static create (ds, source) {
let avatar = new AvatarData(ds)
if (!avatar) {
return false
}
return avatar
}
initArtis () {
this.artis = new AvatarArtis(this.id)
}
_get (key) {
if (charKey.includes(key)) {
return this.char[key]
}
}
setAvatar (ds, source = '') {
this._now = new Date() * 1
this.setBasic(ds, source)
ds.weapon && this.setWeapon(ds.weapon)
ds.talent && this.setTalent(ds.talent, 'original', source)
ds.artis && this.setArtis(ds)
delete this._now
}
/**
* 设置角色基础数据
* @param ds
* @param source
*/
setBasic (ds = {}, source = '') {
const now = this._now || (new Date()) * 1
this.level = ds.lv || ds.level || this.level || 1
this.cons = ds.cons || this.cons || 0
this.fetter = ds.fetter || this.fetter || 0
this._costume = ds.costume || this._costume || 0
this.elem = ds.elem || this.elem || this.char.elem || ''
this.promote = lodash.isUndefined(ds.promote) ? (this.promote || AttrCalc.calcPromote(this.level)) : (ds.promote || 0)
this._source = ds._source || ds.dataSource || this._source || ''
this._time = ds._time || this._time || now
this._update = ds._update || this._update || ds._time || now
this._talent = ds._talent || this._talent || ds._time || now
// 存在数据源时更新时间
if (source) {
this._update = now
if (source !== 'mys') {
this._source = source
this._time = now
} else {
this._source = this._source || source
this._time = this._source !== 'mys' ? (this._time || now) : now
}
}
}
setWeapon (ds = {}) {
let w = Weapon.get(ds.name)
if (!w) {
return false
}
this.weapon = {
name: ds.name,
level: ds.level || ds.lv || 1,
promote: lodash.isUndefined(ds.promote) ? AttrCalc.calcPromote(ds.level || ds.lv || 1) : (ds.promote || 0),
affix: ds.affix,
...w.getData('star,abbr,type,img')
}
if (this.weapon.level < 20) {
this.weapon.promote = 0
}
}
setTalent (ds = false, mode = 'original', updateTime = '') {
const now = this._now || (new Date()) * 1
if (ds) {
let ret = this.char.getAvatarTalent(ds, this.cons, mode)
if (ret) {
this.talent = ret || this.talent
// 设置天赋更新时间
this._talent = ds._talent || this._talent || ds._time || now
}
}
if (updateTime) {
this._talent = now
}
}
setArtis (ds, source) {
this.artis.setArtisData(ds.artis, source)
}
getProfile () {
if (!this.isProfile) {
return false
}
return ProfileData.create(this)
}
// 判断当前profileData是否具备有效圣遗物信息
hasArtis () {
return this.isProfile && this.artis.length > 0
}
// toJSON 供保存使用
toJSON () {
return {
...this.getData('name,id,elem,level,promote,fetter,costume,cons,talent:originalTalent'),
weapon: Data.getData(this.weapon, 'name,level,promote,affix'),
...this.getData('artis,_source,_time,_update,_talent')
}
}
getDetail (keys = '') {
return this.getData(keys || 'id,name,level,star,cons,fetter,elem,face,side,gacha,abbr,weapon,talent,artisSet') || {}
}
getArtisDetail () {
return this.artis.getDetail()
}
}

View File

@ -1,134 +0,0 @@
/*
* 用户角色列表
*
* 批量管理及天赋等数据获取
* 使用 Avatar Model实现兼容处理面板数据及Mys角色数据
* */
import Base from './Base.js'
import lodash from 'lodash'
import { Data, Common, Profile } from '../components/index.js'
import { Avatar, MysApi } from './index.js'
export default class AvatarList extends Base {
constructor (uid, datas = [], withProfile = false) {
super()
if (!uid) {
return false
}
this.uid = uid
let avatars = {}
let profiles = {}
if (withProfile) {
profiles = Profile.getAll(uid)
}
lodash.forEach(datas, (ds) => {
let avatar = new Avatar(ds, profiles[ds.id] || false)
if (avatar) {
avatars[avatar.id] = avatar
}
})
// 使用面板数据补全
lodash.forEach(profiles, (profile) => {
if (!avatars[profile.id]) {
let avatar = new Avatar(profile)
if (avatar) {
avatars[avatar.id] = avatar
}
}
})
this.avatars = avatars
}
getData (ids, keys = '') {
let rets = {}
keys = keys || 'id,name,level,star,cons,fetter,elem,face,side,gacha,abbr,weapon,artisSet'
let avatars = this.avatars
lodash.forEach(ids, (id) => {
rets[id] = avatars[id].getData(keys) || {}
})
return rets
}
getAvatar (id) {
return this.avatars[id]
}
getProfile (id) {
return this.avatars[id]?.profile
}
getIds () {
let rets = []
lodash.forEach(this.avatars, (ds) => {
rets.push(ds.id)
})
return rets
}
async getTalentData (ids = '', mys = false, keys = '') {
if (!ids) {
ids = this.getIds()
}
mys = mys || this._mys
let avatarTalent = await Data.getCacheJSON(`miao:avatar-talent:${this.uid}`)
let needReq = {}
lodash.forEach(ids, (id) => {
if (!avatarTalent[id] || !avatarTalent[id]?.a) {
needReq[id] = true
}
})
let avatars = this.avatars
let needReqIds = lodash.keys(needReq)
if (needReqIds.length > 0) {
if (needReqIds.length > 8) {
this.e && this.e.reply('正在获取角色信息,请稍候...')
}
let num = 10
let ms = 100
let skillRet = []
let avatarArr = lodash.chunk(needReqIds, num)
for (let val of avatarArr) {
for (let id of val) {
let avatar = avatars[id]
skillRet.push(await avatar.getTalent(mys))
}
skillRet = await Promise.all(skillRet)
skillRet = skillRet.filter(item => item.id)
await Common.sleep(ms)
}
lodash.forEach(skillRet, (talent) => {
avatarTalent[talent.id] = talent
})
await Data.setCacheJSON(`miao:avatar-talent:${this.uid}`, avatarTalent, 3600 * 2)
}
let ret = this.getData(ids, keys)
lodash.forEach(ret, (avatar, id) => {
avatar.talent = avatarTalent[id] || {}
})
return ret
}
get isSelfCookie () {
return !!this._mys?.isSelfCookie
}
}
AvatarList.hasTalentCache = async function (uid) {
return !!await redis.get(`miao:avatar-talent:${uid}`)
}
AvatarList.getAll = async function (e, mys = false) {
if (!mys) {
mys = await MysApi.init(e)
}
if (!mys || !mys.uid) return false
let uid = mys.uid
let data = await mys.getCharacter()
if (!data) {
return false
}
let ret = new AvatarList(uid, data.avatars, true)
ret.e = e
ret._mys = mys
return ret
}

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

@ -7,19 +7,22 @@
import lodash from 'lodash'
import Base from './Base.js'
import { Data, Format } from '../components/index.js'
import CharImg from './character-lib/CharImg.js'
import CharTalent from './character-lib/CharTalent.js'
import CharId from './character-lib/CharId.js'
import CharMeta from './character-lib/CharMeta.js'
import CharCfg from './character-lib/CharCfg.js'
import CharImg from './character/CharImg.js'
import CharTalent from './character/CharTalent.js'
import CharId from './character/CharId.js'
import CharMeta from './character/CharMeta.js'
import CharCfg from './character/CharCfg.js'
let { abbrMap, wifeMap, idSort, idMap } = CharId
let { wifeMap, idSort, idMap } = CharId
let getMeta = function (name) {
return Data.readJSON(`resources/meta/character/${name}/data.json`)
}
class Character extends Base {
// 默认获取的数据
_dataKey = 'id,name,abbr,title,star,elem,allegiance,weapon,birthday,astro,cncv,jpcv,ver,desc,talentCons'
constructor ({ id, name = '', elem = '' }) {
super()
// 检查缓存
@ -40,9 +43,6 @@ class Character extends Base {
return this._cache()
}
// 默认获取的数据
_dataKey = 'id,name,abbr,title,star,elem,allegiance,weapon,birthday,astro,cncv,jpcv,ver,desc,talentCons'
// 是否为官方角色
get isOfficial () {
return /[12]0\d{6}/.test(this._id)
@ -140,17 +140,6 @@ class Character extends Base {
return this.meta?.talentCons || {}
}
// 获取attr列表
getAttrList () {
let { meta } = this
return CharMeta.getAttrList(meta.baseAttr, meta.growAttr, this.elemName)
}
// 获取素材
getMaterials (type = 'all') {
return CharMeta.getMaterials(this, type)
}
// 获取生日
get birthday () {
let birth = this.birth
@ -161,130 +150,6 @@ class Character extends Base {
return `${birth[0]}${birth[1]}`
}
// 获取角色character-img图片
getCardImg (se = false, def = true) {
if (this.name === '旅行者') {
return CharImg.getCardImg(['空', '荧'], se, def)
}
return CharImg.getCardImg(this.name, se, def)
}
getAvatarTalent (talent = {}, cons = 0, mode = 'original') {
return CharTalent.getAvatarTalent(this.id, talent, cons, mode, this.talentCons)
}
// 检查老婆类型
checkWifeType (type) {
return !!wifeMap[type][this.id]
}
// 检查时装
checkCostume (id) {
let costume = this.meta?.costume || []
return costume.includes(id * 1)
}
// 判断是否为某种元素角色
isElem (elem = '') {
elem = elem.toLowerCase()
return this.elem === elem || this.elemName === elem
}
// 获取角色插画
getImgs (costume = '') {
if (!lodash.isArray(costume)) {
costume = [costume, false]
}
let costumeCfg = [this.checkCostume(costume[0]) ? '2' : '', costume[1] || 'normal']
let cacheId = `costume${costume[0]}`
if (!this._imgs) {
this._imgs = {}
}
if (!this._imgs[cacheId]) {
this._imgs[cacheId] = CharImg.getImgs(this.name, costumeCfg, this.isTraveler ? this.elem : '', this.source === 'amber' ? 'png' : 'webp')
}
let ret = this._imgs[cacheId]
let nPath = `meta/character/${this.name}`
if (costumeCfg[1] === 'super') {
ret.splash0 = CharImg.getRandomImg(
[`profile/super-character/${this.name}`, `profile/normal-character/${this.name}`],
[`${nPath}/imgs/splash0.webp`, `${nPath}/imgs/splash${costumeCfg[0]}.webp`, `/${nPath}/imgs/splash.webp`]
)
} else {
ret.splash0 = CharImg.getRandomImg(
[`profile/normal-character/${this.name}`],
[`${nPath}/imgs/splash${costumeCfg[0]}.webp`, `/${nPath}/imgs/splash.webp`]
)
}
return ret
}
// 获取详情数据
getDetail (elem = '') {
if (this._detail) {
return this._detail
}
if (this.isCustom) {
return {}
}
const path = 'resources/meta/character'
try {
if (this.isTraveler) {
this._detail = Data.readJSON(`${path}/旅行者/${this.elem}/detail.json`)
} else {
this._detail = Data.readJSON(`${path}/${this.name}/detail.json`)
}
} catch (e) {
console.log(e)
}
return this._detail
}
// 设置旅行者数据
// TODO迁移至Avatar
setTraveler (uid = '') {
if (this.isTraveler && uid && uid.toString().length === 9) {
Data.setCacheJSON(`miao:uid-traveler:${uid}`, {
id: CharId.getTravelerId(this.id),
elem: this.elem
}, 3600 * 24 * 120)
}
}
// 获取旅行者数据
async getTraveler (uid) {
if (this.isTraveler) {
let tData = await Data.getCacheJSON(`miao:uid-traveler:${uid}`)
return Character.get({
id: CharId.getTravelerId(tData.id || this.id),
elem: tData.elem || (this.elem !== 'multi' ? this.elem : 'anemo')
})
}
return this
}
async checkAvatars (avatars, uid = '') {
if (!this.isTraveler) {
return this
}
if (lodash.isObject(avatars) && avatars.id) {
avatars = [avatars]
}
for (let avatar of avatars) {
if (CharId.isTraveler(avatar.id)) {
let char = Character.get({
id: avatar.id,
elem: (avatar.elem || avatar.element || 'anemo').toLowerCase()
})
char.setTraveler(uid)
return char
}
}
return await this.getTraveler(uid)
}
// 基于角色名获取Character
static get (val) {
let id = CharId.getId(val, Character.gsCfg)
@ -307,30 +172,93 @@ class Character extends Base {
})
}
// 基于角色名获取Character
// 当获取角色为旅行者时会考虑当前uid的账号情况返回对应旅行者
static async getAvatar (name, uid) {
let char = Character.get(name)
return await char.getTraveler(uid)
}
// 获取别名数据
// TODO待废弃
static getAbbr () {
return abbrMap
}
// 检查wife类型
// TODO待废弃
static checkWifeType (charid, type) {
return !!wifeMap[type][charid]
}
// 获取排序ID
static sortIds (arr) {
return arr.sort((a, b) => (idSort[a] || 300) - (idSort[b] || 300))
}
// 获取attr列表
getAttrList () {
let { meta } = this
return CharMeta.getAttrList(meta.baseAttr, meta.growAttr, this.elemName)
}
// 获取素材
getMaterials (type = 'all') {
return CharMeta.getMaterials(this, type)
}
// 获取角色character-img图片
getCardImg (se = false, def = true) {
if (this.name === '旅行者') {
return CharImg.getCardImg(['空', '荧'], se, def)
}
return CharImg.getCardImg(this.name, se, def)
}
// 设置旅行者数据
getAvatarTalent (talent = {}, cons = 0, mode = 'original') {
return CharTalent.getAvatarTalent(this.id, talent, cons, mode, this.talentCons)
}
// 检查老婆类型
checkWifeType (type) {
return !!wifeMap[type][this.id]
}
// 检查时装
checkCostume (id) {
let costume = this.meta?.costume || []
return costume.includes(id * 1)
}
// 判断是否为某种元素角色
isElem (elem = '') {
elem = elem.toLowerCase()
return this.elem === elem || this.elemName === elem
}
// 获取角色插画
getImgs (costume = '') {
if (lodash.isArray(costume)) {
costume = costume[0]
}
let costumeIdx = this.checkCostume(costume) ? '2' : ''
let cacheId = `costume${costumeIdx}`
if (!this._imgs) {
this._imgs = {}
}
if (!this._imgs[cacheId]) {
this._imgs[cacheId] = CharImg.getImgs(this.name, costumeIdx, this.isTraveler ? this.elem : '', this.source === 'amber' ? 'png' : 'webp')
}
return this._imgs[cacheId]
}
// 基于角色名获取Character
// 获取详情数据
getDetail (elem = '') {
if (this._detail) {
return this._detail
}
if (this.isCustom) {
return {}
}
const path = 'resources/meta/character'
try {
if (this.isTraveler) {
this._detail = Data.readJSON(`${path}/旅行者/${this.elem}/detail.json`)
} else {
this._detail = Data.readJSON(`${path}/${this.name}/detail.json`)
}
} catch (e) {
console.log(e)
}
return this._detail
}
// 获取伤害计算配置
getCalcRule () {
if (!this._calcRule && this._calcRule !== false) {

View File

@ -4,7 +4,7 @@
import lodash from 'lodash'
import Base from './Base.js'
import { Data } from '../components/index.js'
import MaterialMeta from './material-lib/MaterialMeta.js'
import MaterialMeta from './material/MaterialMeta.js'
let data = Data.readJSON('resources/meta/material/data.json')
let abbr = await Data.importDefault('resources/meta/material/abbr.js')

View File

@ -12,6 +12,26 @@ export default class MysApi {
e.isSelfCookie = this.isSelfCookie
}
get isSelfCookie () {
return this.uid * 1 === this.ckUid * 1 || this?.mysInfo?.isSelf
}
get ckUid () {
return this.ckInfo.uid
}
get ck () {
return this.ckInfo.ck
}
get selfUser () {
return new User({ id: this.e.user_id, uid: this.uid })
}
get targetUser () {
return new User({ id: this.e.user_id, uid: this.uid })
}
static async init (e, auth = 'all') {
if (!e.runtime) {
Version.runtime()
@ -50,31 +70,12 @@ export default class MysApi {
if (uid) {
return new User({ id: e.user_id, uid })
} else {
e.reply('请先#绑定uid')
e.reply('请先发送【#绑定+你的UID】来绑定查询目标')
e._replyNeedUid = true
return false
}
}
get isSelfCookie () {
return this.uid * 1 === this.ckUid * 1 || this?.mysInfo?.isSelf
}
get ckUid () {
return this.ckInfo.uid
}
get ck () {
return this.ckInfo.ck
}
get selfUser () {
return new User({ id: this.e.user_id, uid: this.uid })
}
get targetUser () {
return new User({ id: this.e.user_id, uid: this.uid })
}
async getMysApi (e, targetType = 'all', option = {}) {
if (this.mys) {
return this.mys

307
models/Player.js Normal file
View File

@ -0,0 +1,307 @@
/**
* 用户数据文件
* 数据存储在/data/userData/${uid}.json
* 兼容处理面板户数及Mys数据
*
*/
import lodash from 'lodash'
import Base from './Base.js'
import { Data } from '../components/index.js'
import { AvatarData, ProfileRank, Character } from './index.js'
import MysAvatar from './player/MysAvatar.js'
import Profile from './player/Profile.js'
Data.createDir('/data/userData', 'root')
export default class Player extends Base {
constructor (uid) {
super()
uid = uid?._mys?.uid || uid?.uid || uid
if (!uid) {
return false
}
let cacheObj = this._getCache(`player:${uid}`)
if (cacheObj) {
return cacheObj
}
this.uid = uid
this.reload()
return this._cache()
}
get hasProfile () {
let ret = false
lodash.forEach(this._avatars, (avatar) => {
if (avatar.isProfile) {
ret = true
return false
}
})
return ret
}
static create (e) {
if (e?._mys?.uid || e.uid) {
// 传入为e
let player = new Player(e?._mys?.uid || e.uid)
player.e = e
return player
} else {
return new Player(e)
}
}
// 获取面板更新服务名
static getProfileServName (uid) {
let Serv = Profile.getServ(uid)
return Serv.name
}
static delByUid (uid) {
let player = Player.create(uid)
if (player) {
player.del()
}
}
/**
* 重新加载json文件
*/
reload () {
let data
data = Data.readJSON(`/data/UserData/${this.uid}.json`, 'root')
this.setBasicData(data)
if (data.chars) {
this.setAvatars(data.chars)
// 暂时保留旧数据,防止异常情况
this._chars = data.chars
}
this.setAvatars(data.avatars || [])
}
/**
* 保存json文件
*/
save () {
let ret = Data.getData(this, 'uid,name,level,word,face,card,sign,info,_info,_mys,_profile')
ret.avatars = {}
this.forEachAvatar((avatar) => {
ret.avatars[avatar.id] = avatar.toJSON()
})
// 暂时保留旧数据,防止异常情况
if (this._chars) {
ret.chars = this._chars
}
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
*/
setBasicData (ds) {
this.name = ds.name || this.name || ''
this.level = ds.level || this.level || ''
this.word = ds.word || this.word || ''
this.face = ds.face || this.face || ''
this.card = ds.card || this.card || ''
this.sign = ds.sign || this.sign || ''
this.info = ds.info || this.info || false
this._avatars = this._avatars || {}
this._profile = ds._profile || this._profile
this._mys = ds._mys || this._mys
this._info = ds._info || this._info
}
// 设置角色列表
setAvatars (ds) {
lodash.forEach(ds, (avatar) => {
this.setAvatar(avatar)
})
}
// 设置角色数据
setAvatar (ds, source = '') {
let avatar = this.getAvatar(ds.id, true)
avatar.setAvatar(ds, source)
}
// 获取Avatar角色
getAvatar (id, create = false) {
let char = Character.get(id)
let avatars = this._avatars
// 兼容处理旅行者的情况
if (char.isTraveler && !create) {
id = avatars['10000005'] ? 10000005 : 10000007
}
if (!avatars[id] && create) {
avatars[id] = AvatarData.create({ id })
}
return avatars[id] || false
}
// 异步循环角色
async forEachAvatarAsync (fn) {
for (let id in this._avatars) {
let ret = await fn(this._avatars[id], id)
if (ret === false) {
return false
}
}
}
// 循环Avatar
forEachAvatar (fn) {
for (let id in this._avatars) {
let avatar = this._avatars[id]
if (avatar && avatar.hasData) {
let ret = fn(this._avatars[id])
if (ret === false) {
return false
}
}
}
}
// 获取所有Avatar数据
getAvatarData (ids = '') {
let ret = {}
if (!ids) {
this.forEachAvatar((avatar) => {
ret[avatar.id] = avatar.getDetail()
})
} else {
lodash.forEach(ids, (id) => {
let avatar = this.getAvatar(id)
if (avatar) {
ret[id] = avatar.getDetail()
}
})
}
return ret
}
// 获取指定角色的面板数据
getProfile (id) {
let avatar = this.getAvatar(id)
return avatar ? avatar.getProfile() : false
}
// 获取所有面板数据
getProfiles () {
let ret = {}
lodash.forEach(this._avatars, (avatar) => {
let profile = avatar.getProfile()
if (profile) {
ret[profile.id] = profile
}
})
return ret
}
getUpdateTime () {
let ret = {}
if (this._profile) {
ret.profile = MysAvatar.getDate(this._profile)
}
if (this._mys) {
ret.mys = MysAvatar.getDate(this._mys)
}
return ret
}
getInfo () {
return MysAvatar.getInfo(this)
}
// 更新面板
async refreshProfile (force = 2) {
return await Profile.refreshProfile(this, force)
}
// 更新米游社数据
/**
* 更新米游社数据
* @param force: 0:不强制长超时时间 1短超时时间 2无视缓存强制刷新
* @returns {Promise<boolean>}
*/
async refreshMysDetail (force = 0) {
return MysAvatar.refreshMysDetail(this, force)
}
async refreshMysInfo (force = 0) {
return await MysAvatar.refreshMysInfo(this, force)
}
// 通过已有的Mys CharData更新
setMysCharData (charData) {
MysAvatar.setMysCharData(this, charData)
}
// 使用MysApi刷新指定角色的天赋信息
async refreshTalent (ids = '', force = 0) {
return await MysAvatar.refreshTalent(this, ids, force)
}
async refresh (cfg) {
if (cfg.detail || cfg.detail === 0) {
await this.refreshMysDetail(cfg.detail)
}
if (cfg.talent || cfg.talent === 0) {
await this.refreshTalent(cfg.ids, cfg.talent)
}
if (cfg.profile || cfg.profile === 0) {
await this.refreshProfile(cfg.profile)
}
}
async refreshAndGetAvatarData (cfg) {
await this.refresh(cfg)
let rank = false
let e = this.e
if (cfg.rank === true && e && e.group_id) {
rank = await ProfileRank.create({ group: e.group_id, uid: this.uid, qq: e.user_id })
}
let avatarRet = {}
this.forEachAvatar((avatar) => {
let { talent } = avatar
let ds = avatar.getDetail()
ds.aeq = talent?.a?.original + talent?.e?.original + talent?.q?.original || 3
avatarRet[ds.id] = ds
let profile = avatar.getProfile()
if (profile) {
let mark = profile.getArtisMark(false)
ds.artisMark = Data.getData(mark, 'mark,markClass,names')
if (rank) {
rank.getRank(profile)
}
}
})
if (cfg.retType !== 'array') {
return avatarRet
}
avatarRet = lodash.values(avatarRet)
if (cfg.sort) {
let sortKey = 'level,star,aeq,cons,weapon.level,weapon.star,weapon.affix,fetter'.split(',')
avatarRet = lodash.orderBy(avatarRet, sortKey)
avatarRet = avatarRet.reverse()
}
return avatarRet
}
}

View File

@ -1,30 +0,0 @@
import Base from './Base.js'
export default class Profile extends Base {
constructor (uid) {
super()
if (!uid) {
return false
}
let cacheObj = this._getCache(`profile:${uid}`)
if (cacheObj) {
return cacheObj
}
this.uid = uid
return this._cache()
}
getProfileData () {
}
static create (uid) {
let profile = new Profile(uid)
return profile
}
static get (uid, id) {
let profile = Profile.create(uid)
return profile.getProfileData(id)
}
}

View File

@ -2,187 +2,27 @@
* 面板圣遗物
*/
import lodash from 'lodash'
import Base from './Base.js'
import AvatarArtis from './AvatarArtis.js'
import { Artifact, ArtifactSet, Character } from './index.js'
import { Format, Data } from '../components/index.js'
import ArtisMark from './profile-lib/ArtisMark.js'
import { attrMap, attrValue } from '../resources/meta/artifact/index.js'
import CharArtis from './profile-lib/CharArtis.js'
import { Format } from '../components/index.js'
import ArtisMark from './profile/ArtisMark.js'
import { attrMap } from '../resources/meta/artifact/index.js'
import CharArtis from './profile/CharArtis.js'
export default class ProfileArtis extends Base {
export default class ProfileArtis extends AvatarArtis {
constructor (charid = 0, elem = '') {
super()
this.charid = charid
super(charid)
this.elem = elem
this.artis = {}
}
setProfile (profile, artis) {
this.profile = profile
this.elem = profile.elem || profile.char?.elem
if (artis) {
this.setArtisSet(artis)
this.setArtisData(artis, true)
}
}
setArtisSet (ds) {
for (let key in ds) {
this.setArtis(key, ds[key] || {})
}
}
setArtis (idx = 1, ds = {}) {
idx = idx.toString().replace('arti', '')
let ret = {}
ret.name = ds.name || ArtifactSet.getArtiNameBySet(ds.set, idx) || ''
ret.set = ds.set || Artifact.getSetNameByArti(ret.name) || ''
ret.level = ds.level || 1
ret.main = ArtisMark.formatAttr(ds.main || {})
ret.attrs = []
for (let attrIdx in ds.attrs || []) {
if (ds.attrs[attrIdx]) {
ret.attrs.push(ArtisMark.formatAttr(ds.attrs[attrIdx]))
}
}
this.artis[idx] = ret
}
forEach (fn) {
lodash.forEach(this.artis, (ds, idx) => {
if (ds.name) {
fn(ds, idx)
}
})
}
_get (key) {
let artis = this.artis
switch (key) {
case 'length':
return lodash.keys(artis).length
}
if (artis[key]) {
return artis[key]
}
}
toJSON () {
return this.getData('1,2,3,4,5')
}
get sets () {
return this.getSetData().sets || {}
}
get names () {
return this.getSetData().names || []
}
get hasArtis () {
return !lodash.isEmpty(this.artis)
}
mainAttr (idx = '') {
if (!idx) {
let ret = {}
for (let i = 1; i <= 5; i++) {
ret[i] = this.mainAttr(i)
}
return ret
}
let main = this.artis[idx]?.main
if (!main) {
return ''
}
return main.key || ''
}
is (check, pos = '') {
if (pos) {
return this.isAttr(check, pos)
}
let sets = this.getSetData()?.abbrs || []
let ret = false
Data.eachStr(check, (s) => {
if (sets.includes(s)) {
ret = true
return false
}
})
return ret
}
isAttr (attr, pos = '3,4,5') {
let mainAttr = this.mainAttr()
let check = true
Data.eachStr(pos.toString(), (p) => {
let attrs = attr.split(',')
if (!attrs.includes(mainAttr[p]) && (p === '4' && !attrs.includes('dmg') && Format.isElem(mainAttr[p]))) {
check = false
return false
}
})
return check
}
// 获取圣遗物数据
getArtisData () {
let ret = {}
this.forEach((ds, idx) => {
let arti = Artifact.get(ds.name)
ret[idx] = {
...ds,
name: arti.name,
img: arti.img
}
})
return ret
}
/**
* 获取圣遗物套装数据
* @returns {*|{imgs: *[], names: *[], sets: {}, abbrs: *[], sName: string, name: (string|*)}}
* sets: 套装名:2/4
* names: [套装名]
* imgs: [img]
* abbrs[别名]
* name: '组合名字' 若为4件套会使用套装完整名
* sName: '简写名字'若为4件套也会使用简写
*/
getSetData () {
if (this._setData) {
return this._setData
}
let setCount = {}
this.forEach((arti, idx) => {
setCount[arti.set] = (setCount[arti.set] || 0) + 1
})
let sets = {}
let names = []
let imgs = []
let abbrs = []
let abbrs2 = []
for (let set in setCount) {
if (setCount[set] >= 2) {
let count = setCount[set] >= 4 ? 4 : 2
sets[set] = count
let artiSet = ArtifactSet.get(set)
names.push(artiSet.name)
imgs.push(artiSet.img)
abbrs.push(artiSet.abbr + count)
abbrs2.push(artiSet.name + count)
}
}
this._setData = {
sets,
names,
imgs,
abbrs: [...abbrs, ...abbrs2],
name: (abbrs.length > 1 || abbrs2[0]?.length > 7) ? abbrs.join('+') : abbrs2[0],
sName: abbrs.join('+')
}
return this._setData
}
/**
* 获取角色配置
* @returns {{classTitle: *, weight: *, posMaxMark: {}, mark: {}, attrs: {}}}
@ -202,13 +42,13 @@ export default class ProfileArtis extends Base {
...attr,
weight,
fixWeight: weight,
mark: weight / attrValue[key]
mark: weight / attr.value
}
if (!k) {
ret.mark = weight / attrValue[key]
ret.mark = weight / attr.value
} else {
let plus = k === 'atk' ? 520 : 0
ret.mark = weight / attrValue[k] / (baseAttr[k] + plus) * 100
ret.mark = weight / attrMap[k].value / (baseAttr[k] + plus) * 100
ret.fixWeight = weight * attr.value / attrMap[k].value / (baseAttr[k] + plus) * 100
}
attrs[key] = ret
@ -280,24 +120,4 @@ export default class ProfileArtis extends Base {
}
return ret
}
static _eachArtisSet (sets, fn) {
lodash.forEach(sets || [], (v, k) => {
let artisSet = ArtifactSet.get(k)
if (artisSet) {
if (v >= 4) {
fn(artisSet, 2)
}
fn(artisSet, v)
}
})
}
eachArtisSet (fn) {
ProfileArtis._eachArtisSet(this.sets, fn)
}
static getArtisKeyTitle () {
return ArtisMark.getKeyTitleMap()
}
}

View File

@ -1,53 +1,60 @@
import lodash from 'lodash'
import Base from './Base.js'
import moment from 'moment'
import AvatarData from './AvatarData.js'
import { Data } from '../components/index.js'
import { Character, ProfileArtis, ProfileDmg } from './index.js'
import AttrCalc from './profile-lib/AttrCalc.js'
import { ProfileArtis, ProfileDmg } from './index.js'
import AttrCalc from './profile/AttrCalc.js'
import CharImg from './character/CharImg.js'
export default class ProfileData extends Base {
constructor (ds = {}, uid, attrCalc = true) {
super()
let char = Character.get({ id: ds.id, elem: ds.elem })
if (!char) {
return false
}
this.id = char.id
this.char = char
this.uid = uid || ''
this.setBasic(ds)
ds.attr && this.setAttr(ds.attr)
ds.weapon && this.setWeapon(ds.weapon)
ds.talent && this.setTalent(ds.talent)
this.artis = new ProfileArtis(this.id, this.elem)
ds.artis && this.setArtis(ds.artis)
if (attrCalc && this.hasData) {
export default class ProfileData extends AvatarData {
constructor (ds = {}, calc = true) {
super(ds)
if (calc) {
this.calcAttr()
}
}
static create (ds, uid) {
let profile = new ProfileData(ds, uid)
// 判断当前profileData是否具有有效数据
get hasData () {
return this.isProfile
}
get costumeSplash () {
let costume = this._costume
costume = this.char.checkCostume(costume) ? '2' : ''
let nPath = `meta/character/${this.name}`
let isSuper = false
let talent = this.talent ? lodash.map(this.talent, (ds) => ds.original).join('') : ''
if (this.cons === 6 || ['ACE', 'ACE²'].includes(this.artis?.markClass) || talent === '101010') {
isSuper = true
}
if (isSuper) {
return CharImg.getRandomImg(
[`profile/super-character/${this.name}`, `profile/normal-character/${this.name}`],
[`${nPath}/imgs/splash0.webp`, `${nPath}/imgs/splash${costume}.webp`, `/${nPath}/imgs/splash.webp`]
)
} else {
return CharImg.getRandomImg(
[`profile/normal-character/${this.name}`],
[`${nPath}/imgs/splash${costume}.webp`, `/${nPath}/imgs/splash.webp`]
)
}
}
get hasDmg () {
return this.hasData && !!ProfileDmg.dmgRulePath(this.name)
}
static create (ds) {
let profile = new ProfileData(ds)
if (!profile) {
return false
}
return profile
}
calcAttr () {
this._attr = AttrCalc.create(this)
this.attr = this._attr.calc()
}
setBasic (ds = {}) {
this.level = ds.lv || ds.level || 1
this.cons = ds.cons || 0
this.fetter = ds.fetter || 0
this._costume = ds.costume || 0
this.elem = ds.elem || this.char.elem || ''
this.dataSource = ds.dataSource || 'enka'
this.promote = lodash.isUndefined(ds.promote) ? AttrCalc.calcPromote(this.level) : (ds.promote || 0)
this._time = ds._time || ds.updateTime || new Date() * 1
initArtis () {
this.artis = new ProfileArtis(this.id, this.elem)
}
setAttr (ds) {
@ -60,104 +67,13 @@ export default class ProfileData extends Base {
})
}
setWeapon (ds = {}) {
this.weapon = {
name: ds.name,
star: ds.rank || ds.star || 1,
level: ds.level || ds.lv || 1,
promote: lodash.isUndefined(ds.promote) ? AttrCalc.calcPromote(ds.level || ds.lv || 1) : (ds.promote || 0),
affix: ds.affix
}
let w = this.weapon
if (w.level < 20) {
w.promote = 0
}
calcAttr () {
this._attr = AttrCalc.create(this)
this.attr = this._attr.calc()
}
setArtis (ds = false) {
this.artis.setProfile(this, ds)
}
setTalent (ds = {}, mode = 'original') {
this.talent = this.char.getAvatarTalent(ds, this.cons, mode)
}
get name () {
return this.char?.name || ''
}
// 判断当前profileData是否具有有效数据
get hasData () {
// 检查数据源
if (!this.dataSource || !['enka', 'change', 'miao'].includes(this.dataSource)) {
return false
}
// 检查属性
if (!this.weapon || !this.talent || !this.artis) {
return false
}
// 检查旅行者
if (['空', '荧'].includes(this.name)) {
return !!this.elem
}
return true
}
// 判断当前profileData是否具备有效圣遗物信息
hasArtis () {
return this.hasData && this.artis.length > 0
}
get costume () {
let costume = this._costume
if (lodash.isArray(costume)) {
costume = costume[0]
}
let talent = this.talent ? lodash.map(this.talent, (ds) => ds.original).join('') : ''
if (this.cons === 6 || ['ACE', 'ACE²'].includes(this.artis?.markClass) || talent === '101010') {
return [costume, 'super']
}
return [costume, 'normal']
}
get originalTalent () {
return lodash.mapValues(this.talent, (ds) => ds.original)
}
// toJSON 供保存使用
toJSON () {
let ret = {
...this.getData('id,name,elem,level,promote,fetter,costume,cons,talent,attr,weapon,artis,dataSource,_time')
}
ret.talent = lodash.mapValues(this.talent, (ds) => ds.original)
ret.costume = this.costume[0] || 0
return ret
}
get updateTime () {
let time = this._time
if (!time) {
return ''
}
if (lodash.isString(time)) {
return moment(time).format('MM-DD HH:mm')
}
if (lodash.isNumber(time)) {
return moment(new Date(time)).format('MM-DD HH:mm')
}
return ''
}
get dataSourceName () {
return {
enka: 'Enka.Network',
miao: '喵喵Api',
input: 'Input'
}[this.dataSource] || 'Enka.NetWork'
}
get isProfile () {
return true
this.artis?.setProfile(this, ds.artis?.artis || ds.artis || ds)
}
// 获取当前profileData的圣遗物评分withDetail=false仅返回简略信息
@ -168,10 +84,6 @@ export default class ProfileData extends Base {
return {}
}
get hasDmg () {
return this.hasData && !!ProfileDmg.dmgRulePath(this.name)
}
// 计算当前profileData的伤害信息
async calcDmg ({ enemyLv = 91, mode = 'profile', dmgIdx = 0 }) {
if (!this.dmg) {

View File

@ -3,9 +3,9 @@ import lodash from 'lodash'
import Base from './Base.js'
import { Character } from './index.js'
import { attrMap } from '../resources/meta/artifact/index.js'
import DmgBuffs from './profile-lib/DmgBuffs.js'
import DmgAttr from './profile-lib/DmgAttr.js'
import DmgCalc from './profile-lib/DmgCalc.js'
import DmgBuffs from './profile/DmgBuffs.js'
import DmgAttr from './profile/DmgAttr.js'
import DmgCalc from './profile/DmgCalc.js'
import { Common } from '../components/index.js'
export default class ProfileDmg extends Base {

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

@ -1,17 +1,18 @@
import lodash from 'lodash'
import Base from './Base.js'
import ProfileServ from './ProfileServ.js'
import fetch from 'node-fetch'
function sleep (ms) {
return new Promise((resolve) => setTimeout(resolve, ms))
}
export default class ProfileReq extends Base {
constructor ({ e, uid }) {
constructor (e) {
super()
this.e = e
this.uid = uid
this.uid = e.uid
}
static create (e) {
if (!e || !e.uid) {
return false
}
return new ProfileReq(e)
}
async setCd (seconds = 60) {
@ -32,14 +33,14 @@ export default class ProfileReq extends Base {
}
err (msg = '', cd = 0) {
let serv = this.serv
let extra = serv.name ? `当前面板服务${serv.name}` : ''
const msgs = {
error: '请求失败,可能是面板服务升级维护或遇到故障,请稍后重试...',
error: `UID${this.uid}更新面板失败,${extra}\n可能是面板服务维护中,请稍后重试...`,
empty: '请将角色放置在【游戏内】角色展柜并打开【显示详情】等待5分钟重新获取面板'
}
msg = msgs[msg] || msg
if (msg) {
this.e.reply(msg)
}
this.msg(msg)
// 设置CD
if (cd) {
this.setCd(cd)
@ -48,21 +49,23 @@ 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 request () {
let Serv = ProfileReq.getServ(this.uid)
let reqParam = await Serv.getReqParam(this.uid)
async requestProfile (player, serv) {
this.serv = serv
let reqParam = await serv.getReqParam(this.uid)
let cdTime = await this.inCd()
if (cdTime) {
if (cdTime && !process.argv.includes('web-debug')) {
return this.err(`请求过快,请${cdTime}秒后重试..`)
}
await this.setCd(20)
this.msg(`开始获取uid:${this.uid}的数据,可能会需要一定时间~`)
await sleep(100)
// 发起请求
logger.mark(`面板请求UID:${this.uid},面板服务:${serv.name}...`)
let data = {}
try {
let params = reqParam.params || {}
@ -83,34 +86,20 @@ export default class ProfileReq extends Base {
console.log('面板请求错误', e)
data = {}
}
data = await Serv.response(data, this)
data = await serv.response(data, this)
// 设置CD
cdTime = Serv.getCdTime(data)
cdTime = serv.getCdTime(data)
if (cdTime) {
await this.setCd(cdTime)
}
if (data === false) {
return false
}
let userData = Serv.getUserData(data)
let profiles = Serv.getProfileData(data)
cdTime = Serv.getCdTime(data)
serv.updatePlayer(player, data)
cdTime = serv.getCdTime(data)
if (cdTime) {
await this.setCd(cdTime)
}
return lodash.extend({
uid: this.uid,
chars: profiles
}, userData)
return player
}
}
ProfileReq.serv = {}
ProfileReq.regServ = function (serv) {
for (let key in serv) {
ProfileReq.serv[key] = serv[key]
}
}
ProfileReq.getServ = function (uid) {
return ProfileServ.getServ({ uid, serv: ProfileReq.serv })
}

View File

@ -75,22 +75,7 @@ export default class ProfileServ extends Base {
return Math.max(cdTime, this.execFn('cdTime', [data], 60))
}
getUserData (data) {
return this.execFn('userData', [data], {})
}
getProfileData (data) {
return this.execFn('profileData', [data], {})
updatePlayer (player, data) {
return this.execFn('updatePlayer', [player, data], {})
}
}
ProfileServ.getServ = function ({ uid, serv }) {
let { Miao, Enka } = serv
let token = diyCfg?.miaoApi?.token
let qq = diyCfg?.miaoApi?.qq
if (qq && token && token.length === 32 && !/^test/.test(token) && Cfg.get('profileServ') === 1) {
return Miao
}
return Enka
}

View File

@ -82,10 +82,8 @@ const CharImg = {
},
// 获取角色的图像资源数据
getImgs (name, costumeCfg = '', travelerElem = '', fileType = 'webp') {
if (!lodash.isArray(costumeCfg)) {
costumeCfg = [costumeCfg, 'normal']
}
getImgs (name, costumeIdx = '', travelerElem = '', fileType = 'webp') {
costumeIdx = costumeIdx === '2' ? '2' : ''
let imgs = {}
if (!['空', '荧', '旅行者'].includes(name)) {
travelerElem = ''
@ -102,10 +100,10 @@ const CharImg = {
let tAdd = (key, path) => {
imgs[key] = `${travelerElem ? tPath : nPath}${path}.${fileType}`
}
add('face', 'imgs/face', `imgs/face${costumeCfg[0]}`)
add('side', 'imgs/side', `imgs/side${costumeCfg[0]}`)
add('face', 'imgs/face', `imgs/face${costumeIdx}`)
add('side', 'imgs/side', `imgs/side${costumeIdx}`)
add('gacha', 'imgs/gacha')
add('splash', 'imgs/splash', `imgs/splash${costumeCfg[0]}`)
add('splash', 'imgs/splash', `imgs/splash${costumeIdx}`)
// 检查彩蛋自定义
tAdd('card', 'imgs/card')
tAdd('banner', 'imgs/banner')

View File

@ -2,9 +2,10 @@ import Base from './Base.js'
import Character from './Character.js'
import Artifact from './Artifact.js'
import ArtifactSet from './ArtifactSet.js'
import Avatar from './Avatar.js'
import AvatarList from './AvatarList.js'
import AvatarData from './AvatarData.js'
import AvatarArtis from './AvatarArtis.js'
import Abyss from './Abyss.js'
import Player from './Player.js'
import ProfileServ from './ProfileServ.js'
import ProfileReq from './ProfileReq.js'
import ProfileData from './ProfileData.js'
@ -23,8 +24,8 @@ export {
Character,
Artifact,
ArtifactSet,
Avatar,
AvatarList,
AvatarData,
AvatarArtis,
ProfileServ,
ProfileReq,
ProfileData,
@ -35,5 +36,6 @@ export {
Material,
Weapon,
User,
MysApi
MysApi,
Player
}

View File

@ -1,14 +1,13 @@
import lodash from 'lodash'
import { Data } from '../index.js'
import { ProfileServ } from '../../models/index.js'
import EnkaData from './enka-data.js'
import { Data } from '../../components/index.js'
import EnkaData from './EnkaData.js'
import MiaoData from './MiaoData.js'
let HttpsProxyAgent = ''
export default new ProfileServ({
export default {
id: 'enka',
cfgKey: 'enkaApi',
// 处理请求参数
async request (api) {
let params = {
@ -16,7 +15,6 @@ export default new ProfileServ({
}
let proxy = this.getCfg('proxyAgent')
if (proxy) {
if (HttpsProxyAgent === '') {
HttpsProxyAgent = await import('https-proxy-agent').catch((err) => {
logger.error(err)
@ -45,8 +43,14 @@ export default new ProfileServ({
return data
},
userData (data) {
return Data.getData(data, 'name:nickname,avatar:profilePicture.avatarId,level,signature')
updatePlayer (player, data) {
player.setBasicData(Data.getData(data, 'name:nickname,face:profilePicture.avatarID,card:nameCardID,level,word:worldLevel,sign:signature'))
lodash.forEach(data.avatarInfoList, (ds) => {
let ret = EnkaData.setAvatar(player, ds)
if (ret) {
player._update.push(ret.id)
}
})
},
profileData (data) {
@ -64,4 +68,4 @@ export default new ProfileServ({
cdTime (data) {
return data.ttl || 60
}
})
}

126
models/player/EnkaData.js Normal file
View File

@ -0,0 +1,126 @@
import lodash from 'lodash'
import { attrMap, idsMap, artisIdxMap } from './ProfileMeta.js'
import { Character, ArtifactSet } from '../index.js'
let EnkaData = {
setAvatar (player, data) {
let char = Character.get(data.avatarId)
if (!char) {
return
}
let avatar = player.getAvatar(char.id, true)
let talentRet = EnkaData.getTalent(char.id, data.skillLevelMap)
avatar.setAvatar({
level: data.propMap['4001'].val * 1,
promote: data.propMap['1002'].val * 1,
cons: data.talentIdList ? data.talentIdList.length : 0,
fetter: data.fetterInfo.expLevel,
costume: char.checkCostume(data.costumeId) ? data.costumeId : 0,
elem: talentRet.elem,
weapon: EnkaData.getWeapon(data.equipList),
talent: talentRet.talent,
artis: EnkaData.getArtifact(data.equipList)
}, 'enka')
return avatar
},
getWeapon (data) {
let ds = {}
lodash.forEach(data, (temp) => {
if (temp.flat && temp.flat.itemType === 'ITEM_WEAPON') {
ds = temp
return false
}
})
let { weapon, flat } = ds
return {
name: idsMap[flat.nameTextMapHash],
level: weapon.level,
promote: weapon.promoteLevel,
affix: (lodash.values(weapon.affixMap)[0] || 0) + 1
}
},
getTalent (charid, ds = {}) {
let char = Character.get(charid)
let { talentId = {}, talentElem = {}, talentKey = {} } = char.meta
let elem = ''
let idx = 0
let ret = {}
lodash.forEach(ds, (lv, id) => {
let key
if (talentId[id]) {
let tid = talentId[id]
key = talentKey[tid]
elem = elem || talentElem[tid]
ret[key] = lv
} else {
key = ['a', 'e', 'q'][idx++]
ret[key] = ret[key] || lv
}
})
return {
elem: elem,
talent: ret
}
},
getArtifact (data) {
let ret = {}
lodash.forEach(data, (ds) => {
let flat = ds.flat || {}
let re = ds.reliquary
let idx = artisIdxMap[flat.equipType]
if (!idx) {
return
}
let setName = idsMap[flat.setNameTextMapHash] || ''
ret[idx] = {
name: ArtifactSet.getArtiNameBySet(setName, idx),
level: Math.min(20, ((re.level) || 1) - 1),
star: flat.rankLevel || 5,
mainId: re.mainPropId,
attrIds: re.appendPropIdList
}
})
return ret
},
getArtifactBak (data) {
let ret = {}
let get = function (d) {
if (!d) {
return {}
}
let id = d.appendPropId || d.mainPropId || ''
id = id.replace('FIGHT_PROP_', '')
if (!attrMap[id]) {
return {}
}
return { key: attrMap[id], value: d.statValue }
}
lodash.forEach(data, (ds) => {
let flat = ds.flat || {}
let sub = flat.reliquarySubstats || []
let idx = artisIdxMap[flat.equipType]
if (!idx) {
return
}
let setName = idsMap[flat.setNameTextMapHash] || ''
ret[idx] = {
name: ArtifactSet.getArtiNameBySet(setName, idx),
level: Math.min(20, ((ds.reliquary && ds.reliquary.level) || 1) - 1),
main: get(flat.reliquaryMainstat),
attrs: [
get(sub[0]),
get(sub[1]),
get(sub[2]),
get(sub[3])
]
}
})
return ret
}
}
export default EnkaData

View File

@ -1,9 +1,8 @@
import lodash from 'lodash'
import { Data } from '../index.js'
import { ProfileServ } from '../../models/index.js'
import MiaoData from './miao-data.js'
import { Data } from '../../components/index.js'
import MiaoData from './MiaoData.js'
export default new ProfileServ({
export default {
key: 'miao',
name: '喵喵Api',
cfgKey: 'miaoApi',
@ -18,19 +17,14 @@ export default new ProfileServ({
return data
},
userData (data) {
return Data.getData(data, 'name:nickname,avatar:profilePicture.avatarID,level,signature')
},
profileData (data) {
let ret = {}
updatePlayer (player, data) {
player.setBasicData(Data.getData(data, 'name:nickname,face:profilePicture.avatarID,card:nameCardID,level,word:worldLevel,sign:signature'))
lodash.forEach(data.showAvatarInfoList, (ds) => {
let profile = MiaoData.getProfile(ds)
if (profile && profile.id) {
ret[profile.id] = profile
let ret = MiaoData.setAvatar(player, ds)
if (ret) {
player._update.push(ret.id)
}
})
return ret
},
// 获取冷却时间
@ -41,4 +35,4 @@ export default new ProfileServ({
}
return 60
}
})
}

101
models/player/MiaoData.js Normal file
View File

@ -0,0 +1,101 @@
import { Character } from '../index.js'
import lodash from 'lodash'
import { attrMap, artisIdxMap } from './ProfileMeta.js'
let MiaoData = {
setAvatar (player, ds) {
let char = Character.get(ds.id)
let avatar = player.getAvatar(ds.id, true)
let talentRet = MiaoData.getTalent(char.id, ds.skill)
avatar.setAvatar({
level: ds.level,
cons: ds.constellationNum || 0,
promote: ds.promoteLevel,
fetter: ds.fetterLevel,
costume: char.checkCostume(ds.costumeID) ? ds.costumeID : 0,
elem: talentRet.elem,
weapon: MiaoData.getWeapon(ds.weapon),
talent: talentRet.talent,
artis: MiaoData.getArtifact(ds.reliquary)
}, 'miao')
return avatar
},
getWeapon (weapon) {
return {
name: weapon.name,
level: weapon.level,
promote: weapon.promoteLevel,
affix: (weapon.affixLevel || 0) + 1
}
},
getTalent (charid, data = {}) {
let char = Character.get(charid)
let { talentId = {}, talentElem = {}, talentKey = {} } = char.meta
let elem = ''
let idx = 0
let ret = {}
lodash.forEach(data, (ds) => {
let key
if (talentId[ds.id]) {
let tid = talentId[ds.id]
key = talentKey[tid]
elem = elem || talentElem[tid]
ret[key] = {
level: ds.level
}
} else {
key = ['a', 'e', 'q'][idx++]
ret[key] = ret[key] || {
level: ds.level
}
}
})
return {
talent: ret,
elem
}
},
getArtifact (data) {
let ret = {}
let get = function (d) {
if (!d) {
return []
}
let name = d.name
name = name.replace('FIGHT_PROP_', '')
if (!attrMap[name]) {
return []
}
let value = d.value
if (value && value < 1) {
value = value * 100
}
return { key: attrMap[name], value }
}
lodash.forEach(data, (ds) => {
let sub = ds.appendAffix || []
let idx = artisIdxMap[ds.type]
if (!idx) {
return
}
ret[idx] = {
name: ds.name,
level: ds.level,
star: ds.rank,
main: get(ds.mainAffix),
attrs: [
get(sub[0]),
get(sub[1]),
get(sub[2]),
get(sub[3])
]
}
})
return ret
}
}
export default MiaoData

303
models/player/MysAvatar.js Normal file
View File

@ -0,0 +1,303 @@
import lodash from 'lodash'
import { Common, Data } from '../../components/index.js'
import { chestInfo } from '../../resources/meta/info/index.js'
import moment from 'moment'
const MysAvatar = {
needRefresh (time, force = 0, forceMap = {}) {
if (!time || force === 2) {
return true
}
if (force === true) {
force = 0
}
let duration = (new Date() * 1 - time * 1) / 1000
if (isNaN(duration) || duration < 0) {
return true
}
let reqTime = forceMap[force] === 0 ? 0 : (forceMap[force] || 60)
return duration > reqTime * 60
},
/**
* 更新米游社角色信息
* @param player
* @param mys
* @param force
* @returns {Promise<boolean>}
*/
async refreshMysDetail (player, force = 0) {
let mys = player?.e?._mys
if (!mys) {
return false
}
if (!MysAvatar.needRefresh(player._mys, force, { 0: 60, 1: 2, 2: 0 })) {
return false
}
let charData = await mys.getCharacter()
if (!charData || !charData.avatars) {
return false
}
MysAvatar.setMysCharData(player, charData)
},
/**
* 更新米游社统计信息
* @param player
* @param force
* @returns {Promise<boolean>}
*/
async refreshMysInfo (player, force = 0) {
let mys = player?.e?._mys
if (!mys) {
return false
} // 不必要更新
if (!MysAvatar.needRefresh(player._info, force, { 0: 60, 1: 2, 2: 0 })) {
return false
}
let infoData = await mys.getIndex()
if (!infoData || !infoData.role) {
return false
}
MysAvatar.setMysInfo(player, infoData)
},
/**
* 根据已有Mys CharData更新player
* @param player
* @param charData
*/
setMysCharData (player, charData) {
let role = charData.role
player.setBasicData({
level: role.level,
name: role.nickname
})
let charIds = {}
lodash.forEach(charData.avatars, (ds) => {
let avatar = Data.getData(ds, 'id,level,cons:actived_constellation_num,fetter')
avatar.elem = ds.element.toLowerCase()
// 处理时装数据
let costume = (ds?.costumes || [])[0]
if (costume && costume.id) {
avatar.costume = costume.id
}
avatar.weapon = Data.getData(ds.weapon, 'name,star:rarity,level,promote:promote_level,affix:affix_level')
// 处理圣遗物数据
let artis = {}
lodash.forEach(ds.reliquaries, (re) => {
const posIdx = { 生之花: 1, 死之羽: 2, 时之沙: 3, 空之杯: 4, 理之冠: 5 }
if (re && re.name && posIdx[re.pos_name]) {
artis[posIdx[re.pos_name]] = {
name: re.name,
level: re.level
}
}
})
avatar.artis = artis
player.setAvatar(avatar, 'mys')
charIds[avatar.id] = true
})
// 若角色数据>8检查缓存删除错误缓存的数据
if (lodash.keys(charIds).length > 8) {
player.forEachAvatar((avatar) => {
if (!charIds[avatar.id] && !avatar.isProfile) {
delete player._avatars[avatar.id]
}
})
}
player._mys = new Date() * 1
player.save()
},
setMysInfo (player, infoData) {
let role = infoData.role
// 设置角色信息
let homeLevel = ((infoData?.homes || [])[0])?.level
if (role) {
player.setBasicData({
level: role.level,
name: role.nickname
})
}
// 设置角色数据
lodash.forEach(infoData?.avatars || [], (ds) => {
let avatar = Data.getData(ds, 'id,level,cons:actived_constellation_num,fetter')
avatar.elem = ds.element.toLowerCase()
player.setAvatar(avatar, 'mys')
})
let stats = {}
lodash.forEach(infoData?.stats || [], (num, key) => {
key = key.replace('_number', '')
if (key !== 'spiral_abyss') {
stats[lodash.camelCase(key)] = num
}
})
let exploration = {}
lodash.forEach(infoData?.world_explorations || [], (ds) => {
let { name } = ds
if (name === '层岩巨渊') {
return true
}
exploration[name === '层岩巨渊·地下矿区' ? '层岩巨渊' : name] = ds.exploration_percentage
})
player.info = {
homeLevel,
stats,
exploration
}
player._info = new Date() * 1
player.save()
},
/**
* 获取当前角色需要更新天赋的角色ID
* @param player
* @param ids 角色列表若传入则查询指定角色列表不传入查询全部
* @param force
* @returns {*[]}
*/
getNeedRefreshIds (player, ids, force = 0) {
let ret = []
if (!ids) {
ids = lodash.keys(player._avatars)
} else if (!lodash.isArray(ids)) {
ids = [ids]
}
lodash.forEach(ids, (id) => {
let avatar = player.getAvatar(id)
if (!avatar) {
return true
}
if (!avatar.hasTalent || MysAvatar.needRefresh(avatar._talent, force, { 0: 60 * 24, 1: 60, 2: 0 })) {
ret.push(avatar.id)
}
})
return ret
},
/**
* 使用MysApi刷新指定角色的天赋信息
* @param player
* @param ids
* @param force
* @returns {Promise<boolean>}
*/
async refreshTalent (player, ids, force = 0) {
let e = player?.e
let mys = e?._mys
if (!e || !mys) {
return false
}
let needReqIds = MysAvatar.getNeedRefreshIds(player, ids, force)
if (needReqIds.length > 0) {
if (needReqIds.length > 8) {
e && e.reply('正在获取角色信息,请稍候...')
}
let failCount = 0
// 并发5请求天赋数据
await Data.asyncPool(5, needReqIds, async (id) => {
let avatar = player.getAvatar(id)
if (!avatar || failCount > 5) {
return false
}
let ret = await MysAvatar.refreshAvatarTalent(avatar, mys)
if (ret === false) {
failCount++
}
})
}
player.save()
},
async refreshAvatarTalent (avatar, mys) {
if (mys && mys.isSelfCookie) {
let char = avatar.char
if (!char) {
return false
}
let id = char.id
let talent = {}
let talentRes = await mys.getDetail(id)
// { data: null, message: '请先登录', retcode: -100, api: 'detail' }
if (talentRes && talentRes.skill_list) {
let talentList = lodash.orderBy(talentRes.skill_list, ['id'], ['asc'])
for (let val of talentList) {
let { max_level: maxLv, level_current: lv } = val
if (val.name.includes('普通攻击')) {
talent.a = lv
continue
}
if (maxLv >= 10 && !talent.e) {
talent.e = lv
continue
}
if (maxLv >= 10 && !talent.q) {
talent.q = lv
}
}
}
let ret = char.getAvatarTalent(talent, avatar.cons, 'original')
avatar.setTalent(ret, 'original', true)
return !!ret
}
return false
},
getDate (time) {
return time ? moment(new Date(time)).format('MM-DD HH:mm') : ''
},
getInfo (player) {
let chestMap = []
Data.eachStr('common,exquisite,precious,luxurious,magic', (key) => {
chestMap.push({
key: `${key}Chest`,
...chestInfo[key]
})
})
let ret = {
...(player.info || {}),
chestMap
}
let stats = ret.stats || {}
if (stats?.activeDay) {
let num = stats?.activeDay
let year = Math.floor(num / 365)
let month = Math.floor((num % 365) / 30.41)
let day = Math.floor((num % 365) % 30.41)
let msg = ''
if (year > 0) {
msg += year + '年'
}
if (month > 0) {
msg += month + '个月'
}
if (day > 0) {
msg += day + '天'
}
ret.activeDay = msg
}
let avatarCount = 0
let avatar5Count = 0
let goldCount = 0
player.forEachAvatar((avatar) => {
avatarCount++
if (avatar.star === 5) {
avatar5Count++
goldCount += (avatar.cons || 0) + 1
}
let w = avatar.weapon
if (w && w.star === 5) {
goldCount += w.affix * 1
}
})
stats.avatar = Math.max(stats.avatar, avatarCount)
stats.goldCount = goldCount
stats.avatar5 = avatar5Count
ret.stats = stats
return ret
}
}
export default MysAvatar

86
models/player/Profile.js Normal file
View File

@ -0,0 +1,86 @@
import { ProfileReq, ProfileServ } from '../index.js'
import { Cfg, Data } from '../../components/index.js'
import MysAvatar from './MysAvatar.js'
import enkaCfg from './EnkaApi.js'
import MiaoApi from './MiaoApi.js'
let { diyCfg } = await Data.importCfg('profile')
const Profile = {
/**
* 根据UID分配请求服务器
* @param uid
* @returns {ProfileServ}
*/
getServ (uid) {
let token = diyCfg?.miaoApi?.token
let qq = diyCfg?.miaoApi?.qq
if (qq && token && token.length === 32 && !/^test/.test(token) && Cfg.get('profileServ') === 1) {
if (!Profile.Miao) {
Profile.Miao = new ProfileServ(MiaoApi)
}
return Profile.Miao
}
if (!Profile.Enka) {
Profile.Enka = new ProfileServ(enkaCfg)
}
return Profile.Enka
},
/**
* 更新面板数据
* @param player
* @param force
* @returns {Promise<boolean|number>}
*/
async refreshProfile (player, force = 2) {
if (!MysAvatar.needRefresh(player._profile, force, { 0: 24, 1: 2, 2: 0 })) {
return false
}
player._update = []
let { uid, e } = player
if (uid.toString().length !== 9 || !e) {
return false
}
let req = ProfileReq.create(e)
if (!req) {
return false
}
let serv = Profile.getServ(uid)
try {
await req.requestProfile(player, serv)
player._profile = new Date() * 1
player.save()
return player._update.length
} catch (err) {
if (!e._isReplyed) {
e.reply(`UID:${uid}更新面板失败,更新服务:${serv.name}`)
}
return false
}
},
isProfile (avatar) {
// 检查数据源
if (!avatar._source || !['enka', 'change', 'miao'].includes(avatar._source)) {
return false
}
// 检查武器及天赋
if (!avatar.weapon || !avatar.talent) {
return false
}
// 检查圣遗物词条是否完备
if (!avatar.artis || !avatar.artis.hasAttr) {
return false
}
// 检查旅行者
if (['空', '荧'].includes(avatar.name)) {
return !!avatar.elem
}
return true
}
}
export default Profile

View File

@ -1,4 +1,5 @@
export default {
// Enka IdsMap
export const idsMap = {
20848859: '黑岩斩刀',
33330467: '元素熟练',
37147251: '匣里日月',
@ -473,3 +474,40 @@ export default {
FIGHT_PROP_WIND_SUB_HURT: '风元素抗性',
level: '等级'
}
// 圣遗物词条映射
export const attrMap = {
HP: 'hpPlus',
HP_PERCENT: 'hp',
ATTACK: 'atkPlus',
ATTACK_PERCENT: 'atk',
DEFENSE: 'defPlus',
DEFENSE_PERCENT: 'def',
FIRE_ADD_HURT: 'pyro',
ICE_ADD_HURT: 'cryo',
ROCK_ADD_HURT: 'geo',
ELEC_ADD_HURT: 'electro',
WIND_ADD_HURT: 'anemo',
WATER_ADD_HURT: 'hydro',
PHYSICAL_ADD_HURT: 'phy',
GRASS_ADD_HURT: 'dendro',
HEAL_ADD: 'heal',
ELEMENT_MASTERY: 'mastery',
CRITICAL: 'cpct',
CRITICAL_HURT: 'cdmg',
CHARGE_EFFICIENCY: 'recharge'
}
// 圣遗物位置映射
export const artisIdxMap = {
EQUIP_BRACER: 1,
EQUIP_NECKLACE: 2,
EQUIP_SHOES: 3,
EQUIP_RING: 4,
EQUIP_DRESS: 5,
生之花: 1,
死之羽: 2,
时之沙: 3,
空之杯: 4,
理之冠: 5
}

View File

@ -1,38 +0,0 @@
import fs from 'node:fs'
import { Data } from '../../components/index.js'
import lodash from 'lodash'
const _path = process.cwd()
const userPath = `${_path}/data/UserData/`
if (!fs.existsSync(userPath)) {
fs.mkdirSync(userPath)
}
let ProfileFile = {
getData (uid) {
let data = Data.readJSON('/data/UserData', 'root')
if (data && data.chars) {
return data
} else {
return {
uid,
chars: {}
}
}
},
saveData (profile) {
let userData = {}
const userFile = `${userPath}/${uid}.json`
if (fs.existsSync(userFile)) {
userData = JSON.parse(fs.readFileSync(userFile, 'utf8')) || {}
}
lodash.assignIn(userData, lodash.pick(data, 'uid,name,lv,avatar'.split(',')))
userData.chars = userData.chars || {}
lodash.forEach(data.chars, (char, charId) => {
userData.chars[charId] = char
})
fs.writeFileSync(userFile, JSON.stringify(userData), '', ' ')
return data
}
}
export default ProfileFile

View File

@ -5,7 +5,7 @@ import { attrNameMap, mainAttr, subAttr, attrMap } from '../../resources/meta/ar
let ArtisMark = {
// 根据Key获取标题
getKeyByTitle (title, dmg = false) {
if (/元素伤害加成/.test(title)) {
if (/元素伤害加成/.test(title) || Format.isElem(title)) {
let elem = Format.matchElem(title)
return dmg ? 'dmg' : elem
} else if (title === '物理伤害加成') {
@ -67,23 +67,31 @@ let ArtisMark = {
let ret = []
let totalUpNum = 0
let ltArr = []
let isIdAttr = false
lodash.forEach(ds, (d) => {
isIdAttr = !!d.eff
let arti = ArtisMark.formatArti(d, charAttrCfg)
ret.push(arti)
if (isIdAttr) {
return true
}
totalUpNum += arti.upNum
if (arti.hasLt) {
ltArr.push(arti)
}
ret.push(arti)
delete arti.hasLt
delete arti.hasGt
})
ltArr = lodash.sortBy(ltArr, 'upNum').reverse()
for (let arti of ltArr) {
if (totalUpNum > 9) {
arti.upNum = arti.upNum - 1
totalUpNum--
} else {
break
if (!isIdAttr) {
ltArr = lodash.sortBy(ltArr, 'upNum').reverse()
for (let arti of ltArr) {
if (totalUpNum > 9) {
arti.upNum = arti.upNum - 1
totalUpNum--
} else {
break
}
}
}
return ret
@ -103,16 +111,13 @@ let ArtisMark = {
if (!key || key === 'undefined') {
return {}
}
let arrCfg = attrMap[isDmg ? 'dmg' : key]
val = Format[arrCfg.format](val, 1)
let ret = {
key,
value: val
}
if (!isMain) {
if (!isMain && !ret.eff) {
let incRet = ArtisMark.getIncNum(key, value)
ret.upNum = incRet.num
ret.hasGt = incRet.hasGt
@ -166,7 +171,10 @@ let ArtisMark = {
getMark (charCfg, posIdx, mainAttr, subAttr, elem = '') {
let ret = 0
let { attrs, posMaxMark } = charCfg
let key = mainAttr.key
let key = mainAttr?.key
if (!key) {
return 0
}
let fixPct = 1
posIdx = posIdx * 1
if (posIdx >= 3) {
@ -226,6 +234,16 @@ let ArtisMark = {
let ret = []
lodash.forEach(tmp, (ds) => ret.push(ds.attr))
return ret.slice(0, maxLen)
},
hasAttr (artis) {
for (let idx = 1; idx <= 5; idx++) {
let ds = artis[idx]
if (ds && (!ds.name || !ds.main || !ds.attrs || !ds?.main?.key)) {
return false
}
}
return true
}
}

View File

@ -24,6 +24,24 @@ class AttrCalc {
return new AttrCalc(profile)
}
static calcPromote (lv) {
if (lv === 20) {
return 1
}
if (lv === 90) {
return 6
}
let lvs = [1, 20, 40, 50, 60, 70, 80, 90]
let promote = 0
for (let idx = 0; idx < lvs.length - 1; idx++) {
if (lv >= lvs[idx] && lv <= lvs[idx + 1]) {
return promote
}
promote++
}
return promote
}
/**
* 面板属性计算
* @returns {{}}
@ -37,9 +55,6 @@ class AttrCalc {
this.setCharAttr()
this.setWeaponAttr()
this.setArtisAttr()
if (process.argv.includes('web-debug')) {
// console.log(this.attr, this.attr.getAttr())
}
return this.attr.getAttr()
}
@ -103,7 +118,7 @@ class AttrCalc {
* 计算武器属性
*/
setWeaponAttr () {
let wData = this.profile?.weapon
let wData = this.profile?.weapon || {}
let weapon = Weapon.get(wData?.name)
let level = wData.level
let promote = lodash.isUndefined(wData.promote) ? -1 : wData.promote
@ -184,6 +199,9 @@ class AttrCalc {
* @returns {boolean}
*/
calcArtisAttr (ds, char) {
if (!ds) {
return false
}
let key = ds.key
if (Format.isElem(key) && char.elem === key) {
key = 'dmg'
@ -196,23 +214,6 @@ class AttrCalc {
}
this.attr.addAttr(key, ds.value * 1)
}
static calcPromote (lv) {
if (lv === 20) {
return 1
}
if (lv === 90) {
return 6
}
let lvs = [1, 20, 40, 50, 60, 70, 80, 90]
let promote = 0
for (let idx = 0; idx < lvs.length - 1; idx++) {
if (lv >= lvs[idx] && lv <= lvs[idx + 1]) {
return promote
}
promote++
}
}
}
export default AttrCalc

View File

@ -19,7 +19,7 @@
<div class="cfg-line">
{{cfgItem.title}}
<span class="cfg-hint"> #喵喵设置{{cfgItem.key}}
{{if cfgItem.type==='num'}} {{cfgItem.def}}{{else}} + 开启/关闭{{/if}}
{{if cfgItem.type==='num'}} {{cfgItem.def}}{{else}} 开启/关闭{{/if}}
</span>
{{if cfgItem.type === 'num'}}
<div class="cfg-status">{{cfg[cfgKey]}}</div>

View File

@ -0,0 +1,227 @@
.container {
width: 740px;
background-size: cover;
}
.head-box {
margin-top: 0;
}
.user-banner {
height: 90px;
background-size: auto 100%;
background-position: right center;
background-repeat: no-repeat;
background-color: #f0ece4;
border-radius: 50px;
padding: 1px;
margin: 5px 0;
display: flex;
white-space: nowrap;
position: relative;
}
.user-banner .face {
width: 70px;
height: 70px;
margin: 10px;
border-radius: 50%;
box-shadow: 0 0 1px #000, 0 0 5px rgba(0, 0, 0, 0.5);
border: 3px solid #fff;
overflow: hidden;
background: url('../common/item/bg5.png');
background-size: cover;
}
.user-banner .face span {
display: block;
width: 64px;
height: 64px;
background-size: auto 100%;
background-repeat: no-repeat;
}
.user-banner .user-info {
padding: 15px 5px;
color: #414e64;
text-shadow: 0 0 2px #f0ece4, 0 0 5px #f0ece4;
}
.user-banner .user-info .name {
height: 34px;
line-height: 34px;
}
.user-banner .user-info .name strong {
font-size: 24px;
}
.user-banner .user-info .name span {
padding-left: 5px;
}
.user-banner .user-info .uid {
height: 22px;
line-height: 22px;
font-size: 16px;
}
.user-banner .stat {
position: absolute;
right: 0;
top: 0;
display: flex;
margin: 16px;
border-radius: 29px;
height: 58px;
box-shadow: 0 0 5px 0 rgba(0, 0, 0, 0.4);
overflow: hidden;
}
.user-banner .stat-li {
padding: 7px;
width: 70px;
height: 58px;
text-align: center;
position: relative;
text-shadow: 0 0 1px #fff;
}
.user-banner .stat-li:nth-child(odd) {
background: rgba(255, 255, 255, 0.65);
}
.user-banner .stat-li:nth-child(even) {
background: rgba(220, 220, 220, 0.5);
}
.user-banner .stat-li:first-child {
width: 80px;
padding-left: 17px;
}
.user-banner .stat-li:last-child {
width: 80px;
padding-right: 17px;
}
.user-banner .stat-li strong {
font-size: 22px;
display: block;
}
.user-banner .stat-li span {
display: block;
font-size: 14px;
color: #414e64;
}
.exploration {
display: flex;
flex-wrap: wrap;
margin: 10px 0;
justify-content: center;
}
.exploration .item {
width: 93px;
height: 116.25px;
background: url('./imgs/exploration.webp') no-repeat;
background-size: auto 100%;
border-radius: 4px;
margin: 3px;
text-align: center;
color: #fff;
align-items: center;
}
.exploration .item strong {
font-size: 22px;
display: block;
height: 30px;
line-height: 30px;
text-shadow: 0 0 1px #000, 1px 1px 3px rgba(0, 0, 0, 0.5);
font-weight: normal;
}
.exploration .item span {
margin-top: 58px;
font-size: 14px;
height: 20px;
line-height: 20px;
display: block;
text-shadow: 0 0 1px rgba(0, 0, 0, 0.5);
}
.chest-list {
margin: 10px 0;
overflow: hidden;
background: rgba(0, 0, 0, 0.6);
padding: 0 15px;
display: flex;
justify-content: center;
}
.chest-list .chest {
width: 20%;
display: flex;
padding: 15px 0;
}
.chest-list .chest:nth-child(even) {
background: rgba(50, 50, 50, 0.5);
}
.chest-list .chest .value {
font-size: 24px;
line-height: 40px;
height: 40px;
padding-right: 8px;
text-align: right;
width: 70px;
text-shadow: 0 0 1px #000, 1px 1px 3px rgba(0, 0, 0, 0.5);
}
.chest-list .chest .detail {
width: 60px;
height: 40px;
font-size: 14px;
}
.chest-list .chest .info {
display: flex;
height: 20px;
line-height: 20px;
}
.chest-list .chest .icon {
display: inline-block;
width: 18px;
height: 18px;
margin: 1px;
background: url('./imgs/chest.webp') no-repeat;
background-size: auto 100%;
vertical-align: center;
}
.chest-list .chest .max {
padding-left: 3px;
color: #aaa;
}
.chest-list .chest .title {
height: 20px;
display: flex;
color: #d3bc8e;
}
.cont-title {
padding: 8px 15px;
}
.avatar-cont-main {
overflow: hidden;
}
.avatar-cont {
background: rgba(0, 0, 0, 0.5);
padding: 0;
margin: 10px 0;
}
.avatar-list {
display: flex;
flex-wrap: wrap;
padding: 8px;
border-radius: 10px;
}
.avatar-list .avatar-card {
margin: 5px;
}
.cont-notice {
text-align: right;
}
.cont-notice strong {
color: #d3bc8e;
}
.cont-notice span {
padding: 0 3px;
color: #aaa;
}
.ck-notice {
text-align: center;
color: rgba(255, 255, 255, 0.9);
font-size: 13px;
}
.ck-notice strong {
color: #d3bc8e;
padding: 0 2px;
font-weight: normal;
}
/*# sourceMappingURL=avatar-list.css.map */

View File

@ -0,0 +1,101 @@
{{extend elemLayout}}
{{block 'css'}}
<link rel="stylesheet" type="text/css" href="{{_res_path}}/character/avatar-list.css"/>
<link rel="stylesheet" type="text/css" href="{{_res_path}}/common/tpl.css?v=1.0"/>
{{/block}}
{{block 'main'}}
<div class="user-banner" style="background-image:url({{_res_path}}{{face?.banner}})">
<div class="face">
<span style="background-image:url({{_res_path}}{{face?.face}})"></span>
</div>
<div class="user-info">
<div class="name">
<strong>{{face.name}}</strong>
{{if face.level && face.level > 1}} Lv.{{face.level}}{{/if}}
</div>
<div class="uid">
{{if uid}}<span> #{{uid}}</span>{{/if}}
{{ if info.activeDay}}{{info.activeDay}} {{/if}}
</div>
</div>
{{if info && info.stats}}
<div class="stat">
{{each info?.statMap title key}}
{{if info.stats[key] }}
<div class="stat-li">
<strong>{{info.stats[key]}}</strong>
<span>{{title}}</span>
</div>
{{/if}}
{{/each}}
</div>
{{/if}}
</div>
{{if !isSelfCookie}}
<div class="ck-notice">未绑定CK或CK失效信息可能不完全。发送<strong>#体力帮助</strong>查看CK绑定方法发送<strong>#更新面板</strong>更新游戏内角色展柜信息</div>
{{/if}}
{{if info && info.exploration && info.exploration['蒙德']}}
{{set citys = ['蒙德','龙脊雪山','璃月','层岩巨渊','稻妻','渊下宫','须弥'] }}
<div class="exploration">
{{each citys city idx}}
<div class="item city-{{idx+1}}" style="background-position:{{idx}}0% 0">
<span>{{city}}</span>
<strong>{{ (info.exploration[city]||0)/10 || '0'}}%</strong>
</div>
{{/each}}
</div>
{{/if}}
{{if info?.stats?.commonChest}}
{{set ds = info?.stats}}
<div class="cont chest-list">
{{each info?.chestMap cfg idx}}
<div class="chest">
<div class="value">{{ds[cfg.key]}}</div>
<div class="detail">
<div class="info">
<div class="icon" style="background-position:{{idx*2}}0% 0"></div>
<div class="max">{{cfg.max>ds[cfg.key]?cfg.max:ds[cfg.key]}}</div>
</div>
<div class="title">{{cfg.title}}</div>
</div>
</div>
{{/each}}
</div>
{{/if}}
<div class="cont avatar-cont">
<div class="cont-title">角色列表</div>
<div class="avatar-list">
{{each avatars avatar idx}}
<% include(_layout_path+'../tpl/avatar-card.html', [avatar,{_res_path, cardType:'mini'}]) %>
{{/each}}
</div>
<div class="cont-footer 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>
{{/block}}

View File

@ -0,0 +1,276 @@
.container {
width: 740px;
background-size: cover;
}
.head-box {
margin-top: 0;
}
.user-banner {
height: 90px;
background-size: auto 100%;
background-position: right center;
background-repeat: no-repeat;
background-color: #f0ece4;
border-radius: 50px;
padding: 1px;
margin: 5px 0;
display: flex;
white-space: nowrap;
position: relative;
.face {
width: 70px;
height: 70px;
margin: 10px;
border-radius: 50%;
box-shadow: 0 0 1px #000, 0 0 5px rgba(0, 0, 0, .5);
border: 3px solid #fff;
overflow: hidden;
background: url('../common/item/bg5.png');
background-size: cover;
span {
display: block;
width: 64px;
height: 64px;
background-size: auto 100%;
background-repeat: no-repeat;
}
}
.user-info {
padding: 15px 5px;
color: #414e64;
text-shadow: 0 0 2px #f0ece4, 0 0 5px #f0ece4;
.name {
height: 34px;
line-height: 34px;
strong {
font-size: 24px;
}
span {
padding-left: 5px;
}
}
.uid {
height: 22px;
line-height: 22px;
font-size: 16px;
}
}
.stat {
position: absolute;
right: 0;
top: 0;
display: flex;
margin: 16px;
border-radius: 29px;
height: 58px;
box-shadow: 0 0 5px 0 rgba(0, 0, 0, .4);
overflow: hidden;
}
.stat-li {
@width: 70px;
@padding: 10px;
padding: 7px;
width: @width;
height: 58px;
text-align: center;
position: relative;
text-shadow: 0 0 1px #fff;
&:nth-child(odd) {
background: rgba(255, 255, 255, .65);
}
&:nth-child(even) {
background: rgba(220, 220, 220, .5);
}
&:first-child {
width: @width+@padding;
padding-left: 7px+@padding;
}
&:last-child {
width: @width+@padding;
padding-right: 7px+@padding;
}
strong {
font-size: 22px;
display: block;
}
span {
display: block;
font-size: 14px;
color: #414e64;
}
}
}
.exploration {
display: flex;
flex-wrap: wrap;
margin: 10px 0;
justify-content: center;
.item {
width: 93px;
height: 93*1.25px;
background: url('./imgs/exploration.webp') no-repeat;
background-size: auto 100%;
border-radius: 4px;
margin: 3px;
text-align: center;
color: #fff;
align-items: center;
strong {
font-size: 22px;
display: block;
height: 30px;
line-height: 30px;
text-shadow: 0 0 1px #000, 1px 1px 3px rgba(0, 0, 0, .5);
font-weight: normal;
}
span {
margin-top: 58px;
font-size: 14px;
height: 20px;
line-height: 20px;
display: block;
text-shadow: 0 0 1px rgba(0, 0, 0, .5);
}
}
}
.chest-list {
margin: 10px 0;
overflow: hidden;
background: rgba(0, 0, 0, .6);
padding: 0 15px;
display: flex;
justify-content: center;
.chest {
width: 20%;
display: flex;
padding: 15px 0;
&:nth-child(even) {
background: rgba(50, 50, 50, .5);
}
.value {
font-size: 24px;
line-height: 40px;
height: 40px;
padding-right: 8px;
text-align: right;
width: 70px;
text-shadow: 0 0 1px #000, 1px 1px 3px rgba(0, 0, 0, .5);
}
.detail {
width: 60px;
height: 40px;
font-size: 14px;
}
.info {
display: flex;
height: 20px;
line-height: 20px;
}
.icon {
display: inline-block;
width: 18px;
height: 18px;
margin: 1px;
background: url('./imgs/chest.webp') no-repeat;
background-size: auto 100%;
vertical-align: center;
}
.max {
padding-left: 3px;
color: #aaa;
}
.title {
height: 20px;
display: flex;
color: #d3bc8e;
}
}
}
.cont-title {
padding: 8px 15px;
}
.avatar-cont-main {
overflow: hidden;
}
.avatar-cont {
background: rgba(0, 0, 0, .5);
padding: 0;
margin: 10px 0;
}
.avatar-list {
display: flex;
flex-wrap: wrap;
padding: 8px;
border-radius: 10px;
.avatar-card {
margin: 5px;
}
}
.cont-notice {
text-align: right;
strong {
color: #d3bc8e;
}
span {
padding: 0 3px;
color: #aaa;
}
}
.ck-notice {
text-align: center;
color: rgba(255, 255, 255, .9);
font-size: 13px;
strong {
color: #d3bc8e;
padding: 0 2px;
font-weight: normal;
}
}

View File

@ -78,7 +78,7 @@
</div>
<div class="copyright data-source">
数据源:{{data.dataSourceName}} {{data.updateTime}}
数据源:{{ {miao:'喵喵API', 'enka':'Enka.Network', mys:'米游社'}[data.source]||data.source }} {{data.updateTime}}
</div>
{{else}}
{{if custom}}

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 236 KiB

View File

@ -11,7 +11,7 @@
{{block 'main'}}
<div class="basic">
<div class="main-pic"
style="background-image:url({{_res_path}}{{imgs.splash0}})"></div>
style="background-image:url({{_res_path}}{{costumeSplash || imgs?.splash}})"></div>
<div class="detail">
<div class="char-name">{{data.name}}</div>
<div class="char-lv">UID {{uid}} - Lv.{{data.level}}
@ -56,7 +56,7 @@
<div class="data-info">
{{if data.dataSource}}
<span>数据源:{{ {miao:"喵喵Api",enka:"Enka.Network",change:"面板变换"}[data.dataSource]||data.dataSource }}</span>
<span>数据源:{{ {change:"面板变换"}[data.dataSource]||data.dataSource }}</span>
{{/if}}
{{if data.updateTime}}
<span class="time">{{data.updateTime}}</span>

View File

@ -12,7 +12,9 @@
<div class="title">#面板列表
<div class="label">UID:{{uid}}</div>
</div>
<div class="label">{{msg+", "}}更新角色时请不要出场对应角色,以获取准确面板数据</div>
{{if msg}}
<div class="label">{{msg}}</div>
{{/if}}
<div class="label">你可以使用<span>#{{demo}}面板</span><span>#{{demo}}伤害</span><span>#{{demo}}圣遗物</span>命令来查看面板信息了</div>
</div>
<div class="cont group-rank-tip {{groupRank?'has-rank':'no-rank'}}">
@ -41,7 +43,7 @@
<span class="name">{{char.abbr}}<span class="cons cons-{{char.cons}}">{{char.cons}}</span></span>
{{if char.groupRank}}
{{set gr = char.groupRank}}
{{set rank = gr.rank >= 15 ? 10:(gr.rank <=3 ? gr.rank : 4)}}
{{set rank = gr.rank >= (rankCfg.number || 15) ? 10:(gr.rank <=3 ? gr.rank : 4)}}
<div class="group-rank rank-{{rank}} rank-type-{{gr.rankType}}">
<span>{{gr.rank}}</span>
</div>
@ -55,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;
}
}

Some files were not shown because too many files have changed in this diff Show More