Discord.py - user.interaction problem with buttons - python

The Context:
I'm creating a discord game where each channel represents a location in the game.
I have a button menu that moves a user from one channel to another when they click button 1. It does this by adding and removing roles attached to each channel.
The execution of button 1 has a delay in the form of asyncio.sleep(). This is to mimic travel time.
Button 2 appears in a response after a user clicks button 1. The goal of Button 2 is to cancel the execution of button 1 while it's still executing.
I am trying to achieve this by adding an if statement right before button 1 finishes executing. This if statement checks if the user has a role called 'cancel' which is added to the user when they click button 2.
If the user has the 'cancel' role, then button 1 just ends its execution immediately. Else it continues through with its normal execution.
The Problem:
User is defined as interaction.user. The user who interacted with the button.
The button function does not see roles added to the user after it has interacted with the button, only roles at the time of interaction.
Therefore, if the 'cancel' role is added after the initial button 1 click, the button 1 function can't see it when checking for it in the if statement.
Resulting in the check always saying the user does not have the 'cancel' role, even if they do.
Full code:
import asyncio
import discord
from discord.ext import commands
# this just imports role names/ids from another file
import data.roles as ccroles
class cancel(discord.ui.View):
def __init__(self):
super().__init__(timeout = None)
#discord.ui.button(label="Cancel",
custom_id="cancel",
style=discord.ButtonStyle.red)
async def buttoncancel(self, interaction, button):
user = interaction.user
await interaction.response.send_message("You clicked me!",
ephemeral=True,)
await user.add_roles(user.guild.get_role(ccroles.cancel))
print('Cancel added.')
class Menu(discord.ui.View):
def __init__(self):
super().__init__(timeout = None)
#discord.ui.button(label="Location 2",
custom_id="location2",
style=discord.ButtonStyle.blurple)
async def button1(self, interaction, button):
user = interaction.user
if ccroles.location_1 in [y.id for y in user.roles]:
await interaction.response.send_message("Traveling to Location 2!",
ephemeral=True, view=cancel())
await asyncio.sleep(10)
print('I waited 10 seconds.')
if ccroles.cancel in [y.id for y in user.roles]:
print('Cancelled travel.')
await user.remove_roles(user.guild.get_role(ccroles.cancel))
else:
await asyncio.sleep(1)
print('I waited 1 second.')
await user.edit(roles=[])
await user.add_roles(user.guild.get_role(ccroles.location_2))
class exampleMenu(commands.Cog):
def __init__(self, client: commands.Bot):
self.client=client
#commands.command()
async def exampleMenu(self, ctx):
embed = discord.Embed(title="Travel Menu",
description="Where would you like to travel?")
await ctx.send(embed=embed,
view=Menu())
async def setup(client:commands.Bot) -> None:
await client.add_cog(exampleMenu(client))
The Question:
Is there any way to get the button 1 function, while it's executing, to recognize that the 'cancel' role has been added to the user?
Alternatively, is there any other way to achieve the goal of button 2?
code is contained within a discord.py cog btw
thank you <3
example of how it looks

