2022-11-18 16:51:24 +00:00
|
|
|
from pydantic import BaseModel
|
|
|
|
from typing import List, Optional, Union
|
|
|
|
|
|
|
|
|
|
|
|
class User(BaseModel):
|
|
|
|
"""Pydantic model for user data.
|
|
|
|
Created from the user response (`/api/v1/user/x`),
|
|
|
|
some values might be unused."""
|
|
|
|
|
|
|
|
Admin: bool = True
|
|
|
|
Banned: bool = False
|
|
|
|
BulletSpr: int = 0
|
|
|
|
CapeColor: int = 0
|
|
|
|
Costume: int = 0
|
|
|
|
Country: int = 0
|
|
|
|
CreatedAt: str = "2020-02-14T10:03:40Z"
|
|
|
|
DeathEffect: int = 0
|
|
|
|
DeletedAt: Optional[str] = None
|
|
|
|
FacialExpression: int = 0
|
|
|
|
Followed: bool = False
|
|
|
|
FollowerColor: int = 0
|
|
|
|
FollowerSpr: int = 0
|
|
|
|
GunSpr: int = 0
|
|
|
|
HairColor: int = 0
|
|
|
|
HairSpr: int = 0
|
|
|
|
HatColor: int = 0
|
|
|
|
HatColorInv: int = 0
|
|
|
|
HatSpr: int = 0
|
|
|
|
ID: int = 0
|
|
|
|
NumMapsClear: int = 0
|
|
|
|
NumMapsCreated: int = 0
|
|
|
|
NumSpeedrunRecords: int = 0
|
|
|
|
PantsColor: int = 0
|
|
|
|
RecordEndurance0: int = 0
|
|
|
|
RecordEndurance1: int = 0
|
|
|
|
RecordEndurance2: int = 0
|
|
|
|
RecordEndurance3: int = 0
|
|
|
|
RecordExplorer: int = 0
|
|
|
|
RecordHardcore: int = 0
|
|
|
|
RecordRoulette: int = 0
|
|
|
|
RecordScribble0: int = 0
|
|
|
|
RecordScribble1: int = 0
|
|
|
|
RecordScribble2: int = 0
|
|
|
|
RecordScribble3: int = 0
|
|
|
|
SaveEffect: int = 0
|
|
|
|
ShirtColor: int = 0
|
|
|
|
ShoesColor: int = 0
|
|
|
|
SkinColor: int = 0
|
|
|
|
SwordSpr: int = 0
|
|
|
|
TextSnd: int = 0
|
|
|
|
Unlocks: str = ""
|
|
|
|
UpdatedAt: str = "2020-03-26T05:22:32Z"
|
|
|
|
Username: str = "magmaus3"
|
|
|
|
Email: str = "user@example.com"
|
|
|
|
|
|
|
|
|
|
|
|
class Notification(BaseModel):
|
|
|
|
"""Pydantic model for Notifications
|
|
|
|
|
|
|
|
Available notification types:
|
|
|
|
- 0: <user> took your record on <map> with time <time>
|
|
|
|
- NotifData seems to need following variables:
|
|
|
|
- `MapID`: `int`
|
|
|
|
- `MapName`: `str`, displayed map name
|
|
|
|
- `ByUserID`: `int`, who took the record
|
|
|
|
- `ByUserName`: `str`, who took the record (displayed name)
|
|
|
|
- `NewTime`: `int`, might be the either the amount of seconds or the time id (TODO: correct the info)
|
|
|
|
|
|
|
|
- 1: Your time on <map> was removed
|
|
|
|
- Reason IDs:
|
|
|
|
- 0: no reason
|
|
|
|
- 1: empty reason (?)
|
|
|
|
- 2: Autofire/macros/scripting
|
|
|
|
- 3: Replay was broken
|
|
|
|
- Other reason IDs return the following message: `Unrecognized reason: <reason>`
|
|
|
|
- Other than `Reason`, NotifData requires following variables as well:
|
|
|
|
- MapID
|
|
|
|
- MapName
|
|
|
|
|
|
|
|
- 2: Your level <level> was removed
|
|
|
|
- Reason IDs:
|
|
|
|
- 0: no reason
|
|
|
|
- 1: empty reason (?)
|
|
|
|
- 2: Inappropriate name or description
|
|
|
|
- 3: Submitted using autofire/macros/scripting
|
|
|
|
- 4: Inappropriate content
|
|
|
|
- 5: Unreasonably long forced waiting
|
|
|
|
- 6: Intentionally lagging the game
|
|
|
|
- Like with type 1, other reason IDs show the "unrecognized reason" message
|
|
|
|
- Other than `Reason`, NotifData requires following variables as well:
|
|
|
|
- MapID
|
|
|
|
- MapName
|
|
|
|
|
|
|
|
- 3: You were temporarily banned.
|
|
|
|
- Reason IDs:
|
|
|
|
- 0: no reason
|
|
|
|
- 1: empty reason (?)
|
|
|
|
- 2: Inappropriate level name or description
|
|
|
|
- 3: Inappropriate user name
|
|
|
|
- 4: Inappropriate playlist name
|
|
|
|
- 5: Autofire/macros/scripting
|
|
|
|
- 6: Inappropriate level content
|
|
|
|
- 7: Level having unreasonably long forced waiting
|
|
|
|
- 8: Level intentionally lagging the game
|
|
|
|
- 9: Exploiting the game
|
|
|
|
- Like with types 1 and 2, other reason IDs return the "unrecognized reason" message
|
|
|
|
- 4: Message from an administrator:
|
|
|
|
- NotifData requires the following variables:
|
|
|
|
- Message: str
|
|
|
|
|
|
|
|
All other types are displayed as an empty notification, without any special handling.
|
|
|
|
|
|
|
|
Note: NotifData must be returned to the game as a string,
|
|
|
|
otherwise the game crashes (as of `Early Access Ver 0.786`)
|
|
|
|
"""
|
|
|
|
|
|
|
|
CreatedAt: str = "2008-01-18T00:00:00Z"
|
|
|
|
UpdatedAt: str = "2008-01-18T00:00:00Z"
|
|
|
|
DeletedAt: Optional[str] = None
|
|
|
|
ForUserID: int = 0
|
|
|
|
ID: int = 0
|
|
|
|
IsRead: bool = False
|
|
|
|
MapID: int = 0 # Might be optional for some notification types
|
|
|
|
|
|
|
|
# NotifData also accepts str for compatibility with the game (see docstring)
|
|
|
|
NotifData: Union[str, dict] = {
|
|
|
|
"MapID": 215884,
|
|
|
|
"MapName": "speedrun_tower.map",
|
|
|
|
"ByUserID": 1,
|
|
|
|
"ByUserName": "user",
|
|
|
|
"NewTime": 1172,
|
|
|
|
"Reason": 2,
|
|
|
|
}
|
|
|
|
Type: int = 0
|
|
|
|
|
2022-11-19 12:51:28 +00:00
|
|
|
|
2022-11-18 16:51:24 +00:00
|
|
|
class Map(BaseModel):
|
|
|
|
"""Pydantic model for maps.
|
|
|
|
It looks like a lot of variables are also included in the user
|
|
|
|
data.
|
|
|
|
"""
|
2022-11-19 12:51:28 +00:00
|
|
|
|
2022-11-18 16:51:24 +00:00
|
|
|
ID: int = 0
|
|
|
|
CreatorId: int = 0
|
|
|
|
|
|
|
|
# CreatorName might be also retrieved from the user data
|
|
|
|
# using the CreatorId
|
|
|
|
CreatorName: str = ""
|
|
|
|
|
|
|
|
Name: str = "Interesting level"
|
|
|
|
Description: str = ""
|
|
|
|
Version: int = 90
|
|
|
|
CreatedAt: str = "2022-06-18T15:10:02Z"
|
|
|
|
DeletedAt: Union[str, None] = None
|
|
|
|
MapCode: str = "AAABBAAA"
|
|
|
|
Listed: bool = True
|
|
|
|
HiddenInChallenges: bool = False
|
|
|
|
DragonCoins: bool = False
|
|
|
|
|
|
|
|
# Might be duplicates from the User data
|
2022-11-19 12:51:28 +00:00
|
|
|
|
2022-11-18 16:51:24 +00:00
|
|
|
ShoesColor: int = 0
|
|
|
|
PantsColor: int = 0
|
|
|
|
ShirtColor: int = 10897172
|
|
|
|
CapeColor: int = 0
|
|
|
|
SkinColor: int = 0
|
|
|
|
HairColor: int = 0
|
|
|
|
HatSpr: int = 1
|
|
|
|
Country: int = 143
|
|
|
|
HairSpr: int = 0
|
|
|
|
HatColor: int = 0
|
|
|
|
HatColorInv: int = 0
|
|
|
|
FacialExpression: int = 0
|
|
|
|
DeathEffect: int = 0
|
|
|
|
GunSpr: int = 0
|
|
|
|
BulletSpr: int = 0
|
|
|
|
SwordSpr: int = 0
|
|
|
|
Costume: int = 0
|
|
|
|
FollowerSpr: int = 0
|
|
|
|
FollowerColor: int = 0
|
|
|
|
SaveEffect: int = 0
|
|
|
|
TextSnd: int = 0
|
|
|
|
|
|
|
|
AverageUserDifficulty: float = 0
|
|
|
|
ComputedDifficulty: float = 0
|
|
|
|
|
|
|
|
# Variables below might be used when sorted
|
|
|
|
# by rating
|
|
|
|
AverageRating: float = 0
|
|
|
|
NumRatings: int = 0
|
|
|
|
NumThumbsUp: int = 0
|
|
|
|
NumThumbsDown: int = 0
|
|
|
|
|
|
|
|
TagIDs: str = ""
|
|
|
|
TagNames: str = ""
|
|
|
|
TagFreqs: str = ""
|
|
|
|
|
|
|
|
PlayCount: int = 0
|
|
|
|
ClearCount: int = 0
|
2022-11-19 12:51:28 +00:00
|
|
|
|
2022-11-18 16:51:24 +00:00
|
|
|
# I'm not sure how ClearRate is handled
|
2022-11-19 12:51:28 +00:00
|
|
|
# in the official server, but I assume it's
|
2022-11-18 16:51:24 +00:00
|
|
|
# calculated as ClearCount / PlayCount.
|
|
|
|
ClearRate: float = 0.0
|
|
|
|
FavoriteCount: int = 0
|
|
|
|
DeathCount: int = 0
|
|
|
|
PlayerCount: int = 0
|
|
|
|
|
|
|
|
BestTimeUserID: int = 0
|
|
|
|
BestTimeUsername: str = ""
|
|
|
|
BestTimePlaytime: int = 0
|
|
|
|
MyBestPlaytime: int = 0
|
|
|
|
|
|
|
|
FirstClearUserID: Union[int, None] = 0
|
|
|
|
|
|
|
|
# Propably not needed, because you can simply read the
|
|
|
|
# FirstClearUserID, and get the name from there
|
|
|
|
# It's still useful for requests though.
|
2022-11-19 12:51:28 +00:00
|
|
|
FirstClearUsername: Union[str, None] = "uwu"
|
2022-11-18 16:51:24 +00:00
|
|
|
MapData: str = ""
|
2022-11-19 12:51:28 +00:00
|
|
|
MapReplay: str = (
|
|
|
|
"" # Might be also stored in DB as a dict of user IDs and the replays
|
|
|
|
)
|
2022-11-18 16:51:24 +00:00
|
|
|
Played: bool = False
|
|
|
|
Clear: bool = False
|
|
|
|
FullClear: bool = False
|
|
|
|
|
|
|
|
# Leaderboard scores
|
|
|
|
Leaderboard: list = []
|
|
|
|
|
|
|
|
|
|
|
|
class MapLeaderboard(BaseModel):
|
|
|
|
"""Pydantic model for Map record leaderboards."""
|
2022-11-19 12:51:28 +00:00
|
|
|
|
2022-11-18 16:51:24 +00:00
|
|
|
BestPlaytime: int
|
|
|
|
BestPlaytimeTime: str
|
|
|
|
BestReplay: str
|
|
|
|
CreatorName: str
|
|
|
|
UserID: int
|
|
|
|
|
|
|
|
ShoesColor: int = 0
|
|
|
|
PantsColor: int = 0
|
|
|
|
ShirtColor: int = 10897172
|
|
|
|
CapeColor: int = 0
|
|
|
|
SkinColor: int = 0
|
|
|
|
HairColor: int = 0
|
|
|
|
HatSpr: int = 1
|
|
|
|
Country: int = 143
|
|
|
|
HairSpr: int = 0
|
|
|
|
HatColor: int = 0
|
|
|
|
HatColorInv: int = 0
|
|
|
|
FacialExpression: int = 0
|
|
|
|
DeathEffect: int = 0
|
|
|
|
GunSpr: int = 0
|
|
|
|
BulletSpr: int = 0
|
|
|
|
SwordSpr: int = 0
|
|
|
|
Costume: int = 0
|
|
|
|
FollowerSpr: int = 0
|
|
|
|
FollowerColor: int = 0
|
|
|
|
SaveEffect: int = 0
|
|
|
|
TextSnd: int = 0
|
2022-11-19 12:51:28 +00:00
|
|
|
|
2022-11-18 16:51:24 +00:00
|
|
|
|
|
|
|
ids_to_names = {
|
|
|
|
0: "Adventure/Variety",
|
|
|
|
6: "Gimmick",
|
|
|
|
7: "Trap/Troll",
|
|
|
|
3: "Joke/Meme",
|
|
|
|
2: "Needle",
|
|
|
|
5: "Puzzle",
|
|
|
|
1: "Boss/Avoidance",
|
|
|
|
9: "Art",
|
|
|
|
8: "Music",
|
|
|
|
4: "Auto",
|
|
|
|
}
|
|
|
|
|
2022-11-19 12:51:28 +00:00
|
|
|
|
2022-11-18 16:51:24 +00:00
|
|
|
def convertTagsToNames(tags):
|
|
|
|
"""Converts tag IDs to names"""
|
|
|
|
global ids_to_names
|
|
|
|
tagNames = []
|
2022-11-19 12:51:28 +00:00
|
|
|
for i in tags.split(","):
|
2022-11-18 16:51:24 +00:00
|
|
|
tagNames.append(ids_to_names[int(i)])
|
|
|
|
return tagNames
|