#更新面板支持B服角色

This commit is contained in:
yoimiya-kokomi 2022-06-15 06:07:24 +08:00
parent 5d0c88d3a3
commit 8e57b7690b
7 changed files with 391 additions and 92 deletions

View File

@ -1,27 +1,24 @@
# 1.7.4
# 1.7.5
* `#更新面板`支持B服角色
* 数据来自喵喵API部分功能尚未完全稳定如有问题请反馈给喵喵
* 由于底层逻辑差异,`#圣遗物列表` 只会展示手工查看过角色的圣遗物
* 已知问题:角色天赋的皇冠及命座加成效果显示可能有问题
# 1.7.1 ~ 1.7.4
* 增加`#原图`命令,可获取喵喵角色卡片原图,感谢 **@牧星长** 提供功能
* 对由`#老婆``#刻晴`发出的角色卡片图回复`#原图`可获取对应图像
* `#角色面板` 圣遗物评分功能调整
* 修复小攻击、小防御、小生命有效词条未被正确高亮的问题
* 调整部分角色的评分词条权重值
* `#录入角色面板` 功能恢复
* 可对已有面板数据的角色手工输入更改面板属性,用于伤害测算
* 例如`#录入雷神面板 暴击80爆伤250`
* 暂不支持设置武器、圣遗物、命座、天赋。后续会增加支持
# 1.7.1 ~ 1.7.3
* `#角色面板`、`#圣遗物列表` 使用新的圣遗物评分逻辑计算评分
* 新的圣遗物评分规针对角色进行了细化,同时将得分进行了拉齐
* 根据角色使用不同词条权重计算。感谢 **@糖炒栗子 @秋声 @49631073**等的权重梳理
* 会在后续提供单独命令,供查看角色圣遗物及计算逻辑与计算逻辑
* 因为计分逻辑变更,所以【分数值及档位】相较于升级之前可能有所变化。如有不合理计算请反馈给 @喵喵
* **新版评分会逐步迭代,分数值可能不稳定**。如需关闭新版评分,可编辑喵喵 lib/app/character.js, const isNewScore = false;
* `#更新面板` 功能升级及问题修正
* 修正武器类型、元素伤害字段导致的伤害计算问题
* 夜兰 请在游戏内不出场状况下获取面板属性
* 为`#面板更新`命令增加uid格式校验
* `#录入角色面板` 功能恢复
* 可对已有面板数据的角色手工输入更改面板属性,用于伤害测算
* 例如`#录入雷神面板 暴击80暴伤250`
* 暂不支持设置武器、圣遗物、命座、天赋。后续会增加支持
* `#角色持有率`、`#深渊出场率` 页面细节样式调整
# 1.7.0

View File

