ZZZ-Plugin/apps/panel.js

298 lines
9.1 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { ZZZPlugin } from '../lib/plugin.js';
import { parsePlayerInfo, refreshPanelFromEnka } from '../model/Enka/enkaApi.js';
import { getCk } from '../lib/common.js';
import {
mergePanel,
getPanelList,
refreshPanel as refreshPanelFunction,
getPanelOrigin,
updatePanelData,
formatPanelData,
getPanelListOrigin,
} from '../lib/avatar.js';
import settings from '../lib/settings.js';
import _ from 'lodash';
import { rulePrefix } from '../lib/common.js';
export class Panel extends ZZZPlugin {
constructor() {
super({
name: '[ZZZ-Plugin]Panel',
dsc: 'zzzpanel',
event: 'message',
priority: _.get(settings.getConfig('priority'), 'panel', 70),
rule: [
{
reg: `${rulePrefix}(.*)面板(展柜)?(刷新|更新|列表)?$`,
fnc: 'handleRule',
},
{
reg: `${rulePrefix}练度(统计)?$`,
fnc: 'proficiency',
},
{
reg: `${rulePrefix}原图$`,
fnc: 'getCharOriImage',
},
],
handler: [
{ key: 'zzz.tool.panel', fn: 'getCharPanelTool' },
{ key: 'zzz.tool.panelList', fn: 'getCharPanelListTool' },
],
});
}
async handleRule() {
if (!this.e.msg) return;
const reg = new RegExp(`${rulePrefix}(.*?)(?:展柜)?面板(?:展柜)?(刷新|更新|列表)?$`);
const pre = this.e.msg.match(reg)[4]?.trim();
const suf = this.e.msg.match(reg)[5]?.trim();
if (['刷新', '更新'].includes(pre) || ['刷新', '更新'].includes(suf))
return await this.refreshPanel();
if (!pre || suf === '列表') return await this.getCharPanelList();
const queryPanelReg = new RegExp(`${rulePrefix}(.*)面板$`);
if (queryPanelReg.test(this.e.msg)) return await this.getCharPanel();
return false;
}
async refreshPanel() {
const uid = await this.getUID();
const lastQueryTime = await redis.get(`ZZZ:PANEL:${uid}:LASTTIME`);
const panelSettings = settings.getConfig('panel');
const coldTime = _.get(panelSettings, 'interval', 300);
if (lastQueryTime && Date.now() - lastQueryTime < 1000 * coldTime) {
return this.reply(`${coldTime}秒内只能更新一次,请稍后再试`);
}
const isEnka = this.e.msg.includes('展柜') || !(await getCk(this.e))
let result
if (isEnka) {
const data = await refreshPanelFromEnka(uid)
.catch(err => err)
if (data instanceof Error) {
logger.warn(`Enka服务调用失败`, data)
return this.reply(`Enka服务调用失败${data.message}`)
}
if (typeof data === 'object') {
await redis.set(`ZZZ:PANEL:${uid}:LASTTIME`, Date.now())
const { playerInfo, panelList } = data
if (!panelList.length) {
return this.reply('面板列表为空,请确保已于游戏中展示角色')
}
result = await mergePanel(uid, panelList)
await this.getPlayerInfo(playerInfo)
} else if (typeof data === 'number') {
return this.reply(`Enka服务调用失败状态码${data}`)
}
} else {
const oriReply = this.reply.bind(this);
let errorMsg = '';
this.reply = (msg) => errorMsg += '\n' + msg;
try {
const { api, deviceFp } = await this.getAPI();
await oriReply('正在更新面板列表,请稍候...');
await this.getPlayerInfo();
await redis.set(`ZZZ:PANEL:${uid}:LASTTIME`, Date.now());
result = await refreshPanelFunction(api, deviceFp);
} catch (err) {
logger.error('面板列表更新失败:', err);
errorMsg = (err.message || '') + errorMsg;
}
this.reply = oriReply;
if (errorMsg && !result) {
return this.reply(`面板列表更新失败,请稍后再试或尝试%更新展柜面板:\n${errorMsg.trim()}`);
}
}
if (!result) return false;
const newChar = result.filter(item => item.isNew);
const finalData = {
newChar: newChar.length,
list: result,
};
await this.render('panel/refresh.html', finalData);
}
async getCharPanelList() {
const uid = await this.getUID();
const result = getPanelList(uid);
if (!result.length) {
return this.reply(`UID:${uid}无本地面板数据,请先%更新面板 或 %更新展柜面板`);
}
const hasCk = !!(await getCk(this.e));
await this.getPlayerInfo(hasCk ? undefined : parsePlayerInfo({ uid }));
const timer = setTimeout(() => {
if (this?.reply) {
this.reply('查询成功,正在下载图片资源,请稍候。');
}
}, 5000);
for (const item of result) {
await item.get_basic_assets();
}
clearTimeout(timer);
const finalData = {
count: result?.length || 0,
list: result,
};
await this.render('panel/list.html', finalData);
}
async getCharPanelListTool(uid, origin = false) {
if (!uid) {
return false;
}
if (origin) {
const result = getPanelListOrigin(uid);
return result;
}
const result = getPanelList(uid);
return result;
}
async getCharPanel() {
const uid = await this.getUID();
const reg = new RegExp(`${rulePrefix}(.+)面板$`);
const match = this.e.msg.match(reg);
if (!match) return false;
const name = match[4];
const data = getPanelOrigin(uid, name);
if (data === false) {
return this.reply(`角色${name}不存在,请确保角色名称/别称存在`);
} else if (data === null) {
return this.reply(`暂无角色${name}面板数据,请先%更新面板`);
}
let handler = this.e.runtime.handler || {};
if (handler.has('zzz.tool.panel')) {
await handler.call('zzz.tool.panel', this.e, {
uid,
data: data,
needSave: false,
});
}
}
async getCharPanelTool(e, _data = {}) {
if (e) this.e = e;
if (e?.reply) this.reply = e.reply;
const {
uid = undefined,
data = undefined,
needSave = true,
reply = true,
needImg = true
} = _data;
if (!uid) {
return this.reply('UID为空');
}
if (!data) {
return this.reply('数据为空');
}
if (needSave) {
updatePanelData(uid, [data]);
}
const timer = setTimeout(() => {
const msg = '查询成功,正在下载图片资源,请稍候。'
if (this?.reply && needImg) {
this.reply(msg);
} else {
logger.mark(msg)
}
}, 5000);
const parsedData = formatPanelData(data);
await parsedData.get_detail_assets();
clearTimeout(timer);
const finalData = {
uid,
charData: parsedData,
};
const image = needImg ? await this.render('panel/card.html', finalData, {
retType: 'base64',
}) : needImg;
if (reply) {
const res = await this.reply(image);
if (res?.message_id && parsedData.role_icon)
await redis.set(
`ZZZ:PANEL:IMAGE:${res.message_id}`,
parsedData.role_icon,
{
EX: 3600 * 3,
}
);
return {
message: res,
image,
};
}
return image;
}
async proficiency() {
const uid = await this.getUID();
const result = getPanelList(uid);
if (!result) {
return this.reply('未找到面板数据,请先%更新面板 或 %更新展柜面板');
}
await this.getPlayerInfo();
result.sort((a, b) => {
return b.proficiency_score - a.proficiency_score;
});
const WeaponCount = result.filter(item => item?.weapon).length,
SWeaponCount = result.filter(
item => item?.weapon && item.weapon.rarity === 'S'
).length;
const general = {
total: result.length,
SCount: result.filter(item => item.rarity === 'S').length,
SWeaponRate: (SWeaponCount / WeaponCount) * 100,
SSSCount: result.reduce((acc, item) => {
if (item.equip) {
acc += item.equip.filter(
equip => ['SSS', 'ACE', 'MAX'].includes(equip.comment)
).length;
}
return acc;
}, 0),
highRank: result.filter(item => item.rank > 4).length,
};
const timer = setTimeout(() => {
if (this?.reply) {
this.reply('查询成功,正在下载图片资源,请稍候。');
}
}, 5000);
for (const item of result) {
await item.get_small_basic_assets();
}
clearTimeout(timer);
const finalData = {
general,
list: result,
};
await this.render('proficiency/index.html', finalData);
}
async getCharOriImage() {
let source;
if (this.e.getReply) {
source = await this.e.getReply();
} else if (this.e.source) {
if (this.e.group?.getChatHistory) {
// 支持at图片添加以及支持后发送
source = (
await this.e.group.getChatHistory(this.e.source?.seq, 1)
).pop();
} else if (this.e.friend?.getChatHistory) {
source = (
await this.e.friend.getChatHistory(this.e.source?.time + 1, 1)
).pop();
}
}
const id = source?.message_id;
if (!id) {
return this.reply('未找到消息源,请引用要查看的图片');
}
const image = await redis.get(`ZZZ:PANEL:IMAGE:${id}`);
if (!image) {
return this.reply('未找到原图');
}
await this.reply(segment.image(image));
}
}