I am making a telegram bot on aiogram. Can't pass variables from message.handler to callback.handler. I use FSM, in message.handler state = "waiting_for_address".
The algorithm is => in message.handler, the bot sends inline_keyboard with the "Take" button to the GROUP. When the button is clicked, a callback is sent and the bot visits callback.handler. The state (state = "waiting_for_address") is saved, but only for the user who used the bot. But when we click the button, another user appears and has no state set. If I manually set the state in callback.handler, then the bot stops working.
Tell me, please, how to do it right?
#dp.message_handler(state=Form.waiting_for_address)
async def address_enter(message: types.Message, state: FSMContext):
inline_button = InlineKeyboardButton(text = 'Take order', callback_data='take')
inline_keyboard = types.InlineKeyboardMarkup(resize_keyboard = True, one_time_keyboard=True).add(inline_button)
address = message.text
await state.update_data(myTelephone=await getPhone(mydb,message))
await state.update_data(myAddress=address)
await state.update_data(myId=message.from_user.id)
user_data = await state.get_data()
chatId = '-###'
await bot.send_message(chatId, text=emoji.emojize(f"❗️ <b>New order</b> ❗️\nAddress : <b>{user_data['myAddress']}</b>\nCustomers telephone number : <b>{user_data['myTelephone']}</b>"),parse_mode='html',reply_markup = inline_keyboard)
await message.answer('Your order is sent',parse_mode='html')
#dp.callback_query_handler(lambda call: call.data == 'take' )
async def agree_ref_start(query: types.CallbackQuery, state: FSMContext):
if query.data == 'take':
await query.answer("I am callback!")
await bot.edit_message_text(chat_id=query.message.chat.id, message_id=query.message.message_id, text=emoji.emojize(f"✅ <b>Order is accepted</b> ✅\nАдрес : <b>{user_data['myAddress']}</b>\nCustomer telephone : <b>{user_data['myTelephone']}</b>\nOrder is accepted by #{query.from_user.username}"),parse_mode='html', reply_markup=None)
await bot.send_message(user_data['myId'],f"✅Your order is accepted\nYour telephone number <b>{user_data['myTelephone']}</b>\ndriver id - {query.from_user.id} ",parse_mode='html')
await state.finish()
Related
How can I have different interactions response on different buttons in pycord/discord.py.
Code:
#client.command()
async def test(ctx: commands.Context):
button = Button(label="Click Below", custom_id="freefire",style=discord.ButtonStyle.green, emoji="<:freefire:944183849779335198>")
button1 = Button(label="Click Below", custom_id="bgmi",style=discord.ButtonStyle.green, emoji="<:bgmi:944184219528208384>")
async def free_fire(interaction: discord.Interaction):
role = ctx.guild.get_role(944152201784336414)
member = ctx.guild.get_member(interaction.user.id)
if role in interaction.user.roles and interaction.custom_id== "freefire":
await interaction.user.remove_roles(role)
await interaction.response.send_message(f"{role} role has been taken from you", ephemeral=True)
else:
await member.add_roles(role)
await interaction.response.send_message(f"{role} role has been given to you", ephemeral=True)
button.callback = free_fire
async def bgmi(interaction: discord.Interaction):
role = ctx.guild.get_role(944152314200088667)
member = ctx.guild.get_member(interaction.user.id)
if role in interaction.user.roles and interaction.custom_id== "bgmi":
await interaction.user.remove_roles(role)
await interaction.response.send_message(f"{role} role has been taken from you", ephemeral=True)
else:
await member.add_roles(role)
await interaction.response.send_message(f"{role} role has been given to you", ephemeral=True)
button.callback = bgmi
It simply gives the role of last button when I click the first button and other buttons do not respond at all there is no error in console or anywhere.
The problem is that you first set button.callback to free_fire, but then you set button.callback to bgmi. This can be solved by changing the second button.callback = ... to button1.callback = ...
The task of my code:
When the moderator issues or removes roles from any server participant, the bot reads the logs, and then sends a message to the specified channel about which role was changed and to which, and so on.
My problem:
When a moderator adds or removes several roles at once, the bot sends a message with information for each role at once. But I want there to be a delay when sending the event. This clogs up the chat logs and is annoying.
For example:
I delete 5 roles at once, the bot has a delay of sending a message of 30 seconds. And in this message, he adds all 5 roles, not one at a time.
CODE:
#Bot.event
async def on_member_update(before, after):
if before.roles != after.roles:
channel = Bot.get_channel(827986763606786099)
emb = discord.Embed(description = f'**Updating user roles - {before.mention}**', colour = discord.Color.red())
emb.add_field(name = '**Roles before**', value = ", ".join([r.mention for r in before.roles]))
emb.add_field(name = '**Roles after**', value = ", ".join([r.mention for r in after.roles]))
async for event in before.guild.audit_logs(limit=1, action=discord.AuditLogAction.member_role_update):
if getattr(event.target, "id", None) != before.id:
continue
emb.add_field(name="Changed roles", value = ", ".join([getattr(r, "mention", r.id) for r in event.before.roles or event.after.roles]))
emb.add_field(name="Moderator", value = event.user)
break
await channel.send(embed = emb)
https://i.stack.imgur.com/v3h1v.jpg
Before running be sure to import asyncio import asyncio
cooldown = []
#bot.event()
async def on_member_update(before, after):
if before.roles != after.roles:
global cooldown
if before in cooldown:
return
cooldown.append(before)
await asyncio.sleep(10) #here you can change how long the cooldown should be
cooldown.remove(before)
channel = bot.get_channel(688344722082627686)
emb = discord.Embed(description=f'**Updating user roles - {before.mention}**', colour=discord.Color.red())
emb.add_field(name='**Roles before**', value=", ".join([r.mention for r in before.roles]))
emb.add_field(name='**Roles after**', value=", ".join([r.mention for r in after.roles]))
changed_roles = []
for role in before.roles:
if role in after.roles:
pass
else:
changed_roles.append(role)
for role in after.roles:
if role in before.roles:
pass
else:
if role in changed_roles:
pass
else:
changed_roles.append(role)
text = ""
for role in changed_roles:
text = text + role.mention
emb.add_field(name="Changed roles", value=text)
async for event in before.guild.audit_logs(limit=1, action=discord.AuditLogAction.member_role_update):
if getattr(event.target, "id", None) != before.id:
continue
emb.add_field(name="Moderator", value=event.user)
break
await channel.send(embed=emb)
Had to change getting the changed roles a bit, since I wasn't able to get how many audit logs I should have fetch in.
So what happens is:
user is getting added to cooldown list, bot waits for 10 seconds so that the moderator can finish removing/adding the roles, after that bot gathers them all, removes the user from cooldown and sends the embed.
Here is the fully working code:
#Bot.event
async def on_member_update(before, after):
if before.roles != after.roles:
global cooldown
if before in cooldown:
return
cooldown.append(before)
await asyncio.sleep(5) # here you can change how long the cooldown should be
cooldown.remove(before)
channel = Bot.get_channel(ID log channel)
emb = discord.Embed(description=f'**Updating user roles - {before.mention}**', colour=discord.Color.orange())
emb.add_field(name='Roles before', value=", ".join([r.mention for r in before.roles][1:]), inline=False)
emb.add_field(name='Roles after', value=", ".join([r.mention for r in after.roles][1:]), inline=False)
changed_roles = []
for role in before.roles:
if role in after.roles:
pass
else:
changed_roles.append(role)
for role in after.roles:
if role in before.roles:
pass
else:
if role in changed_roles:
pass
else:
changed_roles.append(role)
text = ""
blacklist=[797920206407598098,817750571330961410,797916381621256202] # list roles, that should not be read
for role in changed_roles:
if role.id in blacklist:
return
text = text + role.mention
emb.add_field(name="Changed roles", value=text, inline=False)
async for event in before.guild.audit_logs(limit=1, action=discord.AuditLogAction.member_role_update):
if getattr(event.target, "id", None) != before.id:
continue
emb.add_field(name="Moderator", value=f"{event.user} \n**ID**: {event.user.id}")
break
await channel.send(embed=emb)
Thank you very much for helping #NimVrod
I am creating ticket tool for my server. So whenever a user will react to the message a new channel will be created, but I want the bot to send a message in that new channel saying "React below to delete the ticket.". But can I do that without storing msg_id and other data in a global variable?
Code:
#bot.event
async def on_raw_reaction_add(payload):
if payload.member.id != bot.user.id and str(payload.emoji)== u"\U0001F3AB":
msg_id, channel_id, category_id= bot.ticket_configs[payload.guild_id]
if payload.message_id == msg_id:
guild= bot.get_guild(payload.guild_id)
for category in guild.categories:
if category.id == category_id:
break
guild = await bot.fetch_guild(460654239094669332)
channels = guild.text_channels
channel = guild.get_channel(842807548968042536)
duplicate = False
topic = f"<#!{payload.member.id}>'s ticket"
for channel in channels:
if topic == channels.topic:
duplicate = True
break
if duplicate:
return
else:
ticket_num= 1 if len(category.channels) == 0 else int(category.channels[-1].name.split("-")[1]) + 1
ticket_channel= await category.create_text_channel(f"Ticket-{ticket_num}", topic= topic, permission_synced= True)
await ticket_channel.set_permissions(payload.member, read_messages= True, send_messages= True)
print(msg_id)
embed= discord.Embed(title= "New Ticket!!!", description= f"{payload.member.mention} Thank You! for creating this ticket staff will contact you soon. Type **-close** to close the ticket.")
message= await ticket_channel.send(embed=embed)
await message.add_reaction("❌")
Help will be really appreciated
You can use bot.wait_for with a simple check
message = await ctx.send("React below to delete the ticket")
def check(reaction, user):
return reaction.message == message and str(reaction) == "✅" # you can use another emoji, or don't use it at all
try:
await bot.wait_for("reaction_add", check=check, timeout=60.0) # timeout is optional
except asyncio.TimeoutError:
...
else:
await channel.delete() # where `channel` is the channel to delete
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.)
I have a for loop that runs through a list of questions creating an embed for each question. It is meant to then wait for an answer and then ask the next question. I then upload the answers to a mongodb database (after running some checks to ensure they are valid answers.
The issue I am having is that sometimes (3 in 10 tries) it will ask two questions directly after one another without giving any time to respond to the first. I have played around with sleep() but found it still happens.
Would appreciate any help you can offer!
import re
import datetime
from copy import deepcopy
import emojis
import asyncio
import discord
import math
import random
from discord.ext import commands, tasks
from dateutil.relativedelta import relativedelta
from utils.util import GetMessage
from time import sleep
"""
product = ['Name': 'Example_Name', 'Description': 'Example Description', 'Quantity in stock': 10, 'Price': 400]
Name =product[0]
Description = product[1]
Quantity = product[2]
Price= product[3]
"""
class Shop(commands.Cog):
def __init__(self, bot):
self.bot = bot
#commands.Cog.listener()
async def on_ready(self):
print(f"{self.__class__.__name__} Cog has been loaded\n-----")
#commands.command(
name='ShopAddProduct',
#aliases=['w'],
description="List all the Shops",
#usage = "[User] <Name>"
)
#commands.has_role("Server Player")
#commands.has_permissions(send_messages=True)
async def ShopAddProduct(self, ctx):
member = ctx.author
channel = await member.create_dm()
await channel.send("Lets add a product. Answer the following questions to add a product to your shop")
questionList = [
["What is your shop called?","Enter the Name of your shop"],
["What is the product called?","Enter what your product is called"],
["Describe your product","Enter a description"],
["How many of them do you have ready to sell?","Enter how many you have in stock"],
["how much are you selling them for?","Enter how many credits you want for this product."]
]
answers = {}
for i, question in enumerate(questionList):
#answer = await GetMessage(self.bot, ctx, question[0], question[1])
embed = discord.Embed(
title=f"{question[0]}",
description =f"{question[1]}",
)
embed.set_thumbnail(url=ctx.guild.icon_url)
sent = await channel.send(embed=embed)
try:
answer = await self.bot.wait_for(
"message",
timeout=60,
check=lambda message: isinstance(message.channel, discord.channel.DMChannel)
)
except asyncio.TimeoutError:
await sent.delete()
await channel.send("Cancelling due to timeout.", delete_after=10)
sleep(2)
answers[i] = answer.content
embed = discord.Embed(name="Add Product")
for key, value in answers.items():
embed.add_field(name=f"Question: `{questionList[key][0]}`", value=f"Answer: `{value}`", inline=False)
m = await channel.send("Are these all valid?", embed=embed)
await m.add_reaction("✅")
await m.add_reaction("🇽")
try:
reaction, member = await self.bot.wait_for(
"reaction_add",
timeout=60,
check=lambda reaction, user: user == ctx.author
and reaction.message.channel == channel
)
except asyncio.TimeoutError:
await channel.send("Confirmation Failure. Please try again.")
return
if str(reaction.emoji) not in ["✅", "🇽"] or str(reaction.emoji) == "🇽":
await channel.send("Cancelling Product Addition!")
#check answers validity
check = await self.bot.shop.find_by_id(answers[0])
if check == None:
await channel.send(f"The shop `{answers[0]}` does not exist! Please try again.")
return
if not answers[3].isdigit():
#not isinstance(answers[3],int):
await channel.send(f"The quantity you submitted (`{answers[3]}`) must be whole number (integer)! Please try again.")
return
if not answers[4].isdigit():
#not isinstance(answers[4],int):
await channel.send(f"The price you submitted (`{answers[4]}`) must be whole number (integer)! Please try again.")
return
shopcheck = await self.bot.shopCatalogue.find_by_id(answers[0])
productlist = shopcheck['Products']
print(productlist)
product = [answers[1], answers[2], int(answers[3]), int(answers[4])]
productlist.append(product)
print(product)
print(productlist)
data = {
'_id': shopcheck['_id'],
'Products': productlist
}
guild=ctx.guild
await self.bot.shopCatalogue.upsert(data)
embed = discord.Embed(
title=f"Product Added to Shop Catalogue!",
#description =,
)
embed.add_field(
name=f"Product:",
value=answers[1],
inline=True
)
embed.add_field(
name=f"Description:",
value=answers[2],
inline=True
)
embed.add_field(
name=f"Quantity:",
value=answers[3],
inline=False
)
embed.add_field(
name=f"Price:",
value=answers[4],
inline=True
)
embed.set_thumbnail(url=ctx.guild.icon_url)
sent = await channel.send(embed=embed)
def setup(bot):
bot.add_cog(Shop(bot))
I've seen this error before, it's happens when the bots message sends fast enough to pass the check, easy fix just make sure that only you can answer to the messages
# add this
def check(message):
channel_id = message.channel.id
author_id = message.author.id
return channel_id == ctx.channel.id and author_id == ctx.author.id
# change this
reaction, member = await self.bot.wait_for(
"reaction_add",
timeout=60,
check=check
)