# examples/basic_bot.py """ A basic example bot using the Disagreement library. To run this bot: 1. Make sure you have the 'disagreement' library installed or accessible in your PYTHONPATH. If running from the project root, it should be discoverable. 2. Set the DISCORD_BOT_TOKEN environment variable to your bot's token. e.g., export DISCORD_BOT_TOKEN="your_actual_token_here" (Linux/macOS) set DISCORD_BOT_TOKEN="your_actual_token_here" (Windows CMD) $env:DISCORD_BOT_TOKEN="your_actual_token_here" (Windows PowerShell) 3. Run this script: python examples/basic_bot.py """ import asyncio import os import logging # Optional: for more detailed logging # Assuming the 'disagreement' package is in the parent directory or installed import sys import traceback # Add project root to path if running script directly from examples folder # and disagreement is not installed 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, Message, Guild, AuthenticationError, DisagreementException, Cog, command, CommandContext, ) except ImportError: print( "Failed to import disagreement. Make sure it's installed or PYTHONPATH is set correctly." ) print( "If running from the 'examples' directory, try running from the project root: python -m examples.basic_bot" ) 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() # Optional: Configure logging for more insight, especially for gateway events # logging.basicConfig(level=logging.DEBUG) # For very verbose output # logging.getLogger('disagreement.gateway').setLevel(logging.INFO) # Or DEBUG # logging.getLogger('disagreement.http').setLevel(logging.INFO) # --- Bot Configuration --- BOT_TOKEN = os.environ.get("DISCORD_BOT_TOKEN") # --- Intents Configuration --- # Define the intents your bot needs. For basic message reading and responding: intents = ( GatewayIntent.GUILDS | GatewayIntent.GUILD_MESSAGES | GatewayIntent.MESSAGE_CONTENT ) # MESSAGE_CONTENT is privileged! # If you don't need message content and only react to commands/mentions, # you might not need MESSAGE_CONTENT intent. # intents = GatewayIntent.default() # A good starting point without privileged intents # intents |= GatewayIntent.MESSAGE_CONTENT # Add if needed # --- Initialize the Client --- if not BOT_TOKEN: print("Error: The DISCORD_BOT_TOKEN environment variable is not set.") print("Please set it before running the bot.") sys.exit(1) # Initialize Client with a command prefix client = Client(token=BOT_TOKEN, intents=intents, command_prefix="!") # --- Define a Cog for example commands --- class ExampleCog(Cog): # Ensuring this uses commands.Cog def __init__( self, bot_client ): # Renamed client to bot_client to avoid conflict with self.client super().__init__(bot_client) # Pass the client instance to the base Cog @command(name="hello", aliases=["hi"]) async def hello_command(self, ctx: CommandContext, *, who: str = "world"): """Greets someone.""" await ctx.reply(f"Hello {ctx.author.mention} and {who}!") print(f"Executed 'hello' command for {ctx.author.username}, greeting {who}") @command() async def ping(self, ctx: CommandContext): """Responds with Pong!""" await ctx.reply("Pong!") print(f"Executed 'ping' command for {ctx.author.username}") @command() async def me(self, ctx: CommandContext): """Shows information about the invoking user.""" reply_content = ( f"Hello {ctx.author.mention}!\n" f"Your User ID is: {ctx.author.id}\n" f"Your Username: {ctx.author.username}#{ctx.author.discriminator}\n" f"Are you a bot? {'Yes' if ctx.author.bot else 'No'}" ) await ctx.reply(reply_content) print(f"Executed 'me' command for {ctx.author.username}") @command(name="add") async def add_numbers(self, ctx: CommandContext, num1: int, num2: int): """Adds two numbers.""" result = num1 + num2 await ctx.reply(f"The sum of {num1} and {num2} is {result}.") print( f"Executed 'add' command for {ctx.author.username}: {num1} + {num2} = {result}" ) @command(name="say") async def say_something(self, ctx: CommandContext, *, text_to_say: str): """Repeats the text you provide.""" await ctx.reply(f"You said: {text_to_say}") print( f"Executed 'say' command for {ctx.author.username}, saying: {text_to_say}" ) @command(name="whois") async def whois(self, ctx: CommandContext, *, name: str): """Looks up a member by username or nickname using the guild cache.""" if not ctx.guild: await ctx.reply("This command can only be used in a guild.") return member = ctx.guild.get_member_named(name) if member: await ctx.reply( f"Found: {member.username}#{member.discriminator} (display: {member.display_name})" ) else: await ctx.reply("Member not found in cache.") @command(name="quit") async def quit_command(self, ctx: CommandContext): """Shuts down the bot (requires YOUR_USER_ID to be set).""" # Replace YOUR_USER_ID with your actual Discord User ID for a safe shutdown command your_user_id = "YOUR_USER_ID_REPLACE_ME" # IMPORTANT: Replace this if str(ctx.author.id) == your_user_id: print("Quit command received. Shutting down...") await ctx.reply("Shutting down...") await self.client.close() # Access client via self.client from Cog else: await ctx.reply("You are not authorized to use this command.") print( f"Unauthorized quit attempt by {ctx.author.username} ({ctx.author.id})" ) # --- 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}#{client.user.discriminator}" ) print(f"User ID: {client.user.id}") else: print("Bot is ready, but client.user is missing!") print("------") print("Disagreement Bot is operational.") print("Listening for commands...") @client.event async def on_message(message: Message): """Called when a message is created and received.""" # Command processing is now handled by the CommandHandler via client._process_message_for_commands # This on_message can be used for other message-related logic if needed, # or removed if all message handling is command-based. # Example: Log all messages (excluding bot's own, if client.user was available) # if client.user and message.author.id == client.user.id: # return print( f"General on_message: #{message.channel_id} from {message.author.username}: {message.content}" ) # The old if/elif command structure is no longer needed here. @client.on_event( "GUILD_CREATE" ) # Example of listening to a specific event by its Discord name async def on_guild_available(guild: Guild): # The event now passes a Guild object directly print(f"Guild available: {guild.name} (ID: {guild.id})") # --- Main Execution --- async def main(): print("Starting Disagreement Bot...") try: # Add the Cog to the client client.add_cog(ExampleCog(client)) # Pass client instance to Cog constructor # client.add_cog is synchronous, but it schedules cog.cog_load() if it's async. await client.run() except AuthenticationError: print( "Authentication failed. Please check your bot token and ensure it's correct." ) except DisagreementException as e: print(f"A Disagreement library error occurred: {e}") except KeyboardInterrupt: print("Bot shutting down due to KeyboardInterrupt...") except Exception as e: print(f"An unexpected error occurred: {e}") traceback.print_exc() finally: if not client.is_closed(): print("Ensuring client is closed...") await client.close() print("Bot has been shut down.") if __name__ == "__main__": # Note: On Windows, the default asyncio event loop policy might not support add_signal_handler. # If you encounter issues with Ctrl+C not working as expected, # you might need to adjust the event loop policy or handle shutdown differently. # For example, for Windows: # if os.name == 'nt': # asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy()) asyncio.run(main())