|
1 | 1 | import asyncio |
2 | 2 | import dataclasses |
3 | 3 | import hashlib |
| 4 | +import re |
4 | 5 | from functools import reduce |
5 | 6 | from json import JSONDecodeError |
6 | 7 | from operator import concat |
|
17 | 18 | if TYPE_CHECKING: |
18 | 19 | from maimai_py.maimai import MaimaiClient, MaimaiSongs |
19 | 20 |
|
| 21 | +is_jwt = re.compile(r"^[a-zA-Z0-9-_]+\.[a-zA-Z0-9-_]+\.[a-zA-Z0-9-_]+$") |
| 22 | + |
20 | 23 |
|
21 | 24 | class LXNSProvider(ISongProvider, IPlayerProvider, IScoreProvider, IScoreUpdateProvider, IAliasProvider, IItemListProvider): |
22 | 25 | """The provider that fetches data from the LXNS. |
@@ -61,7 +64,10 @@ async def _build_player_request(self, path: str, identifier: PlayerIdentifier, c |
61 | 64 | # user-level API takes the precedence: If personal token provided, use it first |
62 | 65 | assert isinstance(identifier.credentials, str) |
63 | 66 | entrypoint = f"api/v0/user/maimai/player/{path}" |
64 | | - headers = {"X-User-Token": identifier.credentials} |
| 67 | + if is_jwt.match(identifier.credentials) is not None: |
| 68 | + headers = {"Authorization": f"Bearer {identifier.credentials}"} |
| 69 | + else: |
| 70 | + headers = {"X-User-Token": identifier.credentials} |
65 | 71 | else: |
66 | 72 | await self._ensure_friend_code(client, identifier) |
67 | 73 | entrypoint = f"api/v0/maimai/player/{identifier.friend_code}/{path}" |
@@ -235,44 +241,36 @@ async def get_scores_all(self, identifier: PlayerIdentifier, client: "MaimaiClie |
235 | 241 |
|
236 | 242 | @retry(stop=stop_after_attempt(3), retry=retry_if_exception_type(RequestError), reraise=True) |
237 | 243 | async def get_scores_best(self, identifier: PlayerIdentifier, client: "MaimaiClient") -> list[Score]: |
238 | | - if identifier.friend_code is None: |
239 | | - return await self.get_scores_all(identifier, client) |
240 | | - await self._ensure_friend_code(client, identifier) |
241 | | - entrypoint = f"api/v0/maimai/player/{identifier.friend_code}/bests" |
242 | | - resp = await client._client.get(self.base_url + entrypoint, headers=self.headers) |
| 244 | + url, headers, _ = await self._build_player_request("bests", identifier, client) |
| 245 | + resp = await client._client.get(url, headers=headers) |
243 | 246 | resp_data = self._check_response_player(resp)["data"] |
244 | 247 | return [s for score in resp_data["standard"] + resp_data["dx"] if (s := LXNSProvider._deser_score(score))] |
245 | 248 |
|
246 | 249 | @retry(stop=stop_after_attempt(3), retry=retry_if_exception_type(RequestError), reraise=True) |
247 | 250 | async def get_scores_one(self, identifier: PlayerIdentifier, song: Song, client: "MaimaiClient") -> list[Score]: |
248 | | - await self._ensure_friend_code(client, identifier) |
249 | | - request_tasks, create_task = [], lambda type: asyncio.create_task( |
250 | | - client._client.get( |
251 | | - self.base_url + f"api/v0/maimai/player/{identifier.friend_code}/bests", |
252 | | - params={"song_id": song.id if type != SongType.UTAGE else song.id + 100000, "song_type": type.value}, |
253 | | - headers=self.headers, |
254 | | - ) |
255 | | - ) |
| 251 | + async def get_scores_for_type(type: SongType) -> list[Score]: |
| 252 | + url, headers, _ = await self._build_player_request("bests", identifier, client) |
| 253 | + params = {"song_id": song.id if type != SongType.UTAGE else song.id + 100000, "song_type": type.value} |
| 254 | + resp = await client._client.get(url, params=params, headers=headers) |
| 255 | + resp_data = self._check_response_player(resp)["data"] |
| 256 | + return [s for score in resp_data if (s := LXNSProvider._deser_score(score))] |
| 257 | + |
| 258 | + results, _ = [], await self._ensure_friend_code(client, identifier) |
256 | 259 | if len(song.difficulties.standard) > 0: |
257 | | - request_tasks.append(create_task(SongType.STANDARD)) |
| 260 | + results.append(asyncio.create_task(get_scores_for_type(SongType.STANDARD))) |
258 | 261 | if len(song.difficulties.dx) > 0: |
259 | | - request_tasks.append(create_task(SongType.DX)) |
| 262 | + results.append(asyncio.create_task(get_scores_for_type(SongType.DX))) |
260 | 263 | if len(song.difficulties.utage) > 0: |
261 | | - request_tasks.append(create_task(SongType.UTAGE)) |
262 | | - resps = await asyncio.gather(*request_tasks) |
263 | | - resp_data = [self._check_response_player(resp)["data"] for resp in resps] |
264 | | - return [s for score in reduce(concat, resp_data) if (s := LXNSProvider._deser_score(score))] |
| 264 | + results.append(asyncio.create_task(get_scores_for_type(SongType.UTAGE))) |
| 265 | + return [score for score in reduce(concat, await asyncio.gather(*results))] |
265 | 266 |
|
266 | 267 | @retry(stop=stop_after_attempt(3), retry=retry_if_exception_type(RequestError), reraise=True) |
267 | 268 | async def update_scores(self, identifier: PlayerIdentifier, scores: Iterable[Score], client: "MaimaiClient") -> None: |
268 | 269 | maimai_songs = await client.songs() |
269 | 270 | url, headers, _ = await self._build_player_request("scores", identifier, client) |
270 | 271 | scores_dict = {"scores": [json for score in scores if (json := await LXNSProvider._ser_score(score, maimai_songs))]} |
271 | 272 | resp = await client._client.post(url, headers=headers, json=scores_dict) |
272 | | - resp.raise_for_status() |
273 | | - resp_json = resp.json() |
274 | | - if not resp_json["success"] and resp_json["code"] == 400: |
275 | | - raise ValueError(resp_json["message"]) |
| 273 | + self._check_response_player(resp) |
276 | 274 |
|
277 | 275 | @retry(stop=stop_after_attempt(3), retry=retry_if_exception_type(RequestError), reraise=True) |
278 | 276 | async def get_aliases(self, client: "MaimaiClient") -> dict[int, list[str]]: |
|
0 commit comments