Multiple wait_for messages discord.py - python

What I'm trying to do: This is a similar situation to a question I previously asked, I am making a multiplayer game. I wanted to get multiple user inputs at the same time, basically the situation in another question I had found (multiple wait_for_messages wanted discord.py).
My Problem: I need to send messages to a different set of people each time. Basically, one game may have 3 players, while another might have 5 players. The original aim was also to send a message simultaneously to a user's dms and have them answer the input from there. I am unsure how to define the amount of wait_fors I need in my code.
Code attempt 1:
# inside a cog
#commands.command()
async def bagpie(self, ctx, *members: discord.Member):
members = list(members)
if ctx.author not in members:
members.append(ctx.author)
if len(members) > 3:
return
# making checks
def check1(msg):
return msg.author == members[0]
def check2(msg):
return msg.author == members[1]
def check3(msg):
return msg.author == members[2]
# waiting for multiple messages with the checks
ret = await asyncio.gather(
await self.bot.wait_for("message", timeout=10, check=check1),
await self.bot.wait_for("message", timeout=10, check=check2),
await self.bot.wait_for("message", timeout=10, check=check3),
return_exceptions = True
)
# Test for errors
ret = [r if not isinstance(r, Exception) else None for r in ret]
msg1, msg2, msg3 = ret # *ret would cause an error to occur
# setup embed to send final product
fin_string = ""
count = 0
msg_list = [msg1, msg2, msg3]
for member in members:
fin_string += f"{member} says: {msg_list[count]}"
count += 1
embed = discord.Embed(title="This is what you said!", description=fin_string)
await ctx.send(embed=embed)
Result: As you can see, despite there being inputs given, the bot does not respond and instead reverts to the timeout.
Code Attempt 2:
# couldn't get this to work in a cog, so this one is not in a cog
# checks if it's a dm and if author is given member
def is_dm_check(member):
def inner_check(msg):
return msg.author.id==member.id and msg.guild is None
return inner_check
# sends message to member and waits for the message
async def get_input(member):
await member.send("Please provide an input:")
return client.wait_for('message', check=is_dm_check(member), timeout=75)
#client.command()
async def play_game(ctx, members: commands.Greedy[discord.Member]=None):
if not members:
await ctx.send("No Players")
return
if ctx.author not in members:
members.append(ctx.author)
responses = asyncio.gather(*map(get_input, members))
for member, response in zip(members, responses):
await ctx.send(f"{member.mention}: {response.content}")
Result: Messages were supposedly sent simultaneously, however it still throws an error.

Related

Discord.py Snipe command

