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*(.*)$`)
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

@ -4,85 +4,122 @@ import lodash from 'lodash'
import { segment } from 'oicq'
import moment from 'moment'
export async function renderAvatar (e, avatar, renderType = 'card') {
// 如果传递的是名字,则获取
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
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 }
}
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, avatar, renderType = 'card') {
let char = Character.get(avatar.id)
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}`, 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) {
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')
}
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 default Avatar

View File

@ -2,7 +2,7 @@
import lodash from 'lodash'
import { Common } from '../../components/index.js'
import { Character, MysApi, Player } from '../../models/index.js'
import { renderAvatar } from './AvatarCard.js'
import Avatar from './AvatarCard.js'
const relationMap = {
wife: {
@ -32,138 +32,9 @@ 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
if (e.isPoke) {
if (!Common.cfg('avatarPoke')) {
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] || ''
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 avatarList = []
let avatar = {}
let wifeList = []
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 renderAvatar(e, avatar, renderType)
} else {
// 如果指定过,则展示指定角色
return renderAvatar(e, lodash.sample(wifeList), renderType)
}
}
}
// 如果未指定过,则从列表中排序并随机选择
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)
}
e.reply('在当前米游社公开展示的角色中未能找到适合展示的角色..')
return true
case '设置':
case '选择':
case '挑选':
case '指定':
if (!isSelf) {
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} ~`)
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('只能查看自己的哦~')
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
}
export async function pokeWife (e, components) {
return await wife(e, components)
}
async function getAvatarList (player, type, mys) {
async function getAvatarList (player, type) {
await player.refreshMysDetail()
let list = []
player.forEachAvatar((avatar) => {
@ -182,3 +53,136 @@ async function getAvatarList (player, type, mys) {
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('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] || ''
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 avatarList = []
let avatar = {}
let wifeList = []
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)
}
}
}
// 如果未指定过,则从列表中排序并随机选择
avatarList = await getAvatarList(player, e.isPoke ? false : targetCfg.type, mys)
if (avatarList && avatarList.length > 0) {
avatar = lodash.sample(avatarList)
return await Avatar.renderAvatar(e, avatar, renderType)
}
e.reply('在当前米游社公开展示的角色中未能找到适合展示的角色..')
return true
case '设置':
case '选择':
case '挑选':
case '指定':
if (!isSelf) {
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} ~`)
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('只能查看自己的哦~')
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)
}
}
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,9 +1,9 @@
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 { profileDetail } from './profile/ProfileDetail.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 { enemyLv } from './profile/ProfileUtils.js'
import { groupRank, resetRank, refreshRank, manageRank } from './profile/ProfileRank.js'
@ -12,86 +12,110 @@ 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: {
rule: /^#*([^#]+)\s*(详细|详情|面板|面版|圣遗物|伤害[1-7]?)\s*(\d{9})*(.*[换变改].*)?$/,
fn: profileDetail,
name: '角色面板'
},
app.reg('reset-rank', resetRank, {
rule: /^#(重置|重设)(.*)(排名|排行)$/,
name: '重置排名'
})
profileChange: {
rule: /^#.+换.+$/,
fn: profileDetail,
name: '角色面板计算'
},
app.reg('refresh-rank', refreshRank, {
rule: /^#(刷新|更新|重新加载)(群内|群|全部)*(排名|排行)$/,
name: '重置排名'
})
groupProfile: {
rule: /^#(群|群内)?(排名|排行)?(最强|最高|最高分|最牛|第一)+.+/,
fn: groupRank,
name: '群内最强'
},
app.reg('manage-rank', manageRank, {
rule: /^#(开启|打开|启用|关闭|禁用)(群内|群|全部)*(排名|排行)$/,
name: '打开关闭'
})
resetRank: {
rule: /^#(重置|重设)(.*)(排名|排行)$/,
fn: resetRank,
name: '重置排名'
},
app.reg('rank-list', groupRank, {
rule: /^#(群|群内)?.+(排名|排行)(榜)?$/,
name: '面板排名榜'
})
refreshRank: {
rule: /^#(刷新|更新|重新加载)(群内|群|全部)*(排名|排行)$/,
fn: refreshRank,
name: '重置排名'
},
app.reg('artis-list', profileArtisList, {
rule: /^#圣遗物列表\s*(\d{9})?$/,
name: '面板圣遗物列表'
})
manageRank: {
rule: /^#(开启|打开|启用|关闭|禁用)(群内|群|全部)*(排名|排行)$/,
fn: manageRank,
name: '打开关闭'
},
app.reg('profile-list', profileList, {
rule: /^#(面板角色|角色面板|面板)(列表)?\s*(\d{9})?$/,
name: '面板角色列表',
desc: '查看当前已获取面板数据的角色列表'
})
rankList: {
rule: /^#(群|群内)?.+(排名|排行)(榜)?$/,
fn: groupRank,
name: '面板排名榜'
},
app.reg('profile-stat', profileStat, {
rule: /^#(面板|喵喵|角色|武器|天赋|技能|圣遗物)?练度统计$/,
name: '面板练度统计'
})
app.reg('avatar-list', profileStat, {
rule: /^(#(角色|查询|查询角色|角色查询|人物)[ |0-9]*$)|(^(#*uid|#*UID)\+*[1|2|5-9][0-9]{8}$)|(^#[\+|]*[1|2|5-9][0-9]{8})/,
name: '角色查询'
})
artisList: {
rule: /^#圣遗物列表\s*(\d{9})?$/,
fn: profileArtisList,
name: '面板圣遗物列表'
},
app.reg('profile-help', profileHelp, {
rule: /^#(角色|换|更换)?面[板版]帮助$/,
name: '角色面板帮助'
})
profileList: {
rule: /^#(面板角色|角色面板|面板)(列表)?\s*(\d{9})?$/,
fn: ProfileList.render,
name: '面板角色列表',
desc: '查看当前已获取面板数据的角色列表'
},
app.reg('enemy-lv', enemyLv, {
rule: /^#(敌人|怪物)等级\s*\d{1,3}\s*$/,
describe: '【#角色】 设置伤害计算中目标敌人的等级'
})
profileStat: {
rule: /^#(面板|喵喵|角色|武器|天赋|技能|圣遗物)?练度统计$/,
fn: profileStat,
name: '面板练度统计'
},
app.reg('profile-refresh', getProfile, {
rule: /^#(全部面板更新|更新全部面板|获取游戏角色详情|更新面板|面板更新)\s*(\d{9})?$/,
describe: '【#角色】 获取游戏橱窗详情数据'
})
avatarList: {
rule: /^(#(角色|查询|查询角色|角色查询|人物)[ |0-9]*$)|(^(#*uid|#*UID)\+*[1|2|5-9][0-9]{8}$)|(^#[\+|]*[1|2|5-9][0-9]{8})/,
fn: profileStat,
name: '角色查询'
},
app.reg('upload-img', uploadCharacterImg, {
rule: /^#?\s*(?:上传|添加)(.+)(?:面板图)\s*$/,
describe: '【#上传刻晴面板图】 上传角色面板图'
})
app.reg('del-profile', delProfileImg, {
rule: /^#?\s*(?:移除|清除|删除)(.+)(?:面板图)(\d){1,}\s*$/,
describe: '【#删除刻晴面板图1】 删除指定角色面板图(序号)'
})
app.reg('profile-img-list', profileImgList, {
rule: /^#?\s*(.+)(?:面板图列表)\s*$/,
describe: '【#刻晴面板图列表】 删除指定角色面板图(序号)'
profileHelp: {
rule: /^#(角色|换|更换)?面[板版]帮助$/,
fn: profileHelp,
name: '角色面板帮助'
},
enemyLv: {
rule: /^#(敌人|怪物)等级\s*\d{1,3}\s*$/,
fn: enemyLv,
describe: '【#角色】 设置伤害计算中目标敌人的等级'
},
profileRefresh: {
rule: /^#(全部面板更新|更新全部面板|获取游戏角色详情|更新面板|面板更新)\s*(\d{9})?$/,
fn: ProfileList.refresh,
describe: '【#角色】 获取游戏橱窗详情数据'
},
uploadImg: {
rule: /^#?\s*(?:上传|添加)(.+)(?:面板图)\s*$/,
fn: uploadCharacterImg,
describe: '【#上传刻晴面板图】 上传角色面板图'
},
delProfile: {
rule: /^#?\s*(?:移除|清除|删除)(.+)(?:面板图)(\d){1,}\s*$/,
fn: delProfileImg,
describe: '【#删除刻晴面板图1】 删除指定角色面板图(序号)'
},
profileImgList: {
rule: /^#?\s*(.+)(?:面板图列表)\s*$/,
fn: profileImgList,
describe: '【#刻晴面板图列表】 删除指定角色面板图(序号)'
}
})
export default app

View File

@ -4,7 +4,7 @@
* */
import lodash from 'lodash'
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'
/*
@ -12,26 +12,15 @@ 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()

View File

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

View File

@ -1,5 +1,6 @@
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 { MysApi, ProfileRank, ProfileArtis, Player, Character } from '../../models/index.js'
import ProfileChange from './ProfileChange.js'
@ -93,7 +94,7 @@ export async function profileDetail (e) {
if (mode === 'profile' || mode === 'dmg') {
return renderProfile(e, char, mode, { dmgIdx })
} else if (mode === 'refresh') {
await getProfile(e)
await ProfileList.refresh(e)
return true
} else if (mode === 'artis') {
return profileArtis(e)
@ -116,23 +117,8 @@ export async function renderProfile (e, char, mode = 'profile', params = {}) {
return true
}
let player = Player.create(uid)
let profile = e._profile || player.getProfile(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
}
char = profile.char || char

View File

@ -1,93 +1,121 @@
import lodash from 'lodash'
import { autoRefresh, getTargetUid } from './ProfileCommon.js'
import { ProfileRank, Player } from '../../models/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 = 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(uid)
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
await player.forEachAvatarAsync(async function (avatar) {
let profile = avatar.getProfile()
if (!profile) {
const ProfileList = {
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.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
},
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,
allowRank: rank && rank.allowRank,
rankCfg
}, { e, scale: 1.6 })
}
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,
allowRank: rank && rank.allowRank,
rankCfg
}, { e, scale: 1.6 })
}
export default ProfileList

View File

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

View File

@ -2,508 +2,36 @@
* 胡桃数据库的统计
*
* */
import lodash from 'lodash'
import { Cfg, Common, App, Data } from '../components/index.js'
import { Abyss, Character, MysApi, Player } 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 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 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 } from '../../components/index.js'
import lodash from 'lodash'
const ignoreIds = [495, // 有奖问卷调查开启!
@ -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

@ -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
}
}
}