Like you mentioned, it will only see the roles the user has at the time of interaction. You don't necessarily have to have a role for everything, you can do the check within your code. In this example, I used a list.
travelling = []
class Menu(discord.ui.View):
def __init__(self):
super().__init__(timeout = None)
#discord.ui.button(label="Location 2",
custom_id="location2",
style=discord.ButtonStyle.blurple)
async def button1(self, interaction, button):
user = interaction.user
if user.id not in travelling:
travelling.append(user.id)
#This works because .remove() removes all occurrences of this item
#but you can also use try/except: pass
await interaction.response.send_message("Travelling to Location 2!",
ephemeral=True, view=Cancel())
await asyncio.sleep(10)
print('I waited 10 seconds.')
if user.id in travelling:
print("not cancelled") #do stuff here
travelling.remove(user.id)
else:
print("cancelled") #do stuff here
class Cancel(discord.ui.View): #Should capitalise class name
def __init__(self):
super().__init__(timeout = None)
#discord.ui.button(label="Cancel",
custom_id="cancel",
style=discord.ButtonStyle.red)
async def buttoncancel(self, interaction, button):
user = interaction.user
await interaction.response.send_message("You clicked me!",
ephemeral=True, view=Menu())
travelling.remove(user.id)
print('Cancel added.')
However, if you would like to get the roles of the user, you can get the member object again after that 10 seconds which has the new set of roles:
class Menu(discord.ui.View):
def __init__(self):
super().__init__(timeout = None)
#discord.ui.button(label="Location 2",
custom_id="location2",
style=discord.ButtonStyle.blurple)
async def button1(self, interaction, button):
user = interaction.user
await interaction.response.send_message("Travelling to Location 2!", ephemeral=True, view=Cancel())
await asyncio.sleep(10)
print('I waited 10 seconds.')
user = interaction.guild.get_member(user.id) #<<<
#Your check v
if ccroles.cancel in [y.id for y in user.roles]:
print('Cancelled travel.')
await user.remove_roles(user.guild.get_role(ccroles.cancel))
else:
await asyncio.sleep(1)
print('I waited 1 second.')
await user.edit(roles=[])
await user.add_roles(user.guild.get_role(ccroles.location_2))

Related

Discord.py UI send selectionOption when clicking button. (Version = DPY 2.0a)

#client.tree.command(name = "roles", description = "Role Test Menu.", guild =TEST_GUILD)
async def roles(interaction: discord.Interaction):
await interaction.response.send_message(view=RoleButton(), ephemeral = True)
##################################################################################################
class RoleButton(discord.ui.View):
def __init__(self):
super().__init__(timeout=None)
#discord.ui.button(label='Role Menu', style=discord.ButtonStyle.green, custom_id='Role Menu')
async def rolebutton(self, interaction: discord.Interaction, button: discord.ui.Button):
await interaction.response.defer()
test1 = discord.utils.get(interaction.guild.roles, name='Test1')
test2 = discord.utils.get(interaction.guild.roles, name='Test2')
if test1 in interaction.user.roles:
df = True
elif test2 in interaction.user.roles:
df = True
else:
df = False
options = [
discord.SelectOption(label="Test 1", value="Test 1", default = df),
discord.SelectOption(label="Test 2", value="Test 2", default = df)
]
#discord.ui.select(placeholder="Select",custom_id="test",max_values=2, options=options)
async def _action_select(self, interaction: discord.Interaction, select: discord.ui.Select):
await interaction.response.send_message("Done!", ephemeral = True)
##################################################################################################
So, this obviously doesn't work, and I know that. But this is an example of what I am trying to do; essentially, I want the slash command to send the "Role Menu" button. Then when it it clicked by a user, check if the user has "test1" and "test2" roles. If they do/don't, then set the default selectionOption to T/F, then send the select. How can I change this to have it work how I want it to?
To add a ui.Button or a ui.Select to a message, you need a ui.View to which you add those things. The view then gets added to the message.
Solution:
import discord
from discord import ui
bot = commands.Bot()
class Select(ui.Select):
def __init__(self, options):
# placeholder: Will be shown when no option is chosen
# custom_id: The id of the select menu
# options: The dropdown options which can be chosen
# max_values: Indicates that max. 2 options can be picked
super().__init__(placeholder="Select", custom_id="test", options=options, max_values=2)
# This function is called when the user has chosen an option
async def callback(self, interaction: discord.Interaction):
# With the interaction parameter, you can send a response message.
# With self.values you get a list of the user's selected options.
print(self.values)
await interaction.response.send_message(f"Done!", ephemeral=True)
class ViewButton(ui.View):
# label: The label of the button which is displayed
# style: The background color of the button
#ui.button(label="Role Menu", style=discord.ButtonStyle.blurple)
async def role_menu_btn(self, interaction: discord.Interaction, button_obj: ui.Button):
# This function is called when a user clicks on the button
# get the roles
test1_role = interaction.guild.get_role(1007237710295081020)
test2_role = interaction.guild.get_role(1007237773230620728)
# check if user has the role or not
df1 = True if test1_role in interaction.user.roles else False
df2 = True if test2_role in interaction.user.roles else False
options = [
discord.SelectOption(label="Test 1", value="Test 1", default=df1),
discord.SelectOption(label="Test 2", value="Test 2", default=df2)
]
# create ui.Select instance and add it to a new view
select = Select(options=options)
view_select = ui.View()
view_select.add_item(select)
# edit the message with the new view
await interaction.response.edit_message(content="Choose an option", view=view_select)
#bot.tree.command(name="btn", description="Wonderful button")
async def btn(interaction: discord.Interaction):
await interaction.response.send_message("Click it!", view=ViewButton())
References:
ui.View
ui.button
ui.Select

