mirror of
https://github.com/ZZZure/ZZZ-Plugin.git
synced 2025-12-16 21:27:47 +00:00
typo: 代码注释
This commit is contained in:
parent
51b3908afd
commit
4c90ca5354
12 changed files with 457 additions and 139 deletions
|
|
@ -1,10 +1,12 @@
|
|||
import { findLowestLatencyUrl } from '../utils/network.js';
|
||||
|
||||
// 保存上次找的节点
|
||||
let lastFindFastestUrl = {
|
||||
url: null,
|
||||
time: 0,
|
||||
};
|
||||
|
||||
// 节点列表
|
||||
const URL_LIB = {
|
||||
'[JPFRP]': 'http://jp-3.lcf.1l1.icu:17217',
|
||||
'[HKFRP]': 'http://hk-1.lcf.1l1.icu:10200',
|
||||
|
|
@ -15,35 +17,46 @@ const URL_LIB = {
|
|||
'[Singapore]': 'https://sg.qxqx.cf',
|
||||
};
|
||||
|
||||
// 文件类型路径
|
||||
const TYPE_PATH = {
|
||||
wiki: 'wiki',
|
||||
resource: 'resource',
|
||||
guide: 'guide',
|
||||
};
|
||||
|
||||
// 资源路径
|
||||
const RESOURCE_PATH = {
|
||||
role: 'role',
|
||||
role_circle: 'role_circle',
|
||||
weapon: 'weapon',
|
||||
};
|
||||
|
||||
// 图鉴路径
|
||||
const GUIDE_PATH = {
|
||||
flower: 'flower',
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取最快节点
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
export const getFatestUrl = async () => {
|
||||
if (
|
||||
lastFindFastestUrl.url &&
|
||||
Date.now() - lastFindFastestUrl.time < 1000 * 60 * 5
|
||||
) {
|
||||
// 如果上次找到的节点在 5 分钟内,直接返回
|
||||
return lastFindFastestUrl.url;
|
||||
}
|
||||
// 获取最快节点
|
||||
const urls = Object.values(URL_LIB);
|
||||
const url = findLowestLatencyUrl(urls);
|
||||
// 保存节点
|
||||
lastFindFastestUrl = {
|
||||
url,
|
||||
time: Date.now(),
|
||||
};
|
||||
// 返回节点
|
||||
return url;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -14,15 +14,21 @@ try {
|
|||
* @param {string} mysid 米游社ID
|
||||
* @returns
|
||||
*/
|
||||
export function getStoken(e, mysid = '') {
|
||||
let userId = e.user_id;
|
||||
let user = new User(e);
|
||||
let file = `${user.stokenPath}${userId}.yaml`;
|
||||
export const getStoken = (e, mysid = '') => {
|
||||
// 获取QQ号
|
||||
const userId = e.user_id;
|
||||
// 实例化用户
|
||||
const user = new User(e);
|
||||
// 获取 sk 文件路径
|
||||
const filePath = `${user.stokenPath}${userId}.yaml`;
|
||||
try {
|
||||
let cks = fs.readFileSync(file, 'utf-8');
|
||||
cks = YAML.parse(cks);
|
||||
// 读取文件
|
||||
const file = fs.readFileSync(filePath, 'utf-8');
|
||||
// 解析文件
|
||||
const cks = YAML.parse(file);
|
||||
for (const ck in cks) {
|
||||
if (cks[ck]['stuid'] === mysid) {
|
||||
if (cks?.[ck]?.['stuid'] && cks[ck]['stuid'] === mysid) {
|
||||
// 如果 ck 存在并且 stuid 与 mysid 相同则返回(这两者都是字符串,不需要类型转换,因此不需要使用弱比较,强比较速度更快,没有隐式转换一步骤,注意代码规范与代码风格)
|
||||
return cks[ck];
|
||||
}
|
||||
}
|
||||
|
|
@ -31,20 +37,26 @@ export function getStoken(e, mysid = '') {
|
|||
logger.debug(`[zzz:error]getStoken`, error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 此方法依赖逍遥插件
|
||||
* @returns {Promise<void>}
|
||||
* @param {*} e yunzai Event
|
||||
* @param {*} _user
|
||||
* @param {string} zzzUid
|
||||
* @param {string} authAppid
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
export async function getAuthKey(e, _user, zzzUid, authAppid = 'csc') {
|
||||
export const getAuthKey = async (e, _user, zzzUid, authAppid = 'csc') => {
|
||||
if (!User) {
|
||||
throw new Error('未安装逍遥插件,无法自动刷新抽卡链接');
|
||||
}
|
||||
// 获取 UID 数据
|
||||
const uidData = _user.getUidData(zzzUid, 'zzz', e);
|
||||
if (!uidData || uidData?.type != 'ck' || !uidData?.ltuid) {
|
||||
throw new Error(`当前UID${zzzUid}未绑定cookie,请切换UID后尝试`);
|
||||
}
|
||||
// 获取 sk
|
||||
let ck = getStoken(e, uidData.ltuid);
|
||||
if (!ck) {
|
||||
throw new Error(
|
||||
|
|
@ -56,7 +68,9 @@ export async function getAuthKey(e, _user, zzzUid, authAppid = 'csc') {
|
|||
`当前UID${zzzUid}查询所使用的米游社ID${ck.stuid}与当前切换的米游社ID${uidData.ltuid}不匹配,请切换UID后尝试`
|
||||
);
|
||||
}
|
||||
// 拼接 sk
|
||||
ck = `stuid=${ck.stuid};stoken=${ck.stoken};mid=${ck.mid};`;
|
||||
// 实例化 API
|
||||
const api = new MysZZZApi(zzzUid, ck);
|
||||
let type = 'zzzAuthKey';
|
||||
switch (authAppid) {
|
||||
|
|
@ -66,14 +80,16 @@ export async function getAuthKey(e, _user, zzzUid, authAppid = 'csc') {
|
|||
}
|
||||
default:
|
||||
}
|
||||
logger.mark(type);
|
||||
// 获取链接
|
||||
const { url, headers, body } = api.getUrl(type);
|
||||
logger.mark(url);
|
||||
// 发送请求
|
||||
let res = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers,
|
||||
body,
|
||||
});
|
||||
// 获取数据
|
||||
res = await res.json();
|
||||
// 返回 authkey
|
||||
return res?.data?.authkey;
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -11,17 +11,20 @@ import { char } from './convert.js';
|
|||
* @param {boolean} origin 是否返回原始数据
|
||||
* @returns {Promise<ZZZAvatarBasic[] | null>}
|
||||
*/
|
||||
export async function getAvatarBasicList(e, api, deviceFp, origin = false) {
|
||||
export const getAvatarBasicList = async (e, api, deviceFp, origin = false) => {
|
||||
// 获取米游社角色列表
|
||||
const avatarBaseListData = await api.getFinalData(e, 'zzzAvatarList', {
|
||||
deviceFp,
|
||||
});
|
||||
if (!avatarBaseListData) return null;
|
||||
// 是否返回原始数据
|
||||
if (origin) return avatarBaseListData.avatar_list;
|
||||
// 格式化数据
|
||||
const result = avatarBaseListData.avatar_list.map(
|
||||
item => new ZZZAvatarBasic(item)
|
||||
);
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取角色详细信息列表
|
||||
|
|
@ -31,9 +34,11 @@ export async function getAvatarBasicList(e, api, deviceFp, origin = false) {
|
|||
* @param {string} deviceFp
|
||||
* @param {boolean} origin 是否返回原始数据
|
||||
*/
|
||||
export async function getAvatarInfoList(e, api, deviceFp, origin = false) {
|
||||
export const getAvatarInfoList = async (e, api, deviceFp, origin = false) => {
|
||||
// 获取角色基础信息列表
|
||||
const avatarBaseList = await getAvatarBasicList(e, api, deviceFp, origin);
|
||||
if (!avatarBaseList) return null;
|
||||
// 获取角色详细信息
|
||||
const avatarInfoList = await api.getFinalData(e, 'zzzAvatarInfo', {
|
||||
deviceFp,
|
||||
query: {
|
||||
|
|
@ -41,12 +46,14 @@ export async function getAvatarInfoList(e, api, deviceFp, origin = false) {
|
|||
},
|
||||
});
|
||||
if (!avatarInfoList) return null;
|
||||
// 是否返回原始数据
|
||||
if (origin) return avatarInfoList.avatar_list;
|
||||
// 格式化数据
|
||||
const result = avatarInfoList.avatar_list.map(
|
||||
item => new ZZZAvatarInfo(item)
|
||||
);
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 刷新面板
|
||||
|
|
@ -56,38 +63,47 @@ export async function getAvatarInfoList(e, api, deviceFp, origin = false) {
|
|||
* @param {string} deviceFp
|
||||
* @returns {Promise<ZZZAvatarInfo[] | null>}
|
||||
*/
|
||||
export async function refreshPanel(e, api, uid, deviceFp) {
|
||||
export const refreshPanel = async (e, api, uid, deviceFp) => {
|
||||
// 获取已保存数据
|
||||
const originData = getPanelData(uid);
|
||||
// 获取新数据
|
||||
const newData = await getAvatarInfoList(e, api, deviceFp, true);
|
||||
if (!newData) return null;
|
||||
// 初始化最终数据
|
||||
const finalData = [...newData];
|
||||
// 如果有已保存的数据
|
||||
if (originData) {
|
||||
// 合并数据
|
||||
for (const item of originData) {
|
||||
if (!finalData.find(i => i.id === item.id)) {
|
||||
// 将已保存的数据添加到最终数据中(放在后面)
|
||||
finalData.push(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
// 保存数据
|
||||
savePanelData(uid, finalData);
|
||||
// 格式化数据
|
||||
finalData.forEach(item => {
|
||||
item.isNew = newData.find(i => i.id === item.id);
|
||||
});
|
||||
const formattedData = finalData.map(item => new ZZZAvatarInfo(item));
|
||||
for (const item of formattedData) {
|
||||
// 下载图片资源
|
||||
await item.get_basic_assets();
|
||||
}
|
||||
return formattedData;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
*获取面板数据
|
||||
* @param {string} uid
|
||||
* @returns {ZZZAvatarInfo[]}
|
||||
*/
|
||||
export function getPanelList(uid) {
|
||||
export const getPanelList = uid => {
|
||||
const data = getPanelData(uid);
|
||||
return data.map(item => new ZZZAvatarInfo(item));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取某个角色的面板数据
|
||||
|
|
@ -95,12 +111,15 @@ export function getPanelList(uid) {
|
|||
* @param {string} name
|
||||
* @returns {ZZZAvatarInfo | null}
|
||||
*/
|
||||
export function getPanel(uid, name) {
|
||||
export const getPanel = (uid, name) => {
|
||||
const _data = getPanelData(uid);
|
||||
// 获取所有面板数据
|
||||
const data = _data.map(item => new ZZZAvatarInfo(item));
|
||||
// 通过名称(包括别名)获取角色 ID
|
||||
const id = char.atlasToID(name);
|
||||
if (!id) return null;
|
||||
// 通过 ID 获取角色数据
|
||||
const result = data.find(item => item.id === id);
|
||||
if (!result) return null;
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -3,12 +3,18 @@ import { getStoken } from './authkey.js';
|
|||
|
||||
export const rulePrefix = '^((#|\\%)?(zzz|ZZZ|绝区零))';
|
||||
|
||||
export async function getCk(e, s = false) {
|
||||
/**
|
||||
* 获取米游社用户的 cookie
|
||||
* @param {Object} e yunzai事件
|
||||
* @param {boolean} s 是否获取 stoken
|
||||
* @returns {Promise<Object>}
|
||||
*/
|
||||
export const getCk = async (e, s = false) => {
|
||||
e.isSr = true;
|
||||
let stoken = '';
|
||||
let user = new User(e);
|
||||
const user = new User(e);
|
||||
if (s) {
|
||||
stoken = await getStoken(e);
|
||||
stoken = getStoken(e);
|
||||
}
|
||||
if (typeof user.getCk === 'function') {
|
||||
let ck = user.getCk();
|
||||
|
|
@ -19,7 +25,7 @@ export async function getCk(e, s = false) {
|
|||
});
|
||||
return ck;
|
||||
}
|
||||
let mysUser = (await user.user()).getMysUser('zzz');
|
||||
const mysUser = (await user.user()).getMysUser('zzz');
|
||||
let ck;
|
||||
if (mysUser) {
|
||||
ck = {
|
||||
|
|
@ -33,4 +39,4 @@ export async function getCk(e, s = false) {
|
|||
};
|
||||
}
|
||||
return ck;
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -25,19 +25,24 @@ const ZZZ_SUIT_PATH = path.join(imageResourcesPath, 'suit');
|
|||
*/
|
||||
const downloadFile = async (url, savePath) => {
|
||||
const _download = async (url, savePath, retry = 0) => {
|
||||
// 重试次数超过 5 次则返回 null
|
||||
if (retry > 5) {
|
||||
return null;
|
||||
}
|
||||
// 下载文件
|
||||
try {
|
||||
const download = await fetch(url);
|
||||
const arrayBuffer = await download.arrayBuffer();
|
||||
const buffer = Buffer.from(arrayBuffer);
|
||||
// 保存文件
|
||||
if (!fs.existsSync(path.dirname(savePath))) {
|
||||
fs.mkdirSync(path.dirname(savePath), { recursive: true });
|
||||
}
|
||||
fs.writeFileSync(savePath, buffer);
|
||||
// 返回保存路径
|
||||
return savePath;
|
||||
} catch (error) {
|
||||
// 下载失败,重试
|
||||
return await _download(url, savePath, retry + 1);
|
||||
}
|
||||
};
|
||||
|
|
@ -97,7 +102,7 @@ export const getWeaponImage = async id => {
|
|||
*/
|
||||
export const getRoleImage = async id => {
|
||||
const sprite = char.IDToCharSprite(id);
|
||||
if (sprite === null) return null;
|
||||
if (!sprite) return null;
|
||||
const filename = `IconRole${sprite}.png`;
|
||||
const rolePath = path.join(ZZZ_ROLE_PATH, filename);
|
||||
if (fs.existsSync(rolePath)) return rolePath;
|
||||
|
|
@ -114,7 +119,7 @@ export const getRoleImage = async id => {
|
|||
*/
|
||||
export const getRoleCircleImage = async id => {
|
||||
const sprite = char.IDToCharSprite(id);
|
||||
if (sprite === null) return null;
|
||||
if (!sprite) return null;
|
||||
const filename = `IconRoleCircle${sprite}.png`;
|
||||
const roleCirclePath = path.join(ZZZ_ROLE_CIRCLE_PATH, filename);
|
||||
if (fs.existsSync(roleCirclePath)) return roleCirclePath;
|
||||
|
|
|
|||
210
lib/gacha.js
210
lib/gacha.js
|
|
@ -4,6 +4,7 @@ import { rank } from './convert.js';
|
|||
import { getGachaLog, saveGachaLog } from './db.js';
|
||||
import { ZZZ_GET_GACHA_LOG_API } from './mysapi/api.js';
|
||||
|
||||
// 池子代码
|
||||
export const gacha_type_meta_data = {
|
||||
音擎频段: ['3001'],
|
||||
独家频段: ['2001'],
|
||||
|
|
@ -11,13 +12,50 @@ export const gacha_type_meta_data = {
|
|||
邦布频段: ['5001'],
|
||||
};
|
||||
|
||||
// 欧非阈值
|
||||
const FLOORS_MAP = {
|
||||
邦布频段: [50, 70],
|
||||
音擎频段: [50, 70],
|
||||
独家频段: [60, 80],
|
||||
常驻频段: [60, 80],
|
||||
};
|
||||
|
||||
// 欧非标签
|
||||
const HOMO_TAG = ['非到极致', '运气不好', '平稳保底', '小欧一把', '欧狗在此'];
|
||||
|
||||
// 欧非表情
|
||||
const EMOJI = [
|
||||
[4, 8, 13],
|
||||
[1, 10, 5],
|
||||
[16, 15, 2],
|
||||
[12, 3, 9],
|
||||
[6, 14, 7],
|
||||
];
|
||||
|
||||
// 常驻名称
|
||||
const NORMAL_LIST = [
|
||||
'「11号」',
|
||||
'猫又',
|
||||
'莱卡恩',
|
||||
'丽娜',
|
||||
'格莉丝',
|
||||
'珂蕾妲',
|
||||
'拘缚者',
|
||||
'燃狱齿轮',
|
||||
'嵌合编译器',
|
||||
'钢铁肉垫',
|
||||
'硫磺石',
|
||||
'啜泣摇篮',
|
||||
];
|
||||
|
||||
/**
|
||||
* @param {string} authKey
|
||||
* @param {string} gachaType
|
||||
* 获取抽卡链接
|
||||
* @param {string} authKey 米游社认证密钥
|
||||
* @param {string} gachaType 祈愿类型(池子代码)
|
||||
* @param {string} initLogGachaBaseType
|
||||
* @param {number} page
|
||||
* @param {string} endId
|
||||
* @returns {Promise<string>}
|
||||
* @param {number} page 页数
|
||||
* @param {string} endId 最后一个数据的 id
|
||||
* @returns {Promise<string>} 抽卡链接
|
||||
*/
|
||||
export const getZZZGachaLink = async (
|
||||
authKey,
|
||||
|
|
@ -26,10 +64,11 @@ export const getZZZGachaLink = async (
|
|||
page = 1,
|
||||
endId = '0'
|
||||
) => {
|
||||
// 暂时直接写死服务器为国服
|
||||
const serverId = 'prod_gf_cn';
|
||||
const url = ZZZ_GET_GACHA_LOG_API;
|
||||
const timestamp = Math.floor(Date.now() / 1000);
|
||||
|
||||
// 请求参数
|
||||
const params = new URLSearchParams({
|
||||
authkey_ver: '1',
|
||||
sign_type: '2',
|
||||
|
|
@ -50,26 +89,27 @@ export const getZZZGachaLink = async (
|
|||
size: '20',
|
||||
end_id: endId,
|
||||
});
|
||||
|
||||
// 完整链接
|
||||
return `${url}?${params}`;
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} authKey
|
||||
* @param {*} gachaType
|
||||
* @param {*} initLogGachaBaseType
|
||||
* @param {number} page
|
||||
* @param {string} endId
|
||||
* @returns {Promise<ZZZGachaLogResp>}
|
||||
* 通过米游社认证密钥获取抽卡记录
|
||||
* @param {string} authKey 米游社认证密钥
|
||||
* @param {string} gachaType 祈愿类型(池子代码)
|
||||
* @param {string} initLogGachaBaseType
|
||||
* @param {number} page 页数
|
||||
* @param {string} endId 最后一个数据的 id
|
||||
* @returns {Promise<ZZZGachaLogResp>} 抽卡记录
|
||||
*/
|
||||
export async function getZZZGachaLogByAuthkey(
|
||||
export const getZZZGachaLogByAuthkey = async (
|
||||
authKey,
|
||||
gachaType = '2001',
|
||||
initLogGachaBaseType = '2',
|
||||
page = 1,
|
||||
endId = '0'
|
||||
) {
|
||||
) => {
|
||||
// 获取抽卡链接
|
||||
const link = await getZZZGachaLink(
|
||||
authKey,
|
||||
gachaType,
|
||||
|
|
@ -77,25 +117,25 @@ export async function getZZZGachaLogByAuthkey(
|
|||
page,
|
||||
endId
|
||||
);
|
||||
|
||||
// 发送请求
|
||||
const response = await fetch(link, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
// 获取数据
|
||||
const data = await response.json();
|
||||
|
||||
if (!data || !data?.data) return null;
|
||||
|
||||
return new ZZZGachaLogResp(data.data);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} authKey
|
||||
* @param {string} uid
|
||||
* 更新抽卡数据
|
||||
* @param {string} authKey 米游社认证密钥
|
||||
* @param {string} uid ZZZUID
|
||||
* @returns {Promise<{
|
||||
* data: {
|
||||
* [x: string]: SingleGachaLog[];
|
||||
|
|
@ -103,26 +143,39 @@ export async function getZZZGachaLogByAuthkey(
|
|||
* count: {
|
||||
* [x: string]: number;
|
||||
* }
|
||||
* }>}
|
||||
* }>} 更新后的抽卡数据
|
||||
*/
|
||||
export async function updateGachaLog(authKey, uid) {
|
||||
export const updateGachaLog = async (authKey, uid) => {
|
||||
// 获取之前的抽卡数据
|
||||
let previousLog = getGachaLog(uid);
|
||||
if (!previousLog) {
|
||||
// 如果没有数据,初始化为空对象
|
||||
previousLog = {};
|
||||
}
|
||||
// 新的抽卡数据
|
||||
let newCount = {};
|
||||
// 遍历所有池子
|
||||
for (const name in gacha_type_meta_data) {
|
||||
if (!previousLog[name]) {
|
||||
// 如果没有数据,初始化为空数组
|
||||
previousLog[name] = [];
|
||||
}
|
||||
// 初始化新数据计数(当前池子)
|
||||
newCount[name] = 0;
|
||||
// 转换为 SingleGachaLog 对象
|
||||
previousLog[name] = previousLog[name].map(i => new SingleGachaLog(i));
|
||||
// 获取上一次保存的数据
|
||||
const lastSaved = previousLog[name]?.[0];
|
||||
// 初始化页数和最后一个数据的 id
|
||||
let page = 1;
|
||||
let endId = '0';
|
||||
// 新数据
|
||||
const newData = [];
|
||||
// 遍历当前池子的所有类型
|
||||
for (const type of gacha_type_meta_data[name]) {
|
||||
// 循环获取数据
|
||||
queryLabel: while (true) {
|
||||
// 获取抽卡记录
|
||||
const log = await getZZZGachaLogByAuthkey(
|
||||
authKey,
|
||||
type,
|
||||
|
|
@ -133,58 +186,42 @@ export async function updateGachaLog(authKey, uid) {
|
|||
if (!log || !log?.list || log?.list?.length === 0) {
|
||||
break;
|
||||
}
|
||||
// 遍历数据 (从最新的开始)
|
||||
for (const item of log.list) {
|
||||
if (lastSaved && lastSaved.equals(item)) {
|
||||
// 如果数据相同,说明已经获取完毕
|
||||
break queryLabel;
|
||||
}
|
||||
// 添加到新数据中
|
||||
newData.push(item);
|
||||
// 当前池子新数据计数加一
|
||||
newCount[name]++;
|
||||
}
|
||||
// 更新页数和最后一个数据的 id
|
||||
endId = log.list[log.list.length - 1]?.id || endId;
|
||||
page++;
|
||||
// 防止请求过快
|
||||
await sleep(400);
|
||||
}
|
||||
}
|
||||
// 合并新数据和之前的数据
|
||||
previousLog[name] = [...newData, ...previousLog[name]];
|
||||
}
|
||||
// 保存数据到文件
|
||||
saveGachaLog(uid, previousLog);
|
||||
// 返回数据
|
||||
return {
|
||||
data: previousLog,
|
||||
count: newCount,
|
||||
};
|
||||
}
|
||||
|
||||
const HOMO_TAG = ['非到极致', '运气不好', '平稳保底', '小欧一把', '欧狗在此'];
|
||||
const EMOJI = [
|
||||
[4, 8, 13],
|
||||
[1, 10, 5],
|
||||
[16, 15, 2],
|
||||
[12, 3, 9],
|
||||
[6, 14, 7],
|
||||
];
|
||||
const NORMAL_LIST = [
|
||||
'「11号」',
|
||||
'猫又',
|
||||
'莱卡恩',
|
||||
'丽娜',
|
||||
'格莉丝',
|
||||
'珂蕾妲',
|
||||
'拘缚者',
|
||||
'燃狱齿轮',
|
||||
'嵌合编译器',
|
||||
'钢铁肉垫',
|
||||
'硫磺石',
|
||||
'啜泣摇篮',
|
||||
];
|
||||
|
||||
const FLOORS_MAP = {
|
||||
邦布频段: [50, 70],
|
||||
音擎频段: [50, 70],
|
||||
独家频段: [60, 80],
|
||||
常驻频段: [60, 80],
|
||||
};
|
||||
|
||||
function getLevelFromList(ast, lst) {
|
||||
/**
|
||||
* 欧非分析
|
||||
* @param {number} ast
|
||||
* @param {number[]} lst
|
||||
*/
|
||||
const getLevelFromList = (ast, lst) => {
|
||||
if (ast === 0) {
|
||||
return 2;
|
||||
}
|
||||
|
|
@ -197,40 +234,77 @@ function getLevelFromList(ast, lst) {
|
|||
}
|
||||
}
|
||||
return level;
|
||||
}
|
||||
};
|
||||
|
||||
export async function anaylizeGachaLog(uid) {
|
||||
/**
|
||||
* 抽卡分析
|
||||
* @param {string} uid ZZZUID
|
||||
* @returns {Promise<{
|
||||
* name: string;
|
||||
* timeRange: string;
|
||||
* list: SingleGachaLog[];
|
||||
* lastFive: number | string;
|
||||
* fiveStars: number;
|
||||
* upCount: number;
|
||||
* totalCount: number;
|
||||
* avgFive: string;
|
||||
* avgUp: string;
|
||||
* level: number;
|
||||
* tag: string;
|
||||
* emoji: number;
|
||||
* }[]>} 分析结果
|
||||
*/
|
||||
export const anaylizeGachaLog = async uid => {
|
||||
// 读取已保存的数据
|
||||
const savedData = getGachaLog(uid);
|
||||
if (!savedData) {
|
||||
return null;
|
||||
}
|
||||
// 初始化结果
|
||||
const result = [];
|
||||
// 遍历所有池子
|
||||
for (const name in savedData) {
|
||||
// 转换为 SingleGachaLog 对象
|
||||
const data = savedData[name].map(item => new SingleGachaLog(item));
|
||||
// 获取最早和最晚的数据
|
||||
const earliest = data[data.length - 1];
|
||||
const latest = data[0];
|
||||
// 初始化五星列表
|
||||
const list = [];
|
||||
// 初始化最后五星位置
|
||||
let lastFive = null;
|
||||
// 初始化前一个索引
|
||||
let preIndex = 0;
|
||||
// 遍历数据
|
||||
let i = 0;
|
||||
for (const item of data) {
|
||||
// 是否为UP
|
||||
let isUp = true;
|
||||
// 如果是五星
|
||||
if (item.rank_type === '4') {
|
||||
// 下载图片资源
|
||||
await item.get_assets();
|
||||
// 判断是否为常驻
|
||||
if (NORMAL_LIST.includes(item.name)) {
|
||||
isUp = false;
|
||||
}
|
||||
// 如果是第一个五星
|
||||
if (lastFive === null) {
|
||||
// 记录位置
|
||||
lastFive = i;
|
||||
}
|
||||
// 如果不是第一个五星
|
||||
if (list.length > 0) {
|
||||
// 计算前一个五星到当前五星的次数(即前一个五星出卡所用的抽数)
|
||||
list[list.length - 1]['totalCount'] = i - preIndex;
|
||||
// 根据次数设置颜色
|
||||
if (i - preIndex <= FLOORS_MAP[name][0]) {
|
||||
list[list.length - 1]['color'] = 'rgb(63, 255, 0)';
|
||||
} else if (i - preIndex >= FLOORS_MAP[name][1]) {
|
||||
list[list.length - 1]['color'] = 'rgb(255, 20, 20)';
|
||||
}
|
||||
}
|
||||
// 添加到列表中
|
||||
list.push({
|
||||
...item,
|
||||
rank_type_label: rank.getRankChar(item.rank_type),
|
||||
|
|
@ -240,8 +314,11 @@ export async function anaylizeGachaLog(uid) {
|
|||
});
|
||||
preIndex = i;
|
||||
}
|
||||
// 如果是最后一个数据
|
||||
if (i === data.length - 1 && list.length > 0) {
|
||||
// 计算前一个五星到当前五星的次数(即前一个五星出卡所用的抽数)
|
||||
list[list.length - 1]['totalCount'] = i - preIndex + 1;
|
||||
// 根据次数设置颜色
|
||||
if (i - preIndex + 1 <= FLOORS_MAP[name][0]) {
|
||||
list[list.length - 1]['color'] = 'rgb(63, 255, 0)';
|
||||
} else if (i - preIndex + 1 >= FLOORS_MAP[name][1]) {
|
||||
|
|
@ -250,23 +327,37 @@ export async function anaylizeGachaLog(uid) {
|
|||
}
|
||||
i++;
|
||||
}
|
||||
// 计算五星数量和UP数量
|
||||
const upCount = list.filter(i => i.isUp).length;
|
||||
// 计算总次数
|
||||
const totalCount = data.length;
|
||||
// 计算五星数量
|
||||
const fiveStars = list.length;
|
||||
// 初始化时间范围
|
||||
let timeRange = '还没有抽卡';
|
||||
// 初始化平均五星次数
|
||||
let avgFive = '-';
|
||||
// 初始化平均UP次数
|
||||
let avgUp = '-';
|
||||
// 初始化欧非等级
|
||||
let level = 2;
|
||||
// 如果有数据
|
||||
if (data.length > 0) {
|
||||
// 设置时间范围
|
||||
timeRange = `${latest.time} ~ ${earliest.time}`;
|
||||
// 计算平均五星次数
|
||||
if (fiveStars > 0)
|
||||
avgFive = ((totalCount - lastFive) / fiveStars).toFixed(1);
|
||||
// 计算平均UP次数
|
||||
if (upCount > 0) avgUp = ((totalCount - lastFive) / upCount).toFixed(1);
|
||||
}
|
||||
// 如果没有最后五星
|
||||
if (!lastFive && fiveStars === 0) {
|
||||
// 设置最后五星为 '-'
|
||||
if (totalCount > 0) lastFive = totalCount;
|
||||
else lastFive = '-';
|
||||
}
|
||||
// 根据不同池子计算欧非等级
|
||||
if (avgUp !== '-') {
|
||||
if ('音擎频段' === name) {
|
||||
level = getLevelFromList(avgUp, [62, 75, 88, 99, 111]);
|
||||
|
|
@ -281,10 +372,13 @@ export async function anaylizeGachaLog(uid) {
|
|||
level = getLevelFromList(avgFive, [53, 60, 68, 73, 75]);
|
||||
}
|
||||
}
|
||||
// 设置欧非标签
|
||||
const tag = HOMO_TAG[level];
|
||||
// 设置欧非表情
|
||||
const emojis = EMOJI[level];
|
||||
// 随机选取一个
|
||||
const emoji = emojis[Math.floor(Math.random() * emojis.length)];
|
||||
// 写入数据
|
||||
result.push({
|
||||
name,
|
||||
timeRange,
|
||||
|
|
@ -301,4 +395,4 @@ export async function anaylizeGachaLog(uid) {
|
|||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -2,10 +2,12 @@ import md5 from 'md5';
|
|||
import _ from 'lodash';
|
||||
import crypto from 'crypto';
|
||||
import ZZZApiTool from './mysapi/tool.js';
|
||||
import { randomString } from '../utils/data.js';
|
||||
import MysApi from '../../genshin/model/mys/mysApi.js';
|
||||
|
||||
// const DEVICE_ID = randomString(32).toUpperCase()
|
||||
const DEVICE_NAME = randomString(_.random(1, 10));
|
||||
// const DEVICE_NAME = randomString(_.random(1, 10));
|
||||
|
||||
const game_region = [
|
||||
'prod_gf_cn',
|
||||
'prod_gf_us',
|
||||
|
|
@ -13,9 +15,14 @@ const game_region = [
|
|||
'prod_gf_jp',
|
||||
'prod_gf_sg',
|
||||
];
|
||||
|
||||
/**
|
||||
* 米游社ZZZAPI(继承自MysApi)
|
||||
*/
|
||||
export default class MysZZZApi extends MysApi {
|
||||
constructor(uid, cookie, option = {}) {
|
||||
super(uid, cookie, option, true);
|
||||
// 初始化 uid、server、apiTool
|
||||
this.uid = uid;
|
||||
this.server = this.getServer(uid);
|
||||
this.apiTool = new ZZZApiTool(uid, this.server);
|
||||
|
|
@ -30,6 +37,10 @@ export default class MysZZZApi extends MysApi {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取服务器
|
||||
* @returns {string}
|
||||
*/
|
||||
getServer() {
|
||||
const _uid = this.uid?.toString();
|
||||
if (_uid.length < 10) {
|
||||
|
|
@ -47,10 +58,19 @@ export default class MysZZZApi extends MysApi {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取请求网址
|
||||
* @param {string} type
|
||||
* @param {object} data
|
||||
* @returns {object|boolean}
|
||||
*/
|
||||
getUrl(type, data = {}) {
|
||||
// 设置设备ID
|
||||
data.deviceId = this._device;
|
||||
let urlMap = this.apiTool.getUrlMap(data);
|
||||
// 获取请求地址
|
||||
const urlMap = this.apiTool.getUrlMap(data);
|
||||
if (!urlMap[type]) return false;
|
||||
// 获取请求参数(即APITool中默认的请求参数,此参数理应是不可获取的,详细请参照 lib/mysapi/tool.js`)
|
||||
let {
|
||||
url,
|
||||
query = '',
|
||||
|
|
@ -58,7 +78,9 @@ export default class MysZZZApi extends MysApi {
|
|||
noDs = false,
|
||||
dsSalt = '',
|
||||
} = urlMap[type];
|
||||
// 如果有query,拼接到url上
|
||||
if (query) url += `?${query}`;
|
||||
// 如果传入了 query 参数,将 query 参数拼接到 url 上
|
||||
if (data.query) {
|
||||
let str = '';
|
||||
for (let key in data.query) {
|
||||
|
|
@ -77,19 +99,22 @@ export default class MysZZZApi extends MysApi {
|
|||
url += `?${str}`;
|
||||
}
|
||||
}
|
||||
// 写入 body
|
||||
if (body) body = JSON.stringify(body);
|
||||
|
||||
// 获取请求头
|
||||
let headers = this.getHeaders(query, body);
|
||||
if (data.deviceFp) {
|
||||
headers['x-rpc-device_fp'] = data.deviceFp;
|
||||
// 兼容喵崽
|
||||
this._device_fp = { data: { device_fp: data.deviceFp } };
|
||||
}
|
||||
// 写入 cookie
|
||||
headers.cookie = this.cookie;
|
||||
|
||||
// 写入设备ID
|
||||
if (this._device) {
|
||||
headers['x-rpc-device_id'] = this._device;
|
||||
}
|
||||
// 写入DS
|
||||
switch (dsSalt) {
|
||||
case 'web': {
|
||||
headers.DS = this.getDS2();
|
||||
|
|
@ -106,6 +131,7 @@ export default class MysZZZApi extends MysApi {
|
|||
} else {
|
||||
headers.DS = this.getDs(query, body);
|
||||
}
|
||||
// 如果不需要 DS,删除 DS
|
||||
if (noDs) {
|
||||
delete headers.DS;
|
||||
if (this._device) {
|
||||
|
|
@ -115,9 +141,16 @@ export default class MysZZZApi extends MysApi {
|
|||
}
|
||||
}
|
||||
logger.debug(`[mysapi]请求url:${url}`);
|
||||
// 返回请求参数
|
||||
return { url, headers, body };
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取DS
|
||||
* @param {string} q
|
||||
* @param {string} b
|
||||
* @returns {string}
|
||||
*/
|
||||
getDs(q = '', b = '') {
|
||||
let n = '';
|
||||
if (['prod_gf_cn'].includes(this.server)) {
|
||||
|
|
@ -125,19 +158,29 @@ export default class MysZZZApi extends MysApi {
|
|||
} else {
|
||||
n = 'okr4obncj8bw5a65hbnn5oo6ixjc3l9w';
|
||||
}
|
||||
let t = Math.round(new Date().getTime() / 1000);
|
||||
let r = Math.floor(Math.random() * 900000 + 100000);
|
||||
let DS = md5(`salt=${n}&t=${t}&r=${r}&b=${b}&q=${q}`);
|
||||
const t = Math.round(new Date().getTime() / 1000);
|
||||
const r = Math.floor(Math.random() * 900000 + 100000);
|
||||
const DS = md5(`salt=${n}&t=${t}&r=${r}&b=${b}&q=${q}`);
|
||||
return `${t},${r},${DS}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取DS2
|
||||
* @returns {string}
|
||||
*/
|
||||
getDS2() {
|
||||
let t = Math.round(new Date().getTime() / 1000);
|
||||
let r = randomString(6);
|
||||
let sign = md5(`salt=BIPaooxbWZW02fGHZL1If26mYCljPgst&t=${t}&r=${r}`);
|
||||
const t = Math.round(new Date().getTime() / 1000);
|
||||
const r = randomString(6);
|
||||
const sign = md5(`salt=BIPaooxbWZW02fGHZL1If26mYCljPgst&t=${t}&r=${r}`);
|
||||
return `${t},${r},${sign}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取请求头
|
||||
* @param {string} query
|
||||
* @param {string} body
|
||||
* @returns {object}
|
||||
*/
|
||||
getHeaders(query = '', body = '') {
|
||||
const cn = {
|
||||
app_version: '2.63.1',
|
||||
|
|
@ -191,7 +234,7 @@ export default class MysZZZApi extends MysApi {
|
|||
return false;
|
||||
}
|
||||
this.e = e;
|
||||
this.e.isSr = true;
|
||||
this.e.isZZZ = true;
|
||||
res.retcode = Number(res.retcode);
|
||||
switch (res.retcode) {
|
||||
case 0:
|
||||
|
|
@ -255,20 +298,3 @@ export default class MysZZZApi extends MysApi {
|
|||
return _data.data;
|
||||
}
|
||||
}
|
||||
|
||||
export function randomString(length) {
|
||||
let randomStr = '';
|
||||
for (let i = 0; i < length; i++) {
|
||||
randomStr += _.sample('abcdefghijklmnopqrstuvwxyz0123456789');
|
||||
}
|
||||
return randomStr;
|
||||
}
|
||||
|
||||
export function generateSeed(length = 16) {
|
||||
const characters = '0123456789abcdef';
|
||||
let result = '';
|
||||
for (let i = 0; i < length; i++) {
|
||||
result += characters[Math.floor(Math.random() * characters.length)];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,49 +6,62 @@ import settings from '../lib/settings.js';
|
|||
|
||||
export class ZZZPlugin extends plugin {
|
||||
/**
|
||||
*
|
||||
* @returns {Promise<string>}
|
||||
* 获取用户 UID(如果需要同时获取API,可以直接调用 getAPI)
|
||||
* @returns {Promise<string | boolean>}
|
||||
*/
|
||||
async getUID() {
|
||||
// 默认为当前用户
|
||||
let user = this.e;
|
||||
// 获取配置
|
||||
const query = settings.getConfig('config').query;
|
||||
const allow = _.get(query, 'others', true);
|
||||
// 如果 at 存在且允许查看其他用户
|
||||
if (this.e.at && allow) {
|
||||
// 将当前用户的 user_id 设置为 at 对象的 user_id
|
||||
this.e.user_id = this.e.at;
|
||||
// 将当前用户设置为 at 对象
|
||||
user = this.e.at;
|
||||
}
|
||||
// 获取用户信息(米游社),因此这里会导致查询一次米游社的信息
|
||||
this.User = await NoteUser.create(user);
|
||||
// let uid = this.e.msg.match(/\d+/)?.[0];
|
||||
// 获取用户 UID
|
||||
const uid = this.User?.getUid('zzz');
|
||||
// 如果 UID 不存在,说明没有绑定 cookie
|
||||
if (!uid) {
|
||||
await this.reply('uid为空,米游社查询请先绑定cookie,其他查询请携带uid');
|
||||
return false;
|
||||
}
|
||||
// 返回 UID
|
||||
return uid;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* 获取米游社 API
|
||||
* @returns {Promise<{api: MysZZZApi, uid: string, deviceFp: string}>}
|
||||
*/
|
||||
async getAPI() {
|
||||
let uid = await this.getUID();
|
||||
// 直接调用获取 UID
|
||||
const uid = await this.getUID();
|
||||
if (!uid) return false;
|
||||
// 获取用户的 cookie
|
||||
const ck = await getCk(this.e);
|
||||
// 如果 cookie 不存在或者 cookie 为空,说明没有绑定 cookie
|
||||
if (!ck || Object.keys(ck).filter(k => ck[k].ck).length === 0) {
|
||||
await this.reply('尚未绑定cookie,请先绑定cookie');
|
||||
return false;
|
||||
}
|
||||
|
||||
// 创建米游社 API 对象
|
||||
const api = new MysZZZApi(uid, ck);
|
||||
// 获取设备指纹
|
||||
let deviceFp = await redis.get(`ZZZ:DEVICE_FP:${uid}`);
|
||||
if (!deviceFp) {
|
||||
let sdk = api.getUrl('getFp');
|
||||
let res = await fetch(sdk.url, {
|
||||
const sdk = api.getUrl('getFp');
|
||||
const res = await fetch(sdk.url, {
|
||||
headers: sdk.headers,
|
||||
method: 'POST',
|
||||
body: sdk.body,
|
||||
});
|
||||
let fpRes = await res.json();
|
||||
const fpRes = await res.json();
|
||||
deviceFp = fpRes?.data?.device_fp;
|
||||
if (deviceFp) {
|
||||
await redis.set(`ZZZ:DEVICE_FP:${uid}`, deviceFp, {
|
||||
|
|
@ -56,19 +69,25 @@ export class ZZZPlugin extends plugin {
|
|||
});
|
||||
}
|
||||
}
|
||||
// 返回数据(API、UID、设备指纹)
|
||||
return { api, uid, deviceFp };
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* 获取玩家信息(当调用此方法时,会获取用户的玩家信息,并将其保存到`e.playerCard`中,方便渲染用户信息(此部分请查阅`lib/render.js`中两个模块的作用))
|
||||
* @returns {Promise<boolean | object>}
|
||||
*/
|
||||
async getPlayerInfo() {
|
||||
// 获取 米游社 API
|
||||
const { api } = await this.getAPI();
|
||||
if (!api) return false;
|
||||
// 获取用户信息
|
||||
let userData = await api.getFinalData(this.e, 'zzzUser');
|
||||
if (!userData) return false;
|
||||
// 取第一个用户信息
|
||||
userData = userData?.list[0];
|
||||
|
||||
// 获取用户头像
|
||||
let avatar = this.e.bot.avatar;
|
||||
if (this.e?.user_id) {
|
||||
avatar = `https://q1.qlogo.cn/g?b=qq&s=0&nk=${this.e.user_id}`;
|
||||
|
|
@ -77,10 +96,12 @@ export class ZZZPlugin extends plugin {
|
|||
} else if (this.e.friend?.getAvatarUrl) {
|
||||
avatar = await this.e.friend.getAvatarUrl();
|
||||
}
|
||||
// 写入数据
|
||||
this.e.playerCard = {
|
||||
avatar: avatar,
|
||||
player: userData,
|
||||
};
|
||||
// 返回数据
|
||||
return userData;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import version from './version.js';
|
|||
* @param {object} cfg 配置
|
||||
* @returns
|
||||
*/
|
||||
function render(e, renderPath, renderData = {}, cfg = {}) {
|
||||
const render = (e, renderPath, renderData = {}, cfg = {}) => {
|
||||
// 判断是否存在e.runtime
|
||||
if (!e.runtime) {
|
||||
console.log('未找到e.runtime,请升级至最新版Yunzai');
|
||||
|
|
@ -30,31 +30,55 @@ function render(e, renderPath, renderData = {}, cfg = {}) {
|
|||
|
||||
// 调用e.runtime.render方法渲染
|
||||
return e.runtime.render(pluginName, renderPath, renderData, {
|
||||
// 合并传入的配置
|
||||
...cfg,
|
||||
beforeRender({ data }) {
|
||||
// 资源路径
|
||||
const resPath = data.pluResPath;
|
||||
// 布局路径
|
||||
const layoutPath = data.pluResPath + 'common/layout/';
|
||||
// 当前的渲染路径
|
||||
const renderPathFull = data.pluResPath + renderPath.split('/')[0] + '/';
|
||||
// 合并数据
|
||||
return {
|
||||
// 玩家信息
|
||||
player: e?.playerCard?.player,
|
||||
// 玩家头像
|
||||
avatar: e?.playerCard?.avatar,
|
||||
// 传入的数据
|
||||
...data,
|
||||
// 资源路径
|
||||
_res_path: resPath,
|
||||
// 布局路径
|
||||
_layout_path: layoutPath,
|
||||
// 默认布局路径
|
||||
defaultLayout: path.join(layoutPathFull, 'index.html'),
|
||||
// 系统配置
|
||||
sys: {
|
||||
// 缩放比例
|
||||
scale: pct,
|
||||
// 资源路径
|
||||
resourcesPath: resPath,
|
||||
// 当前渲染的路径
|
||||
currentPath: renderPathFull,
|
||||
/**
|
||||
* 下面两个模块的作用在于,你可以在你的布局文件中使用这两个模块,就可以显示用户信息和特殊标题,使用方法如下:
|
||||
* 1. 展示玩家信息:首先你要在查询的时候调用`this.getPlayerInfo()`,这样,玩家数据就会保存在`e.playerCard`中,然后你就可以在布局文件中使用`{{include sys.playerInfo}}`来展示玩家信息。
|
||||
* 2. 展示特殊标题:你可以在布局文件中使用`<% include(sys.specialTitle, {en: 'PROPERTY' , cn: '属性' , count: 6 }) %>`来展示特殊标题,其中`count`为可选参数,默认为9。
|
||||
*/
|
||||
// 玩家信息模块
|
||||
playerInfo: path.join(layoutPathFull, 'playerinfo.html'),
|
||||
// 特殊标题模块
|
||||
specialTitle: path.join(layoutPathFull, 'specialtitle.html'),
|
||||
// 版权信息
|
||||
copyright: `Created By ${version.name}<span class="version">${version.yunzai}</span> & ${pluginName}<span class="version">${version.version}</span>`,
|
||||
// 版权信息(简化版)
|
||||
createdby: `Created By <span class="highlight">${pluginName}</span> & Powered By <span class="highlight">ZZZure</span>`,
|
||||
},
|
||||
quality: 100,
|
||||
};
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export default render;
|
||||
|
|
|
|||
|
|
@ -59,12 +59,17 @@ class Setting {
|
|||
|
||||
// 配置对象分析 用于锅巴插件界面设置
|
||||
analysis(config) {
|
||||
for (let key of Object.keys(config)) {
|
||||
for (const key of Object.keys(config)) {
|
||||
this.setConfig(key, config[key]);
|
||||
}
|
||||
}
|
||||
|
||||
// 获取对应模块数据文件
|
||||
/**
|
||||
* 获取对应模块数据文件
|
||||
* @param {string} filepath
|
||||
* @param {string} filename
|
||||
* @returns {object | false}
|
||||
*/
|
||||
getData(filepath, filename) {
|
||||
filename = `${filename}.yaml`;
|
||||
filepath = path.join(this.dataPath, filepath);
|
||||
|
|
@ -79,7 +84,13 @@ class Setting {
|
|||
}
|
||||
}
|
||||
|
||||
// 写入对应模块数据文件
|
||||
/**
|
||||
* 写入对应模块数据文件
|
||||
* @param {string} filepath
|
||||
* @param {string} filename
|
||||
* @param {object} data
|
||||
* @returns {boolean}
|
||||
*/
|
||||
setData(filepath, filename, data) {
|
||||
filename = `${filename}.yaml`;
|
||||
filepath = path.join(this.dataPath, filepath);
|
||||
|
|
@ -93,24 +104,38 @@ class Setting {
|
|||
YAML.stringify(data),
|
||||
'utf8'
|
||||
);
|
||||
return true;
|
||||
} catch (error) {
|
||||
logger.error(`[${pluginName}] [${filename}] 写入失败 ${error}`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 获取对应模块默认配置
|
||||
/**
|
||||
* 获取对应模块默认配置
|
||||
* @param {'atlas'|'config'|'gacha'|'panel'} app
|
||||
* @returns {object}
|
||||
*/
|
||||
getdefSet(app) {
|
||||
return this.getYaml(app, 'defSet');
|
||||
}
|
||||
|
||||
// 获取对应模块用户配置
|
||||
/**
|
||||
* 获取对应模块用户配置(配置文件名)
|
||||
* @param {'atlas'|'config'|'gacha'|'panel'} app
|
||||
* @returns {object}
|
||||
*/
|
||||
getConfig(app) {
|
||||
return { ...this.getdefSet(app), ...this.getYaml(app, 'config') };
|
||||
// return this.mergeConfigObjectArray({...this.getdefSet(app)},{...this.getYaml(app, 'config')});
|
||||
}
|
||||
|
||||
//合并两个对象 相同的数组对象 主要用于yml的列表属性合并 并去重 先备份一下方法
|
||||
/**
|
||||
* 合并两个对象 相同的数组对象 主要用于yml的列表属性合并 并去重 先备份一下方法
|
||||
* @param {object} obj1
|
||||
* @param {object} obj2
|
||||
* @returns {object}
|
||||
*/
|
||||
mergeConfigObjectArray(obj1, obj2) {
|
||||
for (const key in obj2) {
|
||||
if (Array.isArray(obj2[key]) && Array.isArray(obj1[key])) {
|
||||
|
|
@ -126,9 +151,14 @@ class Setting {
|
|||
return obj1;
|
||||
}
|
||||
|
||||
// 设置对应模块用户配置
|
||||
setConfig(app, Object) {
|
||||
return this.setYaml(app, 'config', { ...this.getdefSet(app), ...Object });
|
||||
/**
|
||||
* 设置对应模块用户配置
|
||||
* @param {'atlas'|'config'|'gacha'|'panel'} app
|
||||
* @param {object} obj
|
||||
* @returns
|
||||
*/
|
||||
setConfig(app, obj) {
|
||||
return this.setYaml(app, 'config', { ...this.getdefSet(app), ...obj });
|
||||
}
|
||||
|
||||
// 将对象写入YAML文件
|
||||
|
|
|
|||
|
|
@ -3,42 +3,70 @@ import lodash from 'lodash';
|
|||
import path from 'path';
|
||||
import { pluginPath } from './path.js';
|
||||
|
||||
// 更新日志文件位置
|
||||
const _logPath = path.join(pluginPath, 'CHANGELOG.md');
|
||||
|
||||
// 存放日志
|
||||
let logs = {};
|
||||
|
||||
// 存放更新日志
|
||||
let changelogs = [];
|
||||
|
||||
// 当前版本
|
||||
let currentVersion;
|
||||
|
||||
// 版本数量
|
||||
let versionCount = 4;
|
||||
|
||||
// 读取 package.json(此处为读取Yunzai-Bot的package.json)
|
||||
let packageJson = JSON.parse(fs.readFileSync('package.json', 'utf8'));
|
||||
|
||||
/**
|
||||
* 将 Markdown 行转换为 HTML
|
||||
* @param {*} line
|
||||
* @returns
|
||||
*/
|
||||
const getLine = function (line) {
|
||||
// 去除行首空格和换行符
|
||||
line = line.replace(/(^\s*\*|\r)/g, '');
|
||||
// 替换行内代码块
|
||||
line = line.replace(/\s*`([^`]+`)/g, '<span class="cmd">$1');
|
||||
line = line.replace(/`\s*/g, '</span>');
|
||||
// 替换行内加粗
|
||||
line = line.replace(/\s*\*\*([^\*]+\*\*)/g, '<span class="strong">$1');
|
||||
line = line.replace(/\*\*\s*/g, '</span>');
|
||||
// 替换行内表示更新内容
|
||||
line = line.replace(/ⁿᵉʷ/g, '<span class="new"></span>');
|
||||
// 返回转换后的行内容(HTML)
|
||||
return line;
|
||||
};
|
||||
|
||||
// 尝试读取更新日志文件
|
||||
try {
|
||||
if (fs.existsSync(_logPath)) {
|
||||
// 如果文件存在,读取文件内容
|
||||
logs = fs.readFileSync(_logPath, 'utf8') || '';
|
||||
// 将文件内容按行分割
|
||||
logs = logs.split('\n');
|
||||
|
||||
// 遍历每一行,提取版本号和更新内容
|
||||
let temp = {};
|
||||
let lastLine = {};
|
||||
lodash.forEach(logs, line => {
|
||||
// 如果版本数量小于0,返回false
|
||||
if (versionCount <= -1) {
|
||||
return false;
|
||||
}
|
||||
let versionRet = /^#\s*([0-9a-zA-Z\\.~\s]+?)\s*$/.exec(line);
|
||||
// 匹配版本号
|
||||
const versionRet = /^#\s*([0-9a-zA-Z\\.~\s]+?)\s*$/.exec(line);
|
||||
if (versionRet && versionRet[1]) {
|
||||
let v = versionRet[1].trim();
|
||||
// 如果匹配到版本号,提取版本号
|
||||
const v = versionRet[1].trim();
|
||||
if (!currentVersion) {
|
||||
// 如果当前版本号不存在,将当前版本号设置为匹配到的版本号
|
||||
currentVersion = v;
|
||||
} else {
|
||||
// 写入更新日志
|
||||
changelogs.push(temp);
|
||||
if (/0\s*$/.test(v) && versionCount > 0) {
|
||||
versionCount = 0;
|
||||
|
|
@ -46,15 +74,16 @@ try {
|
|||
versionCount--;
|
||||
}
|
||||
}
|
||||
|
||||
temp = {
|
||||
version: v,
|
||||
logs: [],
|
||||
};
|
||||
} else {
|
||||
// 如果行为空,不继续执行
|
||||
if (!line.trim()) {
|
||||
return;
|
||||
}
|
||||
// 如果行以 * 开头,表示更新内容
|
||||
if (/^\*/.test(line)) {
|
||||
lastLine = {
|
||||
title: getLine(line),
|
||||
|
|
@ -71,18 +100,27 @@ try {
|
|||
// void error
|
||||
}
|
||||
|
||||
// 读取Yunzai-Bot的版本号
|
||||
const yunzaiVersion = packageJson.version;
|
||||
|
||||
// 判断是否为Yunzai-Bot v3
|
||||
const isV3 = yunzaiVersion[0] === '3';
|
||||
|
||||
// 是否为喵(默认为否)
|
||||
let isMiao = false;
|
||||
// bot名称
|
||||
let name = 'Yunzai-Bot';
|
||||
if (packageJson.name === 'miao-yunzai') {
|
||||
// 如果是喵,将 isMiao 设置为 true,name 设置为 Miao-Yunzai
|
||||
isMiao = true;
|
||||
name = 'Miao-Yunzai';
|
||||
} else if (packageJson.name === 'trss-yunzai') {
|
||||
// 如果是 TRSS-Yunzai,将 isMiao 设置为 true,name 设置为 TRSS-Yunzai
|
||||
isMiao = true;
|
||||
name = 'TRSS-Yunzai';
|
||||
}
|
||||
|
||||
// 导出版本信息
|
||||
const version = {
|
||||
isV3,
|
||||
isMiao,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue