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
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 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 parse_dict(self) -> ExtDict: return ExtDict( {k: getattr(self, k) for k in ("name", "id", "account_id")})
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