apps目录结构调整,面板自动更新逻辑更新

This commit is contained in:
Kokomi 2023-02-14 03:47:22 +08:00
parent a54b9c6bd4
commit f4353fc5ca
65 changed files with 1625 additions and 1511 deletions

View File

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

View File

@ -1,71 +1,36 @@
import { Common, App } from '../components/index.js' import { App } from '../components/index.js'
import { Character } from '../models/index.js'
import { renderAvatar } from './character/AvatarCard.js'
import { uploadCharacterImg } from './character/ImgUpload.js' import { uploadCharacterImg } from './character/ImgUpload.js'
import { wife, wifeReg } from './character/AvatarWife.js'
import { getOriginalPicture } from './profile/ProfileUtils.js' import { getOriginalPicture } from './profile/ProfileUtils.js'
import Avatar from './character/AvatarCard.js'
import Wife from './character/AvatarWife.js'
let app = App.init({ let app = App.init({
id: 'character', id: 'character',
name: '角色查询' name: '角色查询'
}) })
app.reg('character', character, { app.reg({
character: {
rule: /^#喵喵角色卡片$/, rule: /^#喵喵角色卡片$/,
check: checkCharacter, fn: Avatar.render,
check: Avatar.check,
name: '角色卡片' name: '角色卡片'
}) },
uploadImg: {
app.reg('upload-img', uploadCharacterImg, {
rule: /^#*(喵喵)?(上传|添加)(.+)(照片|写真|图片|图像)\s*$/, rule: /^#*(喵喵)?(上传|添加)(.+)(照片|写真|图片|图像)\s*$/,
fn: uploadCharacterImg,
name: '上传角色写真' name: '上传角色写真'
}) },
wife: {
app.reg('wife', wife, { rule: Wife.reg,
rule: wifeReg, fn: Wife.render,
describe: '#老公 #老婆 查询' describe: '#老公 #老婆 查询'
}) },
originalPic: {
app.reg('original-pic', getOriginalPicture, {
rule: /^#?(获取|给我|我要|求|发|发下|发个|发一下)?原图(吧|呗)?$/, rule: /^#?(获取|给我|我要|求|发|发下|发个|发一下)?原图(吧|呗)?$/,
fn: getOriginalPicture,
describe: '【#原图】 回复角色卡片,可获取原图' describe: '【#原图】 回复角色卡片,可获取原图'
}
}) })
export default app 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

@ -4,7 +4,14 @@ import lodash from 'lodash'
import { segment } from 'oicq' import { segment } from 'oicq'
import moment from 'moment' import moment from 'moment'
export async function renderAvatar (e, avatar, renderType = 'card') { 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') { if (typeof (avatar) === 'string') {
// 检查角色 // 检查角色
@ -26,11 +33,10 @@ export async function renderAvatar (e, avatar, renderType = 'card') {
} }
} }
} }
return await renderCard(e, avatar, renderType) return await Avatar.renderCard(e, avatar, renderType)
} },
// 渲染角色卡片 async renderCard (e, avatar, renderType = 'card') {
async function renderCard (e, avatar, renderType = 'card') {
let char = Character.get(avatar.id) let char = Character.get(avatar.id)
if (!char) { if (!char) {
return false return false
@ -85,4 +91,35 @@ async function renderCard (e, avatar, renderType = 'card') {
await redis.set(`miao:original-picture:${msgRes.message_id}`, bg.img, { EX: 3600 * 3 }) await redis.set(`miao:original-picture:${msgRes.message_id}`, bg.img, { EX: 3600 * 3 })
} }
return true 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
}
}
export default Avatar

View File

@ -2,7 +2,7 @@
import lodash from 'lodash' import lodash from 'lodash'
import { Common } from '../../components/index.js' import { Common } from '../../components/index.js'
import { Character, MysApi, Player } from '../../models/index.js' import { Character, MysApi, Player } from '../../models/index.js'
import { renderAvatar } from './AvatarCard.js' import Avatar from './AvatarCard.js'
const relationMap = { const relationMap = {
wife: { wife: {
@ -32,9 +32,31 @@ const relationMap = {
} }
const relation = lodash.flatMap(relationMap, (d) => d.keyword) 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) { async function getAvatarList (player, type) {
await player.refreshMysDetail()
let list = []
player.forEachAvatar((avatar) => {
if (type !== false) {
if (!avatar.char.checkWifeType(avatar.id, type)) {
return true
}
}
list.push(avatar)
})
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 || '' let msg = e.msg || ''
if (!msg && !e.isPoke) return false if (!msg && !e.isPoke) return false
@ -95,10 +117,10 @@ export async function wife (e) {
// 如果选择为全部,则从列表中随机选择一个 // 如果选择为全部,则从列表中随机选择一个
avatarList = await getAvatarList(player, targetCfg.type, mys) avatarList = await getAvatarList(player, targetCfg.type, mys)
let avatar = lodash.sample(avatarList) let avatar = lodash.sample(avatarList)
return renderAvatar(e, avatar, renderType) return Avatar.renderAvatar(e, avatar, renderType)
} else { } else {
// 如果指定过,则展示指定角色 // 如果指定过,则展示指定角色
return renderAvatar(e, lodash.sample(wifeList), renderType) return Avatar.renderAvatar(e, lodash.sample(wifeList), renderType)
} }
} }
} }
@ -106,7 +128,7 @@ export async function wife (e) {
avatarList = await getAvatarList(player, e.isPoke ? false : targetCfg.type, mys) avatarList = await getAvatarList(player, e.isPoke ? false : targetCfg.type, mys)
if (avatarList && avatarList.length > 0) { if (avatarList && avatarList.length > 0) {
avatar = lodash.sample(avatarList) avatar = lodash.sample(avatarList)
return await renderAvatar(e, avatar, renderType) return await Avatar.renderAvatar(e, avatar, renderType)
} }
e.reply('在当前米游社公开展示的角色中未能找到适合展示的角色..') e.reply('在当前米游社公开展示的角色中未能找到适合展示的角色..')
return true return true
@ -157,28 +179,10 @@ export async function wife (e) {
break break
} }
return true return true
},
async poke (e) {
return await Wife.render(e)
}
} }
export async function pokeWife (e, components) { export default Wife
return await wife(e, components)
}
async function getAvatarList (player, type, mys) {
await player.refreshMysDetail()
let list = []
player.forEachAvatar((avatar) => {
if (type !== false) {
if (!avatar.char.checkWifeType(avatar.id, type)) {
return true
}
}
list.push(avatar)
})
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
}

View File

