def test_image(self): self.assertEqual(Song(image_url="image").image, "image") self.assertEqual( Song(image_url="image", thumbnail="thumb").image, "image" ) self.assertEqual(Song(thumbnail="thumb").image, "thumb") self.assertEqual(Song().image, None)
async def youtube_playlist(self, url: str) -> List[Song]: """ Extract information from YouTube by Playlist url @param url: @return: """ url = VariableStore.youtube_url_to_id(url) node: Node = self.node_controller.get_best_node() response = await node.client.request("youtube_playlist", url, response=True, timeout=10) if not response.successful: raise PlaylistExtractionException() songs = [] for track in json.loads(response.text): song = Song() song.title = track["title"] song.link = track["link"] songs.append(song) return songs
def test_song_copy(self): """ Test Song Copy @return: """ a = Song(title="Test1", image_url="fest1", duration=1) b = Song(image_url="kalb53") c = Song.copy_song(a, b) self.assertEqual(c.title, "Test1") self.assertEqual(c.image_url, "kalb53") self.assertEqual(c.duration, 1)
async def youtube_url(self, url, guild_id: int) -> Song: """ Extract information from YouTube by url @param url: @param guild_id: @return: """ url = VariableStore.youtube_url_to_id(url) node: Node = self.node_controller.get_best_node(guild_id) response = await node.client.request("youtube_video", url, response=True, timeout=10) if not response.successful: LOG.warning(f"[YT-URL] {url} {response.text}") raise SongExtractionException() song_dict: dict = json.loads(response.text) if song_dict == {}: raise NoResultsFound(Errors.no_results_found) song: Song = Song.from_dict(song_dict) return song
async def spotify_album(self, album_url) -> List[Song]: """ Retrieves an album from Spotify. @param album_url: @return: """ token = await self._request_token() album = SpotifyType(album_url) if not album.valid: raise PlaylistExtractionException() url = "https://api.spotify.com/v1/albums/" + album.id + "?limit=50" header = {"Authorization": "Bearer " + token} result = await self._request_get(url, header) json_result = json.loads(result) if "error" in json_result: raise PlaylistExtractionException() track_list = [] for item in json_result["tracks"]["items"]: track_list.append( Song( title=f"{item['artists'][0]['name']} - {item['name']}", artist=item["artists"][0]["name"], image_url=json_result["images"][0]["url"], song_name=item["name"], )) if not track_list: raise PlaylistExtractionException() return track_list
async def spotify_artist(self, artist_url: str) -> List[Song]: """ Retrieves a Spotify Artist @param artist_url: @return: """ token = await self._request_token() artist = SpotifyType(artist_url) if not artist.valid: raise PlaylistExtractionException() url = ("https://api.spotify.com/v1/artists/" + artist.id + "/top-tracks?country=DE") header = {"Authorization": "Bearer " + token} result = await self._request_get(url, header) json_result = json.loads(result) if "error" in json_result: raise PlaylistExtractionException() track_list = [] for item in json_result["tracks"]: track_list.append( Song( title=f"{item['artists'][0]['name']} - {item['name']}", artist=item["artists"][0]["name"], image_url=item["album"]["images"][0]["url"], song_name=item["name"], )) if not track_list: raise PlaylistExtractionException return track_list
async def soundcloud_playlist(self, url: str) -> List[Song]: """ Retrieve a playlist from SoundCloud @param url: @return: """ try: node: Node = self.node_controller.get_best_node() response: karp.response.Response = await node.client.request( "soundcloud_playlist", url, timeout=10, response=True) if not response.successful: self.log.warning(f"[SC-SET] {url} {response.text}") raise PlaylistExtractionException() parsed_response = json.loads(response.text) songs = [] for _song in parsed_response: song: Song = Song() song.link = _song.get("link", None) song.title = _song.get("title", "") if song.link: songs.append(song) return songs except (TimeoutError, AttributeError): self.log.error(traceback.format_exc()) raise PlaylistExtractionException(Errors.default)
async def soundcloud_search(self, song: Song) -> Song: """ Search SoundCloud :param song: :return: """ term = getattr(song, "title", getattr(song, "term", None)) if not term: raise NoResultsFound(Errors.no_results_found) node: Node = self.node_controller.get_best_node(guild_id=song.guild_id) response = await node.client.request("soundcloud_search", term, response=True, timeout=10) if not response.successful: self.log.warning(f"[SC-TERM] {term} {response.text}") raise SongExtractionException() song_dict: dict = json.loads(response.text) song_dict["term"] = term song: Song = Song.from_dict(song_dict) return song
async def youtube_term(self, song: (Song, str)): if isinstance(song, Song): if song.term: term = song.term elif song.title: term = song.title else: return Error(True, Errors.no_results_found) else: term = song node = self.node_controller.get_best_node(guild_id=song.guild_id) url = await self.http_post( self.term_url.format(node.ip, node.port), term ) if isinstance(url, Error): return url url = VariableStore.youtube_url_to_id(url) sd = await self.http_post( url=self.url_url.format(node.ip, node.port), data=url ) if isinstance(sd, Error): return sd song_dict: dict = json.loads(sd) song_dict["term"] = term song: Song = Song.from_dict(song_dict) return song
async def extract_first_infos_youtube(self, url, ctx): youtube_type = Url.determine_youtube_type(url=url) if youtube_type == Url.youtube_url: __song = Song() __song.user = ctx.message.author __song.link = url return [__song] if youtube_type == Url.youtube_playlist: __songs = [] __song_list = await self.parent.youtube.youtube_playlist(url) if len(__song_list) == 0: await self.parent.send_error_message(ctx, Errors.spotify_pull) return [] for track in __song_list: track.user = ctx.message.author __songs.append(track) return __songs
async def preload_song(self, ctx: commands.Context) -> None: """ Preload of the next song. :param ctx: :return: """ try: if self.guilds[ctx.guild.id].song_queue.qsize() == 0: return i = 0 for item in self.guilds[ctx.guild.id].song_queue.queue: item: Song if item.stream: continue backup_title: str = str(item.title) if item.link is not None: try: type_of_source = Url.determine_source(item.link) if type_of_source == Url.youtube_url: youtube_dict = await self.parent.youtube.youtube_url( item.link, ctx.guild.id) elif type_of_source == Url.soundcloud_track: youtube_dict = await self.parent.soundcloud.soundcloud_track( item.link) else: continue except BasicError: self.parent.log( logging_manager.debug_info(traceback.format_exc())) continue youtube_dict.user = item.user else: if item.title: continue try: youtube_dict = await self._search_song(ctx, item) except BasicError: continue youtube_dict.user = item.user j: int = 0 for _song in self.guilds[ctx.guild.id].song_queue.queue: _song: Song if _song.title != backup_title: j += 1 continue self.guilds[ ctx.guild.id].song_queue.queue[j] = Song.copy_song( youtube_dict, self.guilds[ctx.guild.id].song_queue.queue[j], ) break break i += 1 except IndexError: pass except AttributeError: traceback.print_exc()
async def youtube_playlist(self, url): url = VariableStore.youtube_url_to_id(url) node = self.node_controller.get_best_node() sd = await self.http_post( url=self.playlist_url.format(node.ip, node.port), data=url ) if isinstance(sd, Error): return [] songs = [] for t in json.loads(sd): s = Song() s.title = t["title"] s.link = t["link"] songs.append(s) return songs
async def _extract_first_infos_spotify( self, url: str, ctx: commands.Context) -> List[Song]: spotify_type = Url.determine_spotify_type(url=url) __songs = [] __song = Song() __song.user = ctx.message.author if spotify_type == Url.spotify_playlist: song_list = await self.parent.spotify.spotify_playlist(url) return song_list if spotify_type == Url.spotify_track: track = await self.parent.spotify.spotify_track(url) if track: return [track] return [] if spotify_type == Url.spotify_artist: song_list = await self.parent.spotify.spotify_artist(url) return song_list if spotify_type == Url.spotify_album: song_list = await self.parent.spotify.spotify_album(url) return song_list
async def player(self, ctx: commands.Context, small_dict: Song) -> None: """ Plays a Song. @param ctx: @param small_dict: @return: """ try: self.guilds[ctx.guild.id].unlock_queue() if self.guilds[ctx.guild.id].voice_client is None: return try: small_dict.guild_id = ctx.guild.id await self.guilds[ctx.guild.id].voice_client.play(small_dict) self.guilds[ctx.guild.id].voice_client.set_after( self.song_conclusion, ctx) except discord.ClientException: if ctx.guild.voice_client is None: if self.guilds[ctx.guild.id].voice_channel is not None: self.guilds[ ctx.guild.id].voice_client = await self.guilds[ ctx.guild.id ].voice_channel.connect(timeout=10, reconnect=True) small_dict.guild_id = ctx.guild.id await self.guilds[ctx.guild.id ].voice_client.play(small_dict, ) self.guilds[ctx.guild.id].voice_client.set_after( self.song_conclusion, ctx) self.guilds[ctx.guild.id].now_playing = small_dict if self.guilds[ctx.guild.id].announce: if not self.guilds[ctx.guild.id].now_playing_message: self.guilds[ ctx.guild.id].now_playing_message = NowPlayingMessage( self.parent) await self.guilds[ctx.guild.id ].now_playing_message.new_song(ctx=ctx) except (Exception, discord.ClientException) as discord_exception: self.parent.log.debug( logging_manager.debug_info( traceback.format_exc(discord_exception)))
async def tts(self, ctx: commands.Context, *, text: str): quoted = quote(text) if await self.parent.player.join_check(ctx): if await self.parent.player.join_channel(ctx): song: Song = Song.from_dict( { "guild_id": ctx.guild.id, "stream": f"{self.tts_base_url}{quoted}", } ) self.tts_queue.put_nowait(song) if not self.guilds[ctx.guild.id].voice_client.is_playing(): await self.next_tts(ctx)
async def youtube_url(self, url, guild_id: int): url = VariableStore.youtube_url_to_id(url) node = self.node_controller.get_best_node(guild_id) sd = await self.http_post( url=self.url_url.format(node.ip, node.port), data=url ) if isinstance(sd, Error): return sd song_dict: dict = json.loads(sd) if song_dict == {}: return Error(Errors.default) song: Song = Song.from_dict(song_dict) return song
async def preload_song(self, ctx): """ Preload of the next song. :param ctx: :return: """ try: if self.parent.guilds[ctx.guild.id].song_queue.qsize() > 0: i = 0 for item in self.parent.guilds[ctx.guild.id].song_queue.queue: item: Song if item.stream is None: backup_title: str = str(item.title) if item.link is not None: youtube_dict = await self.parent.youtube.youtube_url( item.link, ctx.guild.id) youtube_dict.user = item.user else: if item.title is not None: youtube_dict = await self.parent.youtube.youtube_term( item) else: youtube_dict = await self.parent.youtube.youtube_term( item) youtube_dict.user = item.user j: int = 0 for _song in self.parent.guilds[ ctx.guild.id].song_queue.queue: _song: Song if _song.title == backup_title: self.parent.guilds[ ctx.guild. id].song_queue.queue[j] = Song.copy_song( youtube_dict, self.parent.guilds[ ctx.guild.id].song_queue.queue[j], ) break j -= -1 break i += 1 except IndexError: pass except AttributeError as e: traceback.print_exc()
async def extract_first_infos_other(self, url, ctx): if url == "charts": __songs = [] __song = Song() __song.user = ctx.message.author song_list = await self.extract_first_infos_spotify( "https://open.spotify.com/playlist/37i9dQZEVXbMDoHDwVN2tF?si=vgYiEOfYTL-ejBdn0A_E2g", ctx, ) for track in song_list: track.user = ctx.message.author __songs.append(track) return __songs __song = Song() __song.title = url __song.user = ctx.message.author return [__song]
async def youtube_term(self, song: (Song, str), service: str) -> Song: """ Extract information from YouTube by Term @param song: @param service: @return: """ term = getattr(song, "title", getattr(song, "term", None)) if not term: raise NoResultsFound(Errors.no_results_found) node: Node = self.node_controller.get_best_node(guild_id=song.guild_id) response = await node.client.request( "youtube_search", json.dumps({ "service": service, "term": term }), response=True, timeout=10, ) if not response.successful: raise NoResultsFound(response.text) url = response.text url = VariableStore.youtube_url_to_id(url) response = await node.client.request("youtube_video", url, response=True, timeout=10) if not response.successful: LOG.warning(f"[YT-TERM] {url} {response.text}") raise SongExtractionException() song_dict: dict = json.loads(response.text) song_dict["term"] = term song: Song = Song.from_dict(song_dict) return song
async def soundcloud_playlist(self, url: str): try: async with async_timeout.timeout(timeout=10): async with aiohttp.request( "POST", "http://parent:8008/research/soundcloud_playlist", data=url, ) as r: response = await r.text() if r.status != 200: return Error(True, response) parsed_response = json.loads(response) songs = [] for s in parsed_response: song: Song = Song() song.link = s.get("link", None) if song.link: songs.append(song) return songs except (TimeoutError, AttributeError) as e: self.log.error(e) return Error(True)
async def soundcloud_track(self, url: str) -> Song: """ Retrieve Track information from SoundCloud @param url: @return: """ try: node: Node = self.node_controller.get_best_node() response: karp.response.Response = await node.client.request( "soundcloud_track", url, response=True, timeout=10) if not response.successful: self.log.warning(f"[SC-TRK] {url} {response.text}") raise SongExtractionException() response: dict = json.loads(response.text) song: Song = Song( title=response.get("title", None), term=response.get("term", ""), link=response.get("link", ""), stream=response.get("stream", None), duration=response.get("duration", 0), loadtime=response.get("loadtime", 0), thumbnail=response.get("thumbnail", ""), abr=response.get("abr", None), codec=response.get("codec", ""), ) if song.duration == 0: # try to determine the songs length by content length if song.abr: abr = song.abr * 1000 / 8 async with aiohttp.request("HEAD", song.stream) as _r: content_length = _r.headers.get("Content-Length", "") try: song.duration = int(content_length) / abr except ValueError: pass return song except (TimeoutError, AttributeError) as thrown_exception: self.log.error(traceback.format_exc(thrown_exception)) raise SongExtractionException()
async def spotify_track(self, track_url) -> Song: """ Extract Information from a Spotify Track url. @param track_url: Spotify Track Url @return: Spotify Song @rtype: Song """ token = await self._request_token() track = SpotifyType(track_url) if not track.valid: raise SongExtractionException() url = "https://api.spotify.com/v1/tracks/" + track.id header = {"Authorization": "Bearer " + token} result = await self._request_get(url, header) result = json.loads(result) if "error" in result: raise SongExtractionException() return Song( title=result["artists"][0]["name"] + " - " + result["name"], image_url=result["album"]["images"][0]["url"], song_name=result["name"], artist=result["artists"][0]["name"], )
async def soundcloud_track(self, url: str): node = self.node_controller.get_best_node() try: async with async_timeout.timeout(timeout=10): async with self.client.post( f"http://{node.ip}:{node.port}/research/soundcloud_track", data=url, ) as r: if r.status != 200: return Error(True) response: dict = json.loads(await r.text()) song: Song = Song( title=response.get("title", None), term=response.get("term", ""), link=response.get("link", ""), stream=response.get("stream", None), duration=response.get("duration", 0), loadtime=response.get("loadtime", 0), thumbnail=response.get("thumbnail", ""), abr=response.get("abr", None), codec=response.get("codec", ""), ) if song.duration == 0: # try to determine the songs length by content length if song.abr: abr = song.abr * 1000 / 8 async with aiohttp.request("HEAD", song.stream) as _r: cl = _r.headers.get("Content-Length", "") try: song.duration = int(cl) / abr except ValueError: pass return song except (TimeoutError, AttributeError) as e: self.log.error(e) return Error(True)
async def pre_player(self, ctx, bypass=None): if (self.parent.guilds[ctx.guild.id].song_queue.qsize() > 0 or bypass is not None): if bypass is None: small_dict = await self.parent.guilds[ctx.guild.id ].song_queue.get() else: small_dict = bypass self.parent.guilds[ ctx.guild.id].now_playing_message = NowPlayingMessage( message=await self.parent.send_embed_message(ctx=ctx, message=" Loading ... ", delete_after=None), ctx=ctx, ) if small_dict.stream is None: if small_dict.link is not None: # url _type = Url.determine_source(small_dict.link) if _type == Url.youtube: youtube_dict = Song.copy_song( await self.parent.youtube.youtube_url( small_dict.link, ctx.guild.id), small_dict, ) elif _type == Url.soundcloud: youtube_dict = Song.copy_song( await self.parent.soundcloud.soundcloud_track( small_dict.link), small_dict, ) else: self.parent.log.warning("Incompatible Song Type: " + _type) return else: if small_dict.title is None: self.parent.log.warning(small_dict) # term youtube_dict = Song.copy_song( await self.parent.youtube.youtube_term(small_dict), small_dict, ) if isinstance(youtube_dict, Error): if youtube_dict.reason != Errors.error_please_retry: await self.parent.send_error_message( ctx, youtube_dict.reason) await self.parent.guilds[ ctx.guild.id].now_playing_message.message.delete() await self.pre_player(ctx) return await self.parent.guilds[ ctx.guild.id].now_playing_message.message.delete() await self.pre_player(ctx, bypass=small_dict) return youtube_dict.user = small_dict.user youtube_dict.image_url = small_dict.image_url await self.player(ctx, youtube_dict) if hasattr(youtube_dict, "title"): asyncio.ensure_future( self.parent.mongo.append_most_played( youtube_dict.title)) if hasattr(youtube_dict, "loadtime"): asyncio.ensure_future( self.parent.mongo.append_response_time( youtube_dict.loadtime)) else: await self.player(ctx, small_dict) if hasattr(small_dict, "title"): asyncio.ensure_future( self.parent.mongo.append_most_played(small_dict.title)) if hasattr(small_dict, "loadtime"): asyncio.ensure_future( self.parent.mongo.append_response_time( small_dict.loadtime)) asyncio.ensure_future(self.preload_song(ctx=ctx))
def test_song_from_dict(self): self.assertEqual( str(Song.from_dict({"title": "hello", "duration": 10})), str(Song(title="hello", duration=10)), )
def test_song_copy_b(self): a = Song(title="2", duration=10, artist="jeff") self.assertEqual(str(Song(a)), str(a))
async def extract_first_infos_spotify(self, url, ctx): spotify_type = Url.determine_spotify_type(url=url) __songs = [] __song = Song() __song.user = ctx.message.author if spotify_type == Url.spotify_playlist: __song_list = await self.parent.spotify.spotify_playlist(url) if len(__song_list) == 0: await self.parent.send_error_message( ctx=ctx, message=Errors.spotify_pull) return [] for track in __song_list: track: SpotifySong __song = Song(song=__song) __song.title = track.title __song.image_url = track.image_url __song.artist = track.artist __song.song_name = track.song_name __songs.append(__song) return __songs if spotify_type == Url.spotify_track: track = await self.parent.spotify.spotify_track(url) if track is not None: __song.title = track.title __song.image_url = track.image_url __song.artist = track.artist __song.song_name = track.song_name return [__song] return [] if spotify_type == Url.spotify_artist: song_list = await self.parent.spotify.spotify_artist(url) for track in song_list: __song = Song(song=__song) __song.title = track __songs.append(__song) return __songs if spotify_type == Url.spotify_album: song_list = await self.parent.spotify.spotify_album(url) for track in song_list: __song = Song(song=__song) __song.title = track __songs.append(__song) return __songs
async def player(self, ctx, small_dict): if isinstance(small_dict, Error): error_message = small_dict.reason await self.parent.send_error_message(ctx, error_message) if error_message in (Errors.no_results_found, Errors.default): await self.parent.guilds[ ctx.guild.id].now_playing_message.message.delete() return small_dict = Song.copy_song( await self.parent.youtube.youtube_url(small_dict.link, ctx.guild.id), small_dict, ) if isinstance(small_dict, Error): self.parent.log.error(small_dict.reason) await self.parent.send_error_message(ctx, small_dict.reason) return try: self.parent.guilds[ctx.guild.id].now_playing = small_dict if self.parent.guilds[ctx.guild.id].voice_client is None: return volume = getattr( self.parent.guilds[ctx.guild.id], "volume", await self.parent.mongo.get_volume(ctx.guild.id), ) if small_dict.codec == "opus": self.parent.log.debug("Using OPUS Audio.") # source = await FFmpegOpusAudioB.from_probe( # small_dict.stream, # volume=volume, # before_options="-reconnect 1 -reconnect_streamed 1 -reconnect_delay_max 5", # commented, because of spamming messages of failed reconnects. # ) else: # only used for backup soundcloud atm self.parent.log.debug("Using PCM Audio.") # source = PCMVolumeTransformerB( # FFmpegPCMAudioB( # small_dict.stream, # before_options="-reconnect 1 -reconnect_streamed 1 -reconnect_delay_max 5", # ), # volume=volume, # ) try: small_dict.guild_id = ctx.guild.id self.parent.guilds[ctx.guild.id].voice_client.play( small_dict # source, # after=lambda error: self.song_conclusion(ctx, error=error), ) self.parent.guilds[ctx.guild.id].voice_client.set_after( self.song_conclusion, ctx, error=None) except discord.ClientException: if ctx.guild.voice_client is None: if (self.parent.guilds[ctx.guild.id].voice_channel is not None): self.parent.guilds[ ctx.guild. id].voice_client = await self.parent.guilds[ ctx.guild.id ].voice_channel.connect(timeout=10, reconnect=True) small_dict.guild_id = ctx.guild.id self.parent.guilds[ctx.guild.id].voice_client.play( small_dict, # after=lambda error: self.song_conclusion( # ctx, error=error # ), ) self.parent.guilds[ ctx.guild.id].voice_client.set_after( self.song_conclusion, ctx, error=None) full, empty = await self.parent.mongo.get_chars(ctx.guild.id) self.parent.guilds[ ctx.guild.id].now_playing_message = NowPlayingMessage( ctx=ctx, message=self.parent.guilds[ ctx.guild.id].now_playing_message.message, song=self.parent.guilds[ctx.guild.id].now_playing, full=full, empty=empty, discord_music=self.parent, voice_client=self.parent.guilds[ctx.guild.id].voice_client, ) await self.parent.guilds[ctx.guild.id].now_playing_message.send() except (Exception, discord.ClientException) as e: self.parent.log.debug( logging_manager.debug_info(traceback.format_exc(e)))
async def pre_player(self, ctx: commands.Context, bypass=None) -> None: """ Routine called before a song gets played. @param ctx: @param bypass: @return: """ guild_id = ctx.guild.id if self.guilds[guild_id].song_queue.qsize() > 0 or bypass is not None: if bypass is None: small_dict = await self.guilds[ctx.guild.id].song_queue.get() else: small_dict = bypass if small_dict.stream is None: try: if small_dict.link is not None: # url _type = Url.determine_source(small_dict.link) if _type == Url.youtube: youtube_dict = Song.copy_song( await self.parent.youtube.youtube_url( small_dict.link, ctx.guild.id), small_dict, ) elif _type == Url.soundcloud: youtube_dict = Song.copy_song( await self.parent.soundcloud.soundcloud_track( small_dict.link), small_dict, ) else: self.parent.log.warning( f"Incompatible Song Type: {_type}") return else: if small_dict.title is None: self.parent.log.warning(small_dict) # term youtube_dict = Song.copy_song( await self._search_song(ctx, small_dict), small_dict) except NoResultsFound: await self.parent.send_error_message( ctx, Errors.no_results_found) self.guilds[guild_id].unlock_queue() await self.pre_player(ctx) return except SongExtractionException: await self.parent.send_error_message( ctx, Errors.youtube_video_not_available) self.guilds[guild_id].unlock_queue() await self.pre_player(ctx) return except (BasicError, asyncio.TimeoutError) as basic_error: if str(basic_error) != Errors.error_please_retry: await self.parent.send_error_message( ctx, str(basic_error)) self.guilds[guild_id].unlock_queue() await self.pre_player(ctx) return return await self.pre_player(ctx, bypass=small_dict) youtube_dict.user = small_dict.user youtube_dict.image_url = small_dict.image_url await self.player(ctx, youtube_dict) # add stats to website if hasattr(youtube_dict, "title"): asyncio.ensure_future( self.parent.mongo.append_most_played( youtube_dict.title)) if hasattr(youtube_dict, "loadtime"): asyncio.ensure_future( self.parent.mongo.append_response_time( youtube_dict.loadtime)) else: await self.player(ctx, small_dict) if hasattr(small_dict, "title"): asyncio.ensure_future( self.parent.mongo.append_most_played(small_dict.title)) if hasattr(small_dict, "loadtime"): asyncio.ensure_future( self.parent.mongo.append_response_time( small_dict.loadtime)) asyncio.ensure_future(self.preload_song(ctx=ctx))
async def spotify_playlist(self, playlist_url) -> List[Song]: """ Extracts all songs from a spotify playlist. @param playlist_url: Spotify Playlist Url @return: List of Songs @rtype: List[Song] """ token = await self._request_token() playlist = SpotifyType(playlist_url) if not playlist.valid: raise PlaylistExtractionException() url = ("https://api.spotify.com/v1/playlists/" + playlist.id + "/tracks?limit=100&offset=0") header = {"Authorization": "Bearer " + token} result = await self._request_get(url, header) json_result = json.loads(result) if "error" in json_result: raise PlaylistExtractionException() t_list = [] more = True while more is True: try: for track in json_result["items"]: if track["is_local"]: try: t_list.append( Song( title=track["track"]["artists"][0]["name"] + " - " + track["track"]["name"], image_url=None, song_name=track["track"]["name"], artist=track["track"]["artists"][0] ["name"], )) except ( IndexError, KeyError, TypeError, ): # pragma: no cover # Probably invalid local file continue else: try: t_list.append( Song( title=track["track"]["album"]["artists"][0] ["name"] + " - " + track["track"]["name"], image_url=track["track"]["album"]["images"] [0]["url"], song_name=track["track"]["name"], artist=track["track"]["artists"][0] ["name"], )) except ( IndexError, KeyError, TypeError, ): # catch preemtively to prevent # no-response pass if json_result["next"] is None: more = False else: url = json_result["next"] result = await self._request_get(url, header) json_result = json.loads(result) except KeyError as key_error: self.log.warning( logging_manager.debug_info( str(key_error) + " " + str(json_result))) if "error" in json_result: self.token = "" more = False if not t_list: raise PlaylistExtractionException() return t_list