import discord from discord.ext import commands import re import logging logger = logging.getLogger(__name__) class OwnerUtilsCog(commands.Cog, name="Owner Utils"): """Owner-only utility commands for bot management.""" def __init__(self, bot): self.bot = bot def _parse_user_and_message(self, content: str): """ Parse user identifier and message content from command arguments. Args: content: The full command content after the command name Returns: tuple: (user_id, message_content) or (None, None) if parsing fails """ if not content.strip(): return None, None # Split content into parts parts = content.strip().split(None, 1) if len(parts) < 2: return None, None user_part, message_content = parts # Try to extract user ID from mention format <@123456> or <@!123456> mention_match = re.match(r"<@!?(\d+)>", user_part) if mention_match: try: user_id = int(mention_match.group(1)) return user_id, message_content except ValueError: return None, None # Try to parse as raw user ID try: user_id = int(user_part) return user_id, message_content except ValueError: return None, None @commands.command( name="dm", aliases=["send_dm"], help="Send a direct message to a specified user (Owner only)", ) @commands.is_owner() async def dm_command(self, ctx, *, content: str = None): """ Send a direct message to a specified user. Usage: !dm @user message content here !dm 123456789012345678 message content here Args: content: User mention/ID followed by the message to send """ if not content: await ctx.reply( "❌ **Usage:** `!dm <@user|user_id> `\n" "**Examples:**\n" "• `!dm @username Hello there!`\n" "• `!dm 123456789012345678 Hello there!`" ) return # Parse user and message content user_id, message_content = self._parse_user_and_message(content) if user_id is None or not message_content: await ctx.reply( "❌ **Invalid format.** Please provide a valid user mention or ID followed by a message.\n" "**Usage:** `!dm <@user|user_id> `" ) return # Validate message content length if len(message_content) > 2000: await ctx.reply( "❌ **Message too long.** Discord messages must be 2000 characters or fewer.\n" f"Your message is {len(message_content)} characters." ) return try: # Fetch the target user target_user = self.bot.get_user(user_id) if not target_user: target_user = await self.bot.fetch_user(user_id) if not target_user: await ctx.reply( f"❌ **User not found.** Could not find a user with ID `{user_id}`." ) return # Attempt to send the DM try: await target_user.send(message_content) # Send confirmation to command invoker embed = discord.Embed( title="✅ DM Sent Successfully", color=discord.Color.green(), timestamp=discord.utils.utcnow(), ) embed.add_field( name="Recipient", value=f"{target_user.mention} (`{target_user.name}#{target_user.discriminator}`)", inline=False, ) embed.add_field( name="Message Preview", value=message_content[:100] + ("..." if len(message_content) > 100 else ""), inline=False, ) embed.set_footer(text=f"User ID: {user_id}") await ctx.reply(embed=embed) logger.info( f"DM sent successfully from {ctx.author} to {target_user} (ID: {user_id})" ) except discord.Forbidden: await ctx.reply( f"❌ **Cannot send DM to {target_user.mention}.**\n" "The user likely has DMs disabled or has blocked the bot." ) logger.warning( f"Failed to send DM to {target_user} (ID: {user_id}) - Forbidden (DMs disabled or blocked)" ) except discord.HTTPException as e: await ctx.reply( f"❌ **Failed to send DM due to Discord API error.**\n" f"Error: {str(e)}" ) logger.error( f"HTTPException when sending DM to {target_user} (ID: {user_id}): {e}" ) except discord.NotFound: await ctx.reply( f"❌ **User not found.** No user exists with ID `{user_id}`." ) logger.warning(f"Attempted to send DM to non-existent user ID: {user_id}") except discord.HTTPException as e: await ctx.reply( f"❌ **Failed to fetch user due to Discord API error.**\n" f"Error: {str(e)}" ) logger.error(f"HTTPException when fetching user {user_id}: {e}") except Exception as e: await ctx.reply( f"❌ **An unexpected error occurred.**\n" f"Error: {str(e)}" ) logger.error(f"Unexpected error in dm_command: {e}", exc_info=True) async def setup(bot): """Setup function to load the cog.""" try: logger.info("Attempting to load OwnerUtilsCog...") await bot.add_cog(OwnerUtilsCog(bot)) logger.info("OwnerUtilsCog loaded successfully.") except Exception as e: logger.error(f"Failed to load OwnerUtilsCog: {e}") raise