295 lines
9.9 KiB
Python
295 lines
9.9 KiB
Python
import os
|
|
import asyncio
|
|
from typing import Union, Optional
|
|
from disagreement import Client, ui, HybridContext
|
|
from disagreement.models import (
|
|
Message,
|
|
SelectOption,
|
|
User,
|
|
Member,
|
|
Role,
|
|
Attachment,
|
|
Channel,
|
|
ActionRow,
|
|
Button,
|
|
Section,
|
|
TextDisplay,
|
|
Thumbnail,
|
|
UnfurledMediaItem,
|
|
MediaGallery,
|
|
MediaGalleryItem,
|
|
Container,
|
|
)
|
|
from disagreement.enums import (
|
|
ButtonStyle,
|
|
GatewayIntent,
|
|
ChannelType,
|
|
MessageFlags,
|
|
InteractionCallbackType,
|
|
MessageFlags,
|
|
)
|
|
from disagreement.ext.commands.cog import Cog
|
|
from disagreement.ext.commands.core import CommandContext
|
|
from disagreement.ext.app_commands.decorators import hybrid_command, slash_command
|
|
from disagreement.ext.app_commands.context import AppCommandContext
|
|
from disagreement.interactions import (
|
|
Interaction,
|
|
InteractionResponsePayload,
|
|
InteractionCallbackData,
|
|
)
|
|
|
|
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(ui.View):
|
|
def __init__(self):
|
|
super().__init__(timeout=180) # 180-second timeout
|
|
self.click_count = 0
|
|
|
|
@ui.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,
|
|
)
|
|
|
|
@ui.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(ui.View):
|
|
def __init__(self):
|
|
super().__init__(timeout=180)
|
|
self.index = 0
|
|
|
|
@ui.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,
|
|
)
|
|
|
|
|
|
async 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))
|
|
await client.run()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
asyncio.run(main())
|