Im trying to make a command where the bot "snipes" the last deleted message. this is my current code:
snipe_message_content = None
snipe_message_author = None
#client.event
async def on_message_delete(message):
snipe_message_author.remove(None)
snipe_message_content.remove(None)
snipe_message_content.append(message.content)
snipe_message_author.append(message.author.id)
await asyncio.sleep(str(60))
snipe_message_author.remove(message.author.id)
snipe_message_content.remove(message.content)
#client.command()
async def snipe(message):
if snipe_message_content==None:
await message.channel.send("Theres nothing to snipe.")
else:
embed = discord.Embed(description=f"{snipe_message_content}")
embed.set_footer(text=f"Asked by {message.author.name}#{message.author.discriminator}", icon_url=message.author.avatar_url)
embed.set_author(name= f"<#{snipe_message_author}>")
await message.channel.send(embed=embed)
return
the await message.channel.send("Theres nothing to snipe.") part works perfectly fine, but the rest wont work. Can anyone help?
Well your on_message_delete() function is just not working.
First of all, your variables snipe_message_author and snipe_message_content are of the type None, but the methods remove and append are part of the type list, so you'd have to declare lists
snipe_message_content = []
snipe_message_author = []
in order for them to work.
Still, you wouldn't have to do this anyway. Just give your current variables a new value:
snipe_message_content = None
snipe_message_author = None
#client.event
async def on_message_delete(message):
global snipe_message_content
global snipe_message_author
# Variables outside a function have to be declared as global in order to be changed
snipe_message_content = message.content
snipe_message_author = message.author.id
await asyncio.sleep(60)
snipe_message_author = None
snipe_message_content = None
Also, do not convert 60 to a string. time.sleep and asyncio.sleep both need an integer in order to work (side note: if you wanted 60 to be a string, just write "60" in quotation marks).
At last, be aware of the following case: If a message x gets deleted, but 50 seconds after that, a new message y gets deleted, snipe_message_author and snipe_message_content would be assigned to the new message y. But 10 seconds later, the function executed by message x would set the value of snipe_message_author and snipe_message_content to None.
Therefore, after await asyncio.sleep(60), check whether your message is still the same as before:
snipe_message_content = None
snipe_message_author = None
snipe_message_id = None
#client.event
async def on_message_delete(message):
global snipe_message_content
global snipe_message_author
global snipe_message_id
snipe_message_content = message.content
snipe_message_author = message.author.id
snipe_message_id = message.id
await asyncio.sleep(60)
if message.id == snipe_message_id:
snipe_message_author = None
snipe_message_content = None
snipe_message_id = None
Your command works now probably, but there's a problem. If I delete a message in my server, and you run the command in your server, you'll probably see the message.
What you should do is make the snipe_message_author and snipe_message_content variables dictionaries.
This is how the event should be:
snipe_message_author = {}
snipe_message_content = {}
#client.event
async def on_message_delete(message):
snipe_message_author[message.channel.id] = message.author
snipe_message_content[message.channel.id] = message.content
await sleep(60)
del snipe_message_author[message.channel.id]
del snipe_message_content[message.channel.id]
#client.command(name = 'snipe')
async def snipe(ctx):
channel = ctx.channel
try: #This piece of code is run if the bot finds anything in the dictionary
em = discord.Embed(name = f"Last deleted message in #{channel.name}", description = snipe_message_content[channel.id])
em.set_footer(text = f"This message was sent by {snipe_message_author[channel.id]}")
await ctx.send(embed = em)
except KeyError: #This piece of code is run if the bot doesn't find anything in the dictionary
await ctx.send(f"There are no recently deleted messages in #{channel.name}")
#If the bot sends the embed, but it's empty, it simply means that the deleted message was either a media file or another embed.
To summarize it, here's what this fixes for you:
Doesn't show deleted messages from other servers
Doesn't show deleted messages from other channels
Deleted message in one server won't replace the deleted message in another server
Hope this helped :)

Multiple Choice Reaction python (discord.py)

