disagreement/examples/component_bot.py
Slipstream 1464937f6f
Some checks failed
Deploy MkDocs / deploy (push) Has been cancelled
Add async start and sync run (#94)
2025-06-15 18:11:44 -06:00

291 lines
9.5 KiB
Python

import os
import asyncio
from typing import Union
from disagreement import (
Client,
HybridContext,
Message,
SelectOption,
User,
Member,
Role,
Attachment,
Channel,
ActionRow,
Button,
Section,
TextDisplay,
Thumbnail,
UnfurledMediaItem,
MediaGallery,
MediaGalleryItem,
Container,
ButtonStyle,
GatewayIntent,
ChannelType,
MessageFlags,
Interaction,
Cog,
CommandContext,
AppCommandContext,
hybrid_command,
View,
button,
select,
)
try:
from dotenv import load_dotenv
except ImportError: # pragma: no cover - example helper
load_dotenv = None
print("python-dotenv is not installed. Environment variables will not be loaded")
if load_dotenv:
load_dotenv()
# Get the bot token and application ID from the environment variables
token = os.getenv("DISCORD_BOT_TOKEN")
application_id = os.getenv("DISCORD_APPLICATION_ID")
if not token:
raise ValueError("Bot token not found in environment variables")
if not application_id:
raise ValueError("Application ID not found in environment variables")
# Define the intents
intents = GatewayIntent.default() | GatewayIntent.MESSAGE_CONTENT
# Create a new client
client = Client(
token=token,
intents=intents,
command_prefix="!",
mention_replies=True,
)
# Simple stock data used for the stock cycling example
STOCKS = [
{"symbol": "AAPL", "name": "Apple Inc.", "price": "$175"},
{"symbol": "MSFT", "name": "Microsoft Corp.", "price": "$315"},
{"symbol": "GOOGL", "name": "Alphabet Inc.", "price": "$128"},
]
# Define a View class that contains our components
class MyView(View):
def __init__(self):
super().__init__(timeout=180) # 180-second timeout
self.click_count = 0
@button(label="Click Me!", style=ButtonStyle.SUCCESS, emoji="🖱️")
async def click_me(self, interaction: Interaction):
self.click_count += 1
await interaction.respond(
content=f"You've clicked the button {self.click_count} times!",
ephemeral=True,
)
@select(
custom_id="string_select",
placeholder="Choose an option",
options=[
SelectOption(
label="Option 1", value="opt1", description="This is the first option"
),
SelectOption(
label="Option 2", value="opt2", description="This is the second option"
),
],
)
async def select_menu(self, interaction: Interaction):
if interaction.data and interaction.data.values:
await interaction.respond(
content=f"You selected: {interaction.data.values[0]}",
ephemeral=True,
)
async def on_timeout(self):
# This method is called when the view times out.
# You can use this to edit the original message, for example.
print("View has timed out!")
# View for cycling through available stocks
class StockView(View):
def __init__(self):
super().__init__(timeout=180)
self.index = 0
@button(label="Next Stock", style=ButtonStyle.PRIMARY)
async def next_stock(self, interaction: Interaction):
self.index = (self.index + 1) % len(STOCKS)
stock = STOCKS[self.index]
# Edit the message by responding to the interaction with an update
await interaction.edit(
content=f"**{stock['symbol']}** - {stock['name']}\nPrice: {stock['price']}",
components=self.to_components(),
# Preserve the original reply mention
allowed_mentions={"replied_user": True},
)
class ComponentCommandsCog(Cog):
def __init__(self, client: Client):
super().__init__(client)
@hybrid_command(name="components", description="Sends interactive components.")
async def components_command(self, ctx: Union[CommandContext, AppCommandContext]):
# Send a message with the view using a hybrid context helper
hybrid = HybridContext(ctx)
await hybrid.send("Here are your components:", view=MyView())
@hybrid_command(
name="stocks", description="Shows stock information with navigation."
)
async def stocks_command(self, ctx: Union[CommandContext, AppCommandContext]):
# Show the first stock and attach a button to cycle through them
first = STOCKS[0]
hybrid = HybridContext(ctx)
await hybrid.send(
f"**{first['symbol']}** - {first['name']}\nPrice: {first['price']}",
view=StockView(),
)
@hybrid_command(name="sectiondemo", description="Shows a section layout.")
async def section_demo(self, ctx: Union[CommandContext, AppCommandContext]) -> None:
section = Section(
components=[
TextDisplay(content="## Advanced Components"),
TextDisplay(content="Sections group text with accessories."),
],
accessory=Thumbnail(
media=UnfurledMediaItem(url="https://placehold.co/100x100.png")
),
)
container = Container(components=[section], accent_color=0x5865F2)
hybrid = HybridContext(ctx)
await hybrid.send(
components=[container],
flags=MessageFlags.IS_COMPONENTS_V2.value,
)
@hybrid_command(name="gallerydemo", description="Shows a media gallery.")
async def gallery_demo(self, ctx: Union[CommandContext, AppCommandContext]) -> None:
gallery = MediaGallery(
items=[
MediaGalleryItem(
media=UnfurledMediaItem(url="https://placehold.co/600x400.png")
),
MediaGalleryItem(
media=UnfurledMediaItem(url="https://placehold.co/600x400.jpg")
),
]
)
hybrid = HybridContext(ctx)
await hybrid.send(
components=[gallery],
flags=MessageFlags.IS_COMPONENTS_V2.value,
)
@hybrid_command(
name="complex_components",
description="Shows a complex layout with multiple containers.",
)
async def complex_components(
self, ctx: Union[CommandContext, AppCommandContext]
) -> None:
container1 = Container(
components=[
Section(
components=[
TextDisplay(content="## Complex Layout Example"),
TextDisplay(
content="This container has an accent color and includes a section with an action row of buttons. There is a thumbnail accessory to the right."
),
],
accessory=Thumbnail(
media=UnfurledMediaItem(url="https://placehold.co/100x100.png")
),
),
ActionRow(
components=[
Button(
style=ButtonStyle.PRIMARY,
label="Primary",
custom_id="complex_primary",
),
Button(
style=ButtonStyle.SUCCESS,
label="Success",
custom_id="complex_success",
),
Button(
style=ButtonStyle.DANGER,
label="Destructive",
custom_id="complex_destructive",
),
]
),
],
accent_color=0x5865F2,
)
container2 = Container(
components=[
TextDisplay(
content="## Another Container\nThis container has no accent color and includes a media gallery."
),
MediaGallery(
items=[
MediaGalleryItem(
media=UnfurledMediaItem(
url="https://placehold.co/300x200.png"
)
),
MediaGalleryItem(
media=UnfurledMediaItem(
url="https://placehold.co/300x200.jpg"
)
),
MediaGalleryItem(
media=UnfurledMediaItem(
url="https://placehold.co/300x200.gif"
)
),
]
),
]
)
hybrid = HybridContext(ctx)
await hybrid.send(
components=[container1, container2],
flags=MessageFlags.IS_COMPONENTS_V2.value,
)
def main():
@client.event
async def on_ready():
if client.user:
print(f"Logged in as {client.user.username}")
if client.application_id:
try:
print("Attempting to sync application commands...")
await client.app_command_handler.sync_commands(
application_id=client.application_id
)
print("Application commands synced successfully.")
except Exception as e:
print(f"Error syncing application commands: {e}")
else:
print(
"Client's application ID is not set. Skipping application command sync."
)
client.add_cog(ComponentCommandsCog(client))
client.run()
if __name__ == "__main__":
main()