View File

@ -5,7 +5,7 @@ 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-lib/ArtisMark.js'
import ArtisMark from './profile/ArtisMark.js'
export default class AvatarArtis extends Base {
constructor (charid = 0) {

View File

@ -3,8 +3,8 @@ 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-lib/AttrCalc.js'
import Profile from './player-lib/Profile.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(',')
@ -29,6 +29,10 @@ export default class AvatarData extends Base {
return this.char?.name || ''
}
get hasData () {
return !!(this.level > 1 || this?.weapon?.name || this?.talent?.a)
}
// 是否是合法面板数据
get isProfile () {
return Profile.isProfile(this)

View File

@ -7,11 +7,11 @@
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 { wifeMap, idSort, idMap } = CharId

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

View File

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

View File

@ -5,9 +5,9 @@ import lodash from 'lodash'
import AvatarArtis from './AvatarArtis.js'
import { Artifact, ArtifactSet, Character } from './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 CharArtis from './profile-lib/CharArtis.js'
import CharArtis from './profile/CharArtis.js'
export default class ProfileArtis extends AvatarArtis {
constructor (charid = 0, elem = '') {

View File

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

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

@ -1,13 +1,17 @@
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) {
if (!time || force === 2) {
return true
}
if (force === true) {
force = 0
}
let duration = new Date() * 1 - time * 1
if (isNaN(duration) || duration < 0) {
return true
@ -250,15 +254,16 @@ const MysAvatar = {
},
getInfo (player) {
let chestMap = []
Data.eachStr('common,exquisite,precious,luxurious,magic', (key) => {
chestMap.push({
key: `${key}Chest`,
...chestInfo[key]
})
})
let ret = {
...(player.info || {}),
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 }
]
chestMap
}
let stats = ret.stats || {}
if (stats?.activeDay) {

View File

@ -1,5 +1,6 @@
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'
@ -34,7 +35,10 @@ const Profile = {
* @param force
* @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 = []
let { uid, e } = player
if (uid.toString().length !== 9 || !e) {

View File

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

View File

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

View File

@ -171,6 +171,11 @@
padding: 3px 7.5px 3px 4.5px;
border-radius: 0 6px 0 0;
color: #fff;
font-size: 16px;
text-shadow: 0 0 1px #000;
}
.avatar-card .avatar-face .avatar-level span {
font-size: 12px;
}
.avatar-card .cons {
border-radius: 0 0 0 7.5px;
@ -178,6 +183,11 @@
position: absolute;
right: 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 {
height: 30px;
@ -253,15 +263,23 @@
width: 114px;
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-position: 0 10%;
height: 135px;
height: 202.5px;
margin-top: -13.5px;
}
.avatar-card.card-wide .avatar-info {
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 {
display: block;
height: 1px;
@ -307,8 +325,12 @@
.avatar-card .avatar-artis {
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 {
background: rgba(0, 0, 0, 0.5);
background: rgba(0, 0, 0, 0.4);
}
.avatar-card .avatar-artis.artis2 .img {
position: absolute;

View File

@ -10,21 +10,24 @@
<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>
<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 class="avatar-info">
{{set talent = avatar.talent || {} }}
{{if talent.a && talent.a.level }}
<div class="avatar-name wide">
<strong>{{avatar.abbr}}</strong>
<span class="cons cons-{{avatar.cons}}">{{avatar.cons}}</span>
<span class="avatar-level">Lv{{avatar.level}}</span>
<div class="lv-info">
<span class="cons cons-{{avatar.cons}}">{{avatar.cons}}</span>
<span class="avatar-level">Lv{{avatar.level}}</span>
</div>
<div class="line"></div>
</div>
{{if talent.a && talent.a.level }}
<div class="avatar-talent">
{{each talentMap k}}
{{set t = talent[k] || {} }} <span
class="talent-item talent-{{k}} talent-{{t.original===10?'crown':'none'}} talent-{{t.level>t.original?'plus':'none'}}">{{t.level}}</span>
{{set t = talent[k] || {} }}
<span class="talent-item talent-{{k}} talent-{{t.original===10?'crown':'none'}} talent-{{t.level>t.original?'plus':'none'}}">{{t.level}}</span>
{{/each}}
</div>
{{else}}
@ -35,7 +38,7 @@
<div class="item avatar-weapon">
<div class="item-icon star{{weapon.star}}">
<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 class="item avatar-artis artis{{avatar.artisSet?.names?.length}}">

View File

@ -37,6 +37,12 @@
padding: @px*2 @px*5 @px*2 @px*3;
border-radius: 0 @px*4 0 0;
color: #fff;
font-size: 16px;
text-shadow: 0 0 1px #000;
span {
font-size: 12px;
}
}
}
@ -47,6 +53,12 @@
position: absolute;
right: 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 {
@ -136,17 +148,27 @@
height: @px * 126;
width: @px*76;
border-radius: @px*7 0 0 @px*7;
}
.img {
background-size: 100% auto;
background-position: 0 10%;
height: 135px;
margin-top: @px*-9;
.img {
background-size: 100% auto;
background-position: 0 10%;
height: @px*135;
margin-top: @px*-9;
}
}
.avatar-info {
width: @px*70;
strong {
display: block;
height: @px * 30;
line-height: @px*30;
}
.lv-info {
height: @px*20;
}
}
.line {
@ -211,8 +233,15 @@
.avatar-artis {
position: relative;
&.artis0 {
.item-icon {
background: url('./item/artifact-icon.webp') rgba(0, 0, 0, .3) center no-repeat;
background-size: auto 80%;
}
}
.artis {
background: rgba(0, 0, 0, 0.5)
background: rgba(0, 0, 0, 0.4)
}
&.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) {
}
}],
裁叶萃光: [{
title: '暴击率提升[cpct]%',
refine: {
cpct: step(4)
}
}, {
title: '暴击率提升4%,普攻与元素战技造成的伤害值提高[aPlus]',
裁叶萃光: [staticStep('cpct', 4), {
title: '普攻与元素战技造成的伤害值提高[aPlus]',
data: {
aPlus: ({ 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;
}
.avatar-banner {
height: 265px;
height: 300px;
width: 175px;
background-repeat: no-repeat;
background-size: 100% auto;

View File

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