#All buttons fail
1 messages · Page 1 of 1 (latest)
Hey! Once your issue is solved, press the button below to close this thread!
Here's some updated code that still doesn't work:
i think it cant see your component callbacks since theyre in a class. It may be worth moving everything in that class to an extension, and then loading the extension
or alternatively just taking all the interactions parts out of the class
actually a lot of stuff is weird here
# template.py
from interactions import (
Button,
ButtonStyle,
Client,
ComponentContext,
Extension,
component_callback,
slash_command,
SlashContext,
Embed
)
class Template(Extension):
def __init__(self, client: Client):
self.client = client
@slash_command(
name="template-command",
description="Just a template command",
)
async def template_command(self, ctx: SlashContext):
button = Button(style=ButtonStyle.BLUE, label="Test Button", custom_id="test_button")
await ctx.send(embed=Embed(description=f"Sent from a template command!"), components=button)
# Put component callbacks here
@component_callback("test_button")
async def test_button_callback(self, ctx: ComponentContext):
await ctx.send("Responding to the test button!")
class Game:
# Put your game stuff here
def __init__(self) -> None:
pass
def setup(bot):
# This is called by interactions.py so it knows how to load the Extension
Template(bot)
Try use this kind of structure, then load the file as an extension. It shouldnt need your on_socket_event or handle button click functions
I don't know much if anything about extensions but I'll give it a try. If it doesn't need to handle button clicks that's great!
Thank you!
you can see the guide on extensions here if u need https://interactions-py.github.io/interactions.py/Guides/20 Extensions/
Hello smart people, I have returned.
I'm trying to get the custom_id working for the buttons here to be assigned to whichever map they are on, but it always just ends up with interaction failed. Even the print statements don't fire signaling that it ain't working. How can I do this?
class Main:
@component_callback()
async def guess_callback(self, ctx: ComponentContext):
if not ctx.component.custom_id.startswith("guess_"):
return
game = game_instances[ctx.guild.id]
map_guess = ctx.component.custom_id.split('guess_', 1)[1]
print(f"Received guess: {map_guess}")
await game.handle_guess(ctx.author.id, map_guess)
await game.update_message(ctx.author.id, ctx)
class Game:
def update_message()
components = spread_to_rows(
Button(style=ButtonStyle.GREEN, label="Start New Game", custom_id="start", disabled=(self.game_status != "idle")),
Button(style=ButtonStyle.RED, label="Leave Game", custom_id="leave"),
*[
Button(style=ButtonStyle.SECONDARY, label=f"{map_name}", custom_id=f"guess_{map_name}", disabled=(self.current_round is None or player_id in self.current_round["guesses"]))
for map_name in maps
]
)
I've also tried things like this:
class Main(Extension):
def __init__(self, client: Client):
self.client = client
# Generate callback for all maps
for i, map_name in enumerate(maps):
self.make_guess_callback(i, map_name)
def make_guess_callback(self, map_id, map_name):
@component_callback(f"guess_{map_id}")
async def guess_callback(self, ctx: ComponentContext):
game = game_instances[ctx.guild.id]
map_guess = maps[int(ctx.component.custom_id.split('guess_', 1)[1])]
print(f"Received guess: {map_guess}") # Add this line
await game.handle_guess(ctx.author.id, map_guess)
await game.update_message(ctx.author.id, ctx)
setattr(self, f"guess_{map_id}_callback", guess_callback)
class Game:
# ...
async def update_message(self, player_id, ctx=ComponentContext):
# ...
components = spread_to_rows(
Button(style=ButtonStyle.GREEN, label="Start New Game", custom_id="start", disabled=(self.game_status != "idle")),
Button(style=ButtonStyle.RED, label="Leave Game", custom_id="leave"),
*[
Button(style=ButtonStyle.SECONDARY, label=f"{map_name}", custom_id=f"guess_{i}", disabled=(self.current_round is None or player_id in self.current_round["guesses"]))
for i, map_name in enumerate(maps)
]
)
# ...
@livid bolt Sorry to bug you again mate, but if you have a few seconds could you take a look at this?
To clarify further, I have a table of names, and for each of the names it creates a new button. The issue is that the custom_id's don't like this system of having one callback to cover multiple buttons and handle them properly. Essentially the end result should be that if the random map chosen matches the user's input then it should add points.
its not @component_callback that you are trying to use here its @listen
But @component_callback is what is used for buttons no? I use @component_callback for all of the other buttons and it works (but it does say interaction failed all the time)
i use @listen and i prefer using it as you can put multiple button callbacks into it
@listen()
async def on_component(ctx: ComponentContext):
event = ctx.ctx
if event.custom_id == "contributors":
if event.channel.id not in channel_cooldown:
embed = Embed(
title="⭐ Contributors",
description=f"Awesome people who have helped to make Larss_Bot what it is today!",
timestamp=datetime.utcnow(),
color=Color.from_hex("5e50d4"),
)
embed.set_footer(
text=f"Requested by {event.author.display_name}", icon_url=event.author.avatar.url
)
value = ""
for contributor in epiccontribbutingppl:
value += f"> <@{contributor}> \n"
embed.add_field(
name="Contributors",
value=value,
)
value = ""
for lilhelper in lilhelpers:
value += f"> <@{lilhelper}> \n"
embed.add_field(
name="Little helpers",
value=value,
)
await event.send(embed=embed)
channel_cooldown.append(event.channel.id)
await asyncio.sleep(15)
channel_cooldown.remove(event.channel.id)
else:
await event.send(
"Looks like someone already pressed the button. No need to do it again.",
ephemeral=True,
)
if event.custom_id == "guildsinvites":
if event.channel.id not in invite_cooldown:
embed = Embed(
title="Partner servers",
description=f"Press any of the buttons below to get invited to one of the partnered servers",
timestamp=datetime.utcnow(),
color=Color.from_hex("5e50d4"),
)
embed.set_footer(
text=f"Requested by {event.author.display_name}", icon_url=event.author.avatar.url
)
btn1 = Button(
style=ButtonStyle.URL,
label="Dev lab",
emoji="🧪",
url="https://discord.gg/TReMEyBQsh",
)
btn2 = Button(
style=ButtonStyle.URL,
label="Big-Floppa software solutions",
emoji="🐱",
url="https://discord.gg/MDVcb8wdUd",
)
btn3 = Button(
style=ButtonStyle.URL,
label="Tyler army",
emoji="🐸",
url="https://discord.gg/w78rcjW8ck",
)
components: list[ActionRow] = spread_to_rows(
btn1,
btn2,
btn3,
)
await event.send(embed=embed, components=components)
channel_cooldown.append(event.channel.id)
await asyncio.sleep(15)
channel_cooldown.remove(event.channel.id)
else:
# ephemeral not gonna work, once again
await event.send(
"Looks like someone already pressed the button. No need to do it again.",
ephemeral=True,
)
Let me try that, thanks
@finite badge Now I get the problem of this:
event = ctx.ctx
AttributeError: 'Main' object has no attribute 'ctx'
File "/root/TTS/game.py", line 79, in on_component
if ctx.custom_id.startswith("guess_"):
AttributeError: 'Main' object has no attribute 'custom_id'
can you send your code please
I reverted back to something more readable. You can focus on the update_message funciton and make_guess_callback.
I apologize for the mess. I first make a giant pile of spagette and then clean it up once I get something working.
its ctx.ctx.custom_id
why two?
because the first ctx isnt an object
i dunno i cant really explain it
might want to ask polls or someone else that has developed this lib
Alas that did not work:
File "/root/TTS/game.py", line 79, in on_component
if ctx.ctx.custom_id.startswith("guess_"):
AttributeError: 'Main' object has no attribute 'ctx'
@finite badge
on_component returns a component object which contains ctx, but i dont think theres any point using @listen instead of a component callback for your use case as far as i understand it
oh wait nvm it does look kind of useful here
@mossy bloom you can get rid of ```py
Generate callback for all maps
for i, map_name in enumerate(maps):
self.make_guess_callback(i, map_name)```
from your init and change
def make_guess_callback(self, map_id, map_name):
@listen()
async def on_component(ctx: ComponentContext):
if ctx.component.custom_id == f"guess_{map_id}":
game = game_instances[ctx.guild.id]
map_guess = maps[int(ctx.component.custom_id.split('guess_', 1)[1])]
print(f"Received guess: {map_guess}")
await game.handle_guess(ctx.author.id, map_guess)
await game.update_message(ctx.author.id, ctx)
setattr(self, f"guess_{map_id}_callback", on_component)
to ```py
@listen("on_component")
async def make_guess_callback(self, component):
ignore non guess buttons
if not component.ctx.custom_id.startswith("guess"):
return
map_id = component.ctx.custom_id.split("_")[1]
game = game_instances[component.ctx.guild.id]
map_guess = maps[int(map_id)]
print(f"Received guess: {map_guess}")
await game.handle_guess(component.ctx.author.id, map_guess)
await game.update_message(component.ctx.author.id, ctx)
idrk whats happening with the game instances and maps stuff so might need to massage the code a bit to make it work
Thank you! I'll give that a whirl and see what happens :)
It was 0:31 for me 🗿
My apologies! You were one so I presumed you were around.
@livid bolt Any clue how I can fix the interaction failed message for everything?
you need to respond to the interaction
But I am, I'm editting the message
is it being edited successfully?
can u send the bit where u edit the message
async def update_message(self, player_id, ctx: ComponentContext):
# Load the scores before updating the message
self.load_scores()
if self.message is None and ctx is not None:
self.message = await ctx.send("Starting game...")
leaderboard_text = '\n'.join([f"<@{player}>: {score}" for player, score in sorted(self.players.items(), key=lambda x: x[1], reverse=True)])
components = spread_to_rows(
Button(style=ButtonStyle.GREEN, label="Start New Game", custom_id="start", disabled=(self.game_status != "idle")),
Button(style=ButtonStyle.RED, label="Leave Game", custom_id="leave"),
*[
Button(style=ButtonStyle.SECONDARY, label=f"{map_name}", custom_id=f"guess_{i}", disabled=(self.current_round is None or player_id in self.current_round["guesses"]))
for i, map_name in enumerate(maps)
]
)
if self.current_round is not None:
with open(os.path.join(self.image_dir, self.current_round["image_file"]), "rb") as img_file:
file = File(img_file, file_name=self.current_round["image_file"])
await self.message.edit(content=leaderboard_text, components=components, files=[file])
else:
await self.message.edit(content=f"Not Functioning", components=components)
game = game_instances[ctx.guild.id]
initial_message = "Welcome to Geoguesser! Click 'Start Game' to begin a new game, or 'Join Game' to join an existing game. You can also view the leaderboard."
components = [
ActionRow(
Button(style=ButtonStyle.GREEN, label="Start Game", custom_id="start"),
Button(style=ButtonStyle.GREEN, label="Join Game", custom_id="join"),
Button(style=ButtonStyle.BLURPLE, label="Leaderboard", custom_id="leaderboard"),
)
]
await game.message.edit(content=initial_message, components=components)
oh
when u edit the message it doesnt count as being a response to the interaction cause its not through the interactions context
so u gotta use ctx.edit_origin() instead
or ctx.edit() if its not from a button
gotcha, let me try that
I get this back: await self.message.edit_origin(content=f"Not Functioning", components=components) AttributeError: 'Message' object has no attribute 'edit_origin'
Ignoring exception in Component Callback for start: HTTPException: 400|Bad Request || Interaction has already been acknowledged.
It can only edit once it seems
yeah only once per interaction
after the first u need to edit the message like how u were before
So I understand correctly, I only edit the message once with edit_origin? But wouldn't the other buttons then just have the interaction error still?
The error you sent happens when you respond to an interaction more than once
So you must be editing multiple times per button press
Or just responding generally multiple times
If that’s the case then u can avoid the error by responding only once, and then editing the message separately afterward