Disconnect the music bot from voice channel - python

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

Related

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())

Get informations in a function to use it in another function DISCORD.PY

i have a little problem with my code and i don't find any solutions,...
I write a code for my discord bot :
#client.event
async def on_message(message):
if message.content.startswith("!init"):
if message.content.split()[1] == 'règles':
id_channel = 893442599019511829
embed = discord.Embed(title = 'Création bot', description = "par Archi's modo")
embed.add_field(name="Règlement de la LSPD", value="En cliquant sur l'icône ✅ vous reconnaissez avoir blablabla,...")
mess = await client.get_channel(id_channel).send(embed=embed)
await mess.add_reaction('✅')
#client.event
async def on_raw_reaction_add(payload):
id_channel = 893442599019511829
id_message = ?????
role_a_donner = "zabloublou"
message_id = payload.message_id
member = payload.member
if message_id == id_message:
guild_id = payload.guild_id
guild = discord.utils.find(lambda g : g.id == guild_id, client.guilds)
if payload.emoji.name == '✅':
role = discord.utils.get(guild.roles, name=str(role_a_donner))
else:
role = discord.utils.get(guild.role, name=payload.emoji.name)
if role is not None:
if member is not None:
await member.add_roles(role)
channel = client.get_channel(id_channel)
await channel.send(member.mention)
And i don't know how can i get id of the message sended by the bot to use it in my function on_raw_reaction_add
Can someone help me please ?
You should consider using wait_for instead for this kind of use.Your method is mostly used for reaction roles.
Here is an example of wait_for reaction grabbed from discord.py docs for wait_for:
#client.event
async def on_message(message):
if message.content.startswith('$thumb'):
channel = message.channel
await channel.send('Send me that 👍 reaction, mate')
def check(reaction, user):
return user == message.author and str(reaction.emoji) == '👍'
try:
reaction, user = await client.wait_for('reaction_add', timeout=60.0, check=check)
except asyncio.TimeoutError:
await channel.send('👎')
else:
await channel.send('👍')

Discord bot commands randomly stop working with python?

