增加 #角色持有率 #角色命座 分析统计功能

增加 #深渊出场率 #深渊十二层出场率 查询功能
This commit is contained in:
yoimiya-kokomi 2022-04-05 05:36:44 +08:00
parent 336da7ac6a
commit ea8b9b8f79
17 changed files with 876 additions and 4 deletions

View File

@ -79,6 +79,8 @@ export async function wife(e, { render, User }) {
action = msgRet[2] || "卡片", action = msgRet[2] || "卡片",
actionParam = msgRet[3] || ""; actionParam = msgRet[3] || "";
actionParam = actionParam.toString();
let targetCfg = lodash.find(relationMap, (cfg, key) => { let targetCfg = lodash.find(relationMap, (cfg, key) => {
cfg.key = key; cfg.key = key;
return cfg.keyword.includes(target); return cfg.keyword.includes(target);

153
apps/stat.js Normal file
View File

@ -0,0 +1,153 @@
/*
* 胡桃数据库的统计
*
* */
import { HutaoApi, Character } from "../components/models.js";
import lodash from "lodash";
import { segment } from "oicq";
export async function consStat(e, { render }) {
let consData = await HutaoApi.getCons();
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 ret = [];
lodash.forEach(consData.data, (ds) => {
let char = Character.get(ds.avatar);
let data = {
name: char.name || ds.avatar,
star: char.star || 3,
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
}
ret.push(data);
});
if (conNum > -1) {
ret = lodash.sortBy(ret, [`cons[${conNum}].value`]);
ret.reverse();
} else {
ret = lodash.sortBy(ret, ['hold']);
}
let base64 = await render("stat", "character", {
chars: ret,
abbr: Character.getAbbr(),
mode: mode,
conNum,
lastUpdate: consData.lastUpdate,
pct: function (num) {
return (num * 100).toFixed(2);
}
}, "png");
if (base64) {
e.reply(segment.image(`base64://${base64}`));
}
return true;
}
export async function abyssPct(e, { render }) {
let abyssData = await HutaoApi.getAbyssPct();
if (!abyssData) {
e.reply("暂时无法查询");
return true;
}
let ret = [], chooseFloor = -1, 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;
}
});
console.log('floor', chooseFloor);
lodash.forEach(abyssData.data, (floorData) => {
let floor = {
floor: floorData.floor,
};
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
})
}
})
avatars = lodash.sortBy(avatars, "value", ["asc"]);
avatars.reverse();
if (chooseFloor === -1) {
avatars = avatars.slice(0, 14);
}
ret.push({
floor: floorData.floor,
avatars
});
})
let base64 = await render("stat", "abyss-pct", {
abyss: ret,
floorName,
chooseFloor,
lastUpdate: abyssData.lastUpdate,
pct: function (num) {
return (num * 100).toFixed(2);
}
}, "png");
if (base64) {
e.reply(segment.image(`base64://${base64}`));
}
return true;
}

View File

@ -1,3 +1,4 @@
import Character from "./models/Character.js"; import Character from "./models/Character.js";
import HutaoApi from "./models/HutaoApi.js";
export { Character }; export { Character, HutaoApi };

View File

