I'm currently coding a quizz bot and I came up with this code:
class answers(nextcord.ui.View):
def __init__(self, ans1, ans2, ans3):
super().__init__(timeout=None)
self.ans1 = ans1
self.ans2 = ans2
self.ans3 = ans3
self.value = False
#nextcord.ui.button(label = '1', custom_id="1", style = nextcord.ButtonStyle.red)
async def reaction(self, button: nextcord.ui.Button, interaction: nextcord.Interaction):
await interaction.response.send_message('ans1')
self.value = True
self.stop()
#nextcord.ui.button(label='2', custom_id='2' )
async def reaction(self, button: nextcord.ui.Button, interaction: nextcord.Interaction):
await interaction.response.send_message('ans2')
self.value = True
self.stop()
#nextcord.ui.button(label='3', custom_id='3')
async def reaction(self, button: nextcord.ui.Button, interaction: nextcord.Interaction):
await interaction.response.send_message('ans3')
self.value = True
self.stop()
This is the class that should display 3 buttons. However it displays only the third one. I do not get any errors.
How can I write this code so it does display all three?
You'll need to use different method names for each of the buttons. Changing the names to reaction1, reaction2, reaction3 would fix your issue.
The reason is the ui.button decorator doesn't store the button info elsewhere, instead it attaches the relevant info onto the method and returns the method back. So having the same name for the three method, you're actually reassigning answers.reaction every time you define a button, and only the last one gets kept. discord.py only evaluates all of the components from a view at runtime, so the last button is all it sees—thus it only displays the last button.
Related
I am trying to implement a "Back" button that will return to the previous dropdown.
I am using Pycord.
I have a dropdown with options to pick different food categories, after you pick a category, the dropdown menu changes to a new dropdown where see items in that category.
In addition, you have a "Back" button that should get you to the previous dropdown.
At the moment I get In components.0.components.0.options.1: The specified option value is already used Error, after I click the back button and click the same category again.
Here is where I recreate the issue, first I run the slash command /shop and I click the "Meats" category
Then I get to a new dropdown and I click the "Back" Button:
I get to the original dropdown.
and if I click the "Meats" cateogry again it crushes.
main.py
import discord
from example1 import CategoryView
bot = discord.Bot()
#bot.command()
async def shop(ctx):
await ctx.respond("Choose a category from the dropdown below! ✨", view=CategoryView())
#bot.event
async def on_ready():
print("Ready!")
bot.run("TOKEN")
example1.py
import discord
from example2 import CategoryPriceView
class CategoryView(discord.ui.View):
def __init__(self):
super().__init__()
#discord.ui.select(
placeholder = "Choose a food category!",
min_values = 1,
max_values = 1,
options = [
discord.SelectOption(
label="Meats",
emoji='🍖'
),
discord.SelectOption(
label="Salads",
emoji='🥗'
)
]
)
async def select_callback(self, select, interaction):
category = select.values[0]
await interaction.response.edit_message(content="Choose your item!", view=CategoryPriceView(category, self))
example2.py
import discord
options = []
class CategoryPriceView(discord.ui.View):
def __init__(self, category, father):
super().__init__()
global options
self.category = category
self.father = father
if self.category == 'Meats':
options.append(discord.SelectOption(label='Steak', description="Price: 40$"))
elif self.category == 'Salads':
options.append(discord.SelectOption(label='Greek Salad', description="Price: 30$"))
#discord.ui.button(label="Back", row=1, style=discord.ButtonStyle.blurple)
async def button_callback(self, button, interaction):
await interaction.response.edit_message(content="Choose a category from the dropdown below! ✨", view=self.father)
#discord.ui.select(
placeholder = "Choose an item!",
min_values = 1,
max_values = 1,
options = options
)
async def select_callback(self, select, interaction):
item = select.values[0]
await interaction.response.edit_message(content=f"You chose {item}! ", view=None)
EDIT: My answer was completely wrong. Edited to provide actual answer.
The issue is the global options. Every time you select an option from the original CategoryView, you're creating a new instance of CategoryPriceView and adding to options. As options is global and never gets cleared - there are duplicates in there - hence the duplicate value error. You can resolve this by subclassing Select and creating a custom CategoryPriceSelect that has the options logic you want. See below.
# example2.py
import discord
class CategoryPriceSelect(discord.ui.Select):
def __init__(self, category: str) -> None:
options = []
if category == 'Meats':
options.append(discord.SelectOption(label='Steak', description="Price: 40$"))
elif category == 'Salads':
options.append(discord.SelectOption(label='Greek Salad', description="Price: 30$"))
super().__init__(
placeholder="Choose an item!",
min_values=1,
max_values=1,
options=options
)
async def callback(self, interaction: discord.Interaction):
item = self.values[0]
await interaction.response.edit_message(content=f"You chose {item}! ", view=None)
class CategoryPriceView(discord.ui.View):
def __init__(self, category, father):
super().__init__()
self.category = category
self.father = father
select = CategoryPriceSelect(self.category)
self.add_item(select)
#discord.ui.button(label="Back", row=1, style=discord.ButtonStyle.blurple)
async def button_callback(self, button, interaction):
await interaction.response.edit_message(content="Choose a category from the dropdown below! ✨", view=self.father)
I've tested this and this works now without throwing the error and I believe this has your desired functionality.
I'm trying to create a bot that my friends and I can do predictions through for the upcoming Soccer World Championship.
Now I want this bot to be able to ask the result of each match as a question, with a button for answer.
Creating one question works perfectly, but then moving on to the next question is what I have issues with.
I found the following online:
"Each message is a different view, but you don't need to program each view. You can create a class that inherits from discord.ui.button where it takes a team name as a string, then pass that button (plus another for the 2nd team) to a class that inherits from discord.ui.View"
I'm a beginner when it comes to python and I have no clue how I actually do this. Any tips?
What I want:
Message 1: "Team A vs Team B"
Button 1: Team A
Button 2: Team B
Message 2: "Team C vs Team C"
Button 1: Team C
Button 2: Team D
The code I currently already have:
class WorldsGeneral(discord.ui.View):
def __init__(self, *, timeout=10):
super().__init__(timeout=timeout)
#discord.ui.button(label="Groups", style=discord.ButtonStyle.red)
async def Finals(self, interaction: discord.Interaction, button: discord.ui.Button):
await interaction.response.send_message()
# The messages for predictions would have to be send by pressing this button ^
#discord.ui.button(label="Standings")
async def Standings(
self, interaction: discord.Interaction, button: discord.ui.Button
):
await interaction.response.send_message()
#bot.command(aliases=["poule", "predictions])
async def worlds(ctx):
embed = discord.Embed(
colour=0x0433f3,
title=":trophy: Worlds Poule 2022 :trophy:",
)
embed.set_image(
url=f"https://thumbs.gfycat.com/AccomplishedAdorableClumber-size_restricted.gif"
)
view = WorldsGeneral()
msg = await ctx.send(
":construction: Major Work In Progress :construction:", view=view
)
view.orig_mes = msg
You have an issue with your worlds command: there's a missing quotation at the very top. Here's how the fixed code should look:
#bot.command(aliases=["poule", "predictions"]) # Missing quotation right here
async def worlds(ctx):
embed = discord.Embed(
colour=0x0433f3,
title=":trophy: Worlds Poule 2022 :trophy:",
)
embed.set_image(
url=f"https://thumbs.gfycat.com/AccomplishedAdorableClumber-size_restricted.gif"
)
view = WorldsGeneral()
msg = await ctx.send(
":construction: Major Work In Progress :construction:", view=view
)
view.orig_mes = msg
I want to start working with SlashCommand autocompletion in pycord. Since I think that my request in the previous question is impossible, I thought to myself that maybe I can change the autocomplete based on the user's choice in one of the slash-command entries and display the desired entry to the user earlier.
I'm asking this because I don't found any good resource for learning this and I don't understand docs
there is an example from the Pycord documents, I think if you experiment with this part of the code, you will understand how it can work
https://github.com/Pycord-Development/pycord/blob/master/examples/app_commands/slash_autocomplete.py
If you want use them in cogs
there's my part for cogs
import discord
from discord import option
from discord.ext import commands
mycolors = ["red", "white", "yellow"]
class ExampleAutocompletion(commands.Cog):
ctx_parse = discord.ApplicationContext
def __init__(self, bot: discord.Bot):
self.bot = bot.user
#staticmethod
def colorAutocomplete(self: discord.AutocompleteContext):
return mycolors
#staticmethod
def flowertypeAutocomplete(self: discord.AutocompleteContext):
chosen_color = self.options["color"]
match chosen_color:
case "red":
return ["Rose", "Georgina"]
case "white":
return ["Chrisantem", "Gortensia"]
case "yellow":
return ["Sunflower", "Narciss"]
case _:
return ["There is no flower with this color"]
#commands.slash_command(
guild_ids=["Your Guild ID for test"],
description="")
#option("color", description="What's your favourite color?",
autocomplete=colorAutocomplete)
#option("flowertype", description="and that's your flower shape!",
autocomplete=flowertypeAutocomplete)
async def beautyflowers(self, ctx: ctx_parse,
color: str, flowertype: str):
await ctx.respond(f"My flower is {flowertype} and its color is {color}!")
def setup(bot):
bot.add_cog(ExampleAutocompletion(bot))
I'm trying to get it so I can add links in text rendered by Textual.
My text may have multiple links, for example:
Hello [#click=hello]World[/] there, how are you?
This is a test of [#click=more] more info[/] being clickable as well.
In this simple sample I made, clicking on the word "World" should hopefully change the background color to red, but it doesn't work.
NOTE: I also bound the "b" key to do pretty much the same thing, so I could see it work
It should change the background color, and the subtitle of the app.
import os
import sys
from rich.console import RenderableType
from rich.panel import Panel
from rich.text import Text
from textual.app import App
from textual.widgets import Header, Footer, ScrollView
from textual.widgets import Placeholder
class MyApp(App):
async def on_load(self) -> None:
await self.bind("b", "color('blue')")
async def on_mount(self) -> None:
await self.view.dock(Header(), size=5, edge="top")
await self.view.dock(Footer(), edge="bottom")
await self.view.dock(ScrollView(Panel("Hello [#click=hello]World[/] more info here")), edge="top")
async def action_color(self, color:str) -> None:
self.app.sub_title = "KEYBOARD"
self.background = f"on {color}"
async def action_hello(self) -> None:
self.app.sub_title = "CLICKED"
self.background = "on red"
MyApp.run(title="Test click", log="textual.log")
I asked this same question in the textual discussions and originally rich discussions, but haven't been able to see how to make this work from the feedback I received there, which was helpful for sure, but I'm missing something here, so thanks for any input.
It seems like #click=action doesn't work in textual (at least I couldn't make it work at all).
After digging up in rich documentation, I stepped upon Text class. That one, has on method, that can create click callback.
It supports __add__, so You can concat multiple Text(s) together with + operator.
Second piece of the puzzle was to find out what to set as a click callback. Again I looked in the source code and found _action_targets in app.py. That contains {"app", "view"} set.
Putting all together, You can create link(s) using Text with on(click="app.callback()"), which will call action_callback method of MyApp Class (instance of textual App). Then creating final panel text by concating other Text(s) and link(s) together.
Here is working example, turning background to red clicking on Hello or green clicking on World.
from rich.panel import Panel
from rich.text import Text
from textual.app import App
from textual.widgets import Header, Footer, ScrollView
class MyApp(App):
async def on_load(self) -> None:
await self.bind("b", "color('blue')")
async def on_mount(self) -> None:
await self.view.dock(Header(), size=5, edge="top")
await self.view.dock(Footer(), edge="bottom")
link1 = Text("Hello").on(click="app.hello()")
link2 = Text("World").on(click="app.world()")
panel_text = link1 + " " + link2 + Text(" more info here")
await self.view.dock(ScrollView(Panel(panel_text)), edge="top")
async def action_color(self, color: str) -> None:
self.app.sub_title = "KEYBOARD"
self.background = f"on {color}"
async def action_hello(self) -> None:
self.app.sub_title = "CLICKED Hello"
self.background = "on red"
async def action_world(self) -> None:
self.app.sub_title = "CLICKED World"
self.background = "on green"
MyApp.run(title="Test click", log="textual.log")
I know it's not ideal solution, but it's closest I could get to what You want.
You can also make a text with different properties with Text.assemble():
async def on_mount(self) -> None:
await self.view.dock(Header(), size=5, edge="top")
await self.view.dock(Footer(), edge="bottom")
panel_text = Text.assemble(
"Hello ",
Text("World","dark_blue u").on({"#click" : "app.hello()"}),
" more ",
Text("info","dark_blue u").on({"#click" : "app.color('blue')"}),
" here")
await self.view.dock(ScrollView(Panel(panel_text)), edge="top")
I'm building a Telegram bot using pyTelegramBotAPI and I'm facing a problem that it's always returning the second function even when I send a text that doesn't exist.
My code is:
import os
import telebot
import requests
from eletronic.light import turnofflight, turnonlight
bot = telebot.TeleBot(os.getenv('BOT_TOKEN'))
#bot.message_handler(commands=["hello"])
def greeting(message):
bot.reply_to(message, "Hello")
#bot.message_handler(func=lambda temp: temp.text is not None and 'weather' or 'Weather' in temp.text)
def verify_weather(message):
blumenau = requests.get('https://wttr.in/Blumenau?format=3')
bot.reply_to(message, 'text'+blumenau.text)
#bot.message_handler(func=lambda on: on.text is not None and 'on' or 'On' in on.text)
def botturnonlight(message):
turnonlight()
bot.reply_to(message, "text")
#bot.message_handler(func=lambda off: off.text is not None and 'off' or 'Off' in off.text)
def botturnofflight(message):
turnofflight()
bot.reply_to(message, "text")
def verify(message):
return True
#bot.message_handler(func=verify)
def answer(message):
text = """
Text"""
bot.reply_to(message.chat.id, text)
bot.infinity_polling()
That's probably something pretty simple there that I can't see what is it. Could someone help?
Thanks!!
This code be fixed removing the is not None from the bot's decorator in each function like:
#bot.message_handler(func=lambda on: on.text == 'on' or 'On' in on.text)