infinitode.py 1.1.4__tar.gz → 1.1.6__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.4 → infinitode.py-1.1.6}/PKG-INFO +1 -1
- {infinitode.py-1.1.4 → infinitode.py-1.1.6}/infinitode/__init__.py +1 -1
- {infinitode.py-1.1.4 → infinitode.py-1.1.6}/infinitode/core.py +58 -39
- {infinitode.py-1.1.4 → infinitode.py-1.1.6}/infinitode/score.py +2 -4
- {infinitode.py-1.1.4 → infinitode.py-1.1.6}/infinitode/utils.py +1 -4
- {infinitode.py-1.1.4 → infinitode.py-1.1.6}/infinitode.py.egg-info/PKG-INFO +1 -1
- {infinitode.py-1.1.4 → infinitode.py-1.1.6}/README.md +0 -0
- {infinitode.py-1.1.4 → infinitode.py-1.1.6}/infinitode/badge.py +0 -0
- {infinitode.py-1.1.4 → infinitode.py-1.1.6}/infinitode/errors.py +0 -0
- {infinitode.py-1.1.4 → infinitode.py-1.1.6}/infinitode/leaderboard.py +0 -0
- {infinitode.py-1.1.4 → infinitode.py-1.1.6}/infinitode/player.py +0 -0
- {infinitode.py-1.1.4 → infinitode.py-1.1.6}/infinitode/py.typed +0 -0
- {infinitode.py-1.1.4 → infinitode.py-1.1.6}/infinitode.py.egg-info/SOURCES.txt +0 -0
- {infinitode.py-1.1.4 → infinitode.py-1.1.6}/infinitode.py.egg-info/dependency_links.txt +0 -0
- {infinitode.py-1.1.4 → infinitode.py-1.1.6}/infinitode.py.egg-info/requires.txt +0 -0
- {infinitode.py-1.1.4 → infinitode.py-1.1.6}/infinitode.py.egg-info/top_level.txt +0 -0
- {infinitode.py-1.1.4 → infinitode.py-1.1.6}/setup.cfg +0 -0
- {infinitode.py-1.1.4 → infinitode.py-1.1.6}/setup.py +0 -0
|
@@ -51,7 +51,7 @@ def base_url(beta: bool = False) -> str:
|
|
|
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,10 @@ 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
|
-
# Rough parameter checking, "*" makes params keyword only
|
|
69
68
|
@staticmethod
|
|
70
|
-
def
|
|
69
|
+
def _kwarg_check(
|
|
71
70
|
*,
|
|
72
71
|
mapname: Optional[str] = None,
|
|
73
72
|
playerid: Optional[str] = None,
|
|
@@ -87,13 +86,13 @@ class Session:
|
|
|
87
86
|
|
|
88
87
|
# not being more specific with the payload type
|
|
89
88
|
# so the typechecker stops annoying me
|
|
90
|
-
async def
|
|
89
|
+
async def _post(
|
|
91
90
|
self, arg: str, data: Optional[Dict[str, Any]] = None, *, beta: bool = False
|
|
92
91
|
) -> Dict[str, Any]:
|
|
93
92
|
"""Internal post method to communicate with Rainy's API"""
|
|
94
93
|
url = base_url(beta) + f"?m=api&a={arg}&apiv=1&g=com.prineside.tdi2&v=282"
|
|
95
94
|
LOG.info("Sending POST request %s with data %s", arg, data)
|
|
96
|
-
async with self.
|
|
95
|
+
async with self._session.post(url, data=data) as r:
|
|
97
96
|
try:
|
|
98
97
|
r.raise_for_status()
|
|
99
98
|
except aiohttp.ClientResponseError:
|
|
@@ -121,10 +120,10 @@ class Session:
|
|
|
121
120
|
Retrieves a Score of the given player.
|
|
122
121
|
A valid playerid needs to be specified.
|
|
123
122
|
"""
|
|
124
|
-
self.
|
|
123
|
+
self._kwarg_check(
|
|
125
124
|
mapname=mapname, playerid=playerid, mode=mode, difficulty=difficulty
|
|
126
125
|
)
|
|
127
|
-
payload = await self.
|
|
126
|
+
payload = await self._post(
|
|
128
127
|
"getLeaderboardsRank",
|
|
129
128
|
data={
|
|
130
129
|
"gamemode": "BASIC_LEVELS",
|
|
@@ -153,11 +152,11 @@ class Session:
|
|
|
153
152
|
Retrieves a Leaderboard.
|
|
154
153
|
The leaderboard contains the top 200 scores of the specified map.
|
|
155
154
|
"""
|
|
156
|
-
self.
|
|
155
|
+
self._kwarg_check(
|
|
157
156
|
mapname=mapname, playerid=playerid, mode=mode, difficulty=difficulty
|
|
158
157
|
)
|
|
159
158
|
|
|
160
|
-
payload = await self.
|
|
159
|
+
payload = await self._post(
|
|
161
160
|
"getLeaderboards",
|
|
162
161
|
data={
|
|
163
162
|
"gamemode": "BASIC_LEVELS",
|
|
@@ -189,10 +188,10 @@ class Session:
|
|
|
189
188
|
A valid playerid needs to be specified.
|
|
190
189
|
The leaderboard contains the top 200 scores and one Score for each top% of the specified map.
|
|
191
190
|
"""
|
|
192
|
-
self.
|
|
191
|
+
self._kwarg_check(
|
|
193
192
|
mapname=mapname, playerid=playerid, mode=mode, difficulty=difficulty
|
|
194
193
|
)
|
|
195
|
-
payload = await self.
|
|
194
|
+
payload = await self._post(
|
|
196
195
|
"getRuntimeLeaderboards",
|
|
197
196
|
data={
|
|
198
197
|
"gamemode": "BASIC_LEVELS",
|
|
@@ -216,9 +215,9 @@ class Session:
|
|
|
216
215
|
The leaderboard contains the top 3 skill point owners (looking at you, Eupho!).
|
|
217
216
|
"""
|
|
218
217
|
if playerid is not None:
|
|
219
|
-
self.
|
|
218
|
+
self._kwarg_check(playerid=playerid)
|
|
220
219
|
|
|
221
|
-
payload = await self.
|
|
220
|
+
payload = await self._post(
|
|
222
221
|
"getSkillPointLeaderboard", data={"playerid": playerid}, beta=beta
|
|
223
222
|
)
|
|
224
223
|
lb = Leaderboard.from_payload(
|
|
@@ -248,7 +247,7 @@ class Session:
|
|
|
248
247
|
date = date.strftime("%Y-%m-%d")
|
|
249
248
|
else:
|
|
250
249
|
try:
|
|
251
|
-
datetime.datetime.strptime(date, "%Y-%m-%d")
|
|
250
|
+
date_obj = datetime.datetime.strptime(date, "%Y-%m-%d")
|
|
252
251
|
except ValueError:
|
|
253
252
|
if warning is True:
|
|
254
253
|
LOG.warning(
|
|
@@ -256,11 +255,13 @@ class Session:
|
|
|
256
255
|
date,
|
|
257
256
|
)
|
|
258
257
|
date = datetime.datetime.now(datetime.timezone.utc).strftime("%Y-%m-%d")
|
|
258
|
+
else:
|
|
259
|
+
date = date_obj.strftime("%Y-%m-%d") # allows for missing leading zeros
|
|
259
260
|
|
|
260
261
|
if playerid is not None:
|
|
261
|
-
self.
|
|
262
|
+
self._kwarg_check(playerid=playerid)
|
|
262
263
|
|
|
263
|
-
payload = await self.
|
|
264
|
+
payload = await self._post(
|
|
264
265
|
"getDailyQuestLeaderboards",
|
|
265
266
|
data={"date": date, "playerid": playerid},
|
|
266
267
|
beta=beta,
|
|
@@ -278,7 +279,7 @@ class Session:
|
|
|
278
279
|
return lb
|
|
279
280
|
|
|
280
281
|
@async_expiring_cache()
|
|
281
|
-
async def seasonal_leaderboard(self, beta: bool = False) -> Leaderboard:
|
|
282
|
+
async def seasonal_leaderboard(self, *, beta: bool = False) -> Leaderboard:
|
|
282
283
|
"""
|
|
283
284
|
Retrieves the season Leaderboard.
|
|
284
285
|
The leaderboard contains the top 100 scores in the season.
|
|
@@ -287,7 +288,7 @@ class Session:
|
|
|
287
288
|
url = base_url(beta) + "xdx/?url=seasonal_leaderboard"
|
|
288
289
|
LOG.info("Sending GET request to %s", url)
|
|
289
290
|
|
|
290
|
-
r = await self.
|
|
291
|
+
r = await self._session.get(url=url)
|
|
291
292
|
try:
|
|
292
293
|
r.raise_for_status()
|
|
293
294
|
except aiohttp.ClientResponseError:
|
|
@@ -317,16 +318,32 @@ class Session:
|
|
|
317
318
|
return lb
|
|
318
319
|
|
|
319
320
|
@async_expiring_cache()
|
|
320
|
-
async def player(
|
|
321
|
+
async def player(
|
|
322
|
+
self,
|
|
323
|
+
playerid: str | None = None,
|
|
324
|
+
nickname: str | None = None,
|
|
325
|
+
*,
|
|
326
|
+
beta: bool = False,
|
|
327
|
+
) -> Player:
|
|
321
328
|
"""
|
|
322
329
|
Retrieves the Player.
|
|
323
330
|
A valid playerid needs to be specified.
|
|
324
331
|
"""
|
|
325
|
-
|
|
326
|
-
|
|
332
|
+
if playerid and nickname:
|
|
333
|
+
raise BadArgument("You can't specify both playerid and nickname.")
|
|
334
|
+
|
|
335
|
+
url = base_url(beta)
|
|
336
|
+
if nickname:
|
|
337
|
+
url = url + "xdx/index.php?url=profile/view&nickname=" + nickname
|
|
338
|
+
elif playerid:
|
|
339
|
+
self._kwarg_check(playerid=playerid)
|
|
340
|
+
url = url + "xdx/index.php?url=profile/view&id=" + playerid
|
|
341
|
+
else:
|
|
342
|
+
raise BadArgument("You need to specify either playerid or nickname.")
|
|
343
|
+
|
|
327
344
|
LOG.info("Sending GET request to %s", url)
|
|
328
345
|
|
|
329
|
-
r = await self.
|
|
346
|
+
r = await self._session.get(url=url)
|
|
330
347
|
try:
|
|
331
348
|
r.raise_for_status()
|
|
332
349
|
except aiohttp.ClientResponseError:
|
|
@@ -335,31 +352,33 @@ class Session:
|
|
|
335
352
|
loop = asyncio.get_event_loop()
|
|
336
353
|
try:
|
|
337
354
|
return await loop.run_in_executor(
|
|
338
|
-
None, self.
|
|
355
|
+
None, self._parse_player, await r.text(), beta
|
|
339
356
|
)
|
|
340
357
|
except Exception as exc:
|
|
341
|
-
raise BadArgument(
|
|
358
|
+
raise BadArgument(
|
|
359
|
+
f"Invalid playerid/nickname: {playerid or nickname}"
|
|
360
|
+
) from exc
|
|
342
361
|
|
|
343
362
|
@classmethod
|
|
344
|
-
def
|
|
363
|
+
def _parse_player(cls, content: str, beta: bool) -> Player:
|
|
345
364
|
data = BeautifulSoup(content, features="lxml")
|
|
346
365
|
|
|
347
366
|
t: Dict[str, Any] = {}
|
|
348
|
-
t["playerid"] = playerid
|
|
349
367
|
t["beta"] = beta
|
|
368
|
+
t["playerid"] = data.select_one("label:not([i18n],[font-min-size])").text # type: ignore
|
|
350
369
|
t["nickname"] = data.select_one("label:not([i18n])").text # type: ignore
|
|
351
370
|
|
|
352
|
-
cls.
|
|
353
|
-
cls.
|
|
354
|
-
cls.
|
|
355
|
-
cls.
|
|
356
|
-
cls.
|
|
357
|
-
cls.
|
|
371
|
+
cls._parse_totals(data, t)
|
|
372
|
+
cls._parse_level_from_comments(data, t)
|
|
373
|
+
cls._parse_xp_data(data, t)
|
|
374
|
+
cls._parse_levels(data, t)
|
|
375
|
+
cls._parse_badges(data, t)
|
|
376
|
+
cls._parse_misc(data, t)
|
|
358
377
|
|
|
359
378
|
return Player(**t)
|
|
360
379
|
|
|
361
380
|
@staticmethod
|
|
362
|
-
def
|
|
381
|
+
def _parse_totals(data: BeautifulSoup, t: Dict[str, Any]) -> None:
|
|
363
382
|
totals = data.select_one('div[width="522"][height="140"][align="center"]')
|
|
364
383
|
if totals is None:
|
|
365
384
|
t.update({"total_score": 0, "total_rank": 0, "total_top": 0})
|
|
@@ -374,7 +393,7 @@ class Session:
|
|
|
374
393
|
t.update({"total_score": 0, "total_rank": 0, "total_top": "0%"})
|
|
375
394
|
|
|
376
395
|
@staticmethod
|
|
377
|
-
def
|
|
396
|
+
def _parse_level_from_comments(data: BeautifulSoup, t: Dict[str, Any]) -> None:
|
|
378
397
|
comments = data.findAll(text=lambda text: isinstance(text, Comment))
|
|
379
398
|
for x in comments:
|
|
380
399
|
if "Level:" in x:
|
|
@@ -384,7 +403,7 @@ class Session:
|
|
|
384
403
|
t["level"] = 1
|
|
385
404
|
|
|
386
405
|
@staticmethod
|
|
387
|
-
def
|
|
406
|
+
def _parse_xp_data(data: BeautifulSoup, t: Dict[str, Any]) -> None:
|
|
388
407
|
xp_data = data.select_one('div[width="330"][height="64"]')
|
|
389
408
|
xp_data = xp_data.select_one("label").text.split(" / ") # type: ignore
|
|
390
409
|
t["xp"] = int(xp_data[0])
|
|
@@ -409,7 +428,7 @@ class Session:
|
|
|
409
428
|
t["season_level"] = int(season_level_data["data"].split(":")[1]) # type: ignore
|
|
410
429
|
|
|
411
430
|
@staticmethod
|
|
412
|
-
def
|
|
431
|
+
def _parse_levels(data: BeautifulSoup, t: Dict[str, Any]) -> None:
|
|
413
432
|
t["levels"] = {}
|
|
414
433
|
|
|
415
434
|
for x in data.select('div[width="800"][height="40"]')[1:]:
|
|
@@ -437,7 +456,7 @@ class Session:
|
|
|
437
456
|
)
|
|
438
457
|
|
|
439
458
|
@staticmethod
|
|
440
|
-
def
|
|
459
|
+
def _parse_badges(data: BeautifulSoup, t: Dict[str, Any]) -> None:
|
|
441
460
|
t["badges"] = {}
|
|
442
461
|
|
|
443
462
|
icos = [
|
|
@@ -474,7 +493,7 @@ class Session:
|
|
|
474
493
|
t["badges"][ico] = (rar, col)
|
|
475
494
|
|
|
476
495
|
@staticmethod
|
|
477
|
-
def
|
|
496
|
+
def _parse_misc(data: BeautifulSoup, t: Dict[str, Any]) -> None:
|
|
478
497
|
labels = data.select('table[width="800"][align="center"]')[-1].select("label")
|
|
479
498
|
replays = labels[-3].string.split(" ") # type: ignore
|
|
480
499
|
t["replays"] = 0 if len(replays) != 4 else int(replays[3])
|
|
@@ -67,10 +67,8 @@ class Score:
|
|
|
67
67
|
self._nickname = nickname
|
|
68
68
|
self._pinned_badge: Optional[Badge] = (
|
|
69
69
|
Badge(**pinnedBadge) if pinnedBadge is not None else None
|
|
70
|
-
)
|
|
71
|
-
self._position: Optional[int] = (
|
|
72
|
-
int(position) if position is not None else None
|
|
73
|
-
) # nopep8
|
|
70
|
+
)
|
|
71
|
+
self._position: Optional[int] = int(position) if position is not None else None
|
|
74
72
|
self._top = top
|
|
75
73
|
# apparently some payloads provide total, but i think it is pointless as a property
|
|
76
74
|
# so i will keep it as a private attribute just in case it will ever be needed
|
|
@@ -20,10 +20,7 @@ def try_int(string: str, /, *, default: int = 0) -> int:
|
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
def async_expiring_cache(seconds: int = 60) -> Any:
|
|
23
|
-
"""Decorator to cache coroutines return value for the given amount of seconds.
|
|
24
|
-
|
|
25
|
-
It is not perfect because
|
|
26
|
-
"""
|
|
23
|
+
"""Decorator to cache coroutines return value for the given amount of seconds."""
|
|
27
24
|
|
|
28
25
|
def decorator(func: Callable[P, Coroutine[Any, Any, T]]) -> Callable[P, asyncio.Task[T]]:
|
|
29
26
|
cache: Dict[Tuple[Any, ...], Tuple[asyncio.Task[T], float]] = {}
|
|
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
|
|
File without changes
|
|
File without changes
|