infinitode.py 1.1.3__tar.gz → 1.1.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {infinitode.py-1.1.3 → infinitode.py-1.1.4}/PKG-INFO +1 -1
- {infinitode.py-1.1.3 → infinitode.py-1.1.4}/infinitode/__init__.py +2 -2
- {infinitode.py-1.1.3 → infinitode.py-1.1.4}/infinitode/core.py +123 -96
- {infinitode.py-1.1.3 → infinitode.py-1.1.4}/infinitode/leaderboard.py +1 -1
- {infinitode.py-1.1.3 → infinitode.py-1.1.4}/infinitode/player.py +13 -11
- {infinitode.py-1.1.3 → infinitode.py-1.1.4}/infinitode/score.py +2 -2
- {infinitode.py-1.1.3 → infinitode.py-1.1.4}/infinitode/utils.py +1 -1
- {infinitode.py-1.1.3 → infinitode.py-1.1.4}/infinitode.py.egg-info/PKG-INFO +1 -1
- {infinitode.py-1.1.3 → infinitode.py-1.1.4}/README.md +0 -0
- {infinitode.py-1.1.3 → infinitode.py-1.1.4}/infinitode/badge.py +0 -0
- {infinitode.py-1.1.3 → infinitode.py-1.1.4}/infinitode/errors.py +0 -0
- {infinitode.py-1.1.3 → infinitode.py-1.1.4}/infinitode/py.typed +0 -0
- {infinitode.py-1.1.3 → infinitode.py-1.1.4}/infinitode.py.egg-info/SOURCES.txt +0 -0
- {infinitode.py-1.1.3 → infinitode.py-1.1.4}/infinitode.py.egg-info/dependency_links.txt +0 -0
- {infinitode.py-1.1.3 → infinitode.py-1.1.4}/infinitode.py.egg-info/requires.txt +0 -0
- {infinitode.py-1.1.3 → infinitode.py-1.1.4}/infinitode.py.egg-info/top_level.txt +0 -0
- {infinitode.py-1.1.3 → infinitode.py-1.1.4}/setup.cfg +0 -0
- {infinitode.py-1.1.3 → infinitode.py-1.1.4}/setup.py +0 -0
|
@@ -7,10 +7,10 @@ An asynchronous wrapper for the Infinitode API.
|
|
|
7
7
|
|
|
8
8
|
__author__ = "Sprylos"
|
|
9
9
|
__title__ = "infinitode.py"
|
|
10
|
-
__version__ = "1.1.
|
|
10
|
+
__version__ = "1.1.4"
|
|
11
11
|
__license__ = """MIT License
|
|
12
12
|
|
|
13
|
-
Copyright (c)
|
|
13
|
+
Copyright (c) 2025 Sprylos
|
|
14
14
|
|
|
15
15
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
16
16
|
of this software and associated documentation files (the "Software"), to deal
|
|
@@ -14,7 +14,7 @@ from typing import (
|
|
|
14
14
|
|
|
15
15
|
# packages
|
|
16
16
|
import aiohttp
|
|
17
|
-
import
|
|
17
|
+
from bs4 import BeautifulSoup, Comment
|
|
18
18
|
|
|
19
19
|
# local
|
|
20
20
|
from .errors import APIError, BadArgument
|
|
@@ -26,7 +26,7 @@ from .utils import async_expiring_cache, try_int
|
|
|
26
26
|
|
|
27
27
|
__all__ = ("Session",)
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
LOG = logging.getLogger(__name__)
|
|
30
30
|
|
|
31
31
|
ID_REGEX = re.compile(r"U-([A-Z0-9]{4}-){2}[A-Z0-9]{6}")
|
|
32
32
|
|
|
@@ -45,13 +45,13 @@ DIFFICULTIES = ('EASY', 'NORMAL', 'ENDLESS_I')
|
|
|
45
45
|
# fmt: on
|
|
46
46
|
|
|
47
47
|
|
|
48
|
-
def
|
|
48
|
+
def base_url(beta: bool = False) -> str:
|
|
49
49
|
return f"https://{'beta.' if beta else ''}infinitode.prineside.com/"
|
|
50
50
|
|
|
51
51
|
|
|
52
52
|
class Session:
|
|
53
53
|
def __init__(self, session: Optional[aiohttp.ClientSession] = None) -> None:
|
|
54
|
-
self.
|
|
54
|
+
self.__session = session or aiohttp.ClientSession()
|
|
55
55
|
|
|
56
56
|
# async enter and exit allow for the fancy "with" statements
|
|
57
57
|
# useful so you don't have to close the session yourself
|
|
@@ -63,11 +63,11 @@ class Session:
|
|
|
63
63
|
|
|
64
64
|
async def close(self):
|
|
65
65
|
"""Closes the internal ClientSession."""
|
|
66
|
-
await self.
|
|
66
|
+
await self.__session.close()
|
|
67
67
|
|
|
68
68
|
# Rough parameter checking, "*" makes params keyword only
|
|
69
69
|
@staticmethod
|
|
70
|
-
def
|
|
70
|
+
def __kwarg_check(
|
|
71
71
|
*,
|
|
72
72
|
mapname: Optional[str] = None,
|
|
73
73
|
playerid: Optional[str] = None,
|
|
@@ -87,20 +87,20 @@ class Session:
|
|
|
87
87
|
|
|
88
88
|
# not being more specific with the payload type
|
|
89
89
|
# so the typechecker stops annoying me
|
|
90
|
-
async def
|
|
90
|
+
async def __post(
|
|
91
91
|
self, arg: str, data: Optional[Dict[str, Any]] = None, *, beta: bool = False
|
|
92
92
|
) -> Dict[str, Any]:
|
|
93
93
|
"""Internal post method to communicate with Rainy's API"""
|
|
94
|
-
url =
|
|
95
|
-
|
|
96
|
-
async with self.
|
|
94
|
+
url = base_url(beta) + f"?m=api&a={arg}&apiv=1&g=com.prineside.tdi2&v=282"
|
|
95
|
+
LOG.info("Sending POST request %s with data %s", arg, data)
|
|
96
|
+
async with self.__session.post(url, data=data) as r:
|
|
97
97
|
try:
|
|
98
98
|
r.raise_for_status()
|
|
99
99
|
except aiohttp.ClientResponseError:
|
|
100
100
|
raise APIError("Something went wrong. Try again later")
|
|
101
101
|
|
|
102
102
|
payload: Dict[str, Any] = await r.json()
|
|
103
|
-
|
|
103
|
+
LOG.debug("Response to POST request %s: %s", arg, payload)
|
|
104
104
|
|
|
105
105
|
if payload["status"] == "success":
|
|
106
106
|
return payload
|
|
@@ -121,10 +121,10 @@ class Session:
|
|
|
121
121
|
Retrieves a Score of the given player.
|
|
122
122
|
A valid playerid needs to be specified.
|
|
123
123
|
"""
|
|
124
|
-
self.
|
|
124
|
+
self.__kwarg_check(
|
|
125
125
|
mapname=mapname, playerid=playerid, mode=mode, difficulty=difficulty
|
|
126
126
|
)
|
|
127
|
-
payload = await self.
|
|
127
|
+
payload = await self.__post(
|
|
128
128
|
"getLeaderboardsRank",
|
|
129
129
|
data={
|
|
130
130
|
"gamemode": "BASIC_LEVELS",
|
|
@@ -153,11 +153,11 @@ class Session:
|
|
|
153
153
|
Retrieves a Leaderboard.
|
|
154
154
|
The leaderboard contains the top 200 scores of the specified map.
|
|
155
155
|
"""
|
|
156
|
-
self.
|
|
156
|
+
self.__kwarg_check(
|
|
157
157
|
mapname=mapname, playerid=playerid, mode=mode, difficulty=difficulty
|
|
158
158
|
)
|
|
159
159
|
|
|
160
|
-
payload = await self.
|
|
160
|
+
payload = await self.__post(
|
|
161
161
|
"getLeaderboards",
|
|
162
162
|
data={
|
|
163
163
|
"gamemode": "BASIC_LEVELS",
|
|
@@ -189,10 +189,10 @@ class Session:
|
|
|
189
189
|
A valid playerid needs to be specified.
|
|
190
190
|
The leaderboard contains the top 200 scores and one Score for each top% of the specified map.
|
|
191
191
|
"""
|
|
192
|
-
self.
|
|
192
|
+
self.__kwarg_check(
|
|
193
193
|
mapname=mapname, playerid=playerid, mode=mode, difficulty=difficulty
|
|
194
194
|
)
|
|
195
|
-
payload = await self.
|
|
195
|
+
payload = await self.__post(
|
|
196
196
|
"getRuntimeLeaderboards",
|
|
197
197
|
data={
|
|
198
198
|
"gamemode": "BASIC_LEVELS",
|
|
@@ -216,9 +216,9 @@ class Session:
|
|
|
216
216
|
The leaderboard contains the top 3 skill point owners (looking at you, Eupho!).
|
|
217
217
|
"""
|
|
218
218
|
if playerid is not None:
|
|
219
|
-
self.
|
|
219
|
+
self.__kwarg_check(playerid=playerid)
|
|
220
220
|
|
|
221
|
-
payload = await self.
|
|
221
|
+
payload = await self.__post(
|
|
222
222
|
"getSkillPointLeaderboard", data={"playerid": playerid}, beta=beta
|
|
223
223
|
)
|
|
224
224
|
lb = Leaderboard.from_payload(
|
|
@@ -251,16 +251,16 @@ class Session:
|
|
|
251
251
|
datetime.datetime.strptime(date, "%Y-%m-%d")
|
|
252
252
|
except ValueError:
|
|
253
253
|
if warning is True:
|
|
254
|
-
|
|
254
|
+
LOG.warning(
|
|
255
255
|
"Invalid date in daily_quest_leaderboards (Use YYYY-MM-DD format): %s",
|
|
256
256
|
date,
|
|
257
257
|
)
|
|
258
258
|
date = datetime.datetime.now(datetime.timezone.utc).strftime("%Y-%m-%d")
|
|
259
259
|
|
|
260
260
|
if playerid is not None:
|
|
261
|
-
self.
|
|
261
|
+
self.__kwarg_check(playerid=playerid)
|
|
262
262
|
|
|
263
|
-
payload = await self.
|
|
263
|
+
payload = await self.__post(
|
|
264
264
|
"getDailyQuestLeaderboards",
|
|
265
265
|
data={"date": date, "playerid": playerid},
|
|
266
266
|
beta=beta,
|
|
@@ -284,16 +284,16 @@ class Session:
|
|
|
284
284
|
The leaderboard contains the top 100 scores in the season.
|
|
285
285
|
This coroutine never takes arguments.
|
|
286
286
|
"""
|
|
287
|
-
url =
|
|
288
|
-
|
|
287
|
+
url = base_url(beta) + "xdx/?url=seasonal_leaderboard"
|
|
288
|
+
LOG.info("Sending GET request to %s", url)
|
|
289
289
|
|
|
290
|
-
r = await self.
|
|
290
|
+
r = await self.__session.get(url=url)
|
|
291
291
|
try:
|
|
292
292
|
r.raise_for_status()
|
|
293
293
|
except aiohttp.ClientResponseError:
|
|
294
294
|
raise APIError("Bad Gateway.")
|
|
295
295
|
|
|
296
|
-
seasonal =
|
|
296
|
+
seasonal = BeautifulSoup(await r.text(), features="lxml")
|
|
297
297
|
|
|
298
298
|
# fmt: off
|
|
299
299
|
season = int(seasonal.select_one('label[i18n="season_formatted"]')['i18nf'].replace('["', '').replace('"]', '')) # type: ignore
|
|
@@ -316,56 +316,102 @@ class Session:
|
|
|
316
316
|
|
|
317
317
|
return lb
|
|
318
318
|
|
|
319
|
-
|
|
320
|
-
|
|
319
|
+
@async_expiring_cache()
|
|
320
|
+
async def player(self, playerid: str, beta: bool = False) -> Player:
|
|
321
|
+
"""
|
|
322
|
+
Retrieves the Player.
|
|
323
|
+
A valid playerid needs to be specified.
|
|
324
|
+
"""
|
|
325
|
+
self.__kwarg_check(playerid=playerid)
|
|
326
|
+
url = base_url(beta) + "xdx/index.php?url=profile/view&id=" + playerid
|
|
327
|
+
LOG.info("Sending GET request to %s", url)
|
|
328
|
+
|
|
329
|
+
r = await self.__session.get(url=url)
|
|
330
|
+
try:
|
|
331
|
+
r.raise_for_status()
|
|
332
|
+
except aiohttp.ClientResponseError:
|
|
333
|
+
raise APIError("Bad Gateway.")
|
|
334
|
+
|
|
335
|
+
loop = asyncio.get_event_loop()
|
|
336
|
+
try:
|
|
337
|
+
return await loop.run_in_executor(
|
|
338
|
+
None, self.__parse_player, await r.text(), playerid, beta
|
|
339
|
+
)
|
|
340
|
+
except Exception as exc:
|
|
341
|
+
raise BadArgument("Invalid playerid: " + playerid) from exc
|
|
342
|
+
|
|
343
|
+
@classmethod
|
|
344
|
+
def __parse_player(cls, content: str, playerid: str, beta: bool) -> Player:
|
|
345
|
+
data = BeautifulSoup(content, features="lxml")
|
|
346
|
+
|
|
321
347
|
t: Dict[str, Any] = {}
|
|
322
348
|
t["playerid"] = playerid
|
|
323
349
|
t["beta"] = beta
|
|
324
350
|
t["nickname"] = data.select_one("label:not([i18n])").text # type: ignore
|
|
351
|
+
|
|
352
|
+
cls.__parse_totals(data, t)
|
|
353
|
+
cls.__parse_level_from_comments(data, t)
|
|
354
|
+
cls.__parse_xp_data(data, t)
|
|
355
|
+
cls.__parse_levels(data, t)
|
|
356
|
+
cls.__parse_badges(data, t)
|
|
357
|
+
cls.__parse_misc(data, t)
|
|
358
|
+
|
|
359
|
+
return Player(**t)
|
|
360
|
+
|
|
361
|
+
@staticmethod
|
|
362
|
+
def __parse_totals(data: BeautifulSoup, t: Dict[str, Any]) -> None:
|
|
325
363
|
totals = data.select_one('div[width="522"][height="140"][align="center"]')
|
|
326
364
|
if totals is None:
|
|
327
365
|
t.update({"total_score": 0, "total_rank": 0, "total_top": 0})
|
|
366
|
+
return
|
|
367
|
+
|
|
368
|
+
totals = totals.select("label")
|
|
369
|
+
if len(totals) >= 4:
|
|
370
|
+
t["total_score"] = try_int(totals[1].text.replace(",", ""))
|
|
371
|
+
t["total_rank"] = try_int(totals[2].text.replace(",", ""))
|
|
372
|
+
t["total_top"] = totals[3].text.replace("- Top ", "")
|
|
328
373
|
else:
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
else:
|
|
335
|
-
t.update({"total_score": 0, "total_rank": 0, "total_top": "0%"})
|
|
336
|
-
comments = data.findAll(text=lambda text: isinstance(text, bs4.Comment))
|
|
374
|
+
t.update({"total_score": 0, "total_rank": 0, "total_top": "0%"})
|
|
375
|
+
|
|
376
|
+
@staticmethod
|
|
377
|
+
def __parse_level_from_comments(data: BeautifulSoup, t: Dict[str, Any]) -> None:
|
|
378
|
+
comments = data.findAll(text=lambda text: isinstance(text, Comment))
|
|
337
379
|
for x in comments:
|
|
338
380
|
if "Level:" in x:
|
|
339
381
|
t["level"] = int(x.split(">")[3].split("<")[0])
|
|
340
382
|
break
|
|
341
383
|
else:
|
|
342
384
|
t["level"] = 1
|
|
385
|
+
|
|
386
|
+
@staticmethod
|
|
387
|
+
def __parse_xp_data(data: BeautifulSoup, t: Dict[str, Any]) -> None:
|
|
343
388
|
xp_data = data.select_one('div[width="330"][height="64"]')
|
|
344
|
-
if xp_data is None:
|
|
345
|
-
raise BadArgument("Invalid playerid: " + playerid)
|
|
346
389
|
xp_data = xp_data.select_one("label").text.split(" / ") # type: ignore
|
|
347
390
|
t["xp"] = int(xp_data[0])
|
|
348
391
|
t["xp_max"] = int(xp_data[1])
|
|
349
392
|
season_xp_data = data.select_one(
|
|
350
393
|
'div[width="530"][align="center"][height="64"][pad-bottom="10"]'
|
|
351
394
|
)
|
|
395
|
+
|
|
352
396
|
if season_xp_data is None:
|
|
353
|
-
t
|
|
354
|
-
|
|
397
|
+
t.update({"season_xp": 0, "season_xp_max": 500, "season_level": 1})
|
|
398
|
+
return
|
|
399
|
+
|
|
400
|
+
season_xp_borders = season_xp_data.select_one("label").text.split(" / ") # type: ignore
|
|
401
|
+
t["season_xp"] = int(season_xp_borders[0])
|
|
402
|
+
t["season_xp_max"] = int(season_xp_borders[1])
|
|
403
|
+
season_level_data = season_xp_data.select_one(
|
|
404
|
+
'div[x="466"][width="64"][height="64"]'
|
|
405
|
+
)
|
|
406
|
+
if season_level_data is None:
|
|
355
407
|
t["season_level"] = 1
|
|
356
408
|
else:
|
|
357
|
-
|
|
358
|
-
t["season_xp"] = int(season_xp_borders[0])
|
|
359
|
-
t["season_xp_max"] = int(season_xp_borders[1])
|
|
360
|
-
season_level_data = season_xp_data.select_one(
|
|
361
|
-
'div[x="466"][width="64"][height="64"]'
|
|
362
|
-
)
|
|
363
|
-
if season_level_data is None:
|
|
364
|
-
t["season_level"] = 1
|
|
365
|
-
else:
|
|
366
|
-
t["season_level"] = int(season_level_data["data"].split(":")[1]) # type: ignore
|
|
409
|
+
t["season_level"] = int(season_level_data["data"].split(":")[1]) # type: ignore
|
|
367
410
|
|
|
411
|
+
@staticmethod
|
|
412
|
+
def __parse_levels(data: BeautifulSoup, t: Dict[str, Any]) -> None:
|
|
368
413
|
t["levels"] = {}
|
|
414
|
+
|
|
369
415
|
for x in data.select('div[width="800"][height="40"]')[1:]:
|
|
370
416
|
level_data = x.select("label")
|
|
371
417
|
level = level_data[0].text
|
|
@@ -381,7 +427,7 @@ class Session:
|
|
|
381
427
|
level,
|
|
382
428
|
"score",
|
|
383
429
|
"NORMAL",
|
|
384
|
-
playerid,
|
|
430
|
+
t["playerid"],
|
|
385
431
|
rank=rank,
|
|
386
432
|
score=score,
|
|
387
433
|
total=total,
|
|
@@ -389,7 +435,11 @@ class Session:
|
|
|
389
435
|
level=t["level"],
|
|
390
436
|
nickname=t["nickname"],
|
|
391
437
|
)
|
|
438
|
+
|
|
439
|
+
@staticmethod
|
|
440
|
+
def __parse_badges(data: BeautifulSoup, t: Dict[str, Any]) -> None:
|
|
392
441
|
t["badges"] = {}
|
|
442
|
+
|
|
393
443
|
icos = [
|
|
394
444
|
"daily-game",
|
|
395
445
|
"invited-players",
|
|
@@ -400,63 +450,40 @@ class Session:
|
|
|
400
450
|
"beta-tester-season-2",
|
|
401
451
|
f"high-leveled-{t['level'] // 10 if t['level'] < 100 else 10}",
|
|
402
452
|
]
|
|
453
|
+
rars = (
|
|
454
|
+
"not-received",
|
|
455
|
+
"common",
|
|
456
|
+
"rare",
|
|
457
|
+
"very-rare",
|
|
458
|
+
"epic",
|
|
459
|
+
"legendary",
|
|
460
|
+
"supreme",
|
|
461
|
+
"artifact",
|
|
462
|
+
)
|
|
463
|
+
|
|
403
464
|
for x in data.select('div[width="80"][height="80"]'):
|
|
404
465
|
rar: str = x.select("img")[0]["src"].split("bg-")[1] # type: ignore
|
|
405
|
-
if rar in
|
|
406
|
-
"not-received",
|
|
407
|
-
"common",
|
|
408
|
-
"rare",
|
|
409
|
-
"very-rare",
|
|
410
|
-
"epic",
|
|
411
|
-
"legendary",
|
|
412
|
-
"supreme",
|
|
413
|
-
"artifact",
|
|
414
|
-
]:
|
|
466
|
+
if rar in rars:
|
|
415
467
|
ico: str = x.select("img")[1]["src"].split("icon-")[1] # type: ignore
|
|
416
|
-
if
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
f"season-level-{rar}-2",
|
|
422
|
-
]
|
|
423
|
-
or ico[:8] == "season-1"
|
|
424
|
-
):
|
|
468
|
+
if ico in icos + [
|
|
469
|
+
"youtube-author-" + rar,
|
|
470
|
+
f"season-level-{rar}-2",
|
|
471
|
+
f"season-level-{rar}-3",
|
|
472
|
+
] or ico[:8] in ("season-1", "season-2"):
|
|
425
473
|
col: str = x.select("img")[-1]["color"] # type: ignore
|
|
426
474
|
t["badges"][ico] = (rar, col)
|
|
475
|
+
|
|
476
|
+
@staticmethod
|
|
477
|
+
def __parse_misc(data: BeautifulSoup, t: Dict[str, Any]) -> None:
|
|
427
478
|
labels = data.select('table[width="800"][align="center"]')[-1].select("label")
|
|
428
479
|
replays = labels[-3].string.split(" ") # type: ignore
|
|
429
480
|
t["replays"] = 0 if len(replays) != 4 else int(replays[3])
|
|
481
|
+
|
|
430
482
|
issues = labels[-2].string.split(" ") # type: ignore
|
|
431
483
|
t["issues"] = 0 if len(issues) != 6 else int(issues[0][3:])
|
|
484
|
+
|
|
432
485
|
sp = list(labels[-1].string.split("ned ")[1].split(" ")) # type: ignore
|
|
433
486
|
day = sp[0][:-2] if len(sp[0][:2]) == 2 else "0" + sp[0][:2]
|
|
434
487
|
t["created_at"] = datetime.datetime.strptime(
|
|
435
488
|
day + " " + sp[-2] + " " + sp[-1], "%d %B %Y"
|
|
436
489
|
).strftime("%Y-%m-%d")
|
|
437
|
-
|
|
438
|
-
return Player(**t)
|
|
439
|
-
|
|
440
|
-
@async_expiring_cache()
|
|
441
|
-
async def player(self, playerid: str, beta: bool = False) -> Player:
|
|
442
|
-
"""
|
|
443
|
-
Retrieves the Player.
|
|
444
|
-
A valid playerid needs to be specified.
|
|
445
|
-
"""
|
|
446
|
-
self._kwarg_check(playerid=playerid)
|
|
447
|
-
url = _base_url(beta) + "xdx/index.php?url=profile/view&id=" + playerid
|
|
448
|
-
_log.info("Sending GET request to %s", url)
|
|
449
|
-
|
|
450
|
-
r = await self._session.get(url=url)
|
|
451
|
-
try:
|
|
452
|
-
r.raise_for_status()
|
|
453
|
-
except aiohttp.ClientResponseError:
|
|
454
|
-
raise APIError("Bad Gateway.")
|
|
455
|
-
|
|
456
|
-
loop = asyncio.get_event_loop()
|
|
457
|
-
try:
|
|
458
|
-
return await loop.run_in_executor(
|
|
459
|
-
None, self._parse_player, await r.text(), playerid, beta
|
|
460
|
-
)
|
|
461
|
-
except Exception as exc:
|
|
462
|
-
raise BadArgument("Invalid playerid: " + playerid) from exc
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
# std
|
|
4
|
-
from typing import Dict, Optional, Union, Tuple, TYPE_CHECKING
|
|
4
|
+
from typing import Any, Dict, Optional, Union, Tuple, TYPE_CHECKING
|
|
5
5
|
|
|
6
6
|
# local
|
|
7
7
|
from .score import Score
|
|
@@ -166,7 +166,7 @@ class Player:
|
|
|
166
166
|
@property
|
|
167
167
|
def avatar_link(self):
|
|
168
168
|
"""The link to the player's avatar. Invalid URL if the user doesn't have a pfp."""
|
|
169
|
-
return AVATAR_URL.format(
|
|
169
|
+
return AVATAR_URL.format("beta" if self._beta else "", self._playerid)
|
|
170
170
|
|
|
171
171
|
@property
|
|
172
172
|
def daily_quest(self):
|
|
@@ -219,18 +219,19 @@ class Player:
|
|
|
219
219
|
if session is None:
|
|
220
220
|
if self._daily_quest is None:
|
|
221
221
|
return
|
|
222
|
-
|
|
222
|
+
|
|
223
223
|
# daily_quest is MISSING
|
|
224
224
|
raise InfinitodeError(
|
|
225
225
|
"You need to provide a Session to fetch the daily quest score."
|
|
226
226
|
)
|
|
227
|
-
|
|
228
|
-
lb = await session.daily_quest_leaderboards(
|
|
227
|
+
|
|
228
|
+
lb = await session.daily_quest_leaderboards(
|
|
229
|
+
playerid=self._playerid, beta=self._beta
|
|
230
|
+
)
|
|
229
231
|
self._daily_quest = lb.player
|
|
230
232
|
|
|
231
233
|
return self._daily_quest
|
|
232
234
|
|
|
233
|
-
|
|
234
235
|
async def fetch_skill_point(
|
|
235
236
|
self, session: Optional[Session] = None
|
|
236
237
|
) -> Optional[Score]:
|
|
@@ -240,26 +241,27 @@ class Player:
|
|
|
240
241
|
"""
|
|
241
242
|
if self._skill_point:
|
|
242
243
|
return self._skill_point
|
|
243
|
-
|
|
244
|
+
|
|
244
245
|
if session is None:
|
|
245
246
|
if self._skill_point is None:
|
|
246
247
|
return
|
|
247
|
-
|
|
248
|
+
|
|
248
249
|
# skill_point is MISSING
|
|
249
250
|
raise InfinitodeError(
|
|
250
251
|
"You need to provide a Session to fetch the skill point score."
|
|
251
252
|
)
|
|
252
253
|
|
|
253
|
-
lb = await session.skill_point_leaderboard(
|
|
254
|
+
lb = await session.skill_point_leaderboard(
|
|
255
|
+
playerid=self._playerid, beta=self._beta
|
|
256
|
+
)
|
|
254
257
|
self._skill_point = lb.player
|
|
255
258
|
|
|
256
259
|
return self._skill_point
|
|
257
260
|
|
|
258
|
-
|
|
259
261
|
# magic methods
|
|
260
262
|
|
|
261
263
|
def __repr__(self) -> str:
|
|
262
|
-
attrs = {
|
|
264
|
+
attrs: dict[str, Any] = {
|
|
263
265
|
"playerid": self._playerid,
|
|
264
266
|
"nickname": self._nickname,
|
|
265
267
|
"total_rank": self._total_rank,
|
|
@@ -174,7 +174,7 @@ class Score:
|
|
|
174
174
|
raise InfinitodeError(
|
|
175
175
|
"The score is not valid for formatting (There is no nickname attached to this score)."
|
|
176
176
|
)
|
|
177
|
-
|
|
177
|
+
|
|
178
178
|
return "#{:<5} {:<22} {:>0,}".format(
|
|
179
179
|
self._rank,
|
|
180
180
|
self._nickname if len(self._nickname) < 21 else f"{self._nickname[:19]}...",
|
|
@@ -188,7 +188,7 @@ class Score:
|
|
|
188
188
|
# magic methods
|
|
189
189
|
|
|
190
190
|
def __repr__(self) -> str:
|
|
191
|
-
attrs = {
|
|
191
|
+
attrs: dict[str, Any] = {
|
|
192
192
|
"method": self._method,
|
|
193
193
|
"mapname": self._mapname,
|
|
194
194
|
"mode": self._mode,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|