How do add a ratio? - python
Hi I'm wanting to add a ratio to my vote count. Currently I'm making a music module. The music commands such as pause, skip, volume and so on are based on reactions (emojis) given by a user.
Currently to skip a song requires 5 reactions. One of the issues raised that sometimes there is less than five listening to music in a voice channel at one time and therefore cannot skip the song.
So I'm needing to implement ratio of some kind that say if 2 members are in a voice channel then 2 skips will skip that song for example. I'm a bit unsure how to implement it but I gave it a go.
More specifically I'm looking at this line:
if control == 'skip':
skip = await channel.send(f':poop: **{user.name}** voted to skip **{source.title}**. **{react.count}/5** voted.', delete_after=8)
if react.count >= 5: # bot counts as 1 reaction.
vc.stop()
await channel.send('**Skipping song...**', delete_after=5)
Here is the full code I'm working with:
if not discord.opus.is_loaded():
load_opus_lib()
ytdlopts = {
'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'
}
ffmpegopts = {
'before_options': '-nostdin -preset ultrafast',
'options': '-vn -threads 1'
}
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')
if self.title is None:
self.title = "No title available"
self.web_url = data.get('webpage_url')
self.thumbnail = data.get('thumbnail')
if self.thumbnail is None:
self.thumbnail = "http://ppc.tools/wp-content/themes/ppctools/img/no-thumbnail.jpg"
self.duration = data.get('duration')
if self.duration is None:
self.duration = 0
self.uploader = data.get('uploader')
if self.uploader is None:
self.uploader = "Unkown"
# 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':notes: **{data["title"]} added to the queue.**')
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 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.
"""
__slots__ = ('bot', '_guild', '_ctxs', '_channel', '_cog', 'queue', 'next', 'current', 'np', 'volume', 'buttons', 'music', 'music_controller', 'restmode')
def __init__(self, ctx):
self.buttons = {'⏯': 'rp',
'⏭': 'skip',
'➕': 'vol_up',
'➖': 'vol_down',
'🖼': 'thumbnail',
'⏹': 'stop',
'ℹ': 'queue',
'❔': 'tutorial'}
self.bot = ctx.bot
self._guild = ctx.guild
self._ctxs = ctx
self._channel = ctx.channel
self._cog = ctx.cog
self.queue = asyncio.Queue()
self.next = asyncio.Event()
self.np = None
self.volume = .5
self.current = None
self.music_controller = None
ctx.bot.loop.create_task(self.player_loop())
async def buttons_controller(self, guild, current, source, channel, context):
vc = guild.voice_client
vctwo = context.voice_client
for react in self.buttons:
await current.add_reaction(str(react))
def check(r, u):
if not current:
return False
elif str(r) not in self.buttons.keys():
return False
elif u.id == self.bot.user.id or r.message.id != current.id:
return False
elif u not in vc.channel.members:
return False
elif u.bot:
return False
return True
while current:
if vc is None:
return False
react, user = await self.bot.wait_for('reaction_add', check=check)
control = self.buttons.get(str(react))
if control == 'rp':
if vc.is_paused():
vc.resume()
else:
vc.pause()
await current.remove_reaction(react, user)
if control == 'skip':
skip = await channel.send(f':poop: **{user.name}** voted to skip **{source.title}**. **{react.count}/5** voted.', delete_after=8)
if react.count >= 5: # bot counts as 1 reaction.
vc.stop()
await channel.send(':track_next: **Skipping...**', delete_after=5)
if control == 'stop':
mods = get(guild.roles, name="Mods")
for member in list(guild.members):
if mods in member.roles:
await context.invoke(self.bot.get_command("stop"))
return
else:
await channel.send(':raised_hand: **Only a mod can stop and clear the queue. Try skipping the song instead.**', delete_after=5)
await current.remove_reaction(react, user)
if control == 'vol_up':
player = self._cog.get_player(context)
vctwo.source.volume += 2.5
await current.remove_reaction(react, user)
if control == 'vol_down':
player = self._cog.get_player(context)
vctwo.source.volume -= 2.5
await current.remove_reaction(react, user)
if control == 'thumbnail':
await channel.send(embed=discord.Embed(color=0x17FD6E).set_image(url=source.thumbnail).set_footer(text=f"Requested By: {source.requester} | Video Thumbnail: {source.title}", icon_url=source.requester.avatar_url), delete_after=10)
await current.remove_reaction(react, user)
if control == 'tutorial':
await channel.send(embed=discord.Embed(color=0x17FD6E).add_field(name="How to use the music controller?", value="⏯ - Pause\n⏭ - Skip\n➕ - Increase Volume\n➖ - Increase Volume\n🖼 - Get Thumbnail\n⏹ - Stop & Leave\nℹ - Queue\n❔ - Display help for music controls"), delete_after=10)
await current.remove_reaction(react, user)
if control == 'queue':
await self._cog.queue_info(context)
await current.remove_reaction(react, user)
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:
async with timeout(3500):
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'An error occured!.\n'
f'```css\n[{e}]\n```')
continue
source.volume = self.volume
self.current = source
try:
self._guild.voice_client.play(source, after=lambda _: self.bot.loop.call_soon_threadsafe(self.next.set))
except Exception:
continue
embednps = discord.Embed(color=0x17FD6E)
embednps.add_field(name="Currently Playing:", value=f"```fix\n{source.title}```", inline=False)
embednps.add_field(name="Requested By:", value=f"**{source.requester}**", inline=True)
embednps.add_field(name="Source:", value=f"**[URL]({source.web_url})**", inline=True)
embednps.add_field(name="Uploader:", value=f"**{source.uploader}**", inline=True)
embednps.add_field(name="Duration:", value=f"**{datetime.timedelta(seconds=source.duration)}**", inline=True)
embednps.set_thumbnail(url=source.thumbnail)
self.np = await self._channel.send(embed=embednps)
self.music_controller = self.bot.loop.create_task(self.buttons_controller(self._guild, self.np, source, self._channel, self._ctxs))
await self.next.wait()
# Make sure the FFmpeg process is cleaned up.
source.cleanup()
self.current = None
try:
# We are no longer playing this song...
await self.np.delete()
self.music_controller.cancel()
except Exception:
pass
def destroy(self, guild):
"""Disconnect and cleanup the player."""
return self.bot.loop.create_task(self._cog.cleanup(guild))
class Music:
"""Music cog for UKGBot."""
__slots__ = ('bot', 'players', 'musictwo', 'music_controller')
def __init__(self, bot):
self.bot = 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."""
if not ctx.guild:
raise commands.NoPrivateMessage
return True
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 __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(':notes: Command cannot be used in DM.')
except discord.HTTPException:
pass
elif isinstance(error, InvalidVoiceChannel):
await ctx.send("Connect to a voice channel first!")
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='stop', aliases=[ 'l', 'disconnect'])
#checks.is_channel_mod()
async def disconnect_(self, ctx):
"""Stops and leaves the voice channel."""
try:
channel = ctx.author.voice.channel
except AttributeError:
await ctx.send(":notes: You are not connected to a voice channel.", delete_after=20)
if not ctx.guild.voice_client:
return await ctx.send(':notes: I\'m not connected to the voice channel.', delete_after=20)
await ctx.guild.voice_client.disconnect()
await ctx.send(':wave: Stopped and left the channel.', delete_after=20)
#commands.command(name='reconnect', aliases=['rc'])
async def reconnect_(self, ctx):
try:
channel = ctx.author.voice.channel
except AttributeError:
return await ctx.send(":notes: You are not connected to a voice channel.", delete_after=20)
if ctx.guild.voice_client:
await ctx.guild.voice_client.disconnect()
await channel.connect()
#commands.command(name='connect', aliases=['join','summon'])
async def connect_(self, ctx, *, channel: discord.VoiceChannel=None):
"""connectss to a voice channel."""
try:
channel = ctx.author.voice.channel
except AttributeError:
return await ctx.send(":notes: You are not connected to a voice channel.", delete_after=20)
await channel.connect()
#commands.command(name='skip', aliases=['sk'])
#checks.is_channel_mod()
async def skip_(self, ctx, *, channel: discord.VoiceChannel=None):
"""Skips a song (Mods)."""
try:
channel = ctx.author.voice.channel
except AttributeError:
return await ctx.send(":notes: You are not connected to a voice channel.", delete_after=20)
ctx.guild.voice_client.stop()
await ctx.send(':track_next: **Skipping...**', delete_after=5)
#commands.command(name='play', aliases=['sing', 'p'])
async def play_(self, ctx, *, search: str):
"""searches for and plays a song."""
await ctx.trigger_typing()
vc = ctx.voice_client
try:
channel = ctx.author.voice.channel
if not vc:
await ctx.invoke(self.connect_)
except AttributeError:
return await ctx.send(":notes: You are not connected to a voice channel.", delete_after=20)
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=False)
await player.queue.put(source)
#commands.command(name='playing', aliases=['np', 'current', 'currentsong', 'now_playing'])
async def now_playing_(self, ctx):
"""Shows the current song playing."""
vc = ctx.voice_client
if not vc or not vc.is_connected():
return await ctx.send("I'm not connected to a voice channel..", delete_after=20)
elif ctx.author not in ctx.guild.voice_client.channel.members:
return await ctx.send("You need to be in the voice channel first!", delete_after=20)
player = self.get_player(ctx)
if not player.current:
return await ctx.send("There's nothing currently playing.", delete_after=20)
try:
# Remove our previous now_playing message.
await player.np.delete()
except discord.HTTPException:
pass
embednp = discord.Embed(color=0x17FD6E)
embednp.add_field(name="Currently Playing:", value=f"```fix\n{vc.source.title}```", inline=False)
embednp.add_field(name="Requested By:", value=f"**{vc.source.requester}**", inline=True)
embednp.add_field(name="Source:", value=f"**[URL]({vc.source.web_url})**", inline=True)
embednp.add_field(name="Uploader:", value=f"**{vc.source.uploader}**", inline=True)
embednp.add_field(name="Duration:", value=f"**{datetime.timedelta(seconds=vc.source.duration)}**", inline=True)
embednp.set_thumbnail(url=f"{vc.source.thumbnail}")
player.np = await ctx.send(embed=embednp)
self.music_controller = self.bot.loop.create_task(MusicPlayer(ctx).buttons_controller(ctx.guild, player.np, vc.source, ctx.channel, ctx))
async def queue_info(self, ctx):
player = self.get_player(ctx)
if player.queue.empty():
return await ctx.send('**:notes: No songs currently queued.**', delete_after=5)
upcoming = list(itertools.islice(player.queue._queue, 0, 5))
fmt = '\n'.join(f'**`{_["title"]}`**' for _ in upcoming)
embed = discord.Embed(title=f'{len(upcoming)} songs queued.', description=fmt, color=0x17FD6E)
await ctx.send(embed=embed)
def setup(bot):
bot.add_cog(Music(bot))
If anyone could help that would be great!
Sure, you could do something like this
if control == 'skip':
skip = await channel.send(f':poop: **{user.name}** voted to skip **{source.title}**. **{react.count}/5** voted.', delete_after=8)
try:
ratio = react.count / len(ctx.author.voice.channel.members)
except ZeroDivisionError:
pass
if ratio >= .5: # bot counts as 1 reaction.
vc.stop()
await channel.send('**Skipping song...**', delete_after=5)
where it would only skip the song if more than or exactly half of the people in the voice channel voted to skip
You could adjust the ratio you want later on
An easy way to allow all members to vote together when there are less than five is to use something like:
votes_needed_to_skip = min(current_listener_count, 5)
if react.count >= votes_needed_to_skip: # bot counts as 1 reaction.
If you want 3/4 of the people or five people, whichever comes first, to be able to skip it (be aware that 3/4 of six people is four people with this method, so only four votes are needed with six total people even though there are more than five people):
votes_needed_to_skip = min(current_listener_count*3//4, 5)
if react.count >= votes_needed_to_skip: # bot counts as 1 reaction.
Related
Problem with ffmpeg when the bot is uploaded to the heroku
I'm trying to put my music discord bot on heroku hosting. the bot uses ffmpeg to play music. i have installed all the necessary buildpacks and still nothing helps. the bot just connects to the discord server and keeps silent. I tried to install all the buildpacks that are advised, but nothing helps. music-cog for the bot: class music_cog(commands.Cog): def __init__(self, bot): self.bot = bot #all the music related stuff self.is_playing = False self.is_paused = False # 2d array containing [song, channel] self.music_queue = [] self.YDL_OPTIONS = {'format':'bestaudio', 'ignoreerrors':'True'} self.FFMPEG_OPTIONS = {'before_options': '-reconnect 1 -reconnect_streamed 1 -reconnect_delay_max 5', 'options': '-vn'} self.vc = None #searching the item on youtube def search_yt(self, url): songs = [] titles = [] thumbnail = [] webpage_url = [] duration = [] with YoutubeDL(self.YDL_OPTIONS) as ydl: if (url.startswith('https:')): info = ydl.extract_info(url, download=False) else: info = ydl.extract_info('ytsearch:'+url, download=False) if 'entries' in info: info = info['entries'] for i in info: try: songs.append(i['url']) titles.append(i['title']) thumbnail.append(i['thumbnail']) webpage_url.append(i['webpage_url']) duration.append(i['duration']) except: pass else: try: songs.append(info['url']) titles.append(info['title']) thumbnail.append(info['thumbnail']) webpage_url.append(info['webpage_url']) duration.append(info['duration']) except: pass return songs, titles, thumbnail, webpage_url, duration def play_next(self): if len(self.music_queue) > 0: print('зашли в play') self.is_playing = True #get the first url m_url = self.music_queue[0][0]['source'] #remove the first element as you are currently playing it self.music_queue.pop(0) print('перед плей') self.vc.play(discord.FFmpegPCMAudio(m_url, **self.FFMPEG_OPTIONS), after=lambda e: self.play_next()) print('после плей') else: self.is_playing = False print(self.is_playing) # infinite loop checking async def play_music(self, ctx): if len(self.music_queue) > 0: print('зашли в play_music') self.is_playing = True duration = int(self.music_queue[0][0]["duration"]/60*100)/100 duration = str(duration).replace('.', ':') embed = discord.Embed(title="Now playing", description = f"[{self.music_queue[0][0]['title']}]({self.music_queue[0][0]['webpage_url']})", color=0x00e8e4) embed.set_thumbnail(url = self.music_queue[0][0]['thumbnail']) embed.add_field(name = f'00:00 // {duration}',value = "** **", inline=True) print('обложка поставлена') await ctx.send(embed=embed) print('обложка отправлена') m_url = self.music_queue[0][0]['source'] #try to connect to voice channel if you are not already connected if self.vc == None or not self.vc.is_connected(): print('коннект к каналу') self.vc = await self.music_queue[0][1].connect() #in case we fail to connect if self.vc == None: await ctx.send("Could not connect to the voice channel") return else: await self.vc.move_to(self.music_queue[0][1]) #remove the first element as you are currently playing it self.music_queue.pop(0) print('play') self.vc.play(discord.FFmpegPCMAudio(m_url, **self.FFMPEG_OPTIONS), after=lambda e: self.play_next()) else: self.is_playing = False #commands.command(name="play", aliases=["p","playing"], help="Plays a selected song from youtube") async def play(self, ctx, *args): print('вызван play') query = " ".join(args) voice_channel = ctx.author.voice.channel if voice_channel is None: #you need to be connected so that the bot knows where to go await ctx.send("Connect to a voice channel!") elif self.is_paused: self.vc.resume() else: try: check_list = query.split('&')[1] if(check_list.startswith('list=')): await ctx.send('**Loading playlist** :repeat:') except Exception as e: print(e) songs, titles, thumbnail, webpage_url, duration = self.search_yt(query) print('получены песни') if len(songs) == 0: await ctx.send("Could not download the song. Incorrect format try another keyword. This could be due to playlist or a livestream format.") elif len(songs) == 1: print(songs) print('найдено количество') song = {'source': songs[0], 'title': titles[0], 'thumbnail': thumbnail[0], 'webpage_url':webpage_url[0], 'duration':duration[0]} await ctx.send(f"Song **{song['title']}** added to the queue") self.music_queue.append([song, voice_channel]) if self.is_playing == False: print('запустили play') await self.play_music(ctx) else: await ctx.send(f"**{len(songs)}** tracks added to queue") for i in range(len(songs)): song = {'source': songs[i], 'title': titles[i], 'thumbnail': thumbnail[i], 'webpage_url':webpage_url[i], 'duration':duration[i]} self.music_queue.append([song, voice_channel]) if self.is_playing == False: await self.play_music(ctx) #commands.command(name="pause", help="Pauses the current song being played") async def pause(self, ctx, *args): if self.is_playing: self.is_playing = False self.is_paused = True self.vc.pause() elif self.is_paused: self.is_paused = False self.is_playing = True self.vc.resume() #commands.command(name = "resume", aliases=["r"], help="Resumes playing with the discord bot") async def resume(self, ctx, *args): if self.is_paused: self.is_paused = False self.is_playing = True self.vc.resume() #commands.command(name="skip", aliases=["s"], help="Skips the current song being played") async def skip(self, ctx): if self.vc != None and self.vc: self.vc.stop() #try to play next in the queue if it exists await self.play_music(ctx) #commands.command(name="queue", aliases=["q"], help="Displays the current songs in queue") async def queue(self, ctx): retval = "" for i in range(0, len(self.music_queue)): retval += self.music_queue[i][0]['title'] + "\n" if retval != "": await ctx.send(retval) else: await ctx.send("No music in queue") #commands.command(name="clear", aliases=["c", "bin"], help="Stops the music and clears the queue") async def clear(self, ctx): if self.vc != None and self.is_playing: self.vc.stop() self.music_queue = [] await ctx.send("Music queue cleared") #commands.command(name="leave", aliases=["disconnect", "l", "d", "stop"], help="Kick the bot from VC") async def dc(self, ctx): self.is_playing = False self.is_paused = False self.music_queue = [] await self.vc.disconnect() requirements.txt: discord.py==2.0.1 youtube_dl==2021.12.17
TypeError: Music.on_wavelink_track_end() got multiple values for argument 'track'
I making discord music bot and use wavelink library. I write this code: #commands.Cog.listener() async def on_wavelink_track_end(player: wavelink.Player, track: wavelink.Track): ctx = player.ctx vc: player = ctx.voice_client if vc.loop: return await vc.play(track) next_song = vc.queue.get() await vc.play(next_song) await ctx.send(f"Сейчас играет {next_song.title}") #commands.command() async def play(self,ctx: commands.Context, *, search: wavelink.YouTubeTrack): if not ctx.author.voice: return await ctx.send("Ты не в голосовом канале!", delete_after = 10) if not ctx.voice_client: vc: wavelink.Player = await ctx.author.voice.channel.connect(cls = wavelink.Player) else: vc: wavelink.Player = ctx.voice_client if vc.queue.is_empty and vc.is_playing: await vc.play(search) m = search.duration/60 await ctx.send(f"Сейчас играет `{search.title}` продолжительностью `{round(m, 2)}` минут") else: await vc.queue.put_wait(search) await ctx.send(f"Добавлен {search.title} трек в очередь") vc.ctx = ctx setattr(vc, "loop", False) And also I got this error TypeError: Music.on_wavelink_track_end() got multiple values for argument 'track' Another commands works properly, but play command do not append to wavelink queue and just play new song if we type play again. Any ideas?
#commands.Cog.listener() async def on_wavelink_track_end(player: wavelink.Player, track: wavelink.Track): #You need to pass in `self` to here ⤴ So your code will look like this #commands.Cog.listener() async def on_wavelink_track_end(self, player: wavelink.Player, track: wavelink.Track): ctx = player.ctx vc: player = ctx.voice_client if vc.loop: return await vc.play(track) next_song = vc.queue.get() await vc.play(next_song) await ctx.send(f"Сейчас играет {next_song.title}") #commands.command() async def play(self,ctx: commands.Context, *, search: wavelink.YouTubeTrack): if not ctx.author.voice: return await ctx.send("Ты не в голосовом канале!", delete_after = 10) if not ctx.voice_client: vc: wavelink.Player = await ctx.author.voice.channel.connect(cls = wavelink.Player) else: vc: wavelink.Player = ctx.voice_client if vc.queue.is_empty and vc.is_playing: await vc.play(search) m = search.duration/60 await ctx.send(f"Сейчас играет `{search.title}` продолжительностью `{round(m, 2)}` минут") else: await vc.queue.put_wait(search) await ctx.send(f"Добавлен {search.title} трек в очередь") vc.ctx = ctx setattr(vc, "loop", False) This is probably it. • Sxviaat
simply add self async def on_wavelink_track_end(self , player: wavelink.Player, track: wavelink.Track)
How to make Discord.py music bot automatically play next song in queue
After song finishes, song is popped from the playlist queue, but does not continue playing the next song in queue. So the queue command only shows the song sitting in there. How should I change this to run the next song? playlist = [] #client.command(name='play', help='Plays music') async def play(ctx): voice = ctx.message.guild.voice_client def is_connected(): # Tests if bot is connected to voice channel voice_client = discord.utils.get(ctx.bot.voice_clients, guild=ctx.guild) return voice_client and voice_client.is_connected() url = ctx.message.content.lstrip('?play') playlist.append(url.lstrip(' ')) if not ctx.message.author.voice: await ctx.send("You are not connected to a voice channel") playlist.pop(0) return else: channel = ctx.message.author.voice.channel if is_connected(): if voice.is_playing(): # url already added to the playlist, downloads each index index = len(playlist)-1 player = await YTDLSource.from_url(playlist[index], loop=client.loop) await ctx.send('**Added:** {} to queue'.format(player.title)) playlist[index] = player.title else: server = ctx.message.guild voice_channel = server.voice_client async with ctx.typing(): player = await YTDLSource.from_url(playlist[0], loop=client.loop) voice_channel.play(player, after=lambda e: print('Player error: %s' % e) if e else ctx.send('hi')) print(player.title) await ctx.send('**Now playing:** {}'.format(player.title)) playlist[0] = player.title playlist.pop(0) else: await channel.connect() server = ctx.message.guild voice_channel = server.voice_client async with ctx.typing(): player = await YTDLSource.from_url(playlist[0], loop=client.loop) voice_channel.play(player, after=lambda e: print('Player error: %s' % e) if e else None) print(player.title) await ctx.send('**Now playing:** {}'.format(player.title)) playlist[0] = player.title```
await channel.connect() server = ctx.message.guild voice_channel = server.voice_client async with ctx.typing(): player = await YTDLSource.from_url(playlist[0], loop=client.loop) voice_channel.play(player, after=lambda e: asyncio.run_coroutine_threadsafe(after_play(ctx), client.loop)) print(player.title) await ctx.send('**Now playing:** {}'.format(player.title)) playlist[0] = player.title async def after_play(ctx): playlist.pop(0) server = ctx.message.guild voice_channel = server.voice_client player = await YTDLSource.from_url(playlist[0], loop=client.loop) voice_channel.play(player, after=lambda e: asyncio.run_coroutine_threadsafe(after_play(ctx), client.loop)) print(player.title)```
For Music related things you have to set up more than things like commands (play, skip, disconnect, etc.). Kindly look at examples on Music bots and stuff. I have one but it's old but the logic is what you need. https://github.com/Gaming-Rowdies/cs-music/
How do I implement a queue system into my Discord Music Bot?
#client.command() async def play(ctx, *, url = None): if ctx.author.voice is None: msg = await ctx.send("You are not in a voice channel.") await msg.delete(delay = 3) voice_channel = ctx.author.voice.channel if ctx.voice_client is None: await voice_channel.connect() else: await ctx.voice_client.move_to(voice_channel) search_keyword = url if not ("youtube.com/watch?" in search_keyword): search_keyword = search_keyword.replace(" ", "+") html = urllib.request.urlopen(f"https://www.youtube.com/results?search_query={search_keyword}") video_ids = re.findall(r"watch\?v=(\S{11})", html.read().decode()) url = str(f"https://www.youtube.com/watch?v={video_ids[0]}") ctx.voice_client.stop() FFMPEG_OPTIONS = {'before_options': '-reconnect 1 -reconnect_streamed 1 -reconnect_delay_max 5', 'options': '-vn'} YDL_OPTIONS = {'format': 'bestaudio', 'noplaylist':'True'} vc = ctx.voice_client with youtube_dl.YoutubeDL(YDL_OPTIONS) as ydl: info = ydl.extract_info(url, download=False) title = info.get('title', None) length = info['duration'] url2 = info["formats"][0]["url"] source = await discord. FFmpegOpusAudio.from_probe(url2, **FFMPEG_OPTIONS) vc.play(source) embed = discord.Embed(title = "Currently Playing", colour = discord.Colour.blue()) embed.add_field(name = "Song", value = title, inline = False) embed.add_field(name = "Length", value = str(datetime.timedelta(seconds = length)), inline = False) embed.add_field(name = "Link", value = url, inline = False) msg = await ctx.send(embed=embed) await msg.add_reaction("\u23F8") await msg.add_reaction("\u25B6") await msg.add_reaction("\u23F9") while True: try: reaction, user = await client.wait_for("reaction_add", check=lambda reaction, user: user.id == ctx.author.id and reaction.message.id == msg.id and reaction.emoji in ["\u23F8", "\u25B6", "\u23F9"], timeout = length) except asyncio.TimeoutError: return await msg.clear_reactions() async def process(): if reaction.emoji == "\u23F8": await msg.remove_reaction(reaction.emoji, ctx.author) ctx.voice_client.pause() elif reaction.emoji == "\u25B6": await msg.remove_reaction(reaction.emoji, ctx.author) ctx.voice_client.resume() elif reaction.emoji == "\u23F9": await msg.remove_reaction(reaction.emoji, ctx.author) ctx.voice_client.stop() asyncio.create_task(process()) Here's the code for my play command. Right now if the command is invoked while a song is playing, then that song stops and the newly requested one begins, this is because of the ctx.voice_client.stop() before the song is downloaded. However, I'm trying to implement a queue system where if the command is invoked while another song is playing it will be added to queue, and songs in the queue will be played right after the current song ends. Currently my idea of how to do this would be to replace the ctx.voice_client.stop() with an if statement that checks the status of the bot, if it is currently playing or not, and either appends it it to a global list variable containing the current queue or plays the song. But I am clueless on how to make it play the queue one after another and implement a skip command. Another complicated part would be to implement the skip command into a reaction similar to how I've implemented the pause and play commands. I don't now if my idea will work, so any input is appreciated.
first you need to creat a empty queue outside your command queues = {} and a funtion to check if there's a song in queues it will play that song #check queue queues = {} def check_queue(ctx, id): if queues[id] !={}: voice = ctx.guild.voice_client source = queues[id].pop(0) voice.play(source, after=lambda x=0: check_queue(ctx, ctx.message.guild.id)) and inside play command create a funtion to add the next song to queue if a song is already being played and add the check queue funtion if voice.is_playing(): guild_id = ctx.message.guild.id if guild_id in queues: queues[guild_id].append(source) else: queues[guild_id] = [source] else: vc.play(source, after=lambda x=0: check_queue(ctx, ctx.message.guild.id)) You can check out my code here if u need more help Need help Discord bot queue
Disconnect the music bot from voice channel
I have this as my music class to make my bot play music class Music(commands.Cog): def __init__(self, bot): self.bot = bot self.bot.music = lavalink.Client(self.bot.user.id) self.bot.music.add_node("localhost", 7000, 'server-utils', 'eu', 'music-node') self.bot.add_listener(self.bot.music.voice_update_handler, 'on_socket_response') self.bot.music.add_event_hook(self.track_hook) #commands.command(name="music") async def music(self, ctx, opt, *, arg=None): if opt == "join": print(f"music join command worked") member = discord.utils.find(lambda m: m.id == ctx.author.id, ctx.guild.members) if member is not None and member.voice is not None: vc = member.voice.channel player = self.bot.music.player_manager.create(ctx.guild.id, endpoint=str(ctx.guild.region)) if not player.is_connected: player.store('channel', ctx.guild.id) await self.connect_to(ctx.guild.id, str(vc.id)) if opt == "play" and arg is not None: try: player = self.bot.music.player_manager.get(ctx.guild.id) query = f'ytsearch:{arg}' results = await player.node.get_tracks(query) tracks = results['tracks'][0:10] i = 0 query_result = '' for track in tracks: i = i + 1 query_result = query_result + f'{i}) {track["info"]["title"]}\n' embed = discord.Embed() embed.description = query_result await ctx.channel.send(embed=embed) def check(m): return m.author.id == ctx.author.id reponse = await self.bot.wait_for('message', check=check) track = tracks[int(reponse.content)-1] player.add(requester=ctx.author.id, track=track) if not player.is_playing: await player.play() except Exception as error: print(error) if opt == "stop": try: player = self.bot.music.player_manager.get(ctx.guild.id) if player.is_playing: await player.stop() except Exception as error: print(error) if opt == "leave": player = self.bot.music.player_manager.get(ctx.guild.id) if player.is_playing: await player.stop() await self.disconnect_from(ctx.guild.id) async def track_hook(self, event): if isinstance(event, lavalink.events.QueueEndEvent): guild_id = int(event.player.guild_id) await self.connect_to(guild_id, None) async def connect_to(self, guild_id: int, channel_id: int): ws = self.bot._connection._get_websocket(guild_id) await ws.voice_state(str(guild_id), channel_id) async def disconnect_from(self, guild_id: int): ws = self.bot._connection.voice_clients print(ws) def setup(bot): bot.add_cog(Music(bot)) Everythink works fine except the part to disconnect from the channel he is connected to... I've tried lots of things and can't figure out how to make it disconnect.. Ps: Here the command doesn't do anything but to print a list where I hoped there would be the bot connect, but it had nothing there
I believe it's because you're passing the guild id as opposed to the voice channels id, for example. You're in the guild; Hello World with an ID of 12345 You're in the guilds voice channel of; Hello World voice channel which has an id of 6789. so you're trying to use the guild.id as opposed to the voice channel id. Does this make sense?
Can't you do await ctx.voice_client.disconnect() if opt == "leave": player = self.bot.music.player_manager.get(ctx.guild.id) if player.is_playing: await player.stop() await ctx.voice_client.disconnect()
I did a disconnect command on my Discord bot using VoiceClient.disconnect(). I got some trouble with it in my first approach because the connect command generated the VoiceClient object, but disconnect command could not use it since it was a local variable on the other command, so i just made it global and it works #commands.command() async def enter(self, ctx): try: canal = ctx.author.voice.channel global voice_client voice_client = await canal.connect() except AttributeError: await ctx.send("Você precisa estar conectado a um canal de voz") except discord.errors.ClientException: await ctx.send("Eu já estou conectado ao seu canal de voz") #commands.command() async def leave(self, ctx): try: await voice_client.disconnect() except NameError: await ctx.send("Eu não estou conectado a nenhum canal de voz :worried:") The full music cog that I coded: https://github.com/Voz-bonita/Discord-Bot/blob/master/Music%20extension.py