feat: Refactor API interactions and enhance user settings management

- Updated ezpp API to include user info retrieval and improved error handling.
- Introduced new writable stores for current user info and loading states.
- Added gamemode enums and utility functions for better gamemode handling.
- Refactored global state management to use consistent naming conventions.
- Enhanced loading and login components to provide better user feedback.
- Updated user settings to include preferred mode and type.
- Improved layout and page components for better state management and user experience.
This commit is contained in:
2025-07-03 11:46:50 +02:00
parent 892f2cea07
commit 651592c333
13 changed files with 680 additions and 128 deletions

View File

@@ -1,14 +1,22 @@
import type { EZPPUser } from '@/types';
import type { EZPPUser, EZPPUserInfoResponse, EZPPUserResponse } from '@/types';
import { betterFetch } from '@better-fetch/fetch';
const BANCHO_ENDPOINT = 'https://c.ez-pp.farm/';
const API_ENDPOINT = 'https://api.ez-pp.farm/';
const ENDPOINT = 'https://ez-pp.farm/';
const timeout = 5000; // 5 seconds;
export const ezppfarm = {
ping: async (): Promise<number | undefined> => {
try {
const start = Date.now();
const request = await betterFetch(BANCHO_ENDPOINT);
const request = await betterFetch(BANCHO_ENDPOINT, {
timeout,
headers: {
'User-Agent': 'EZPPLauncher',
},
});
if (request.error) return undefined;
const ping = Date.now() - start;
return ping;
@@ -23,32 +31,22 @@ export const ezppfarm = {
| {
code: number;
message: string;
user?: {
id: number;
donor: boolean;
name: string;
email: string;
};
user?: EZPPUser;
}
| undefined
> => {
const request = await betterFetch<{
code: number;
message: string;
user?: EZPPUser;
}>('https://ez-pp.farm/login/check', {
const request = await betterFetch<EZPPUserResponse>(`${ENDPOINT}login/check`, {
method: 'POST',
timeout,
body: JSON.stringify({
username: username,
password: password,
}),
headers: {
'Content-Type': 'application/json',
'User-Agent':
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 Edg/128.0.0.0',
'User-Agent': 'EZPPLauncher',
},
});
console.log(request.error);
if (request.error) {
if (request.error.status >= 500 && request.error.status < 600)
throw new Error('Server not reachable');
@@ -56,4 +54,18 @@ export const ezppfarm = {
}
return request.data;
},
getUserInfo: async (userId: number) => {
const request = await betterFetch<EZPPUserInfoResponse>(`${API_ENDPOINT}v1/get_player_info`, {
timeout,
query: {
id: userId,
scope: 'all',
},
headers: {
'Content-Type': 'application/json',
'User-Agent': 'EZPPLauncher',
},
});
return request.error ? undefined : request.data;
},
};

4
src/lib/data.ts Normal file
View File

@@ -0,0 +1,4 @@
import { writable } from 'svelte/store';
import type { EZPPUserInfo } from './types';
export const currentUserInfo = writable<EZPPUserInfo | undefined>(undefined);

186
src/lib/gamemode.ts Normal file
View File

@@ -0,0 +1,186 @@
export enum Gamemodes {
VANILLA_OSU = 0,
VANILLA_TAIKO = 1,
VANILLA_CATCH = 2,
VANILLA_MANIA = 3,
RELAX_OSU = 4,
RELAX_TAIKO = 5,
RELAX_CATCH = 6,
AUTOPILOT_OSU = 8,
}
export enum Mode {
OSU = 0,
TAIKO = 1,
CATCH = 2,
MANIA = 3,
}
export enum Type {
VANILLA = 0,
RELAX = 4,
AUTOPILOT = 8,
}
export const validModes = [Mode.OSU, Mode.TAIKO, Mode.CATCH, Mode.MANIA];
export const validTypes = [Type.VANILLA, Type.RELAX, Type.AUTOPILOT];
export const validModeTypeCombinations = [0, 1, 2, 3, 4, 5, 6, 8];
export const validModeTypeCombinationsSorted = [0, 4, 8, 1, 5, 2, 6, 3];
export const validMode = (modeStr: string) => modeStrToInt(modeStr) !== undefined;
export const validType = (typeStr: string) => typeStrToInt(typeStr) !== undefined;
export const modeStrToInt = (modeStr: 'osu' | 'taiko' | 'catch' | 'mania' | string) => {
switch (modeStr) {
case 'taiko':
return Mode.TAIKO;
case 'catch':
return Mode.CATCH;
case 'mania':
return Mode.MANIA;
case 'osu':
return Mode.OSU;
}
return undefined;
};
export const modeIntToStr = (modeInt: number) => {
switch (modeInt) {
case Mode.TAIKO:
return 'taiko';
case Mode.CATCH:
return 'catch';
case Mode.MANIA:
return 'mania';
case Mode.OSU:
return 'osu';
}
return undefined;
};
export const typeStrToInt = (typeStr: 'vanilla' | 'relax' | 'autopilot' | string) => {
switch (typeStr) {
case 'relax':
return Type.RELAX;
case 'autopilot':
return Type.AUTOPILOT;
case 'vanilla':
return Type.VANILLA;
}
return undefined;
};
export const typeIntToStr = (typeInt: number) => {
switch (typeInt) {
case Type.RELAX:
return 'relax';
case Type.AUTOPILOT:
return 'autopilot';
case Type.VANILLA:
return 'vanilla';
}
return undefined;
};
export const getGamemodeInt = (
mode: 'osu' | 'taiko' | 'catch' | 'mania' | string | undefined,
type: 'vanilla' | 'relax' | 'autopilot' | string | undefined
) => {
let modee = 0;
switch (mode) {
case 'taiko':
modee += Mode.TAIKO;
break;
case 'catch':
modee += Mode.CATCH;
break;
case 'mania':
modee += Mode.MANIA;
break;
}
switch (type) {
case 'relax':
modee += Type.RELAX;
break;
case 'autopilot':
modee += Type.AUTOPILOT;
break;
}
return modee;
};
export const getGamemodeName = (
mode: 'osu' | 'taiko' | 'catch' | 'mania' | string | undefined,
type: 'vanilla' | 'relax' | 'autopilot' | string | undefined
) => {
let modeStr = '';
switch (mode) {
case 'taiko':
modeStr += 'taiko!';
break;
case 'catch':
modeStr += 'catch!';
break;
case 'mania':
modeStr += 'mania!';
break;
default:
modeStr += 'osu!';
break;
}
switch (type) {
case 'relax':
modeStr += 'rx';
break;
case 'autopilot':
modeStr += 'ap';
break;
default:
modeStr += 'vn';
break;
}
return modeStr;
};
export const getModeAndTypeFromGamemode = (gamemode: number) => {
let mode = Mode.OSU;
let type = Type.VANILLA;
const vanillaMode = gamemode % 4;
switch (vanillaMode) {
case Mode.TAIKO:
mode = Mode.TAIKO;
break;
case Mode.CATCH:
mode = Mode.CATCH;
break;
case Mode.MANIA:
mode = Mode.MANIA;
break;
}
const typee = gamemode - vanillaMode;
switch (typee) {
case Type.RELAX:
type = Type.RELAX;
break;
case Type.AUTOPILOT:
type = Type.AUTOPILOT;
break;
}
return {
mode,
type,
};
};
export const isValidGamemode = (gamemodeInt: number) => {
return validModeTypeCombinations.includes(gamemodeInt);
};

View File

@@ -3,15 +3,18 @@ import { ezppfarm } from './api/ezpp';
import type { Component } from 'svelte';
import Loading from '../pages/Loading.svelte';
export const current_view = writable<Component>(Loading);
export const first_startup = writable<boolean>(false);
export const currentView = writable<Component>(Loading);
export const server_ping = writable<number | undefined>(undefined);
export const server_connection_fails = writable(0);
export const currentLoadingInfo = writable<string>('Initializing...');
export const online_friends = writable<number | undefined>(undefined);
export const firstStartup = writable<boolean>(false);
export const beatmap_sets = writable<number | undefined>(undefined);
export const serverPing = writable<number | undefined>(undefined);
export const serverConnectionFails = writable(0);
export const onlineFriends = writable<number | undefined>(undefined);
export const beatmapSets = writable<number | undefined>(undefined);
export const setupValues = () => {
updatePing();
@@ -27,23 +30,19 @@ export const setupValues = () => {
};
const updatePing = async () => {
const serverPing = await ezppfarm.ping();
if (!serverPing) {
server_connection_fails.update((num) => num + 1);
const currentServerPing = await ezppfarm.ping();
if (!currentServerPing) {
serverConnectionFails.update((num) => num + 1);
} else {
server_connection_fails.set(0);
server_ping.set(serverPing);
serverConnectionFails.set(0);
serverPing.set(currentServerPing);
}
};
const updateFriends = async () => {
await new Promise((res) => setTimeout(res, Math.random() * 300));
const onlineFriends = Math.round(Math.random() * 10);
online_friends.set(onlineFriends);
const currentOnlineFriends = Math.round(Math.random() * 10);
onlineFriends.set(currentOnlineFriends);
};
const updateBeatmapSets = async () => {
await new Promise((res) => setTimeout(res, Math.random() * 1500));
const beatmapSets = Math.round(Math.random() * 5000);
beatmap_sets.set(beatmapSets);
};
const updateBeatmapSets = async () => {};

View File

@@ -1,6 +1,97 @@
export type EZPPUserResponse = {
code: number;
message: string;
user?: EZPPUser;
};
export type EZPPUser = {
id: number;
donor: boolean;
name: string;
email: string;
};
export type EZPPUserInfoResponse = {
status: string;
player: EZPPUserInfo;
};
export type EZPPUserInfo = {
info: {
id: number;
name: string;
safe_name: string;
priv: number;
country: string;
silence_end: number;
donor_end: number;
creation_time: number;
latest_activity: number;
clan_id: number;
clan_priv: number;
preferred_mode: number;
preferred_type: number;
play_style: number;
custom_badge_enabled: number;
custom_badge_name: string;
custom_badge_icon: string;
custom_badge_color: string;
userpage_content: string;
recentFailed: number;
social_discord: string;
social_youtube: string;
social_twitter: string;
social_twitch: string;
social_github: string;
social_osu: string;
clan: {
id: number;
name: string;
tag: string;
owner: number;
created_at: Date;
};
username_history: string[];
};
stats: {
[key: string]: {
id: number;
mode: number;
tscore: number;
rscore: number;
pp: number;
plays: number;
playtime: number;
acc: number;
max_combo: number;
total_hits: number;
replay_views: number;
xh_count: number;
x_count: number;
sh_count: number;
s_count: number;
a_count: number;
level: number;
level_progress: number;
rank: number;
country_rank: number;
history: {
pp: number[];
};
};
};
events: {
userId: number;
name: string;
mapId: number;
setId: number;
artist: string;
title: string;
version: string;
mode: number;
rank: number;
grade: string;
event: 'GAINED' | 'LOST';
time: Date;
}[];
};

View File

@@ -3,9 +3,12 @@ import { Config } from './config';
export const userSettings = writable<Config>(new Config('user_settings', false));
export const customCursor = writable<boolean>(true);
export const cursorSmoothening = writable<boolean>(true);
export const customCursor = writable<boolean>(false);
export const cursorSmoothening = writable<boolean>(false);
export const cursorSmoothness = writable<number>(180);
export const reduceAnimations = writable<boolean>(false);
export const osuInstallationPath = writable<string>('');
export const preferredMode = writable<number>(0);
export const preferredType = writable<number>(0);

View File

@@ -1,11 +1,11 @@
import { createAudioStore } from "@elron/svelte-audio-store";
import { type ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";
import { createAudioStore } from '@elron/svelte-audio-store';
import { type ClassValue, clsx } from 'clsx';
import { twMerge } from 'tailwind-merge';
const sounds = {
menuHeartbeat: "/audio/menuHeartbeat.mp3",
menuBack: "/audio/menuBack.wav",
menuHit: "/audio/menuHit.wav",
menuHeartbeat: '/audio/menuHeartbeat.mp3',
menuBack: '/audio/menuBack.wav',
menuHit: '/audio/menuHit.wav',
};
export const gameSounds = createAudioStore(sounds);
@@ -23,3 +23,30 @@ export const playAudio = (path: string, volume: number) => {
audio.volume = volume;
audio.play();
};
export const isNumber = (value: unknown) => {
if (typeof value === 'number' || typeof value === 'string') {
return value.toString().match(/^-?\d+(\.\d+)?$/) !== null;
}
return false;
};
export const formatTimeReadable = (initialSeconds: number) => {
let seconds = initialSeconds;
const days = Math.floor(seconds / (24 * 3600));
seconds -= days * 24 * 3600;
const hours = Math.floor(seconds / 3600);
seconds -= hours * 3600;
const minutes = Math.floor(seconds / 60);
let result = '';
if (days > 0) result += `${days}d `;
if (hours > 0) result += `${hours}h `;
result += `${minutes}m`;
return result.trim();
};