@ -1,7 +1,5 @@
import lodash from 'lodash' import { App } from '../components/index.js'
import fs from 'fs' import Help from './help/Help.js'
import { Cfg, Version, Common, Data, App } from '../components/index.js'
import HelpTheme from './help/HelpTheme.js'
let app = App.init({ let app = App.init({
id: 'help', id: 'help',
@ -9,84 +7,17 @@ let app = App.init({
desc: '喵喵帮助' desc: '喵喵帮助'
}) })
app.reg('help', help, { app.reg({
help: {
rule: /^#?(喵喵)?(命令|帮助|菜单|help|说明|功能|指令|使用说明)$/, rule: /^#?(喵喵)?(命令|帮助|菜单|help|说明|功能|指令|使用说明)$/,
fn: Help.render,
desc: '【#帮助】 #喵喵帮助' desc: '【#帮助】 #喵喵帮助'
}) },
version: {
app.reg('version', versionInfo, {
rule: /^#?喵喵版本$/, rule: /^#?喵喵版本$/,
fn: Help.version,
desc: '【#帮助】 喵喵版本介绍' desc: '【#帮助】 喵喵版本介绍'
}
}) })
export default app 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 { App } from '../components/index.js'
import { pokeWife } from './character/AvatarWife.js' import Wife from './character/AvatarWife.js'
let app = App.init({ let app = App.init({
id: 'poke', id: 'poke',
@ -7,8 +7,11 @@ let app = App.init({
event: 'poke' event: 'poke'
}) })
app.reg('pock-wife', pokeWife, { app.reg({
pockWife: {
fn: Wife.poke,
describe: '#老公 #老婆 查询' describe: '#老公 #老婆 查询'
}
}) })
export default app export default app

View File

@ -1,9 +1,9 @@
import { App } from '../components/index.js' import { App } from '../components/index.js'
import { getProfile, profileHelp } from './profile/ProfileCommon.js' import { profileHelp } from './profile/ProfileCommon.js'
import { profileArtisList } from './profile/ProfileArtis.js' import { profileArtisList } from './profile/ProfileArtis.js'
import { profileDetail } from './profile/ProfileDetail.js' import { profileDetail } from './profile/ProfileDetail.js'
import { profileStat } from './profile/ProfileStat.js' import { profileStat } from './profile/ProfileStat.js'
import { profileList } from './profile/ProfileList.js' import ProfileList from './profile/ProfileList.js'
import { uploadCharacterImg, delProfileImg, profileImgList } from './character/ImgUpload.js' import { uploadCharacterImg, delProfileImg, profileImgList } from './character/ImgUpload.js'
import { enemyLv } from './profile/ProfileUtils.js' import { enemyLv } from './profile/ProfileUtils.js'
import { groupRank, resetRank, refreshRank, manageRank } from './profile/ProfileRank.js' import { groupRank, resetRank, refreshRank, manageRank } from './profile/ProfileRank.js'
@ -12,86 +12,110 @@ let app = App.init({
id: 'profile', id: 'profile',
name: '角色面板' name: '角色面板'
}) })
app.reg('profile-detail', profileDetail, {
app.reg({
profileDetail: {
rule: /^#*([^#]+)\s*(详细|详情|面板|面版|圣遗物|伤害[1-7]?)\s*(\d{9})*(.*[换变改].*)?$/, rule: /^#*([^#]+)\s*(详细|详情|面板|面版|圣遗物|伤害[1-7]?)\s*(\d{9})*(.*[换变改].*)?$/,
fn: profileDetail,
name: '角色面板' name: '角色面板'
}) },
app.reg('profile-change', profileDetail, {
profileChange: {
rule: /^#.+换.+$/, rule: /^#.+换.+$/,
fn: profileDetail,
name: '角色面板计算' name: '角色面板计算'
}) },
app.reg('group-profile', groupRank, { groupProfile: {
rule: /^#(群|群内)?(排名|排行)?(最强|最高|最高分|最牛|第一)+.+/, rule: /^#(群|群内)?(排名|排行)?(最强|最高|最高分|最牛|第一)+.+/,
fn: groupRank,
name: '群内最强' name: '群内最强'
}) },
app.reg('reset-rank', resetRank, { resetRank: {
rule: /^#(重置|重设)(.*)(排名|排行)$/, rule: /^#(重置|重设)(.*)(排名|排行)$/,
fn: resetRank,
name: '重置排名' name: '重置排名'
}) },
app.reg('refresh-rank', refreshRank, { refreshRank: {
rule: /^#(刷新|更新|重新加载)(群内|群|全部)*(排名|排行)$/, rule: /^#(刷新|更新|重新加载)(群内|群|全部)*(排名|排行)$/,
fn: refreshRank,
name: '重置排名' name: '重置排名'
}) },
app.reg('manage-rank', manageRank, { manageRank: {
rule: /^#(开启|打开|启用|关闭|禁用)(群内|群|全部)*(排名|排行)$/, rule: /^#(开启|打开|启用|关闭|禁用)(群内|群|全部)*(排名|排行)$/,
fn: manageRank,
name: '打开关闭' name: '打开关闭'
}) },
app.reg('rank-list', groupRank, { rankList: {
rule: /^#(群|群内)?.+(排名|排行)(榜)?$/, rule: /^#(群|群内)?.+(排名|排行)(榜)?$/,
fn: groupRank,
name: '面板排名榜' name: '面板排名榜'
}) },
app.reg('artis-list', profileArtisList, { artisList: {
rule: /^#圣遗物列表\s*(\d{9})?$/, rule: /^#圣遗物列表\s*(\d{9})?$/,
fn: profileArtisList,
name: '面板圣遗物列表' name: '面板圣遗物列表'
}) },
app.reg('profile-list', profileList, { profileList: {
rule: /^#(面板角色|角色面板|面板)(列表)?\s*(\d{9})?$/, rule: /^#(面板角色|角色面板|面板)(列表)?\s*(\d{9})?$/,
fn: ProfileList.render,
name: '面板角色列表', name: '面板角色列表',
desc: '查看当前已获取面板数据的角色列表' desc: '查看当前已获取面板数据的角色列表'
}) },
app.reg('profile-stat', profileStat, { profileStat: {
rule: /^#(面板|喵喵|角色|武器|天赋|技能|圣遗物)?练度统计$/, rule: /^#(面板|喵喵|角色|武器|天赋|技能|圣遗物)?练度统计$/,
fn: profileStat,
name: '面板练度统计' name: '面板练度统计'
}) },
app.reg('avatar-list', profileStat, {
avatarList: {
rule: /^(#(角色|查询|查询角色|角色查询|人物)[ |0-9]*$)|(^(#*uid|#*UID)\+*[1|2|5-9][0-9]{8}$)|(^#[\+|]*[1|2|5-9][0-9]{8})/, rule: /^(#(角色|查询|查询角色|角色查询|人物)[ |0-9]*$)|(^(#*uid|#*UID)\+*[1|2|5-9][0-9]{8}$)|(^#[\+|]*[1|2|5-9][0-9]{8})/,
fn: profileStat,
name: '角色查询' name: '角色查询'
}) },
app.reg('profile-help', profileHelp, { profileHelp: {
rule: /^#(角色|换|更换)?面[板版]帮助$/, rule: /^#(角色|换|更换)?面[板版]帮助$/,
fn: profileHelp,
name: '角色面板帮助' name: '角色面板帮助'
}) },
app.reg('enemy-lv', enemyLv, { enemyLv: {
rule: /^#(敌人|怪物)等级\s*\d{1,3}\s*$/, rule: /^#(敌人|怪物)等级\s*\d{1,3}\s*$/,
fn: enemyLv,
describe: '【#角色】 设置伤害计算中目标敌人的等级' describe: '【#角色】 设置伤害计算中目标敌人的等级'
}) },
app.reg('profile-refresh', getProfile, { profileRefresh: {
rule: /^#(全部面板更新|更新全部面板|获取游戏角色详情|更新面板|面板更新)\s*(\d{9})?$/, rule: /^#(全部面板更新|更新全部面板|获取游戏角色详情|更新面板|面板更新)\s*(\d{9})?$/,
fn: ProfileList.refresh,
describe: '【#角色】 获取游戏橱窗详情数据' describe: '【#角色】 获取游戏橱窗详情数据'
}) },
app.reg('upload-img', uploadCharacterImg, { uploadImg: {
rule: /^#?\s*(?:上传|添加)(.+)(?:面板图)\s*$/, rule: /^#?\s*(?:上传|添加)(.+)(?:面板图)\s*$/,
fn: uploadCharacterImg,
describe: '【#上传刻晴面板图】 上传角色面板图' describe: '【#上传刻晴面板图】 上传角色面板图'
}) },
app.reg('del-profile', delProfileImg, {
delProfile: {
rule: /^#?\s*(?:移除|清除|删除)(.+)(?:面板图)(\d){1,}\s*$/, rule: /^#?\s*(?:移除|清除|删除)(.+)(?:面板图)(\d){1,}\s*$/,
fn: delProfileImg,
describe: '【#删除刻晴面板图1】 删除指定角色面板图(序号)' describe: '【#删除刻晴面板图1】 删除指定角色面板图(序号)'
}) },
app.reg('profile-img-list', profileImgList, {
profileImgList: {
rule: /^#?\s*(.+)(?:面板图列表)\s*$/, rule: /^#?\s*(.+)(?:面板图列表)\s*$/,
fn: profileImgList,
describe: '【#刻晴面板图列表】 删除指定角色面板图(序号)' describe: '【#刻晴面板图列表】 删除指定角色面板图(序号)'
}
}) })
export default app export default app

View File

@ -4,7 +4,7 @@
* */ * */
import lodash from 'lodash' import lodash from 'lodash'
import { Cfg, Common } from '../../components/index.js' import { Cfg, Common } from '../../components/index.js'
import { getTargetUid, profileHelp, autoGetProfile } from './ProfileCommon.js' import { getTargetUid, profileHelp, getProfileRefresh } from './ProfileCommon.js'
import { Artifact, Character, ProfileArtis, Player } from '../../models/index.js' import { Artifact, Character, ProfileArtis, Player } from '../../models/index.js'
/* /*
@ -12,26 +12,15 @@ import { Artifact, Character, ProfileArtis, Player } from '../../models/index.js
* */ * */
export async function profileArtis (e) { export async function profileArtis (e) {
let { uid, avatar } = e let { uid, avatar } = e
let profile = e._profile || await getProfileRefresh(e, avatar)
let profile if (!profile) {
if (e._profile) { return true
profile = e._profile
} else {
let autoRet = await autoGetProfile(e, uid, avatar, async () => {
await profileArtis(e)
})
if (autoRet.err) {
return false
} }
profile = autoRet.profile
}
let char = profile.char
if (!profile.hasArtis()) { if (!profile.hasArtis()) {
e.reply('未能获得圣遗物详情,请重新获取面板信息后查看') e.reply('未能获得圣遗物详情,请重新获取面板信息后查看')
return true return true
} }
let char = profile.char
let charCfg = profile.artis.getCharCfg() let charCfg = profile.artis.getCharCfg()
let { attrMap } = Artifact.getMeta() let { attrMap } = Artifact.getMeta()

View File

@ -1,9 +1,7 @@
/* /*
* 面板公共方法及处理 * 面板公共方法及处理
* */ * */
import lodash from 'lodash'
import { segment } from 'oicq' import { segment } from 'oicq'
import { profileList } from './ProfileList.js'
import { Version } from '../../components/index.js' import { Version } from '../../components/index.js'
import { Character, MysApi, Player } from '../../models/index.js' import { Character, MysApi, Player } from '../../models/index.js'
@ -57,7 +55,7 @@ const _getTargetUid = async function (e) {
return false return false
} }
uid = user.uid uid = user.uid
if (!uid || !uidReg.test(uid)) { if ((!uid || !uidReg.test(uid)) && !e._replyNeedUid) {
e.reply('请先发送【#绑定+你的UID】来绑定查询目标') e.reply('请先发送【#绑定+你的UID】来绑定查询目标')
return false return false
} }
@ -75,114 +73,24 @@ export async function getTargetUid (e) {
return uid return uid
} }
/* export async function getProfileRefresh (e, avatar) {
* 自动更新面板数据
* */
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 player = Player.create(e)
let data = await player.refreshProfile()
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 autoGetProfile (e, uid, avatar, callback) {
let refresh = async () => {
let refreshRet = await autoRefresh(e)
if (refreshRet) {
await callback()
}
return refreshRet
}
let char = Character.get(avatar) let char = Character.get(avatar)
if (!char) { if (!char) {
return { err: true } return false
} }
let player = Player.create(e) let player = Player.create(e)
let profile = player.getProfile(char.id) let profile = player.getProfile(char.id)
if (!profile || !profile.hasData) { if (!profile || !profile.hasData) {
if (await refresh()) { logger.mark(`本地无UID:${player.uid}${char.name}面板数据,尝试自动请求...`)
return { err: true } await player.refresh({ profile: true })
} else { profile = player.getProfile(char.id)
}
if (!profile || !profile.hasData) {
e.reply(`请确认${char.name}已展示在【游戏内】的角色展柜中,并打开了“显示角色详情”。然后请使用 #更新面板\n命令来获取${char.name}的面板详情`) e.reply(`请确认${char.name}已展示在【游戏内】的角色展柜中,并打开了“显示角色详情”。然后请使用 #更新面板\n命令来获取${char.name}的面板详情`)
return false
} }
return { err: true } return profile
}
return { profile, char, refresh }
}
/*
* 面板数据更新
* */
export async function getProfile (e) {
let uid = await getTargetUid(e)
if (!uid) {
return true
}
// 数据更新
let player = Player.create(e)
let ret = await player.refreshProfile()
if (!ret) {
return true
}
if (!player._update.length === 0) {
e.reply('获取角色面板数据失败请确认角色已在游戏内橱窗展示并开放了查看详情。设置完毕后请5分钟后再进行请求~')
} else {
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(e)
}
}
return true
} }
/* /*

View File

@ -1,5 +1,6 @@
import lodash from 'lodash' import lodash from 'lodash'
import { autoRefresh, getProfile, getTargetUid } from './ProfileCommon.js' import { getTargetUid, getProfileRefresh } from './ProfileCommon.js'
import ProfileList from './ProfileList.js'
import { Cfg, Common, Format } from '../../components/index.js' import { Cfg, Common, Format } from '../../components/index.js'
import { MysApi, ProfileRank, ProfileArtis, Player, Character } from '../../models/index.js' import { MysApi, ProfileRank, ProfileArtis, Player, Character } from '../../models/index.js'
import ProfileChange from './ProfileChange.js' import ProfileChange from './ProfileChange.js'
@ -93,7 +94,7 @@ export async function profileDetail (e) {
if (mode === 'profile' || mode === 'dmg') { if (mode === 'profile' || mode === 'dmg') {
return renderProfile(e, char, mode, { dmgIdx }) return renderProfile(e, char, mode, { dmgIdx })
} else if (mode === 'refresh') { } else if (mode === 'refresh') {
await getProfile(e) await ProfileList.refresh(e)
return true return true
} else if (mode === 'artis') { } else if (mode === 'artis') {
return profileArtis(e) return profileArtis(e)
@ -116,23 +117,8 @@ export async function renderProfile (e, char, mode = 'profile', params = {}) {
return true return true
} }
let player = Player.create(uid) let profile = e._profile || await getProfileRefresh(e, char.id)
let profile = e._profile || player.getProfile(char.id) if (!profile) {
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}的面板详情`)
}
return true return true
} }
char = profile.char || char char = profile.char || char

View File

@ -1,9 +1,40 @@
import lodash from 'lodash' import lodash from 'lodash'
import { autoRefresh, getTargetUid } from './ProfileCommon.js' import { getTargetUid } from './ProfileCommon.js'
import { ProfileRank, Player } from '../../models/index.js' import { ProfileRank, Player, Character } from '../../models/index.js'
import { Common, Data } from '../../components/index.js' import { Common, Data } from '../../components/index.js'
export async function profileList (e) { const ProfileList = {
async refresh (e) {
let uid = await getTargetUid(e)
if (!uid) {
return true
}
// 数据更新
let player = Player.create(e)
await player.refreshProfile(2)
if (!player?._update?.length) {
e.reply('获取角色面板数据失败请确认角色已在游戏内橱窗展示并开放了查看详情。设置完毕后请5分钟后再进行请求~')
} else {
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
},
async render (e) {
let uid = await getTargetUid(e) let uid = await getTargetUid(e)
if (!uid) { if (!uid) {
return true return true
@ -27,8 +58,16 @@ export async function profileList (e) {
} }
const cfg = await Data.importCfg('cfg') const cfg = await Data.importCfg('cfg')
// 获取面板数据 // 获取面板数据
let player = Player.create(uid) 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 profiles = player.getProfiles()
// 检测标志位 // 检测标志位
let qq = (e.at && !e.atBot) ? e.at : e.qq let qq = (e.at && !e.atBot) ? e.at : e.qq
await ProfileRank.setUidInfo({ uid, profiles, qq, uidType: isSelfUid ? 'ck' : 'bind' }) await ProfileRank.setUidInfo({ uid, profiles, qq, uidType: isSelfUid ? 'ck' : 'bind' })
@ -39,11 +78,8 @@ export async function profileList (e) {
} }
const rankCfg = await ProfileRank.getGroupCfg(groupId) const rankCfg = await ProfileRank.getGroupCfg(groupId)
const groupRank = rank && (cfg?.diyCfg?.groupRank || false) && rankCfg.status !== 1 const groupRank = rank && (cfg?.diyCfg?.groupRank || false) && rankCfg.status !== 1
await player.forEachAvatarAsync(async function (avatar) { for (let id in profiles) {
let profile = avatar.getProfile() let profile = profiles[id]
if (!profile) {
return true
}
let char = profile.char let char = profile.char
let tmp = char.getData('id,face,name,abbr,element,star') let tmp = char.getData('id,face,name,abbr,element,star')
tmp.face = char.getImgs(profile.costume).face tmp.face = char.getImgs(profile.costume).face
@ -58,16 +94,6 @@ export async function profileList (e) {
tmp.groupRank = await rank.getRank(profile, !!tmp.isNew) tmp.groupRank = await rank.getRank(profile, !!tmp.isNew)
} }
chars.push(tmp) chars.push(tmp)
})
if (chars.length === 0) {
if (await autoRefresh(e)) {
await profileList(e)
return true
} else {
e.reply(`本地暂无uid${uid}的面板数据...`)
}
return true
} }
if (newCount > 0) { if (newCount > 0) {
@ -91,3 +117,5 @@ export async function profileList (e) {
rankCfg rankCfg
}, { e, scale: 1.6 }) }, { e, scale: 1.6 })
} }
}
export default ProfileList

View File

@ -26,6 +26,8 @@ export async function profileStat (e) {
await player.refreshMysInfo() await player.refreshMysInfo()
let avatarRet = await player.refreshAndGetAvatarData({ let avatarRet = await player.refreshAndGetAvatarData({
detail: 1,
talent: 0,
rank: true, rank: true,
retType: 'array', retType: 'array',
sort: true sort: true

View File

@ -2,508 +2,36 @@
* 胡桃数据库的统计 * 胡桃数据库的统计
* *
* */ * */
import lodash from 'lodash' import { App } from '../components/index.js'
import { Cfg, Common, App, Data } from '../components/index.js' import { ConsStat, AbyssPct } from './stat/AbyssStat.js'
import { Abyss, Character, MysApi, Player } from '../models/index.js' import { AbyssTeam } from './stat/AbyssTeam.js'
import HutaoApi from './wiki/HutaoApi.js' import { AbyssSummary } from './stat/AbyssSummary.js'
let app = App.init({ let app = App.init({
id: 'stat', id: 'stat',
name: '深渊统计' name: '深渊统计'
}) })
app.reg('cons-stat', consStat, { app.reg({
consStat: {
rule: /^#(喵喵)?角色(持有|持有率|命座|命之座|.命)(分布|统计|持有|持有率)?$/, rule: /^#(喵喵)?角色(持有|持有率|命座|命之座|.命)(分布|统计|持有|持有率)?$/,
fn: ConsStat,
desc: '【#统计】 #角色持有率 #角色5命统计' desc: '【#统计】 #角色持有率 #角色5命统计'
}) },
app.reg('abyss-pct', abyssPct, { abyssPct: {
rule: /^#(喵喵)?深渊(第?.{1,2}层)?(角色)?(出场|使用)(率|统计)*$/, rule: /^#(喵喵)?深渊(第?.{1,2}层)?(角色)?(出场|使用)(率|统计)*$/,
fn: AbyssPct,
desc: '【#统计】 #深渊出场率 #深渊12层出场率' desc: '【#统计】 #深渊出场率 #深渊12层出场率'
}) },
app.reg('abyss-team', abyssTeam, { abyssTeam: {
rule: /#深渊(组队|配队)/, rule: /#深渊(组队|配队)/,
fn: AbyssTeam,
describe: '【#角色】 #深渊组队' describe: '【#角色】 #深渊组队'
}) },
app.reg('upload-data', uploadData, { abyssSummary: {
rule: /^#*(喵喵|上传|本期)*(深渊|深境|深境螺旋)[ |0-9]*(数据)?$/, rule: /^#*(喵喵|上传|本期)*(深渊|深境|深境螺旋)[ |0-9]*(数据)?$/,
fn: AbyssSummary,
desc: '上传深渊' desc: '上传深渊'
}
}) })
export default app 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 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 })
}
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 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.sName)
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
}

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 { App } from '../components/index.js'
import lodash from 'lodash'
import Calendar from './wiki/Calendar.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' import CharWiki from './wiki/CharWiki.js'
let wikiReg = /^(?:#|喵喵)?(.*)(天赋|技能|命座|命之座|资料|图鉴|照片|写真|图片|图像)$/
let app = App.init({ let app = App.init({
id: 'wiki', id: 'wiki',
name: '角色资料' name: '角色资料'
}) })
app.reg('wiki', wiki, { app.reg({
wiki: {
rule: '^#喵喵WIKI$', rule: '^#喵喵WIKI$',
check: checkCharacter, check: CharWiki.check,
fn: CharWiki.wiki,
desc: '【#资料】 #神里天赋 #夜兰命座' desc: '【#资料】 #神里天赋 #夜兰命座'
}) },
app.reg('calendar', calendar, { calendar: {
rule: /^(#|喵喵)+(日历|日历列表)$/, rule: /^(#|喵喵)+(日历|日历列表)$/,
fn: Calendar.render,
desc: '【#日历】 活动日历' desc: '【#日历】 活动日历'
}
}) })
export default app 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 fetch from 'node-fetch'
import moment from 'moment' import moment from 'moment'
import { Character, Material } from '../../models/index.js' import { Character, Material } from '../../models/index.js'
import { Data } from '../../components/index.js' import { Common, Data } from '../../components/index.js'
import lodash from 'lodash' import lodash from 'lodash'
const ignoreIds = [495, // 有奖问卷调查开启! const ignoreIds = [495, // 有奖问卷调查开启!
@ -397,6 +397,19 @@ let Cal = {
nowTime: now.format('YYYY-MM-DD HH:mm'), nowTime: now.format('YYYY-MM-DD HH:mm'),
nowDate: now.date() 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 lodash from 'lodash'
import { Format } from '../../components/index.js' import CharWikiData from './CharWikiData.js'
import { ArtifactSet, Weapon } from '../../models/index.js' import CharMaterial from './CharMaterial.js'
let CharWiki = { const wikiReg = /^(?:#|喵喵)?(.*)(天赋|技能|命座|命之座|资料|图鉴|照片|写真|图片|图像)$/
/**
* 角色命座持有 const CharWiki = {
* @param id check (e) {
* @returns {Promise<{}>} let msg = e.original_msg || e.msg
*/ if (!e.msg) {
async getHolding (id) { return false
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 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 wiki (e) {
* 角色武器圣遗物使用 let mode = e.wikiMode
* @param id let char = e.char
* @returns {Promise<{}|{artis: *[], weapons: *[]}>}
*/ if (mode === 'pic') {
async getUsage (id) { let img = char.getCardImg(Cfg.get('charPicSe', false), false)
let ud = (await HutaoApi.getUsage()).data || {} if (img && img.img) {
if (!ud[id]) { e.reply(segment.image(process.cwd() + '/plugins/miao-plugin/resources/' + img.img))
return {} } else {
e.reply('暂无图片')
} }
ud = ud[id] return true
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)
}, },
/** async render ({ e, char }) {
* 武器使用 let data = char.getData()
* @param data lodash.extend(data, char.getData('weaponTypeName,elemName'))
* @returns {*[]} // 命座持有
*/ let holding = await CharWikiData.getHolding(char.id)
getWeaponsData (data = []) { let usage = await CharWikiData.getUsage(char.id)
let weapons = [] return await Common.render('wiki/character-wiki', {
data,
lodash.forEach(data, (ds) => { attr: char.getAttrList(),
let weapon = Weapon.get(ds.item) || {} detail: char.getDetail(),
weapons.push({ imgs: char.getImgs(),
...weapon.getData('name,abbr,img,star'), holding,
value: ds.rate usage,
}) materials: char.getMaterials(),
}) elem: char.elem
}, { e, scale: 1.4 })
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 CharWiki 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

@ -9,11 +9,17 @@ class App {
} }
reg (key, fn, cfg = {}) { reg (key, fn, cfg = {}) {
if (lodash.isPlainObject(key)) {
lodash.forEach(key, (cfg, k) => {
this.reg(k, cfg.fn, cfg)
})
} else {
this.apps[key] = { this.apps[key] = {
fn, fn,
...cfg ...cfg
} }
} }
}
// 获取v3执行方法 // 获取v3执行方法
v3App () { v3App () {

View File

@ -5,7 +5,7 @@ import lodash from 'lodash'
import Base from './Base.js' import Base from './Base.js'
import { Artifact, ArtifactSet } from './index.js' import { Artifact, ArtifactSet } from './index.js'
import { Format, Data } from '../components/index.js' import { Format, Data } from '../components/index.js'
import ArtisMark from './profile-lib/ArtisMark.js' import ArtisMark from './profile/ArtisMark.js'
export default class AvatarArtis extends Base { export default class AvatarArtis extends Base {
constructor (charid = 0) { constructor (charid = 0) {

View File

@ -3,8 +3,8 @@ import Base from './Base.js'
import moment from 'moment' import moment from 'moment'
import { Character, AvatarArtis, ProfileData, Weapon } from './index.js' import { Character, AvatarArtis, ProfileData, Weapon } from './index.js'
import { Data } from '../components/index.js' import { Data } from '../components/index.js'
import AttrCalc from './profile-lib/AttrCalc.js' import AttrCalc from './profile/AttrCalc.js'
import Profile from './player-lib/Profile.js' import Profile from './player/Profile.js'
const charKey = 'name,abbr,sName,star,imgs,face,side,gacha,weaponTypeName'.split(',') const charKey = 'name,abbr,sName,star,imgs,face,side,gacha,weaponTypeName'.split(',')
@ -29,6 +29,10 @@ export default class AvatarData extends Base {
return this.char?.name || '' return this.char?.name || ''
} }
get hasData () {
return !!(this.level > 1 || this?.weapon?.name || this?.talent?.a)
}
// 是否是合法面板数据 // 是否是合法面板数据
get isProfile () { get isProfile () {
return Profile.isProfile(this) return Profile.isProfile(this)

View File

@ -7,11 +7,11 @@
import lodash from 'lodash' import lodash from 'lodash'
import Base from './Base.js' import Base from './Base.js'
import { Data, Format } from '../components/index.js' import { Data, Format } from '../components/index.js'
import CharImg from './character-lib/CharImg.js' import CharImg from './character/CharImg.js'
import CharTalent from './character-lib/CharTalent.js' import CharTalent from './character/CharTalent.js'
import CharId from './character-lib/CharId.js' import CharId from './character/CharId.js'
import CharMeta from './character-lib/CharMeta.js' import CharMeta from './character/CharMeta.js'
import CharCfg from './character-lib/CharCfg.js' import CharCfg from './character/CharCfg.js'
let { wifeMap, idSort, idMap } = CharId let { wifeMap, idSort, idMap } = CharId

View File

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

View File

@ -12,6 +12,26 @@ export default class MysApi {
e.isSelfCookie = this.isSelfCookie 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') { static async init (e, auth = 'all') {
if (!e.runtime) { if (!e.runtime) {
Version.runtime() Version.runtime()
@ -50,31 +70,12 @@ export default class MysApi {
if (uid) { if (uid) {
return new User({ id: e.user_id, uid }) return new User({ id: e.user_id, uid })
} else { } else {
e.reply('请先#绑定uid') e.reply('请先发送【#绑定+你的UID】来绑定查询目标')
e._replyNeedUid = true
return false 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 = {}) { async getMysApi (e, targetType = 'all', option = {}) {
if (this.mys) { if (this.mys) {
return this.mys return this.mys

View File

@ -9,8 +9,8 @@ import Base from './Base.js'
import { Data } from '../components/index.js' import { Data } from '../components/index.js'
import { AvatarData, ProfileRank, Character } from './index.js' import { AvatarData, ProfileRank, Character } from './index.js'
import MysAvatar from './player-lib/MysAvatar.js' import MysAvatar from './player/MysAvatar.js'
import Profile from './player-lib/Profile.js' import Profile from './player/Profile.js'
Data.createDir('/data/userData', 'root') Data.createDir('/data/userData', 'root')
@ -30,6 +30,17 @@ export default class Player extends Base {
return this._cache() 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) { static create (e) {
if (e?._mys?.uid || e.uid) { if (e?._mys?.uid || e.uid) {
// 传入为e // 传入为e
@ -68,8 +79,8 @@ export default class Player extends Base {
save () { save () {
let ret = Data.getData(this, 'uid,name,level,word,face,card,sign,info,_info,_mys,_profile') let ret = Data.getData(this, 'uid,name,level,word,face,card,sign,info,_info,_mys,_profile')
ret.avatars = {} ret.avatars = {}
lodash.forEach(this._avatars, (ds) => { this.forEachAvatar((avatar) => {
ret.avatars[ds.id] = ds.toJSON() ret.avatars[avatar.id] = avatar.toJSON()
}) })
// 暂时保留旧数据,防止异常情况 // 暂时保留旧数据,防止异常情况
if (this._chars) { if (this._chars) {
@ -136,12 +147,15 @@ export default class Player extends Base {
// 循环Avatar // 循环Avatar
forEachAvatar (fn) { forEachAvatar (fn) {
for (let id in this._avatars) { for (let id in this._avatars) {
let ret = fn(this._avatars[id], id) let avatar = this._avatars[id]
if (avatar && avatar.hasData) {
let ret = fn(this._avatars[id])
if (ret === false) { if (ret === false) {
return false return false
} }
} }
} }
}
// 获取所有Avatar数据 // 获取所有Avatar数据
getAvatarData (ids = '') { getAvatarData (ids = '') {
@ -195,7 +209,7 @@ export default class Player extends Base {
} }
// 更新面板 // 更新面板
async refreshProfile (force = 1) { async refreshProfile (force = 2) {
return await Profile.refreshProfile(this, force) return await Profile.refreshProfile(this, force)
} }
@ -223,14 +237,20 @@ export default class Player extends Base {
return await MysAvatar.refreshTalent(this, ids, force) return await MysAvatar.refreshTalent(this, ids, force)
} }
async refreshAndGetAvatarData (cfg) { async refresh (cfg) {
// 更新角色信息 if (cfg.detail || cfg.detail === 0) {
await this.refreshMysDetail(cfg.force || 0) await this.refreshMysDetail(cfg.detail)
// 更新天赋信息
if (cfg.refreshTalent !== false) {
await this.refreshTalent(cfg.ids, cfg.force || 0)
} }
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 rank = false
let e = this.e let e = this.e

View File

@ -5,9 +5,9 @@ import lodash from 'lodash'
import AvatarArtis from './AvatarArtis.js' import AvatarArtis from './AvatarArtis.js'
import { Artifact, ArtifactSet, Character } from './index.js' import { Artifact, ArtifactSet, Character } from './index.js'
import { Format } from '../components/index.js' import { Format } from '../components/index.js'
import ArtisMark from './profile-lib/ArtisMark.js' import ArtisMark from './profile/ArtisMark.js'
import { attrMap } from '../resources/meta/artifact/index.js' import { attrMap } from '../resources/meta/artifact/index.js'
import CharArtis from './profile-lib/CharArtis.js' import CharArtis from './profile/CharArtis.js'
export default class ProfileArtis extends AvatarArtis { export default class ProfileArtis extends AvatarArtis {
constructor (charid = 0, elem = '') { constructor (charid = 0, elem = '') {

View File

@ -2,8 +2,8 @@ import lodash from 'lodash'
import AvatarData from './AvatarData.js' import AvatarData from './AvatarData.js'
import { Data } from '../components/index.js' import { Data } from '../components/index.js'
import { ProfileArtis, ProfileDmg } from './index.js' import { ProfileArtis, ProfileDmg } from './index.js'
import AttrCalc from './profile-lib/AttrCalc.js' import AttrCalc from './profile/AttrCalc.js'
import CharImg from './character-lib/CharImg.js' import CharImg from './character/CharImg.js'
export default class ProfileData extends AvatarData { export default class ProfileData extends AvatarData {
constructor (ds = {}, calc = true) { constructor (ds = {}, calc = true) {

View File

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

View File

@ -1,13 +1,17 @@
import lodash from 'lodash' import lodash from 'lodash'
import { Common, Data } from '../../components/index.js' import { Common, Data } from '../../components/index.js'
import { chestInfo } from '../../resources/meta/info/index.js'
import moment from 'moment' import moment from 'moment'
const MysAvatar = { const MysAvatar = {
needRefresh (time, force = 0, forceMap = {}) { needRefresh (time, force = 0, forceMap = {}) {
if (!time) { if (!time || force === 2) {
return true return true
} }
if (force === true) {
force = 0
}
let duration = new Date() * 1 - time * 1 let duration = new Date() * 1 - time * 1
if (isNaN(duration) || duration < 0) { if (isNaN(duration) || duration < 0) {
return true return true
@ -250,15 +254,16 @@ const MysAvatar = {
}, },
getInfo (player) { getInfo (player) {
let chestMap = []
Data.eachStr('common,exquisite,precious,luxurious,magic', (key) => {
chestMap.push({
key: `${key}Chest`,
...chestInfo[key]
})
})
let ret = { let ret = {
...(player.info || {}), ...(player.info || {}),
chestMap: [ chestMap
{ key: 'commonChest', title: '普通宝箱', max: 2521 },
{ key: 'exquisiteChest', title: '精致宝箱', max: 1585 },
{ key: 'preciousChest', title: '珍贵宝箱', max: 482 },
{ key: 'luxuriousChest', title: '豪华宝箱', max: 184 },
{ key: 'magicChest', title: '奇馈宝箱', max: 140 }
]
} }
let stats = ret.stats || {} let stats = ret.stats || {}
if (stats?.activeDay) { if (stats?.activeDay) {

View File

@ -1,5 +1,6 @@
import { ProfileReq, ProfileServ } from '../index.js' import { ProfileReq, ProfileServ } from '../index.js'
import { Cfg, Data } from '../../components/index.js' import { Cfg, Data } from '../../components/index.js'
import MysAvatar from './MysAvatar.js'
import enkaCfg from './EnkaApi.js' import enkaCfg from './EnkaApi.js'
import MiaoApi from './MiaoApi.js' import MiaoApi from './MiaoApi.js'
@ -34,7 +35,10 @@ const Profile = {
* @param force * @param force
* @returns {Promise<boolean|number>} * @returns {Promise<boolean|number>}
*/ */
async refreshProfile (player, force = 1) { async refreshProfile (player, force = 2) {
if (!MysAvatar.needRefresh(player._profile, force, { 0: 24, 1: 2, 2: 0 })) {
return false
}
player._update = [] player._update = []
let { uid, e } = player let { uid, e } = player
if (uid.toString().length !== 9 || !e) { if (uid.toString().length !== 9 || !e) {

View File

@ -159,7 +159,7 @@
.chest-list .chest .detail { .chest-list .chest .detail {
width: 60px; width: 60px;
height: 40px; height: 40px;
font-size: 13px; font-size: 14px;
} }
.chest-list .chest .info { .chest-list .chest .info {
display: flex; display: flex;
@ -176,7 +176,6 @@
vertical-align: center; vertical-align: center;
} }
.chest-list .chest .max { .chest-list .chest .max {
font-size: 12px;
padding-left: 3px; padding-left: 3px;
color: #aaa; color: #aaa;
} }

View File

@ -191,7 +191,7 @@
.detail { .detail {
width: 60px; width: 60px;
height: 40px; height: 40px;
font-size: 13px; font-size: 14px;
} }
.info { .info {
@ -211,7 +211,6 @@
} }
.max { .max {
font-size: 12px;
padding-left: 3px; padding-left: 3px;
color: #aaa; color: #aaa;
} }

View File

@ -171,6 +171,11 @@
padding: 3px 7.5px 3px 4.5px; padding: 3px 7.5px 3px 4.5px;
border-radius: 0 6px 0 0; border-radius: 0 6px 0 0;
color: #fff; color: #fff;
font-size: 16px;
text-shadow: 0 0 1px #000;
}
.avatar-card .avatar-face .avatar-level span {
font-size: 12px;
} }
.avatar-card .cons { .avatar-card .cons {
border-radius: 0 0 0 7.5px; border-radius: 0 0 0 7.5px;
@ -178,6 +183,11 @@
position: absolute; position: absolute;
right: 0; right: 0;
top: 0; top: 0;
font-size: 16px;
text-shadow: 0 0 1px #000, 1px 1px 3px rgba(0, 0, 0, 0.8);
}
.avatar-card .cons.cons-0 {
display: none;
} }
.avatar-card .avatar-talent { .avatar-card .avatar-talent {
height: 30px; height: 30px;
@ -253,15 +263,23 @@
width: 114px; width: 114px;
border-radius: 10.5px 0 0 10.5px; border-radius: 10.5px 0 0 10.5px;
} }
.avatar-card.card-wide .img { .avatar-card.card-wide .avatar-face .img {
background-size: 100% auto; background-size: 100% auto;
background-position: 0 10%; background-position: 0 10%;
height: 135px; height: 202.5px;
margin-top: -13.5px; margin-top: -13.5px;
} }
.avatar-card.card-wide .avatar-info { .avatar-card.card-wide .avatar-info {
width: 105px; width: 105px;
} }
.avatar-card.card-wide .avatar-info strong {
display: block;
height: 45px;
line-height: 45px;
}
.avatar-card.card-wide .avatar-info .lv-info {
height: 30px;
}
.avatar-card.card-wide .line { .avatar-card.card-wide .line {
display: block; display: block;
height: 1px; height: 1px;
@ -307,8 +325,12 @@
.avatar-card .avatar-artis { .avatar-card .avatar-artis {
position: relative; position: relative;
} }
.avatar-card .avatar-artis.artis0 .item-icon {
background: url('./item/artifact-icon.webp') rgba(0, 0, 0, 0.3) center no-repeat;
background-size: auto 80%;
}
.avatar-card .avatar-artis .artis { .avatar-card .avatar-artis .artis {
background: rgba(0, 0, 0, 0.5); background: rgba(0, 0, 0, 0.4);
} }
.avatar-card .avatar-artis.artis2 .img { .avatar-card .avatar-artis.artis2 .img {
position: absolute; position: absolute;

View File

@ -10,21 +10,24 @@
<div class="img mini" style="background-image:url({{_res_path}}{{avatar.face}})"></div> <div class="img mini" style="background-image:url({{_res_path}}{{avatar.face}})"></div>
<div class="img wide avatar-{{avatar.name}}" style="background-image:url({{_res_path}}{{avatar.gacha}})"></div> <div class="img wide avatar-{{avatar.name}}" style="background-image:url({{_res_path}}{{avatar.gacha}})"></div>
<span class="cons cons-{{avatar.cons}} mini">{{avatar.cons}}</span> <span class="cons cons-{{avatar.cons}} mini">{{avatar.cons}}</span>
<div class="avatar-level">Lv{{avatar.level}}</div> <div class="avatar-level"><span>Lv</span>{{avatar.level}} {{avatar.abbr}}</div>
</div> </div>
<div class="avatar-info"> <div class="avatar-info">
{{set talent = avatar.talent || {} }} {{set talent = avatar.talent || {} }}
{{if talent.a && talent.a.level }}
<div class="avatar-name wide"> <div class="avatar-name wide">
<strong>{{avatar.abbr}}</strong> <strong>{{avatar.abbr}}</strong>
<div class="lv-info">
<span class="cons cons-{{avatar.cons}}">{{avatar.cons}}</span> <span class="cons cons-{{avatar.cons}}">{{avatar.cons}}</span>
<span class="avatar-level">Lv{{avatar.level}}</span> <span class="avatar-level">Lv{{avatar.level}}</span>
</div>
<div class="line"></div> <div class="line"></div>
</div> </div>
{{if talent.a && talent.a.level }}
<div class="avatar-talent"> <div class="avatar-talent">
{{each talentMap k}} {{each talentMap k}}
{{set t = talent[k] || {} }} <span {{set t = talent[k] || {} }}
class="talent-item talent-{{k}} talent-{{t.original===10?'crown':'none'}} talent-{{t.level>t.original?'plus':'none'}}">{{t.level}}</span> <span class="talent-item talent-{{k}} talent-{{t.original===10?'crown':'none'}} talent-{{t.level>t.original?'plus':'none'}}">{{t.level}}</span>
{{/each}} {{/each}}
</div> </div>
{{else}} {{else}}
@ -35,7 +38,7 @@
<div class="item avatar-weapon"> <div class="item avatar-weapon">
<div class="item-icon star{{weapon.star}}"> <div class="item-icon star{{weapon.star}}">
<span class="img" style="background-image:url({{_res_path}}{{weapon.img}})"></span> <span class="img" style="background-image:url({{_res_path}}{{weapon.img}})"></span>
<span class="cons cons-{{weapon.affix}}">{{weapon.affix}}</span> <span class="cons cons-{{weapon.affix > 4 ? weapon.affix + 1 : weapon.affix}}">{{weapon.affix}}</span>
</div> </div>
</div> </div>
<div class="item avatar-artis artis{{avatar.artisSet?.names?.length}}"> <div class="item avatar-artis artis{{avatar.artisSet?.names?.length}}">

View File

@ -37,6 +37,12 @@
padding: @px*2 @px*5 @px*2 @px*3; padding: @px*2 @px*5 @px*2 @px*3;
border-radius: 0 @px*4 0 0; border-radius: 0 @px*4 0 0;
color: #fff; color: #fff;
font-size: 16px;
text-shadow: 0 0 1px #000;
span {
font-size: 12px;
}
} }
} }
@ -47,6 +53,12 @@
position: absolute; position: absolute;
right: 0; right: 0;
top: 0; top: 0;
font-size: 16px;
text-shadow: 0 0 1px #000, 1px 1px 3px rgba(0, 0, 0, .8);
&.cons-0 {
display: none;
}
} }
.avatar-talent { .avatar-talent {
@ -136,17 +148,27 @@
height: @px * 126; height: @px * 126;
width: @px*76; width: @px*76;
border-radius: @px*7 0 0 @px*7; border-radius: @px*7 0 0 @px*7;
}
.img { .img {
background-size: 100% auto; background-size: 100% auto;
background-position: 0 10%; background-position: 0 10%;
height: 135px; height: @px*135;
margin-top: @px*-9; margin-top: @px*-9;
} }
}
.avatar-info { .avatar-info {
width: @px*70; width: @px*70;
strong {
display: block;
height: @px * 30;
line-height: @px*30;
}
.lv-info {
height: @px*20;
}
} }
.line { .line {
@ -211,8 +233,15 @@
.avatar-artis { .avatar-artis {
position: relative; position: relative;
&.artis0 {
.item-icon {
background: url('./item/artifact-icon.webp') rgba(0, 0, 0, .3) center no-repeat;
background-size: auto 80%;
}
}
.artis { .artis {
background: rgba(0, 0, 0, 0.5) background: rgba(0, 0, 0, 0.4)
} }
&.artis2 { &.artis2 {

View File

@ -0,0 +1,23 @@
// 报箱数统计
export const chestInfo = {
common: {
title: '普通宝箱',
max: 2542
},
exquisite: {
title: '精致宝箱',
max: 1594
},
precious: {
title: '珍贵宝箱',
max: 488
},
luxurious: {
title: '豪华宝箱',
max: 185
},
magic: {
title: '奇馈宝箱',
max: 146
}
}

View File

@ -148,13 +148,8 @@ export default function (step, staticStep) {
} }
}], }],
裁叶萃光: [{ 裁叶萃光: [staticStep('cpct', 4), {
title: '暴击率提升[cpct]%', title: '普攻与元素战技造成的伤害值提高[aPlus]',
refine: {
cpct: step(4)
}
}, {
title: '暴击率提升4%,普攻与元素战技造成的伤害值提高[aPlus]',
data: { data: {
aPlus: ({ attr, calc, refine }) => calc(attr.mastery) * step(120)[refine] / 100, aPlus: ({ attr, calc, refine }) => calc(attr.mastery) * step(120)[refine] / 100,
ePlus: ({ attr, calc, refine }) => calc(attr.mastery) * step(120)[refine] / 100 ePlus: ({ attr, calc, refine }) => calc(attr.mastery) * step(120)[refine] / 100

View File

@ -84,7 +84,7 @@ body,
color: #d3bc8e; color: #d3bc8e;
} }
.avatar-banner { .avatar-banner {
height: 265px; height: 300px;
width: 175px; width: 175px;
background-repeat: no-repeat; background-repeat: no-repeat;
background-size: 100% auto; background-size: 100% auto;

View File

@ -97,7 +97,7 @@ body, .container {
.avatar-banner { .avatar-banner {
height: 265px; height: 300px;
width: 175px; width: 175px;
background-repeat: no-repeat; background-repeat: no-repeat;
background-size: 100% auto; background-size: 100% auto;