Some checks failed
Deploy MkDocs / deploy (push) Has been cancelled
Makes commonly used classes, functions, and decorators from `disagreement.ext` and `disagreement.ui` submodules directly accessible under the `disagreement` namespace. This change simplifies import statements for users, leading to cleaner and more concise code. Documentation and examples have been updated to reflect these new, simplified import paths.
158 lines
4.9 KiB
Python
158 lines
4.9 KiB
Python
# examples/reactions.py
|
|
|
|
"""
|
|
An example bot demonstrating reaction handling with the Disagreement library.
|
|
|
|
This bot will:
|
|
1. React to a specific command with a thumbs-up emoji.
|
|
2. Log when any reaction is added to a message in a server it's in.
|
|
3. Log when any reaction is removed from a message.
|
|
|
|
To run this bot:
|
|
1. Follow the setup steps in 'basic_bot.py' to set your DISCORD_BOT_TOKEN.
|
|
2. Ensure you have the GUILD_MESSAGE_REACTIONS intent enabled for your bot in the Discord Developer Portal.
|
|
3. Run this script: python examples/reactions.py
|
|
"""
|
|
|
|
import asyncio
|
|
import os
|
|
import sys
|
|
import logging
|
|
import traceback
|
|
|
|
# Add project root to path for local development
|
|
if os.path.join(os.getcwd(), "examples") == os.path.dirname(os.path.abspath(__file__)):
|
|
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
|
|
|
try:
|
|
from disagreement import (
|
|
Client,
|
|
GatewayIntent,
|
|
Reaction,
|
|
User,
|
|
Member,
|
|
HTTPException,
|
|
AuthenticationError,
|
|
Cog,
|
|
command,
|
|
CommandContext,
|
|
)
|
|
except ImportError:
|
|
print(
|
|
"Failed to import disagreement. Make sure it's installed or PYTHONPATH is set correctly."
|
|
)
|
|
sys.exit(1)
|
|
|
|
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()
|
|
|
|
# --- Bot Configuration ---
|
|
BOT_TOKEN = os.environ.get("DISCORD_BOT_TOKEN")
|
|
|
|
# --- Intents Configuration ---
|
|
# We need GUILDS for server context, GUILD_MESSAGES to receive messages,
|
|
# and GUILD_MESSAGE_REACTIONS to listen for reaction events.
|
|
intents = (
|
|
GatewayIntent.GUILDS
|
|
| GatewayIntent.GUILD_MESSAGES
|
|
| GatewayIntent.GUILD_MESSAGE_REACTIONS
|
|
| GatewayIntent.MESSAGE_CONTENT # For commands
|
|
)
|
|
|
|
# --- Initialize the Client ---
|
|
if not BOT_TOKEN:
|
|
print("Error: The DISCORD_BOT_TOKEN environment variable is not set.")
|
|
sys.exit(1)
|
|
|
|
client = Client(token=BOT_TOKEN, intents=intents, command_prefix="!")
|
|
|
|
|
|
# --- Define a Cog for reaction-related commands ---
|
|
class ReactionCog(Cog):
|
|
def __init__(self, bot_client):
|
|
super().__init__(bot_client)
|
|
|
|
@command(name="react")
|
|
async def react_command(self, ctx: CommandContext):
|
|
"""Reacts to the command message with a thumbs up."""
|
|
try:
|
|
# The emoji can be a standard Unicode emoji or a custom one in the format '<:name:id>'
|
|
await ctx.message.add_reaction("👍")
|
|
print(f"Reacted to command from {ctx.author.username}")
|
|
except HTTPException as e:
|
|
print(f"Failed to add reaction: {e}")
|
|
await ctx.reply(
|
|
"I couldn't add the reaction. I might be missing permissions."
|
|
)
|
|
|
|
|
|
# --- Event Handlers ---
|
|
|
|
|
|
@client.event
|
|
async def on_ready():
|
|
"""Called when the bot is ready and connected to Discord."""
|
|
if client.user:
|
|
print(f"Bot is ready! Logged in as {client.user.username}")
|
|
else:
|
|
print("Bot is ready, but client.user is missing!")
|
|
print("------")
|
|
print("Reaction example bot is operational.")
|
|
|
|
|
|
@client.on_event("MESSAGE_REACTION_ADD")
|
|
async def on_reaction_add(reaction: Reaction, user: User | Member):
|
|
"""Called when a message reaction is added."""
|
|
# We can ignore reactions from the bot itself
|
|
if client.user and user.id == client.user.id:
|
|
return
|
|
|
|
print(
|
|
f"Reaction '{reaction.emoji}' added by {user.username} "
|
|
f"to message ID {reaction.message_id} in channel ID {reaction.channel_id}"
|
|
)
|
|
# You can fetch the message if you need its content, but it's an extra API call.
|
|
# try:
|
|
# channel = await client.fetch_channel(reaction.channel_id)
|
|
# if isinstance(channel, disagreement.TextChannel):
|
|
# message = await channel.fetch_message(reaction.message_id)
|
|
# print(f" Message content: '{message.content}'")
|
|
# except disagreement.errors.NotFound:
|
|
# print(" Could not fetch message (maybe it was deleted).")
|
|
|
|
|
|
@client.on_event("MESSAGE_REACTION_REMOVE")
|
|
async def on_reaction_remove(reaction: Reaction, user: User | Member):
|
|
"""Called when a message reaction is removed."""
|
|
print(
|
|
f"Reaction '{reaction.emoji}' removed by {user.username} "
|
|
f"from message ID {reaction.message_id} in channel ID {reaction.channel_id}"
|
|
)
|
|
|
|
|
|
# --- Main Execution ---
|
|
async def main():
|
|
print("Starting Reaction Bot...")
|
|
try:
|
|
client.add_cog(ReactionCog(client))
|
|
await client.run()
|
|
except AuthenticationError:
|
|
print("Authentication failed. Check your bot token.")
|
|
except Exception as e:
|
|
print(f"An unexpected error occurred: {e}")
|
|
traceback.print_exc()
|
|
finally:
|
|
if not client.is_closed():
|
|
await client.close()
|
|
print("Bot has been shut down.")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
asyncio.run(main())
|