How to queue music by using the play command - python-3.x

I'm working on a Discord music Bot and I'm now at the part that I want the bot to be able to add a song to the queue whenever someone commands it to play a song while it is already playing one.
(I don't want to have a seperate "queue" command, I want it to do it automatically)
So far this is my code (only the functions that matter):
async def play_queue(self, ctx: discord.ext.commands.context.Context):
audio = await self.get_audio(self.queue.pop(0))
vc = ctx.voice_client
if not self.is_playing(ctx):
vc.play(audio)
if len(self.queue) > 0:
await self.play_queue(ctx)
#commands.command(name="play", pass_ctx=True)
async def play(self, ctx: discord.ext.commands.context.Context, url=""):
await self.handle_connected(ctx)
self.queue.append(url)
# send a message to inform that a song has been queued or being played
await self.send_playing(ctx)
await self.play_queue(ctx)
I can Q 1 song at a time but can't queue multiple because it's stuck in the while loop

Done it by using the modules: threading, asyncio
This is the code segment that works:
async def idle_speaker(self, ctx: discord.ext.commands.context.Context):
while self.is_playing(ctx):
time.sleep(1)
await self.play_queue(ctx)
def wait_for_idle(self, ctx: discord.ext.commands.context.Context):
self.thread = True
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
loop.run_until_complete(self.idle_speaker(ctx))
loop.close()
self.thread = False
async def play_queue(self, ctx: discord.ext.commands.context.Context):
if not self.is_playing(ctx):
audio = await self.get_audio(self.queue.pop(0))
ctx.voice_client.play(audio)
if len(self.queue) > 0:
playing_t = threading.Thread(target=self.wait_for_idle, name="Song_Listener", args=[ctx])
playing_t.start()
#commands.command(name="play", pass_ctx=True)
async def play(self, ctx: discord.ext.commands.context.Context, url=""):
await self.handle_connected(ctx)
self.queue.append(url)
print(self.queue, self.is_playing(ctx))
# send a message to inform that a song has been queued or being played
await self.send_playing(ctx)
if not self.thread:
await self.play_queue(ctx)

Related

Need to parse two sessions at the same time with telethon on Python

i have some troubles with parsing two or more sessions at the same time with telethon. I have tried this:
class NewSession:
def __init__(self, session_name):
self.client = TelegramClient(session_name, api_id, api_hash)
self.session_name = session_name
async def pool(self):
print("working with:", self.session_name)
#self.client.on(events.NewMessage(outgoing=True))
async def main(event):
message = event.message.to_dict()
msg_text = message['message']
print(msg_text)
try:
await self.client.start()
await self.client.run_until_disconnected()
finally:
await self.client.disconnect()
async def main():
user = NewSession("321")
user2 = NewSession("123")
await user.pool()
await user2.pool()
if __name__ == '__main__':
asyncio.run(main())
But only one is working. Need help :)
The problem is inside your main function. When you await for a coroutine to return it doesn't mean that the execution continues to the next expression. So, in your code the line await user2.pool() is going to be executed only when the user.poll() coroutines returns a value, this is when the session '321' is disconnected.
You need to run the tasks concurrently; you can use the function asyncio.gather. Reworking your main:
async def main():
user = NewSession("321")
user2 = NewSession("123")
await asyncio.gather(user.pool(), user2.pool())

D.py/rewrite - Confessions System

