Improves reactions documentation with examples and events

Expands the reactions guide to include practical examples for adding reactions via message objects, demonstrates low-level client methods, and adds comprehensive event handling examples.

Shows how to listen for reaction add/remove events with proper type hints and user filtering to ignore bot reactions.
This commit is contained in:
Slipstream 2025-06-11 16:05:35 -06:00
parent 64576203ae
commit 6bcde9c5b0
Signed by: slipstream
GPG Key ID: 13E498CE010AC6FD
2 changed files with 187 additions and 13 deletions

View File

@ -1,32 +1,62 @@
# Handling Reactions # Handling Reactions
`disagreement` provides simple helpers for adding and removing message reactions. `disagreement` provides several ways to add, remove, and listen for message reactions.
## HTTP Methods ## Adding & Removing Reactions
Use the `HTTPClient` methods directly if you need lower level control: The easiest way to add a reaction is to use the helper method on a `Message` object. This is often done within a command context.
```python ```python
await client._http.create_reaction(channel_id, message_id, "👍") # Inside a command function:
await client._http.delete_reaction(channel_id, message_id, "👍") # ctx is a commands.CommandContext object
users = await client._http.get_reactions(channel_id, message_id, "👍") await ctx.message.add_reaction("👍")
``` ```
You can also use the higher level helpers on :class:`Client`: You can also remove your own reactions.
```python ```python
await ctx.message.remove_reaction("👍", client.user)
```
## Low-Level Control
For more direct control, you can use methods on the `Client` or `HTTPClient` if you have the channel and message IDs.
```python
# Using the client helper
await client.create_reaction(channel_id, message_id, "👍") await client.create_reaction(channel_id, message_id, "👍")
await client.delete_reaction(channel_id, message_id, "👍")
# Using the raw HTTP method
await client._http.create_reaction(channel_id, message_id, "👍")
```
Similarly, you can delete reactions and get a list of users who reacted.
```python
# Delete a specific user's reaction
await client.delete_reaction(channel_id, message_id, "👍", user_id)
# Get users who reacted with an emoji
users = await client.get_reactions(channel_id, message_id, "👍") users = await client.get_reactions(channel_id, message_id, "👍")
``` ```
## Reaction Events ## Reaction Events
Register listeners for `MESSAGE_REACTION_ADD` and `MESSAGE_REACTION_REMOVE`. Your bot can listen for reaction events by using the `@client.on_event` decorator. The two main events are `MESSAGE_REACTION_ADD` and `MESSAGE_REACTION_REMOVE`.
Each listener receives a `Reaction` model instance.
The event handlers for these events receive both a `Reaction` object and the `User` or `Member` who triggered the event.
```python ```python
import disagreement
from disagreement.models import Reaction, User, Member
@client.on_event("MESSAGE_REACTION_ADD") @client.on_event("MESSAGE_REACTION_ADD")
async def on_reaction(reaction: disagreement.Reaction): async def on_reaction_add(reaction: Reaction, user: User | Member):
print(f"{reaction.user_id} reacted with {reaction.emoji}") # Ignore reactions from the bot itself
``` if client.user and user.id == client.user.id:
return
print(f"{user.username} reacted to message {reaction.message_id} with {reaction.emoji}")
@client.on_event("MESSAGE_REACTION_REMOVE")
async def on_reaction_remove(reaction: Reaction, user: User | Member):
print(f"{user.username} removed their {reaction.emoji} reaction from message {reaction.message_id}")

144
examples/reactions.py Normal file
View File

@ -0,0 +1,144 @@
# 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:
import disagreement
from disagreement.ext import commands
from disagreement.models import Reaction, User, Member
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 = (
disagreement.GatewayIntent.GUILDS
| disagreement.GatewayIntent.GUILD_MESSAGES
| disagreement.GatewayIntent.GUILD_MESSAGE_REACTIONS
| disagreement.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 = disagreement.Client(token=BOT_TOKEN, intents=intents, command_prefix="!")
# --- Define a Cog for reaction-related commands ---
class ReactionCog(commands.Cog):
def __init__(self, bot_client):
super().__init__(bot_client)
@commands.command(name="react")
async def react_command(self, ctx: commands.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 disagreement.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 disagreement.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())