maimai_py.models
1from dataclasses import dataclass 2from datetime import datetime 3from typing import Any, MutableMapping, Optional, Sequence, Union 4 5from maimai_py.enums import * 6from maimai_py.exceptions import * 7from maimai_py.utils import UNSET, _UnsetSentinel 8 9 10@dataclass 11class Song: 12 __slots__ = ("id", "title", "artist", "genre", "bpm", "map", "version", "rights", "aliases", "disabled", "difficulties") 13 14 id: int 15 title: str 16 artist: str 17 genre: Genre 18 bpm: int 19 map: Optional[str] 20 version: int 21 rights: Optional[str] 22 aliases: Optional[list[str]] 23 disabled: bool 24 difficulties: "SongDifficulties" 25 26 def get_difficulty(self, type: SongType, level_index: LevelIndex) -> Optional["SongDifficulty"]: 27 """Get the exact difficulty of this song by type and level index. 28 29 Args: 30 type: The type of the song (DX, STANDARD, UTAGE). 31 level_index: The level index of the difficulty. 32 Returns: 33 The difficulty object if found, otherwise None. 34 """ 35 if type == SongType.DX: 36 return next((diff for diff in self.difficulties.dx if diff.level_index == level_index), None) 37 if type == SongType.STANDARD: 38 return next((diff for diff in self.difficulties.standard if diff.level_index == level_index), None) 39 if type == SongType.UTAGE: 40 return next(iter(self.difficulties.utage), None) 41 42 def get_difficulties(self, song_type: Union[SongType, _UnsetSentinel] = UNSET) -> Sequence["SongDifficulty"]: 43 """Get all difficulties of the song, optionally filtered by type. 44 45 Args: 46 song_type: The type of the song (DX, STANDARD, UTAGE). If UNSET, returns all types. 47 Returns: 48 A sequence of difficulties matching the specified type. 49 """ 50 if isinstance(song_type, _UnsetSentinel): 51 return self.difficulties.standard + self.difficulties.dx + self.difficulties.utage 52 if song_type == SongType.DX: 53 return self.difficulties.dx 54 if song_type == SongType.STANDARD: 55 return self.difficulties.standard 56 if song_type == SongType.UTAGE: 57 return self.difficulties.utage 58 59 def get_divingfish_id(self, type: SongType, level_index: LevelIndex) -> int: 60 """Get the Diving Fish ID for a specific difficulty of this song. 61 62 Args: 63 type: The type of the song (DX, STANDARD, UTAGE). 64 level_index: The level index of the difficulty. 65 Returns: 66 The Diving Fish ID for the specified difficulty. 67 """ 68 difficulty = self.get_difficulty(type, level_index) 69 if difficulty is None: 70 raise ValueError(f"No difficulty found for type {type} and level index {level_index}") 71 if difficulty.type == SongType.DX: 72 return self.id + 10000 73 if difficulty.type == SongType.UTAGE: 74 return self.id + 100000 75 return self.id 76 77 def get_divingfish_ids(self, song_type: Union[SongType, _UnsetSentinel] = UNSET) -> set[int]: 78 """Get a set of Diving Fish IDs for all difficulties of this song. 79 80 Args: 81 song_type: The type of the song (DX, STANDARD, UTAGE). If UNSET, returns IDs for all types. 82 Returns: 83 A set of Diving Fish IDs for the specified type or all types if UNSET. 84 """ 85 ids = set() 86 for difficulty in self.get_difficulties(song_type): 87 ids.add(self.get_divingfish_id(difficulty.type, difficulty.level_index)) 88 return ids 89 90 91@dataclass 92class SongDifficulties: 93 __slots__ = ("standard", "dx", "utage") 94 95 standard: list["SongDifficulty"] 96 dx: list["SongDifficulty"] 97 utage: list["SongDifficultyUtage"] 98 99 100@dataclass 101class CurveObject: 102 __slots__ = ("sample_size", "fit_level_value", "avg_achievements", "stdev_achievements", "avg_dx_score", "rate_sample_size", "fc_sample_size") 103 104 sample_size: int 105 fit_level_value: float 106 avg_achievements: float 107 stdev_achievements: float 108 avg_dx_score: float 109 rate_sample_size: dict[RateType, int] 110 fc_sample_size: dict[FCType, int] 111 112 113@dataclass 114class SongDifficulty: 115 __slots__ = ( 116 "id", 117 "type", 118 "level", 119 "level_value", 120 "level_index", 121 "note_designer", 122 "version", 123 "tap_num", 124 "hold_num", 125 "slide_num", 126 "touch_num", 127 "break_num", 128 "curve", 129 ) 130 131 type: SongType 132 level: str 133 level_value: float 134 level_index: LevelIndex 135 note_designer: str 136 version: int 137 tap_num: int 138 hold_num: int 139 slide_num: int 140 touch_num: int 141 break_num: int 142 curve: Optional[CurveObject] 143 144 145@dataclass 146class SongDifficultyUtage(SongDifficulty): 147 __slots__ = ("kanji", "description", "is_buddy") 148 149 kanji: str 150 description: str 151 is_buddy: bool 152 153 154@dataclass 155class SongAlias: 156 __slots__ = ("song_id", "aliases") 157 158 song_id: int 159 aliases: list[str] 160 161 162@dataclass 163class PlayerIdentifier: 164 qq: Optional[int] = None 165 username: Optional[str] = None 166 friend_code: Optional[int] = None 167 credentials: Union[str, MutableMapping[str, Any], None] = None 168 169 def __post_init__(self): 170 if self.qq is None and self.username is None and self.friend_code is None and self.credentials is None: 171 raise InvalidPlayerIdentifierError("At least one of the following must be provided: qq, username, friend_code, credentials") 172 173 def _as_diving_fish(self) -> dict[str, Any]: 174 if self.qq: 175 return {"qq": str(self.qq)} 176 elif self.username: 177 return {"username": self.username} 178 elif self.friend_code: 179 raise InvalidPlayerIdentifierError("Friend code is not applicable for Diving Fish") 180 else: 181 raise InvalidPlayerIdentifierError("No valid identifier provided") 182 183 def _as_lxns(self) -> str: 184 if self.friend_code: 185 return str(self.friend_code) 186 elif self.qq: 187 return f"qq/{str(self.qq)}" 188 elif self.username: 189 raise InvalidPlayerIdentifierError("Username is not applicable for LXNS") 190 else: 191 raise InvalidPlayerIdentifierError("No valid identifier provided") 192 193 194@dataclass 195class PlayerItem: 196 @staticmethod 197 def _namespace() -> str: 198 raise NotImplementedError 199 200 201@dataclass 202class PlayerTrophy(PlayerItem): 203 __slots__ = ("id", "name", "color") 204 205 id: int 206 name: str 207 color: str 208 209 @staticmethod 210 def _namespace(): 211 return "trophies" 212 213 214@dataclass 215class PlayerIcon(PlayerItem): 216 __slots__ = ("id", "name", "description", "genre") 217 218 id: int 219 name: str 220 description: Optional[str] 221 genre: Optional[str] 222 223 @staticmethod 224 def _namespace(): 225 return "icons" 226 227 228@dataclass 229class PlayerNamePlate(PlayerItem): 230 __slots__ = ("id", "name", "description", "genre") 231 232 id: int 233 name: str 234 description: Optional[str] 235 genre: Optional[str] 236 237 @staticmethod 238 def _namespace(): 239 return "nameplates" 240 241 242@dataclass 243class PlayerFrame(PlayerItem): 244 __slots__ = ("id", "name", "description", "genre") 245 246 id: int 247 name: str 248 description: Optional[str] 249 genre: Optional[str] 250 251 @staticmethod 252 def _namespace(): 253 return "frames" 254 255 256@dataclass 257class PlayerPartner(PlayerItem): 258 __slots__ = ("id", "name") 259 260 id: int 261 name: str 262 263 @staticmethod 264 def _namespace(): 265 return "partners" 266 267 268@dataclass 269class PlayerChara(PlayerItem): 270 __slots__ = ("id", "name") 271 272 id: int 273 name: str 274 275 @staticmethod 276 def _namespace(): 277 return "charas" 278 279 280@dataclass 281class PlayerRegion: 282 __slots__ = ("region_id", "region_name", "play_count", "created_at") 283 284 region_id: int 285 region_name: str 286 play_count: int 287 created_at: datetime 288 289 290@dataclass 291class Player: 292 __slots__ = ("identifier", "name", "rating") 293 294 name: str 295 rating: int 296 297 298@dataclass 299class DivingFishPlayer(Player): 300 __slots__ = ("nickname", "plate", "additional_rating") 301 302 nickname: str 303 plate: str 304 additional_rating: int 305 306 307@dataclass 308class LXNSPlayer(Player): 309 __slots__ = ("friend_code", "course_rank", "class_rank", "star", "frame", "icon", "trophy", "name_plate", "upload_time") 310 311 friend_code: int 312 course_rank: int 313 class_rank: int 314 star: int 315 frame: Optional[PlayerFrame] 316 icon: Optional[PlayerIcon] 317 trophy: Optional[PlayerTrophy] 318 name_plate: Optional[PlayerNamePlate] 319 upload_time: str 320 321 322@dataclass 323class ArcadePlayer(Player): 324 __slots__ = ("is_login", "icon", "trophy", "name_plate") 325 326 is_login: bool 327 icon: Optional[PlayerIcon] 328 trophy: Optional[PlayerTrophy] 329 name_plate: Optional[PlayerNamePlate] 330 331 332@dataclass 333class AreaCharacter: 334 __slots__ = ("id", "name", "illustrator", "description1", "description2", "team", "props") 335 336 name: str 337 illustrator: str 338 description1: str 339 description2: str 340 team: str 341 props: dict[str, str] 342 343 344@dataclass 345class AreaSong: 346 __slots__ = ("id", "title", "artist", "description", "illustrator", "movie") 347 348 id: Optional[int] 349 title: str 350 artist: str 351 description: str 352 illustrator: Optional[str] 353 movie: Optional[str] 354 355 356@dataclass 357class Area: 358 __slots__ = ("id", "name", "comment", "description", "video_id", "characters", "songs") 359 360 id: str 361 name: str 362 comment: str 363 description: str 364 video_id: str 365 characters: list[AreaCharacter] 366 songs: list[AreaSong] 367 368 369@dataclass 370class Score: 371 __slots__ = ("id", "level", "level_index", "achievements", "fc", "fs", "dx_score", "dx_rating", "play_count", "rate", "type", "song") 372 373 id: int 374 level: str 375 level_index: LevelIndex 376 achievements: Optional[float] 377 fc: Optional[FCType] 378 fs: Optional[FSType] 379 dx_score: Optional[int] 380 dx_rating: Optional[float] 381 play_count: Optional[int] 382 rate: RateType 383 type: SongType 384 385 def _compare(self, other: Optional["Score"]) -> "Score": 386 if other is None: 387 return self 388 if self.dx_score != other.dx_score: # larger value is better 389 return self if (self.dx_score or 0) > (other.dx_score or 0) else other 390 if self.achievements != other.achievements: # larger value is better 391 return self if (self.achievements or 0) > (other.achievements or 0) else other 392 if self.rate != other.rate: # smaller value is better 393 self_rate = self.rate.value if self.rate is not None else 100 394 other_rate = other.rate.value if other.rate is not None else 100 395 return self if self_rate < other_rate else other 396 if self.fc != other.fc: # smaller value is better 397 self_fc = self.fc.value if self.fc is not None else 100 398 other_fc = other.fc.value if other.fc is not None else 100 399 return self if self_fc < other_fc else other 400 if self.fs != other.fs: # bigger value is better 401 self_fs = self.fs.value if self.fs is not None else -1 402 other_fs = other.fs.value if other.fs is not None else -1 403 return self if self_fs > other_fs else other 404 return self # we consider they are equal 405 406 def _join(self, other: Optional["Score"]) -> "Score": 407 if other is not None: 408 if self.level_index != other.level_index or self.type != other.type: 409 raise ValueError("Cannot join scores with different level indexes or types") 410 self.achievements = max(self.achievements or 0, other.achievements or 0) 411 if self.fc != other.fc: 412 self_fc = self.fc.value if self.fc is not None else 100 413 other_fc = other.fc.value if other.fc is not None else 100 414 selected_value = min(self_fc, other_fc) 415 self.fc = FCType(selected_value) if selected_value != 100 else None 416 if self.fs != other.fs: 417 self_fs = self.fs.value if self.fs is not None else -1 418 other_fs = other.fs.value if other.fs is not None else -1 419 selected_value = max(self_fs, other_fs) 420 self.fs = FSType(selected_value) if selected_value != -1 else None 421 if self.rate != other.rate: 422 selected_value = min(self.rate.value, other.rate.value) 423 self.rate = RateType(selected_value) 424 return self 425 426 427@dataclass 428class ScoreExtend(Score): 429 __slots__ = ["title", "level_value", "level_dx_score"] 430 431 title: str 432 level_value: float 433 level_dx_score: int 434 435 436@dataclass 437class PlateObject: 438 __slots__ = ("song", "levels", "scores") 439 440 song: Song 441 levels: set[LevelIndex] 442 scores: list[ScoreExtend] 443 444 445@dataclass 446class PlayerSong: 447 __slots__ = ["song", "scores"] 448 449 song: Song 450 scores: list[ScoreExtend] 451 452 453@dataclass 454class PlayerBests: 455 __slots__ = ["rating", "rating_b35", "rating_b15", "scores_b35", "scores_b15"] 456 457 rating: int 458 rating_b35: int 459 rating_b15: int 460 scores_b35: list[ScoreExtend] 461 scores_b15: list[ScoreExtend] 462 463 @property 464 def scores(self) -> list[ScoreExtend]: 465 """Get all scores, including both B35 and B15.""" 466 return self.scores_b35 + self.scores_b15
@dataclass
class
Song:
11@dataclass 12class Song: 13 __slots__ = ("id", "title", "artist", "genre", "bpm", "map", "version", "rights", "aliases", "disabled", "difficulties") 14 15 id: int 16 title: str 17 artist: str 18 genre: Genre 19 bpm: int 20 map: Optional[str] 21 version: int 22 rights: Optional[str] 23 aliases: Optional[list[str]] 24 disabled: bool 25 difficulties: "SongDifficulties" 26 27 def get_difficulty(self, type: SongType, level_index: LevelIndex) -> Optional["SongDifficulty"]: 28 """Get the exact difficulty of this song by type and level index. 29 30 Args: 31 type: The type of the song (DX, STANDARD, UTAGE). 32 level_index: The level index of the difficulty. 33 Returns: 34 The difficulty object if found, otherwise None. 35 """ 36 if type == SongType.DX: 37 return next((diff for diff in self.difficulties.dx if diff.level_index == level_index), None) 38 if type == SongType.STANDARD: 39 return next((diff for diff in self.difficulties.standard if diff.level_index == level_index), None) 40 if type == SongType.UTAGE: 41 return next(iter(self.difficulties.utage), None) 42 43 def get_difficulties(self, song_type: Union[SongType, _UnsetSentinel] = UNSET) -> Sequence["SongDifficulty"]: 44 """Get all difficulties of the song, optionally filtered by type. 45 46 Args: 47 song_type: The type of the song (DX, STANDARD, UTAGE). If UNSET, returns all types. 48 Returns: 49 A sequence of difficulties matching the specified type. 50 """ 51 if isinstance(song_type, _UnsetSentinel): 52 return self.difficulties.standard + self.difficulties.dx + self.difficulties.utage 53 if song_type == SongType.DX: 54 return self.difficulties.dx 55 if song_type == SongType.STANDARD: 56 return self.difficulties.standard 57 if song_type == SongType.UTAGE: 58 return self.difficulties.utage 59 60 def get_divingfish_id(self, type: SongType, level_index: LevelIndex) -> int: 61 """Get the Diving Fish ID for a specific difficulty of this song. 62 63 Args: 64 type: The type of the song (DX, STANDARD, UTAGE). 65 level_index: The level index of the difficulty. 66 Returns: 67 The Diving Fish ID for the specified difficulty. 68 """ 69 difficulty = self.get_difficulty(type, level_index) 70 if difficulty is None: 71 raise ValueError(f"No difficulty found for type {type} and level index {level_index}") 72 if difficulty.type == SongType.DX: 73 return self.id + 10000 74 if difficulty.type == SongType.UTAGE: 75 return self.id + 100000 76 return self.id 77 78 def get_divingfish_ids(self, song_type: Union[SongType, _UnsetSentinel] = UNSET) -> set[int]: 79 """Get a set of Diving Fish IDs for all difficulties of this song. 80 81 Args: 82 song_type: The type of the song (DX, STANDARD, UTAGE). If UNSET, returns IDs for all types. 83 Returns: 84 A set of Diving Fish IDs for the specified type or all types if UNSET. 85 """ 86 ids = set() 87 for difficulty in self.get_difficulties(song_type): 88 ids.add(self.get_divingfish_id(difficulty.type, difficulty.level_index)) 89 return ids
Song( id: int, title: str, artist: str, genre: maimai_py.enums.Genre, bpm: int, map: Optional[str], version: int, rights: Optional[str], aliases: Optional[list[str]], disabled: bool, difficulties: SongDifficulties)
genre: maimai_py.enums.Genre
difficulties: SongDifficulties
def
get_difficulty( self, type: maimai_py.enums.SongType, level_index: maimai_py.enums.LevelIndex) -> Optional[SongDifficulty]:
27 def get_difficulty(self, type: SongType, level_index: LevelIndex) -> Optional["SongDifficulty"]: 28 """Get the exact difficulty of this song by type and level index. 29 30 Args: 31 type: The type of the song (DX, STANDARD, UTAGE). 32 level_index: The level index of the difficulty. 33 Returns: 34 The difficulty object if found, otherwise None. 35 """ 36 if type == SongType.DX: 37 return next((diff for diff in self.difficulties.dx if diff.level_index == level_index), None) 38 if type == SongType.STANDARD: 39 return next((diff for diff in self.difficulties.standard if diff.level_index == level_index), None) 40 if type == SongType.UTAGE: 41 return next(iter(self.difficulties.utage), None)
Get the exact difficulty of this song by type and level index.
Arguments:
- type: The type of the song (DX, STANDARD, UTAGE).
- level_index: The level index of the difficulty.
Returns:
The difficulty object if found, otherwise None.
def
get_difficulties( self, song_type: Union[maimai_py.enums.SongType, maimai_py.utils.sentinel._UnsetSentinel] = Unset) -> Sequence[SongDifficulty]:
43 def get_difficulties(self, song_type: Union[SongType, _UnsetSentinel] = UNSET) -> Sequence["SongDifficulty"]: 44 """Get all difficulties of the song, optionally filtered by type. 45 46 Args: 47 song_type: The type of the song (DX, STANDARD, UTAGE). If UNSET, returns all types. 48 Returns: 49 A sequence of difficulties matching the specified type. 50 """ 51 if isinstance(song_type, _UnsetSentinel): 52 return self.difficulties.standard + self.difficulties.dx + self.difficulties.utage 53 if song_type == SongType.DX: 54 return self.difficulties.dx 55 if song_type == SongType.STANDARD: 56 return self.difficulties.standard 57 if song_type == SongType.UTAGE: 58 return self.difficulties.utage
Get all difficulties of the song, optionally filtered by type.
Arguments:
- song_type: The type of the song (DX, STANDARD, UTAGE). If UNSET, returns all types.
Returns:
A sequence of difficulties matching the specified type.
def
get_divingfish_id( self, type: maimai_py.enums.SongType, level_index: maimai_py.enums.LevelIndex) -> int:
60 def get_divingfish_id(self, type: SongType, level_index: LevelIndex) -> int: 61 """Get the Diving Fish ID for a specific difficulty of this song. 62 63 Args: 64 type: The type of the song (DX, STANDARD, UTAGE). 65 level_index: The level index of the difficulty. 66 Returns: 67 The Diving Fish ID for the specified difficulty. 68 """ 69 difficulty = self.get_difficulty(type, level_index) 70 if difficulty is None: 71 raise ValueError(f"No difficulty found for type {type} and level index {level_index}") 72 if difficulty.type == SongType.DX: 73 return self.id + 10000 74 if difficulty.type == SongType.UTAGE: 75 return self.id + 100000 76 return self.id
Get the Diving Fish ID for a specific difficulty of this song.
Arguments:
- type: The type of the song (DX, STANDARD, UTAGE).
- level_index: The level index of the difficulty.
Returns:
The Diving Fish ID for the specified difficulty.
def
get_divingfish_ids( self, song_type: Union[maimai_py.enums.SongType, maimai_py.utils.sentinel._UnsetSentinel] = Unset) -> set[int]:
78 def get_divingfish_ids(self, song_type: Union[SongType, _UnsetSentinel] = UNSET) -> set[int]: 79 """Get a set of Diving Fish IDs for all difficulties of this song. 80 81 Args: 82 song_type: The type of the song (DX, STANDARD, UTAGE). If UNSET, returns IDs for all types. 83 Returns: 84 A set of Diving Fish IDs for the specified type or all types if UNSET. 85 """ 86 ids = set() 87 for difficulty in self.get_difficulties(song_type): 88 ids.add(self.get_divingfish_id(difficulty.type, difficulty.level_index)) 89 return ids
Get a set of Diving Fish IDs for all difficulties of this song.
Arguments:
- song_type: The type of the song (DX, STANDARD, UTAGE). If UNSET, returns IDs for all types.
Returns:
A set of Diving Fish IDs for the specified type or all types if UNSET.
@dataclass
class
SongDifficulties:
92@dataclass 93class SongDifficulties: 94 __slots__ = ("standard", "dx", "utage") 95 96 standard: list["SongDifficulty"] 97 dx: list["SongDifficulty"] 98 utage: list["SongDifficultyUtage"]
SongDifficulties( standard: list[SongDifficulty], dx: list[SongDifficulty], utage: list[SongDifficultyUtage])
standard: list[SongDifficulty]
dx: list[SongDifficulty]
utage: list[SongDifficultyUtage]
@dataclass
class
CurveObject:
101@dataclass 102class CurveObject: 103 __slots__ = ("sample_size", "fit_level_value", "avg_achievements", "stdev_achievements", "avg_dx_score", "rate_sample_size", "fc_sample_size") 104 105 sample_size: int 106 fit_level_value: float 107 avg_achievements: float 108 stdev_achievements: float 109 avg_dx_score: float 110 rate_sample_size: dict[RateType, int] 111 fc_sample_size: dict[FCType, int]
CurveObject( sample_size: int, fit_level_value: float, avg_achievements: float, stdev_achievements: float, avg_dx_score: float, rate_sample_size: dict[maimai_py.enums.RateType, int], fc_sample_size: dict[maimai_py.enums.FCType, int])
rate_sample_size: dict[maimai_py.enums.RateType, int]
fc_sample_size: dict[maimai_py.enums.FCType, int]
@dataclass
class
SongDifficulty:
114@dataclass 115class SongDifficulty: 116 __slots__ = ( 117 "id", 118 "type", 119 "level", 120 "level_value", 121 "level_index", 122 "note_designer", 123 "version", 124 "tap_num", 125 "hold_num", 126 "slide_num", 127 "touch_num", 128 "break_num", 129 "curve", 130 ) 131 132 type: SongType 133 level: str 134 level_value: float 135 level_index: LevelIndex 136 note_designer: str 137 version: int 138 tap_num: int 139 hold_num: int 140 slide_num: int 141 touch_num: int 142 break_num: int 143 curve: Optional[CurveObject]
SongDifficulty( type: maimai_py.enums.SongType, level: str, level_value: float, level_index: maimai_py.enums.LevelIndex, note_designer: str, version: int, tap_num: int, hold_num: int, slide_num: int, touch_num: int, break_num: int, curve: Optional[CurveObject])
type: maimai_py.enums.SongType
level_index: maimai_py.enums.LevelIndex
curve: Optional[CurveObject]
146@dataclass 147class SongDifficultyUtage(SongDifficulty): 148 __slots__ = ("kanji", "description", "is_buddy") 149 150 kanji: str 151 description: str 152 is_buddy: bool
SongDifficultyUtage( type: maimai_py.enums.SongType, level: str, level_value: float, level_index: maimai_py.enums.LevelIndex, note_designer: str, version: int, tap_num: int, hold_num: int, slide_num: int, touch_num: int, break_num: int, curve: Optional[CurveObject], kanji: str, description: str, is_buddy: bool)
Inherited Members
@dataclass
class
SongAlias:
@dataclass
class
PlayerIdentifier:
163@dataclass 164class PlayerIdentifier: 165 qq: Optional[int] = None 166 username: Optional[str] = None 167 friend_code: Optional[int] = None 168 credentials: Union[str, MutableMapping[str, Any], None] = None 169 170 def __post_init__(self): 171 if self.qq is None and self.username is None and self.friend_code is None and self.credentials is None: 172 raise InvalidPlayerIdentifierError("At least one of the following must be provided: qq, username, friend_code, credentials") 173 174 def _as_diving_fish(self) -> dict[str, Any]: 175 if self.qq: 176 return {"qq": str(self.qq)} 177 elif self.username: 178 return {"username": self.username} 179 elif self.friend_code: 180 raise InvalidPlayerIdentifierError("Friend code is not applicable for Diving Fish") 181 else: 182 raise InvalidPlayerIdentifierError("No valid identifier provided") 183 184 def _as_lxns(self) -> str: 185 if self.friend_code: 186 return str(self.friend_code) 187 elif self.qq: 188 return f"qq/{str(self.qq)}" 189 elif self.username: 190 raise InvalidPlayerIdentifierError("Username is not applicable for LXNS") 191 else: 192 raise InvalidPlayerIdentifierError("No valid identifier provided")
@dataclass
class
PlayerItem:
@dataclass
class
PlayerRegion:
@dataclass
class
Player:
299@dataclass 300class DivingFishPlayer(Player): 301 __slots__ = ("nickname", "plate", "additional_rating") 302 303 nickname: str 304 plate: str 305 additional_rating: int
Inherited Members
308@dataclass 309class LXNSPlayer(Player): 310 __slots__ = ("friend_code", "course_rank", "class_rank", "star", "frame", "icon", "trophy", "name_plate", "upload_time") 311 312 friend_code: int 313 course_rank: int 314 class_rank: int 315 star: int 316 frame: Optional[PlayerFrame] 317 icon: Optional[PlayerIcon] 318 trophy: Optional[PlayerTrophy] 319 name_plate: Optional[PlayerNamePlate] 320 upload_time: str
LXNSPlayer( name: str, rating: int, friend_code: int, course_rank: int, class_rank: int, star: int, frame: Optional[PlayerFrame], icon: Optional[PlayerIcon], trophy: Optional[PlayerTrophy], name_plate: Optional[PlayerNamePlate], upload_time: str)
frame: Optional[PlayerFrame]
icon: Optional[PlayerIcon]
trophy: Optional[PlayerTrophy]
name_plate: Optional[PlayerNamePlate]
Inherited Members
323@dataclass 324class ArcadePlayer(Player): 325 __slots__ = ("is_login", "icon", "trophy", "name_plate") 326 327 is_login: bool 328 icon: Optional[PlayerIcon] 329 trophy: Optional[PlayerTrophy] 330 name_plate: Optional[PlayerNamePlate]
ArcadePlayer( name: str, rating: int, is_login: bool, icon: Optional[PlayerIcon], trophy: Optional[PlayerTrophy], name_plate: Optional[PlayerNamePlate])
icon: Optional[PlayerIcon]
trophy: Optional[PlayerTrophy]
name_plate: Optional[PlayerNamePlate]
Inherited Members
@dataclass
class
AreaCharacter:
333@dataclass 334class AreaCharacter: 335 __slots__ = ("id", "name", "illustrator", "description1", "description2", "team", "props") 336 337 name: str 338 illustrator: str 339 description1: str 340 description2: str 341 team: str 342 props: dict[str, str]
@dataclass
class
AreaSong:
345@dataclass 346class AreaSong: 347 __slots__ = ("id", "title", "artist", "description", "illustrator", "movie") 348 349 id: Optional[int] 350 title: str 351 artist: str 352 description: str 353 illustrator: Optional[str] 354 movie: Optional[str]
@dataclass
class
Area:
357@dataclass 358class Area: 359 __slots__ = ("id", "name", "comment", "description", "video_id", "characters", "songs") 360 361 id: str 362 name: str 363 comment: str 364 description: str 365 video_id: str 366 characters: list[AreaCharacter] 367 songs: list[AreaSong]
Area( id: str, name: str, comment: str, description: str, video_id: str, characters: list[AreaCharacter], songs: list[AreaSong])
characters: list[AreaCharacter]
songs: list[AreaSong]
@dataclass
class
Score:
370@dataclass 371class Score: 372 __slots__ = ("id", "level", "level_index", "achievements", "fc", "fs", "dx_score", "dx_rating", "play_count", "rate", "type", "song") 373 374 id: int 375 level: str 376 level_index: LevelIndex 377 achievements: Optional[float] 378 fc: Optional[FCType] 379 fs: Optional[FSType] 380 dx_score: Optional[int] 381 dx_rating: Optional[float] 382 play_count: Optional[int] 383 rate: RateType 384 type: SongType 385 386 def _compare(self, other: Optional["Score"]) -> "Score": 387 if other is None: 388 return self 389 if self.dx_score != other.dx_score: # larger value is better 390 return self if (self.dx_score or 0) > (other.dx_score or 0) else other 391 if self.achievements != other.achievements: # larger value is better 392 return self if (self.achievements or 0) > (other.achievements or 0) else other 393 if self.rate != other.rate: # smaller value is better 394 self_rate = self.rate.value if self.rate is not None else 100 395 other_rate = other.rate.value if other.rate is not None else 100 396 return self if self_rate < other_rate else other 397 if self.fc != other.fc: # smaller value is better 398 self_fc = self.fc.value if self.fc is not None else 100 399 other_fc = other.fc.value if other.fc is not None else 100 400 return self if self_fc < other_fc else other 401 if self.fs != other.fs: # bigger value is better 402 self_fs = self.fs.value if self.fs is not None else -1 403 other_fs = other.fs.value if other.fs is not None else -1 404 return self if self_fs > other_fs else other 405 return self # we consider they are equal 406 407 def _join(self, other: Optional["Score"]) -> "Score": 408 if other is not None: 409 if self.level_index != other.level_index or self.type != other.type: 410 raise ValueError("Cannot join scores with different level indexes or types") 411 self.achievements = max(self.achievements or 0, other.achievements or 0) 412 if self.fc != other.fc: 413 self_fc = self.fc.value if self.fc is not None else 100 414 other_fc = other.fc.value if other.fc is not None else 100 415 selected_value = min(self_fc, other_fc) 416 self.fc = FCType(selected_value) if selected_value != 100 else None 417 if self.fs != other.fs: 418 self_fs = self.fs.value if self.fs is not None else -1 419 other_fs = other.fs.value if other.fs is not None else -1 420 selected_value = max(self_fs, other_fs) 421 self.fs = FSType(selected_value) if selected_value != -1 else None 422 if self.rate != other.rate: 423 selected_value = min(self.rate.value, other.rate.value) 424 self.rate = RateType(selected_value) 425 return self
Score( id: int, level: str, level_index: maimai_py.enums.LevelIndex, achievements: Optional[float], fc: Optional[maimai_py.enums.FCType], fs: Optional[maimai_py.enums.FSType], dx_score: Optional[int], dx_rating: Optional[float], play_count: Optional[int], rate: maimai_py.enums.RateType, type: maimai_py.enums.SongType)
level_index: maimai_py.enums.LevelIndex
fc: Optional[maimai_py.enums.FCType]
fs: Optional[maimai_py.enums.FSType]
rate: maimai_py.enums.RateType
type: maimai_py.enums.SongType
428@dataclass 429class ScoreExtend(Score): 430 __slots__ = ["title", "level_value", "level_dx_score"] 431 432 title: str 433 level_value: float 434 level_dx_score: int
ScoreExtend( id: int, level: str, level_index: maimai_py.enums.LevelIndex, achievements: Optional[float], fc: Optional[maimai_py.enums.FCType], fs: Optional[maimai_py.enums.FSType], dx_score: Optional[int], dx_rating: Optional[float], play_count: Optional[int], rate: maimai_py.enums.RateType, type: maimai_py.enums.SongType, title: str, level_value: float, level_dx_score: int)
Inherited Members
@dataclass
class
PlateObject:
437@dataclass 438class PlateObject: 439 __slots__ = ("song", "levels", "scores") 440 441 song: Song 442 levels: set[LevelIndex] 443 scores: list[ScoreExtend]
PlateObject( song: Song, levels: set[maimai_py.enums.LevelIndex], scores: list[ScoreExtend])
song: Song
levels: set[maimai_py.enums.LevelIndex]
scores: list[ScoreExtend]
@dataclass
class
PlayerSong:
446@dataclass 447class PlayerSong: 448 __slots__ = ["song", "scores"] 449 450 song: Song 451 scores: list[ScoreExtend]
PlayerSong( song: Song, scores: list[ScoreExtend])
song: Song
scores: list[ScoreExtend]
@dataclass
class
PlayerBests:
454@dataclass 455class PlayerBests: 456 __slots__ = ["rating", "rating_b35", "rating_b15", "scores_b35", "scores_b15"] 457 458 rating: int 459 rating_b35: int 460 rating_b15: int 461 scores_b35: list[ScoreExtend] 462 scores_b15: list[ScoreExtend] 463 464 @property 465 def scores(self) -> list[ScoreExtend]: 466 """Get all scores, including both B35 and B15.""" 467 return self.scores_b35 + self.scores_b15
PlayerBests( rating: int, rating_b35: int, rating_b15: int, scores_b35: list[ScoreExtend], scores_b15: list[ScoreExtend])
scores_b35: list[ScoreExtend]
scores_b15: list[ScoreExtend]
scores: list[ScoreExtend]
464 @property 465 def scores(self) -> list[ScoreExtend]: 466 """Get all scores, including both B35 and B15.""" 467 return self.scores_b35 + self.scores_b15
Get all scores, including both B35 and B15.