Problem with ffmpeg when the bot is uploaded to the heroku - python

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

Related

Keep getting "discord.ext.commands.errors.CommandInvokeError: Command raised an exception: KeyError: 'source'" error

Everytime I try to run play command in my bot, I get this error in the terminal, kind of new to coding so not exactly sure whats going on. It was working just fine, then it started not to work.
import discord
from discord.ext import commands
from youtube_dl import YoutubeDL
class music_cog(commands.Cog):
def __init__(self, bot):
self.bot = bot
self.is_playing = False
self.is_paused = False
self.music_queue = []
self.YDL_OPTIONS = {'format': 'bestaudio', 'noplaylist': 'True'}
self.FFMPEG_OPTIONS = {'before_options': '-reconnect 1 -reconnect_streamed 1 -reconnect_delay_max 5', 'options': '-vn'}
self.vc = None
def search_yt(self, item):
with YoutubeDL(self.YDL_OPTIONS) as ydl:
try:
info = ydl.extract_info("ytsearch:%s" % item, download=False)['entries'][0]
except Exception:
return False
return {'sourcffmpege': info['formats'][0]['url'], 'title': info['title']}
def play_next(self):
if len(self.music_queue) > 0:
self.is_playing = True
m_url = self.music_queue[0][0]['source']
self.music_queue.pop(0)
self.vc.play(discord.FFmpegPCMAudio(executable="ffmpeg", source=m_url, **self.FFMPEG_OPTIONS), after=lambda e: self.play_next())
else:
self.is_playing = False
async def play_music(self, ctx):
if len(self.music_queue) > 0:
self.is_playing = True
m_url=self.music_queue[0][0]['source']
if self.vc == None or not self.vc.is_connected():
self.vc = await self.music_queue[0][1].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])
self.music_queue.pop(0)
self.vc.play(discord.FFmpegPCMAudio(executable="ffmpeg", source=m_url, **self.FFMPEG_OPTIONS), after=lambda e: self.play_next())
#commands.command(name="play", aliases=["p", "playing"], help="Play the selected song from youtube")
async def play(self, ctx, *args):
query = " ".join(args)
voice_channel = ctx.author.voice.channel
if voice_channel is None:
await ctx.send("Connect to a voice channel!")
elif self.is_paused:
self.vc.resume()
else:
song = self.search_yt(query)
if type(song) == type(True):
await ctx.send("Could not download the song. Incorrect format, try a different keyword")
else:
await ctx.send("Song added to the queue")
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.vc.resume()
#commands.command(name="resume", aliases=["r"], help="Resumes playing the current song")
async def resume(self, ctx, *args):
if self.is_paused:
self.is_playing = True
self.is_paused = False
self.vc.resume()
#commands.command(name="skip", aliases=["s"], help="Skips the currently played song")
async def skip(self, ctx, *args):
if self.vc != None and self.vc:
self.vc.stop()
await self.play_music(ctx)
#commands.command(name="queue", aliases=["q"], help="Displays all the songs currently in the queue")
async def queue(self, ctx):
retval = ""
for i in range(0, len(self.music_queue)):
if i > 5: break
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 current song and clears the queue")
async def clear(self, ctx, *args):
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=["l"], help="Kicks the bot from the voice channel")
async def leave(self, ctx):
self.is_playing = False
self.is_paused = False
await self.vc.disconnect()
async def setup(bot):
await bot.add_cog(music_cog(bot))
this is my music_cogs.py, this is where error is coming from
was working just fine then it started to give me this error after a while.
raceback (most recent call last):
File "C:\Users\poopt\AppData\Local\Programs\Python\Python38\lib\site-packages\discord\ext\commands\core.py", line 229, in wrapped
ret = await coro(*args, **kwargs)
File "c:\Users\poopt\Code\cool_bot\cogs\music_cog.py", line 77, in play
await self.play_music(ctx)
File "c:\Users\poopt\Code\cool_bot\cogs\music_cog.py", line 44, in play_music
m_url=self.music_queue[0][0]['source']
KeyError: 'source'
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
line 1349, in invoke
await ctx.command.invoke(ctx)
File "C:\Users\poopt\AppData\Local\Programs\Python\Python38\lib\site-packages\discord\ext\commands\core.py", line 1023, in invoke
await injected(*ctx.args, **ctx.kwargs) # type: ignore
File "C:\Users\poopt\AppData\Local\Programs\Python\Python38\lib\site-packages\discord\ext\commands\core.py", line 238, in wrapped
raise CommandInvokeError(exc) from exc
discord.ext.commands.errors.CommandInvokeError: Command raised an exception: KeyError: 'source'

