mirror of
https://github.com/ZZZure/ZZZ-Plugin.git
synced 2025-12-17 21:57:44 +00:00
321 lines
10 KiB
JavaScript
321 lines
10 KiB
JavaScript
import { ZZZPlugin } from '../lib/plugin.js';
|
||
import {
|
||
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';
|
||
import { getZzzEnkaData } from '../lib/ekapi/query.js'
|
||
import { _enka_data_to_mys_data } from '../lib/ekapi/enka_to_mys.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();
|
||
let playerInfo = null;
|
||
try {
|
||
playerInfo = await this.getPlayerInfo();
|
||
if (!playerInfo) playerInfo = this.e.player;
|
||
if (!playerInfo) {
|
||
playerInfo = { uid: uid, nickname: `用户${uid}`, level: '??', region_name: '未知服务器' };
|
||
}
|
||
} catch (playerInfoError) {
|
||
playerInfo = { uid: uid, nickname: `用户${uid}`, level: '??', region_name: '错误', error: playerInfoError.message };
|
||
}
|
||
|
||
this.result = null;
|
||
const useEnka = _.get(settings.getConfig('config'), 'useEnka', true); // 读取配置,Enka 优先
|
||
logger.mark(`[panel.js] useEnka 设置值: ${useEnka}`);
|
||
|
||
if (useEnka) {
|
||
logger.mark('[panel.js] 进入 Enka 逻辑块');
|
||
try {
|
||
const enkaData = await getZzzEnkaData(uid);
|
||
if (!enkaData || enkaData === -1 || !enkaData.PlayerInfo) { throw new Error('获取或验证 Enka 数据失败'); }
|
||
this.result = await _enka_data_to_mys_data(enkaData);
|
||
} catch (enkaError) {
|
||
logger.error('处理 Enka 逻辑时出错:', enkaError);
|
||
await this.reply(`处理Enka数据时出错: ${enkaError.message}`);
|
||
return false;
|
||
}
|
||
} else {
|
||
try {
|
||
const { api } = await this.getAPI(); // MYS 需要 api 对象
|
||
// MYS 逻辑需要冷却判断
|
||
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) {
|
||
await this.reply(`${coldTime}秒内只能刷新一次,请稍后再试`);
|
||
return false;
|
||
}
|
||
await redis.set(`ZZZ:PANEL:${uid}:LASTTIME`, Date.now());
|
||
await this.reply('正在刷新面板列表 (MYS API),请稍候...');
|
||
|
||
const mysResult = await refreshPanelFunction(api); // 调用 MYS 刷新函数
|
||
if (!mysResult) { throw new Error('MYS API 返回空结果'); }
|
||
this.result = mysResult; // <<< MYS 结果赋给 this.result
|
||
logger.mark('[panel.js] MYS API refreshPanelFunction 调用完成.');
|
||
} catch (mysError) {
|
||
logger.error('[panel.js] MYS API 刷新出错:', mysError);
|
||
this.reply(`MYS API 刷新出错: ${mysError.message}`);
|
||
return false;
|
||
}
|
||
}
|
||
|
||
if (this.result && Array.isArray(this.result)) { // 确保有有效数据 (非 null, 是数组)
|
||
// 并且至少包含一个角色数据才存,避免存空数组?(可选)
|
||
if (this.result.length > 0) {
|
||
try {
|
||
|
||
await updatePanelData(uid, this.result);
|
||
|
||
} catch (cacheError) {
|
||
logger.error('出错:', cacheError);
|
||
// 记录错误,但可能继续
|
||
}
|
||
} else {
|
||
logger.warn('[panel.js] 获取到的角色列表为空数组,不执行缓存更新。');
|
||
// 如果是 Enka 路径且展示柜为空,这是正常情况
|
||
}
|
||
} else {
|
||
logger.warn('[panel.js] 没有有效的角色列表数据 (this.result),跳过缓存更新。');
|
||
// 如果之前的步骤没有 return false,这里可能需要提示用户
|
||
if (!useEnka) { // MYS 失败的情况
|
||
await this.reply('未能获取或处理有效的面板列表数据。');
|
||
return false; // 如果 MYS 失败且结果无效,应该退出
|
||
}
|
||
// 如果是 Enka 路径且结果无效/非数组,也提示并退出
|
||
await this.reply('处理后的面板数据格式无效。');
|
||
return false;
|
||
}
|
||
const currentResult = this.result || [];
|
||
const newCharCount = (currentResult.length > 0 && currentResult[0]?.isNew !== undefined)
|
||
? currentResult.filter(item => item && item.isNew).length
|
||
: 0;
|
||
const finalData = {
|
||
newChar: newCharCount,
|
||
list: currentResult,
|
||
player: playerInfo,
|
||
uid: uid
|
||
};
|
||
|
||
try {
|
||
await this.render('panel/refresh.html', finalData);
|
||
} catch (renderError) {
|
||
logger.error('[panel.js] 渲染 refresh.html 模板失败:', renderError);
|
||
await this.reply(`生成刷新结果图片时出错: ${renderError.message}`);
|
||
}
|
||
}
|
||
|
||
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) {
|
||
await this.reply(`未找到角色${name}的面板信息,请先刷新面板`);
|
||
return;
|
||
}
|
||
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,
|
||
});
|
||
}
|
||
return 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) {
|
||
await this.reply('UID为空');
|
||
return false;
|
||
}
|
||
if (!data) {
|
||
await this.reply('数据为空');
|
||
return false;
|
||
}
|
||
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) {
|
||
await this.reply('未找到面板数据,请先%刷新面板');
|
||
return false;
|
||
}
|
||
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 => equip.comment === 'SSS' || equip.comment === 'ACE'
|
||
).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) {
|
||
await this.reply('未找到消息源,请引用要查看的图片');
|
||
return false;
|
||
}
|
||
const image = await redis.get(`ZZZ:PANEL:IMAGE:${id}`);
|
||
if (!image) {
|
||
await this.reply('未找到原图');
|
||
return false;
|
||
}
|
||
await this.reply(segment.image(image));
|
||
return false;
|
||
}
|
||
}
|