How to end an interaction in discord embed buttons (Python)

I have made a meme command which looks like this
Is there any way to end the interaction on clicking the End Interaction button. Please help me with the end_interaction function.
Code:
#commands.command(aliases=['m'], description='Posts memes from r/memes')
async def meme(self, ctx):
submissions = await get_memes('memes')
button1 = Button(label='Next Meme', style=discord.ButtonStyle.green)
button2 = Button(label='End Interaction', style=discord.ButtonStyle.red)
async def next_meme(interaction):
if len(submissions) == 0:
await interaction.response.edit_message(content='No more memes available')
return
submissions.pop(0)
embed.title = submissions[0].title
embed.url = submissions[0].url
embed.set_image(url=submissions[0].url)
await interaction.response.edit_message(embed=embed)
async def end_interaction(interaction):
pass
# I have no idea what to do here
view = View()
view.add_item(button1)
view.add_item(button2)
embed = discord.Embed(title=submissions[0].title, url=submissions[0].url, colour=discord.Colour.random())
embed.set_image(url=submissions[0].url)
await ctx.send(embed=embed, view=view)
button1.callback = next_meme
button2.callback = end_interaction
If I understand what you're trying to do properly, can't you try to change the button1 & button2 callback to None? (or any other way to kind of "unbind" the button to the functions).
EDIT: Seems there is a disabled bool on the buttons, you can try something like
async def end_interaction(interaction):
button1.disabled = True
button2.disabled = True
Source of the edit info: https://stackoverflow.com/a/71013761/18312347

Nextcord; Button is not showing

