Last active
August 30, 2025 05:56
-
-
Save EvieePy/ab667b74e9758433b3eb806c53a19f34 to your computer and use it in GitHub Desktop.
Revisions
-
EvieePy revised this gist
Apr 13, 2018 . 1 changed file with 7 additions and 4 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal 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 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=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 information about the currently playing song.""" vc = ctx.voice_client if not vc or not vc.is_connected(): -
EvieePy revised this gist
Apr 2, 2018 . 1 changed file with 9 additions and 3 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -193,9 +193,15 @@ def __init__(self, bot): self.players = {} async def cleanup(self, guild): 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.""" -
EvieePy revised this gist
Mar 15, 2018 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal 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#0666 """ import discord from discord.ext import commands -
EvieePy revised this gist
Mar 15, 2018 . 1 changed file with 270 additions and 162 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal 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 itertools import sys import traceback from async_timeout import timeout from functools import partial from youtube_dl import YoutubeDL ytdlopts = { 'format': 'bestaudio/best', '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 } ffmpegopts = { 'before_options': '-nostdin', 'options': '-vn' } ytdl = YoutubeDL(ytdlopts) class VoiceConnectionError(commands.CommandError): """Custom Exception class for connection errors.""" class InvalidVoiceChannel(VoiceConnectionError): """Exception for cases of invalid Voice Channels.""" class YTDLSource(discord.PCMVolumeTransformer): def __init__(self, source, *, data, requester): super().__init__(source) self.requester = requester self.title = data.get('title') 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 create_source(cls, ctx, search: str, *, loop, download=False): loop = loop or asyncio.get_event_loop() 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 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) return cls(discord.FFmpegPCMAudio(data['url']), data=data, requester=requester) 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 simultaneously. 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.np = None # Now playing message self.volume = .5 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() 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: # We are no longer playing this song... await self.np.delete() except discord.HTTPException: pass def destroy(self, guild): """Disconnect and cleanup the player.""" return self.bot.loop.create_task(self._cog.cleanup(guild)) class Music: """Music related commands.""" __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 local check which applies to all commands in this cog.""" if not ctx.guild: 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(ctx) self.players[ctx.guild.id] = player return player @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 also handles moving the bot to different channels. """ if not channel: try: channel = ctx.author.voice.channel except AttributeError: raise InvalidVoiceChannel('No channel to join. Please either specify a valid channel or join one.') vc = ctx.voice_client if vc: if vc.channel.id == channel.id: return try: await vc.move_to(channel) 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.') await ctx.send(f'Connected to: **{channel}**', delete_after=20) @commands.command(name='play', aliases=['sing']) async def play_(self, ctx, *, search: str): """Request a song and add it to the queue. 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. 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() vc = ctx.voice_client if not vc: await ctx.invoke(self.connect_) player = self.get_player(ctx) # 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 player.queue.put(source) @commands.command(name='pause') async def pause_(self, ctx): """Pause the currently playing song.""" vc = ctx.voice_client 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}`**: Paused the song!') @commands.command(name='resume') async def resume_(self, ctx): """Resume the currently paused song.""" 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) elif not vc.is_paused(): return vc.resume() await ctx.send(f'**`{ctx.author}`**: Resumed the song!') @commands.command(name='skip') async def skip_(self, ctx): """Skip the song.""" 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) if vc.is_paused(): pass elif not vc.is_playing(): return vc.stop() 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='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 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 not player.current: return await ctx.send('I am not currently playing anything!') try: # Remove our previous now_playing message. await player.np.delete() except discord.HTTPException: pass 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 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.') player = self.get_player(ctx) if vc.source: vc.source.volume = vol / 100 player.volume = vol / 100 await ctx.send(f'**`{ctx.author}`**: Set the volume to **{vol}%**') @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) await self.cleanup(ctx.guild) def setup(bot): bot.add_cog(Music(bot)) -
EvieePy revised this gist
Jan 15, 2018 . 1 changed file with 8 additions and 6 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal 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): """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) -
EvieePy revised this gist
Jan 15, 2018 . 1 changed file with 4 additions and 2 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal 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 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 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) -
EvieePy revised this gist
Jan 15, 2018 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal 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: return await ctx.send('No channel to join. Please either specify a valid channel or join one.') vc = ctx.guild.voice_client -
EvieePy revised this gist
Jan 15, 2018 . 1 changed file with 6 additions and 5 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal 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: 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 vc: try: await channel.connect(timeout=15) -
EvieePy revised this gist
Jan 15, 2018 . 1 changed file with 2 additions and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal 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.""" if not channel: channel = ctx.author.voice.channel vc = ctx.guild.voice_client if not channel: -
EvieePy revised this gist
Jan 15, 2018 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal 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.id == vc.channel.id: return try: await vc.move_to(channel) -
EvieePy revised this gist
Jan 2, 2018 . 1 changed file with 16 additions and 34 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -12,17 +12,8 @@ from discord.ext import commands import asyncio import youtube_dl 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() 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()) async def player_loop(self): 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 @@ -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(.5) class MusicEntry: @@ -204,12 +194,10 @@ async def play_song(self, ctx, *, query: str): player = self.get_player(ctx) 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)) @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) 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) @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)) -
EvieePy revised this gist
Nov 24, 2017 . 1 changed file with 2 additions and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal 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, volume=player.volume)) class MusicPlayer: -
EvieePy revised this gist
Nov 24, 2017 . 1 changed file with 10 additions and 7 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,10 +1,9 @@ """ 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 @@ -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-%(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.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(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=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) -
EvieePy revised this gist
Nov 24, 2017 . 1 changed file with 27 additions and 20 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal 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, 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, entry, *, loop=None, player): loop = loop or asyncio.get_event_loop() 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) 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() 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() 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) 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 Exception: pass player.volume = adj await ctx.send(f'Changed player volume to: **{vol}%**') def setup(bot): -
EvieePy revised this gist
Nov 22, 2017 . 1 changed file with 2 additions and 2 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal 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.HTTPException: pass @@ -276,7 +276,7 @@ async def current_song(self, ctx): try: await player.now_playing.delete() except discord.HTTPException: pass player.now_playing = await ctx.send(msg) -
EvieePy revised this gist
Nov 22, 2017 . 1 changed file with 2 additions and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal 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. 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: -
EvieePy revised this gist
Nov 22, 2017 . 1 changed file with 7 additions and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal 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. 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] -
EvieePy revised this gist
Nov 22, 2017 . 1 changed file with 3 additions and 2 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal 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: inact.cancel() except Exception as e: print(e) -
EvieePy revised this gist
Nov 22, 2017 . 1 changed file with 5 additions and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal 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): -
EvieePy created this gist
Nov 22, 2017 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal 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))