-
Notifications
You must be signed in to change notification settings - Fork 1
/
minecraft.py
78 lines (57 loc) · 2.28 KB
/
minecraft.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
import threading
from json import JSONDecodeError
from requests.exceptions import RequestException
from prism.ratelimiting import RateLimiter
from prism.requests import make_prism_requests_session
USERPROFILES_ENDPOINT = "https://api.mojang.com/users/profiles/minecraft"
REQUEST_LIMIT, REQUEST_WINDOW = 100, 60 # Max requests per time window
# Use a connection pool for the requests
SESSION = make_prism_requests_session()
class MojangAPIError(ValueError):
"""Exception raised when we receive an error from the Mojang api"""
pass
# Be nice to the Mojang api :)
limiter = RateLimiter(limit=REQUEST_LIMIT, window=REQUEST_WINDOW)
# TODO: implement a disk cache
# TTL on this cache can be large because for a username to get a new uuid the user
# must first change their ign, then, after 37 days, someone else can get the name
LOWERCASE_UUID_CACHE: dict[str, str] = {} # Mapping username.lower() -> uuid
UUID_MUTEX = threading.Lock()
def get_uuid(username: str) -> str | None: # pragma: nocover
"""Get the uuid of all the user. None if not found."""
with UUID_MUTEX:
cache_hit = LOWERCASE_UUID_CACHE.get(username.lower(), None)
if cache_hit is not None:
return cache_hit
try:
# Uphold our prescribed rate-limits
with limiter:
response = SESSION.get(f"{USERPROFILES_ENDPOINT}/{username}")
except RequestException as e:
raise MojangAPIError(
f"Request to Mojang API failed due to a connection error {e}"
) from e
if not response:
raise MojangAPIError(
f"Request to Mojang API failed with status code {response.status_code}. "
f"Response: {response.text}"
)
if response.status_code != 200:
return None
try:
response_json = response.json()
except JSONDecodeError:
raise MojangAPIError(
"Failed parsing the response from the Mojang API. "
f"Raw content: {response.text}"
)
# reponse is {"id": "...", "name": "..."}
uuid = response_json["id"]
if not isinstance(uuid, str):
raise MojangAPIError(
f"Request to Mojang API returned wrong type for uuid {uuid=}"
)
# Set cache
with UUID_MUTEX:
LOWERCASE_UUID_CACHE[username.lower()] = uuid
return uuid