async def volume(self, ctx: context.Context, volume: int = None) -> None: """ Changes the volume of the player. `volume`: The volume to change too, between 0 and 100. """ if not ctx.voice_client or not ctx.voice_client.is_connected: raise exceptions.VoiceError( 'I am not connected to any voice channels.') channel = getattr(ctx.author.voice, 'channel', None) if not channel or channel.id != ctx.voice_client.channel.id: raise exceptions.VoiceError( f'You must be connected to the same voice channel as me to use this command.' ) if not volume and volume != 0: await ctx.send( f'The players volume is `{ctx.voice_client.volume}%`.') return if volume < 0 or volume > 100 and ctx.author.id not in self.bot.config.owner_ids: raise exceptions.VoiceError( f'That was not a valid volume, Please choose a value between `0` and and `100`.' ) await ctx.voice_client.set_volume(volume=volume) await ctx.send( f'The players volume is now `{ctx.voice_client.volume}%`.')
async def queue_remove(self, ctx: context.Context, entry: int = 0) -> None: """ Removes a track from the queue. `entry`: The position of the track you want to remove. """ if not ctx.voice_client or not ctx.voice_client.is_connected: raise exceptions.VoiceError( 'I am not connected to any voice channels.') channel = getattr(ctx.author.voice, 'channel', None) if not channel or channel.id != ctx.voice_client.channel.id: raise exceptions.VoiceError( f'You must be connected to the same voice channel as me to use this command.' ) if ctx.voice_client.queue.is_empty: raise exceptions.VoiceError('The players queue is empty.') if entry <= 0 or entry > len(ctx.voice_client.queue): raise exceptions.VoiceError( f'That was not a valid track entry. Choose a number between `1` and `{len(ctx.voice_client.queue)}` ' ) item = await ctx.voice_client.queue.get(position=entry - 1, put_history=False) await ctx.send(f'Removed `{item.title}` from the queue.')
async def queue_sort(self, ctx: context.Context, method: Literal['title', 'length', 'author'], reverse: bool = False) -> None: """ Sorts the queue. `method`: The method to sort the queue with. Can be `title`, `length` or `author`. `reverse`: Whether or not to reverse the sort, as in `5, 3, 2, 4, 1` -> `5, 4, 3, 2, 1` instead of `5, 3, 2, 4, 1` -> `1, 2, 3, 4, 5`. """ if not ctx.voice_client or not ctx.voice_client.is_connected: raise exceptions.VoiceError( 'I am not connected to any voice channels.') channel = getattr(ctx.author.voice, 'channel', None) if not channel or channel.id != ctx.voice_client.channel.id: raise exceptions.VoiceError( f'You must be connected to the same voice channel as me to use this command.' ) if ctx.voice_client.queue.is_empty: raise exceptions.VoiceError('The players queue is empty.') if method == 'title': ctx.voice_client.queue._queue.sort(key=lambda track: track.title, reverse=reverse) elif method == 'author': ctx.voice_client.queue._queue.sort(key=lambda track: track.author, reverse=reverse) elif method == 'length': ctx.voice_client.queue._queue.sort(key=lambda track: track.length, reverse=reverse) await ctx.send(f'The queue has been sorted with method `{method}`.')
async def queue_history(self, ctx: context.Context) -> None: """ Displays the queue history. """ if not ctx.voice_client or not ctx.voice_client.is_connected: raise exceptions.VoiceError( 'I am not connected to any voice channels.') history = list(ctx.voice_client.queue.history) if not history: raise exceptions.VoiceError('The queue history is empty.') time = self.bot.utils.format_seconds( seconds=round(sum(track.length for track in history)) / 1000, friendly=True) header = f'Showing `{min([10, len(history)])}` out of `{len(history)}` track(s) in the queues history. Total queue history time is `{time}`.\n\n' entries = [ f'`{index + 1}.` [{str(track.title)}]({track.uri}) | {self.bot.utils.format_seconds(seconds=round(track.length) / 1000)} | {track.requester.mention}' for index, track in enumerate(history) ] await ctx.paginate_embed(entries=entries, per_page=10, title='Queue history:', header=header)
async def now_playing(self, ctx: context.Context) -> None: """ Displays the player controller. """ if not ctx.voice_client or not ctx.voice_client.is_connected: raise exceptions.VoiceError( 'I am not connected to any voice channels.') if not ctx.voice_client.is_playing: raise exceptions.VoiceError(f'There are no tracks playing.') await ctx.voice_client.invoke_controller()
async def skip(self, ctx: context.Context, amount: int = 1) -> None: """ Skips an amount of tracks. `amount`: The amount of tracks to skip. Defaults to 1 """ if not ctx.voice_client or not ctx.voice_client.is_connected: raise exceptions.VoiceError( 'I am not connected to any voice channels.') channel = getattr(ctx.author.voice, 'channel', None) if not channel or channel.id != ctx.voice_client.channel.id: raise exceptions.VoiceError( f'You must be connected to the same voice channel as me to use this command.' ) if not ctx.voice_client.is_playing: raise exceptions.VoiceError(f'There are no tracks playing.') if ctx.voice_client.current.requester.id != ctx.author.id: amount = 1 if ctx.author not in ctx.voice_client.listeners: raise exceptions.VoiceError( 'You can not vote to skip as you are currently deafened or server deafened.' ) if ctx.author.id in ctx.voice_client.skip_requests: ctx.voice_client.skip_requests.remove(ctx.author.id) raise exceptions.VoiceError(f'Removed your vote to skip.') else: ctx.voice_client.skip_requests.append(ctx.author.id) await ctx.send('Added your vote to skip.') skips_needed = (len(ctx.voice_client.listeners) // 2) + 1 if len(ctx.voice_client.skip_requests) < skips_needed: raise exceptions.VoiceError( f'Currently on `{len(ctx.voice_client.skip_requests)}` out of `{skips_needed}` votes needed to skip.' ) if amount != 1: if amount <= 0 or amount > len(ctx.voice_client.queue) + 1: raise exceptions.VoiceError( f'There are not enough tracks in the queue to skip that many. Choose a number between `1` and `{len(ctx.voice_client.queue) + 1}`.' ) for index, track in enumerate(ctx.voice_client.queue[:amount - 1]): if track.requester.id != ctx.author.id: raise exceptions.VoiceError( f'You only skipped `{index + 1}` out of the next `{amount}` tracks because you were not the requester of all them.' ) ctx.voice_client.queue.get() await ctx.voice_client.stop() await ctx.send( f'Skipped `{amount}` {"track." if amount == 1 else "tracks."}')
async def play(self, ctx: context.Context, *, query: str) -> None: """ Plays or queues a track with the given search. `query`: The search term to find tracks for. You can prepend this query with soundcloud to search for tracks on soundcloud. This command supports youtube/soundcloud searching or youtube, soundcloud, spotify, bandcamp, beam, twitch, and vimeo links. """ if not ctx.voice_client or not ctx.voice_client.is_connected: await ctx.invoke(self.join) channel = getattr(ctx.author.voice, 'channel', None) if not channel or channel.id != ctx.voice_client.channel.id: raise exceptions.VoiceError( f'You must be connected to the same voice channel as me to use this command.' ) async with ctx.channel.typing(): search = await ctx.voice_client.search(query=query, ctx=ctx) if search.source == 'HTTP' and ctx.author.id not in self.bot.config.owner_ids: raise exceptions.VoiceError( 'You are unable to play HTTP links.') if search.source == 'spotify': message = f'Added the Spotify {search.search_type} `{search.search_result.name}` to the queue.' if search.search_type in ('album', 'playlist'): message = f'{message[:-1]} with a total of `{len(search.tracks)}` tracks.' tracks = search.tracks else: if search.search_type == 'track': message = f'Added the {search.source} {search.search_type} `{search.tracks[0].title}` to the queue.' tracks = [search.tracks[0]] elif search.search_type == 'playlist': message = f'Added the {search.source} {search.search_type} `{search.search_result.name}` to the queue with a total of **{len(search.tracks)}** track(s)' tracks = search.tracks ctx.voice_client.queue.put(items=tracks) await ctx.send(message)
async def leave(self, ctx: context.Context) -> None: """ Leaves the voice channel. """ if not ctx.voice_client or not ctx.voice_client.is_connected: raise exceptions.VoiceError( 'I am not connected to any voice channels.') channel = getattr(ctx.author.voice, 'channel', None) if not channel or channel.id != ctx.voice_client.channel.id: raise exceptions.VoiceError( f'You must be connected to the same voice channel as me to use this command.' ) await ctx.send(f'Left the voice channel `{ctx.voice_client.channel}`.') await ctx.voice_client.stop() await ctx.voice_client.disconnect()
async def queue_loop_current(self, ctx: context.Context) -> None: """ Loops the current track. """ if not ctx.voice_client or not ctx.voice_client.is_connected: raise exceptions.VoiceError( 'I am not connected to any voice channels.') channel = getattr(ctx.author.voice, 'channel', None) if not channel or channel.id != ctx.voice_client.channel.id: raise exceptions.VoiceError( f'You must be connected to the same voice channel as me to use this command.' ) ctx.voice_client.queue.set_looping( looping=not ctx.voice_client.queue.is_looping, current=True) await ctx.send( f'I will {"start" if ctx.voice_client.queue.is_looping else "stop"} looping the current track.' )
async def queue_reverse(self, ctx: context.Context) -> None: """ Reverses the queue. """ if not ctx.voice_client or not ctx.voice_client.is_connected: raise exceptions.VoiceError( 'I am not connected to any voice channels.') channel = getattr(ctx.author.voice, 'channel', None) if not channel or channel.id != ctx.voice_client.channel.id: raise exceptions.VoiceError( f'You must be connected to the same voice channel as me to use this command.' ) if ctx.voice_client.queue.is_empty: raise exceptions.VoiceError('The players queue is empty.') ctx.voice_client.queue.reverse() await ctx.send(f'The queue has been reversed.')
async def join(self, ctx: context.Context) -> None: """ Joins your voice channel. """ channel = getattr(ctx.author.voice, 'channel', None) if not channel: raise exceptions.VoiceError( 'You must be in a voice channel to use this command.') if ctx.voice_client and ctx.voice_client.is_connected: raise exceptions.VoiceError('I am already in a voice channel.') if ctx.voice_client: await ctx.voice_client.reconnect(channel=channel) else: await self.slate.create_player(channel=channel, cls=Player) ctx.voice_client.text_channel = ctx.channel await ctx.send(f'Joined the voice channel `{channel}`.')
async def unpause(self, ctx: context.Context) -> None: """ Resumes the player. """ if not ctx.voice_client or not ctx.voice_client.is_connected: raise exceptions.VoiceError( 'I am not connected to any voice channels.') channel = getattr(ctx.author.voice, 'channel', None) if not channel or channel.id != ctx.voice_client.channel.id: raise exceptions.VoiceError( f'You must be connected to the same voice channel as me to use this command.' ) if ctx.voice_client.is_paused is False: raise exceptions.VoiceError('The player is not paused.') await ctx.voice_client.set_pause(pause=False) await ctx.send(f'The player is now resumed.')
async def destroy(self, ctx: context.Context) -> None: """ Completely destroys the guilds player. """ if not ctx.voice_client: raise exceptions.VoiceError( 'I am not connected to any voice channels.') await ctx.send(f'Destroyed this guilds player.') await ctx.voice_client.destroy()
def sort(self, method: str = 'title', reverse: bool = False) -> None: if method == 'title': self.queue.sort(key=lambda track: track.title, reverse=reverse) elif method == 'author': self.queue.sort(key=lambda track: track.author, reverse=reverse) elif method == 'length': self.queue.sort(key=lambda track: track.length, reverse=reverse) else: raise exceptions.VoiceError('That was not a valid queue sort operation. Please choose either `title`, `length` or `author`') self.player.dispatch_event(data={'type': 'PlayerQueueUpdate', 'player': self.player})
async def queue_history_clear(self, ctx: context.Context) -> None: """ Clears the queue history. """ if not ctx.voice_client or not ctx.voice_client.is_connected: raise exceptions.VoiceError( 'I am not connected to any voice channels.') channel = getattr(ctx.author.voice, 'channel', None) if not channel or channel.id != ctx.voice_client.channel.id: raise exceptions.VoiceError( f'You must be connected to the same voice channel as me to use this command.' ) history = list(ctx.voice_client.queue.history) if not history: raise exceptions.VoiceError('The queue history is empty.') ctx.voice_client.queue.clear_history() await ctx.send(f'The queue history has been cleared.')
async def queue_move(self, ctx: context.Context, entry_1: int = 0, entry_2: int = 0) -> None: """ Move a track in the queue to a different position. `entry_1`: The position of the track you want to move from. `entry_2`: The position of the track you want to move too. """ if not ctx.voice_client or not ctx.voice_client.is_connected: raise exceptions.VoiceError( 'I am not connected to any voice channels.') channel = getattr(ctx.author.voice, 'channel', None) if not channel or channel.id != ctx.voice_client.channel.id: raise exceptions.VoiceError( f'You must be connected to the same voice channel as me to use this command.' ) if ctx.voice_client.queue.is_empty: raise exceptions.VoiceError('The players queue is empty.') if entry_1 <= 0 or entry_1 > len(ctx.voice_client.queue): raise exceptions.VoiceError( f'That was not a valid track entry to move from. Choose a number between `1` and `{len(ctx.voice_client.queue)}` ' ) if entry_2 <= 0 or entry_2 > len(ctx.voice_client.queue): raise exceptions.VoiceError( f'That was not a valid track entry to move too. Choose a number between `1` and `{len(ctx.voice_client.queue)}` ' ) track = ctx.voice_client.queue.get(position=entry_1 - 1, put_history=False) ctx.voice_client.queue.put(items=track, position=entry_2 - 1) await ctx.send( f'Moved `{track.title}` from position `{entry_1}` to position `{entry_2}`.' )
async def seek(self, ctx: context.Context, seconds: int = None) -> None: """ Changes the position of the player. `position`: The position to seek too, in seconds. """ if not ctx.voice_client or not ctx.voice_client.is_connected: raise exceptions.VoiceError( 'I am not connected to any voice channels.') channel = getattr(ctx.author.voice, 'channel', None) if not channel or channel.id != ctx.voice_client.channel.id: raise exceptions.VoiceError( f'You must be connected to the same voice channel as me to use this command.' ) if not ctx.voice_client.is_playing: raise exceptions.VoiceError(f'There are no tracks playing.') if not ctx.voice_client.current.is_seekable: raise exceptions.VoiceError('The current track is not seekable.') if not seconds and seconds != 0: await ctx.send( f'The players position is `{self.bot.utils.format_seconds(seconds=ctx.voice_client.position / 1000)}`' ) return milliseconds = seconds * 1000 if milliseconds < 0 or milliseconds > ctx.voice_client.current.length: raise exceptions.VoiceError( f'That was not a valid position. Please choose a value between `0` and `{round(ctx.voice_client.current.length / 1000)}`.' ) await ctx.voice_client.set_position(position=milliseconds) await ctx.send( f'The players position is now `{self.bot.utils.format_seconds(seconds=milliseconds / 1000)}`.' )
async def queue_detailed(self, ctx: context.Context) -> None: """ Displays detailed information about the queue. """ if not ctx.voice_client or not ctx.voice_client.is_connected: raise exceptions.VoiceError( 'I am not connected to any voice channels.') if ctx.voice_client.queue.is_empty: raise exceptions.VoiceError('The players queue is empty.') entries = [] for index, track in enumerate(ctx.voice_client.queue): embed = discord.Embed(colour=ctx.colour) embed.set_image(url=track.thumbnail) embed.description = f'Showing detailed information about track `{index + 1}` out of `{len(ctx.voice_client.queue)}` in the queue.\n\n' \ f'[{track.title}]({track.uri})\n\n`Author:` {track.author}\n`Source:` {track.source}\n' \ f'`Length:` {self.bot.utils.format_seconds(seconds=round(track.length) / 1000, friendly=True)}\n' \ f'`Live:` {track.is_stream}\n`Seekable:` {track.is_seekable}\n`Requester:` {track.requester.mention}' entries.append(embed) await ctx.paginate_embeds(entries=entries)
async def search(self, query: str, ctx: context.Context) -> objects.SearchResult: search_result = None search_tracks = None spotify_url_check = self.spotify_url_regex.match(query) if spotify_url_check is not None: source = 'spotify' spotify_client: spotify.Client = self.bot.cogs["Music"].spotify spotify_http_client: spotify.HTTPClient = self.bot.cogs[ "Music"].spotify_http search_type = spotify_url_check.group('type') spotify_id = spotify_url_check.group('id') try: if search_type == 'album': search_result = await spotify_client.get_album( spotify_id=spotify_id) search_tracks = await search_result.get_all_tracks() elif search_type == 'playlist': search_result = spotify.Playlist( client=spotify_client, data=await spotify_http_client.get_playlist(spotify_id)) search_tracks = await search_result.get_all_tracks() elif search_type == 'track': search_result = await spotify_client.get_track( spotify_id=spotify_id) search_tracks = [search_result] except spotify.NotFound or HTTPException: raise exceptions.VoiceError( f'No results were found for your Spotify link.') if not search_tracks: raise exceptions.VoiceError( f'No results were found for your Spotify link.') tracks = [ slate.Track( track_id='', ctx=ctx, track_info={ 'title': track.name or 'Unknown', 'author': ', '.join(artist.name for artist in track.artists) or 'Unknown', 'length': track.duration or 0, 'identifier': track.id or 'Unknown', 'uri': track.url or 'spotify', 'isStream': False, 'isSeekable': False, 'position': 0, 'thumbnail': track.images[0].url if track.images else None }, ) for track in search_tracks ] else: url = yarl.URL(query) if not url.host or not url.scheme: if query.startswith('soundcloud'): query = f'scsearch:{query[11:]}' else: query = f'ytsearch:{query}' try: search_result = await self.node.search(query=query, ctx=ctx) except slate.TrackLoadError as error: raise exceptions.VoiceError( f'`{error.status_code}` error code while searching for results. For support use `{self.bot.config.prefix}support`.' ) except slate.TrackLoadFailed as error: raise exceptions.VoiceError( f'`{error.severity}` error while searching for results. For support use `{self.bot.config.prefix}support`.\nReason: `{error.message}`' ) if not search_result: raise exceptions.VoiceError( f'No results were found for your search.') if isinstance(search_result, slate.Playlist): source = 'youtube' search_type = 'playlist' tracks = search_result.tracks else: source = search_result[0].source search_type = 'track' tracks = search_result return objects.SearchResult(source=source, search_type=search_type, search_result=search_result, tracks=tracks)
async def lyrics(self, ctx: context.Context, *, query: str = 'spotify') -> None: if query == 'spotify': spotify_activity = discord.utils.find( lambda activity: isinstance(activity, discord.Spotify), ctx.author.activities) if not spotify_activity: raise exceptions.VoiceError( 'I was unable to detect a Spotify status on your account.') query = f'{spotify_activity.title} - {spotify_activity.album} - {spotify_activity.artist}' elif query == 'player': if not ctx.voice_client or not ctx.voice_client.is_connected: raise exceptions.VoiceError( 'I am not connected to any voice channels.') if not ctx.voice_client.is_playing: raise exceptions.VoiceError(f'There are no tracks playing.') query = f'{ctx.voice_client.current.title} - {ctx.voice_client.current.requester}' try: results = await self.ksoft.music.lyrics(query=query, limit=20) except ksoftapi.NoResults: raise exceptions.ArgumentError( f'No results were found for the query `{query}`.') except ksoftapi.APIError: raise exceptions.VoiceError( 'The API used to fetch lyrics is currently down/broken.') paginator = await ctx.paginate_embed( entries=[ f'`{index + 1}.` {result.name} - {result.artist}' for index, result in enumerate(results) ], per_page=10, header= f'**__Please choose the number of the track you would like lyrics for:__**\n`Query`: {query}\n\n' ) try: response = await self.bot.wait_for( 'message', check=lambda msg: msg.author == ctx.author and msg.channel == ctx.channel, timeout=30.0) except asyncio.TimeoutError: raise exceptions.ArgumentError('You took too long to respond.') response = await commands.clean_content().convert( ctx=ctx, argument=response.content) try: response = int(response) - 1 except ValueError: raise exceptions.ArgumentError('That was not a valid number.') if response < 0 or response >= len(results): raise exceptions.ArgumentError( 'That was not one of the available lyrics.') await paginator.stop() result = results[response] entries = [] for line in result.lyrics.split('\n'): if not entries: entries.append(line) continue last_entry = entries[-1] if len(last_entry) >= 1000 or len(last_entry) + len(line) >= 1000: entries.append(line) continue entries[-1] += f'\n{line}' await ctx.paginate_embed( entries=entries, header=f'Lyrics for `{result.name}` by `{result.artist}`:\n\n', embed_add_footer='Lyrics provided by KSoft.Si API.', per_page=1)