diff --git a/bot.py b/bot.py index f348ef2..66645fc 100644 --- a/bot.py +++ b/bot.py @@ -2939,33 +2939,12 @@ async def on_message(message): member_role_ids = {role.id for role in member.roles} if not (ignore_role_ids & member_role_ids): - archived_message_data = { - "id": message.id, - "channel_id": message.channel.id, - "author_id": member.id, - "author_name": str(member), - "content": message.content, - "attachments": json.dumps( - [ - { - "filename": a.filename, - "url": a.url, - "content_type": a.content_type, - "size": a.size - } - for a in message.attachments - ] - ) if message.attachments else "[]" - } - try: await message.delete() except Exception: pass action_taken = None - honeypot_mute_id = None - acc_age_min = int(guild_settings.get("honeypot_acc_age_min") or 30) preserve = guild_settings.get("honeypot_preserve_old_accounts", False) @@ -2979,7 +2958,7 @@ async def on_message(message): if is_old_account: try: - result = await apply_full_mute( + result = await apply_mute_action( guild=message.guild, member=member, moderator=client.user, @@ -2987,16 +2966,14 @@ async def on_message(message): duration_label="365d", reason="Honeypot: wrote in honeypot channel (protected old member)", source_channel=message.channel, - message_data=archived_message_data, + message_data=None, message_id=message.id, - send_dm=True, - log_action=True, - remove_existing_roles=True, - save_current_roles=True, + remove_existing_roles=False, + save_removed_roles=False, increment_mute_count=True ) - honeypot_mute_id = result["mute_id"] action_taken = "mute" + honeypot_mute_id = result["mute_id"] except discord.Forbidden: logger.warning(f"Honeypot: no permission to mute {member.id} in guild {guild_id}") except Exception as e: @@ -3032,7 +3009,7 @@ async def on_message(message): embed.add_field(name="User", value=f"{member} (`{member.id}`)", inline=True) embed.add_field(name="Channel", value=f"<#{message.channel.id}>", inline=True) - if honeypot_mute_id: + if action_taken == "mute": embed.add_field(name="Mute Record ID", value=f"`{honeypot_mute_id}`", inline=True) if preserve: @@ -3055,7 +3032,7 @@ async def on_message(message): logger.error(f"Honeypot: error sending log: {e}") return - # ── End honeypot check ───────────────────────────────────────────────────────── + # ── End honeypot check ───────────────────────────────────────────────────────── cooldown_key = (user_id, guild_id) current_time = time.time() @@ -6580,7 +6557,7 @@ async def mute( context_range: int = 3, silent: bool = False ): - """Mutes a user for a specified duration (Requires Permission Level 5 or higher).""" + """Mute a user for a specified duration.""" is_slash_command = hasattr(ctx, "interaction") and ctx.interaction is not None @@ -6599,16 +6576,17 @@ async def mute( else: await ctx.send(content=content, embed=embed, file=file) except Exception as e: - logger.error(f"Error sending response in mute command: {e}") + logger.error(f"Error sending mute response: {e}") try: if embed: await ctx.send(embed=embed) elif content: - await ctx.send(content=content) + await ctx.send(content) except Exception as fallback_error: - logger.error(f"Fallback send also failed: {fallback_error}") + logger.error(f"Fallback send failed: {fallback_error}") try: + original_reason = reason message_data = None parsed_context_range = 3 @@ -6618,13 +6596,16 @@ async def mute( potential_context = reason_words[-1] if ( - potential_msg_id and 17 <= len(potential_msg_id) <= 20 and potential_msg_id.isdigit() - and potential_context and len(potential_context) <= 3 and potential_context.isdigit() + potential_msg_id + and 17 <= len(potential_msg_id) <= 20 + and potential_msg_id.isdigit() + and potential_context + and len(potential_context) <= 3 + and potential_context.isdigit() ): parsed_context_range = int(potential_context) message_id = potential_msg_id reason = " ".join(reason_words[:-2]) - elif len(reason_words) >= 1: potential_msg_id = reason_words[-1] if 17 <= len(potential_msg_id) <= 20 and potential_msg_id.isdigit(): @@ -6702,7 +6683,7 @@ async def mute( await send_response(embed=embed, ephemeral=True) return - result = await apply_full_mute( + result = await apply_mute_action( guild=ctx.guild, member=member, moderator=ctx.author, @@ -6712,16 +6693,15 @@ async def mute( source_channel=ctx.channel, message_data=message_data, message_id=int(message_id) if message_id else None, - send_dm=True, - log_action=True, remove_existing_roles=True, - save_current_roles=True, + save_removed_roles=True, increment_mute_count=True ) end_time = result["end_time"] mute_id = result["mute_id"] process_uuid = result["process_uuid"] + mute_role = result["mute_role"] user_data = result["user_data"] embed = discord.Embed( @@ -6734,22 +6714,18 @@ async def mute( embed.add_field(name="⏰ Ends At", value=f"", inline=True) embed.add_field(name="📝 Reason", value=reason or "No reason provided", inline=False) embed.add_field(name="👮 Moderator", value=ctx.author.mention, inline=True) - embed.add_field(name="🔇 Mute Count", value=f"{user_data['mutes']}", inline=True) + embed.add_field(name="🔇 Mute Count", value=str(user_data["mutes"]), inline=True) if message_data: if isinstance(message_data, dict) and "main_message" in message_data: main_msg = message_data.get("main_message") - context_msgs = message_data.get("context_messages", []) - if main_msg: message_info = f"**Message ID:** `{main_msg['id']}`\n" message_info += f"**Channel:** <#{main_msg['channel_id']}>\n" message_info += f"**Author:** {main_msg['author_name']}\n" - if main_msg.get("content"): content_preview = main_msg["content"][:200] + "..." if len(main_msg["content"]) > 200 else main_msg["content"] message_info += f"**Content:** {content_preview}" - embed.add_field(name="📄 Referenced Message", value=message_info, inline=False) if main_msg.get("attachments"): @@ -6757,7 +6733,7 @@ async def mute( attachments_data = json.loads(main_msg["attachments"]) if attachments_data: attachment_info = "" - for att in attachments_data[:3]: + for i, att in enumerate(attachments_data[:3]): attachment_info += f"• {att.get('filename', 'Unknown file')}\n" if len(attachments_data) > 3: attachment_info += f"• +{len(attachments_data) - 3} more attachments" @@ -6793,20 +6769,84 @@ async def mute( silent_embed.add_field(name="⏱️ Duration", value=duration, inline=True) silent_embed.add_field(name="⏰ Ends At", value=f"", inline=True) silent_embed.add_field(name="📝 Reason", value=reason or "No reason provided", inline=False) - silent_embed.add_field(name="🔇 Mute Count", value=f"{user_data['mutes']}", inline=True) + silent_embed.add_field(name="🔇 Mute Count", value=str(user_data["mutes"]), inline=True) silent_embed.add_field(name="🆔 Mute Record ID", value=f"`{mute_id}`", inline=True) silent_embed.add_field( name="🔔 Actions Taken", - value="• User muted\n• Roles saved\n• User received DM notification if possible\n• Mod log entry created\n• No public announcement", + value="• User muted\n• User received DM notification if possible\n• Mod log entry created\n• No public announcement", inline=False ) silent_embed.set_footer(text=f"Silent Mode • User ID: {user.id} | Use /viewmute {mute_id} for details") silent_embed.set_thumbnail(url=user.display_avatar.url) - await send_response(embed=silent_embed, ephemeral=True) - return + try: + if is_slash_command: + if hasattr(ctx, "followup") and ctx.followup is not None: + await ctx.followup.send(embed=silent_embed, ephemeral=True) + elif hasattr(ctx, "interaction") and ctx.interaction: + await ctx.interaction.followup.send(embed=silent_embed, ephemeral=True) + else: + raise RuntimeError("No followup available after defer") + else: + await ctx.send(embed=silent_embed) + except Exception as e: + logger.error(f"Error sending silent mute response: {e}") + else: + await send_response(embed=embed) - await send_response(embed=embed) + log_additional_info = { + "Mute Count": str(user_data["mutes"]), + "Process ID": str(process_uuid)[:8], + "Mute Record ID": str(mute_id) + } + + if message_data: + if isinstance(message_data, dict) and "main_message" in message_data: + main_msg = message_data.get("main_message") + if main_msg: + log_additional_info["Referenced Message"] = f"ID: {main_msg['id']} in <#{main_msg['channel_id']}>" + else: + log_additional_info["Referenced Message"] = f"ID: {message_data['id']} in <#{message_data['channel_id']}>" + + await log_moderation_action( + guild=ctx.guild, + action_type="mute", + moderator=ctx.author, + target_user=user, + reason=reason, + duration=duration, + additional_info=log_additional_info + ) + + try: + dm_embed = discord.Embed( + title="🔇 You have been muted", + description=f"You have been muted in **{ctx.guild.name}**", + color=0xff0000, + timestamp=datetime.now() + ) + dm_embed.add_field(name="⏱️ Duration", value=duration, inline=True) + dm_embed.add_field(name="⏰ Ends At", value=f"", inline=True) + dm_embed.add_field(name="📝 Reason", value=reason or "No reason provided", inline=False) + dm_embed.add_field(name="👮 Moderator", value=ctx.author.display_name, inline=True) + + if message_data: + preview_content = None + if isinstance(message_data, dict) and "main_message" in message_data: + main_msg = message_data.get("main_message") + if main_msg: + preview_content = main_msg.get("content") + else: + preview_content = message_data.get("content") + + if preview_content: + content_preview = preview_content[:200] + "..." if len(preview_content) > 200 else preview_content + dm_embed.add_field(name="📄 Referenced Message", value=f"```{content_preview}```", inline=False) + + dm_embed.set_footer(text=f"Server: {ctx.guild.name}") + await user.send(embed=dm_embed) + except discord.Forbidden: + pass logger.info(f"User {user.id} muted by {ctx.author.id} in guild {ctx.guild.id} for {duration}. Reason: {reason}") @@ -6819,7 +6859,8 @@ async def mute( ) await send_response(embed=embed, ephemeral=True) -async def apply_full_mute( + +async def apply_mute_action( *, guild: discord.Guild, member: discord.Member, @@ -6827,18 +6868,16 @@ async def apply_full_mute( duration_seconds: int, duration_label: str, reason: str, - source_channel: discord.TextChannel | None = None, + source_channel=None, message_data: dict | None = None, message_id: int | None = None, - send_dm: bool = True, - log_action: bool = True, remove_existing_roles: bool = True, - save_current_roles: bool = True, + save_removed_roles: bool = True, increment_mute_count: bool = True ): guild_settings = get_guild_settings(guild.id) - if save_current_roles: + if save_removed_roles: await save_user_roles(member.id, guild.id, member.roles) if remove_existing_roles: @@ -6857,8 +6896,7 @@ async def apply_full_mute( user_data["mutes"] += 1 update_user_data(member.id, guild.id, "mutes", user_data["mutes"]) - start_time = datetime.now() - end_time = start_time + timedelta(seconds=duration_seconds) + end_time = datetime.now() + timedelta(seconds=duration_seconds) process_data = { "user_id": member.id, @@ -6885,7 +6923,7 @@ async def apply_full_mute( moderator_id=moderator.id, reason=reason, duration=duration_label, - start_time=start_time, + start_time=datetime.now(), end_time=end_time, process_uuid=process_uuid, channel_id=source_channel.id if source_channel else None, @@ -6894,69 +6932,9 @@ async def apply_full_mute( message_id=message_id ) - if log_action: - additional_info = { - "Mute Count": str(user_data["mutes"]), - "Process ID": str(process_uuid)[:8], - "Mute Record ID": str(mute_id) - } - - if message_data: - if isinstance(message_data, dict) and "main_message" in message_data: - main_msg = message_data.get("main_message") - if main_msg: - additional_info["Referenced Message"] = f"ID: {main_msg['id']} in <#{main_msg['channel_id']}>" - else: - if message_data.get("id") and message_data.get("channel_id"): - additional_info["Referenced Message"] = f"ID: {message_data['id']} in <#{message_data['channel_id']}>" - - await log_moderation_action( - guild=guild, - action_type="mute", - moderator=moderator, - target_user=member, - reason=reason, - duration=duration_label, - additional_info=additional_info - ) - - if send_dm: - try: - dm_embed = discord.Embed( - title="🔇 You have been muted", - description=f"You have been muted in **{guild.name}**", - color=0xff0000, - timestamp=datetime.now() - ) - dm_embed.add_field(name="⏱️ Duration", value=duration_label, inline=True) - dm_embed.add_field(name="⏰ Ends At", value=f"", inline=True) - dm_embed.add_field(name="📝 Reason", value=reason or "No reason provided", inline=False) - dm_embed.add_field(name="👮 Moderator", value=getattr(moderator, "display_name", str(moderator)), inline=True) - - preview_content = None - if message_data: - if isinstance(message_data, dict) and "main_message" in message_data: - main_msg = message_data.get("main_message") - if main_msg: - preview_content = main_msg.get("content") - else: - preview_content = message_data.get("content") - - if preview_content: - content_preview = preview_content[:200] + "..." if len(preview_content) > 200 else preview_content - dm_embed.add_field(name="📄 Referenced Message", value=f"```{content_preview}```", inline=False) - - dm_embed.set_footer(text=f"Server: {guild.name}") - await member.send(embed=dm_embed) - except discord.Forbidden: - pass - except Exception as e: - logger.error(f"Failed to DM muted user {member.id}: {e}") - return { "mute_role": mute_role, "user_data": user_data, - "start_time": start_time, "end_time": end_time, "process_uuid": process_uuid, "mute_id": mute_id