RuntimeWarning: coroutine 'setup' was never awaited | discord bot

So, I am making a discord bot and recently I updated my ubuntu server. When I later tried to host the discord bot in my ubuntu server the code is returning "RuntimeWarning: coroutine 'setup' was never awaited" and I can't figure out why the code wont work after the system update. When I created the discord bot I wrote it on my windows machine and transported it over to linux and mixed some things up so that it would match linux, example the os.system("cls") on windows => os.system("clear") on linux. I can run the code on windows but not on linux. The bot gets online but doesn't responde to commands.
Some strings/comments may be in Swedish since I am Swedish and the bot too :)
ERROR MESSAGE:
Bot is starting...
/home/johan/discord/abdi/bot.py:32: RuntimeWarning: coroutine 'setup' was never awaited
cogs[i].setup(bot)
Object allocated at (most recent call last):
File "/home/johan/discord/abdi/bot.py", lineno 32
cogs[i].setup(bot)
Bot logged in as: mybotstagishere
Bot is online and ready
Logging:
|
I am using multiple code files so it can be organized;
MAIN CODE:
#Import
import discord
import tracemalloc
from discord.ext import commands
import os
from datetime import date
#Cog import
import music
import normalcommands
import events
import economy
import asyncio
#Setup
os.system("clear")
print("Bot is starting...")
intents = discord.Intents.default()
#intents = discord.Intents.all()
intents.members = True
intents.reactions = True
#case_insensitive=True sätt den i bot =>
PREFIX = "!"
TOKEN = "blablaiamnotgoingtogiveyoumytokenheheheblabla"
client = discord.Client(intents=intents)
bot = commands.Bot(command_prefix=PREFIX, intents=intents, )
cogs = [music, normalcommands, events, economy]
tracemalloc.start()
for i in range(len(cogs)):
cogs[i].setup(bot)
#Startup prints
#bot.event
async def on_ready():
print("Bot logged in as: {0.user}".format(bot))
print("Bot is online and ready")
print("Logging:")
print()
print("|")
#Logs
#bot.event
async def on_message(message):
username = str(message.author).split("#")[0]
user_message = str(message.content)
#channel = str(message.channel.name)
#Logging
if message.author == bot.user:
return
else:
fullstring = user_message
substring = PREFIX
if fullstring.find(substring) != -1:
date.today()
print(date.today())
print(f"{username}: {user_message}")
print("|")
else:
pass
await bot.process_commands(message)
#Info
#bot.command(aliases=["om", "hjälp"])
async def info(ctx):
await ctx.send(f"Vanliga kommandon:**!ping, !random (min) (max), !rickroll, !blötfis, !hogrida, !beinis (personen).** **Musik kommandon: !play (url/namn), !loop, !join, !leave, !queue, !clearqueue, !stop, !pause, !resume, !skip. På vissa av musik kommandona kan man bara ta första bokstaven, exempel !j som är !join. Ekonomi system kommer senare!**")
#Startup
bot.run(TOKEN)
MUSIC CODE:
#Import
import discord
from discord.ext import commands
import requests
from youtube_dl import YoutubeDL
from discord import FFmpegPCMAudio
import asyncio
#Inställningar
YDL_OPTIONS = {
'format': 'bestaudio/best',
'noplaylist': True,
'prostprocessors': [{
'key': 'FFmpegExtractAudio',
'preferredcodec': 'mp3',
'preferredquality': '256',
}]
}
FFMPEG_OPTIONS = {'before_options': '-reconnect 1 -reconnect_streamed 1 -reconnect_delay_max 5', 'options': '-vn'}
#Variablar
#Funktioner
def search(query):
with YoutubeDL(YDL_OPTIONS) as ydl:
try:
requests.get(query)
except:
info = ydl.extract_info(f"ytsearch:{query}", download=False)['entries'][0]
else:
info = ydl.extract_info(query, download=False)
return (info, info['formats'][0]['url'])
def play_next(self,ctx):
vc = ctx.voice_client
song_queue = self.song_queue
if len(song_queue) >= 1:
query = song_queue[0]
video, source = search(query)
del song_queue[0]
vc.play(discord.FFmpegPCMAudio(source=source, **FFMPEG_OPTIONS), after=lambda e: after_song(self, ctx))
asyncio.run_coroutine_threadsafe(ctx.send(f"Nu spelas: **{video['title']}**"), self.bot.loop)
vc.is_playing()
#asyncio.run_coroutine_threadsafe(self.bot.loop)
else:
pass
def after_song(self,ctx):
if self.loop_activate == False:
try:
del self.current_song[:]
except:
pass
play_next(self,ctx)
elif self.loop_activate == True:
song_queue = self.song_queue
del song_queue[:]
query = self.current_song
video, source = search(query)
vc = ctx.voice_client
vc.play(discord.FFmpegPCMAudio(source=source, **FFMPEG_OPTIONS), after=lambda e: after_song(self,ctx))
vc.is_playing()
#Program
class music(commands.Cog):
def __init__(self, bot):
self.bot = bot
##commands.Cog.listener() / #bot.event():
##commands.command() / #bot.command():
#---------------------------------------------------
#Spel listor
song_queue = []
current_song = []
loop_activate = False
#Spela
#commands.command(aliases=["p", "spela"])
async def play(self,ctx,*,query):
emoji = '\N{THUMBS UP SIGN}'
await ctx.message.add_reaction(emoji)
member_voice = ctx.author.voice
if member_voice and member_voice.channel:
if ctx.voice_client:
pass
else:
try:
await member_voice.channel.connect()
except:
await ctx.send("Lyckades inte joina vc! Kan ha med perms på röst kanalen.")
vc = ctx.voice_client
video, source = search(query)
self.current_song.append(video['title'])
if not vc.is_playing():
#Uppspelning
vc.play(discord.FFmpegPCMAudio(source=source, **FFMPEG_OPTIONS), after=lambda e: after_song(self,ctx))
vc.is_playing()
await ctx.send(f"Nu spelas: **{video['title']}**")
else:
self.song_queue.append(video['title'])
await ctx.send(f"**{video['title']}** har lagts till i kön!")
#Repeat
#commands.command(aliases=["upprepa", "repeat"])
async def loop(self,ctx):
emoji = '\N{THUMBS UP SIGN}'
await ctx.message.add_reaction(emoji)
song_queue = self.song_queue
del song_queue[:]
if self.loop_activate == True:
self.loop_activate = False
await ctx.send("Upprepning är avstängt!")
else:
self.loop_activate = True
await ctx.send(f"Upprepning är på!")
#Join
#commands.command(aliases=["j", "joina", "gåmed"])
async def join(self,ctx):
emoji = '\N{THUMBS UP SIGN}'
await ctx.message.add_reaction(emoji)
member_voice = ctx.author.voice
if member_voice and member_voice.channel:
if ctx.voice_client:
pass
else:
try:
await member_voice.channel.connect()
except:
await ctx.send("Kunde inte gå med i samtalet:cry:, kan bero på att det är privat eller att du inte sitter i något samtal! Om jag redan sitter i ett samtal så måste du dra mig till ditt samtal.")
#Visa kön
#commands.command(aliases=["kö", "visakö"])
async def queue(self,ctx):
emoji = '\N{THUMBS UP SIGN}'
await ctx.message.add_reaction(emoji)
song_queue = self.song_queue
song_queue_length = len(song_queue)
song_queue_str = str(song_queue)
song_queue_str1 = song_queue_str.replace("[", "")
song_queue_str2 = song_queue_str1.replace("]", "")
song_queue_str3 = song_queue_str2.replace("'", "")
song_queue_show = song_queue_str3
if song_queue == []:
await ctx.send(f"Finns inget på kö!")
else:
await ctx.send(f"Det är {song_queue_length} låt/ar på kö! Kön ser ut såhär: **{song_queue_show}**")
#Töm kön
#commands.command(aliases=["clearqueue", "töm", "tömkön"])
async def clear(self,ctx):
song_queue = self.song_queue
del song_queue[:]
#Stoppa
#commands.command(aliases=["stop", "restart"])
async def stopp(self,ctx):
emoji = '\N{THUMBS UP SIGN}'
await ctx.message.add_reaction(emoji)
self.current_song = []
self.loop_activate = False
song_queue = self.song_queue
del song_queue[:]
vc = ctx.voice_client
vc.stop()
#Lämna samtal
#commands.command(aliases=["leave", "l"])
async def lämna(self,ctx):
emoji = '\N{THUMBS UP SIGN}'
await ctx.message.add_reaction(emoji)
member_voice = ctx.author.voice
if member_voice and member_voice.channel:
if ctx.voice_client:
if member_voice.channel == ctx.voice_client.channel:
try:
await ctx.voice_client.disconnect()
except:
await ctx.send("Kunde inte lämna!")
else:
await ctx.send("Du måste vara i samma samtal som mig!")
else:
pass
#Pausa
#commands.command(aliases=["pausa"])
async def pause(self,ctx):
emoji = '\N{THUMBS UP SIGN}'
await ctx.message.add_reaction(emoji)
vc = ctx.voice_client
try:
vc.pause()
except:
await ctx.send("Lyckades inte pausa!")
#Fortsätt spela
#commands.command(aliases=["continue", "r", "fortsätt"])
async def resume(self,ctx):
emoji = '\N{THUMBS UP SIGN}'
await ctx.message.add_reaction(emoji)
vc = ctx.voice_client
try:
vc.resume()
except:
await ctx.send("Lyckades inte fortsätta spela upp!")
#Skippa
#commands.command(aliases=["s", "hoppa", "skip"])
async def skippa(self,ctx):
emoji = '\N{THUMBS UP SIGN}'
await ctx.message.add_reaction(emoji)
vc = ctx.voice_client
try:
vc.stop()
except:
await ctx.send("Kunde inte skippa!")
#Add cog
async def setup(bot):
await bot.add_cog(music(bot))
Your setup function in your separate modules like music is an async function, and if you notice in line 32 cogs[i].setup(bot) is not awaited where you're calling it in the loop. In order to await in the iteration, you may use a wait wrapper around a list of tasks containing your setup calls.
def init_configs(bot):
loop = asyncio.get_event_loop()
tasks = [config.setup(bot) for config in cogs]
loop.run_until_complete(asyncio.wait(tasks))
init_configs(bot)
# ...
Or if you want to use async/await:
import asyncio
async def init_configs(bot):
tasks = [config.setup(bot) for config in cogs]
await asyncio.wait(tasks)
import asyncio
# ...
async def main():
await init_configs(bot)
# All the bot codes like:
#bot.event
async def on_message(message):
# ...
asyncio.run(main())

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

How do add a ratio?

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.

Categories