I'm trying to do a multiple choice button / reaction in discord using python (discord.py) -- something similar to the image below:
For example when it reacts to 2️⃣, that shows page 2, when it reacts to 3️⃣, that shows page 3 ...
Could anyone help me, please?
import discord
from discord.ext import commands
class Wiki(commands.Cog):
def __init__(self,bot):
self.bot=bot
#commands
#commands.command(name="wiki",aliases=["w"])
async def wiki(self,ctx):
page1=discord.Embed(
title='Page 1/3',
description='Description1',
colour=discord.Colour.orange()
)
page2=discord.Embed(
title='Page 2/3',
description='Description2',
colour=discord.Colour.orange()
)
page3=discord.Embed(
title='Page 3/3',
description='Description3',
colour=discord.Colour.orange()
)
pages=[page1,page2,page3]
message= await ctx.send(embed=page1)
await message.add_reaction('1️⃣')
await message.add_reaction('2️⃣')
await message.add_reaction('3️⃣')
emoji=""
if emoji=="1️⃣":
await message.edit_message(message,embed=pages[0])
if emoji=="2️⃣":
await message.edit_message(message,embed=pages[1])
if emoji=="3️⃣":
await message.edit_message(message,embed=pages[2])
def setup(bot):
bot.add_cog(Wiki(bot))
Take a look at the documentation for wait_for, which is how you should treat this kind of case. I won't spoonfeed you the code though, you should give it a fair shot yourself first.
Also, discord.py already has something built-in for embeds that change pages automatically based on reactions called menus, so it'll probably be easier to just use that instead of re-implementing it yourself.
Answering my own question.
I know it's not the best answer, but it works XD.
Any suggestion will be well accepted. Thank you all so much for your help.
import asyncio
import discord
from discord.ext import commands
class Wiki(commands.Cog):
def __init__(self,bot):
self.bot=bot
#commands
#commands.command(name="wiki",aliases=["w"])
async def wiki(self,ctx):
first_run = True
while True:
if first_run:
page1=discord.Embed(title='Page 1/3',description='Description1',colour=discord.Colour.orange())
first_run=False
msg = await ctx.send(embed=page1)
reactmoji = ["1️⃣","2️⃣","3️⃣"]
for react in reactmoji:
await msg.add_reaction(react)
def check_react(reaction, user):
if reaction.message.id != msg.id:
return False
if user != ctx.message.author:
return False
if str(reaction.emoji) not in reactmoji:
return False
return True
try:
res, user = await self.bot.wait_for('reaction_add', check=check_react)
except asyncio.TimeoutError:
return await msg.clear_reactions()
if user != ctx.message.author:
pass
elif '1️⃣' in str(res.emoji):
print('<<1️⃣>>')
await msg.remove_reaction("1️⃣",user)
await msg.edit(embed=page1)
elif '2️⃣' in str(res.emoji):
print('<<2️⃣>>')
page2=discord.Embed(title='Page 2/3',description='Description2',colour=discord.Colour.orange())
await msg.remove_reaction("2️⃣",user)
await msg.edit(embed=page2)
elif '3️⃣' in str(res.emoji):
print('<<3️⃣>>')
page3=discord.Embed(title='Page 3/3',description='Description3',colour=discord.Colour.orange())
await msg.remove_reaction("3️⃣",user)
await msg.edit(embed=page3)
def setup(bot):
bot.add_cog(Wiki(bot))

BotBuilder Python - Handling multiple dialog and intent

I have the following code to handle multiple intents,
Code
async def on_message_activity(self, turn_context: TurnContext):
recognizer_result = await self.luis.recognize(self.recognizer, turn_context)
intent = self.luis.get_top_intent(recognizer_result)
await self.process_intent(turn_context, recognizer_result, intent)
async def process_intent(self, turn_context: TurnContext, recognizer_result, intent):
if intent == 'Greeting_Wishes':
await greeting_wishes(turn_context, user_info)
elif intent == 'Greeting_Question':
await greeting_question(turn_context)
elif intent == 'Movement':
dialog = Movement(recognizer_result)
await DialogHelper.run_dialog(
dialog,
turn_context,
self.dialog_state
)
Problem
Greeting intent is working fine
Movement intent is properly taking to the configured dialog but after asking a couple of inputs to the user and when the user enters their value it is either going back to greeting intent or going nowhere since the intent is None
Can someone help how to handle multiple intents with dialogs?
Any help would be appreciated!
I ended up having one main dialog and extended the other dialogs depending upon the other intent. Look at the code sample below,
async def on_message_activity(self, turn_context: TurnContext):
recognizer_result = await self.luis.recognize(self.recognizer, turn_context)
intent = self.luis.get_top_intent(recognizer_result)
await self.process_intent(turn_context, recognizer_result, intent)
async def process_intent(self, turn_context: TurnContext, recognizer_result, intent):
if intent == "Greeting_Wishes" and not entity:
await greeting_wishes(turn_context, user_info)
return
if intent == "Greeting_Question" and not entity:
await greeting_question(turn_context)
return
dialog = MainDialog(self.luis, intent, recognizer_result)
await DialogHelper.run_dialog(
dialog,
turn_context,
self.conversation_state.create_property("DialogState")
)
main_dialog.py
Within the main dialog, I'll check for the intent and begin the appropriate dialog.
class MainDialog(ComponentDialog):
def __init__(self, luis, intent, recognizer_result):
super(MainDialog, self).__init__(MainDialog.__name__)
self.luis = luis
self.intent = intent
self.recognizer_result = recognizer_result
self.add_dialog(SampleDialog1())
self.add_dialog(SampleDialog2())
self.add_dialog(
WaterfallDialog(
"main_dialog_id", [self.main_step]
)
)
self.initial_dialog_id = "main_dialog_id"
async def main_step(self, step_context: WaterfallStepContext) -> DialogTurnResult:
dialog_detail = self.luis.get_entities(self.intent, self.recognizer_result)
if self.intent == "one":
return await step_context.begin_dialog(SampleDialog1.__name__, dialog_detail)
elif self.intent == "two":
return await step_context.begin_dialog(SampleDialog2.__name__, dialog_detail)

