diff --git a/lib/assets.js b/lib/assets.js new file mode 100644 index 0000000..3dfc5ed --- /dev/null +++ b/lib/assets.js @@ -0,0 +1,65 @@ +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', + '[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', +}; + +export const getFatestUrl = async () => { + if ( + lastFindFastestUrl.url && + Date.now() - lastFindFastestUrl.time < 1000 * 60 * 5 + ) { + return lastFindFastestUrl.url; + } + const urls = Object.values(URL_LIB); + const url = findLowestLatencyUrl(urls); + lastFindFastestUrl = { + url, + time: Date.now(), + }; + return url; +}; + +/** + * Get resource remote path + * @param {keyof TYPE_PATH} type + * @param {keyof RESOURCE_PATH | keyof GUIDE_PATH} label + * @param {string} name + * @returns + */ +export const getRemotePath = async (type, label, name) => { + const url = await getFatestUrl(); + return `${url}/ZZZeroUID/${type}/${label}/${name}`; +}; + +// 获取资源远程路径 +export const getResourceRemotePath = async (label, name) => { + return getRemotePath(TYPE_PATH.resource, label, name); +}; diff --git a/lib/convert.js b/lib/convert.js index 80d0252..acd8188 100644 --- a/lib/convert.js +++ b/lib/convert.js @@ -1 +1,5 @@ export * as element from './convert/element.js'; + +export * as char from './convert/char.js'; + +export * as weapon from './convert/weapon.js'; diff --git a/lib/convert/char.js b/lib/convert/char.js index fd40a1d..2045f59 100644 --- a/lib/convert/char.js +++ b/lib/convert/char.js @@ -1,5 +1,5 @@ import settings from '../settings.js'; -import PartnerId2SpriteId from '../../resources/map/PartnerId2SpriteId.json' assert { type: "json" }; +import PartnerId2SpriteId from '../../resources/map/PartnerId2Data.json' assert { type: 'json' }; /** * diff --git a/lib/convert/weapon.js b/lib/convert/weapon.js index 49fab8b..1fec456 100644 --- a/lib/convert/weapon.js +++ b/lib/convert/weapon.js @@ -1,10 +1,10 @@ -import WeaponId2Sprite from '../../resources/map/WeaponId2Sprite.json' assert { type: "json" }; +import WeaponId2Sprite from '../../resources/map/WeaponId2Sprite.json' assert { type: 'json' }; /** * @param {string} id * @returns string */ -export const IDToWeaponName = id => { +export const IDToWeaponFileName = id => { const data = WeaponId2Sprite?.[id]; return data; }; @@ -13,7 +13,7 @@ export const IDToWeaponName = id => { * @param {string} name * @returns string */ -export const weaponNameToID = name => { +export const weaponFileNameToID = name => { for (const [id, data] of Object.entries(WeaponId2Sprite)) { if (data === name) return id; } diff --git a/lib/download.js b/lib/download.js index 2a06505..c5f3bf7 100644 --- a/lib/download.js +++ b/lib/download.js @@ -2,14 +2,39 @@ import path from 'path'; import fs from 'fs'; import { ZZZ_SQUARE_AVATAR, ZZZ_SQUARE_BANGBOO } from './mysapi/api.js'; import { imageResourcesPath } from './path.js'; +import { weapon } from './convert.js'; +import { getResourceRemotePath } from './assets.js'; const ZZZ_SQUARE_AVATAR_PATH = path.join(imageResourcesPath, 'square_avatar'); const ZZZ_SQUARE_BANGBOO_PATH = path.join( imageResourcesPath, 'bangboo_square_avatar' ); +const ZZZ_WEAPON_PATH = path.join(imageResourcesPath, 'weapon'); const ZZZ_GUIDES_PATH = path.join(imageResourcesPath, 'guides'); +// 将下面的下载封装起来,支持错误重试5次 +const downloadFile = async (url, savePath) => { + const _download = async (url, savePath, retry = 0) => { + 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); + } + }; + return await _download(url, savePath); +}; + /** * * @param {string | number} charID @@ -21,14 +46,8 @@ export const getSquareAvatar = async charID => { if (fs.existsSync(avatarPath)) return avatarPath; const url = `${ZZZ_SQUARE_AVATAR}/${filename}`; const savePath = avatarPath; - const download = await fetch(url); - const arrayBuffer = await download.arrayBuffer(); - const buffer = Buffer.from(arrayBuffer); - if (!fs.existsSync(ZZZ_SQUARE_AVATAR_PATH)) { - fs.mkdirSync(ZZZ_SQUARE_AVATAR_PATH, { recursive: true }); - } - fs.writeFileSync(savePath, buffer); - return avatarPath; + const download = await downloadFile(url, savePath); + return download; }; /** @@ -42,12 +61,25 @@ export const getSquareBangboo = async bangbooId => { if (fs.existsSync(bangbooPath)) return bangbooPath; const url = `${ZZZ_SQUARE_BANGBOO}/${filename}`; const savePath = bangbooPath; - const download = await fetch(url); - const arrayBuffer = await download.arrayBuffer(); - const buffer = Buffer.from(arrayBuffer); - if (!fs.existsSync(ZZZ_SQUARE_BANGBOO_PATH)) { - fs.mkdirSync(ZZZ_SQUARE_BANGBOO_PATH, { recursive: true }); - } - fs.writeFileSync(savePath, buffer); - return bangbooPath; + const download = await downloadFile(url, savePath); + return download; +}; + +/** + * Get weapon image path + * @param {string} id + * @returns Promise + */ +export const getWeaponImage = async id => { + logger.mark('getWeaponImage', id); + const name = weapon.IDToWeaponFileName(id); + logger.mark('getWeaponImage', name); + const filename = `${name}.png`; + const weaponPath = path.join(ZZZ_WEAPON_PATH, filename); + if (fs.existsSync(weaponPath)) return weaponPath; + const url = await getResourceRemotePath('weapon', filename); + const savePath = weaponPath; + const download = await downloadFile(url, savePath); + logger.mark('getWeaponImage', download); + return download; }; diff --git a/lib/gacha.js b/lib/gacha.js index e474994..60a2ba9 100644 --- a/lib/gacha.js +++ b/lib/gacha.js @@ -100,6 +100,7 @@ export async function updateGachaLog(authKey, uid) { const lastSaved = previousLog[name]?.[0]; let page = 1; let endId = '0'; + const newData = []; for (const type of gacha_type_meta_data[name]) { queryLabel: while (true) { const log = await getZZZGachaLogByAuthkey( @@ -116,13 +117,14 @@ export async function updateGachaLog(authKey, uid) { if (lastSaved && lastSaved.equals(item)) { break queryLabel; } - previousLog[name].push(item); + newData.push(item); } endId = log.list[log.list.length - 1]?.id || endId; page++; await sleep(400); } } + previousLog[name] = [...newData, ...previousLog[name]]; } saveGachaLog(uid, previousLog); return previousLog; @@ -180,9 +182,9 @@ export async function anaylizeGachaLog(uid) { let luck = 0; let i = 0; for (const item of data) { - await item.get_assets(); let isUp = true; if (item.rank_type === '4') { + await item.get_assets(); if (NORMAL_LIST.includes(item.name)) { isUp = false; } diff --git a/model/gacha.js b/model/gacha.js index 68f135f..73900c9 100644 --- a/model/gacha.js +++ b/model/gacha.js @@ -1,4 +1,8 @@ -import { getSquareAvatar, getSquareBangboo } from '../lib/download.js'; +import { + getSquareAvatar, + getSquareBangboo, + getWeaponImage, +} from '../lib/download.js'; /** * @class @@ -59,6 +63,8 @@ export class SingleGachaLog { async get_assets() { if (this.item_type === '音擎') { + const result = await getWeaponImage(this.item_id); + this.square_icon = result; } else if (this.item_type === '邦布') { const result = await getSquareBangboo(this.item_id); this.square_icon = result; diff --git a/resources/images/.gitkeep b/resources/images/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/resources/map/EquipId2Data_1.0.0.json b/resources/map/EquipId2Data_1.0.0.json new file mode 100644 index 0000000..267347a --- /dev/null +++ b/resources/map/EquipId2Data_1.0.0.json @@ -0,0 +1,86 @@ +{ + "31000": { + "equip_id_list": [ + 31021, 31022, 31023, 31024, 31025, 31026, 31031, 31032, 31033, 31034, + 31035, 31036, 31041, 31042, 31043, 31044, 31045, 31046 + ], + "sprite_file": "3DSuitWoodpeckerElectro" + }, + "31100": { + "equip_id_list": [ + 31121, 31122, 31123, 31124, 31125, 31126, 31131, 31132, 31133, 31134, + 31135, 31136, 31141, 31142, 31143, 31144, 31145, 31146 + ], + "sprite_file": "3DSuitPufferElectro" + }, + "31200": { + "equip_id_list": [ + 31221, 31222, 31223, 31224, 31225, 31226, 31231, 31232, 31233, 31234, + 31235, 31236, 31241, 31242, 31243, 31244, 31245, 31246 + ], + "sprite_file": "3DSuitShockstarDisco" + }, + "31300": { + "equip_id_list": [ + 31321, 31322, 31323, 31324, 31325, 31326, 31331, 31332, 31333, 31334, + 31335, 31336, 31341, 31342, 31343, 31344, 31345, 31346 + ], + "sprite_file": "3DSuitFreedomBlues" + }, + "31400": { + "equip_id_list": [ + 31421, 31422, 31423, 31424, 31425, 31426, 31431, 31432, 31433, 31434, + 31435, 31436, 31441, 31442, 31443, 31444, 31445, 31446 + ], + "sprite_file": "3DSuitHormonePunk" + }, + "31500": { + "equip_id_list": [ + 31521, 31522, 31523, 31524, 31525, 31526, 31531, 31532, 31533, 31534, + 31535, 31536, 31541, 31542, 31543, 31544, 31545, 31546 + ], + "sprite_file": "3DSuitSoulRock" + }, + "31600": { + "equip_id_list": [ + 31621, 31622, 31623, 31624, 31625, 31626, 31631, 31632, 31633, 31634, + 31635, 31636, 31641, 31642, 31643, 31644, 31645, 31646 + ], + "sprite_file": "3DSuitSwingJazz" + }, + "32200": { + "equip_id_list": [ + 32221, 32222, 32223, 32224, 32225, 32226, 32231, 32232, 32233, 32234, + 32235, 32236, 32241, 32242, 32243, 32244, 32245, 32246 + ], + "sprite_file": "3DSuitInfernoMetal" + }, + "32300": { + "equip_id_list": [ + 32321, 32322, 32323, 32324, 32325, 32326, 32331, 32332, 32333, 32334, + 32335, 32336, 32341, 32342, 32343, 32344, 32345, 32346 + ], + "sprite_file": "3DSuitChaosMetal" + }, + "32400": { + "equip_id_list": [ + 32421, 32422, 32423, 32424, 32425, 32426, 32431, 32432, 32433, 32434, + 32435, 32436, 32441, 32442, 32443, 32444, 32445, 32446 + ], + "sprite_file": "3DSuitThunderMetal" + }, + "32500": { + "equip_id_list": [ + 32521, 32522, 32523, 32524, 32525, 32526, 32531, 32532, 32533, 32534, + 32535, 32536, 32541, 32542, 32543, 32544, 32545, 32546 + ], + "sprite_file": "3DSuitPolarMetal" + }, + "32600": { + "equip_id_list": [ + 32621, 32622, 32623, 32624, 32625, 32626, 32631, 32632, 32633, 32634, + 32635, 32636, 32641, 32642, 32643, 32644, 32645, 32646 + ], + "sprite_file": "3DSuitFangedMetal" + } +} diff --git a/resources/map/PartnerId2SpriteId.json b/resources/map/PartnerId2Data.json similarity index 100% rename from resources/map/PartnerId2SpriteId.json rename to resources/map/PartnerId2Data.json diff --git a/utils/network.js b/utils/network.js new file mode 100644 index 0000000..afec6b0 --- /dev/null +++ b/utils/network.js @@ -0,0 +1,31 @@ +import http from 'http'; +import https from 'https'; +export async function checkLatency(url) { + let request = http; + if (url.startsWith('https')) { + request = https; + } + return new Promise(resolve => { + const start = Date.now(); + request + .get(url, res => { + res.on('data', () => {}); + res.on('end', () => { + const latency = Date.now() - start; + resolve({ url, latency }); + }); + }) + .on('error', err => { + logger.mark(`Error checking ${url}:`, err.message); + resolve({ url, latency: Infinity }); + }); + }); +} + +export async function findLowestLatencyUrl(urls) { + const results = await Promise.all(urls.map(checkLatency)); + const lowestLatencyResult = results.reduce((prev, curr) => + prev.latency < curr.latency ? prev : curr + ); + return lowestLatencyResult.url; +}