async def like( self, item_id: int, typeid: int, special: int, dislike: bool = False, *, client: Client ) -> None: like = dislike ^ 1 rs, udid, uuid = Coder.gen_rs(), Params.gen_udid(), Params.gen_uuid() values = [special, item_id, like, typeid, rs, client.account_id, udid, uuid] chk = Coder.gen_chk(type="like_rate", values=values) payload = ( Params() .create_new(game_version=20) .put_definer("accountid", client.account_id) .put_password(client.encodedpass) .put_udid(udid) .put_uuid(uuid) .put_definer("itemid", item_id) .put_like(like) .put_type(typeid) .put_special(special) .put_rs(rs) .put_chk(chk) .finish() ) resp = await self.http.request(Route.LIKE_ITEM, payload) if resp != 1: raise MissingAccess(f"Failed to like an item by ID: {item_id}.")
async def rate_level(self, level_id: int, rating: int, *, client: Client) -> None: assert 0 < rating <= 10, "Invalid star value given." rs, udid, uuid = Coder.gen_rs(), Params.gen_udid(), Params.gen_uuid() values = [level_id, rating, rs, client.account_id, udid, uuid] chk = Coder.gen_chk(type="like_rate", values=values) payload = ( Params() .create_new() .put_definer("levelid", level_id) .put_definer("accountid", client.account_id) .put_password(client.encodedpass) .put_udid(udid) .put_uuid(uuid) .put_definer("stars", rating) .put_rs(rs) .put_chk(chk) .finish() ) resp = await self.http.request(Route.RATE_LEVEL_STARS, payload) if resp != 1: raise MissingAccess(f"Failed to rate level by ID: {level_id}.")
def _attempt_zip(s: str): unzip = all(char not in s for char in "|;,.") # O(4n) try: if unzip: return Coder.unzip(s) return Coder.zip(s) except Exception: return s
def _attempt_zip(string: str) -> str: unzip = all(char not in string for char in "|;,.") # O(m*n) try: if unzip: return Coder.unzip(string) return Coder.zip(string) except Exception: return string
def from_data(cls, data: ExtDict, user_2: Union[ExtDict, AbstractUser], client: Client) -> FriendRequest: user_1 = AbstractUser( name=data.get(Index.REQUEST_SENDER_NAME, "unknown"), id=data.getcast(Index.REQUEST_SENDER_ID, 0, int), account_id=data.getcast(Index.REQUEST_SENDER_ACCOUNT_ID, 0, int), client=client, ) if isinstance(user_2, ExtDict): user_2 = AbstractUser(**user_2, client=client) indicator = data.getcast(Index.REQUEST_INDICATOR, 0, int) is_normal = indicator ^ 1 return cls( id=data.getcast(Index.REQUEST_ID, 0, int), timestamp=str(data.get(Index.REQUEST_TIMESTAMP, "unknown")), body=Coder.do_base64(data.get(Index.REQUEST_BODY, ""), encode=False, errors="replace"), is_read=(not data.get(Index.REQUEST_STATUS)), author=(user_1 if is_normal else user_2), recipient=(user_2 if is_normal else user_1), type=MessageOrRequestType.from_value(indicator, 0), client=client, )
def from_data(cls, data: ExtDict, user_2: Union[ExtDict, AbstractUser], client: Client) -> Message: user_1 = AbstractUser( name=data.get(Index.MESSAGE_SENDER_NAME, "unknown"), id=data.getcast(Index.MESSAGE_SENDER_ID, 0, int), account_id=data.getcast(Index.MESSAGE_SENDER_ACCOUNT_ID, 0, int), client=client, ) if isinstance(user_2, ExtDict): user_2 = AbstractUser(**user_2, client=client) indicator = data.getcast(Index.MESSAGE_INDICATOR, 0, int) is_normal = indicator ^ 1 subject = Coder.do_base64(data.get(Index.MESSAGE_SUBJECT, ""), encode=False, errors="replace") return Message( id=data.getcast(Index.MESSAGE_ID, 0, int), timestamp=data.get(Index.MESSAGE_TIMESTAMP, "unknown"), subject=subject, is_read=bool(data.getcast(Index.MESSAGE_IS_READ, 0, int)), author=(user_1 if is_normal else user_2), recipient=(user_2 if is_normal else user_1), type=MessageOrRequestType.from_value(indicator, 0), client=client, )
def put_save_data(self, data_seq: Sequence[Union[bytes, str]]) -> Parameters: """Self explanatory. Puts `'saveData'` parameter. Parameters ---------- data_seq: Sequence[Union[:class:`bytes`, :class:`str`]] Data to put. Returns ------- :class:`.Parameters` ``self`` """ parts = [] for data in data_seq: if isinstance(data, bytes): data = data.decode(errors="ignore") if "?xml" in data: # not encoded data = Coder.encode_save(data, needs_xor=False) parts.append(data) self.dict["saveData"] = ";".join(parts) return self
def from_data(cls, data: ExtDict, author: Union[ExtDict, AbstractUser], client: Client) -> Comment: if isinstance(author, ExtDict): if any(key.isdigit() for key in author.keys()): author = AbstractUser.from_data(author, client=client) else: author = AbstractUser(**author, client=client) color_string = data.get(Index.COMMENT_COLOR, "255,255,255") color = Color.from_rgb(*map(int, color_string.split(","))) return cls( body=Coder.do_base64(data.get(Index.COMMENT_BODY, ""), encode=False, errors="replace"), rating=data.getcast(Index.COMMENT_RATING, 0, int), timestamp=data.get(Index.COMMENT_TIMESTAMP, "unknown"), id=data.getcast(Index.COMMENT_ID, 0, int), is_spam=bool(data.getcast(Index.COMMENT_IS_SPAM, 0, int)), type=CommentType.from_value( data.getcast(Index.COMMENT_TYPE, 0, int), 0), color=color, level_id=data.getcast(Index.COMMENT_LEVEL_ID, 0, int), level_percentage=data.getcast(Index.COMMENT_LEVEL_PERCENTAGE, -1, int), author=author, client=client, )
async def update_profile(self, settings: Dict[str, int], *, client: Client) -> None: settings_cased = {Converter.snake_to_camel(name): value for name, value in settings.items()} rs = Coder.gen_rs() req_chk_params = [client.account_id] req_chk_params.extend( settings.get(param, 0) for param in ( "user_coins", "demons", "stars", "coins", "icon_type", "icon", "diamonds", "acc_icon", "acc_ship", "acc_ball", "acc_bird", "acc_dart", "acc_robot", "acc_glow", "acc_spider", "acc_explosion", ) ) chk = Coder.gen_chk(type="userscore", values=req_chk_params) payload = ( Params() .create_new() .put_definer("accountid", client.account_id) .put_password(client.encodedpass) .put_username(client.name) .put_seed(rs) .put_seed(chk, suffix=str(2)) .finish() ) payload.update(settings_cased) resp = await self.http.request(Route.UPDATE_USER_SCORE, payload) if not resp > 0: raise MissingAccess(f"Failed to update profile of a client: {client!r}")
def put_message(self, subject: str, body: str) -> Parameters: """Puts message's subject and body. Parameters ---------- subject: :class:`str` NOT ENCODED message subject. body: :class:`str` NOT ENCODED message body. Returns ------- :class:`.Parameters` ``self`` """ self.dict["subject"] = Coder.do_base64(subject) self.dict["body"] = Coder.encode(type="message", string=body) return self
def into_level( self, client: Optional[Client] = None, get_data: bool = True, server_style: bool = False, ) -> Level: if self.is_demon(): difficulty = DemonDifficulty.from_name(self.difficulty) else: difficulty = LevelDifficulty.from_name(self.difficulty) if get_data: data = official_levels_data.get(self.name, "") if data: data = Coder.unzip(data) else: data = "" return Level( id=self.level_id, name=self.name, description=f"Official Level: {self.name}", version=1, creator=AbstractUser(name="RobTop", id=16, account_id=71, client=client), song=Song.official(self.get_song_id(server_style), client=client, server_style=server_style), data=data, password=None, copyable=False, is_demon=self.is_demon(), is_auto=self.is_auto(), difficulty=difficulty, stars=self.stars, coins=self.coins, verified_coins=True, is_epic=False, original=True, low_detail_mode=False, downloads=0, rating=0, score=1, uploaded_timestamp="unknown", last_updated_timestamp="unknown", length=LevelLength.from_name(self.length), stars_requested=self.stars, object_count=0, type=TimelyType.from_value(0), time_n=-1, cooldown=-1, client=client, )
def put_comment(self, content: str, values: List[Any]) -> Parameters: """Puts a comment. Parameters ---------- content: :class:`str` The content of the comment. values: :class:`list` A list of values to generate a ``chk`` parameter with. Returns ------- :class:`.Parameters` ``self`` """ comment = Coder.do_base64(content) self.dict["comment"] = comment values.insert(1, comment) self.put_chk(Coder.gen_chk(type="comment", values=values)) return self
def put_fr_comment(self, item: str) -> Parameters: """Pretty self explanatory. Puts `'comment'` parameter. Parameters ---------- item: :class:`str` A comment to put. Returns ------- :class:`.Parameters` ``self`` """ self.dict["comment"] = Coder.do_base64(item) return self
async def read_message( self, typeof: MessageOrRequestType, message_id: int, client: Client ) -> str: payload = ( Params() .create_new() .put_definer("accountid", client.account_id) .put_definer("messageid", message_id) .put_is_sender(typeof.name.lower()) .put_password(client.encodedpass) .finish() ) codes = {-1: MissingAccess(f"Failed to read a message by ID: {message_id!r}.")} resp = await self.http.request(Route.READ_PRIVATE_MESSAGE, payload, error_codes=codes,) mapped = Parser().with_split(":").should_map().parse(resp) return Coder.decode(type="message", string=mapped.get(Index.MESSAGE_BODY, ""))
def put_level_desc(self, content: str) -> Parameters: """Encodes given content and puts ``'levelDesc'``. Parameters ---------- content: :class:`str` Content of the new description, NOT encoded in Base64. Returns ------- :class:`.Parameters` ``self`` """ if content is None: content = "" desc = Coder.do_base64(content) self.dict["levelDesc"] = desc return self
def from_data( cls, data: ExtDict, creator: Union[ExtDict, AbstractUser], song: Union[ExtDict, Song], client: Client, ) -> Level: if isinstance(creator, ExtDict): creator = AbstractUser(**creator, client=client) if isinstance(song, ExtDict): if any(key.isdigit() for key in song.keys()): song = Song.from_data(song, client=client) else: song = Song(**song, client=client) string = data.get(Index.LEVEL_PASS) if string is None: copyable, password = False, None else: if string == ZERO_STR: copyable, password = False, None else: try: # decode password password = Coder.decode(type="levelpass", string=string) except Exception: # failed to get password copyable, password = False, None else: copyable = True if not password: password = None else: # password is in format 1XXXXXX password = password[1:] password = int(password) if password.isdigit() else None desc = Coder.do_base64( data.get(Index.LEVEL_DESCRIPTION, ""), encode=False, errors="replace" ) level_data = data.get(Index.LEVEL_DATA, "") try: level_data = Coder.unzip(level_data) except Exception: # conversion failed pass diff = data.getcast(Index.LEVEL_DIFFICULTY, 0, int) demon_diff = data.getcast(Index.LEVEL_DEMON_DIFFICULTY, 0, int) is_demon = bool(data.getcast(Index.LEVEL_IS_DEMON, 0, int)) is_auto = bool(data.getcast(Index.LEVEL_IS_AUTO, 0, int)) difficulty = Converter.convert_level_difficulty( diff=diff, demon_diff=demon_diff, is_demon=is_demon, is_auto=is_auto ) return cls( id=data.getcast(Index.LEVEL_ID, 0, int), name=data.get(Index.LEVEL_NAME, "unknown"), description=desc, version=data.getcast(Index.LEVEL_VERSION, 0, int), creator=creator, song=song, data=level_data, password=password, copyable=copyable, is_demon=is_demon, is_auto=is_auto, low_detail_mode=bool(data.get(Index.LEVEL_HAS_LDM)), difficulty=difficulty, stars=data.getcast(Index.LEVEL_STARS, 0, int), coins=data.getcast(Index.LEVEL_COIN_COUNT, 0, int), verified_coins=bool(data.getcast(Index.LEVEL_COIN_VERIFIED, 0, int)), is_epic=bool(data.getcast(Index.LEVEL_IS_EPIC, 0, int)), original=data.getcast(Index.LEVEL_ORIGINAL, 0, int), downloads=data.getcast(Index.LEVEL_DOWNLOADS, 0, int), rating=data.getcast(Index.LEVEL_LIKES, 0, int), score=data.getcast(Index.LEVEL_FEATURED_SCORE, 0, int), uploaded_timestamp=data.get(Index.LEVEL_UPLOADED_TIMESTAMP, "unknown"), last_updated_timestamp=data.get(Index.LEVEL_LAST_UPDATED_TIMESTAMP, "unknown"), length=LevelLength.from_value(data.getcast(Index.LEVEL_LENGTH, 0, int), "XL"), game_version=data.getcast(Index.LEVEL_GAME_VERSION, 0, int), stars_requested=data.getcast(Index.LEVEL_REQUESTED_STARS, 0, int), object_count=data.getcast(Index.LEVEL_OBJECT_COUNT, 0, int), type=TimelyType.from_value(data.getcast(Index.LEVEL_TIMELY_TYPE, 0, int), 0), time_n=data.getcast(Index.LEVEL_TIMELY_INDEX, -1, int), cooldown=data.getcast(Index.LEVEL_TIMELY_COOLDOWN, -1, int), client=client, )
def _b64_failsafe(string: str, encode: bool = True) -> str: try: return Coder.do_base64(string, encode=encode) except Exception: return string
async def upload_level( self, data: str, name: str, level_id: int, version: int, length: LevelLength, audio_track: int, desc: str, song_id: int, is_auto: bool, original: int, two_player: bool, objects: int, coins: int, stars: int, unlisted: bool, ldm: bool, password: Optional[Union[int, str]], copyable: bool, *, client: Client, ) -> int: data = Coder.zip(data) extra_string = "_".join(map(str, (0 for _ in range(55)))) desc = Coder.do_base64(desc) upload_seed = Coder.gen_level_upload_seed(data) seed2 = Coder.gen_chk(type="level", values=[upload_seed]) seed = Coder.gen_rs() pwd = 0 if copyable and password is None: pwd = 1 check, add = str(password), 1000000 if check.isdigit() and int(check) < add: pwd = add + int(password) payload = ( Params() .create_new() .put_definer("accountid", client.account_id) .put_definer("levelid", level_id) .put_definer("song", song_id) .put_seed(seed) .put_seed(seed2, suffix=2) .put_seed(0, prefix="wt") .put_seed(0, prefix="wt", suffix=2) .put_password(client.encodedpass) .put_username(client.name) .finish() ) options = { "level_name": name, "level_desc": desc, "level_version": version, "level_length": length.value, "audio_track": audio_track, "auto": int(is_auto), "original": int(original), "two_player": int(two_player), "objects": objects, "coins": coins, "requested_stars": stars, "unlisted": int(unlisted), "ldm": int(ldm), "password": pwd, "level_string": data, "extra_string": extra_string, "level_info": "H4sIAAAAAAAAC_NIrVQoyUgtStVRCMpPSi0qUbDStwYAsgpl1RUAAAA=", } payload_cased = { Converter.snake_to_camel(key): str(value) for key, value in options.items() } payload.update(payload_cased) level_id = await self.http.request(Route.UPLOAD_LEVEL, payload) if level_id == -1: raise MissingAccess("Failed to upload a level.") return level_id