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

169 lines
7.2 KiB
Python

import os
import discord
from discord.ext import commands
from discord import app_commands
import random as random_module
import typing # Need this for Optional
# Cache to store uploaded file URLs (local to this cog)
file_url_cache = {}
class RandomCog(commands.Cog):
def __init__(self, bot):
self.bot = bot
# Updated _random_logic
async def _random_logic(
self, interaction_or_ctx, hidden: bool = False
) -> typing.Optional[str]:
"""Core logic for the random command. Returns an error message string or None if successful."""
# NSFW Check
is_nsfw_channel = False
channel = interaction_or_ctx.channel
if isinstance(channel, discord.TextChannel) and channel.is_nsfw():
is_nsfw_channel = True
elif isinstance(
channel, discord.DMChannel
): # DMs are considered NSFW for this purpose
is_nsfw_channel = True
if not is_nsfw_channel:
# Return error message directly, ephemeral handled by caller
return "This command can only be used in age-restricted (NSFW) channels or DMs."
directory = os.getenv("UPLOAD_DIRECTORY")
if not directory:
return "UPLOAD_DIRECTORY is not set in the .env file."
if not os.path.isdir(directory):
return (
"The specified UPLOAD_DIRECTORY does not exist or is not a directory."
)
files = [
f
for f in os.listdir(directory)
if os.path.isfile(os.path.join(directory, f))
]
if not files:
return "The specified directory is empty."
# Attempt to send a random file, handling potential size issues
original_files = list(files) # Copy for checking if all files failed
while files:
chosen_file_name = random_module.choice(files)
file_path = os.path.join(directory, chosen_file_name)
# Check cache first
if chosen_file_name in file_url_cache:
# For interactions, defer if not already done, using the hidden flag
if (
not isinstance(interaction_or_ctx, commands.Context)
and not interaction_or_ctx.response.is_done()
):
await interaction_or_ctx.response.defer(
ephemeral=hidden
) # Defer before sending cached URL
# Send cached URL
if isinstance(interaction_or_ctx, commands.Context):
await interaction_or_ctx.reply(
file_url_cache[chosen_file_name]
) # Prefix commands can't be ephemeral
else:
await interaction_or_ctx.followup.send(
file_url_cache[chosen_file_name], ephemeral=hidden
)
return None # Indicate success
try:
# Determine how to send the file based on context/interaction
if isinstance(interaction_or_ctx, commands.Context):
message = await interaction_or_ctx.reply(
file=discord.File(file_path)
) # Use reply for context
else: # It's an interaction
# Interactions need followup for files after defer()
if not interaction_or_ctx.response.is_done():
await interaction_or_ctx.response.defer(
ephemeral=hidden
) # Defer before sending file
# Send file ephemerally if hidden is True
message = await interaction_or_ctx.followup.send(
file=discord.File(file_path), ephemeral=hidden
)
# Cache the URL if successfully sent
if message and message.attachments:
file_url_cache[chosen_file_name] = message.attachments[0].url
# Success, no further message needed
return None
else:
# Should not happen if send succeeded, but handle defensively
files.remove(chosen_file_name)
print(
f"Warning: File {chosen_file_name} sent but no attachment URL found."
) # Log warning
continue
except discord.HTTPException as e:
if e.code == 40005: # Request entity too large
print(f"File too large: {chosen_file_name}")
files.remove(chosen_file_name)
continue # Try another file
else:
print(f"HTTP Error sending file: {e}")
# Return error message directly, ephemeral handled by caller
return f"Failed to upload the file due to an HTTP error: {e}"
except Exception as e:
print(f"Generic Error sending file: {e}")
# Return error message directly, ephemeral handled by caller
return f"An unexpected error occurred while uploading the file: {e}"
# If loop finishes without returning/sending, all files were too large
# Return error message directly, ephemeral handled by caller
return "All files in the directory were too large to upload."
# --- Prefix Command ---
@commands.command(name="random")
async def random(self, ctx: commands.Context):
"""Upload a random NSFW image from the configured directory."""
# Call _random_logic, hidden is False by default and irrelevant for prefix
response = await self._random_logic(ctx)
if response is not None:
await ctx.reply(response)
# --- Slash Command ---
# Updated signature and logic
@app_commands.command(
name="random",
description="Upload a random NSFW image from the configured directory",
)
@app_commands.describe(
hidden="Set to True to make the response visible only to you (default: False)"
)
async def random_slash(
self, interaction: discord.Interaction, hidden: bool = False
):
"""Slash command version of random."""
# Pass hidden parameter to logic
response = await self._random_logic(interaction, hidden=hidden)
# If response is None, the logic already sent the file via followup/deferral
if response is not None: # An error occurred
# Ensure interaction hasn't already been responded to or deferred
if not interaction.response.is_done():
# Send error message ephemerally if hidden is True OR if it's the NSFW channel error
ephemeral_error = hidden or response.startswith(
"This command can only be used"
)
await interaction.response.send_message(
response, ephemeral=ephemeral_error
)
else:
# If deferred, use followup. Send ephemerally based on hidden flag.
await interaction.followup.send(response, ephemeral=hidden)
async def setup(bot):
await bot.add_cog(RandomCog(bot))