Skip to main content

hypixel/
mojang.rs

1//! Username and UUID resolution against the Mojang API.
2//!
3//! Every keyed Hypixel endpoint takes a player UUID, but people know
4//! usernames; these helpers bridge the two. They call Mojang, not Hypixel,
5//! and need no API key.
6
7use serde::Deserialize;
8
9use crate::error::{Error, Result};
10
11const PROFILE_URL: &str = "https://api.mojang.com/users/profiles/minecraft";
12const SESSION_URL: &str = "https://sessionserver.mojang.com/session/minecraft/profile";
13
14#[derive(Deserialize)]
15struct MojangProfile {
16    id: String,
17    name: String,
18}
19
20/// Resolve a Minecraft username to its undashed UUID.
21///
22/// Returns `Ok(None)` when no account has that name.
23pub async fn uuid_for_username(username: &str) -> Result<Option<String>> {
24    Ok(fetch_profile(&format!("{PROFILE_URL}/{username}"))
25        .await?
26        .map(|p| p.id))
27}
28
29/// Resolve a player UUID (dashed or undashed) to its current username.
30///
31/// Returns `Ok(None)` when the UUID matches no account.
32pub async fn username_for_uuid(uuid: &str) -> Result<Option<String>> {
33    let uuid = uuid.replace('-', "");
34    Ok(fetch_profile(&format!("{SESSION_URL}/{uuid}"))
35        .await?
36        .map(|p| p.name))
37}
38
39async fn fetch_profile(url: &str) -> Result<Option<MojangProfile>> {
40    let response = reqwest::get(url).await?;
41    let status = response.status();
42    if status.as_u16() == 404 || status.as_u16() == 204 {
43        return Ok(None);
44    }
45    if !status.is_success() {
46        return Err(Error::Api {
47            status: status.as_u16(),
48            cause: "Mojang API error".to_string(),
49        });
50    }
51    let bytes = response.bytes().await?;
52    Ok(Some(serde_json::from_slice(&bytes)?))
53}