So, I have a command that should give me an image and after that bot should ask whether user wants to keep it or not (I use thumbs-up reaction for the answer). If user reacts with 'thumbs-up', then bot says 'Saved!' and actually save the image URL to a .txt file. The problem is if user doesn't react with thumbs up and after that types command again and save this latest image, bot saves previous ones too. I use Replit for hosting. Here is the code block that doesn't work as expected:
#bot.command()
async def myfunc(ctx):
... # here goes the message attributes, requests (no problem with the requests)
thumb_up = "👍"
thumb_down = "👎"
imagesList = []
await message.add_reaction(thumb_up)
userDict = {}
n = 0
while True:
try:
reaction, user = await bot.wait_for("reaction_add", timeout=30)
if user not in userDict:
userDict[user] = 0
if userDict[user] == 0:
if (str(reaction.emoji) == thumb_up and user != bot.user):
imagesList.append(url)
fileWrite = open(f"data{user}.txt", "a")
fileWrite.write(f"{url} \n")
fileWrite.close()
await ctx.send("Saved!")
userDict[user] += 1
imagesList.clear()
except:
pass
imagesList.clear()
#bot.event
async def on_reaction_add(reaction, user):
embed = reaction.message.attachments
emoji = reaction.emoji
if emoji == ":thumbsup:":
await reaction.channel.send(embed)
It shouldn't save previous URL's to users file. It should save only URL's that the user reacted with 'thumbs-up'. I use dict to avoid that but didn't work.
Okay, so I am not much familiar with discord.py and have no clue why it writes multiple times, but I think the problem is if the message id that the user reacted is the message user reacted, I mean if there are multiple lines get written, we should discard or not to use previous of them. That's why I created a check function:
msgId = message.id
def check(reaction, user):
if user != bot and reaction.emoji == thumb_up and reaction.message.id == msgId :
return True
else:
return False
userDict = {}
while True:
reaction, user = await bot.wait_for("reaction_add", check=check, timeout=30)
if(user.id not in userDict.keys() and user.id != <id of bot>):
userDict[user.id] = 0
if(reaction.emoji == thumb_up and user.id != <id of bot> and userDict[user.id] == 0 ):
userDict[user.id] = 1
fileWrite = open(f"data{user}.txt", "a")
fileWrite.write(f"{url} \n")
fileWrite.close()
await ctx.send(f"Saved {user}")
userDict[user.id] = 1
Related
I'm making a "dynamic" help menu command for my Discord bot. Now my commands work with "normal" messages. But I want to use Embed. So, let me explain.
Basically, I do re!help and the bot sends an Embed. Then I add two reactions ("◀" and "▶"), and if I click on a reaction (eg "▶") the Embed is updated with the commands (I want both the title and the description of the Embed to be updated). I hope I was clear and if you did not understand what I want tell me I will try to explain it again. Here is my code anyway:
#client.command()
async def help(ctx):
contents = [
"""
text 1
""",
"""
text 2
""",
"""
text 3
""",
"""
text 4
"""]
pages = 4
cur_page = 1
message = await ctx.send(f"{contents[cur_page-1]}")
# getting the message object for editing and reacting
await message.add_reaction("◀️")
await message.add_reaction("▶️")
def check(reaction, user):
return user == ctx.author and str(reaction.emoji) in ["◀️", "▶️"]
# This makes sure nobody except the command sender can interact with the "menu"
while True:
try:
reaction, user = await client.wait_for("reaction_add", timeout=60, check=check)
# waiting for a reaction to be added - times out after x seconds, 60 in this
# example
if str(reaction.emoji) == "▶️" and cur_page != pages:
cur_page += 1
await message.edit(content=f"Page {cur_page}/{pages}:\n{contents[cur_page-1]}")
await message.remove_reaction(reaction, user)
elif str(reaction.emoji) == "◀️" and cur_page > 1:
cur_page -= 1
await message.edit(content=f"Page {cur_page}/{pages}:\n{contents[cur_page-1]}")
await message.remove_reaction(reaction, user)
else:
await message.remove_reaction(reaction, user)
# removes reactions if the user tries to go forward on the last page or
# backwards on the first page
except asyncio.TimeoutError:
await message.delete()
break
Wish someone could help!
Instead of contents being a list of strings, make it a list of embeds, then edit the message appropiately
contents = [
discord.Embed(title="page 1"),
discord.Embed(title="page 2"),
discord.Embed(title="page 3"),
discord.Embed(title="page 4")
]
...
await message.edit(
content=f"Page {cur_page}/{pages}",
embed=contents[cur_page - 1]
)
I am currently making a car economy bot and I wanted to be able to interact with the bot just like how a game would. I wanted to use reactions for buttons but the problem was that when I used the code,
confirm_right = await client.wait_for("reaction_add", check=react_check_right)
confirm_left = await client.wait_for("reaction_add", check=react_check_left)
It didn't work as expected, first it waits for the confirm_right then the confirm_left.
I'm hoping to find a way to be able to make them both work at the same time.
This is my check functions,
def check(message):
return ctx.author.id == message.author.id
def react_check_right(reaction, user):
return user == ctx.author and str(reaction.emoji) in ["➡️"]
Found the answer
reaction, user = await client.wait_for('reaction_add', check = lambda reaction, user: reaction.emoji in ["➡️", "⬅️"])
if user == ctx.author:
if reaction.emoji == '➡️':
if car_shown < len(users[str(user.id)]['car']) - 1:
car_shown += 1
else:
car_shown = 0
```
this was basically it
I have been using the following two methods to get a user input from a discord embed. The first I use to get a text based message input and the second I use to get a reaction input (used to paginate the embed). What I would like to know is if I can merge these two to create a method to wait for either a reaction or a message input?
#message input
try:
answer = await self.bot.wait_for(
"message",
timeout=60,
check=lambda message: message.author.id == ctx.author.id
and isinstance(message.channel, discord.channel.DMChannel)
)
#reaction input
try:
reaction, user = await self.bot.wait_for(
"reaction_add",
timeout=60,
check=lambda reaction, user: user.id == ctx.author.id
and reaction.emoji in buttons
#and isinstance(reaction.channel, discord.channel.DMChannel),
)
UPDATE:
So I have tried to implement the method duckboycool linked to (Many thanks man!).
The issue I am having now is that when I react to the paginated embed it works perfectly, the reaction is noted and the embed is updated accordingly. But if I input a message instead it get the following error:
"reaction, emoji = await task
TypeError: cannot unpack non-iterable Message object"
here is my code:
finished =0
while finished == 0:
done_tasks = None
check1=lambda reaction, user: user.id == ctx.author.id and reaction.emoji in buttons
check2=lambda message: message.author.id == ctx.author.id and isinstance(message.channel, discord.channel.DMChannel)
pending_tasks = [self.bot.wait_for('reaction_add',check=check1),self.bot.wait_for('message',check=check2)]
done_tasks, pending_tasks = await asyncio.wait(pending_tasks, return_when=asyncio.FIRST_COMPLETED)
#print(done_tasks)
for task in pending_tasks:
task.cancel()
for task in done_tasks:
reaction, emoji = await task
message = await task
if reaction:
print(reaction)
previous_page = current
if reaction.emoji == u"\u23EA":
current = 0
elif reaction.emoji == u"\u25C0":
if current > 0:
current -= 1
elif reaction.emoji == u"\u25B6":
if current < len(pages)-1:
current += 1
elif reaction.emoji == u"\u23E9":
current = len(pages)-1
if current != previous_page:
await msg.edit(embed = pages[current])
else:
print(message.content)
In the updated code, you need to check what kind of awaitable the event is so that you can unpack with the correct number of values and complete the intended behavior. With your examples of message and reaction_add, this would probably look something like this.
for task in done_tasks:
taskobj = await task
if isinstance(taskobj, discord.Message):
message = taskobj
#Message logic
else:
reaction, user = taskobj
#Reaction logic
You might have to do different things depending on which events you're using. (Here, it relies on a tuple being recognized as not an instance of discord.Message.)
So I want to make a command that can reply to either .invites or .invites #user
When I run this command as .invites, my error handler kicks in and says I need a #user.
I tried removing my error handler and the command does nothing.
How can I make this command work both ways?
#commands.command()
async def invites(self, ctx, user: discord.Member):
global last1
global invites1
try:
userinvitecount = 0
gld = self.bot.get_guild(int(guild_id))
while True:
invs = await gld.invites()
tmp = []
for i in invs:
if user.id == i.inviter.id:
top = i.uses
userinvitecount += int(top)
tmp.append(tuple((i.inviter.id, i.code, i.uses)))
invites1 = tmp
break
usr = gld.get_member(int(user.id))
eme = discord.Embed(description=f"{usr.mention} has {userinvitecount} invite's", color=0x03d692, title=" ")
await ctx.send(embed=eme)
except commands.MissingRequiredArgument:
user = ctx.message.author.id
userinvitecount = 0
gld = self.bot.get_guild(int(guild_id))
while True:
invs = await gld.invites()
tmp = []
for i in invs:
if user.id == i.inviter.id:
top = i.uses
userinvitecount += int(top)
tmp.append(tuple((i.inviter.id, i.code, i.uses)))
invites1 = tmp
break
usr = gld.get_member(int(user.id))
eme = discord.Embed(description=f"{usr.mention} has {userinvitecount} invite's", color=0x03d692, title=" ")
await ctx.send(embed=eme)
if isinstance(error, commands.MissingRequiredArgument):
# generic error handler for commands entered wrong
embed = discord.Embed(
title="Failed",
description="Failed to use command properly \n Please try again.",
colour=discord.Colour.blurple(),
)
await ctx.send(embed=embed)
You can do this by using default values.
In your command you can then simply check whether user is None or not.
Default values can be used e.g. by setting user to None like seen below.
#commands.command()
async def invites(self, ctx, user: discord.Member = None):
New python-enthusiast here.
I am currently making a discord bot that responds with 16 pictures in a 4x4-grid when a specific user types in the chat:
XXXX
XXXX
XXXX
XXXX
This is my current code:
import discord
from secrets import TOKEN
from emotes import *
# user IDs
bonvang_ID = "417010736988946444"
djursing_ID = "124806046568022016"
jonathan_ID = "151788707760832512"
alex_ID = "151745760935936001"
snuffles_ID = "221360712222507009"
# bot = commands.Bot(command_prefix='!')
client = discord.Client()
# prints message in terminal when bot is ready
#client.event
async def on_ready():
print(f"Logged in as {client.user}")
# #client.event
# async def on_member_join(member):
# await message.channel.send(f"""Welcome to the server {member}!\n
# I'm just a simple bot made by Mr.Nofox.\n
# If you want to know more about the commands, ask him or try your luck with the others! :)""")
def member_amount():
# create a variable that contains all the servers
active_servers = client.servers
# create a variable to store amount of members per server
sum = 0
# loop through the servers, get all members and add them up, this includes bots
for s in active_servers:
sum += len(s.members)
return sum
def picture_grid():
return (f"{pic01} {pic02} {pic03} {pic04}\n{pic05} {pic06} {pic07} {pic08}\n{pic09} {pic10} {pic11} {pic12}\n{pic13} {pic14} {pic15} {pic16}\n)
#client.event
async def on_message(message):
# print(f"{message.channel}: {message.author}: \"{message.content}\"")
guild = client.get_all_members()
if "!count()" == message.content:
await client.send_message(message.channel, f"```py\nTotal members: {member_amount()}```")
elif "!report()" == message.content:
online = 0
idle = 0
offline = 0
for member in guild:
if str(member.status) == "online":
online += 1
if str(member.status) == "offline":
offline += 1
if str(member.status) == "idle" or str(member.status) == "dnd":
idle += 1
await client.send_message(message.channel, f"```Online: {online}\nIdle/busy.dnd: {idle}\nOffline: {offline}```")
#TODO: Make a neat function for the pictures in message that responds with 4x4 image in chat when a given user types
if (message.author.id == alex_ID or message.author.id == snuffles_ID):
await client.send_message(message.channel, "DET ER MIN SØN!")
await client.send_message(message.channel, picture_grid())
# add bot-token found on website
client.run(TOKEN)
All the emotes are in a file called "emotes.py" which I import.
Each emote is a string-code, example:
pic01 = '<:1_:543049824644235265>'
The line I am using to get this to work atm. is:
await client.send_message(message.channel, f"{pic01} {pic02} {pic03} {pic04}\n{pic05} {pic06} {pic07} {pic08}\n{pic09} {pic10} {pic11} {pic12}\n{pic13} {pic14} {pic15} {pic16}\n")
This is awful coding practice and therefore I want to make a function that returns:
f"{pic01} {pic02} {pic03} {pic04}\n{pic05} {pic06} {pic07} {pic08}\n{pic09} {pic10} {pic11} {pic12}\n{pic13} {pic14} {pic15} {pic16}\n"
My initial thought was something like:
def picture_grid():
return (f"{pic01} {pic02} {pic03} {pic04}\n{pic05} {pic06} {pic07} {pic08}\n{pic09} {pic10} {pic11} {pic12}\n{pic13} {pic14} {pic15} {pic16}\n")
This does the trick and converts the first code to:
await client.send_message(message.channel, picture_grid())
But merely moves the bad code to another place.. I am currently stuck by what should be a simple task.
Any ideas?