Trying to send a message to a specific channel using Discord.py rewrite and it isn't working

I'm currently working on a discord bot and I'm trying to send a message to a specific channel using Discord.py rewrite once the user levels up, and I'm getting this error:
await channel.message.send(f"{message.author.mention} is now level {self.users[author_id]['level']}! congrats!")
AttributeError: 'NoneType' object has no attribute 'message'
Here is all the code:
import discord
from discord.ext import commands
import json
import asyncio
class Levels(commands.Cog):
#commands.Cog.listener()
async def on_message(self, message):
if message.author == self.bot.user:
return
author_id = str(message.author.id)
bot = commands.Bot(command_prefix='!')
if author_id not in self.users:
self.users[author_id] = {}
self.users[author_id]['level'] = 1
self.users[author_id]['exp'] = 0
self.users[author_id]['exp'] += 1
if author_id in self.users:
if self.lvl_up(author_id):
channel = bot.get_channel('636399538650742795')
await channel.message.send(f"{message.author.mention} is now level {self.users[author_id]['level']}! congrats!")
def __init__(self, bot):
self.bot = bot
with open(r"cogs\userdata.json", 'r') as f:
self.users = json.load(f)
self.bot.loop.create_task(self.save_users())
async def save_users(self):
await self.bot.wait_until_ready()
while not self.bot.is_closed():
with open(r"cogs\userdata.json", 'w') as f:
json.dump(self.users, f, indent=4)
await asyncio.sleep(5)
def lvl_up(self, author_id):
author_id = str(author_id)
current_xp = self.users[author_id]['exp']
current_lvl = self.users[author_id]['level']
if current_xp >= ((3 * (current_lvl ** 2)) / .5):
self.users[author_id]['level'] += 1
return True
else:
return False
I'm really not sure what the issue is here but if anyone knows the problem I would be very appreciative if you could let me know how I can correct this.
Thanks for reading I've been trying to figure this out for hours.
Edit: Still having the issue.
You get AttributeError because channel is None.
To fix it you need to remove quotes from channel id like this:
channel = bot.get_channel(636399538650742795)
This is described here: https://discordpy.readthedocs.io/en/latest/migrating.html#snowflakes-are-int
Also i see another error on the next line. The channel has no message attribute too. I think you need to fix it like this:
await channel.send(f"{message.author.mention} is now level {self.users[author_id]['level']}! congrats!")
I was able to send messages using this guide: https://discordpy.readthedocs.io/en/latest/faq.html#how-do-i-send-a-message-to-a-specific-channel
the code I used is:
channel = client.get_channel(12324234183172)
await channel.send('hello')
#bot.command()
async def lvl_up(member: discord.Member):
"""Send a level up message"""
channel = bot.get_channel(channel_id) # channel id should be an int
if not channel:
return
await channel.send(f"GG {member}, u lvled up") # Whatever msg u want to put
Try using that code for the channel and sending the message, then add ur logic. I'm new to Stack Overflow so idk if I formatted that code correctly
Not sure if this is solved (sorry I'm new to stack overflow) but using this made it work
#bot.command()
async def Hello(ctx):
channel = bot.get_channel(Insert Channel ID)
await channel.send('Hello')
Using this I didn't get the NoneType error.
you need to put it in an async function
so instead of
channel = bot.get_channel(<channel id>)
you should do
async def get_channel():
channel = bot.get_channel(<channel id>)
asyncio.run(get_channel())

Missing 1 required positional argument: 'number'

Hi I'm having an issue running a asyncio loop it's asking for a missing 1 required positional argument: 'number'.
Here is what I'm working with:
async def purge_modlog(ctx, number):
tomorrow = datetime.now()+timedelta(days=1)
midnight = datetime(year=tomorrow.year, month=tomorrow.month,
day=tomorrow.day, hour=20, minute=35, second=0)
number = int(number)
server = before.server
db = fileIO(self.direct, "load")
if not server.id in db:
return
channel = db[server.id]["Channel"]
if number > 99 or number < 1:
await ctx.send("I can only delete messages within a range of 1 - 99", delete_after=10)
else:
author = ctx.message.author
authorID = author.id
mgs = []
number = int(number)
channel = modlog
async for x in bot.logs_from((channel), limit = int(number+1)):
mgs.append(x)
await asyncio.sleep((midnight - datetime.now()).seconds)
print("Deleting modlog messages 14 day or older")
await asyncio.sleep(5)
await delete_messages(mgs)
await ctx.send('Success!', delete_after=4)
await asyncio.sleep(86400) # Wait 24 hours
def check_folder():
if not os.path.exists('data/modlogset'):
print('Creating data/modlogset folder...')
os.makedirs('data/modlogset')
def check_file():
f = 'data/modlogset/settings.json'
if not fileIO(f, 'check'):
print('Creating default settings.json...')
fileIO(f, 'save', {})
def setup(bot):
check_folder()
check_file()
q = ModLog(bot)
loop = asyncio.get_event_loop()
loop.create_task(q.purge_modlog())
bot.add_cog(q)
To loop the event under def def setup(bot): You can see
loop = asyncio.get_event_loop()
loop.create_task(q.purge_modlog())
This is supposed to loop the event (q.purge_modlog()) I'm not sure what I'm doing wrong here. I have already tried the follow (q.purge_modlog(ctx, number))
line 686, in setup
loop.create_task(q.purge_modlog(ctx, number))
NameError: name 'ctx' is not defined
If anyone could help me that would be great. To add this is a module.
I have corrected some mistakes here. Try to read through the discord.py documentation on purging messages.
class ModLog:
def __init__(self, bot):
self.bot = bot
async def on_ready(self):
await self.bot.wait_until_ready()
for server in self.bot.servers:
channel = self.bot.get_channel("channel ID here")
if channel:
self.bot.loop.create_task(self.modlog_purge(channel))
async def modlog_purge(self, channel):
while True:
now = datetime.utcnow()
two_weeks_ago = now - timedelta(days=14)
await self.bot.purge_from(channel, before=two_weeks_ago)
await asyncio.sleep(86400)
def setup(bot):
q = ModLog(bot)
bot.add_cog(q)
class ModLog:
def __init__(self, bot):
self.bot = bot
async def on_ready(self):
await self.bot.wait_until_ready()
for guild in self.bot.guilds:
channel = await self.get_channel(guild)
if channel:
self.bot.loop.create_task(self.modlog_purge(channel))
async def get_channel(guild):
# Whatever your logic for getting the Channel for a given guild is
async def modlog_purge(self, channel):
while True:
now = datetime.utcnow()
two_weeks_ago = now - timedelta(days=14)
await channel.purge(before=two_weeks_ago)
await asyncio.sleep(86400)
def setup(bot):
q = ModLog(bot)
bot.add_cog(q)
Here's how I would structure this (I'm not on a computer with discord.py at the moment, so there may be some errors). We have an on_ready event that kicks off background tasks for each server that has a channel we want to maintain (this could instead loop through a list of channels, or something similar).
The actual purge is all taken care of by the TextChannel.purge coroutine. We just pass it a datetime object, and it deletes 100 messages from before that date (this is configurable). It then sleeps for a day and repeats.

Categories

Resources