aboutsummaryrefslogtreecommitdiff
path: root/examples
diff options
context:
space:
mode:
authorRapptz <[email protected]>2016-05-12 06:02:15 -0400
committerRapptz <[email protected]>2016-05-12 06:06:39 -0400
commit493bffc68553c6cf950d05834ada9ca28fd22e00 (patch)
tree1bb8a9138b1214226baade5be184802c2762f9d7 /examples
parentMake Player threads into daemon threads. (diff)
downloaddiscord.py-493bffc68553c6cf950d05834ada9ca28fd22e00.tar.xz
discord.py-493bffc68553c6cf950d05834ada9ca28fd22e00.zip
Rework playlist example to work with multi-server voice.
Diffstat (limited to 'examples')
-rw-r--r--examples/playlist.py303
1 files changed, 223 insertions, 80 deletions
diff --git a/examples/playlist.py b/examples/playlist.py
index 3dc4cc11..7607b0cf 100644
--- a/examples/playlist.py
+++ b/examples/playlist.py
@@ -1,103 +1,246 @@
import asyncio
import discord
+from discord.ext import commands
if not discord.opus.is_loaded():
# the 'opus' library here is opus.dll on windows
# or libopus.so on linux in the current directory
# you should replace this with the location the
# opus library is located in and with the proper filename.
+ # note that on windows this DLL is automatically provided for you
discord.opus.load_opus('opus')
class VoiceEntry:
- def __init__(self, message, song):
+ def __init__(self, message, player):
self.requester = message.author
self.channel = message.channel
- self.song = song
+ self.player = player
-class Bot(discord.Client):
- def __init__(self):
- super().__init__()
- self.songs = asyncio.Queue()
- self.play_next_song = asyncio.Event()
- self.starter = None
- self.player = None
- self.current = None
-
- def toggle_next_song(self):
- self.loop.call_soon_threadsafe(self.play_next_song.set)
+ def __str__(self):
+ fmt = '*{0.title}* uploaded by {0.uploader} and requested by {1.display_name}'
+ duration = self.player.duration
+ if duration:
+ fmt = fmt + ' [length: {0[0]}m {0[1]}s]'.format(divmod(duration, 60))
+ return fmt.format(self.player, self.requester)
- def can_control_song(self, author):
- return author == self.starter or (self.current is not None and author == self.current.requester)
+class VoiceState:
+ def __init__(self, bot):
+ self.current = None
+ self.voice = None
+ self.bot = bot
+ self.play_next_song = asyncio.Event()
+ self.songs = asyncio.Queue()
+ self.skip_votes = set() # a set of user_ids that voted
+ self.audio_player = self.bot.loop.create_task(self.audio_player_task())
def is_playing(self):
- return self.player is not None and self.player.is_playing()
+ if self.voice is None or self.current is None:
+ return False
+
+ player = self.current.player
+ return not player.is_done()
+
+ @property
+ def player(self):
+ return self.current.player
+
+ def skip(self):
+ self.skip_votes.clear()
+ if self.is_playing():
+ self.player.stop()
+
+ def toggle_next(self):
+ self.bot.loop.call_soon_threadsafe(self.play_next_song.set)
+
+ async def audio_player_task(self):
+ while True:
+ self.play_next_song.clear()
+ self.current = await self.songs.get()
+ await self.bot.send_message(self.current.channel, 'Now playing ' + str(self.current))
+ self.current.player.start()
+ await self.play_next_song.wait()
+
+class Music:
+ """Voice related commands.
+
+ Works in multiple servers at once.
+ """
+ def __init__(self, bot):
+ self.bot = bot
+ self.voice_states = {}
+
+ def get_voice_state(self, server):
+ state = self.voice_states.get(server.id)
+ if state is None:
+ state = VoiceState(self.bot)
+ self.voice_states[server.id] = state
+
+ return state
+
+ async def create_voice_client(self, channel):
+ voice = await self.bot.join_voice_channel(channel)
+ state = self.get_voice_state(channel.server)
+ state.voice = voice
+
+ def __unload(self):
+ for state in self.voice_states.values():
+ try:
+ state.audio_player.cancel()
+ if state.voice:
+ self.bot.loop.create_task(state.voice.disconnect())
+ except:
+ pass
+
+ @commands.command(pass_context=True, no_pm=True)
+ async def join(self, ctx, *, channel : discord.Channel):
+ """Joins a voice channel."""
+ try:
+ await self.create_voice_client(channel)
+ except discord.ClientException:
+ await self.bot.say('Already in a voice channel...')
+ except discord.InvalidArgument:
+ await self.bot.say('This is not a voice channel...')
+ else:
+ await self.bot.say('Ready to play audio in ' + channel.name)
+
+ @commands.command(pass_context=True, no_pm=True)
+ async def summon(self, ctx):
+ """Summons the bot to join your voice channel."""
+ summoned_channel = ctx.message.author.voice_channel
+ if summoned_channel is None:
+ await self.bot.say('You are not in a voice channel.')
+ return False
+
+ state = self.get_voice_state(ctx.message.server)
+ if state.voice is None:
+ state.voice = await self.bot.join_voice_channel(summoned_channel)
+ else:
+ await state.voice.move_to(summoned_channel)
+
+ return True
+
+ @commands.command(pass_context=True, no_pm=True)
+ async def play(self, ctx, *, song : str):
+ """Plays a song.
+
+ If there is a song currently in the queue, then it is
+ queued until the next song is done playing.
+
+ This command automatically searches as well from YouTube.
+ The list of supported sites can be found here:
+ https://rg3.github.io/youtube-dl/supportedsites.html
+ """
+ state = self.get_voice_state(ctx.message.server)
+ opts = {
+ 'default_search': 'auto',
+ 'quiet': True,
+ }
+
+ if state.voice is None:
+ success = await ctx.invoke(self.summon)
+ if not success:
+ return
- async def on_message(self, message):
- if message.author == self.user:
+ try:
+ player = await state.voice.create_ytdl_player(song, ytdl_options=opts, after=state.toggle_next)
+ except Exception as e:
+ fmt = 'An error occurred while processing this request: ```py\n{}: {}\n```'
+ await self.bot.send_message(ctx.message.channel, fmt.format(type(e).__name__, e))
+ else:
+ player.volume = 0.6
+ entry = VoiceEntry(ctx.message, player)
+ await self.bot.say('Enqueued ' + str(entry))
+ await state.songs.put(entry)
+
+ @commands.command(pass_context=True, no_pm=True)
+ async def volume(self, ctx, value : int):
+ """Sets the volume of the currently playing song."""
+
+ state = self.get_voice_state(ctx.message.server)
+ if state.is_playing():
+ player = state.player
+ player.volume = value / 100
+ await self.bot.say('Set the volume to {:.0%}'.format(player.volume))
+
+ @commands.command(pass_context=True, no_pm=True)
+ async def pause(self, ctx):
+ """Pauses the currently played song."""
+ state = self.get_voice_state(ctx.message.server)
+ if state.is_playing():
+ player = state.player
+ player.pause()
+
+ @commands.command(pass_context=True, no_pm=True)
+ async def resume(self, ctx):
+ """Resumes the currently played song."""
+ state = self.get_voice_state(ctx.message.server)
+ if state.is_playing():
+ player = state.player
+ player.resume()
+
+ @commands.command(pass_context=True, no_pm=True)
+ async def stop(self, ctx):
+ """Stops playing audio and leaves the voice channel.
+
+ This also clears the queue.
+ """
+ server = ctx.message.server
+ state = self.get_voice_state(server)
+
+ if state.is_playing():
+ player = state.player
+ player.stop()
+
+ try:
+ state.audio_player.cancel()
+ del self.voice_states[server.id]
+ await state.voice.disconnect()
+ except:
+ pass
+
+ @commands.command(pass_context=True, no_pm=True)
+ async def skip(self, ctx):
+ """Vote to skip a song. The song requester can automatically skip.
+
+ 3 skip votes are needed for the song to be skipped.
+ """
+
+ state = self.get_voice_state(ctx.message.server)
+ if not state.is_playing():
+ await self.bot.say('Not playing any music right now...')
return
- if message.channel.is_private:
- await self.send_message(message.channel, 'You cannot use this bot in private messages.')
-
- elif message.content.startswith('$join'):
- if self.is_voice_connected():
- await self.send_message(message.channel, 'Already connected to a voice channel')
- channel_name = message.content[5:].strip()
- check = lambda c: c.name == channel_name and c.type == discord.ChannelType.voice
- channel = discord.utils.find(check, message.server.channels)
- if channel is None:
- await self.send_message(message.channel, 'Cannot find a voice channel by that name.')
+ voter = ctx.message.author
+ if voter == state.current.requester:
+ await self.bot.say('Requester requested skipping song...')
+ state.skip()
+ elif voter.id not in state.skip_votes:
+ state.skip_votes.add(voter.id)
+ total_votes = len(state.skip_votes)
+ if total_votes >= 3:
+ await self.bot.say('Skip vote passed, skipping song...')
+ state.skip()
else:
- await self.join_voice_channel(channel)
- self.starter = message.author
+ await self.bot.say('Skip vote added, currently at [{}/3]'.format(total_votes))
+ else:
+ await self.bot.say('You have already voted to skip this song.')
+
+ @commands.command(pass_context=True, no_pm=True)
+ async def playing(self, ctx):
+ """Shows info about the currently played song."""
+
+ state = self.get_voice_state(ctx.message.server)
+ if state.current is None:
+ await self.bot.say('Not playing anything.')
+ else:
+ skip_count = len(state.skip_votes)
+ await self.bot.say('Now playing {} [skips: {}/3]'.format(state.current, skip_count))
+
+bot = commands.Bot(command_prefix=commands.when_mentioned_or('$'), description='A playlist example for discord.py')
+bot.add_cog(Music(bot))
+
+async def on_ready():
+ print('Logged in as:\n{0} (ID: {0.id})'.format(bot.user))
- elif message.content.startswith('$leave'):
- if not self.can_control_song(message.author):
- return
- self.starter = None
- await self.voice.disconnect()
-
- elif message.content.startswith('$pause'):
- if not self.can_control_song(message.author):
- fmt = 'Only the requester ({0.current.requester}) can control this song'
- await self.send_message(message.channel, fmt.format(self))
- elif self.player.is_playing():
- self.player.pause()
-
- elif message.content.startswith('$resume'):
- if not self.can_control_song(message.author):
- fmt = 'Only the requester ({0.current.requester}) can control this song'
- await self.send_message(message.channel, fmt.format(self))
- elif self.player is not None and not self.is_playing():
- self.player.resume()
-
- elif message.content.startswith('$next'):
- filename = message.content[5:].strip()
- await self.songs.put(VoiceEntry(message, filename))
- await self.send_message(message.channel, 'Successfully registered {}'.format(filename))
-
- elif message.content.startswith('$play'):
- if self.player is not None and self.player.is_playing():
- await self.send_message(message.channel, 'Already playing a song')
- return
- while True:
- if not self.is_voice_connected():
- await self.send_message(message.channel, 'Not connected to a voice channel')
- return
- self.play_next_song.clear()
- self.current = await self.songs.get()
- self.player = self.voice.create_ffmpeg_player(self.current.song, after=self.toggle_next_song)
- self.player.start()
- fmt = 'Playing song "{0.song}" from {0.requester}'
- await self.send_message(self.current.channel, fmt.format(self.current))
- await self.play_next_song.wait()
-
- async def on_ready(self):
- print('Logged in as')
- print(self.user.name)
- print(self.user.id)
- print('------')
-
-
-bot = Bot()
bot.run('token')