def from_data(cls, data: ExtDict, client: Client) -> Gauntlet: try: level_ids = tuple(map(int, data.get(Index.GAUNTLET_LEVEL_IDS, "").split(","))) except ValueError: level_ids = () gid = data.getcast(Index.GAUNTLET_ID, 0, int) name = Converter.get_gauntlet_name(gid) return cls(id=gid, name=name, level_ids=level_ids, client=client)
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, )
def from_data(cls, data: ExtDict, type: str = "normal", *, client: Client) -> AbstractUser: return cls( account_id=data.getcast(Index.USER_ACCOUNT_ID, 0, int), id=data.getcast(Index.USER_PLAYER_ID, 0, int), name=data.get(Index.USER_NAME, "unknown"), client=client, )
def from_data(cls, data: ExtDict, client: Client) -> UserStats: return cls( account_id=data.getcast(Index.USER_ACCOUNT_ID, 0, int), name=data.get(Index.USER_NAME, "unknown"), id=data.getcast(Index.USER_PLAYER_ID, 0, int), stars=data.getcast(Index.USER_STARS, 0, int), demons=data.getcast(Index.USER_DEMONS, 0, int), cp=data.getcast(Index.USER_CREATOR_POINTS, 0, int), diamonds=data.getcast(Index.USER_DIAMONDS, 0, int), coins=data.getcast(Index.USER_COINS, 0, int), secret_coins=data.getcast(Index.USER_SECRET_COINS, 0, int), place=data.getcast(Index.USER_TOP_PLACE, 0, int), 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 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, )
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
def to_normal_song(song_id: int, server_style: bool = True) -> ExtDict: if server_style: cases = _cases else: cases = {number + 1: value for number, value in _cases.items()} # get author and name, just like gd does author, name = cases.get(song_id, ("DJVI", "Unknown")) return ExtDict(name=name, author=author, id=song_id, size=0.0, links={}, custom=False)
def extract_info_from_endpoint(text: str) -> ExtDict: artist, whitelisted, scouted, song, api, *_ = filter( is_not_empty, re.split(r"</?br>", text)) return ExtDict( artist=artist.split("Artist: ").pop(), song=song.split("Song: ").pop(), whitelisted=check_not(whitelisted), scouted=check_not(scouted), api=check_not(api), )
def extract_users(text: str) -> List[ExtDict]: tree, result = html_parse(text), [] for a in tree.findall(r'.//div[@class="item-details-main"]/h4/a'): url = URL(a.attrib["href"]).with_scheme("https") name = a.text result.append(ExtDict(link=url, name=name)) return result
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
def from_data(cls, data: ExtDict, strategy: LevelLeaderboardStrategy, client: Client) -> LevelRecord: return cls( account_id=data.getcast(Index.USER_ACCOUNT_ID, 0, int), name=data.get(Index.USER_NAME, "unknown"), id=data.getcast(Index.USER_PLAYER_ID, 0, int), level_id=data.getcast(Index.USER_LEVEL_ID, 0, int), place=data.getcast(Index.USER_TOP_PLACE, 0, int), percentage=data.getcast(Index.USER_PERCENT, 0, int), coins=data.getcast(Index.USER_SECRET_COINS, 0, int), timestamp=data.get(Index.USER_RECORD_TIMESTAMP, "unknown"), type=strategy, client=client, )
def from_data(cls, data: ExtDict, *, custom: bool = True, client: Client) -> Song: return cls( # name and author - cp1252 encoding seems to fix weird characters - Alex1304 name=fix_song_encoding(data.get(Index.SONG_TITLE, "unknown")), author=fix_song_encoding(data.get(Index.SONG_AUTHOR, "unknown")), id=data.getcast(Index.SONG_ID, 0, int), size=data.getcast(Index.SONG_SIZE, 0.0, float), links=dict( normal=Route.NEWGROUNDS_SONG_LISTEN + data.get(Index.SONG_ID, ""), download=unquote(data.get(Index.SONG_URL, "")), ), custom=custom, client=client, )
async def get_ng_song(self, song_id: int = 0) -> ExtDict: # just like get_song(), but gets anything available on NG. link = Route.NEWGROUNDS_SONG_LISTEN + str(song_id) content = await self.http.normal_request(link) html = content.decode().replace("\\", "") try: info = find_song_info(html) except ValueError: raise MissingAccess(f"Song was not found by ID: {song_id}") from None return ExtDict( name=info.name, author=info.author, id=song_id, size=round(info.size / 1024 / 1024, 2), links=dict(normal=link, download=info.link), custom=True, )
def extract_user_songs( json: Dict[str, Dict[str, Dict[str, Union[Dict[str, str], List[str]]]]] ) -> List[ExtDict]: result = [] try: years = json["years"].values() except (TypeError, AttributeError): # not found return result for entry in chain.from_iterable(year["items"] for year in years): tree = html_parse(entry) a = tree.findall(r'.//a[@class="item-link"]')[0] url = URL(a.attrib["href"]).with_scheme("https") song_id = int(url.parts[-1]) name = a.attrib["title"] result.append( ExtDict(id=song_id, name=name, links={"normal": str(url)})) return result
def search_song_data(text: str) -> List[ExtDict]: tree, result = html_parse(text), [] for a, div in zip( tree.findall(r'.//a[@class="item-audiosubmission"]'), tree.findall(r'.//div[@class="detail-title"]'), ): url = URL(a.attrib["href"]).with_scheme("https") song_id = int(url.parts[-1]) h4, span, *_ = div.getchildren() name = switch_if_none(h4.text, "") + "".join( switch_if_none(mark.text, "") + switch_if_none(mark.tail, "") for mark in h4.getchildren()) author = span.getchildren()[0].text result.append( ExtDict(id=song_id, name=name, author=author, links={"normal": str(url)})) return result
def from_data(cls, data: ExtDict, client: Client) -> MapPack: try: level_ids = tuple(map(int, data.get(Index.MAP_PACK_LEVEL_IDS, "").split(","))) except ValueError: level_ids = () color_string = data.get(Index.MAP_PACK_COLOR, "255,255,255") color = Color.from_rgb(*map(int, color_string.split(","))) difficulty = Converter.value_to_pack_difficulty( data.getcast(Index.MAP_PACK_DIFFICULTY, 0, int) ) return cls( id=data.getcast(Index.MAP_PACK_ID, 0, int), name=data.get(Index.MAP_PACK_NAME, "unknown"), level_ids=level_ids, stars=data.getcast(Index.MAP_PACK_STARS, 0, int), coins=data.getcast(Index.MAP_PACK_COINS, 0, int), difficulty=difficulty, color=color, client=client, )
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, )
async def search_levels_on_page( self, page: int = 0, query: str = "", filters: Optional[Filters] = None, user_id: Optional[int] = None, gauntlet: Optional[int] = None, *, exclude: Tuple[Type[BaseException]] = (), client: Client, ) -> Tuple[List[ExtDict], List[ExtDict], List[ExtDict]]: # levels, creators, songs if filters is None: filters = Filters.setup_empty() params = ( Params() .create_new() .put_definer("search", query) .put_page(page) .put_total(0) .put_filters(filters) ) codes = {-1: MissingAccess("No levels were found.")} if filters.strategy == SearchStrategy.BY_USER: if user_id is None: check_logged_obj(client, "search_levels_on_page(...)") user_id = client.id params.put_definer("accountid", client.account_id).put_password(client.encodedpass) params.put_local(1) params.put_definer("search", user_id) # override the 'str' parameter in request elif filters.strategy == SearchStrategy.FRIENDS: check_logged_obj(client, "search_levels_on_page(..., client=client)") params.put_definer("accountid", client.account_id).put_password(client.encodedpass) if gauntlet is not None: params.put_definer("gauntlet", gauntlet) payload = params.finish() resp = await self.http.request( Route.LEVEL_SEARCH, payload, exclude=exclude, error_codes=codes ) if not resp: return [], [], [] resp, parser = resp.split("#"), Parser().with_split("~|~").should_map() lvdata, cdata, sdata = resp[:3] songs = list(map(parser.parse, filter(is_not_empty, sdata.split("~:~")))) creators = [ ExtDict(zip(("id", "name", "account_id"), creator.split(":"))) for creator in filter(is_not_empty, cdata.split("|")) ] parser.with_split(":").add_ext({"101": 0, "102": -1, "103": -1}) levels = list(map(parser.parse, filter(is_not_empty, lvdata.split("|")))) return levels, creators, songs
def parse_dict(self) -> ExtDict: return ExtDict( {k: getattr(self, k) for k in ("name", "id", "account_id")})
def from_data(cls, data: ExtDict, client: Client) -> User: youtube = data.get(Index.USER_YOUTUBE, "") youtube = { "normal": youtube, "link": "https://www.youtube.com/channel/" + youtube } twitter = data.get(Index.USER_TWITTER, "") twitter = {"normal": twitter, "link": "https://twitter.com/" + twitter} twitch = data.get(Index.USER_TWITCH, "") twitch = {"normal": twitch, "link": "https://twitch.tv/" + twitch} return cls( name=data.get(Index.USER_NAME, "unknown"), id=data.getcast(Index.USER_PLAYER_ID, 0, int), stars=data.getcast(Index.USER_STARS, 0, int), demons=data.getcast(Index.USER_DEMONS, 0, int), secret_coins=data.getcast(Index.USER_SECRET_COINS, 0, int), coins=data.getcast(Index.USER_COINS, 0, int), cp=data.getcast(Index.USER_CREATOR_POINTS, 0, int), diamonds=data.getcast(Index.USER_DIAMONDS, 0, int), role=data.getcast(Index.USER_ROLE, 0, int), global_rank=data.getcast(Index.USER_GLOBAL_RANK, None, int), account_id=data.getcast(Index.USER_ACCOUNT_ID, 0, int), youtube=youtube, twitter=twitter, twitch=twitch, message_policy=MessagePolicyType.from_value( data.getcast(Index.USER_PRIVATE_MESSAGE_POLICY, 0, int), 0), friend_request_policy=FriendRequestPolicyType.from_value( data.getcast(Index.USER_FRIEND_REQUEST_POLICY, 0, int), 0), comment_policy=CommentPolicyType.from_value( data.getcast(Index.USER_COMMENT_HISTORY_POLICY, 0, int), 0), icon_setup=IconSet( main_icon=data.getcast(Index.USER_ICON, 1, int), color_1=colors[data.getcast(Index.USER_COLOR_1, 0, int)], color_2=colors[data.getcast(Index.USER_COLOR_2, 0, int)], main_icon_type=IconType.from_value( data.getcast(Index.USER_ICON_TYPE, 0, int), 0), has_glow_outline=bool( data.getcast(Index.USER_GLOW_OUTLINE_2, 0, int)), icon_cube=data.getcast(Index.USER_ICON_CUBE, 1, int), icon_ship=data.getcast(Index.USER_ICON_SHIP, 1, int), icon_ball=data.getcast(Index.USER_ICON_BALL, 1, int), icon_ufo=data.getcast(Index.USER_ICON_UFO, 1, int), icon_wave=data.getcast(Index.USER_ICON_WAVE, 1, int), icon_robot=data.getcast(Index.USER_ICON_ROBOT, 1, int), icon_spider=data.getcast(Index.USER_ICON_SPIDER, 1, int), icon_explosion=data.getcast(Index.USER_EXPLOSION, 1, int), client=client, ), client=client, )