so I've been trying to make a bot that explains other bots economy system.
There's a message that comes up after you click a certain button and I want to add another button to that certain message. It doesn't work and I have no idea why.
Heres the code:
from nextcord.ext import commands
from nextcord.ui import view
def Sbdrugs(self, ctx):
embed = nextcord.Embed(title="Drugs", description="Drugs play an important role in Slotbot. So make sure to remember what they do.")
embed.set_author(name = ctx.author.display_name, icon_url = ctx.author.avatar_url)
embed.add_field(name = "Weed", value = "Weed can be smoked to restart cooldowns of almost everything including drink. (*Doesnt include drugs.*) Remember, weed is the currency of the black market.", inline = False)
embed.add_field(name = "Drink", value = "Drinking beers basically restarts every cooldown except drugs.", inline = False)
embed.add_field(name = "Steroids", value = "Dosing steroids makes you immune against the ~hex command and gives you access to the ~beatup command. Steroids even decrease the chance of getting scammed from 30% to 10%. It shortens the feed cooldown too.", inline = False)
embed.add_field(name = "Opioids", value = "Make you immune against gun shots, makes you immune against beatup too and resets all cooldowns (except drugs) including smoke and drink.", inline = False)
embed.add_field(name = "Anesthesia **AKA** Anes", value = "Stuns yourself but also makes others not possible to interact with your slotbot stats (bal, goose etc they cant check anything from you).", inline = False)
embed.add_field(name = "LSD", value = "Shortens every cooldown to 15 secs but makes it very hard to do other cooldowns like bal or farm for example. LSD is mostly used for pill farming, constantly shooting people and for some other things but i currently cant remember.", inline = False)
def Helpmeembed(self, ctx):
embed = nextcord.Embed(title = "You called me...?", description= "What do you need help with?")
#still working on this
class SbDrugs(nextcord.ui.View):
def __init__(self):
super().__init__()
self.value = None
#nextcord.ui.button(label = "1", style = nextcord.ButtonStyle.blurple)
async def helpb(self, button: nextcord.ui.Button, interaction: nextcord.Interaction):
await interaction.response.send_message({Sbdrugs}, ephemeral=False)
self.value = True
self.stop()
class Helpb(nextcord.ui.View):
def __init__(self):
super().__init__()
self.value = None
#nextcord.ui.button(label = "1", style = nextcord.ButtonStyle.blurple)
async def helpb(self, ctx, button: nextcord.ui.Button, interaction: nextcord.Interaction):
view = Sbdrugs()
await interaction.response.send_message("What do you need help with on Slotbot?\n**1. Drugs**", ephemeral=False, view = view)
self.value = True
self.stop()
class Help(commands.Cog, name="Help"):
def __init__(self, bot: commands.Bot):
self.bot = bot
#commands.command()
async def helpme(self, ctx):
view = Helpb()
await ctx.send("Hey, I'm here, what do you need help with?\n\n**Slotbot**\n*Press 1 for more information.*", view = view)
await view.wait()
if view.value is None:
await ctx.send("Ay, where you at bruh")
def setup(bot: commands.Bot):
bot.add_cog(Help(bot))
This is the Error im getting:
Ignoring exception in view <Helpb timeout=180.0 children=1> for item <Button style=<ButtonStyle.primary: 1> url=None disabled=False label='1' emoji=None row=None>:
Traceback (most recent call last):
File "C:\Users\Windows\AppData\Local\Programs\Python\Python39\lib\site-packages\nextcord\ui\view.py", line 359, in _scheduled_task
await item.callback(interaction)
TypeError: helpb() missing 1 required positional argument: 'interaction'
You set "ctx" to second param, you should remove it, like this
class Helpb(nextcord.ui.View):
def __init__(self):
super().__init__()
self.value = None
#nextcord.ui.button(label = "1", style = nextcord.ButtonStyle.blurple)
# async def helpb(self, ctx, button: nextcord.ui.Button, interaction: nextcord.Interaction):
async def helpb(self, button: nextcord.ui.Button, interaction: nextcord.Interaction):
view = Sbdrugs()
await interaction.response.send_message("What do you need help with on Slotbot?\n**1. Drugs**", ephemeral=False, view = view)
self.value = True
self.stop()

How to change the disabled parameter of a button, depending on instance variables. Nextcord.py

I want to disable the button when the product store reaches one of the bounds and you can no longer click on the "Next Page" or "Past Page" button. But since you can't pass instance variables to decorators, I can't compare values and pass them to #nextcord.ui.button. Can you help with this problem? Here is my code:
import nextcord
from nextcord.ext import commands
from SupportClasses import BuildShop as bd
from SupportClasses import Build
class ShopBuildings(commands.Cog):
def __init__(self, bot: commands.Bot):
self.bot = bot
#commands.command()
async def ShopBuild(self, ctx, numberPage=1):
shopBuildings = bd.getBuildShop()
view = ShopPages(shopBuildings, int(numberPage))
await ctx.send(embed=createEmded('ShopBuildings', shopBuildings[int(numberPage)-1], view=view))
class ShopPages(nextcord.ui.View):
def __init__(self, shop, numberPage):
super().__init__()
self.__shop = shop
self.__numberPage = numberPage-1
#nextcord.ui.button(label='Past Page', style=nextcord.ButtonStyle.primary)
async def BackPage(self, button: nextcord.ui.button, interaction: nextcord.Interaction):
self.__numberPage-=1
await interaction.response.edit_message(embed=embed=createEmded('ShopBuildings', self.__shop[self.__numberPage]))
#nextcord.ui.button(label='Next Page', style=nextcord.ButtonStyle.primary)
async def NextPage(self, button: nextcord.ui.button, interaction: nextcord.Interaction):
self.__numberPage+=1
await interaction.response.edit_message(embed=createEmded('ShopBuildings', self.__shop[self.__numberPage]))
You can disable buttons with self.mybutton.disabled = True.
So for your code it would be something like:
def __init__(self, shop, numberPage):
super().__init__()
self.__shop = shop
self.__numberPage = numberPage-1
if __numberPage == 0:
self.BackPage.disabled == True
if __numberPage == __maxPage:
self.NextPage.disabled == True

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)

Categories

Resources