From e6a1d30026740cb38319877fb8205166aa9d1b76 Mon Sep 17 00:00:00 2001 From: Choco Date: Tue, 8 Aug 2023 13:14:07 +0800 Subject: [PATCH] Optimize code for improved cleanliness and enhanced performance. --- addons/settings.py | 37 +---------- cogs/basic.py | 18 +++--- function.py | 4 +- views/controller.py | 2 +- views/search.py | 2 +- voicelink/objects.py | 116 ++++++++++++++++++++++------------- voicelink/player.py | 2 +- voicelink/pool.py | 2 +- voicelink/queue.py | 12 ++-- voicelink/spotify/client.py | 12 ++-- voicelink/spotify/objects.py | 108 ++++++++++++++++++++------------ 11 files changed, 172 insertions(+), 143 deletions(-) diff --git a/addons/settings.py b/addons/settings.py index 38f5c50..bd18fec 100644 --- a/addons/settings.py +++ b/addons/settings.py @@ -13,42 +13,9 @@ class Settings: self.emoji_source_raw = settings.get("emoji_source_raw", {}) self.cooldowns_settings = settings.get("cooldowns", {}) self.aliases_settings = settings.get("aliases", {}) - self.controller = settings.get("default_controller", - { - "embeds": { - "active": { - "description": "**Now Playing: ```[@@track_name@@]```\nLink: [Click Me](@@track_url@@) | Requester: @@requester@@ | DJ: @@dj@@**", - "footer": { - "text": "Queue Length: @@queue_length@@ | Duration: @@duration@@ | Volume: @@volume@@% {{loop_mode!=Off ?? | Repeat: @@loop_mode@@}}", - }, - "image": "@@track_thumbnail@@", - "author": { - "name": "Music Controller | @@channel_name@@", - "icon_url": "@@bot_icon@@" - }, - "color": "@@default_embed_color@@" - }, - "inactive": { - "title": { - "name": "There are no songs playing right now" - }, - "description": "[Support](@@server_invite_link@@) | [Invite](@@invite_link@@) | [Questionnaire](https://forms.gle/Qm8vjBfg2kp13YGD7)", - "image": "https://i.imgur.com/dIFBwU7.png", - "color": "@@default_embed_color@@" - } - }, - "default_buttons": [ - ["back", "resume", "skip", {"stop": "red"}, "add"], - ["tracks"] - ] - }) + self.controller = settings.get("default_controller", {}) self.lyrics_platform = settings.get("lyrics_platform", "A_ZLyrics").lower() - self.ipc_server = settings.get("ipc_server", { - "host": "127.0.0.1", - "port": 8000, - "enable": False - } - ) + self.ipc_server = settings.get("ipc_server", {}) self.version = settings.get("version", "") class TOKENS: diff --git a/cogs/basic.py b/cogs/basic.py index 5c79b30..c16d447 100644 --- a/cogs/basic.py +++ b/cogs/basic.py @@ -33,7 +33,7 @@ async def nowplay(ctx: commands.Context, player: voicelink.Player): if not track: return await ctx.send(player.get_msg('noTrackPlaying'), ephemeral=True) - upnext = "\n".join(f"`{index}.` `[{track.formatLength}]` [{track.title[:30]}]({track.uri})" for index, track in enumerate( + upnext = "\n".join(f"`{index}.` `[{track.formatted_length}]` [{track.title[:30]}]({track.uri})" for index, track in enumerate( player.queue.tracks()[:2], start=2)) embed = discord.Embed(description=player.get_msg( 'nowplayingDesc').format(track.title), color=settings.embed_color) @@ -48,7 +48,7 @@ async def nowplay(ctx: commands.Context, player: voicelink.Player): ":pause_button:" if player.is_paused else ":arrow_forward:") embed.add_field( - name="\u2800", value=f"{icon} {pbar} **[{ctime(player.position)}/{track.formatLength}]**", inline=False) + name="\u2800", value=f"{icon} {pbar} **[{ctime(player.position)}/{track.formatted_length}]**", inline=False) return await ctx.send(embed=embed, view=LinkView(player.get_msg('nowplayingLink').format(track.source), track.emoji, track.uri)) @@ -103,7 +103,7 @@ class Basic(commands.Cog): await ctx.send(player.get_msg('playlistLoad').format(tracks.name, index)) else: position = await player.add_track(tracks[0]) - await ctx.send((f"`{player.get_msg('live')}`" if tracks[0].is_stream else "") + (player.get_msg('trackLoad_pos').format(tracks[0].title, tracks[0].uri, tracks[0].author, tracks[0].formatLength, position) if position >= 1 and player.is_playing else player.get_msg('trackLoad').format(tracks[0].title, tracks[0].uri, tracks[0].author, tracks[0].formatLength)), allowed_mentions=False) + await ctx.send((f"`{player.get_msg('live')}`" if tracks[0].is_stream else "") + (player.get_msg('trackLoad_pos').format(tracks[0].title, tracks[0].uri, tracks[0].author, tracks[0].formatted_length, position) if position >= 1 and player.is_playing else player.get_msg('trackLoad').format(tracks[0].title, tracks[0].uri, tracks[0].author, tracks[0].formatted_length)), allowed_mentions=False) except voicelink.QueueFull as e: await ctx.send(e) finally: @@ -143,7 +143,7 @@ class Basic(commands.Cog): await interaction.response.send_message(player.get_msg('playlistLoad').format(tracks.name, index)) else: position = await player.add_track(tracks[0]) - await interaction.response.send_message((f"`{player.get_msg('live')}`" if tracks[0].is_stream else "") + (player.get_msg('trackLoad_pos').format(tracks[0].title, tracks[0].uri, tracks[0].author, tracks[0].formatLength, position) if position >= 1 and player.is_playing else player.get_msg('trackLoad').format(tracks[0].title, tracks[0].uri, tracks[0].author, tracks[0].formatLength)), allowed_mentions=False) + await interaction.response.send_message((f"`{player.get_msg('live')}`" if tracks[0].is_stream else "") + (player.get_msg('trackLoad_pos').format(tracks[0].title, tracks[0].uri, tracks[0].author, tracks[0].formatted_length, position) if position >= 1 and player.is_playing else player.get_msg('trackLoad').format(tracks[0].title, tracks[0].uri, tracks[0].author, tracks[0].formatted_length)), allowed_mentions=False) except voicelink.QueueFull as e: await interaction.response.send_message(e) @@ -190,7 +190,7 @@ class Basic(commands.Cog): return await ctx.send(player.get_msg('noTrackFound')) query_track = "\n".join( - f"`{index}.` `[{track.formatLength}]` **{track.title[:35]}**" for index, track in enumerate(tracks[0:10], start=1)) + f"`{index}.` `[{track.formatted_length}]` **{track.title[:35]}**" for index, track in enumerate(tracks[0:10], start=1)) embed = discord.Embed(title=player.get_msg('searchTitle').format(query), description=player.get_msg( 'searchDesc').format(emoji_source(platform), platform, len(tracks[0:10]), query_track), color=settings.embed_color) view = SearchView(tracks=tracks[0:10], lang=player.lang) @@ -202,8 +202,8 @@ class Basic(commands.Cog): for value in view.values: track = tracks[int(value.split(". ")[0]) - 1] position = await player.add_track(track) - msg += ((f"`{player.get_msg('live')}`" if track.is_stream else "") + (player.get_msg('trackLoad_pos').format(track.title, track.uri, track.author, track.formatLength, - position) if position >= 1 else player.get_msg('trackLoad').format(track.title, track.uri, track.author, track.formatLength))) + msg += ((f"`{player.get_msg('live')}`" if track.is_stream else "") + (player.get_msg('trackLoad_pos').format(track.title, track.uri, track.author, track.formatted_length, + position) if position >= 1 else player.get_msg('trackLoad').format(track.title, track.uri, track.author, track.formatted_length))) await ctx.send(msg, allowed_mentions=False) if not player.is_playing: @@ -231,7 +231,7 @@ class Basic(commands.Cog): await ctx.send(player.get_msg('playlistLoad').format(tracks.name, index)) else: position = await player.add_track(tracks[0], at_font=True) - await ctx.send((f"`{player.get_msg('live')}`" if tracks[0].is_stream else "") + (player.get_msg('trackLoad_pos').format(tracks[0].title, tracks[0].uri, tracks[0].author, tracks[0].formatLength, position) if position >= 1 and player.is_playing else player.get_msg('trackLoad').format(tracks[0].title, tracks[0].uri, tracks[0].author, tracks[0].formatLength)), allowed_mentions=False) + await ctx.send((f"`{player.get_msg('live')}`" if tracks[0].is_stream else "") + (player.get_msg('trackLoad_pos').format(tracks[0].title, tracks[0].uri, tracks[0].author, tracks[0].formatted_length, position) if position >= 1 and player.is_playing else player.get_msg('trackLoad').format(tracks[0].title, tracks[0].uri, tracks[0].author, tracks[0].formatted_length)), allowed_mentions=False) except voicelink.QueueFull as e: await ctx.send(e) @@ -262,7 +262,7 @@ class Basic(commands.Cog): await ctx.send(player.get_msg('playlistLoad').format(tracks.name, index)) else: await player.add_track(tracks[0], at_font=True) - await ctx.send((f"`{player.get_msg('live')}`" if tracks[0].is_stream else "") + player.get_msg('trackLoad').format(tracks[0].title, tracks[0].uri, tracks[0].author, tracks[0].formatLength), allowed_mentions=False) + await ctx.send((f"`{player.get_msg('live')}`" if tracks[0].is_stream else "") + player.get_msg('trackLoad').format(tracks[0].title, tracks[0].uri, tracks[0].author, tracks[0].formatted_length), allowed_mentions=False) except voicelink.QueueFull as e: await ctx.send(e) diff --git a/function.py b/function.py index b684331..be945c5 100644 --- a/function.py +++ b/function.py @@ -17,7 +17,7 @@ if not os.path.exists(os.path.join(root_dir, "settings.json")): raise Exception("Settings file not set!") #-------------- API Clients -------------- -tokens: TOKENS = TOKENS(); +tokens: TOKENS = TOKENS() if not (tokens.mongodb_name and tokens.mongodb_url): raise Exception("MONGODB_NAME and MONGODB_URL can't not be empty in .env") @@ -133,7 +133,7 @@ def formatTime(number:str) -> Optional[int]: return (int(num.tm_hour) * 3600 + int(num.tm_min) * 60 + int(num.tm_sec)) * 1000 -def emoji_source(emoji:str): +def emoji_source(emoji:str) -> str: return settings.emoji_source_raw.get(emoji.lower(), "🔗") def gen_report() -> Optional[discord.File]: diff --git a/views/controller.py b/views/controller.py index 06c087e..d3bb926 100644 --- a/views/controller.py +++ b/views/controller.py @@ -376,7 +376,7 @@ class Tracks(discord.ui.Select): for index, track in enumerate(self.player.queue.tracks(), start=1): if index > 10: break - options.append(discord.SelectOption(label=f"{index}. {track.title[:40]}", description=f"{track.author[:30]} · " + ("Live" if track.is_stream else track.formatLength), emoji=track.emoji)) + options.append(discord.SelectOption(label=f"{index}. {track.title[:40]}", description=f"{track.author[:30]} · " + ("Live" if track.is_stream else track.formatted_length), emoji=track.emoji)) super().__init__( placeholder=player.get_msg("playerDropdown"), diff --git a/views/search.py b/views/search.py index a8c9141..84cee9d 100644 --- a/views/search.py +++ b/views/search.py @@ -31,7 +31,7 @@ class SearchDropdown(discord.ui.Select): self.lang = lang options = [] for index, track in enumerate(self.tracks, start=1): - options.append(discord.SelectOption(label=f"{index}. {track.title[:50]}", description=f"{track.author[:50]} · {track.formatLength}")) + options.append(discord.SelectOption(label=f"{index}. {track.title[:50]}", description=f"{track.author[:50]} · {track.formatted_length}")) super().__init__(placeholder=langs[lang]['searchWait'], min_values=1, max_values=len(tracks), diff --git a/voicelink/objects.py b/voicelink/objects.py index 5138363..9a3bcd4 100644 --- a/voicelink/objects.py +++ b/voicelink/objects.py @@ -41,6 +41,28 @@ class Track: You can also pass in commands.Context to get a discord.py Context object in your track. """ + __slots__ = ( + "track_id", + "info", + "identifier", + "title", + "author", + "uri", + "source", + "spotify", + "artist_id", + "original", + "_search_type", + "spotify_track", + "thumbnail", + "emoji", + "length", + "requester", + "is_stream", + "is_seekable", + "position" + ) + def __init__( self, *, @@ -50,73 +72,83 @@ class Track: search_type: SearchType = SearchType.ytsearch, spotify_track = None, ): - self.track_id = track_id - self.info = info + self.track_id: str = track_id + self.info: dict = info - self.identifier = info.get("identifier") - self.title = info.get("title", "Unknown") - self.author = info.get("author", "Unknown") - self.uri = info.get("uri", "https://discord.com/application-directory/605618911471468554") - self.source = info.get("sourceName", extract(self.uri).domain) - self.spotify = True if self.source == "spotify" else False + self.identifier: str = info.get("identifier") + self.title: str = info.get("title", "Unknown") + self.author: str = info.get("author", "Unknown") + self.uri: str = info.get("uri", "https://discord.com/application-directory/605618911471468554") + self.source: str = info.get("sourceName", extract(self.uri).domain) + self.spotify: bool = True if self.source == "spotify" else False if self.spotify: - self.artistId: Optional[list] = info.get("artistId") + self.artist_id: Optional[list] = info.get("artist_id") self.original: Optional[Track] = None if self.spotify else self - self._search_type = SearchType.ytmsearch if self.spotify else search_type - self.spotify_track = spotify_track + self._search_type: SearchType = SearchType.ytmsearch if self.spotify else search_type + self.spotify_track: Track = spotify_track - self.thumbnail = None - - self.emoji = emoji_source(self.source) + self.thumbnail: str = None + self.emoji: str = emoji_source(self.source) if info.get("thumbnail"): self.thumbnail = info.get("thumbnail") elif YOUTUBE_REGEX.match(self.uri): self.thumbnail = f"https://img.youtube.com/vi/{self.identifier}/hqdefault.jpg" - if self.source == "soundcloud" and "/preview/" in self.identifier: - self.length = 30000 - else: - self.length = info.get("length") + self.length: float = 3000 if self.source == "soundcloud" and "/preview/" in self.identifier else info.get("length") - self.formatLength = ctime(self.length) - self.requester = requester - self.is_stream = info.get("isStream", False) - self.is_seekable = info.get("isSeekable", True) - self.position = info.get("position", 0) + self.requester: Member = requester + self.is_stream: bool = info.get("isStream", False) + self.is_seekable: bool = info.get("isSeekable", True) + self.position: int = info.get("position", 0) if not track_id: self.track_id = encode(self) - def toDict(self): + def __eq__(self, other) -> bool: + if not isinstance(other, Track): + return False + + return other.track_id == self.track_id + + def __str__(self) -> str: + return self.title + + def __repr__(self) -> str: + return f" length={self.length}>" + + def toDict(self) -> dict: return { "track_id": self.track_id, "info": self.info, "thumbnail": self.thumbnail } - def encode(self): + def encode(self) -> bytes: return encode(self) - def __eq__(self, other): - if not isinstance(other, Track): - return False - - return other.track_id == self.track_id - - def __str__(self): - return self.title - - def __repr__(self): - return f" length={self.length}>" - + @property + def formatted_length(self) -> str: + return ctime(self.length) + class Playlist: """The base playlist object. Returns critical playlist information needed for parsing by Lavalink. You can also pass in commands.Context to get a discord.py Context object in your tracks. """ + __slots__ = ( + "playlist_info", + "tracks_raw", + "spotify", + "name", + "spotify_playlist", + "_thumbnail", + "_uri", + "tracks" + ) + def __init__( self, *, @@ -147,12 +179,10 @@ class Playlist: self._thumbnail = None self._uri = None - self.track_count = len(self.tracks) - - def __str__(self): + def __str__(self) -> str: return self.name - def __repr__(self): + def __repr__(self) -> str: return f"" @property @@ -164,3 +194,7 @@ class Playlist: def thumbnail(self) -> Optional[str]: """Spotify album/playlist thumbnail, or None if not a Spotify object.""" return self._thumbnail + + @property + def track_count(self) -> int: + return len(self.tracks) diff --git a/voicelink/player.py b/voicelink/player.py index 6b3b5ea..e093380 100644 --- a/voicelink/player.py +++ b/voicelink/player.py @@ -441,7 +441,7 @@ class Player(VoiceProtocol): try: tracks = await self._node._spotify_client.trackSearch(query=query) - except: + except Exception as _: raise TrackLoadError("Not able to find the provided Spotify entity, is it private?") return [ Track( diff --git a/voicelink/pool.py b/voicelink/pool.py index 7a2e543..030499d 100644 --- a/voicelink/pool.py +++ b/voicelink/pool.py @@ -394,7 +394,7 @@ class Node: try: spotify_results = await self._spotify_client.search(query=query) - except: + except Exception as _: raise TrackLoadError("Not able to find the provided Spotify entity, is it private?") if isinstance(spotify_results, spotify.Track): diff --git a/voicelink/queue.py b/voicelink/queue.py index 4672401..3133411 100644 --- a/voicelink/queue.py +++ b/voicelink/queue.py @@ -53,12 +53,12 @@ class LoopTypeCycle: class Queue: def __init__(self, size: int, allow_duplicate: bool, get_msg: Callable[[str], str]) -> None: - self._queue = [] - self._position = 0 - self._size = size - self._repeat = LoopTypeCycle() - self._repeat_position = 0 - self._allow_duplicate = allow_duplicate + self._queue: List[Track] = [] + self._position: int = 0 + self._size: int = size + self._repeat: LoopTypeCycle = LoopTypeCycle() + self._repeat_position: int = 0 + self._allow_duplicate: bool = allow_duplicate self.get_msg = get_msg diff --git a/voicelink/spotify/client.py b/voicelink/spotify/client.py index 016b7e0..66e76c2 100644 --- a/voicelink/spotify/client.py +++ b/voicelink/spotify/client.py @@ -23,12 +23,12 @@ SOFTWARE. import re import time -from base64 import b64encode - import aiohttp +from base64 import b64encode +from typing import List, Union from .objects import Track, Album, Artist, Playlist -from .exceptions import InvalidSpotifyURL, SpotifyRequestException +from .exceptions import InvalidSpotifyURL, SpotifyRequestException GRANT_URL = "https://accounts.spotify.com/api/token" REQUEST_URL = "https://api.spotify.com/v1/{type}s/{id}" @@ -71,7 +71,7 @@ class Client: self._expiry = time.time() + (int(data["expires_in"]) - 10) self._bearer_headers = {"Authorization": f"Bearer {self._bearer_token}"} - async def trackSearch(self, query: str, track: str = "track", limit: int = 10) -> list: + async def trackSearch(self, query: str, track: str = "track", limit: int = 10) -> List[Track]: if not self._bearer_token or time.time() >= self._expiry: await self._fetch_bearer_token() @@ -87,7 +87,7 @@ class Client: return [ Track(track) for track in data['tracks']['items'] ] - async def similar_track(self, seed_tracks: str, *, limit: int = 5) -> list: + async def similar_track(self, seed_tracks: str, *, limit: int = 5) -> List[Track]: if not self._bearer_token or time.time() >= self._expiry: await self._fetch_bearer_token() @@ -103,7 +103,7 @@ class Client: return [ Track(track) for track in data['tracks'] ] - async def search(self, *, query: str): + async def search(self, *, query: str) -> Union(Track, Album, Playlist): if not self._bearer_token or time.time() >= self._expiry: await self._fetch_bearer_token() diff --git a/voicelink/spotify/objects.py b/voicelink/spotify/objects.py index 94b65f9..ad7a1b8 100644 --- a/voicelink/spotify/objects.py +++ b/voicelink/spotify/objects.py @@ -3,22 +3,24 @@ from typing import List class Track: """The base class for a Spotify Track""" + __slots__ = ( + "name", + "artists", + "artist_id", + "length", + "id", + "image", + "uri" + ) + def __init__(self, data: dict, image=None) -> None: - self.name = data.get('name', 'Unknown') - self.artists = ", ".join(artist["name"] for artist in data.get('artists')) - self.artistId = [artist['id'] for artist in data.get('artists')] - self.length = data.get('duration_ms') - self.id = data.get('id') - - if data.get("album") and data["album"].get("images"): - self.image = data["album"]["images"][0]["url"] - else: - self.image = image - - if data["is_local"]: - self.uri = None - else: - self.uri = data["external_urls"]["spotify"] + self.name: str = data.get('name', 'Unknown') + self.artists: str = ", ".join(artist["name"] for artist in data.get('artists')) + self.artist_id: list = [artist['id'] for artist in data.get('artists')] + self.length: int = data.get('duration_ms') + self.id: str = data.get('id') + self.image: str = images[0]["url"] if (images := data.get("album", {}).get("images")) else image + self.uri: str = None if data["is_local"] else data["external_urls"]["spotify"] def to_dict(self) -> dict: return { @@ -26,7 +28,7 @@ class Track: "author": self.artists, "length": self.length, "identifier": self.id, - "artistId": self.artistId, + "artist_id": self.artist_id, "uri": self.uri, "isStream": False, "isSeekable": True, @@ -43,14 +45,24 @@ class Track: class Album: """The base class for a Spotify album""" + __slots__ = ( + "name", + "artists", + "image", + "tracks", + "total_tracks", + "id", + "uri" + ) + def __init__(self, data: dict) -> None: - self.name = data.get('name', 'Unknown') - self.artists = ", ".join(artist["name"] for artist in data.get('artists')) - self.image = data["images"][0]["url"] - self.tracks = [Track(track, image=self.image) for track in data["tracks"]["items"]] - self.total_tracks = data["total_tracks"] - self.id = data.get('id') - self.uri = data["external_urls"]["spotify"] + self.name: str = data.get('name', 'Unknown') + self.artists: str = ", ".join(artist["name"] for artist in data.get('artists')) + self.image: str = data["images"][0]["url"] + self.tracks: list = [Track(track, image=self.image) for track in data["tracks"]["items"]] + self.total_tracks: int = data["total_tracks"] + self.id: str = data.get('id') + self.uri: str = data["external_urls"]["spotify"] def __repr__(self) -> str: return ( @@ -61,15 +73,24 @@ class Album: class Artist: """The base class for a Spotify playlist""" + __slots__ = ( + "tracks", + "image", + "total_tracks", + "owner", + "id", + "uri", + "name" + ) def __init__(self, data: dict) -> None: - self.tracks = [Track(track) for track in data['tracks']] + self.tracks: List[Track] = [Track(track) for track in data['tracks']] if self.tracks: - self.image = self.tracks[0].image - self.total_tracks = len(self.tracks) - self.owner = self.tracks[0].artists - self.id = self.tracks[0].artistId - self.uri = data['tracks'][0]['album']['artists'][0]['external_urls']['spotify'] - self.name = f"Top tracks - {self.owner}" + self.image: str = self.tracks[0].image + self.total_tracks: int = len(self.tracks) + self.owner: str = self.tracks[0].artists + self.id: str = self.tracks[0].artist_id + self.uri: str = data['tracks'][0]['album']['artists'][0]['external_urls']['spotify'] + self.name: str = f"Top tracks - {self.owner}" def __repr__(self) -> str: return ( @@ -80,17 +101,24 @@ class Artist: class Playlist: """The base class for a Spotify playlist""" + __slots__ = ( + "name", + "tracks", + "owner", + "total_tracks", + "id", + "image", + "uri" + ) + def __init__(self, data: dict, tracks: List[Track]) -> None: - self.name = data.get('name', 'Unknown') - self.tracks = tracks - self.owner = data["owner"]["display_name"] - self.total_tracks = data["tracks"]["total"] - self.id = data.get('id') - if data.get("images") and len(data["images"]): - self.image = data["images"][0]["url"] - else: - self.image = None - self.uri = data["external_urls"]["spotify"] + self.name: str = data.get('name', 'Unknown') + self.tracks: List[Track] = tracks + self.owner: str = data["owner"]["display_name"] + self.total_tracks: int = data["tracks"]["total"] + self.id: str = data.get('id') + self.image: str = data["images"][0]["url"] if len(data.get("images", [])) else None + self.uri: str = data["external_urls"]["spotify"] def __repr__(self) -> str: return (