refactor: 重构lib代码(无实质性功能更新,可不更新)

This commit is contained in:
bietiaop 2024-08-22 13:34:08 +08:00
parent a7f06d404b
commit aa3b7928ec
26 changed files with 547 additions and 483 deletions

View file

@ -1,41 +1,16 @@
import { findLowestLatencyUrl } from '../utils/network.js';
import {
URL_LIB,
TYPE_PATH,
RESOURCE_PATH,
GUIDE_PATH,
} from './assets/const.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',
'[USFRP]': 'http://us-6.lcf.1l1.icu:28596',
'[XiaoWu]': 'http://frp.xiaowuap.com:63481',
'[Chuncheon]': 'https://kr.qxqx.cf',
'[Seoul]': 'https://kr-s.qxqx.cf',
'[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>}
@ -61,10 +36,10 @@ export const getFatestUrl = async () => {
};
/**
* Get resource remote path
* @param {keyof TYPE_PATH} type
* @param {keyof RESOURCE_PATH | keyof GUIDE_PATH} label
* @param {string} name
* 获取远程路径
* @param {keyof TYPE_PATH} type 资源类型
* @param {keyof RESOURCE_PATH | keyof GUIDE_PATH} label 资源标签
* @param {string} name 资源名称
* @returns
*/
export const getRemotePath = async (type, label, name) => {
@ -72,7 +47,12 @@ export const getRemotePath = async (type, label, name) => {
return `${url}/ZZZeroUID/${type}/${label}/${name}`;
};
// 获取资源远程路径
/**
* 获取资源远程路径
* @param {keyof RESOURCE_PATH} label 资源标签
* @param {string} name 资源名称
* @returns {Promise<string>}
*/
export const getResourceRemotePath = async (label, name) => {
return getRemotePath(TYPE_PATH.resource, label, name);
};

29
lib/assets/const.js Normal file
View file

@ -0,0 +1,29 @@
// 节点列表
export const URL_LIB = {
'[JPFRP]': 'http://jp-3.lcf.1l1.icu:17217',
'[HKFRP]': 'http://hk-1.lcf.1l1.icu:10200',
'[USFRP]': 'http://us-6.lcf.1l1.icu:28596',
'[XiaoWu]': 'http://frp.xiaowuap.com:63481',
'[Chuncheon]': 'https://kr.qxqx.cf',
'[Seoul]': 'https://kr-s.qxqx.cf',
'[Singapore]': 'https://sg.qxqx.cf',
};
// 文件类型路径
export const TYPE_PATH = {
wiki: 'wiki',
resource: 'resource',
guide: 'guide',
};
// 资源路径
export const RESOURCE_PATH = {
role: 'role',
role_circle: 'role_circle',
weapon: 'weapon',
};
// 图鉴路径
export const GUIDE_PATH = {
flower: 'flower',
};

View file

@ -5,14 +5,13 @@ import { char } from './convert.js';
/**
* 获取角色基础信息列表
* @param {*} e 消息事件
* @param {MysZZZApi} api
* @param {boolean} origin 是否返回原始数据
* @returns {Promise<ZZZAvatarBasic[] | null>}
*/
export const getAvatarBasicList = async (e, api, origin = false) => {
export const getAvatarBasicList = async (api, origin = false) => {
// 获取米游社角色列表
const avatarBaseListData = await api.getFinalData(e, 'zzzAvatarList');
const avatarBaseListData = await api.getFinalData('zzzAvatarList');
if (!avatarBaseListData) return null;
// 是否返回原始数据
if (origin) return avatarBaseListData.avatar_list;
@ -25,17 +24,16 @@ export const getAvatarBasicList = async (e, api, origin = false) => {
/**
* 获取角色详细信息列表
* @param {*} e 消息事件
* @param {MysZZZApi} api
* @returns {Promise<ZZZAvatarInfo[] | null>}
* @param {boolean} origin 是否返回原始数据
*/
export const getAvatarInfoList = async (e, api, origin = false) => {
export const getAvatarInfoList = async (api, origin = false) => {
// 获取角色基础信息列表
const avatarBaseList = await getAvatarBasicList(e, api, origin);
const avatarBaseList = await getAvatarBasicList(api, origin);
if (!avatarBaseList) return null;
// 获取角色详细信息
const avatarInfoList = await api.getFinalData(e, 'zzzAvatarInfo', {
const avatarInfoList = await api.getFinalData('zzzAvatarInfo', {
query: {
id_list: avatarBaseList.map(item => item.id),
},
@ -52,16 +50,15 @@ export const getAvatarInfoList = async (e, api, origin = false) => {
/**
* 刷新面板
* @param {*} e 消息事件
* @param {MysZZZApi} api
* @param {string} uid
* @returns {Promise<ZZZAvatarInfo[] | null>}
*/
export const refreshPanel = async (e, api, uid) => {
export const refreshPanel = async (api, uid) => {
// 获取已保存数据
const originData = getPanelData(uid);
// 获取新数据
const newData = await getAvatarInfoList(e, api, true);
const newData = await getAvatarInfoList(api, true);
if (!newData) return null;
// 初始化最终数据
const finalData = [...newData];

View file

@ -1,7 +1,7 @@
import User from '../../genshin/model/user.js';
import { getStoken } from './authkey.js';
export const rulePrefix = '^((#|%)?(zzz|ZZZ|绝区零))';
export const rulePrefix = '^((#|%|/)?(zzz|ZZZ|绝区零))';
/**
* 获取米游社用户的 cookie

View file

@ -1,47 +1,4 @@
import { readFileSync, writeFileSync } from 'fs';
import path from 'path';
import { checkFolderExistAndCreate } from '../utils/file.js';
import { dataPath } from './path.js';
const dbPath = {
gacha: 'gacha',
panel: 'panel',
};
/**
*
* @param {string} dbName
* @param {string} dbFile
* @returns {object | Array<object> | null}
*/
export function getDB(dbName, dbFile) {
const db = dbPath[dbName];
const dbFolder = path.join(dataPath, db);
try {
const dbPath = path.join(dbFolder, `${dbFile}.json`);
return JSON.parse(readFileSync(dbPath, 'utf-8'));
} catch (error) {
logger.debug(`读取数据库失败: ${error.message}`);
return null;
}
}
/**
*
* @param {string} dbName
* @param {string} dbFile
* @param {object} data
*/
export function setDB(dbName, dbFile, data) {
const db = dbPath[dbName];
const dbFolder = path.join(dataPath, db);
try {
checkFolderExistAndCreate(dbFolder);
const dbPath = path.join(dbFolder, `${dbFile}.json`);
writeFileSync(dbPath, JSON.stringify(data, null, 2));
} catch (error) {
logger.debug(`读取数据库失败: ${error.message}`);
}
}
import { getDB, setDB } from './db/core.js';
/**
* @param {string} uid

44
lib/db/core.js Normal file
View file

@ -0,0 +1,44 @@
import { readFileSync, writeFileSync } from 'fs';
import path from 'path';
import { checkFolderExistAndCreate } from '../../utils/file.js';
import { dataPath } from '../path.js';
export const dbPath = {
gacha: 'gacha',
panel: 'panel',
};
/**
* 读取数据库
* @param {keyof dbPath} dbName
* @param {string} dbFile
* @returns {object | Array<object> | null}
*/
export function getDB(dbName, dbFile) {
const db = dbPath[dbName];
const dbFolder = path.join(dataPath, db);
try {
const dbPath = path.join(dbFolder, `${dbFile}.json`);
return JSON.parse(readFileSync(dbPath, 'utf-8'));
} catch (error) {
logger.debug(`读取数据库失败: ${error.message}`);
return null;
}
}
/**
* 写入数据库
* @param {string} dbName
* @param {string} dbFile
* @param {object} data
*/
export function setDB(dbName, dbFile, data) {
const db = dbPath[dbName];
const dbFolder = path.join(dataPath, db);
try {
checkFolderExistAndCreate(dbFolder);
const dbPath = path.join(dbFolder, `${dbFile}.json`);
writeFileSync(dbPath, JSON.stringify(data, null, 2));
} catch (error) {
logger.debug(`读取数据库失败: ${error.message}`);
}
}

View file

@ -1,72 +1,8 @@
import path from 'path';
import fs from 'fs';
import * as convert from './convert.js';
import {
ZZZ_SQUARE_AVATAR,
ZZZ_SQUARE_BANGBOO,
NEW_ZZZ_SQUARE_BANGBOO,
NEW_ZZZ_SQUARE_AVATAR,
} from './mysapi/api.js';
import { imageResourcesPath } from './path.js';
import { char, equip, weapon } from './convert.js';
import { getResourceRemotePath } from './assets.js';
import request from '../utils/request.js';
const ZZZ_SQUARE_AVATAR_PATH = path.join(imageResourcesPath, 'square_avatar');
const ZZZ_SMALL_SQUARE_AVATAR_PATH = path.join(
imageResourcesPath,
'role_general'
);
const ZZZ_SQUARE_BANGBOO_PATH = path.join(
imageResourcesPath,
'bangboo_square_avatar'
);
const ZZZ_WEAPON_PATH = path.join(imageResourcesPath, 'weapon');
const ZZZ_ROLE_PATH = path.join(imageResourcesPath, 'role');
const ZZZ_ROLE_CIRCLE_PATH = path.join(imageResourcesPath, 'role_circle');
const ZZZ_SUIT_3D_PATH = path.join(imageResourcesPath, 'suit_3d');
const ZZZ_SUIT_PATH = path.join(imageResourcesPath, 'suit');
// const ZZZ_GUIDES_PATH = path.join(imageResourcesPath, 'guides');
/**
* 下载文件
* @param {string} url 下载地址
* @param {string} savePath 保存路径
* @returns {Promise<string | null>} 保存路径
*/
export const downloadFile = async (url, savePath) => {
// 下载文件
try {
const download = await request(url, {}, 5);
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 null;
}
};
/**
* 查看文件是否存在如果存在则返回路径否则下载文件
* @param {string} url 下载地址
* @param {string} savePath 保存路径
* @returns {Promise<string | null>} 保存路径
*/
export const checkFile = async (url, savePath) => {
if (fs.existsSync(savePath)) {
const stats = fs.statSync(savePath);
if (stats.size > 0) {
return savePath;
}
}
const download = await downloadFile(url, savePath);
return download;
};
downloadMysImage,
downloadResourceImage,
} from './download/download.js';
/**
* 获取角色头像方形
@ -75,96 +11,94 @@ export const checkFile = async (url, savePath) => {
*/
export const getSquareAvatar = async charID => {
const filename = `role_square_avatar_${charID}.png`;
const avatarPath = path.join(ZZZ_SQUARE_AVATAR_PATH, filename);
let url = `${ZZZ_SQUARE_AVATAR}/${filename}`;
let result = await checkFile(url, avatarPath);
if (!result) {
url = `${NEW_ZZZ_SQUARE_AVATAR}/${filename}`;
result = await checkFile(url, avatarPath);
}
const result = await downloadMysImage(
'ZZZ_SQUARE_AVATAR',
'ZZZ_SQUARE_AVATAR_PATH',
filename,
'NEW_ZZZ_SQUARE_AVATAR'
);
return result;
};
/**
* 获取角色头像小方形
* @param {string | number} charID
* @returns Promise<string>
* @returns {Promise<string>}
*/
export const getSmallSquareAvatar = async charID => {
const sprite = char.IDToCharSprite(charID);
const sprite = convert.char.IDToCharSprite(charID);
if (!sprite) return null;
const filename = `IconRoleGeneral${sprite}.png`;
const avatarPath = path.join(ZZZ_SMALL_SQUARE_AVATAR_PATH, filename);
const url = await getResourceRemotePath('role_general', filename);
const result = await checkFile(url, avatarPath);
const result = await downloadResourceImage(
'role_general',
'ZZZ_SMALL_SQUARE_AVATAR_PATH',
filename
);
return result;
};
/**
* 获取邦布头像方形
* @param {string | number} bangbooId
* @returns Promise<string>
* @returns {Promise<string>}
*/
export const getSquareBangboo = async bangbooId => {
const filename = `bangboo_rectangle_avatar_${bangbooId}.png`;
const bangbooPath = path.join(ZZZ_SQUARE_BANGBOO_PATH, filename);
let url = `${ZZZ_SQUARE_BANGBOO}/${filename}`;
let result = await checkFile(url, bangbooPath);
if (!result) {
url = `${NEW_ZZZ_SQUARE_BANGBOO}/${filename}`;
result = await checkFile(url, bangbooPath);
}
const result = await downloadMysImage(
'ZZZ_SQUARE_BANGBOO',
'ZZZ_SQUARE_BANGBOO_PATH',
filename,
'NEW_ZZZ_SQUARE_BANGBOO'
);
return result;
};
/**
* 获取武器图片
* @param {string} id
* @returns Promise<string>
* @returns {Promise<string>}
*/
export const getWeaponImage = async id => {
const name = weapon.IDToWeaponFileName(id);
let filename = `${name}_High.png`;
const weaponPath = path.join(ZZZ_WEAPON_PATH, filename);
const url = await getResourceRemotePath('weapon', filename);
let result = await checkFile(url, weaponPath);
if (!result) {
filename = `${name}.png`;
const weaponPath = path.join(ZZZ_WEAPON_PATH, filename);
const url = await getResourceRemotePath('weapon', filename);
result = await checkFile(url, weaponPath);
}
const name = convert.weapon.IDToWeaponFileName(id);
if (!name) return null;
const filename = `${name}_High.png`;
const replaceFilename = `${name}_High.png`;
const result = await downloadResourceImage(
'weapon',
'ZZZ_WEAPON_PATH',
filename,
replaceFilename
);
return result;
};
/**
* 获取角色图片
* @param {string | number} id
* @returns Promise<string>
* @returns {Promise<string>}
*/
export const getRoleImage = async id => {
const sprite = char.IDToCharSprite(id);
const sprite = convert.char.IDToCharSprite(id);
if (!sprite) return null;
const filename = `IconRole${sprite}.png`;
const rolePath = path.join(ZZZ_ROLE_PATH, filename);
const url = await getResourceRemotePath('role', filename);
const result = await checkFile(url, rolePath);
const result = await downloadResourceImage('role', 'ZZZ_ROLE_PATH', filename);
return result;
};
/**
* 获取角色圆形图片
* @param {string | number} id
* @returns Promise<string>
* @returns {Promise<string>}
*/
export const getRoleCircleImage = async id => {
const sprite = char.IDToCharSprite(id);
const sprite = convert.char.IDToCharSprite(id);
if (!sprite) return null;
const filename = `IconRoleCircle${sprite}.png`;
const roleCirclePath = path.join(ZZZ_ROLE_CIRCLE_PATH, filename);
const url = await getResourceRemotePath('role_circle', filename);
const result = await checkFile(url, roleCirclePath);
const result = await downloadResourceImage(
'role_circle',
'ZZZ_ROLE_CIRCLE_PATH',
filename
);
return result;
};
@ -174,11 +108,10 @@ export const getRoleCircleImage = async id => {
* @returns Promise<string>
*/
export const getSuitImage = async suitId => {
const suitName = equip.equipIdToSprite(suitId);
const suitName = convert.equip.equipIdToSprite(suitId);
if (!suitName) return null;
const filename = `${suitName}.png`;
const suitPath = path.join(ZZZ_SUIT_PATH, filename);
const url = await getResourceRemotePath('suit', filename);
const result = await checkFile(url, suitPath);
const result = await downloadResourceImage('suit', 'ZZZ_SUIT_PATH', filename);
return result;
};
@ -188,10 +121,12 @@ export const getSuitImage = async suitId => {
* @returns Promise<string>
*/
export const getSuit3DImage = async suitId => {
const suitName = equip.equipIdToSprite(suitId);
const suitName = convert.equip.equipIdToSprite(suitId);
const filename = `${suitName}_3d.png`;
const suitPath = path.join(ZZZ_SUIT_3D_PATH, filename);
const url = await getResourceRemotePath('suit_3d', filename);
const result = await checkFile(url, suitPath);
const result = await downloadResourceImage(
'suit_3d',
'ZZZ_SUIT_3D_PATH',
filename
);
return result;
};

18
lib/download/const.js Normal file
View file

@ -0,0 +1,18 @@
import path from 'path';
import { imageResourcesPath } from '../path.js';
export const ZZZ_SQUARE_AVATAR_PATH = path.join(
imageResourcesPath,
'square_avatar'
),
ZZZ_SMALL_SQUARE_AVATAR_PATH = path.join(imageResourcesPath, 'role_general'),
ZZZ_SQUARE_BANGBOO_PATH = path.join(
imageResourcesPath,
'bangboo_square_avatar'
),
ZZZ_WEAPON_PATH = path.join(imageResourcesPath, 'weapon'),
ZZZ_ROLE_PATH = path.join(imageResourcesPath, 'role'),
ZZZ_ROLE_CIRCLE_PATH = path.join(imageResourcesPath, 'role_circle'),
ZZZ_SUIT_3D_PATH = path.join(imageResourcesPath, 'suit_3d'),
ZZZ_SUIT_PATH = path.join(imageResourcesPath, 'suit');
// const ZZZ_GUIDES_PATH = path.join(imageResourcesPath, 'guides');

44
lib/download/core.js Normal file
View file

@ -0,0 +1,44 @@
import path from 'path';
import fs from 'fs';
import request from '../../utils/request.js';
/**
* 下载文件
* @param {string} url 下载地址
* @param {string} savePath 保存路径
* @returns {Promise<string | null>} 保存路径
*/
export const downloadFile = async (url, savePath) => {
// 下载文件
try {
const download = await request(url, {}, 5);
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 null;
}
};
/**
* 查看文件是否存在如果存在则返回路径否则下载文件
* @param {string} url 下载地址
* @param {string} savePath 保存路径
* @returns {Promise<string | null>} 保存路径
*/
export const checkFile = async (url, savePath) => {
if (fs.existsSync(savePath)) {
const stats = fs.statSync(savePath);
if (stats.size > 0) {
return savePath;
}
}
const download = await downloadFile(url, savePath);
return download;
};

57
lib/download/download.js Normal file
View file

@ -0,0 +1,57 @@
import path from 'path';
import { checkFile } from './core.js';
import { getResourceRemotePath } from '../assets.js';
import * as MysURL from '../mysapi/api.js';
import * as LocalURI from './const.js';
/**
* 下载米游社图片
* @param {keyof MysURL} base 远程地址
* @param {keyof LocalURI} localBase 本地地址
* @param {string} filename 文件名
* @param {keyof MysURL} newBase 新远程地址
*/
export const downloadMysImage = async (
base,
localBase,
filename,
newBase = ''
) => {
base = MysURL[base];
localBase = LocalURI[localBase];
if (!!newBase) {
newBase = MysURL[newBase];
}
const finalPath = path.join(localBase, filename);
let url = `${base}/${filename}`;
let result = await checkFile(url, finalPath);
if (!result && !!newBase) {
url = `${newBase}/${filename}`;
result = await checkFile(url, finalPath);
}
return result;
};
/**
* 下载资源库图片
* @param {Parameters<getResourceRemotePath>[0]} remoteLabel 远程地址
* @param {keyof LocalURI} localBase 本地地址
* @param {string} filename 文件名
* @param {string} replaceFilename 替换文件名(如果资源不存在)
*/
export const downloadResourceImage = async (
remoteLabel,
localBase,
filename,
replaceFilename = ''
) => {
localBase = LocalURI[localBase];
const finalPath = path.join(localBase, filename);
const url = await getResourceRemotePath(remoteLabel, filename);
let result = await checkFile(url, finalPath);
if (!result && !!replaceFilename) {
const finalPath = path.join(localBase, replaceFilename);
const url = await getResourceRemotePath(remoteLabel, replaceFilename);
result = await checkFile(url, finalPath);
}
return result;
};

37
lib/error.js Normal file
View file

@ -0,0 +1,37 @@
export class MysError extends Error {
/**
* 自定义错误类
* @param {string} code 错误码
*/
constructor(code, uid, result) {
let msg = '未知错误';
switch (code) {
case '10102':
if (result.message === 'Data is not public for the user') {
msg = `UID:${uid},米游社数据未公开`;
} else {
msg = `UID:${uid},请先去米游社绑定角色`;
}
break;
case '10041':
case '5003':
msg = `UID:${uid},米游社账号异常,暂时无法查询,请发送 %绑定设备帮助 查看如何绑定设备`;
break;
case '10035':
case '1034':
msg = `UID:${uid},米游社查询遇到验证码`;
break;
default:
if (/(登录|login)/i.test(res.message)) {
msg = `UID:${uid}米游社cookie已失效`;
} else {
msg = `UID:${uid},米游社接口报错:${res.message}`;
}
}
super(msg);
this.code = Number(code);
this.uid = uid;
this.result = result;
this.name = 'MysError';
}
}

View file

@ -1,139 +1,16 @@
import { SingleGachaLog, ZZZGachaLogResp } from '../model/gacha.js';
import { SingleGachaLog } from '../model/gacha.js';
import { sleep } from '../utils/time.js';
import { rank } from './convert.js';
import { getGachaLog, saveGachaLog } from './db.js';
import { ZZZ_GET_GACHA_LOG_API } from './mysapi/api.js';
import request from '../utils/request.js';
// 池子代码
export const gacha_type_meta_data = {
音擎频段: ['3001'],
独家频段: ['2001'],
常驻频段: ['1001'],
邦布频段: ['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} initLogGachaBaseType
* @param {number} page 页数
* @param {string} endId 最后一个数据的 id
* @param {string} size 页面数据大小
* @returns {Promise<string>} 抽卡链接
*/
export const getZZZGachaLink = async (
authKey,
gachaType = '2001',
initLogGachaBaseType = '2',
page = 1,
endId = '0',
size = '20'
) => {
// 暂时直接写死服务器为国服
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',
auth_appid: 'webview_gacha',
init_log_gacha_type: gachaType,
init_log_gacha_base_type: initLogGachaBaseType,
gacha_id: '2c1f5692fdfbb733a08733f9eb69d32aed1d37',
timestamp: timestamp.toString(),
lang: 'zh-cn',
device_type: 'mobile',
plat_type: 'ios',
region: serverId,
authkey: authKey,
game_biz: 'nap_cn',
gacha_type: gachaType,
real_gacha_type: initLogGachaBaseType,
page: page,
size: size,
end_id: endId,
});
// 完整链接
return `${url}?${params}`;
};
/**
* 通过米游社认证密钥获取抽卡记录
* @param {string} authKey 米游社认证密钥
* @param {string} gachaType 祈愿类型池子代码
* @param {string} initLogGachaBaseType
* @param {number} page 页数
* @param {string} endId 最后一个数据的 id
* @returns {Promise<ZZZGachaLogResp>} 抽卡记录
*/
export const getZZZGachaLogByAuthkey = async (
authKey,
gachaType = '2001',
initLogGachaBaseType = '2',
page = 1,
endId = '0'
) => {
// 获取抽卡链接
const link = await getZZZGachaLink(
authKey,
gachaType,
initLogGachaBaseType,
page,
endId
);
// 发送请求
const response = await request(link, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
retry: 3,
});
// 获取数据
const data = await response.json();
if (!data || !data?.data) return null;
return new ZZZGachaLogResp(data.data);
};
import {
gacha_type_meta_data,
FLOORS_MAP,
HOMO_TAG,
EMOJI,
NORMAL_LIST,
} from './gacha/const.js';
import { getLevelFromList } from './gacha/tool.js';
import { getZZZGachaLogByAuthkey } from './gacha/core.js';
/**
* 更新抽卡数据
@ -219,26 +96,6 @@ export const updateGachaLog = async (authKey, uid) => {
};
};
/**
* 欧非分析
* @param {number} ast
* @param {number[]} lst
*/
const getLevelFromList = (ast, lst) => {
if (ast === 0) {
return 2;
}
let level = 0;
for (let numIndex = 0; numIndex < lst.length; numIndex++) {
if (ast <= lst[numIndex]) {
level = 4 - numIndex;
break;
}
}
return level;
};
/**
* 抽卡分析
* @param {string} uid ZZZUID

49
lib/gacha/const.js Normal file
View file

@ -0,0 +1,49 @@
// 池子代码
export const gacha_type_meta_data = {
音擎频段: ['3001'],
独家频段: ['2001'],
常驻频段: ['1001'],
邦布频段: ['5001'],
};
// 欧非阈值
export const FLOORS_MAP = {
邦布频段: [50, 70],
音擎频段: [50, 70],
独家频段: [60, 80],
常驻频段: [60, 80],
};
// 欧非标签
export const HOMO_TAG = [
'非到极致',
'运气不好',
'平稳保底',
'小欧一把',
'欧狗在此',
];
// 欧非表情
export const EMOJI = [
[4, 8, 13],
[1, 10, 5],
[16, 15, 2],
[12, 3, 9],
[6, 14, 7],
];
// 常驻名称
export const NORMAL_LIST = [
'「11号」',
'猫又',
'莱卡恩',
'丽娜',
'格莉丝',
'珂蕾妲',
'拘缚者',
'燃狱齿轮',
'嵌合编译器',
'钢铁肉垫',
'硫磺石',
'啜泣摇篮',
];

89
lib/gacha/core.js Normal file
View file

@ -0,0 +1,89 @@
import { ZZZ_GET_GACHA_LOG_API } from '../mysapi/api.js';
import request from '../../utils/request.js';
import { ZZZGachaLogResp } from '../../model/gacha.js';
/**
* 获取抽卡链接
* @param {string} authKey 米游社认证密钥
* @param {string} gachaType 祈愿类型池子代码
* @param {string} initLogGachaBaseType
* @param {number} page 页数
* @param {string} endId 最后一个数据的 id
* @param {string} size 页面数据大小
* @returns {Promise<string>} 抽卡链接
*/
export const getZZZGachaLink = async (
authKey,
gachaType = '2001',
initLogGachaBaseType = '2',
page = 1,
endId = '0',
size = '20'
) => {
// 暂时直接写死服务器为国服
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',
auth_appid: 'webview_gacha',
init_log_gacha_type: gachaType,
init_log_gacha_base_type: initLogGachaBaseType,
gacha_id: '2c1f5692fdfbb733a08733f9eb69d32aed1d37',
timestamp: timestamp.toString(),
lang: 'zh-cn',
device_type: 'mobile',
plat_type: 'ios',
region: serverId,
authkey: authKey,
game_biz: 'nap_cn',
gacha_type: gachaType,
real_gacha_type: initLogGachaBaseType,
page: page,
size: size,
end_id: endId,
});
// 完整链接
return `${url}?${params}`;
};
/**
* 通过米游社认证密钥获取抽卡记录
* @param {string} authKey 米游社认证密钥
* @param {string} gachaType 祈愿类型池子代码
* @param {string} initLogGachaBaseType
* @param {number} page 页数
* @param {string} endId 最后一个数据的 id
* @returns {Promise<ZZZGachaLogResp>} 抽卡记录
*/
export const getZZZGachaLogByAuthkey = async (
authKey,
gachaType = '2001',
initLogGachaBaseType = '2',
page = 1,
endId = '0'
) => {
// 获取抽卡链接
const link = await getZZZGachaLink(
authKey,
gachaType,
initLogGachaBaseType,
page,
endId
);
// 发送请求
const response = await request(link, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
retry: 3,
});
// 获取数据
const data = await response.json();
if (!data || !data?.data) return null;
return new ZZZGachaLogResp(data.data);
};

19
lib/gacha/tool.js Normal file
View file

@ -0,0 +1,19 @@
/**
* 欧非分析
* @param {number} ast
* @param {number[]} lst
*/
export const getLevelFromList = (ast, lst) => {
if (ast === 0) {
return 2;
}
let level = 0;
for (let numIndex = 0; numIndex < lst.length; numIndex++) {
if (ast <= lst[numIndex]) {
level = 4 - numIndex;
break;
}
}
return level;
};

View file

@ -4,6 +4,7 @@ import crypto from 'crypto';
import ZZZApiTool from './mysapi/tool.js';
import { randomString } from '../utils/data.js';
import MysApi from '../../genshin/model/mys/mysApi.js';
import { MysError } from './error.js';
// const DEVICE_ID = randomString(32).toUpperCase()
// const DEVICE_NAME = randomString(_.random(1, 10));
@ -18,6 +19,7 @@ export default class MysZZZApi extends MysApi {
this.uid = uid;
this.server = this.getServer(uid);
this.apiTool = new ZZZApiTool(uid, this.server);
this.handler = option?.handler || {};
if (typeof this.cookie !== 'string' && this.cookie) {
const ck = Object.values(this.cookie).find(item => {
return item.ck && item.uid === uid;
@ -239,81 +241,32 @@ export default class MysZZZApi extends MysApi {
* @param data 查询请求的数据
* @returns {Promise<*|boolean>}
*/
async checkCode(e, res, type, data = {}) {
if (!res || !e) {
e.reply('米游社接口请求失败,暂时无法查询');
return false;
async checkCode(res, type, data = {}) {
if (!res) {
throw new Error('米游社接口返回数据为空');
}
this.e = e;
this.e.isZZZ = true;
res.retcode = Number(res.retcode);
switch (res.retcode) {
case 0:
break;
case 10102:
if (res.message === 'Data is not public for the user') {
this.e.reply(`\nUID:${this.uid},米游社数据未公开`, false, {
at: this.userId,
});
} else {
this.e.reply(`UID:${this.uid},请先去米游社绑定角色`);
}
break;
case 10041:
case 5003:
this.e.reply(
`UID:${this.uid},米游社账号异常,暂时无法查询,请发送 %绑定设备帮助 查看如何绑定设备`
const code = String(res.retcode);
if (code === '1034' || code === '10035') {
// 如果有注册的mys.req.err调用
if (!!this?.handler && this?.handler?.has('mys.req.err')) {
logger.mark(
`[米游社绝区零查询失败][UID:${this.uid}][qq:${this.userId}] 遇到验证码,尝试调用 Handler mys.req.err`
);
break;
case 10035:
case 1034: {
let handler = this.e.runtime?.handler || {};
// 如果有注册的mys.req.err调用
if (handler.has('mys.req.err')) {
logger.mark(
`[米游社zzz查询失败][UID:${this.uid}][qq:${this.userId}] 遇到验证码,尝试调用 Handler mys.req.err`
);
res =
(await handler.call('mys.req.err', this.e, {
mysApi: this,
type,
res,
data,
mysInfo: this,
})) || res;
}
if (!res || res?.retcode === 1034 || res?.retcode === 10035) {
logger.mark(
`[米游社zzz查询失败][UID:${this.uid}][qq:${this.userId}] 遇到验证码`
);
this.e.reply('米游社zzz查询遇到验证码请稍后再试');
}
break;
res =
(await this.handler.call('mys.req.err', this.e, {
mysApi: this,
type,
res,
data,
mysInfo: this,
})) || res;
}
default:
if (/(登录|login)/i.test(res.message)) {
logger.mark(`[ck失效][UID:${this.uid}]`);
this.e.reply(`UID:${this.uid}米游社cookie已失效`);
} else {
this.e.reply(
`米游社接口报错,暂时无法查询:${res.message || 'error'}`
);
}
break;
}
if (res.retcode !== 0) {
logger.mark(
`[米游社zzz接口报错]${JSON.stringify(res)}UID${this.uid}`
);
throw new Error({
type: 'mysApi',
uid: this.uid,
retcode: res.retcode,
result: res,
});
if (code === '0') {
return res;
}
return res;
throw new MysError(code, this.uid, res);
}
/**
@ -323,8 +276,8 @@ export default class MysZZZApi extends MysApi {
* @param {{deviceFp?: string; query: Record<string, any>; headers: object;}} data
* @param {boolean} cached
*/
async getFinalData(e, type, data = {}, cached = false) {
if (!data.headers) data.headers = {};
async getFinalData(type, data = {}, cached = false) {
if (!data?.headers) data.headers = {};
if (data.deviceFp) {
data.headers['x-rpc-device_fp'] = data.deviceFp;
}
@ -364,7 +317,7 @@ export default class MysZZZApi extends MysApi {
}
}
const result = await this.getData(type, data, cached);
const _data = await this.checkCode(e, result, type, {});
const _data = await this.checkCode(result, type, {});
if (!_data || _data.retcode !== 0) return false;
return _data.data;
}

View file

@ -75,7 +75,9 @@ export class ZZZPlugin extends plugin {
}
// 创建米游社 API 对象
const api = new MysZZZApi(uid, ck);
const api = new MysZZZApi(uid, ck, {
handler: this.e?.runtime?.handler || {},
});
const currentCK = Object.values(ck).find(item => {
return item.ck && item.uid === uid;
});
@ -175,7 +177,11 @@ export class ZZZPlugin extends plugin {
// 获取 米游社 API
const { api, uid } = await this.getAPI();
// 获取用户信息
let userData = await api.getFinalData(this.e, 'zzzUser');
let userData = await api.getFinalData('zzzUser').catch(e => {
this.reply(e.message);
throw e;
});
if (!userData) throw new Error('获取用户数据失败');
// 取第一个用户信息
userData =