feat: add osu status to rpc

This commit is contained in:
2025-07-08 14:56:44 +02:00
parent 9f62332334
commit d6623891bb
10 changed files with 256 additions and 15 deletions

View File

@@ -1,4 +1,9 @@
import type { EZPPUser, EZPPUserInfoResponse, EZPPUserResponse } from '@/types';
import type {
EZPPUser,
EZPPUserInfoResponse,
EZPPUserResponse,
EZPPUSerStatusResponse,
} from '@/types';
import { betterFetch } from '@better-fetch/fetch';
const BANCHO_ENDPOINT = 'https://c.ez-pp.farm/';
@@ -68,4 +73,20 @@ export const ezppfarm = {
});
return request.error ? undefined : request.data;
},
getUserStatus: async (userId: number) => {
const request = await betterFetch<EZPPUSerStatusResponse>(
`${API_ENDPOINT}v1/get_player_status`,
{
timeout,
query: {
id: userId,
},
headers: {
'Content-Type': 'application/json',
'User-Agent': 'EZPPLauncher',
},
}
);
return request.error ? undefined : request.data;
},
};

View File

@@ -3,15 +3,15 @@ import { invoke } from '@tauri-apps/api/core';
export const connect = async () => await invoke('presence_connect');
export const disconnect = async () => await invoke('presence_disconnect');
export const updateStatus = async (status: {
state?: string;
details?: string;
large_image_key?: string;
state?: string | null;
details?: string | null;
largeImageKey?: string;
}) =>
await invoke('presence_update_status', {
state: status.state,
details: status.details,
largeImageKey: status.large_image_key,
largeImageKey: status.largeImageKey,
});
export const updateUser = async (user: { username: string; id: string }) =>
export const updateUser = async (user: { username: string; id?: string | null }) =>
await invoke('presence_update_user', { username: user.username, id: user.id });
export const isConnected = async () => await invoke<boolean>('presence_is_connected');

View File

@@ -185,3 +185,65 @@ export type Release = {
browser_download_url: string;
}[];
};
export type EZPPUSerStatusResponse = EZPPUserOfflineStatus | EZPPUserOnlineStatus;
type EZPPUserOfflineStatus = {
status: string;
player_status: {
online: false;
last_seen: number;
};
};
type EZPPUserOnlineStatus = {
status: string;
player_status: {
online: true;
login_time: number;
status: {
action: EZPPActionStatus;
info_text: string;
mode: number;
mods: number;
beatmap: EZPPUserBeatmapStatus | null;
};
};
};
type EZPPUserBeatmapStatus = {
md5: string;
id: number;
set_id: number;
artist: string;
title: string;
version: string;
creator: string;
last_update: string;
total_length: number;
max_combo: number;
status: number;
plays: number;
passes: number;
mode: number;
bpm: number;
cs: number;
od: number;
ar: number;
hp: number;
diff: number;
};
export enum EZPPActionStatus {
AFK = 1,
PLAYING = 2,
EDITING = 3,
MODDING = 4,
MULTIPLAYER_SELECT = 5,
WATCHING = 6,
TESTING = 8,
SUBMITTING = 9,
MULTIPLAYER_IDLE = 11,
MULTIPLAYER_PLAYING = 12,
DIRECT = 13,
}

View File

