async def get_user(self, account_id: int = 0, return_only_stats: bool = False) -> ExtDict: payload = Params().create_new().put_definer("user", account_id).finish() codes = {-1: MissingAccess(f"No users were found with ID: {account_id}.")} resp = await self.http.request(Route.GET_USER_INFO, payload, error_codes=codes) mapped = Parser().with_split(":").should_map().parse(resp) if return_only_stats: return mapped another = ( Params() .create_new() .put_definer("search", mapped.getcast(Index.USER_PLAYER_ID, 0, int)) .put_total(0) .put_page(0) .finish() ) some_resp = await self.http.request(Route.USER_SEARCH, another) new_resp = ( Parser().split("#").take(0).check_empty().split(":").should_map().parse(some_resp) ) if new_resp is None: raise codes.get(-1) mapped.update( {k: new_resp.get(k) for k in (Index.USER_NAME, Index.USER_ICON, Index.USER_ICON_TYPE)} ) return mapped
async def search_user(self, query: Union[int, str], return_abstract: bool = False) -> ExtDict: payload = ( Params().create_new().put_definer("search", query).put_total(0).put_page(0).finish() ) codes = {-1: MissingAccess(f"Searching for {query!r} failed.")} resp = await self.http.request(Route.USER_SEARCH, payload, error_codes=codes) mapped = Parser().split("#").take(0).check_empty().split(":").should_map().parse(resp) if mapped is None: raise codes.get(-1) account_id = mapped.getcast(Index.USER_ACCOUNT_ID, 0, int) if return_abstract or not account_id: return mapped # ok; if we should not return abstract, let's find all other parameters payload = Params().create_new().put_definer("user", account_id).finish() resp = await self.http.request(Route.GET_USER_INFO, payload, error_codes=codes) mapped.update(Parser().with_split(":").should_map().parse(resp)) return mapped
async def get_level_info(self, level_id: int = 0) -> Tuple[ExtDict, ExtDict, ExtDict]: # level data, creator, song assert level_id >= -2, "Invalid Level ID provided." if level_id < 0: type, number, cooldown = await self.get_timely_info(level_id) else: type, number, cooldown = 0, -1, -1 ext = {"101": type, "102": number, "103": cooldown} codes = {-1: MissingAccess(f"Failed to get a level. Given ID: {level_id}")} payload = Params().create_new().put_definer("levelid", level_id).finish() resp = await self.http.request(Route.DOWNLOAD_LEVEL, payload, error_codes=codes) level_data = Parser().split("#").take(0).split(":").add_ext(ext).should_map().parse(resp) real_id = level_data.getcast(Index.LEVEL_ID, 0, int) payload = ( Params() .create_new() .put_definer("search", real_id) .put_filters(Filters.setup_empty()) .finish() ) resp = await self.http.request(Route.LEVEL_SEARCH, payload, error_codes=codes) if not resp or resp.count("#") < 2: raise codes.get(-1) data = resp.split("#") # getting song song_data = data[2] if not song_data: song = Converter.to_normal_song(level_data.getcast(Index.LEVEL_AUDIO_TRACK, 0, int)) else: song = Parser().with_split("~|~").should_map().parse(song_data) # getting creator creator_data = data[1] if not creator_data: id, name, account_id = (0, "unknown", 0) else: id, name, account_id = creator_data.split(":") creator = ExtDict(id=id, name=name, account_id=account_id) return level_data, creator, song
async def get_user_list( self, type: int = 0, *, exclude: Tuple[Type[BaseException]] = (), client: Client ) -> List[ExtDict]: payload = ( Params() .create_new() .put_definer("accountid", client.account_id) .put_password(client.encodedpass) .put_type(type) .finish() ) codes = { -1: MissingAccess("Failed to fetch a user list."), -2: NothingFound("gd.AbstractUser"), } resp = await self.http.request( Route.GET_USER_LIST, payload, error_codes=codes, exclude=exclude ) if resp is None: return [] resp, parser = resp.split("|"), Parser().with_split(":").should_map() return list(map(parser.parse, resp))
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}.")
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 update_settings( self, message_policy: MessagePolicyType, friend_request_policy: FriendRequestPolicyType, comment_policy: CommentPolicyType, youtube: str, twitter: str, twitch: str, *, client: Client, ) -> None: payload = ( Params() .create_new("web") .put_definer("accountid", client.account_id) .put_password(client.encodedpass) .put_profile_upd( message_policy.value, friend_request_policy.value, comment_policy.value, youtube, twitter, twitch, ) .finish_login() ) resp = await self.http.request(Route.UPDATE_ACC_SETTINGS, payload) if resp != 1: raise MissingAccess(f"Failed to update profile settings of a client: {client!r}.")
async def load_save(self, client: Client) -> Tuple[Optional[api.Database], Optional[Save]]: link = Route.GD_URL payload = ( Params() .create_new() .put_username(client.name) .put_definer("password", client.password) .finish_login() ) codes = {-11: MissingAccess(f"Failed to load data for client: {client!r}.")} resp = await self.http.request( Route.LOAD_DATA, payload, error_codes=codes, custom_base=link ) try: main, levels, *_ = resp.split(";") db = await api.save.from_string_async(main, levels, xor=False, follow_os=False) save = await SaveParser.aio_parse(db.main.dump()) return db, save except Exception: return None, None
async def get_page_messages( self, sent_or_inbox: str, page: int, *, exclude: Tuple[Type[BaseException]] = (), client: Client, ) -> List[ExtDict]: assert sent_or_inbox in ("inbox", "sent") inbox = 0 if sent_or_inbox != "sent" else 1 payload = ( Params() .create_new() .put_definer("accountid", client.account_id) .put_password(client.encodedpass) .put_page(page) .put_total(0) .get_sent(inbox) .finish() ) codes = {-1: MissingAccess("Failed to get messages."), -2: NothingFound("gd.Message")} resp = await self.http.request( Route.GET_PRIVATE_MESSAGES, payload, error_codes=codes, exclude=exclude ) resp = Parser().split("#").take(0).check_empty().split("|").parse(resp) if resp is None: return [] parser = Parser().with_split(":").should_map() return list(map(parser.parse, resp))
async def get_song(self, song_id: int = 0) -> ExtDict: payload = Params().create_new().put_definer("song", song_id).finish() codes = { -1: MissingAccess(f"No songs were found with ID: {song_id}."), -2: SongRestrictedForUsage(song_id), } resp = await self.http.request(Route.GET_SONG_INFO, payload, error_codes=codes) return Parser().with_split("~|~").should_map().parse(resp)
async def get_gauntlets(self) -> List[ExtDict]: payload = Params().create_new().finish() resp = await self.http.request(Route.GET_GAUNTLETS, payload) splitted = Parser().split("#").take(0).split("|").parse(resp) parser = Parser().with_split(":").should_map() return list(map(parser.parse, filter(is_not_empty, splitted)))
async def unfriend_user(self, account_id: int, *, client: Client) -> None: payload = ( Params() .create_new() .put_definer("accountid", client.account_id) .put_password(client.encodedpass) .put_definer("user", account_id) .finish() ) resp = await self.http.request(Route.REMOVE_FRIEND, payload) if resp != 1: raise MissingAccess(f"Failed to unfriend a user by Account ID: {account_id!r}.")
async def read_friend_request(self, request_id: int, client: Client) -> None: payload = ( Params() .create_new() .put_definer("accountid", client.account_id) .put_password(client.encodedpass) .put_definer("requestid", request_id) .finish() ) resp = await self.http.request(Route.READ_REQUEST, payload) if resp != 1: raise MissingAccess(f"Failed to read a friend request by ID: {request_id!r}.")
async def delete_level(self, level_id: int, *, client: Client) -> None: payload = ( Params() .create_new() .put_definer("accountid", client.account_id) .put_definer("levelid", level_id) .put_password(client.encodedpass) .finish_level() ) resp = await self.http.request(Route.DELETE_LEVEL, payload) if resp != 1: raise MissingAccess(f"Failed to delete a level by ID: {level_id}.")
async def get_leaderboard( self, level_id: int, strategy: LevelLeaderboardStrategy, *, client: Client ) -> List[ExtDict]: # timely_type: TimelyType, played: bool = False, timely_index: int = 0, percentage: int = 0, # jumps: int = 0, attempts: int = 0, seconds: int = 0, coins: int = 0 # rs = Coder.gen_rs() # seed = Coder.gen_level_lb_seed(jumps, percentage, seconds, played) # if str(timely_type) == 'weekly': # timely_index += 100000 # values = [ # client.account_id, level_id, percentage, seconds, jumps, attempts, # percentage, 100 - percentage, 1, coins, timely_index, rs # ] # chk = Coder.gen_chk(type='levelscore', values=values) params = ( Params() .create_new() .put_definer("accountid", client.account_id) .put_definer("levelid", level_id) .put_password(client.encodedpass) .put_type(strategy.value) ) # params.put_percent(percentage).put_chk(chk) # for index, value in enumerate(( # attempts + 8354, jumps + 3991, seconds + 4085, seed, random.randint(100, 10000), # "", rs, attempts, coins + 5819, timely_index # ), 1): # params.put_seed(value, prefix='s', suffix=index) payload = params.finish() codes = {-1: MissingAccess(f"Failed to get leaderboard of the level by ID: {level_id!r}.")} resp = await self.http.request(Route.GET_LEVEL_SCORES, payload, error_codes=codes) if not resp: return [] resp, parser = ( resp.split("|"), Parser().with_split(":").add_ext({"101": level_id}).should_map(), ) return list(map(parser.parse, filter(is_not_empty, resp)))
async def retrieve_page_comments( self, account_id: int, id: int, type: str = "profile", page: int = 0, *, strategy: CommentStrategy, exclude: Tuple[Type[BaseException]] = (), ) -> List[ExtDict]: assert isinstance(page, int) and page >= 0 assert type in ("profile", "level") is_level = type == "level" typeid = is_level ^ 1 definer = "userid" if is_level else "accountid" selfid = id if is_level else account_id route = Route.GET_COMMENT_HISTORY if is_level else Route.GET_ACC_COMMENTS parser = Parser().add_ext({"101": typeid}).should_map() if is_level: parser.split(":").take(0).split("~") else: parser.with_split("~") param_obj = Params().create_new().put_definer(definer, selfid).put_page(page).put_total(0) if is_level: param_obj.put_mode(strategy.value) payload = param_obj.finish() codes = { -1: MissingAccess(f"Failed to retrieve comment for user by Account ID: {account_id!r}.") } resp = await self.http.request(route, payload, error_codes=codes, exclude=exclude) if not resp: return [] splitted = resp.split("#").pop(0) if not splitted: if issubclass(NothingFound, exclude): return [] raise NothingFound("gd.Comment") return list(map(parser.parse, filter(is_not_empty, splitted.split("|"))))
async def login(self, user: str, password: str) -> Tuple[int, int]: # account_id, id payload = ( Params().create_new().put_login_definer(username=user, password=password).finish_login() ) codes = { -1: LoginFailure(login=user, password=password), -12: MissingAccess(f"Account {user!r} (password {password!r}) is disabled."), } resp = await self.http.request(Route.LOGIN, payload, error_codes=codes) account_id, id, *junk = resp.split(",") return int(account_id), int(id)
async def update_level_desc(self, level_id: int, content: str, *, client: Client) -> None: payload = ( Params() .create_new() .put_definer("accountid", client.account_id) .put_password(client.encodedpass) .put_definer("levelid", level_id) .put_level_desc(content) .finish() ) resp = await self.http.request(Route.UPDATE_LEVEL_DESC, payload) if resp != 1: raise MissingAccess(f"Failed to update description of the level by ID: {level_id}.")
async def test_song(self, song_id: int = 0) -> ExtDict: codes = {-1: MissingAccess(f"Failed to fetch artist info for ID: {song_id}")} payload = Params().create_new("web").put_definer("song", song_id).close() resp = await self.http.request( Route.TEST_SONG, params=payload, method="get", error_codes=codes ) data = ExtDict(id=song_id) try: data.update(extract_info_from_endpoint(resp)) except ValueError: raise MissingAccess(f"Failed to load data. Response: {resp!r}.") from None return data
async def get_level_comments( self, level_id: int, strategy: CommentStrategy, amount: int, exclude: Tuple[Type[BaseException]] = (), ) -> List[Tuple[ExtDict, ExtDict]]: # comment, user payload = ( Params() .create_new() .put_definer("levelid", level_id) .put_page(0) .put_total(0) .put_mode(strategy.value) .put_count(amount) .finish() ) codes = { -1: MissingAccess(f"Failed to get comments of a level by ID: {level_id!r}."), -2: NothingFound("gd.Comment"), } resp = await self.http.request( Route.GET_COMMENTS, payload, error_codes=codes, exclude=exclude ) if resp is None: return [] splitted = Parser().split("#").take(0).split("|").parse(resp) parser = Parser().with_split("~").should_map() res = [] for elem in filter(is_not_empty, splitted): com_data, user_data, *_ = map(parser.parse, elem.split(":")) com_data.update({"1": level_id, "101": 0, "102": 0}) user_data = ExtDict( account_id=user_data.getcast(Index.USER_ACCOUNT_ID, 0, int), id=com_data.getcast(Index.COMMENT_AUTHOR_ID, 0, int), name=user_data.get(Index.USER_NAME, "unknown"), ) res.append((com_data, user_data)) return res
async def delete_message( self, typeof: MessageOrRequestType, message_id: int, client: Client ) -> None: payload = ( Params() .create_new() .put_definer("accountid", client.account_id) .put_definer("messageid", message_id) .put_password(client.encodedpass) .put_is_sender(typeof.name.lower()) .finish() ) resp = await self.http.request(Route.DELETE_PRIVATE_MESSAGE, payload) if resp != 1: raise MissingAccess(f"Failed to delete a message by ID: {message_id!r}.")
async def post_comment(self, content: str, *, client: Client) -> None: to_gen = [client.name, 0, 0, 1] payload = ( Params() .create_new() .put_definer("accountid", client.account_id) .put_username(client.name) .put_password(client.encodedpass) .put_comment(content, to_gen) .comment_for("profile") .finish() ) codes = {-1: MissingAccess("Failed to post a comment.")} await self.http.request(Route.UPLOAD_ACC_COMMENT, payload, error_codes=codes)
async def block_user(self, account_id: int, unblock: bool = False, *, client: Client) -> None: route = Route.UNBLOCK_USER if unblock else Route.BLOCK_USER payload = ( Params() .create_new() .put_definer("accountid", client.account_id) .put_password(client.encodedpass) .put_definer("user", account_id) .finish() ) resp = await self.http.request(route, payload) if resp != 1: raise MissingAccess( f"Failed to {'un' if unblock else ''}block a user by Account ID: {account_id!r}." )
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}")
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, ""))
async def get_page_map_packs( self, page: int = 0, *, exclude: Tuple[Type[BaseException]] = (), ) -> List[ExtDict]: payload = Params().create_new().put_page(page).finish() resp = await self.http.request(Route.GET_MAP_PACKS, payload) splitted = Parser().split("#").take(0).split("|").check_empty().should_map().parse(resp) if not splitted: if issubclass(NothingFound, exclude): return [] raise NothingFound("gd.MapPack") parser = Parser().with_split(":").should_map() return list(map(parser.parse, splitted))
async def delete_friend_request( self, typeof: MessageOrRequestType, user_id: int, client: Client ) -> None: payload = ( Params() .create_new() .put_definer("accountid", client.account_id) .put_definer("user", user_id) .put_password(client.encodedpass) .put_is_sender(typeof.name.lower()) .finish() ) resp = await self.http.request(Route.DELETE_REQUEST, payload) if resp != 1: raise MissingAccess( f"Failed to delete a friend request by User (with ID): {user_id!r}." )
async def delete_comment( self, typeof: CommentType, comment_id: int, level_id: int, *, client: Client ) -> None: cases = {0: Route.DELETE_LEVEL_COMMENT, 1: Route.DELETE_ACC_COMMENT} route = cases.get(typeof.value) payload = ( Params() .create_new() .put_definer("commentid", comment_id) .put_definer("accountid", client.account_id) .put_password(client.encodedpass) .comment_for(typeof.name.lower(), level_id) .finish() ) resp = await self.http.request(route, payload) if resp != 1: raise MissingAccess(f"Failed to delete a comment by ID: {comment_id!r}.")
async def send_message( self, account_id: int, subject: str, body: str, *, client: Client ) -> None: payload = ( Params() .create_new() .put_definer("accountid", client.account_id) .put_message(subject, body) .put_recipient(account_id) .put_password(client.encodedpass) .finish() ) resp = await self.http.request(Route.SEND_PRIVATE_MESSAGE, payload) if resp != 1: raise MissingAccess( f"Failed to send a message to a user by Account ID: {account_id!r}." )
async def get_timely_info(self, type_id: int = -1) -> Tuple[int, int, int]: # Daily: -1, Weekly: -2 weekly = ~type_id payload = Params().create_new().put_weekly(weekly).finish() codes = {-1: MissingAccess(f"Failed to fetch a {type!r} level.")} resp = await self.http.request(Route.GET_TIMELY, payload, error_codes=codes) try: number, cooldown, *_ = map(int, resp.split("|")) except ValueError: # unpacking failed or something else raise MissingAccess( "Failed to fetch a timely level. Most likely it is being refreshed." ) from None number %= 100000 weekly += 1 return (weekly, number, cooldown)