@ -22,6 +22,8 @@ const elemName = {
geo: "岩" geo: "岩"
}; };
let genshin = await import(`file://${_path}/config/genshin/roleId.js`);
class Character extends Base { class Character extends Base {
constructor(name) { constructor(name) {
@ -103,6 +105,8 @@ let getMetaData = function (name) {
// 处理基础信息 // 处理基础信息
let ret = Data.getData(meta, "Name,Title,desc:Description,astro:AstrolabeName", metaCfg); let ret = Data.getData(meta, "Name,Title,desc:Description,astro:AstrolabeName", metaCfg);
ret.star = /4star/.test(meta.Star) ? 4 : 5;
// 处理图像信息 // 处理图像信息
ret.img = Data.getData(meta, "Weapon,Element,City,Profile,GachaCard,GachaSplash,Source", metaCfg); ret.img = Data.getData(meta, "Weapon,Element,City,Profile,GachaCard,GachaSplash,Source", metaCfg);
@ -185,7 +189,12 @@ const getTalentData = function (data) {
Character.get = function (val) { Character.get = function (val) {
let roleid = YunzaiApps.mysInfo['roleIdToName'](val); let roleid;
if (typeof (val) === "number") {
roleid = val;
} else {
roleid = YunzaiApps.mysInfo['roleIdToName'](val);
}
let name = YunzaiApps.mysInfo['roleIdToName'](roleid, true); let name = YunzaiApps.mysInfo['roleIdToName'](roleid, true);
if (!name) { if (!name) {
return false; return false;
@ -197,4 +206,8 @@ Character.get = function (val) {
return characterMap[name]; return characterMap[name];
}; };
Character.getAbbr = function () {
return genshin.abbr;
}
export default Character; export default Character;

View File

@ -0,0 +1,66 @@
/*
* 胡桃API Miao-Plugin 封装
* https://github.com/DGP-Studio/DGP.Genshin.HutaoAPI
*
*
* */
import Base from "./Base.js";
import fetch from "node-fetch";
const host = "https://hutao-api.snapgenshin.com";
let hutaoApi = null;
function getApi(api) {
return `${host}${api}`;
}
let HutaoApi = {
_auth: false,
async login() {
let response = await fetch(getApi('/Auth/Login'), {
method: 'POST',
headers: {
'Content-Type': 'text/json; charset=utf-8',
},
body: JSON.stringify({
"Appid": "appid",
"Secret": "secret"
})
});
let res = await response.json();
},
async req(url, data) {
let cacheData = await redis.get(`hutao:${url}`);
if (cacheData) {
return JSON.parse(cacheData)
}
let response = await fetch(getApi(`${url}?Authorization=demo`), {
method: "GET",
headers: {
Authorization: `Bearer{token}`
}
});
let retData = await response.json();
let d = new Date();
retData.lastUpdate = `${d.toLocaleDateString()} ${d.toTimeString().substr(0, 5)}`;
await redis.set(`hutao:${url}`, JSON.stringify(retData), { EX: 3600 });
return retData;
},
// 角色持有及命座分布
async getCons() {
return await HutaoApi.req("/Statistics/Constellation");
},
async getAbyssPct() {
return await HutaoApi.req("/Statistics/AvatarParticipation");
}
};
export default HutaoApi;

View File

@ -1,7 +1,8 @@
import { character, wife, wifeReg } from "./apps/character.js"; import { character, wife, wifeReg } from "./apps/character.js";
import { consStat, abyssPct } from "./apps/stat.js";
//import { wiki } from "./apps/wiki.js"; //import { wiki } from "./apps/wiki.js";
export { character, wife }; export { character, wife, consStat, abyssPct };
export const rule = { export const rule = {
@ -17,6 +18,16 @@ export const rule = {
priority: 203, priority: 203,
describe: "【#老婆,#老公,#女儿】角色详情", describe: "【#老婆,#老公,#女儿】角色详情",
}, },
consStat: {
hrehash: true,
reg: "^#角色(持有|持有率|命座|命之座|.命)(分布|统计)?$",
priority: 200
},
abyssPct: {
prehash: true,
reg: "^#深渊(第?.{1,2}层)?(角色)?出场(率|统计)*$",
priority: 200
}
/* /*
wiki: { wiki: {
reg: "^#*.*(缓存)$", reg: "^#*.*(缓存)$",

BIN
resources/common/bg3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

BIN
resources/common/bg4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
resources/common/bg5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -0,0 +1,92 @@
.card-list {
display: flex;
flex-wrap: wrap;
justify-content: center;
}
.card-list .item {
margin: 0px 0 10px 10px;
border-radius: 7px;
box-shadow: 0 2px 6px 0 rgb(132 93 90 / 30%);
height: 88px;
position: relative;
overflow: hidden;
background: #e7e5d9;
}
.card-list .item img {
width: 70px;
height: 70px;
border-radius: 7px 7px 20px 0;
}
.card-list .item.star5 img {
background-image: url(../common/bg5.png);
width: 100%;
height: 70px;
/*filter: brightness(1.1);*/
background-size: 100%;
background-repeat: no-repeat;
}
.card-list .item.star4 img {
width: 100%;
height: 70px;
background-image: url(../common/bg4.png);
background-size: 100%;
background-repeat: no-repeat;
}
.card-list .item .num {
position: absolute;
top: 0px;
right: 0px;
z-index: 9;
font-size: 18px;
text-align: center;
color: #fff;
border-radius: 3px;
padding: 1px 5px;
border-radius: 3px;
background: rgb(0 0 0 / 50%);
font-family: "tttgbnumber";
}
.card-list .item .name,
.card-list .item .num_name {
position: absolute;
top: 70px;
left: 0px;
z-index: 9;
font-size: 12px;
text-align: center;
width: 100%;
height: 16px;
line-height: 18px;
}
.card-list .item .num_name {
font-family: "tttgbnumber";
font-size: 18px;
}
.line_box {
height: 32px;
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
font-size: 16px;
color: #fff;
padding-bottom: 5px;
margin-top: 15px;
margin-bottom: 5px;
}
.line_box .line {
height: 1px;
flex-grow: 1;
background-color: #ebebeb;
margin: 0px 10px;
}

View File

@ -0,0 +1,54 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-8"/>
<link rel="shortcut icon" href="#"/>
<link rel="stylesheet" type="text/css" href="{{_res_path}}/stat/common.css?v=1.0"/>
<link rel="stylesheet" type="text/css" href="{{_res_path}}/stat/abyss-pct.css?v=1.0"/>
<link rel="preload" href="{{_res_path}}/font/tttgbnumber.ttf" as="font">
</head>
<body id="container" class="body_box">
<div class="container">
<div class="info_box">
<div class="head-box type{{bgType}}">
{{if chooseFloor == -1}}
<div class="title">深渊出场率统计</div>
<div class="label">全角色出场率统计(出场记录/总记录)</div>
{{else}}
<div class="title">深渊第{{floorName[chooseFloor]}}出场率</div>
<div class="label">全角色出场率统计(出场记录/总记录)</div>
{{/if}}
<img class="genshin_logo" src="{{_sys_res_path}}/genshin/roleAll/原神.png"/>
</div>
{{each abyss ds}}
{{if chooseFloor == -1 || chooseFloor == ds.floor}}
<div class="info_box_border">
<div class="line_box">
<span class="line"></span>
<span class="text">第{{floorName[ds.floor]}}</span>
<span class="line"></span>
</div>
<div class="card-list">
{{each ds.avatars char}}
<div class="item star{{char.star == 4? 4:5}}">
<img class="role"
src="{{_sys_res_path}}/genshin/logo/role/{{char.name}}.png"
/>
<div class="num_name">{{pct(char.value)}}%</div>
</div>
{{/each}}
</div>
</div>
{{/if}}
{{/each}}
<p class="notice notice-center"> 数据来源DGP-Studio-胡桃API . 最后更新时间:{{lastUpdate}} </p>
<div class="copyright"> Create By Yunzai-Bot & Miao-Plugin</div>
</div>
</div>
</body>
</html>

BIN
resources/stat/bg1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 354 KiB

View File

@ -0,0 +1,282 @@
.weapon_mode .for_talent {
display: none !important;
}
.talent_mode .for_weapon {
display: none !important;
}
.data-box {
border-radius: 15px;
margin-top: 20px;
margin-bottom: 20px;
padding: 0px 15px 5px 15px;
overflow: hidden;
background: #f5f5f5;
box-shadow: 0 5px 10px 0 rgb(0 0 0 / 15%);
position: relative;
}
.tab_lable {
position: absolute;
top: -10px;
left: -8px;
background: #a98242;
color: #fff;
font-size: 14px;
padding: 3px 10px;
border-radius: 15px 0px 15px 15px;
z-index: 20;
}
.data_line {
display: flex;
justify-content: space-around;
margin-bottom: 14px;
}
.data_line_item {
width: 100px;
text-align: center;
/*margin: 0 20px;*/
}
.num {
font-family: tttgbnumber;
font-size: 24px;
}
.num .unit {
font-size: 12px;
}
.data-box {
}
.char-list {
display: table;
border-collapse: collapse;
width: calc(100% + 30px);
margin: 0 -15px -5px;
font-size: 12px;
/* border-radius: 0 0 15px 15px; */
overflow: hidden;
}
.char-list .avatar {
display: table-row;
font-family: tttgbnumber;
overflow: visible;
}
.char-list .avatar {
display: table-row;
}
.char-list .avatar > div {
box-shadow: 0 0 1px 0 #555 inset;
}
.char-list .avatar:nth-child(odd) {
background: #e0e0e0;
}
.char-list .avatar:nth-child(even) {
}
.char-list .avatar:nth-child(1) {
background: #ccc;
}
.char-list .avatar > div {
display: table-cell;
text-align: center;
height: 30px;
vertical-align: middle;
line-height: 30px;
}
.char-list .avatar .index {
color: #333;
width: 30px;
padding-left: 5px;
}
.char-list .avatar .name_cont {
width: 80px;
}
.char-list .avatar .star4 {
background: rgba(137, 189, 233, .6);
}
.char-list .avatar .star5 {
background: rgba(239, 214, 137, .6);
}
.char-list .avatar .name_cont {
width: 80px;
}
.char-list .avatar .name {
text-align: left;
display: flex;
width: 80px;
}
.char-list .th,
.char-list .th div{
font-weight: bold;
height:40px;
line-height:40px;
overflow: hidden;
}
.char-list .th .name {
justify-content: center;
}
.char-list .avatar .name .avatar_img {
width: 26px;
height: 26px;
position: relative;
margin-right: 3px;
}
.char-list .avatar .name img {
width: 100%;
height: 100%;
position: absolute;
top: -3px;
margin-left: 3px;
}
.char-list .avatar .name .avatar_name {
white-space: nowrap;
overflow: hidden;
width: 48px;
}
.char-list .avatar .res {
font-size: 12px;
width: 90px;
}
.char-list .avatar .res img {
width: 20px;
height: 20px;
vertical-align: middle;
}
.char-list .avatar > div.lvl90 {
}
.char-list .avatar > div.fetter10 {
background: url("./hart.png") center center no-repeat;
background-size: contain;
color: #fff;
}
.char-list .cons {
width: 400px;
position: relative;
z-index: 98;
overflow: visible;
}
.char-list .cons-pct,
.char-list .cons-bg {
display: flex;
}
.char-list .th .cons-pct {
margin: 0;
color: #fff;
font-weight: bold;
}
.char-list .th .cons-pct > div:first-child {
padding-left: 10px;
}
.char-list .th .cons-pct > div:last-child {
padding-right: 10px;
}
.char-list .cons-pct {
margin: 0 10px;
z-index: 100;
position: relative;
color: #fff;
}
.char-list .cons-pct > div {
flex: 1;
font-size: 12px;
mix-blend-mode: difference;
font-weight: normal;
text-shadow: 1px 1px 0 rgba(0, 0, 0, 0.7);
}
.char-list .cons-bg {
position: absolute;
left: 5px;
right: 5px;
bottom: 5px;
height: 20px;
z-index: 99;
border-radius: 3px;
overflow: hidden;
background: #888;
}
.char-list .life_bg {
background: #888;
}
.char-list .cons-bg > div:last-child {
border-radius: 0 3px 3px 0;
}
.char-list .cons-bg > div {
height: 20px;
}
.char-list .life0 {
background: #5b8185
}
.char-list .life1 {
background: #ce881f;
}
.char-list .life2 {
background: #7ba824;
}
.char-list .life3 {
background: #3e8853;
}
.char-list .life4 {
background: #1a426e;
}
.char-list .life5 {
background: #632e62;
}
.char-list .life6 {
background: #9b2d1f;
}

View File

@ -0,0 +1,88 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-8"/>
<link rel="shortcut icon" href="#"/>
<link rel="stylesheet" type="text/css" href="{{_res_path}}/stat/common.css?v=1.0"/>
<link rel="stylesheet" type="text/css" href="{{_res_path}}/stat/character.css?v=1.0"/>
<link rel="preload" href="{{_res_path}}/font/tttgbnumber.ttf" as="font">
</head>
<body id="container" class="body_box">
<div class="container">
<div class="info_box">
<div class="head-box type{{bgType}}">
{{if mode === "char"}}
<div class="title">角色持有率统计</div>
<div class="label">全角色综合持有率统计</div>
{{else}}
<div class="title">角色{{conNum == -1 ? "命座" : "零一二三四五满"[conNum]+"命"}}分布统计</div>
<div class="label">统计所有角色命座,无论角色是否出场</div>
{{/if}}
<img class="genshin_logo" src="{{_sys_res_path}}/genshin/roleAll/原神.png"/>
</div>
<div class="data-box">
<div class="char-list">
<div class="avatar th">
<div class="index">#</div>
<div class="name">
角色
</div>
<div class="lvl">持有率</div>
<div class="cons">
<div class="cons-pct">
<div class="life0">0命</div>
<div class="life1">1命</div>
<div class="life2">2命</div>
<div class="life3">3命</div>
<div class="life4">4命</div>
<div class="life5">5命</div>
<div class="life6">6命</div>
{{if mode === "char"}}
<div class="life_bg"></div>
{{/if}}
</div>
</div>
</div>
{{each chars char idx}}
<div class="avatar">
<div class="index star{{char.star}}">{{idx+1}}</div>
<div class="name_cont star{{char.star}}">
<div class="name">
<div class="avatar_img">
<img src="{{_sys_res_path}}genshin/logo/side/{{char.name}}.png" onerror="whenError(this)"/>
</div>
<div class="avatar_name">
{{abbr[char.name] || char.name}}
</div>
</div>
</div>
<div class="pct">{{pct(char.hold)}}</div>
<div class="cons">
<div class="cons-pct">
{{each char.cons con idx}}
<div>{{pct(con.value)}}</div>
{{/each}}
{{if mode ==="char"}}
<div>{{pct(1-char.hold)}}</div>
{{/if}}
</div>
<div class="cons-bg">
{{each char.cons con idx}}
<div class="life{{con.id}}" style='{{"width:"+pct(con.value)+"%"}}'></div>
{{/each}}
</div>
</div>
</div>
{{/each}}
</div>
<p class="notice"> 数据来源DGP-Studio-胡桃API . 最后更新时间:{{lastUpdate}} </p>
</div>
<div class="copyright"> Create By Yunzai-Bot & Miao-Plugin</div>
</div>
</div>
</body>
</html>

103
resources/stat/common.css Normal file
View File

@ -0,0 +1,103 @@
@font-face {
font-family: "tttgbnumber";
src: url("../../font/tttgbnumber.ttf");
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: HYWenHei-55W;
src: url('../../font/HYWenHei-55W.ttf');
}
@font-face {
font-family: "NZBZ";
src: url("../font/NZBZ.ttf");
font-weight: normal;
font-style: normal;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
user-select: none;
}
body {
font-size: 18px;
color: #1e1f20;
font-family: PingFangSC-Medium, PingFang SC, sans-serif;
transform: scale(1.4);
transform-origin: 0 0;
background: url("./bg1.png") top left no-repeat #2a3860;
background-size: contain;
width: 600px;
}
.container {
width: 600px;
padding: 20px 15px 10px 15px;
background: url("./footer.png") left bottom no-repeat;
background-size: contain;
}
.head-box {
border-radius: 15px;
padding: 10px 20px;
position: relative;
color: #fff;
margin-top:30px;
}
.head-box .title {
font-size: 36px;
font-family: NZBZ;
}
.head-box .genshin_logo {
position: absolute;
top: 1px;
right: 15px;
width: 97px;
}
.head-box .label{
font-size:16px;
}
.notice {
color: #888;
font-size: 12px;
text-align: right;
padding: 12px 5px 5px;
}
.notice-center{
color:#fff;
text-align: center;
margin-bottom:10px;
text-shadow:1px 1px 1px #333;
}
.copyright {
font-size: 16px;
font-family: "tttgbnumber";
text-align: center;
color: #fff;
position: relative;
text-align: center;
padding-left: 10px;
text-shadow:1px 1px 1px #000;
margin-bottom:10px;
}

BIN
resources/stat/footer.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 333 KiB

View File

@ -10,13 +10,20 @@ git地址https://gitee.com/yoimiya-kokomi/miao-plugin
Miao-Plugin需要最新版本的Yunzai-Bot Miao-Plugin需要最新版本的Yunzai-Bot
将整个miao-plugin文件夹放置在Yunzai-Bot/plugins/目录下重启Yunzai-Bot即可使用。 将整个miao-plugin文件夹放置在Yunzai-Bot/plugins/目录下重启Yunzai-Bot即可使用。
【命令说明】 #老婆 命令说明】
#心海 #神里 #宵宫 查询角色卡片与Yunzai命令一致会以新版卡片样式进行展示 #心海 #神里 #宵宫 查询角色卡片与Yunzai命令一致会以新版卡片样式进行展示
#老婆 #老公 #女朋友展示老婆、老公卡片默认会在角色池符合条件的5个内随机挑选 #老婆 #老公 #女朋友展示老婆、老公卡片默认会在角色池符合条件的5个内随机挑选
#老婆设置心海,神里,宵宫 :设置老婆,如需设置多个老婆可逗号分割。未持有或不符合的角色会自动忽略 #老婆设置心海,神里,宵宫 :设置老婆,如需设置多个老婆可逗号分割。未持有或不符合的角色会自动忽略
#老婆设置随机 设置为随机,角色池中所有符合条件的角色都会作为备选随机展示 #老婆设置随机 设置为随机,角色池中所有符合条件的角色都会作为备选随机展示
#老婆写真 #老婆照片: 会返回不包含任何信息的纯图片 #老婆写真 #老婆照片: 会返回不包含任何信息的纯图片
【#角色/#深渊 命令说明】
#角色持有率:查看当前所有玩家角色持有率统计
#角色命座:查看当前所有玩家角色命座分布统计。如需查看自己的角色命座请使用 #命座 命令
#深渊出场率查看当期9-12层深渊出场前14的角色
#深渊12层出场率查看当期12层深渊所有角色的出场率
> 此版块的数据源来自 DGP-Studio-胡桃API
【自定义角色背景图】 【自定义角色背景图】
如需添加自定义的图片,可放置在 miao-plugin/resources/characterImg 的对应角色名的目录下 如需添加自定义的图片,可放置在 miao-plugin/resources/characterImg 的对应角色名的目录下
支持 jpg/jpeg/png/webp 格式的图片 支持 jpg/jpeg/png/webp 格式的图片