From 7343d142251c60327f2280fe81b4a048e3700e76 Mon Sep 17 00:00:00 2001 From: cloudwithax Date: Fri, 8 Oct 2021 18:34:27 -0400 Subject: [PATCH 1/5] Finally fixed SearchType and cleaned up some code --- pomice/__init__.py | 13 +++++++--- pomice/enums.py | 9 ++++--- pomice/events.py | 2 +- pomice/objects.py | 13 ++++++---- pomice/player.py | 14 +++-------- pomice/{node.py => pool.py} | 47 +++++++++++++++++++++---------------- pomice/utils.py | 1 - 7 files changed, 55 insertions(+), 44 deletions(-) rename pomice/{node.py => pool.py} (91%) diff --git a/pomice/__init__.py b/pomice/__init__.py index afe9d4e..58b951a 100644 --- a/pomice/__init__.py +++ b/pomice/__init__.py @@ -4,10 +4,17 @@ __version__ = "1.0.5" __title__ = "pomice" __author__ = "cloudwithax" +import discord + +if discord.__version__ != '2.0.0a': + raise exit("You must have discord.py 2.0 to use this library. Uninstall your current version and install discord.py 2.0 using 'pip install git+https://github.com/Rapptz/discord.py'") + +from .enums import SearchType from .events import * from .exceptions import * from .filters import * -from .node import Node, NodePool -from .objects import Track, Playlist +from .pool import * +from .objects import * from .player import Player -from .enums import SearchType + + diff --git a/pomice/enums.py b/pomice/enums.py index 9aef2e0..0ac9c66 100644 --- a/pomice/enums.py +++ b/pomice/enums.py @@ -11,9 +11,12 @@ class SearchType(Enum): SearchType.SCSEARCH searches for a Spotify track using SoundCloud, which is an alternative to YouTube or YouTube Music. """ - YTSEARCH = f'ytsearch:{track.artist} - {track.title}' - YTMSEARCH = f'ytmsearch:{track.artist} - {track.title}' - SCSEARCH = f'scsearch:{track.artist} - {track.title}' + YTSEARCH = 'ytsearch:' + YTMSEARCH = 'ytmsearch:' + SCSEARCH = 'scsearch:' + + def __str__(self) -> str: + return self.value diff --git a/pomice/events.py b/pomice/events.py index 1ceef7f..209e59c 100644 --- a/pomice/events.py +++ b/pomice/events.py @@ -1,4 +1,4 @@ -from .node import NodePool +from .pool import NodePool class PomiceEvent: diff --git a/pomice/objects.py b/pomice/objects.py index e883b1c..6acdbd2 100644 --- a/pomice/objects.py +++ b/pomice/objects.py @@ -1,3 +1,4 @@ +from re import S from typing import Optional from . import SearchType @@ -13,17 +14,14 @@ class Track: self, track_id: str, info: dict, - ctx: Optional[commands.Context] = None, + ctx: Optional[commands.Context], + search_type: SearchType = None, spotify: bool = False ): self.track_id = track_id self.info = info self.spotify = spotify - if self.spotify: - self.youtube_result = None - self.search_type: SearchType = None - self.title = info.get("title") self.author = info.get("author") self.length = info.get("length") @@ -35,6 +33,11 @@ class Track: self.is_seekable = info.get("isSeekable") self.position = info.get("position") + if self.spotify: + self.youtube_result = None + if search_type: + self.search_type = search_type + def __eq__(self, other): if not isinstance(other, Track): return False diff --git a/pomice/player.py b/pomice/player.py index e2ec24a..fc906dd 100644 --- a/pomice/player.py +++ b/pomice/player.py @@ -2,10 +2,10 @@ import time from typing import Any, Dict, Type, Union import discord -from discord import VoiceChannel, VoiceProtocol, Guild, Member +from discord import VoiceChannel, VoiceProtocol, Guild from discord.ext import commands -from . import events, filters, NodePool, objects, Node +from . import events, filters, NodePool, objects, Node from .exceptions import TrackInvalidPosition @@ -24,7 +24,6 @@ class Player(VoiceProtocol): self._bot: Type[Union[discord.Client, commands.Bot, commands.AutoShardedBot]] = client self.channel = channel self._guild: discord.Guild = self.channel.guild - self._dj: discord.Member = None self._node = NodePool.get_node() self._current: objects.Track = None @@ -93,10 +92,6 @@ class Player(VoiceProtocol): """Property which returns the players current volume""" return self._volume - @property - def dj(self) -> Member: - """Property which returns the DJ for the player session""" - return self._dj @property def filter(self) -> filters.Filter: @@ -182,10 +177,7 @@ class Player(VoiceProtocol): async def play(self, track: objects.Track, start_position: int = 0) -> objects.Track: """Plays a track. If a Spotify track is passed in, it will be handled accordingly.""" if track.spotify: - search_type = track.search_type or f"ytmsearch:{track.author} - {track.title}" - spotify_track: objects.Track = (await self._node.get_tracks( - search_type - ))[0] + spotify_track: objects.Track = (await self._node.get_tracks(f"{track.search_type}"))[0] track.youtube_result = spotify_track await self._node.send( op="play", diff --git a/pomice/node.py b/pomice/pool.py similarity index 91% rename from pomice/node.py rename to pomice/pool.py index a680383..4bf989b 100644 --- a/pomice/node.py +++ b/pomice/pool.py @@ -4,15 +4,17 @@ import random import re import socket import time -from typing import Optional, Type, Union +from typing import Optional, Union from urllib.parse import quote import aiohttp import discord from discord.ext import commands +from typing import Union, Optional -from . import __version__, spotify, Player, SearchType + +from . import __version__, spotify, SearchType from .exceptions import ( InvalidSpotifyClientAuthorization, NodeConnectionFailure, @@ -42,16 +44,17 @@ class Node: def __init__( self, pool, - bot: Type[Union[discord.Client, commands.Bot, commands.AutoShardedBot]], + bot: Union[discord.Client, commands.Bot, commands.AutoShardedBot], host: str, port: int, password: str, identifier: str, + session: Optional[aiohttp.ClientSession], spotify_client_id: Optional[str], spotify_client_secret: Optional[str], - session: Optional[aiohttp.ClientSession] + ): - self._bot: Type[Union[discord.Client, commands.Bot, commands.AutoShardedBot]] = bot + self._bot: Union[discord.Client, commands.Bot, commands.AutoShardedBot] = bot self._host: str = host self._port: int = port self._pool: NodePool = pool @@ -122,7 +125,7 @@ class Node: return self._players @property - def bot(self) -> Type[Union[discord.Client, commands.Bot, commands.AutoShardedBot]]: + def bot(self) -> Union[discord.Client, commands.Bot, commands.AutoShardedBot]: """Property which returns the discord.py client linked to this node""" return self._bot @@ -193,13 +196,13 @@ class Node: await player._update_state(data) async def send(self, **data): - if not self.available: + if not self._available: raise NodeNotAvailable( f"The node '{self.identifier}' is not currently available.") await self._websocket.send_str(json.dumps(data)) - def get_player(self, guild_id: int) -> Player: + def get_player(self, guild_id: int): """Takes a guild ID as a parameter. Returns a pomice Player object.""" return self._players.get(guild_id, None) @@ -256,10 +259,10 @@ class Node: "please obtain Spotify API credentials here: https://developer.spotify.com/" ) - search_type = spotify_url_check.group("type") + spotify_search_type = spotify_url_check.group("type") spotify_id = spotify_url_check.group("id") - if search_type == "playlist": + if spotify_search_type == "playlist": results = spotify.Playlist( client=self._spotify_client, data=await self._spotify_http_client.get_playlist(spotify_id) @@ -271,7 +274,7 @@ class Node: Track( track_id=track.id, ctx=ctx, - search_type=search_type, + search_type=f"{search_type}{track.artists[0].name} - {track.name}" if search_type else f"ytmsearch:{track.artists[0].name} - {track.name}", spotify=True, info={ "title": track.name or "Unknown", @@ -303,7 +306,7 @@ class Node: f"Unable to find results for {query}" ) - elif search_type == "album": + elif spotify_search_type == "album": results = await self._spotify_client.get_album(spotify_id=spotify_id) try: @@ -312,7 +315,7 @@ class Node: Track( track_id=track.id, ctx=ctx, - search_type=search_type, + search_type=f"{search_type}{track.artists[0].name} - {track.name}" if search_type else f"ytmsearch:{track.artists[0].name} - {track.name}", spotify=True, info={ "title": track.name or "Unknown", @@ -342,7 +345,7 @@ class Node: except SpotifyException: raise SpotifyAlbumLoadFailed(f"Unable to find results for {query}") - elif search_type == 'track': + elif spotify_search_type == 'track': try: results = await self._spotify_client.get_track(spotify_id=spotify_id) @@ -350,7 +353,7 @@ class Node: Track( track_id=results.id, ctx=ctx, - search_type=search_type, + search_type=f"{search_type}{results.artists[0].name} - {results.name}" if search_type else f"ytmsearch:{results.artists[0].name} - {results.name}", spotify=True, info={ "title": results.name or "Unknown", @@ -409,6 +412,8 @@ class Node: for track in data["tracks"] ] + + class NodePool: """The base class for the node pool. This holds all the nodes that are to be used by the bot. @@ -444,14 +449,16 @@ class NodePool: @classmethod async def create_node( - bot: Type[Union[discord.Client, commands.Bot, commands.AutoShardedBot]], cls, + bot: Union[discord.Client, commands.Bot, commands.AutoShardedBot], host: str, port: str, password: str, identifier: str, - spotify_client_id: Optional[str] = None, - spotify_client_secret: Optional[str] = None + spotify_client_id: Optional[str], + spotify_client_secret: Optional[str], + session: Optional[aiohttp.ClientSession] = None, + ) -> Node: """Creates a Node object to be then added into the node pool. For Spotify searching capabilites, pass in valid Spotify API credentials. @@ -460,9 +467,9 @@ class NodePool: raise NodeCreationError(f"A node with identifier '{identifier}' already exists.") node = Node( - bot=bot, pool=cls, host=host, port=port, password=password, + pool=cls, bot=bot, host=host, port=port, password=password, identifier=identifier, spotify_client_id=spotify_client_id, - spotify_client_secret=spotify_client_secret + session=session, spotify_client_secret=spotify_client_secret ) await node.connect() diff --git a/pomice/utils.py b/pomice/utils.py index b382b2a..708e14f 100644 --- a/pomice/utils.py +++ b/pomice/utils.py @@ -20,7 +20,6 @@ DEALINGS IN THE SOFTWARE. import random import time -from typing import Any __all__ = [ 'ExponentialBackoff', From e51e26cbf75078314d87333b2fa8eadbcb127c49 Mon Sep 17 00:00:00 2001 From: cloudwithax Date: Fri, 8 Oct 2021 18:46:39 -0400 Subject: [PATCH 2/5] Fixed bug where self.current would not set to none after stop --- pomice/player.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pomice/player.py b/pomice/player.py index fc906dd..37da9b0 100644 --- a/pomice/player.py +++ b/pomice/player.py @@ -21,7 +21,7 @@ class Player(VoiceProtocol): super().__init__(client=client, channel=channel) self.client = client - self._bot: Type[Union[discord.Client, commands.Bot, commands.AutoShardedBot]] = client + self._bot: Union[discord.Client, commands.Bot, commands.AutoShardedBot] = client self.channel = channel self._guild: discord.Guild = self.channel.guild @@ -154,11 +154,11 @@ class Player(VoiceProtocol): async def connect(self, *, timeout: float, reconnect: bool): await self.guild.change_voice_state(channel=self.channel) self._node._players[self.guild.id] = self - self.is_connected = True + self._is_connected = True async def stop(self): """Stops a currently playing track.""" - self.current = None + self._current = None await self._node.send(op="stop", guildId=str(self.guild.id)) async def disconnect(self, *, force: bool = False): From d0a149793ffcedcc0dc3cf845a7c703f807abf1a Mon Sep 17 00:00:00 2001 From: cloudwithax Date: Fri, 8 Oct 2021 18:50:26 -0400 Subject: [PATCH 3/5] Fixed versioning --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 0424e8b..300fddb 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ with open("README.md") as f: setuptools.setup( name="pomice", author="cloudwithax", - version="1.0.5", + version="1.0.5.1", url="https://github.com/cloudwithax/pomice", packages=setuptools.find_packages(), license="GPL", From e138091fcf7f52ee07334d15380177246cc1c7c7 Mon Sep 17 00:00:00 2001 From: cloudwithax Date: Fri, 8 Oct 2021 18:58:41 -0400 Subject: [PATCH 4/5] are you fucking kidding me --- pomice/player.py | 5 +++++ setup.py | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/pomice/player.py b/pomice/player.py index 37da9b0..b6147e6 100644 --- a/pomice/player.py +++ b/pomice/player.py @@ -67,6 +67,11 @@ class Player(VoiceProtocol): """Property which returns whether or not the player is actively playing a track.""" return self._is_connected and self.current is not None + @property + def is_connected(self) -> bool: + """Property which returns whether or not the player is connected""" + return self._is_connected + @property def is_paused(self) -> bool: """Property which returns whether or not the player has a track which is paused or not.""" diff --git a/setup.py b/setup.py index 300fddb..aa689c9 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ with open("README.md") as f: setuptools.setup( name="pomice", author="cloudwithax", - version="1.0.5.1", + version="1.0.5.2", url="https://github.com/cloudwithax/pomice", packages=setuptools.find_packages(), license="GPL", From f6a375229831e0e68f0ccc8483ebde284247ee9c Mon Sep 17 00:00:00 2001 From: cloudwithax Date: Fri, 8 Oct 2021 21:11:58 -0400 Subject: [PATCH 5/5] Added Discord audio attachment URL regex to better parse audio tracks. Take that, Groovy... --- pomice/objects.py | 2 +- pomice/pool.py | 27 +++++++++++++++++++++++++++ setup.py | 2 +- 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/pomice/objects.py b/pomice/objects.py index 6acdbd2..700d903 100644 --- a/pomice/objects.py +++ b/pomice/objects.py @@ -16,7 +16,7 @@ class Track: info: dict, ctx: Optional[commands.Context], search_type: SearchType = None, - spotify: bool = False + spotify: bool = False, ): self.track_id = track_id self.info = info diff --git a/pomice/pool.py b/pomice/pool.py index 4bf989b..8060f5e 100644 --- a/pomice/pool.py +++ b/pomice/pool.py @@ -34,6 +34,10 @@ SPOTIFY_URL_REGEX = re.compile( r"https?://open.spotify.com/(?Palbum|playlist|track)/(?P[a-zA-Z0-9]+)" ) +DISCORD_MP3_URL_REGEX = re.compile( + r"https?://cdn.discordapp.com/attachments/(?P[0-9]+)/(?P[0-9]+)/(?P[a-zA-Z0-9_.]+)+" +) + class Node: """The base class for a node. @@ -374,6 +378,29 @@ class Node: except SpotifyException: raise SpotifyTrackLoadFailed(f"Unable to find results for {query}") + elif discord_url := DISCORD_MP3_URL_REGEX.match(query): + async with self._session.get( + url=f"{self._rest_uri}/loadtracks?identifier={quote(query)}", + headers={"Authorization": self._password} + ) as response: + data: dict = await response.json() + + + track: dict = data["tracks"][0] + info: dict = track.get('info') + + return [Track( + track_id=track['track'], + info={ + "title": discord_url.group('file'), + "author": "Unknown", + "length": info.get('length'), + "uri": info.get('uri'), + "position": info.get('position'), + "identifier": info.get('identifier') + }, + ctx=ctx)] + else: async with self._session.get( url=f"{self._rest_uri}/loadtracks?identifier={quote(query)}", diff --git a/setup.py b/setup.py index aa689c9..070079e 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ with open("README.md") as f: setuptools.setup( name="pomice", author="cloudwithax", - version="1.0.5.2", + version="1.0.5.3", url="https://github.com/cloudwithax/pomice", packages=setuptools.find_packages(), license="GPL",