I was just adding some extra code to my discord bot to play youtube music, but then i go to run the bot and it starts as normal and works, but then when i go to test the new commands nothing works, no errors or anything, then i try the basic commands like /hello that was working fine before and that also is not doing anything. The bot as Administration role.
Ive double checked Iam not spelling commands wrong, but even if I was the on_command_error function should call. Also i did a print debug statement at the start of the /play command and not even that gets called, and I havent changed anything major. Anyone have any ideas?
from discord.ext import commands
from dotenv import load_dotenv
from lxml import html
import youtube_dl
import requests
import random
import discord
import requests
import shutil,os
# Load .env file
load_dotenv()
PREFIX = "/"
bot = commands.Bot(command_prefix=PREFIX)
# EVENTS #
#bot.event
async def on_ready():
await bot.get_channel(888736019590053898).send(f"We back online! All thanks to *sploosh* :D")
#bot.event
async def on_command_error(ctx, error):
if isinstance(error, commands.CommandNotFound):
replies = ["Err is that even a command?", "Can you type bro?", "Yeah... thats not a command buddy.", "Sorry forgot you can't spell"]
await ctx.send(random.choice(replies))
#bot.event
async def on_message(message):
if str(message.channel) == "images-videos" and message.content != "":
await message.channel.purge(limit=1)
# COMMANDS #
#bot.command()
async def hello(ctx):
# Get a random fact
url = 'http://randomfactgenerator.net/'
page = requests.get(url)
tree = html.fromstring(page.content)
hr = str(tree.xpath('/html/body/div/div[4]/div[2]/text()'))
await ctx.reply("**Hello Bozo!**\n" + "*Random Fact : *" + hr[:-9]+"]")
#bot.command()
async def randomNum(ctx, start:int = None, end:int = None):
if(start == None or end == None):
await ctx.reply("*Check your arguments!*\n```/randomNum START_NUMBER END_NUMBER```")
else:
randNum = random.randint(start, end)
await ctx.reply(f"*{randNum}*")
#bot.command()
#commands.is_owner()
async def kick(ctx, member:discord.Member = None, *, reason="You smell bozo."):
if(member == None):
await ctx.reply("*Check your arguments!*\n```/kick #MEMBER REASON(optional)```")
elif ctx.author.id in (member.id, bot.user.id):
await ctx.reply("*You cant kick yourself/me you silly.*")
else:
await member.kick(reason=reason)
#bot.command()
#commands.is_owner()
async def ban(ctx, member:discord.Member = None, *, reason="Bye Bye! :D."):
if(member == None):
await ctx.reply("*Check your arguments!*\n```/kick #MEMBER REASON(optional)```")
elif ctx.author.id in (member.id, bot.user.id):
await ctx.reply("*You cant ban yourself/me you silly.*")
else:
await member.ban(reason=reason)
#bot.command()
#commands.is_owner()
async def close_bot(ctx):
replies = ["Well bye!", "Guess I go now?", "Please let me stay..."]
await ctx.send(random.choice(replies))
await bot.close()
# YOUTUBE AUDIO PLAYER
#bot.command()
async def play(ctx, url : str):
print("WORKING")
if(url == None):
await ctx.reply("*Check your arguments!*\n```/play VIDEO_URL```")
else:
song = os.path.isfile("songs/song.mp3")
try: # Check if song exists if so remove it
if(song):
os.remove("songs/song.mp3")
except PermissionError:
await ctx.reply("*Wait for current song to end, or use 'stop' command*")
return
voiceChannel = discord.utils.get(ctx.guild.voice_channels, name="Lounge")
await voiceChannel.connect()
voice = discord.utils.get(bot.voice_clients, guild = ctx.guild)
ytdl_opts = { # Some options you need to pass in
'format': 'bestaudio/best',
'postprocessors':[{
'key':'FFmpegExtractAudio',
'preferredcodec': 'mp3',
'preferredquality': '192'
}],
}
with youtube_dl.YoutubeDL(ytdl_opts) as ydl:
ydl.download(url) # Download the vid
for file in os.listdir("./"): # Change the name and move to songs folder
if file.endswith(".mp3"):
os.rename(file, "song.mp3")
shutil.move("song.mp3", "songs")
voice.play(discord.FFmpegPCMAudio(source="songs/song.mp3")) # Play the song
#bot.command()
async def leave(ctx):
voice = discord.utils.get(bot.voice_clients, guild = ctx.guild)
if(voice.is_connected()):
voice.disconnect()
else:
await ctx.reply("*How can I leave a voice channel if I'am not connected?*")
#bot.command()
async def pause(ctx):
voice = discord.utils.get(bot.voice_clients, guild = ctx.guild)
if(voice.is_playing()):
voice.pause()
else:
await ctx.reply("*Can't pause buddy, nothings playing...*")
#bot.command()
async def resume(ctx):
voice = discord.utils.get(bot.voice_clients, guild = ctx.guild)
if(voice.is_paused()):
voice.resume()
else:
await ctx.reply("*Can't resume buddy, audio is already playing*")
#bot.command()
async def stop(ctx):
voice = discord.utils.get(bot.voice_clients, guild = ctx.guild)
voice.stop()
if __name__ == "__main__":
bot.run(os.getenv("BOT_TOKEN"))
You need to have bot.process_commands(message) inside your custom on_message events,
#bot.event
async def on_message(message):
if str(message.channel) == "images-videos" and message.content != "":
await message.delete()
await bot.process_commands(message)
I've also changed message.channel.purge(limit=1) to message.delete() because it's the proper way of deleting a message
For more info as to why this is required, see the faq

How to check if discord bot is in channel?

I kinda made this working music bot code, it join vc and play the song. But when its in voice it wont play another one, i need to disconnect him. I want that he play next song even if he is joined. How to do it?
code(part where i want the thing):
#bot.command()
async def hraj(ctx, url):
if not ctx.message.author.voice:
await ctx.send("Musíš být ve voicu ty pepego!")
return
else:
channel = ctx.message.author.voice.channel
await channel.connect()
server = ctx.message.guild
voice_channel = server.voice_client
async with ctx.typing():
player = await YTDLSource.from_url(url, loop=bot.loop)
voice_channel.play(player, after=lambda e: print("Error. %s" %e if e else None))
e = discord.Embed(title="Právě hraje:", description=f"[{player.title}]({url})", color=0x0091ff)
e.set_footer(text="Lets go jdem vibovat <333")
await ctx.send(embed=e)
You could use
voice = discord.utils.get(client.voice_clients, guild=ctx.guild)
if voice == None:
channel = ctx.message.author.voice.channel
This works because discord.utils.get(client.voice_clients, guild=ctx.guild) returns None if your bot isn't in a voice channel.
So your code would be:
#bot.command()
async def hraj(ctx, url):
voice = discord.utils.get(client.voice_clients, guild=ctx.guild)
if voice == None:
channel = ctx.message.author.voice.channel
else:
await ctx.send('Musíš být ve voicu' ty pepego)
await channel.connect()
server = ctx.message.guild
voice_channel = server.voice_client
async with ctx.typing():
player = await YTDLSource.from_url(url, loop=bot.loop)
voice_channel.play(player, after=lambda e: print("Error. %s" %e if e else None))
e = discord.Embed(title="Právě hraje:", description=f"[{player.title}]({url})", color=0x0091ff)
e.set_footer(text="Lets go jdem vibovat <333")
await ctx.send(embed=e)
Your question wasn't very clear btw.

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