Skip to content

Instantly share code, notes, and snippets.

@EvieePy
Last active August 30, 2025 05:56
Show Gist options
  • Save EvieePy/ab667b74e9758433b3eb806c53a19f34 to your computer and use it in GitHub Desktop.
Save EvieePy/ab667b74e9758433b3eb806c53a19f34 to your computer and use it in GitHub Desktop.

Revisions

  1. EvieePy revised this gist Apr 13, 2018. 1 changed file with 7 additions and 4 deletions.
    11 changes: 7 additions & 4 deletions music.py
    Original file line number Diff line number Diff line change
    @@ -116,7 +116,7 @@ async def regather_stream(cls, data, *, loop):
    class MusicPlayer:
    """A class which is assigned to each guild using the bot for Music.
    This class implements and queue and loop, which allows for different guilds to listen to different playlists
    This class implements a queue and loop, which allows for different guilds to listen to different playlists
    simultaneously.
    When the bot disconnects from the Voice it's instance will be destroyed.
    @@ -135,6 +135,7 @@ def __init__(self, ctx):

    self.np = None # Now playing message
    self.volume = .5
    self.current = None

    ctx.bot.loop.create_task(self.player_loop())

    @@ -163,6 +164,7 @@ async def player_loop(self):
    continue

    source.volume = self.volume
    self.current = source

    self._guild.voice_client.play(source, after=lambda _: self.bot.loop.call_soon_threadsafe(self.next.set))
    self.np = await self._channel.send(f'**Now Playing:** `{source.title}` requested by '
    @@ -171,6 +173,7 @@ async def player_loop(self):

    # Make sure the FFmpeg process is cleaned up.
    source.cleanup()
    self.current = None

    try:
    # We are no longer playing this song...
    @@ -197,7 +200,7 @@ async def cleanup(self, guild):
    await guild.voice_client.disconnect()
    except AttributeError:
    pass

    try:
    del self.players[guild.id]
    except KeyError:
    @@ -291,7 +294,7 @@ async def play_(self, ctx, *, search: str):

    # If download is False, source will be a dict which will be used later to regather the stream.
    # If download is True, source will be a discord.FFmpegPCMAudio with a VolumeTransformer.
    source = await YTDLSource.create_source(ctx, search, loop=self.bot.loop, download=True)
    source = await YTDLSource.create_source(ctx, search, loop=self.bot.loop, download=False)

    await player.queue.put(source)

    @@ -359,7 +362,7 @@ async def queue_info(self, ctx):

    @commands.command(name='now_playing', aliases=['np', 'current', 'currentsong', 'playing'])
    async def now_playing_(self, ctx):
    """Display the currently playing song."""
    """Display information about the currently playing song."""
    vc = ctx.voice_client

    if not vc or not vc.is_connected():
  2. EvieePy revised this gist Apr 2, 2018. 1 changed file with 9 additions and 3 deletions.
    12 changes: 9 additions & 3 deletions music.py
    Original file line number Diff line number Diff line change
    @@ -193,9 +193,15 @@ def __init__(self, bot):
    self.players = {}

    async def cleanup(self, guild):
    await guild.voice_client.disconnect()

    del self.players[guild.id]
    try:
    await guild.voice_client.disconnect()
    except AttributeError:
    pass

    try:
    del self.players[guild.id]
    except KeyError:
    pass

    async def __local_check(self, ctx):
    """A local check which applies to all commands in this cog."""
  3. EvieePy revised this gist Mar 15, 2018. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion music.py
    Original file line number Diff line number Diff line change
    @@ -16,7 +16,7 @@
    e.g You might like to implement a vote before skipping the song or only allow admins to stop the player.
    Music bots require lots of work, and tuning. Goodluck.
    If you find any bugs feel free to ping me on discord. @Eviee#06660
    If you find any bugs feel free to ping me on discord. @Eviee#0666
    """
    import discord
    from discord.ext import commands
  4. EvieePy revised this gist Mar 15, 2018. 1 changed file with 270 additions and 162 deletions.
    432 changes: 270 additions & 162 deletions music.py
    Original file line number Diff line number Diff line change
    @@ -1,25 +1,38 @@
    """
    Please understand Music bots are complex, and that even this basic example can be daunting to a beginner.
    For this reason it's highly advised you familiarize yourself with discord.py, python and asyncio, BEFORE
    you attempt to write a music bot.
    This example makes use of: Python 3.6
    For a more basic voice example please read:
    https://github.com/Rapptz/discord.py/blob/rewrite/examples/basic_voice.py
    This is a very basic playlist example, which allows per guild playback of unique queues.
    The commands implement very basic logic for basic usage. But allow for expansion. It would be advisable to implement
    your own permissions and usage logic for commands.
    e.g You might like to implement a vote before skipping the song or only allow admins to stop the player.
    Music bots require lots of work, and tuning. Goodluck.
    If you find any bugs feel free to ping me on discord. @Eviee#06660
    """
    import discord
    from discord.ext import commands

    import asyncio
    import youtube_dl
    import itertools
    import sys
    import traceback
    from async_timeout import timeout
    from functools import partial
    from youtube_dl import YoutubeDL

    if not discord.opus.is_loaded():
    raise RuntimeWarning('Libopus was not found. Libopus is required for music to work in discord.')

    opts = {
    ytdlopts = {
    'format': 'bestaudio/best',
    'outtmpl': 'downloads/%(extractor)s-%(id)s-%(title)s-%(autonumber)s.%(ext)s', # Autonumber to avoid conflicts
    'outtmpl': 'downloads/%(extractor)s-%(id)s-%(title)s.%(ext)s',
    'restrictfilenames': True,
    'noplaylist': True,
    'nocheckcertificate': True,
    @@ -30,279 +43,374 @@
    'default_search': 'auto',
    'source_address': '0.0.0.0' # ipv6 addresses cause issues sometimes
    }
    ytdl = youtube_dl.YoutubeDL(opts)

    ffmpeg_options = {
    ffmpegopts = {
    'before_options': '-nostdin',
    'options': '-vn'
    }

    ytdl = YoutubeDL(ytdlopts)

    class YTDLSource(discord.PCMVolumeTransformer):
    """A class which uses YTDL to retrieve a song and returns it as a source for Discord."""

    def __init__(self, source, *, data, entry, volume=.4):
    super().__init__(source, volume)
    class VoiceConnectionError(commands.CommandError):
    """Custom Exception class for connection errors."""


    class InvalidVoiceChannel(VoiceConnectionError):
    """Exception for cases of invalid Voice Channels."""


    class YTDLSource(discord.PCMVolumeTransformer):

    self.data = data
    def __init__(self, source, *, data, requester):
    super().__init__(source)
    self.requester = requester

    self.title = data.get('title')
    self.url = data.get('url')
    self.requester = entry.requester
    self.channel = entry.channel
    self.web_url = data.get('webpage_url')

    # YTDL info dicts (data) have other useful information you might want
    # https://github.com/rg3/youtube-dl/blob/master/README.md

    def __getitem__(self, item: str):
    """Allows us to access attributes similar to a dict.
    This is only useful when you are NOT downloading.
    """
    return self.__getattribute__(item)

    @classmethod
    async def from_url(cls, entry, *, loop=None, player):
    async def create_source(cls, ctx, search: str, *, loop, download=False):
    loop = loop or asyncio.get_event_loop()

    try:
    data = await loop.run_in_executor(None, ytdl.extract_info, entry.query)
    except Exception as e:
    return await entry.channel.send(f'There was an error processing your song.\n'
    f'```css\n[{e}]\n```')
    to_run = partial(ytdl.extract_info, url=search, download=download)
    data = await loop.run_in_executor(None, to_run)

    if 'entries' in data:
    # take first item from a playlist
    data = data['entries'][0]

    await entry.channel.send(f'```ini\n[Added: {data["title"]} to the queue.]\n```', delete_after=15)
    await ctx.send(f'```ini\n[Added {data["title"]} to the Queue.]\n```', delete_after=15)

    if download:
    source = ytdl.prepare_filename(data)
    else:
    return {'webpage_url': data['webpage_url'], 'requester': ctx.author, 'title': data['title']}

    return cls(discord.FFmpegPCMAudio(source), data=data, requester=ctx.author)

    @classmethod
    async def regather_stream(cls, data, *, loop):
    """Used for preparing a stream, instead of downloading.
    Since Youtube Streaming links expire."""
    loop = loop or asyncio.get_event_loop()
    requester = data['requester']

    to_run = partial(ytdl.extract_info, url=data['webpage_url'], download=False)
    data = await loop.run_in_executor(None, to_run)

    filename = ytdl.prepare_filename(data)
    await player.queue.put(cls(discord.FFmpegPCMAudio(filename, **ffmpeg_options), data=data, entry=entry,
    volume=player.volume))
    return cls(discord.FFmpegPCMAudio(data['url']), data=data, requester=requester)


    class MusicPlayer:
    """Music Player instance.
    """A class which is assigned to each guild using the bot for Music.
    Each guild using music will have a separate instance."""
    This class implements and queue and loop, which allows for different guilds to listen to different playlists
    simultaneously.
    def __init__(self, bot, ctx):
    self.bot = bot
    When the bot disconnects from the Voice it's instance will be destroyed.
    """

    __slots__ = ('bot', '_guild', '_channel', '_cog', 'queue', 'next', 'current', 'np', 'volume')

    def __init__(self, ctx):
    self.bot = ctx.bot
    self._guild = ctx.guild
    self._channel = ctx.channel
    self._cog = ctx.cog

    self.queue = asyncio.Queue()
    self.next = asyncio.Event()
    self.die = asyncio.Event()

    self.guild = ctx.guild
    self.default_chan = ctx.channel
    self.current = None
    self.volume = .4
    self.np = None # Now playing message
    self.volume = .5

    self.now_playing = None
    self.player_task = self.bot.loop.create_task(self.player_loop())
    ctx.bot.loop.create_task(self.player_loop())

    async def player_loop(self):
    """Our main player loop."""
    await self.bot.wait_until_ready()

    while not self.bot.is_closed():
    self.next.clear()

    entry = await self.queue.get()

    channel = entry.channel
    requester = entry.requester
    self.guild.voice_client.play(entry, after=lambda s: self.bot.loop.call_soon_threadsafe(self.next.set))

    self.now_playing = await channel.send(f'**Now Playing:** `{entry.title}` requested by `{requester}`')
    await self.next.wait() # Wait until the players after function is called.
    entry.cleanup()

    # You can call call async/standard functions here, right after the song has finished.
    try:
    # Wait for the next song. If we timeout cancel the player and disconnect...
    async with timeout(300): # 5 minutes...
    source = await self.queue.get()
    except asyncio.TimeoutError:
    return self.destroy(self._guild)

    if not isinstance(source, YTDLSource):
    # Source was probably a stream (not downloaded)
    # So we should regather to prevent stream expiration
    try:
    source = await YTDLSource.regather_stream(source, loop=self.bot.loop)
    except Exception as e:
    await self._channel.send(f'There was an error processing your song.\n'
    f'```css\n[{e}]\n```')
    continue

    source.volume = self.volume

    self._guild.voice_client.play(source, after=lambda _: self.bot.loop.call_soon_threadsafe(self.next.set))
    self.np = await self._channel.send(f'**Now Playing:** `{source.title}` requested by '
    f'`{source.requester}`')
    await self.next.wait()

    # Make sure the FFmpeg process is cleaned up.
    source.cleanup()

    try:
    await self.now_playing.delete()
    # We are no longer playing this song...
    await self.np.delete()
    except discord.HTTPException:
    pass

    if self.queue.empty():
    return await self.guild.voice_client.disconnect()

    # Avoid smashing together songs.
    await asyncio.sleep(.5)


    class MusicEntry:
    def __init__(self, ctx, query):
    self.requester = ctx.author
    self.channel = ctx.channel
    self.query = query
    def destroy(self, guild):
    """Disconnect and cleanup the player."""
    return self.bot.loop.create_task(self._cog.cleanup(guild))


    class Music:
    """Music Cog containing various commands for playing music.
    """Music related commands."""

    This cog supports cross guild music playing and implements a queue for playlists."""
    __slots__ = ('bot', 'players')

    def __init__(self, bot):
    self.bot = bot
    self.players = {}

    async def cleanup(self, guild):
    await guild.voice_client.disconnect()

    del self.players[guild.id]

    async def __local_check(self, ctx):
    """A check which applies to all commands in Music."""
    """A local check which applies to all commands in this cog."""
    if not ctx.guild:
    await ctx.send('Music commands can not be used in DMs.')
    return False
    raise commands.NoPrivateMessage
    return True

    async def __error(self, ctx, error):
    """A local error handler for all errors arising from commands in this cog."""
    if isinstance(error, commands.NoPrivateMessage):
    try:
    return await ctx.send('This command can not be used in Private Messages.')
    except discord.HTTPException:
    pass
    elif isinstance(error, InvalidVoiceChannel):
    await ctx.send('Error connecting to Voice Channel. '
    'Please make sure you are in a valid channel or provide me with one')

    print('Ignoring exception in command {}:'.format(ctx.command), file=sys.stderr)
    traceback.print_exception(type(error), error, error.__traceback__, file=sys.stderr)

    def get_player(self, ctx):
    """Retrieve the guild player, or generate one."""
    try:
    player = self.players[ctx.guild.id]
    except KeyError:
    player = MusicPlayer(self.bot, ctx)
    player = MusicPlayer(ctx)
    self.players[ctx.guild.id] = player

    return player

    @commands.command(name='connect', aliases=['summon', 'join', 'move'])
    async def voice_connect(self, ctx, *, channel: discord.VoiceChannel = None):
    """Summon the bot to a voice channel.
    @commands.command(name='connect', aliases=['join'])
    async def connect_(self, ctx, *, channel: discord.VoiceChannel=None):
    """Connect to voice.
    Parameters
    ------------
    channel: discord.VoiceChannel [Optional]
    The channel to connect to. If a channel is not specified, an attempt to join the voice channel you are in
    will be made.
    This command handles both summoning and moving the bot."""
    This command also handles moving the bot to different channels.
    """
    if not channel:
    try:
    channel = ctx.author.voice.channel
    except AttributeError:
    return await ctx.send('No channel to join. Please either specify a valid channel or join one.')
    raise InvalidVoiceChannel('No channel to join. Please either specify a valid channel or join one.')

    vc = ctx.guild.voice_client
    vc = ctx.voice_client

    if not vc:
    try:
    await channel.connect(timeout=15)
    except Exception as e:
    print(e)
    return await ctx.send('Unable to connect to the voice channel at this time. Please try again.')
    await ctx.send(f'Connected to: **{channel}**', delete_after=15)
    else:
    if channel.id == vc.channel.id:
    if vc:
    if vc.channel.id == channel.id:
    return
    try:
    await vc.move_to(channel)
    except Exception as e:
    print(e)
    return await ctx.send('Unable to move this channel. Perhaps missing permissions?')
    await ctx.send(f'Moved to: **{channel}**', delete_after=15)

    @commands.command(name='play')
    async def play_song(self, ctx, *, query: str):
    """Add a song to the queue.
    except asyncio.TimeoutError:
    raise VoiceConnectionError(f'Moving to channel: <{channel}> timed out.')
    else:
    try:
    await channel.connect()
    except asyncio.TimeoutError:
    raise VoiceConnectionError(f'Connecting to channel: <{channel}> timed out.')

    Uses YTDL to auto search for a song. A URL may also be provided."""
    vc = ctx.guild.voice_client
    await ctx.send(f'Connected to: **{channel}**', delete_after=20)

    if vc is None:
    await ctx.invoke(self.voice_connect)
    if not ctx.guild.voice_client:
    return
    else:
    if ctx.author not in vc.channel.members:
    return await ctx.send(f'You must be in **{vc.channel}** to request songs.', delete_after=30)
    @commands.command(name='play', aliases=['sing'])
    async def play_(self, ctx, *, search: str):
    """Request a song and add it to the queue.
    player = self.get_player(ctx)
    This command attempts to join a valid voice channel if the bot is not already in one.
    Uses YTDL to automatically search and retrieve a song.
    async with ctx.typing():
    entry = MusicEntry(ctx, query)
    async with ctx.typing():
    self.bot.loop.create_task(YTDLSource.from_url(entry, loop=self.bot.loop, player=player))
    Parameters
    ------------
    search: str [Required]
    The song to search and retrieve using YTDL. This could be a simple search, an ID or URL.
    """
    await ctx.trigger_typing()

    @commands.command(name='stop')
    async def stop_player(self, ctx):
    """Stops the player and clears the queue."""
    vc = ctx.guild.voice_client
    vc = ctx.voice_client

    if vc is None:
    return
    if not vc:
    await ctx.invoke(self.connect_)

    player = self.get_player(ctx)

    vc.stop()
    try:
    player.player_task.cancel()
    del self.players[ctx.guild.id]
    except Exception as e:
    return print(e)
    # If download is False, source will be a dict which will be used later to regather the stream.
    # If download is True, source will be a discord.FFmpegPCMAudio with a VolumeTransformer.
    source = await YTDLSource.create_source(ctx, search, loop=self.bot.loop, download=True)

    await vc.disconnect()
    await ctx.send('Disconnected from voice and cleared your queue. Goodbye!', delete_after=15)
    await player.queue.put(source)

    @commands.command(name='pause')
    async def pause_song(self, ctx):
    async def pause_(self, ctx):
    """Pause the currently playing song."""
    vc = ctx.guild.voice_client

    if vc is None or not vc.is_playing():
    return await ctx.send('I am not currently playing anything.', delete_after=20)
    vc = ctx.voice_client

    if vc.is_paused():
    return await ctx.send('I am already paused.', delete_after=20)
    if not vc or not vc.is_playing():
    return await ctx.send('I am not currently playing anything!', delete_after=20)
    elif vc.is_paused():
    return

    vc.pause()
    await ctx.send(f'{ctx.author.mention} has paused the song.')
    await ctx.send(f'**`{ctx.author}`**: Paused the song!')

    @commands.command(name='resume')
    async def resume_song(self, ctx):
    """Resume a song if it is currently paused."""
    vc = ctx.guild.voice_client
    async def resume_(self, ctx):
    """Resume the currently paused song."""
    vc = ctx.voice_client

    if vc is None or not vc.is_connected():
    return await ctx.send('I am not currently playing anything.', delete_after=20)
    if not vc or not vc.is_connected():
    return await ctx.send('I am not currently playing anything!', delete_after=20)
    elif not vc.is_paused():
    return

    if vc.is_paused():
    vc.resume()
    await ctx.send(f'{ctx.author.mention} has resumed the song.')
    vc.resume()
    await ctx.send(f'**`{ctx.author}`**: Resumed the song!')

    @commands.command(name='skip')
    async def skip_song(self, ctx):
    """Skip the current song."""
    vc = ctx.guild.voice_client
    async def skip_(self, ctx):
    """Skip the song."""
    vc = ctx.voice_client

    if vc is None or not vc.is_connected():
    return await ctx.send('I am not currently playing anything.', delete_after=20)
    if not vc or not vc.is_connected():
    return await ctx.send('I am not currently playing anything!', delete_after=20)

    if vc.is_paused():
    pass
    elif not vc.is_playing():
    return

    vc.stop()
    await ctx.send(f'{ctx.author.mention} has skipped the song.')
    await ctx.send(f'**`{ctx.author}`**: Skipped the song!')

    @commands.command(name='queue', aliases=['q', 'playlist'])
    async def queue_info(self, ctx):
    """Retrieve a basic queue of upcoming songs."""
    vc = ctx.voice_client

    if not vc or not vc.is_connected():
    return await ctx.send('I am not currently connected to voice!', delete_after=20)

    player = self.get_player(ctx)
    if player.queue.empty():
    return await ctx.send('There are currently no more queued songs.')

    # Grab up to 5 entries from the queue...
    upcoming = list(itertools.islice(player.queue._queue, 0, 5))

    fmt = '\n'.join(f'**`{_["title"]}`**' for _ in upcoming)
    embed = discord.Embed(title=f'Upcoming - Next {len(upcoming)}', description=fmt)

    await ctx.send(embed=embed)

    @commands.command(name='current', aliases=['currentsong', 'nowplaying', 'np'])
    async def current_song(self, ctx):
    """Return some information about the current song."""
    vc = ctx.guild.voice_client
    @commands.command(name='now_playing', aliases=['np', 'current', 'currentsong', 'playing'])
    async def now_playing_(self, ctx):
    """Display the currently playing song."""
    vc = ctx.voice_client

    if not vc.is_playing():
    return await ctx.send('Not currently playing anything.')
    if not vc or not vc.is_connected():
    return await ctx.send('I am not currently connected to voice!', delete_after=20)

    player = self.get_player(ctx)
    msg = player.now_playing.content
    if not player.current:
    return await ctx.send('I am not currently playing anything!')

    try:
    await player.now_playing.delete()
    # Remove our previous now_playing message.
    await player.np.delete()
    except discord.HTTPException:
    pass

    player.now_playing = await ctx.send(msg)
    player.np = await ctx.send(f'**Now Playing:** `{vc.source.title}` '
    f'requested by `{vc.source.requester}`')

    @commands.command(name='volume', aliases=['vol'])
    async def adjust_volume(self, ctx, *, vol: int):
    """Adjust the player volume."""
    async def change_volume(self, ctx, *, vol: float):
    """Change the player volume.
    Parameters
    ------------
    volume: float or int [Required]
    The volume to set the player to in percentage. This must be between 1 and 100.
    """
    vc = ctx.voice_client

    if not vc or not vc.is_connected():
    return await ctx.send('I am not currently connected to voice!', delete_after=20)

    if not 0 < vol < 101:
    return await ctx.send('Please enter a value between 1 and 100.')

    vc = ctx.guild.voice_client
    player = self.get_player(ctx)

    if vc is None:
    return await ctx.send('I am not currently connected to voice.')
    if vc.source:
    vc.source.volume = vol / 100

    player = self.get_player(ctx)
    adj = float(vol) / 100
    player.volume = vol / 100
    await ctx.send(f'**`{ctx.author}`**: Set the volume to **{vol}%**')

    try:
    vc.source.volume = adj
    except Exception:
    pass
    @commands.command(name='stop')
    async def stop_(self, ctx):
    """Stop the currently playing song and destroy the player.
    !Warning!
    This will destroy the player assigned to your guild, also deleting any queued songs and settings.
    """
    vc = ctx.voice_client

    if not vc or not vc.is_connected():
    return await ctx.send('I am not currently playing anything!', delete_after=20)

    player.volume = adj
    await ctx.send(f'Changed player volume to: **{vol}%**')
    await self.cleanup(ctx.guild)


    def setup(bot):
    bot.add_cog(Music(bot))
    bot.add_cog(Music(bot))
  5. EvieePy revised this gist Jan 15, 2018. 1 changed file with 8 additions and 6 deletions.
    14 changes: 8 additions & 6 deletions music.py
    Original file line number Diff line number Diff line change
    @@ -14,6 +14,8 @@
    import asyncio
    import youtube_dl

    if not discord.opus.is_loaded():
    raise RuntimeWarning('Libopus was not found. Libopus is required for music to work in discord.')

    opts = {
    'format': 'bestaudio/best',
    @@ -27,18 +29,18 @@
    'no_warnings': True,
    'default_search': 'auto',
    'source_address': '0.0.0.0' # ipv6 addresses cause issues sometimes
    }
    }
    ytdl = youtube_dl.YoutubeDL(opts)


    ffmpeg_options = {
    'before_options': '-nostdin',
    'options': '-vn'
    }
    }


    class YTDLSource(discord.PCMVolumeTransformer):
    """A class which uses YTDL to retrieve a song and returns it as a source for Discord."""

    def __init__(self, source, *, data, entry, volume=.4):
    super().__init__(source, volume)

    @@ -152,7 +154,7 @@ def get_player(self, ctx):
    return player

    @commands.command(name='connect', aliases=['summon', 'join', 'move'])
    async def voice_connect(self, ctx, *, channel: discord.VoiceChannel=None):
    async def voice_connect(self, ctx, *, channel: discord.VoiceChannel = None):
    """Summon the bot to a voice channel.
    This command handles both summoning and moving the bot."""
    @@ -161,9 +163,9 @@ async def voice_connect(self, ctx, *, channel: discord.VoiceChannel=None):
    channel = ctx.author.voice.channel
    except AttributeError:
    return await ctx.send('No channel to join. Please either specify a valid channel or join one.')

    vc = ctx.guild.voice_client

    if not vc:
    try:
    await channel.connect(timeout=15)
  6. EvieePy revised this gist Jan 15, 2018. 1 changed file with 4 additions and 2 deletions.
    6 changes: 4 additions & 2 deletions music.py
    Original file line number Diff line number Diff line change
    @@ -167,15 +167,17 @@ async def voice_connect(self, ctx, *, channel: discord.VoiceChannel=None):
    if not vc:
    try:
    await channel.connect(timeout=15)
    except asyncio.TimeoutError:
    except Exception as e:
    print(e)
    return await ctx.send('Unable to connect to the voice channel at this time. Please try again.')
    await ctx.send(f'Connected to: **{channel}**', delete_after=15)
    else:
    if channel.id == vc.channel.id:
    return
    try:
    await vc.move_to(channel)
    except Exception:
    except Exception as e:
    print(e)
    return await ctx.send('Unable to move this channel. Perhaps missing permissions?')
    await ctx.send(f'Moved to: **{channel}**', delete_after=15)

  7. EvieePy revised this gist Jan 15, 2018. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion music.py
    Original file line number Diff line number Diff line change
    @@ -160,7 +160,7 @@ async def voice_connect(self, ctx, *, channel: discord.VoiceChannel=None):
    try:
    channel = ctx.author.voice.channel
    except AttributeError:
    await ctx.send('No channel to join. Please either specify a valid channel or join one.')
    return await ctx.send('No channel to join. Please either specify a valid channel or join one.')

    vc = ctx.guild.voice_client

  8. EvieePy revised this gist Jan 15, 2018. 1 changed file with 6 additions and 5 deletions.
    11 changes: 6 additions & 5 deletions music.py
    Original file line number Diff line number Diff line change
    @@ -157,12 +157,13 @@ async def voice_connect(self, ctx, *, channel: discord.VoiceChannel=None):
    This command handles both summoning and moving the bot."""
    if not channel:
    channel = ctx.author.voice.channel
    try:
    channel = ctx.author.voice.channel
    except AttributeError:
    await ctx.send('No channel to join. Please either specify a valid channel or join one.')

    vc = ctx.guild.voice_client

    if not channel:
    return await ctx.send('No channel to join. Please either specify a valid channel or join one.')


    if not vc:
    try:
    await channel.connect(timeout=15)
  9. EvieePy revised this gist Jan 15, 2018. 1 changed file with 2 additions and 1 deletion.
    3 changes: 2 additions & 1 deletion music.py
    Original file line number Diff line number Diff line change
    @@ -156,7 +156,8 @@ async def voice_connect(self, ctx, *, channel: discord.VoiceChannel=None):
    """Summon the bot to a voice channel.
    This command handles both summoning and moving the bot."""
    channel = getattr(ctx.author.voice, 'channel', channel)
    if not channel:
    channel = ctx.author.voice.channel
    vc = ctx.guild.voice_client

    if not channel:
  10. EvieePy revised this gist Jan 15, 2018. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion music.py
    Original file line number Diff line number Diff line change
    @@ -169,7 +169,7 @@ async def voice_connect(self, ctx, *, channel: discord.VoiceChannel=None):
    return await ctx.send('Unable to connect to the voice channel at this time. Please try again.')
    await ctx.send(f'Connected to: **{channel}**', delete_after=15)
    else:
    if channel == vc.channel:
    if channel.id == vc.channel.id:
    return
    try:
    await vc.move_to(channel)
  11. EvieePy revised this gist Jan 2, 2018. 1 changed file with 16 additions and 34 deletions.
    50 changes: 16 additions & 34 deletions music.py
    Original file line number Diff line number Diff line change
    @@ -12,17 +12,8 @@
    from discord.ext import commands

    import asyncio
    import async_timeout
    import youtube_dl

    version = discord.__version__
    if '1.0.0a' not in version:
    raise RuntimeWarning(f'discord.py version 1.0.0a is required. Current version: {version}')


    if not discord.opus.is_loaded():
    discord.opus.load_opus('opus')


    opts = {
    'format': 'bestaudio/best',
    @@ -61,7 +52,12 @@ def __init__(self, source, *, data, entry, volume=.4):
    @classmethod
    async def from_url(cls, entry, *, loop=None, player):
    loop = loop or asyncio.get_event_loop()
    data = await loop.run_in_executor(None, ytdl.extract_info, entry.query)

    try:
    data = await loop.run_in_executor(None, ytdl.extract_info, entry.query)
    except Exception as e:
    return await entry.channel.send(f'There was an error processing your song.\n'
    f'```css\n[{e}]\n```')

    if 'entries' in data:
    data = data['entries'][0]
    @@ -91,26 +87,15 @@ def __init__(self, bot, ctx):
    self.volume = .4

    self.now_playing = None

    self.player_task = self.bot.loop.create_task(self.player_loop())
    self.inactive_task = self.bot.loop.create_task(self.inactive_check(ctx))

    async def inactive_check(self, ctx):
    await self.die.wait()
    await ctx.invoke(self.bot.get_command('stop'))

    async def player_loop(self):
    await self.bot.wait_until_ready()

    while not self.bot.is_closed():
    self.next.clear()

    try:
    with async_timeout.timeout(300): # Auto leave after 5 minutes of inactivity.
    entry = await self.queue.get()
    except asyncio.TimeoutError:
    await self.default_chan.send('I have been inactive for 5 minutes. Goodbye!')
    return self.die.set()
    entry = await self.queue.get()

    channel = entry.channel
    requester = entry.requester
    @@ -120,13 +105,18 @@ async def player_loop(self):
    await self.next.wait() # Wait until the players after function is called.
    entry.cleanup()

    # You can call call async/standard functions here, right after the song has finished.

    try:
    await self.now_playing.delete()
    except discord.HTTPException:
    pass

    if self.queue.empty():
    return await self.guild.voice_client.disconnect()

    # Avoid smashing together songs.
    await asyncio.sleep(1)
    await asyncio.sleep(.5)


    class MusicEntry:
    @@ -204,12 +194,10 @@ async def play_song(self, ctx, *, query: str):

    player = self.get_player(ctx)

    entry = MusicEntry(ctx, query)
    async with ctx.typing():
    try:
    entry = MusicEntry(ctx, query)
    async with ctx.typing():
    self.bot.loop.create_task(YTDLSource.from_url(entry, loop=self.bot.loop, player=player))
    except Exception as e:
    await ctx.send(f'There was an error with retrieving your song: {e}')

    @commands.command(name='stop')
    async def stop_player(self, ctx):
    @@ -220,7 +208,6 @@ async def stop_player(self, ctx):
    return

    player = self.get_player(ctx)
    inact = player.inactive_task

    vc.stop()
    try:
    @@ -232,11 +219,6 @@ async def stop_player(self, ctx):
    await vc.disconnect()
    await ctx.send('Disconnected from voice and cleared your queue. Goodbye!', delete_after=15)

    try:
    inact.cancel()
    except Exception as e:
    print(e)

    @commands.command(name='pause')
    async def pause_song(self, ctx):
    """Pause the currently playing song."""
    @@ -317,4 +299,4 @@ async def adjust_volume(self, ctx, *, vol: int):


    def setup(bot):
    bot.add_cog(Music(bot))
    bot.add_cog(Music(bot))
  12. EvieePy revised this gist Nov 24, 2017. 1 changed file with 2 additions and 1 deletion.
    3 changes: 2 additions & 1 deletion music.py
    Original file line number Diff line number Diff line change
    @@ -69,7 +69,8 @@ async def from_url(cls, entry, *, loop=None, player):
    await entry.channel.send(f'```ini\n[Added: {data["title"]} to the queue.]\n```', delete_after=15)

    filename = ytdl.prepare_filename(data)
    await player.queue.put(cls(discord.FFmpegPCMAudio(filename, **ffmpeg_options), data=data, entry=entry))
    await player.queue.put(cls(discord.FFmpegPCMAudio(filename, **ffmpeg_options), data=data, entry=entry,
    volume=player.volume))


    class MusicPlayer:
  13. EvieePy revised this gist Nov 24, 2017. 1 changed file with 10 additions and 7 deletions.
    17 changes: 10 additions & 7 deletions music.py
    Original file line number Diff line number Diff line change
    @@ -1,10 +1,9 @@
    """A Rewrite example of a cross guild music player with playlist support.
    """
    Please understand Music bots are complex, and that even this basic example can be daunting to a beginner.
    For this reason it's highly advised you familiarize yourself with discord.py, python and asyncio, BEFORE
    you attempt to write a music bot.
    This example makes use of: Python 3.6 and the Rewrite Branch of Discord.py.
    This example makes use of: Python 3.6
    For a more basic voice example please read:
    https://github.com/Rapptz/discord.py/blob/rewrite/examples/basic_voice.py
    @@ -16,14 +15,18 @@
    import async_timeout
    import youtube_dl

    version = discord.__version__
    if '1.0.0a' not in version:
    raise RuntimeWarning(f'discord.py version 1.0.0a is required. Current version: {version}')


    if not discord.opus.is_loaded():
    discord.opus.load_opus('opus')


    opts = {
    'format': 'bestaudio/best',
    'outtmpl': 'downloads/%(extractor)s-%(id)s-%(title)s.%(ext)s',
    'outtmpl': 'downloads/%(extractor)s-%(id)s-%(title)s-%(autonumber)s.%(ext)s', # Autonumber to avoid conflicts
    'restrictfilenames': True,
    'noplaylist': True,
    'nocheckcertificate': True,
    @@ -110,7 +113,7 @@ async def player_loop(self):

    channel = entry.channel
    requester = entry.requester
    self.guild.voice_client.play(entry, after=lambda s: self.bot.loop.call_soon_threadsafe(self.next.set()))
    self.guild.voice_client.play(entry, after=lambda s: self.bot.loop.call_soon_threadsafe(self.next.set))

    self.now_playing = await channel.send(f'**Now Playing:** `{entry.title}` requested by `{requester}`')
    await self.next.wait() # Wait until the players after function is called.
    @@ -122,7 +125,7 @@ async def player_loop(self):
    pass

    # Avoid smashing together songs.
    await asyncio.sleep(.5)
    await asyncio.sleep(1)


    class MusicEntry:
    @@ -170,7 +173,7 @@ async def voice_connect(self, ctx, *, channel: discord.VoiceChannel=None):

    if not vc:
    try:
    await channel.connect(timeout=10)
    await channel.connect(timeout=15)
    except asyncio.TimeoutError:
    return await ctx.send('Unable to connect to the voice channel at this time. Please try again.')
    await ctx.send(f'Connected to: **{channel}**', delete_after=15)
  14. EvieePy revised this gist Nov 24, 2017. 1 changed file with 27 additions and 20 deletions.
    47 changes: 27 additions & 20 deletions music.py
    Original file line number Diff line number Diff line change
    @@ -9,7 +9,6 @@
    For a more basic voice example please read:
    https://github.com/Rapptz/discord.py/blob/rewrite/examples/basic_voice.py
    """

    import discord
    from discord.ext import commands

    @@ -18,6 +17,10 @@
    import youtube_dl


    if not discord.opus.is_loaded():
    discord.opus.load_opus('opus')


    opts = {
    'format': 'bestaudio/best',
    'outtmpl': 'downloads/%(extractor)s-%(id)s-%(title)s.%(ext)s',
    @@ -42,24 +45,28 @@

    class YTDLSource(discord.PCMVolumeTransformer):
    """A class which uses YTDL to retrieve a song and returns it as a source for Discord."""
    def __init__(self, source, *, data, volume=.4):
    def __init__(self, source, *, data, entry, volume=.4):
    super().__init__(source, volume)

    self.data = data

    self.title = data.get('title')
    self.url = data.get('url')
    self.requester = entry.requester
    self.channel = entry.channel

    @classmethod
    async def from_url(cls, url, *, loop=None):
    async def from_url(cls, entry, *, loop=None, player):
    loop = loop or asyncio.get_event_loop()
    data = await loop.run_in_executor(None, ytdl.extract_info, url)
    data = await loop.run_in_executor(None, ytdl.extract_info, entry.query)

    if 'entries' in data:
    data = data['entries'][0]

    await entry.channel.send(f'```ini\n[Added: {data["title"]} to the queue.]\n```', delete_after=15)

    filename = ytdl.prepare_filename(data)
    return cls(discord.FFmpegPCMAudio(filename, **ffmpeg_options), data=data)
    await player.queue.put(cls(discord.FFmpegPCMAudio(filename, **ffmpeg_options), data=data, entry=entry))


    class MusicPlayer:
    @@ -101,25 +108,22 @@ async def player_loop(self):
    await self.default_chan.send('I have been inactive for 5 minutes. Goodbye!')
    return self.die.set()

    try:
    info = await YTDLSource.from_url(entry.query, loop=self.bot.loop)
    info.volume = self.volume
    except Exception:
    await self.default_chan.send(f'There was an error with the request: `{entry.query}`')
    continue

    channel = entry.channel
    requester = entry.requester
    self.guild.voice_client.play(info, after=lambda s: self.bot.loop.call_soon_threadsafe(self.next.set()))
    self.guild.voice_client.play(entry, after=lambda s: self.bot.loop.call_soon_threadsafe(self.next.set()))

    self.now_playing = await channel.send(f'**Now Playing:** `{info.title}` requested by `{requester}`')
    self.now_playing = await channel.send(f'**Now Playing:** `{entry.title}` requested by `{requester}`')
    await self.next.wait() # Wait until the players after function is called.
    entry.cleanup()

    try:
    await self.now_playing.delete()
    except discord.HTTPException:
    pass

    # Avoid smashing together songs.
    await asyncio.sleep(.5)


    class MusicEntry:
    def __init__(self, ctx, query):
    @@ -197,8 +201,11 @@ async def play_song(self, ctx, *, query: str):
    player = self.get_player(ctx)

    entry = MusicEntry(ctx, query)
    await player.queue.put(entry)
    await ctx.send(f'Added: `{query}` to the queue.', delete_after=30)
    async with ctx.typing():
    try:
    self.bot.loop.create_task(YTDLSource.from_url(entry, loop=self.bot.loop, player=player))
    except Exception as e:
    await ctx.send(f'There was an error with retrieving your song: {e}')

    @commands.command(name='stop')
    async def stop_player(self, ctx):
    @@ -298,11 +305,11 @@ async def adjust_volume(self, ctx, *, vol: int):

    try:
    vc.source.volume = adj
    except AttributeError:
    except Exception:
    pass
    finally:
    player.volume = adj
    await ctx.send(f'Changed player volume to: **{vol}%**')

    player.volume = adj
    await ctx.send(f'Changed player volume to: **{vol}%**')


    def setup(bot):
  15. EvieePy revised this gist Nov 22, 2017. 1 changed file with 2 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions music.py
    Original file line number Diff line number Diff line change
    @@ -117,7 +117,7 @@ async def player_loop(self):

    try:
    await self.now_playing.delete()
    except (discord.Forbidden, discord.HTTPException):
    except discord.HTTPException:
    pass


    @@ -276,7 +276,7 @@ async def current_song(self, ctx):

    try:
    await player.now_playing.delete()
    except (discord.Forbidden, discord.HTTPException):
    except discord.HTTPException:
    pass

    player.now_playing = await ctx.send(msg)
  16. EvieePy revised this gist Nov 22, 2017. 1 changed file with 2 additions and 1 deletion.
    3 changes: 2 additions & 1 deletion music.py
    Original file line number Diff line number Diff line change
    @@ -95,7 +95,7 @@ async def player_loop(self):
    self.next.clear()

    try:
    with async_timeout.timeout(10): # Auto leave after 5 minutes of inactivity.
    with async_timeout.timeout(300): # Auto leave after 5 minutes of inactivity.
    entry = await self.queue.get()
    except asyncio.TimeoutError:
    await self.default_chan.send('I have been inactive for 5 minutes. Goodbye!')
    @@ -142,6 +142,7 @@ async def __local_check(self, ctx):
    if not ctx.guild:
    await ctx.send('Music commands can not be used in DMs.')
    return False
    return True

    def get_player(self, ctx):
    try:
  17. EvieePy revised this gist Nov 22, 2017. 1 changed file with 7 additions and 1 deletion.
    8 changes: 7 additions & 1 deletion music.py
    Original file line number Diff line number Diff line change
    @@ -95,7 +95,7 @@ async def player_loop(self):
    self.next.clear()

    try:
    with async_timeout.timeout(300): # Auto leave after 5 minutes of inactivity.
    with async_timeout.timeout(10): # Auto leave after 5 minutes of inactivity.
    entry = await self.queue.get()
    except asyncio.TimeoutError:
    await self.default_chan.send('I have been inactive for 5 minutes. Goodbye!')
    @@ -137,6 +137,12 @@ def __init__(self, bot):
    self.bot = bot
    self.players = {}

    async def __local_check(self, ctx):
    """A check which applies to all commands in Music."""
    if not ctx.guild:
    await ctx.send('Music commands can not be used in DMs.')
    return False

    def get_player(self, ctx):
    try:
    player = self.players[ctx.guild.id]
  18. EvieePy revised this gist Nov 22, 2017. 1 changed file with 3 additions and 2 deletions.
    5 changes: 3 additions & 2 deletions music.py
    Original file line number Diff line number Diff line change
    @@ -202,6 +202,7 @@ async def stop_player(self, ctx):
    return

    player = self.get_player(ctx)
    inact = player.inactive_task

    vc.stop()
    try:
    @@ -212,9 +213,9 @@ async def stop_player(self, ctx):

    await vc.disconnect()
    await ctx.send('Disconnected from voice and cleared your queue. Goodbye!', delete_after=15)

    try:
    player.inactive_task.cancel()
    inact.cancel()
    except Exception as e:
    print(e)

  19. EvieePy revised this gist Nov 22, 2017. 1 changed file with 5 additions and 0 deletions.
    5 changes: 5 additions & 0 deletions music.py
    Original file line number Diff line number Diff line change
    @@ -212,6 +212,11 @@ async def stop_player(self, ctx):

    await vc.disconnect()
    await ctx.send('Disconnected from voice and cleared your queue. Goodbye!', delete_after=15)

    try:
    player.inactive_task.cancel()
    except Exception as e:
    print(e)

    @commands.command(name='pause')
    async def pause_song(self, ctx):
  20. EvieePy created this gist Nov 22, 2017.
    296 changes: 296 additions & 0 deletions music.py
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,296 @@
    """A Rewrite example of a cross guild music player with playlist support.
    Please understand Music bots are complex, and that even this basic example can be daunting to a beginner.
    For this reason it's highly advised you familiarize yourself with discord.py, python and asyncio, BEFORE
    you attempt to write a music bot.
    This example makes use of: Python 3.6 and the Rewrite Branch of Discord.py.
    For a more basic voice example please read:
    https://github.com/Rapptz/discord.py/blob/rewrite/examples/basic_voice.py
    """

    import discord
    from discord.ext import commands

    import asyncio
    import async_timeout
    import youtube_dl


    opts = {
    'format': 'bestaudio/best',
    'outtmpl': 'downloads/%(extractor)s-%(id)s-%(title)s.%(ext)s',
    'restrictfilenames': True,
    'noplaylist': True,
    'nocheckcertificate': True,
    'ignoreerrors': False,
    'logtostderr': False,
    'quiet': True,
    'no_warnings': True,
    'default_search': 'auto',
    'source_address': '0.0.0.0' # ipv6 addresses cause issues sometimes
    }
    ytdl = youtube_dl.YoutubeDL(opts)


    ffmpeg_options = {
    'before_options': '-nostdin',
    'options': '-vn'
    }


    class YTDLSource(discord.PCMVolumeTransformer):
    """A class which uses YTDL to retrieve a song and returns it as a source for Discord."""
    def __init__(self, source, *, data, volume=.4):
    super().__init__(source, volume)

    self.data = data

    self.title = data.get('title')
    self.url = data.get('url')

    @classmethod
    async def from_url(cls, url, *, loop=None):
    loop = loop or asyncio.get_event_loop()
    data = await loop.run_in_executor(None, ytdl.extract_info, url)

    if 'entries' in data:
    data = data['entries'][0]

    filename = ytdl.prepare_filename(data)
    return cls(discord.FFmpegPCMAudio(filename, **ffmpeg_options), data=data)


    class MusicPlayer:
    """Music Player instance.
    Each guild using music will have a separate instance."""

    def __init__(self, bot, ctx):
    self.bot = bot

    self.queue = asyncio.Queue()
    self.next = asyncio.Event()
    self.die = asyncio.Event()

    self.guild = ctx.guild
    self.default_chan = ctx.channel
    self.current = None
    self.volume = .4

    self.now_playing = None

    self.player_task = self.bot.loop.create_task(self.player_loop())
    self.inactive_task = self.bot.loop.create_task(self.inactive_check(ctx))

    async def inactive_check(self, ctx):
    await self.die.wait()
    await ctx.invoke(self.bot.get_command('stop'))

    async def player_loop(self):
    await self.bot.wait_until_ready()

    while not self.bot.is_closed():
    self.next.clear()

    try:
    with async_timeout.timeout(300): # Auto leave after 5 minutes of inactivity.
    entry = await self.queue.get()
    except asyncio.TimeoutError:
    await self.default_chan.send('I have been inactive for 5 minutes. Goodbye!')
    return self.die.set()

    try:
    info = await YTDLSource.from_url(entry.query, loop=self.bot.loop)
    info.volume = self.volume
    except Exception:
    await self.default_chan.send(f'There was an error with the request: `{entry.query}`')
    continue

    channel = entry.channel
    requester = entry.requester
    self.guild.voice_client.play(info, after=lambda s: self.bot.loop.call_soon_threadsafe(self.next.set()))

    self.now_playing = await channel.send(f'**Now Playing:** `{info.title}` requested by `{requester}`')
    await self.next.wait() # Wait until the players after function is called.

    try:
    await self.now_playing.delete()
    except (discord.Forbidden, discord.HTTPException):
    pass


    class MusicEntry:
    def __init__(self, ctx, query):
    self.requester = ctx.author
    self.channel = ctx.channel
    self.query = query


    class Music:
    """Music Cog containing various commands for playing music.
    This cog supports cross guild music playing and implements a queue for playlists."""

    def __init__(self, bot):
    self.bot = bot
    self.players = {}

    def get_player(self, ctx):
    try:
    player = self.players[ctx.guild.id]
    except KeyError:
    player = MusicPlayer(self.bot, ctx)
    self.players[ctx.guild.id] = player

    return player

    @commands.command(name='connect', aliases=['summon', 'join', 'move'])
    async def voice_connect(self, ctx, *, channel: discord.VoiceChannel=None):
    """Summon the bot to a voice channel.
    This command handles both summoning and moving the bot."""
    channel = getattr(ctx.author.voice, 'channel', channel)
    vc = ctx.guild.voice_client

    if not channel:
    return await ctx.send('No channel to join. Please either specify a valid channel or join one.')

    if not vc:
    try:
    await channel.connect(timeout=10)
    except asyncio.TimeoutError:
    return await ctx.send('Unable to connect to the voice channel at this time. Please try again.')
    await ctx.send(f'Connected to: **{channel}**', delete_after=15)
    else:
    if channel == vc.channel:
    return
    try:
    await vc.move_to(channel)
    except Exception:
    return await ctx.send('Unable to move this channel. Perhaps missing permissions?')
    await ctx.send(f'Moved to: **{channel}**', delete_after=15)

    @commands.command(name='play')
    async def play_song(self, ctx, *, query: str):
    """Add a song to the queue.
    Uses YTDL to auto search for a song. A URL may also be provided."""
    vc = ctx.guild.voice_client

    if vc is None:
    await ctx.invoke(self.voice_connect)
    if not ctx.guild.voice_client:
    return
    else:
    if ctx.author not in vc.channel.members:
    return await ctx.send(f'You must be in **{vc.channel}** to request songs.', delete_after=30)

    player = self.get_player(ctx)

    entry = MusicEntry(ctx, query)
    await player.queue.put(entry)
    await ctx.send(f'Added: `{query}` to the queue.', delete_after=30)

    @commands.command(name='stop')
    async def stop_player(self, ctx):
    """Stops the player and clears the queue."""
    vc = ctx.guild.voice_client

    if vc is None:
    return

    player = self.get_player(ctx)

    vc.stop()
    try:
    player.player_task.cancel()
    del self.players[ctx.guild.id]
    except Exception as e:
    return print(e)

    await vc.disconnect()
    await ctx.send('Disconnected from voice and cleared your queue. Goodbye!', delete_after=15)

    @commands.command(name='pause')
    async def pause_song(self, ctx):
    """Pause the currently playing song."""
    vc = ctx.guild.voice_client

    if vc is None or not vc.is_playing():
    return await ctx.send('I am not currently playing anything.', delete_after=20)

    if vc.is_paused():
    return await ctx.send('I am already paused.', delete_after=20)

    vc.pause()
    await ctx.send(f'{ctx.author.mention} has paused the song.')

    @commands.command(name='resume')
    async def resume_song(self, ctx):
    """Resume a song if it is currently paused."""
    vc = ctx.guild.voice_client

    if vc is None or not vc.is_connected():
    return await ctx.send('I am not currently playing anything.', delete_after=20)

    if vc.is_paused():
    vc.resume()
    await ctx.send(f'{ctx.author.mention} has resumed the song.')

    @commands.command(name='skip')
    async def skip_song(self, ctx):
    """Skip the current song."""
    vc = ctx.guild.voice_client

    if vc is None or not vc.is_connected():
    return await ctx.send('I am not currently playing anything.', delete_after=20)

    vc.stop()
    await ctx.send(f'{ctx.author.mention} has skipped the song.')

    @commands.command(name='current', aliases=['currentsong', 'nowplaying', 'np'])
    async def current_song(self, ctx):
    """Return some information about the current song."""
    vc = ctx.guild.voice_client

    if not vc.is_playing():
    return await ctx.send('Not currently playing anything.')

    player = self.get_player(ctx)
    msg = player.now_playing.content

    try:
    await player.now_playing.delete()
    except (discord.Forbidden, discord.HTTPException):
    pass

    player.now_playing = await ctx.send(msg)

    @commands.command(name='volume', aliases=['vol'])
    async def adjust_volume(self, ctx, *, vol: int):
    """Adjust the player volume."""

    if not 0 < vol < 101:
    return await ctx.send('Please enter a value between 1 and 100.')

    vc = ctx.guild.voice_client

    if vc is None:
    return await ctx.send('I am not currently connected to voice.')

    player = self.get_player(ctx)
    adj = float(vol) / 100

    try:
    vc.source.volume = adj
    except AttributeError:
    pass
    finally:
    player.volume = adj
    await ctx.send(f'Changed player volume to: **{vol}%**')


    def setup(bot):
    bot.add_cog(Music(bot))