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:
parent
64576203ae
commit
6bcde9c5b0
@ -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
144
examples/reactions.py
Normal 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())
|
Loading…
x
Reference in New Issue
Block a user