I made a confessions system but there’s some things that are wrong with it. How would I make it so when users want to type, they don’t have to put in *confess and they can just type whatever they want without needing to use a command? And how do I make a mod logs channel to log the deleted confessions with the author name, etc.?
import discord
from discord.ext import commands
class Confess(commands.Cog):
def __init__(self, client: discord.ext.commands.Bot):
self.client = client
#commands.command()
async def confess(self, ctx: commands.Context, *, message: str):
channel = self.client.get_channel(806649868314869760)
await ctx.message.delete()
embed = discord.Embed(title="Success", description=f"I've received your confession and sent it to the <#806649874379964487> channel!")
embed.set_footer(text="Confessions")
await ctx.send(embed=embed, delete_after=10)
channel = self.client.get_channel(806649874379964487)
embed = discord.Embed(title="Confession", description=f"{message}")
embed.set_footer(text="All confessions are anonymous.")
await channel.send(embed=embed)
def setup(client):
client.add_cog(Confess(client))
For the first question
If you want to use a "command" without actually using a command you could make an on_message event, check the id of the channel (like a confessions channel) and if it matches then do the thing
Example:
#commands.Cog.listener()
async def on_message(message):
if message.channel.id == some_channel_id_here:
channel = self.client.get_channel(806649868314869760)
await message.delete()
embed = discord.Embed(title="Success", description=f"I've received your confession and sent it to the <#806649874379964487> channel!")
embed.set_footer(text="Confessions")
await message.channel.send(embed=embed, delete_after=10)
channel = self.client.get_channel(806649874379964487)
embed = discord.Embed(title="Confession", description=f"{message}")
embed.set_footer(text="All confessions are anonymous.")
await channel.send(embed=embed)
For the second question
You can use get_channel again to get the log channel and post in there. (If you mean't on how to check if someone deleted a message/confession, use on_message_delete)
Example:
#commands.command()
async def confess(self, ctx: commands.Context, *, message: str):
channel = self.client.get_channel(806649868314869760)
log_channel = self.client.get_channel(log_channel_id)
await ctx.message.delete()
embed = discord.Embed(title="Success", description=f"I've received your confession and sent it to the <#806649874379964487> channel!")
embed.set_footer(text="Confessions")
await ctx.send(embed=embed, delete_after=10)
channel = self.client.get_channel(806649874379964487)
embed = discord.Embed(title="Confession", description=f"{message}")
embed.set_footer(text="All confessions are anonymous.")
await channel.send(embed=embed)
await logchannel.send("User confessed something!")

ctx.voice_client.pause() causes bot to leave the voice call rather than to pause the audio

I am trying to figure out why my bot is leaving the voice channel rather than pausing the currently playing audio. Here is my code along with other code I have tried:
#commands.command(brief = "Plays a locally hosted song.")
async def play(self, ctx, *, arg):
await ctx.send(f"Joining voice channel: **{ctx.author.voice.channel}**")
destination = ctx.author.voice.channel
await destination.connect()
await ctx.send(f"Joined voice channel: **{ctx.author.voice.channel}**")
voice = discord.utils.get(self.client.voice_clients, guild=ctx.guild)
voice.play(discord.FFmpegPCMAudio(source=f"./audio/{arg}.flac"))
while voice.is_playing():
await asyncio.sleep(.1)
await voice.disconnect()
#commands.command(brief = "Pauses the currently playing song.")
async def pause(self, ctx):
ctx.voice_client.pause()
I have also tried:
#commands.command(brief = "Pauses the currently playing song.")
async def pause(self, ctx):
voice = discord.utils.get(self.client.voice_clients, guild=ctx.guild)
if voice.is_playing():
voice.pause()
else:
await ctx.send("There is currently no audio playing.")
However, this achieves the same result as before. The bot is also not throwing any errors when it disconnects from the call. Any help would be appreciated.
I found your mistake in the code. In the play command you check if the client is playing and if not it disconnects. This was the only problem. To fix it you just need to change your while loop. You can copy my code(includes explanations) or just edit your one.
Documentation Used:
Discord.py Documentation
Code:
#commands.command(brief="Plays a locally hosted song.")
async def play(self, ctx, *, arg):
await ctx.send(f"Joining voice channel: **{ctx.author.voice.channel}**")
# Replaced Destination with ctx.voice_client, Added part where it checks if its already connected and needs to move to another channel
if ctx.voice_client is None:
await ctx.author.voice.channel.connect()
else:
await ctx.voice_client.move(ctx.author.voice.channel)
await ctx.send(f"Joined voice channel: **{ctx.author.voice.channel}**")
# Replaced voice with ctx.voice_client
ctx.voice_client.play(discord.FFmpegPCMAudio(source=f"./audio/{arg}.flac"))
# Edited Loop because it caused the Bot to disconnect
# Loop now runs if Bot is connected to channel
while ctx.voice_client.is_connected():
# checks if the bot is the only one in the channel
if len(ctx.voice_client.channel.members) == 1:
# disconnects
await ctx.voice_client.disconnect()
break
# checks if client is pause
elif ctx.voice_client.is_paused():
await asyncio.sleep(1)
# Checks if client is playing
elif ctx.voice_client.is_playing():
await asyncio.sleep(1)
# if nothing of the above is fulfilled
else:
# disconnect
await ctx.voice_client.disconnect()
break
#commands.command(brief="Pauses the currently playing song.")
async def pause(self, ctx):
# Checks if music is playing and pauses it, otherwise sends the player a message that nothing is playing
try:
ctx.voice_client.pause()
except:
await ctx.send(f"{ctx.author.mention} i'm not playing music at the moment!")
#commands.command(brief="Resumes the paused song.")
async def resume(self, ctx):
# Checks if music is paused and resumes it, otherwise sends the player a message that nothing is playing
try:
ctx.voice_client.resume()
except:
await ctx.send(f"{ctx.author.mention} i'm not playing music at the moment!")

How to make the discord bot queue local mp3?

I'm new to python, so I wonder if there's a way to do so.
Here's my play mp3 command:
#bot.command()
async def play_song1(ctx):
global voice
channel = ctx.message.author.voice.channel
voice = get(bot.voice_clients, guild=ctx.guild)
if voice and voice.is_connected():
await voice.move_to(channel)
else:
voice = await channel.connect()
voice.play(discord.FFmpegPCMAudio('./mp3/song1.mp3'))
voice.source = discord.PCMVolumeTransformer(voice.source)
voice.source.volume = 0.1
await ctx.send ('playing')
while voice.is_playing():
await asyncio.sleep(.1)
await voice.disconnect()
I made 2 more same commands but for song2 and song3, and now I want to queue the mp3 when someone calls them.
Seen as if you're not using cogs, you could try something like this:
guild_queues = {} # Multi-server support as a dict, just use a list for one server
# EDIT: Map song names in a dict
play_messages = {
"song1": "Now playing something cool!",
"song2": "And something even cooler just started playing!",
"song3": "THE COOLEST!"
}
async def handle_queue(ctx, song):
voice = discord.utils.get(bot.voice_clients, guild=ctx.guild)
channel = ctx.author.voice.channel
if voice and channel and voice.channel != channel:
await voice.move_to(channel)
elif not voice and channel:
voice = await channel.connect()
if not voice.is_playing():
audio = discord.FFmpegPCMAudio(f"./{song}.mp3")
source = discord.PCMVolumeTransformer(audio)
source.volume = 0.1
voice.play(audio)
await ctx.send(play_messages[song])
while voice.is_playing():
await asyncio.sleep(.1)
if len(guild_queues[ctx.guild.id]) > 0:
next_song = guild_queues[ctx.guild.id].pop(0)
await handle_queue(ctx, next_song)
else:
await voice.disconnect()
#bot.command()
async def play(ctx, *, song): # I'd recommend adding the filenames as an arg, but it's up to you
# Feel free to add a check if the filename exists
try:
# Get the current guild's queue
queue = guild_queues[ctx.guild.id]
except KeyError:
# Create a queue if it doesn't already exist
guild_queues[ctx.guild.id] = []
queue = guild_queues[ctx.guild.id]
voice = discord.utils.get(bot.voice_clients, guild=ctx.guild)
queue.append(song)
# The one song would be the one currently playing
if voice and len(queue) > 0:
await ctx.send("Added to queue!")
else:
current_song = queue.pop(0)
await handle_queue(ctx, current_song)
References:
utils.get()
Client.voice_clients
Context.guild
Member.voice
VoiceState.channel
VoiceClient.move_to()
VoiceClient.is_playing()
VoiceClient.play()
discord.FFmpegPCMAudio()
discord.PCMVolumeTransformer()
Context.send()

Python Discord Bot - Keep real time message parser from blocking async

I am writing a Discord Bot to take messages from a live chat and relay them to Discord channel, but want it to have other responsive features. Currently the script relays messages by entering a while loop which runs until the right message is recieved.
def chat_parser():
resp = sock.recv(4096).decode('utf-8')
#print(' - ', end='')
filtered_response = filter_fighter_announce(resp)
if resp.startswith('PING'):
# sock.send("PONG :tmi.twitch.tv\n".encode('utf-8'))
print("Ponging iirc server")
sock.send("PONG\n".encode('utf-8'))
return ''
elif (len(filtered_response) > 0):
if (filtered_response.count('ets are OPEN for') > 0):
filtered_response = get_fighters(filtered_response)
return filtered_response
return ''
fight = fight_tracker('') #initialize first fight object
def message_loop():
global first_loop_global
while True:
chat_reception = chat_parser()
if (chat_reception == ''):
continue
fight.set_variables(chat_reception)
return fight.announcement
return ''
The issue with this is that responsive functions for Discord are stuck waiting for this loop to finish. Here is the other code for reference.
#client.event
async def on_ready():
print('Finding channel...')
for guild in client.guilds:
if guild.name == GUILD:
break
channel = guild.get_channel(salty_bet_chat_id)
print('Connected to Channel.')
try:
print('Beginning main loop.')
while True:
message_out = await message_loop()
if (message_out != None and message_out != None):
print('Sending to Discord: ', message_out)
msg = await channel.send(message_out)
await msg.add_reaction(fight.fighter_1[1])
await msg.add_reaction(fight.fighter_2[1])
print('message sent...')
except KeyboardInterrupt:
print('KeyboardInterrupt')
sock.close()
exit()
#client.event
async def on_raw_reaction_add(reaction):
print(reaction)
#client.event
async def on_message(message):
print(message.author)
print(client.user)
client.run(TOKEN)
I have tried making async functions out of chat_parser() and message_loo() and awaiting their return where they are called, but the code is still blocking for the loop. I am new to both async and coding with Discord's library, so I am not sure how to make an async loop function when the only way to start the Discord client is by client.run(TOKEN), which I could not figure out how to incorporate into another event loop.

Resources