Skip to content

Commit eeefa91

Browse files
AmbratolmAmbratolm
authored andcommitted
Enhanced console cog.
1 parent 514c443 commit eeefa91

1 file changed

Lines changed: 166 additions & 9 deletions

File tree

bot/cogs/console_cog.py

Lines changed: 166 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
from discord import (
22
Attachment,
33
Forbidden,
4+
HTTPException,
45
Interaction,
56
Member,
7+
Message,
68
NotFound,
79
Object,
810
Role,
@@ -204,11 +206,13 @@ async def proxy(
204206
@app_commands.default_permissions(administrator=True)
205207
@app_commands.checks.has_permissions(administrator=True)
206208
@app_commands.command(
207-
description="Purge messages in current channel", extras={"category": "Console"}
209+
description="Purge messages in current channel with advanced filters", extras={"category": "Console"}
208210
)
209211
@app_commands.describe(
210-
limit="Maximum number of messages to purge (default: 1)",
212+
limit="Number of targeted messages to delete (default: 1)",
211213
member="Only purge messages from this member (optional)",
214+
role="Only purge messages from members with this role (optional)",
215+
message_id="Target a single specific message ID to delete (optional)",
212216
before="Purge messages before this message ID (optional)",
213217
after="Purge messages after this message ID (optional)",
214218
)
@@ -217,11 +221,13 @@ async def purge(
217221
interaction: Interaction,
218222
limit: int = 1,
219223
member: Member | User | None = None,
224+
role: Role | None = None,
225+
message_id: str | None = None,
220226
before: str | None = None,
221227
after: str | None = None,
222228
):
223229
"""
224-
Purge messages in the current channel with better handling of before/after fields.
230+
Purge messages in the current channel with dynamic criteria scanning.
225231
"""
226232
try:
227233
await interaction.response.defer(ephemeral=True)
@@ -239,30 +245,75 @@ async def purge(
239245
ephemeral=True,
240246
)
241247

242-
# Convert before and after to discord.Message objects if provided
248+
# Convert before, after, and target message IDs to integers/objects if provided
243249
before_msg = None
244250
after_msg = None
251+
target_msg_id = None
252+
245253
try:
246254
if before:
247255
before_msg = await channel.fetch_message(int(before))
248256
if after:
249257
after_msg = await channel.fetch_message(int(after))
258+
if message_id:
259+
target_msg_id = int(message_id)
250260
except Exception as e:
251261
return await interaction.followup.send(
252262
embed=EmbedX.error(f"Invalid message ID provided: {e}"),
253263
ephemeral=True,
254264
)
255265

256-
# Purge messages
266+
# Keep track of how many matching messages we've actually approved for deletion
267+
deleted_count = 0
268+
269+
def purge_check(msg: Message) -> bool:
270+
nonlocal deleted_count
271+
272+
# If we already approved the requested number of deletions, stop matching
273+
if deleted_count >= limit:
274+
return False
275+
276+
# Filter by exact Message ID
277+
if target_msg_id and msg.id != target_msg_id:
278+
return False
279+
280+
# Filter by Member
281+
if member and msg.author.id != member.id:
282+
return False
283+
284+
# Filter by Role (Checks if author is a Member and has the role)
285+
if role:
286+
if not isinstance(msg.author, Member) or role not in msg.author.roles:
287+
return False
288+
289+
# If it passed all filters, it's a match! Increment our target counter.
290+
deleted_count += 1
291+
return True
292+
293+
# Set an upper search ceiling. If looking for a specific message ID,
294+
# or a rare user, we might need to sweep through hundreds of records.
295+
# 1000 is usually a safe maximum history sweep boundary per command call.
296+
search_history_limit = 1000 if (member or role or target_msg_id) else limit
297+
298+
# Run the purge with our flexible criteria checklist
257299
deleted = await channel.purge(
258-
limit=limit,
259-
check=(lambda msg: msg.author == member) if member else MISSING,
300+
limit=search_history_limit,
301+
check=purge_check,
260302
before=before_msg,
261303
after=after_msg,
262304
)
305+
306+
# Construct a dynamic success message based on used parameters
307+
filter_details = []
308+
if member: filter_details.append(f"by {member.mention}")
309+
if role: filter_details.append(f"from roles matching {role.name}")
310+
if target_msg_id: filter_details.append(f"with ID `{target_msg_id}`")
311+
312+
suffix = f" matching {" and ".join(filter_details)}" if filter_details else ""
313+
263314
await interaction.followup.send(
264315
embed=EmbedX.success(
265-
f"Purged {len(deleted)} message(s){f" by {member.mention}" if member else ""}."
316+
f"Successfully purged {len(deleted)} message(s){suffix}."
266317
),
267318
ephemeral=True,
268319
)
@@ -345,4 +396,110 @@ async def role(self, interaction: Interaction, member: Member, role: Role):
345396
@role.error
346397
async def role_error(self, interaction: Interaction, error: app_commands.AppCommandError):
347398
if isinstance(error, app_commands.errors.MissingPermissions):
348-
await interaction.response.send_message("⛔ You need 'Manage Roles' permissions to use this command.", ephemeral=True)
399+
await interaction.response.send_message("⛔ You need 'Manage Roles' permissions to use this command.", ephemeral=True)
400+
401+
402+
#----------------------------------------------------------------------------------------------------
403+
# * Server
404+
#----------------------------------------------------------------------------------------------------
405+
@app_commands.command(
406+
name="server",
407+
description="View or update server configuration settings."
408+
)
409+
@app_commands.describe(
410+
name="Change the server's name",
411+
icon="Upload a new server icon (Image file)",
412+
description="Change the server's description (for public/discoverable servers)",
413+
private_profile="Set if the server profile is private (True/False)"
414+
)
415+
@app_commands.checks.has_permissions(administrator=True)
416+
async def server_cmd(
417+
self,
418+
interaction: Interaction,
419+
name: str|None = None,
420+
icon: Attachment|None = None,
421+
description: str|None = None,
422+
private_profile: bool|None = None
423+
):
424+
guild = interaction.guild
425+
426+
# Guard clause if used outside a server (DM)
427+
if not guild:
428+
await interaction.response.send_message("This command can only be used in a server.", ephemeral=True)
429+
return
430+
431+
# 1. IF NO ARGUMENTS PASSED: Return current server info
432+
if name is None and icon is None and description is None and private_profile is None:
433+
embed = EmbedX.info(
434+
title=f"Server Info",
435+
)
436+
if guild.icon:
437+
embed.set_thumbnail(url=guild.icon.url)
438+
439+
embed.add_field(name="ID", value=guild.id, inline=True)
440+
embed.add_field(name="Name", value=guild.name, inline=False)
441+
embed.add_field(name="Owner", value=guild.owner, inline=True)
442+
embed.add_field(name="Members", value=guild.member_count, inline=True)
443+
embed.add_field(name="Description", value=guild.description or "(No description)", inline=False)
444+
445+
# Discord doesn't natively have a "private profile" boolean for guilds,
446+
# but we can check features like 'DISCOVERABLE' or display custom state.
447+
is_discoverable = "DISCOVERABLE" in guild.features
448+
embed.add_field(name="Publicly Discoverable?", value="Yes" if is_discoverable else "No", inline=True)
449+
450+
await interaction.response.send_message(embed=embed)
451+
return
452+
453+
# 2. IF ARGUMENTS PASSED: Update the server settings
454+
await interaction.response.defer(ephemeral=True) # Defer since API calls take time
455+
changes = []
456+
457+
try:
458+
# Update Name
459+
if name is not None:
460+
await guild.edit(name=name)
461+
changes.append(f"• **Name** updated to: `{name}`")
462+
463+
# Update Description
464+
if description is not None:
465+
await guild.edit(description=description)
466+
changes.append(f"• **Description** updated to: `{description}`")
467+
468+
# Update Icon
469+
if icon is not None:
470+
if icon.content_type and icon.content_type.startswith("image/"):
471+
icon_bytes = await icon.read()
472+
await guild.edit(icon=icon_bytes)
473+
changes.append("• **Server Icon** updated successfully.")
474+
else:
475+
await interaction.followup.send("❌ Provided file for the icon must be an image.", ephemeral=True)
476+
return
477+
478+
# Update Private Profile
479+
# Note: Native discord servers don't have a "private_profile" toggle.
480+
# This toggle changes community discovery features if the server qualifies.
481+
if private_profile is not None:
482+
if private_profile:
483+
if "DISCOVERABLE" in guild.features:
484+
await guild.remove_features("DISCOVERABLE")
485+
changes.append("• **Private Profile**: Enabled (Removed from public discovery if applicable).")
486+
else:
487+
# Enabling discoverability usually requires meeting explicit Discord requirements,
488+
# but we attempt to add it here.
489+
try:
490+
await guild.add_features("DISCOVERABLE")
491+
changes.append("• **Private Profile**: Disabled (Added to public discovery).")
492+
except HTTPException:
493+
changes.append("• **Private Profile**: Failed to disable. (Server may not meet Discord's community discovery requirements).")
494+
495+
# Send success confirmation
496+
result_embed = EmbedX.success(
497+
title="Server Settings Updated",
498+
description="\n".join(changes),
499+
)
500+
await interaction.followup.send(embed=result_embed, ephemeral=True)
501+
502+
except Forbidden:
503+
await interaction.followup.send("❌ I do not have the required permissions (`Manage Server`) to change these settings.", ephemeral=True)
504+
except HTTPException as e:
505+
await interaction.followup.send(f"❌ An error occurred while updating settings: {e}", ephemeral=True)

0 commit comments

Comments
 (0)