import discord from redbot.core import commands, Config, app_commands from typing import Optional import datetime # --- Modal for the Message Form --- class MessageModal(discord.ui.Modal, title="Staff Message"): message_content = discord.ui.TextInput( label="Message to Send", style=discord.TextStyle.paragraph, placeholder="Type the official message you want to send to the user here.", max_length=1800 ) def __init__(self, cog: "StaffMsg", target_user: discord.Member): super().__init__() self.cog = cog self.target_user = target_user async def on_submit(self, interaction: discord.Interaction): guild = interaction.guild if not guild: return # --- Send DM to User --- embed_to_user = discord.Embed( title=f"A Message from the Staff of {guild.name}", description=self.message_content.value, color=await self.cog.bot.get_embed_color(interaction.channel) if interaction.channel else discord.Color.blurple() ) embed_to_user.set_footer(text="This is an official communication. Please do not reply to the bot.") try: await self.target_user.send(embed=embed_to_user) except discord.Forbidden: await interaction.response.send_message( f"I could not send a DM to {self.target_user.mention}. They may have DMs disabled.", ephemeral=True ) return except discord.HTTPException as e: await interaction.response.send_message(f"An error occurred while sending the DM: {e}", ephemeral=True) return # --- Log the Message --- log_channel_id = await self.cog.config.guild(guild).log_channel() if log_channel_id: log_channel = guild.get_channel(log_channel_id) if isinstance(log_channel, discord.TextChannel): log_embed = discord.Embed( title="Staff DM Sent", description=self.message_content.value, color=discord.Color.blue() ) log_embed.set_author(name=f"From: {interaction.user.name} ({interaction.user.id})", icon_url=interaction.user.display_avatar.url) log_embed.add_field(name="To", value=f"{self.target_user.mention} ({self.target_user.id})") try: log_message = await log_channel.send(embed=log_embed) async with self.cog.config.guild(guild).sent_dms() as dms: dms[str(log_message.id)] = datetime.datetime.now(datetime.timezone.utc).isoformat() except discord.Forbidden: pass await interaction.response.send_message(f"Your message has been successfully sent to {self.target_user.mention}.", ephemeral=True) # --- Main Cog Class --- class StaffMsg(commands.Cog): """A cog for staff to send official DMs to users.""" def __init__(self, bot): self.bot = bot self.config = Config.get_conf(self, identifier=1234567892, force_registration=True) default_guild = { "log_channel": None, "authorized_role": None, "sent_dms": {} # For DataManager } self.config.register_guild(**default_guild) async def cog_check(self, ctx: commands.Context) -> bool: if not ctx.guild: await ctx.send("This command can only be used in a server.", ephemeral=True) return False auth_role_id = await self.config.guild(ctx.guild).authorized_role() if not auth_role_id: await ctx.send("The authorized role for this command has not been set.", ephemeral=True) return False author = ctx.author if not isinstance(author, discord.Member): return False if author.guild_permissions.administrator: return True auth_role = ctx.guild.get_role(auth_role_id) if not auth_role or auth_role not in author.roles: await ctx.send("You are not authorized to use this command.", ephemeral=True) return False return True @commands.hybrid_command(name="smsg", aliases=["staff", "staffmsg"]) @app_commands.describe(user="The user to send a DM to.", message="(Optional) The message to send. Opens a form if left blank.") @app_commands.guild_only() async def staff_message(self, ctx: commands.Context, user: discord.Member, *, message: Optional[str] = None): """Send an official DM to a user. Opens a form if no message is provided.""" # This is a clever way to handle both slash and prefix commands. # If the 'message' is None, it means it was likely a slash command # or a prefix command with no text, so we open the modal. if message is None: if not ctx.interaction: await ctx.send("Please provide a message to send.", ephemeral=True) return modal = MessageModal(self, user) await ctx.interaction.response.send_modal(modal) return guild = ctx.guild if not guild: # Should be caught by cog_check but for type safety return embed_to_user = discord.Embed( title=f"A Message from the Staff of {guild.name}", description=message, color=await ctx.embed_color() ) embed_to_user.set_footer(text="This is an official communication. Please do not reply to the bot.") try: await user.send(embed=embed_to_user) except discord.Forbidden: await ctx.send(f"I could not send a DM to {user.mention}. They may have DMs disabled.", ephemeral=True) return log_channel_id = await self.config.guild(guild).log_channel() if log_channel_id: log_channel = guild.get_channel(log_channel_id) if isinstance(log_channel, discord.TextChannel): log_embed = discord.Embed( title="Staff DM Sent", description=message, color=discord.Color.blue() ) log_embed.set_author(name=f"From: {ctx.author.name} ({ctx.author.id})", icon_url=ctx.author.display_avatar.url) log_embed.add_field(name="To", value=f"{user.mention} ({user.id})") try: log_message = await log_channel.send(embed=log_embed) async with self.config.guild(guild).sent_dms() as dms: dms[str(log_message.id)] = datetime.datetime.now(datetime.timezone.utc).isoformat() except discord.Forbidden: pass await ctx.send(f"Your message has been sent to {user.mention}.", ephemeral=True) @commands.group(aliases=["smset"]) # type: ignore @commands.guild_only() @commands.admin_or_permissions(manage_guild=True) async def staffmsgset(self, ctx: commands.Context): """Configure StaffMsg settings.""" pass @staffmsgset.command(name="role") async def set_auth_role(self, ctx: commands.Context, role: discord.Role): """Set the role authorized to use the staff message commands.""" if not ctx.guild: return await self.config.guild(ctx.guild).authorized_role.set(role.id) await ctx.send(f"Authorized role has been set to {role.mention}") @staffmsgset.command(name="logchannel") async def set_log_channel(self, ctx: commands.Context, channel: discord.TextChannel): """Set the channel where sent DMs will be logged.""" if not ctx.guild: return await self.config.guild(ctx.guild).log_channel.set(channel.id) await ctx.send(f"Log channel has been set to {channel.mention}") async def setup(bot): await bot.add_cog(StaffMsg(bot))