import { DateTime } from 'luxon'; import { useEffect, useState } from 'react'; import type { MirrorFileEntry, StoredData } from '../../types'; import { fetchJson } from '../../utils/api'; import { BASE_URL, FILE_SIZE_OPTS, gameTargets, launcherTargets } from '../../utils/constants'; import math from '../../utils/math'; interface Props { mirrorFileDb: MirrorFileEntry[]; } interface GameVersionData { version: string; date: string; } interface OverviewTableData { region: string; channelName: string; version: string; packedSize: string; unpackedSize: string; } interface ResourceVersionData { platform: string; version: string; date: string; } export default function OverviewTab({ mirrorFileDb }: Props) { const [globalPkg, setGlobalPkg] = useState(null); const [chinaPkg, setChinaPkg] = useState(null); const [tableData, setTableData] = useState([]); const [resourceData, setResourceData] = useState([]); const [mirrorStats, setMirrorStats] = useState('---'); useEffect(() => { // 1. Latest Version Info const fetchLatestVersion = async (url: string) => { try { const dat = await fetchJson[]>(url); const latest = dat.at(-1); if (!latest) return { version: '---', date: '---' }; return { version: latest.rsp.version, date: DateTime.fromISO(latest.updatedAt).toFormat('yyyy/MM/dd HH:mm:ss'), }; } catch { return { version: '---', date: '---' }; } }; fetchLatestVersion(`${BASE_URL}/akEndfield/launcher/game/6/all.json`).then(setGlobalPkg); fetchLatestVersion(`${BASE_URL}/akEndfield/launcher/game/1/all.json`).then(setChinaPkg); // 2. Game Packages Table & Mirror Stats Calculation const calculateStatsAndTable = async () => { const mirrorOrigSet = new Set(); for (const m of mirrorFileDb) { try { const u = new URL(m.orig); u.search = ''; mirrorOrigSet.add(u.toString()); } catch {} } const countedUrls = new Set(); let totalMirrorSize = 0; const checkAndAddSize = (url: string, size: number) => { if (!url || isNaN(size)) return; try { const u = new URL(url); u.search = ''; const cleanUrl = u.toString(); if (countedUrls.has(cleanUrl)) return; if (mirrorOrigSet.has(cleanUrl)) { totalMirrorSize += size; countedUrls.add(cleanUrl); } } catch {} }; const newTableData: OverviewTableData[] = []; // Game Packages for (const target of gameTargets) { const url = `${BASE_URL}/akEndfield/launcher/game/${target.dirName}/all.json`; try { const data = await fetchJson[]>(url); if (!data || data.length === 0) continue; const latest = data[data.length - 1]; if (!latest) continue; const version = latest.rsp.version; const packedSize = math.arrayTotal(latest.rsp.pkg.packs.map((f: any) => parseInt(f.package_size))); const totalSize = parseInt(latest.rsp.pkg.total_size); const unpackedSize = totalSize - packedSize; newTableData.push({ region: target.region === 'cn' ? 'China' : 'Global', channelName: target.name, version: version, packedSize: math.formatFileSize(packedSize, FILE_SIZE_OPTS), unpackedSize: math.formatFileSize(unpackedSize, FILE_SIZE_OPTS), }); for (const entry of data) { if (entry.rsp.pkg && entry.rsp.pkg.packs) { for (const pack of entry.rsp.pkg.packs) { checkAndAddSize(pack.url, parseInt(pack.package_size)); } } } } catch (e) { console.warn('Overview: Failed to fetch game data', target.name, e); } } setTableData(newTableData); // Patches (only for stats) for (const target of gameTargets) { const url = `${BASE_URL}/akEndfield/launcher/game/${target.dirName}/all_patch.json`; try { const data = await fetchJson[]>(url); for (const entry of data) { if (!entry.rsp.patch) continue; if (entry.rsp.patch.url) { checkAndAddSize(entry.rsp.patch.url, parseInt(entry.rsp.patch.package_size)); } if (entry.rsp.patch.patches) { for (const p of entry.rsp.patch.patches) { checkAndAddSize(p.url, parseInt(p.package_size)); } } } } catch {} } // Launchers (only for stats) for (const region of launcherTargets) { for (const app of region.apps) { try { const urlZip = `${BASE_URL}/akEndfield/launcher/launcher/${app}/${region.channel}/all.json`; const dataZip = await fetchJson[]>(urlZip); for (const e of dataZip) { checkAndAddSize(e.rsp.zip_package_url, parseInt(e.rsp.package_size)); } } catch {} try { const urlExe = `${BASE_URL}/akEndfield/launcher/launcherExe/${app}/${region.channel}/all.json`; const dataExe = await fetchJson[]>(urlExe); for (const e of dataExe) { checkAndAddSize(e.rsp.exe_url, parseInt(e.rsp.exe_size)); } } catch {} } } setMirrorStats(math.formatFileSize(totalMirrorSize, { ...FILE_SIZE_OPTS, unit: 'G' })); }; calculateStatsAndTable(); // 3. Latest Game Resources const fetchResources = async () => { const resPlatforms = ['Windows', 'Android', 'iOS', 'PlayStation']; const resData = await Promise.all( resPlatforms.map(async (p) => { try { const url = `${BASE_URL}/akEndfield/launcher/game_resources/6/${p}/all.json`; const dat = await fetchJson[]>(url); return dat.at(-1); } catch { return undefined; } }), ); const newResourceData = resPlatforms.map((p, i) => { const item = resData[i]; if (!item) { return { platform: p, version: '---', date: '' }; } const initialRes = item.rsp.resources.find((e: any) => e.name === 'initial'); const mainRes = item.rsp.resources.find((e: any) => e.name === 'main'); let version = '---'; if (initialRes && mainRes) { version = initialRes.version === mainRes.version ? mainRes.version : item.rsp.res_version; } return { platform: p, version: version, date: DateTime.fromISO(item.updatedAt).toFormat('yyyy/MM/dd HH:mm:ss'), }; }); setResourceData(newResourceData); }; fetchResources(); }, [mirrorFileDb]); if (!globalPkg || !chinaPkg) { return (

Loading overview...

); } return (

Latest Game Packages

{globalPkg.version}
{globalPkg.date}
Latest Version (Global)

{chinaPkg.version}
{chinaPkg.date}
Latest Version (China)

{tableData.map((row, i) => ( ))}
Region Channel Version Packed Unpacked
{row.region} {row.channelName} {row.version} {row.packedSize} {row.unpackedSize}

Latest Game Resources

{resourceData.map((res, i) => (

{res.version}
{res.date && ( <> {res.date}
)} {res.platform}

))}

Mirror Statistics

{mirrorStats}
uploaded to mirror

); }