@ -71,7 +71,7 @@ export async function character(e, { render, User }) {
let name = msg.replace(/#|老婆|老公/g, "").trim();
msg = msg.replace("面版", "面板")
let dmgRet = /伤害(\d?)$/.exec(msg), dmgIdx = 0;
if (/(详情|详细|面板|面版)$/.test(msg) && !/更新|录入|输入/.test(msg)) {
if (/(详情|详细|面板|面版)\s*$/.test(msg) && !/更新|录入|输入/.test(msg)) {
mode = 'profile';
name = name.replace(/(详情|详细|面板)/, "").trim();
} else if (dmgRet) {
@ -93,7 +93,6 @@ export async function character(e, { render, User }) {
name = name.replace(/录入|输入|详情|详细|面板|数据|[0-9]|\.|\+/g, "").trim()
}
if (mode === "card" && Common.isDisable(e, "char.char")) {
return;
}
@ -117,7 +116,7 @@ export async function character(e, { render, User }) {
}
let char = Character.get(name);
let char = Character.get(name.trim());
if (!char) {
return false;
@ -675,9 +674,21 @@ async function getTargetUid(e) {
if (uidRet) {
return uidRet[0]
}
let uid = false;
let qq = e.user_id;
if (NoteCookie && NoteCookie[qq]) {
let nc = NoteCookie[qq];
if (nc.uid && uidReg.test(nc.uid)) {
return nc.uid;
}
}
uid = await redis.get(`genshin:id-uid:${qq}`) || await redis.get(`genshin:uid:${qq}`);
if (uid && uidReg.test(uid)) {
return uid;
}
try {
let MysApi = await e.getMysApi({
auth: "all",
@ -694,33 +705,17 @@ async function getTargetUid(e) {
e.reply("请先发送【#绑定+你的UID】来绑定查询目标")
return false;
}
} catch (e) {
let qq = e.user_id;
if (NoteCookie && NoteCookie[qq]) {
let nc = NoteCookie[qq];
if (nc.uid && uidReg.test(nc.uid)) {
return nc.uid;
} catch (err) {
console.log(err);
}
}
uid = await redis.get(`genshin:id-uid:${qq}`) || await redis.get(`genshin:uid:${qq}`);
if (uid && uidReg.test(uid)) {
return uid;
} else {
e.reply("请先发送【#绑定+你的UID】来绑定查询目标");
return false;
}
}
return uid;
return uid || false;
}
export async function renderProfile(e, char, render, mode = "profile", params = {}) {
let MysApi = await e.getMysApi({
auth: "all",
targetType: "all",
cookieType: "all"
let selfUser = await e.checkAuth({
auth: "self"
})
let { selfUser } = MysApi;
if (['荧', '空', '主角', '旅行者'].includes(char.name)) {
e.reply("暂不支持主角的面板信息查看");
@ -740,16 +735,16 @@ export async function renderProfile(e, char, render, mode = "profile", params =
return true;
}
let profile = Profile.get(uid, char.id);
let profile = await Profile.get(uid, char.id);
if (!profile) {
if (await refresh()) {
return true;
} else {
e.reply(`请确认${char.name}已展示在【游戏内】的角色展柜中,并打开了“显示角色详情”。然后请使用 #更新面板\n命令来获取${char.name}的面板详情`);
}
await profileHelp(e);
//await profileHelp(e);
return true;
} else if (!['enka', 'input2'].includes(profile.dataSource)) {
} else if (!['enka', 'input2', 'ysin', 'ysin-pre'].includes(profile.dataSource)) {
if (!await refresh()) {
e.reply(`由于数据格式升级,请重新获取面板信息后查看`);
}
@ -889,15 +884,6 @@ export async function enemyLv(e) {
* 圣遗物列表
* */
export async function getArtis(e, { render }) {
let MysApi = await e.getMysApi({
auth: "all",
targetType: "all",
cookieType: "all",
actionName: "查询角色天赋命座等信息"
});
if (!MysApi || !e.targetUser) {
return true;
}
let uid = await getTargetUid(e);
if (!uid) {
@ -974,7 +960,7 @@ export async function getProfileAll(e) {
let chars = [];
lodash.forEach(profiles || [], (ds) => {
if (!['enka', 'input2'].includes(ds.dataSource)) {
if (!['enka', 'input2', 'ysin-pre', 'ysin'].includes(ds.dataSource)) {
return;
}
ds.name && chars.push(ds.name)
@ -992,7 +978,7 @@ export async function getProfileAll(e) {
return true;
}
e.reply("当前已获取面板角色: " + chars.join(", "));
e.reply(`uid${uid} 已获取面板角色: ` + chars.join(", "));
return true;
}

View File

@ -6,6 +6,8 @@ import Character from "./models/Character.js";
import Reliquaries from "./models/Reliquaries.js";
import Data from "./data/enka.js";
import Ysin from "./profile/ysin.js";
import Enka from "./profile/enka.js";
const _path = process.cwd();
const cfgPath = `${_path}/plugins/miao-plugin/config.js`;
@ -28,49 +30,38 @@ function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
function getServ(uid) {
if ((uid + '')[0] === '5') {
return Ysin;
}
return Enka;
}
let Profile = {
async request(uid, e) {
if (uid.toString().length !== 9) {
return false;
}
let profileApi = config.profileApi || function (uid) {
return `https://enka.shinshin.moe/u/${uid}/__data.json`
};
let api = profileApi(uid);
let Serv = getServ(uid);
let inCd = await redis.get(`miao:role-all:${uid}`);
if (inCd === 'loading') {
e.reply("请求过快,请稍后重试..");
return false;
} else if (inCd === 'pending') {
e.reply("距上次请求刷新成功间隔小于5分钟请稍后重试..");
} else if (inCd === 'pending' && false) {
e.reply(`距上次请求刷新成功间隔小于${Serv.cd}分钟,请稍后重试..`);
return false;
}
await redis.set(`miao:role-all:${uid}`, 'loading', { EX: 20 });
e.reply("开始获取数据,可能会需要一定时间~");
await sleep(1000);
let data;
try {
let req = await fetch(api);
data = await req.json();
if (!data.playerInfo) {
if ((uid + '')[0] === '5') {
e.reply(`请求失败:暂时不支持B服角色面板更新请等待服务后续升级`);
} else {
e.reply(`请求失败:${data.msg || "请求错误,请稍后重试"}`);
}
return false;
}
let details = data.avatarInfoList;
if (!details || details.length === 0 || !details[0].propMap) {
e.reply(`请打开角色展柜的显示详情`);
return false;
}
data = await Serv.request({ uid, e, config });
// enka服务测冷却时间5分钟
await redis.set(`miao:role-all:${uid}`, 'pending', { EX: 300 });
let userData = {};
userData = Profile.save(uid, data, 'enka')
return userData;
await redis.set(`miao:role-all:${uid}`, 'pending', { EX: Serv.cd * 60 });
return Profile.save(uid, data, Serv.key);
} catch (err) {
console.log(err);
e.reply(`请求失败`);
@ -78,26 +69,42 @@ let Profile = {
}
},
save(uid, ds, dataSource = 'enka') {
save(uid, data, dataSource = 'enka') {
let userData = {};
const userFile = `${userPath}/${uid}.json`;
if (fs.existsSync(userFile)) {
userData = JSON.parse(fs.readFileSync(userFile, "utf8")) || {};
}
let data;
data = Data.getData(uid, ds);
lodash.assignIn(userData, lodash.pick(data, "uid,name,lv,avatar".split(",")));
userData.chars = userData.chars || {};
lodash.forEach(data.chars, (char, charId) => {
let original = userData.chars[charId] || {};
if (char.dataSource === "ysin-pre" && original && original.dataSource) {
original.dataSource = char.dataSource;
} else {
userData.chars[charId] = char;
}
});
fs.writeFileSync(userFile, JSON.stringify(userData), "", " ");
return data;
},
get(uid, charId) {
saveCharData(uid, ds) {
if (!uid || !ds.id) {
return;
}
let userData = {};
const userFile = `${userPath}/${uid}.json`;
if (fs.existsSync(userFile)) {
userData = JSON.parse(fs.readFileSync(userFile, "utf8")) || {};
}
userData.chars = userData.chars || {};
userData.chars[ds.id] = ds;
fs.writeFileSync(userFile, JSON.stringify(userData), "", " ");
return ds;
},
_get(uid, charId) {
const userFile = `${userPath}/${uid}.json`;
let userData = {};
if (fs.existsSync(userFile)) {
@ -109,6 +116,15 @@ let Profile = {
return false;
},
async get(uid, charId) {
let data = Profile._get(uid, charId);
let Serv = getServ(uid);
if (Serv.getCharData) {
return await Serv.getCharData(uid, data, Profile.saveCharData);
}
return data;
},
getAll(uid) {
const userFile = `${userPath}/${uid}.json`;
let userData = {};
@ -167,7 +183,7 @@ let Profile = {
inputProfile(uid, e) {
let { avatar, inputData } = e;
let char = Character.get(avatar);
let originalData = Profile.get(uid, char.id);
let originalData = Profile._get(uid, char.id);
if (!originalData || !['enka', 'input2'].includes(originalData.dataSource)) {
return `请先获取${char.name}的面板数据后,再进行面板数据更新`;
}
@ -179,8 +195,8 @@ let Profile = {
def: /防御/,
atk: /攻击/,
mastery: /精通/,
cRate: /(暴击率|率|暴击$)/,
cDmg: /(伤|暴击伤害)/,
cRate: /(暴击率|率|暴击$)/,
cDmg: /(伤|暴击伤害)/,
hInc: /治疗/,
recharge: /充能/,
dmgBonus: /[火|水|雷|草|风|岩|冰|素|^]伤/,
@ -197,6 +213,7 @@ let Profile = {
}
let name = dRet[1].trim(),
data = dRet[2].trim();
name = name.replace("爆", "暴");
let range = (src, min = 0, max = 1200) => Math.max(min, Math.min(max, src * 1 || 0));
lodash.forEach(attrMap, (reg, key) => {

View File

@ -0,0 +1,33 @@
import fetch from "node-fetch";
import Data from "../data/enka.js";
let Enka = {
key: "enka",
cd: 5,
async request({ e, uid, config }) {
let profileApi = config.profileApi || function (uid) {
return `https://enka.shinshin.moe/u/${uid}/__data.json`
};
let api = profileApi(uid);
let req = await fetch(api);
let data = await req.json();
if (!data.playerInfo) {
if ((uid + '')[0] === '5') {
e.reply(`请求失败:暂时不支持B服角色面板更新请等待服务后续升级`);
} else {
e.reply(`请求失败:${data.msg || "请求错误,请稍后重试"}`);
}
return false;
}
let details = data.avatarInfoList;
if (!details || details.length === 0 || !details[0].propMap) {
e.reply(`请打开角色展柜的显示详情`);
return false;
}
return Data.getData(uid, data);
}
}
export default Enka;

220
components/profile/ysin.js Normal file
View File

@ -0,0 +1,220 @@
import fetch from "node-fetch";
import lodash from "lodash";
import Character from "../models/Character.js";
import moment from "moment";
import { artiIdx, artiSetMap, attrMap } from "./ysin_meta.js";
import cmeta from "../data/enka_char.js";
const url = "http://miaoapi.cn/profile";
let Ysin = {
key: "ysin",
cd: 1,
async request({ e, uid, avatar = '' }) {
let api = `${url}/list?uid=${uid}`;
let data;
let req = await fetch(api);
data = await req.json();
if (data.status !== 0) {
e.reply(data.msg || "请求失败");
return false;
}
if (!data.uidListData || data.uidListData.length === 0) {
e.reply(`请打开角色展柜的显示详情`);
return false;
}
return Ysin.getData(uid, data);
},
getData(uid, data) {
let ret = {
uid,
chars: {}
}
lodash.forEach({
name: "nickname",
//avatar: "profilePicture.avatarId",
lv: "level"
}, (src, key) => {
ret[key] = lodash.get(data, src, "");
})
lodash.forEach(data.uidListData, (ds) => {
let char = Ysin.getAvatar(ds);
ret.chars[char.id] = char;
})
return ret;
},
getAvatar(ds) {
let char = Character.get(ds.usernameid);
let now = moment();
return {
id: ds.usernameid,
name: char ? char.name : "",
dataSource: "ysin-pre",
updateTime: now.format("YYYY-MM-DD HH:mm:ss"),
lv: ds.level
};
},
async getCharData(uid, ds, saveCharData) {
if (ds.dataSource === "ysin") {
return ds;
}
try {
let api = `${url}/detail?uid=${uid}&avatar=${ds.id}`;
let req = await fetch(api);
let data = await req.json();
if (data.status === 0 && data.uidData) {
data = Ysin.getAvatarDetail(data);
if (data) {
saveCharData(uid, data);
return data;
}
}
return ds;
} catch (err) {
console.log(err);
return ds;
}
},
getAvatarDetail(data) {
let ds = data.uidData;
let char = Character.get(ds.id);
let now = moment();
return {
id: ds.id,
name: char ? char.name : "",
dataSource: "ysin",
updateTime: now.format("YYYY-MM-DD HH:mm:ss"),
lv: ds.level,
fetter: ds.fetterLevel,
attr: Ysin.getAttr(data.uidDataCombatValue),
weapon: Ysin.getWeapon(ds.weapon),
artis: Ysin.getArtifact(data.uidDataByReliquary),
cons: ds.constellationNum,
talent: Ysin.getTalent(char.id, ds.skill),
_priority: 10
};
},
getAttr(data) {
let ret = {};
lodash.forEach({
atk: "attack",
atkBase: "baseATK",
hp: "health",
hpBase: "baseHP",
def: "defense",
defBase: "baseDEF",
mastery: "elementMastery",
cRate: {
src: "critRate",
pct: true
},
cDmg: {
src: "critDamage",
pct: true
},
hInc: {
src: "heal",
pct: true
},
recharge: {
src: "recharge",
pct: true
}
}, (cfg, key) => {
if (!lodash.isObject(cfg)) {
cfg = { src: cfg };
}
let val = data[cfg.src] || 0;
if (cfg.pct) {
val = val * 100
}
ret[key] = val;
})
let maxDmg = 0, dmg = data.addHurt || {};
lodash.forEach("fire,elec,water,grass,wind,rock,ice".split(","), (key) => {
maxDmg = Math.max(dmg[key] * 100, maxDmg);
});
ret.dmg = maxDmg;
ret.phy = dmg.physical * 100;
return ret;
},
getWeapon(weapon) {
return {
name: weapon.name,
star: weapon.rank,
level: weapon.level,
promote: weapon.promoteLevel,
affix: (weapon.affixLevel || 0) + 1
}
},
getArtifact(data) {
let ret = {};
let get = function (d) {
if (!d) {
return [];
}
let name = d.name;
name = name.replace("FIGHT_PROP_", "");
if (!attrMap[name]) {
return [];
}
let value = d.value;
if (value && value < 1) {
value = value * 100;
}
return [attrMap[name], value];
}
lodash.forEach(data, (ds) => {
let sub = ds.appendAffix || [];
let idx = artiIdx[ds.type];
if (!idx) {
return;
}
ret[`arti${idx}`] = {
name: ds.name,
set: artiSetMap[ds.name] || "",
level: ds.level,
main: get(ds.mainAffix),
attrs: [
get(sub[0]),
get(sub[1]),
get(sub[2]),
get(sub[3])
]
}
})
return ret;
},
getTalent(charid, data = {}) {
let cm = cmeta[charid] || {};
let cn = cm.Skills || {};
let idx = 1;
let idxMap = { 0: 'a', 1: 'e', 2: 'q', 'a': 'a', 's': 'e', 'e': 'q' };
lodash.forEach(cn, (n, id) => {
let nRet = /skill_(\w)/.exec(n.toLowerCase());
idxMap[id] = nRet && nRet[1] ? idxMap[nRet[1]] : idxMap[idx];
idx++;
});
let ret = {};
lodash.forEach(data, (ds) => {
let key = idxMap[ds.id];
ret[key] = {
level_original: ds.level,
level_current: ds.level
}
})
return ret;
},
}
export default Ysin;

View File

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

View File

@ -40,9 +40,8 @@
<span class="cfg-hint"> #喵喵设置查他人 + 开启/关闭</span>
{{@other}}
</div>
<div class="cfg-desc">面板查询暂不支持查他人</div>
<div class="cfg-desc">暂不支持关闭面板的查他人</div>
</li>
</ul>
</div>
<div class="cfg-box">