@@ -89,3 +89,14 @@ export const formatBytes = (bytes: number, decimals = 2) => {
export const openURL = async (url: string) => {
await invoke('open_url_in_browser', { url });
};
export const urlIsValidImage = async (url: string) => {
try {
const request = await fetch(url);
if (!request.ok) return false;
const contentType = request.headers.get('content-type');
return contentType?.startsWith('image/');
} catch {
return false;
}
};

View File

@@ -48,6 +48,7 @@
numberHumanReadable,
openURL,
releaseStreamToReadable,
urlIsValidImage,
} from '@/utils';
import { fade, scale } from 'svelte/transition';
import { Checkbox } from '@/components/ui/checkbox';
@@ -98,6 +99,8 @@
import { getCurrentWindow } from '@tauri-apps/api/window';
import { ezppfarm } from '@/api/ezpp';
import Hearts from '@/components/ui/effects/Hearts.svelte';
import { EZPPActionStatus } from '@/types';
import * as presence from '@/presence';
let selectedTab = $state('home');
let progress = $state(-1);
@@ -315,7 +318,116 @@
await replaceUIFiles(osuPath, false);
await new Promise((res) => setTimeout(res, 1000));
await getCurrentWindow().hide();
let presenceUpdater: number | undefined = undefined;
const isPresenceConnected = await presence.isConnected();
if ($discordPresence && isPresenceConnected) {
let osuDetected = false;
presenceUpdater = window.setInterval(async () => {
if (!osuDetected) {
const osuRunning = await isOsuRunning();
if (osuRunning) osuDetected = true;
return;
}
if ($currentUser) {
const userStatus = await ezppfarm.getUserStatus($currentUser.id);
if (userStatus?.player_status.online) {
let largeImageKey = 'ezppfarm';
let details = 'Idle...';
let state =
userStatus.player_status.status.info_text.length > 0
? userStatus.player_status.status.info_text
: ' ';
let beatmapCover = false;
const gamemode = getModeAndTypeFromGamemode(userStatus.player_status.status.mode);
const gamemodeName = getGamemodeName(
modeIntToStr(gamemode.mode),
typeIntToStr(gamemode.type)
);
switch (userStatus.player_status.status.action) {
case EZPPActionStatus.AFK:
details = 'AFK...';
state = ' ';
break;
case EZPPActionStatus.PLAYING:
details = 'Playing...';
break;
case EZPPActionStatus.EDITING:
details = 'Editing...';
break;
case EZPPActionStatus.MODDING:
details = 'Modding...';
break;
case EZPPActionStatus.MULTIPLAYER_SELECT:
details = 'Multiplayer: Selecting a Beatmap...';
state = ' ';
break;
case EZPPActionStatus.WATCHING:
details = 'Watching...';
break;
case EZPPActionStatus.TESTING:
details = 'Testing...';
break;
case EZPPActionStatus.SUBMITTING:
details = 'Submitting...';
break;
case EZPPActionStatus.MULTIPLAYER_IDLE:
details = 'Multiplayer: Idle...';
state = ' ';
break;
case EZPPActionStatus.MULTIPLAYER_PLAYING:
details = 'Multiplayer: Playing...';
break;
case EZPPActionStatus.DIRECT:
details = 'Browsing osu!direct...';
state = ' ';
break;
}
if (userStatus.player_status.status.beatmap !== null && beatmapCover) {
const beatmapCoverImage = `https://assets.ppy.sh/beatmaps/${userStatus.player_status.status.beatmap.set_id}/covers/list@2x.jpg`;
const isValidImage = await urlIsValidImage(beatmapCoverImage);
if (isValidImage) largeImageKey = beatmapCoverImage;
}
details = `[${gamemodeName}] ${details}`;
await Promise.all([
presence.updateUser({
username: $currentUser.name,
id: $currentUser.id.toFixed(),
}),
presence.updateStatus({
details,
state,
largeImageKey,
}),
]);
}
}
}, 1000 * 5);
}
await runOsu(osuPath, true);
if (presenceUpdater) {
window.clearInterval(presenceUpdater);
await Promise.all([
presence.updateUser({
username: ' ',
id: null,
}),
presence.updateStatus({
details: null,
state: 'Idle in Launcher...',
largeImageKey: 'ezppfarm',
}),
]);
}
launchInfo = 'Cleaning up...';
await getCurrentWindow().show();
await new Promise((res) => setTimeout(res, 1000));

View File

@@ -109,14 +109,6 @@
osuInstallationPath.set(config_osu_installation_path.get(''));
discordPresence.set(config_discord_presence.get(true));
try {
if ($discordPresence) {
presenceLoading.set(true);
await presence.connect();
presenceLoading.set(false);
}
} catch {}
patch.subscribe((val) => config_patching.set(val));
customCursor.subscribe((val) => config_custom_cursor.set(val));
cursorSmoothening.subscribe((val) => config_cursor_smoothening.set(val));
@@ -134,6 +126,15 @@
} catch {}
});
try {
if ($discordPresence) {
currentLoadingInfo.set('Connecting to Discord RPC...');
presenceLoading.set(true);
await presence.connect();
presenceLoading.set(false);
}
} catch {}
firstStartup.set(isFirstStartup);
});
</script>