discordbot/cogs/owner_utils_cog.py
2025-06-05 21:31:06 -06:00

182 lines
6.1 KiB
Python

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> <message>`\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> <message>`"
)
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