feat: Implement interactive image captcha for file uploads
This commit is contained in:
parent
8788cea989
commit
6549a5c324
@ -8,6 +8,27 @@ import json
|
||||
import asyncio
|
||||
import base64
|
||||
from typing import Optional, Dict, Any, Union
|
||||
import discord.ui
|
||||
|
||||
class CaptchaModal(discord.ui.Modal, title="Solve Image Captcha"):
|
||||
def __init__(self, captcha_id: str, captcha_image_file: discord.File):
|
||||
super().__init__()
|
||||
self.captcha_id = captcha_id
|
||||
self.captcha_image_file = captcha_image_file
|
||||
self.solution = discord.ui.TextInput(
|
||||
label="Captcha Solution",
|
||||
placeholder="Enter the text from the image...",
|
||||
required=True,
|
||||
min_length=1,
|
||||
max_length=100
|
||||
)
|
||||
self.add_item(self.solution)
|
||||
|
||||
async def on_submit(self, interaction: discord.Interaction):
|
||||
# This method will be called when the user submits the modal
|
||||
# The actual file upload logic will be handled by the calling function
|
||||
await interaction.response.defer(ephemeral=True) # Defer the response to prevent interaction timeout
|
||||
|
||||
|
||||
class UploadCog(commands.Cog, name="Upload"):
|
||||
"""Cog for interacting with the upload API"""
|
||||
@ -46,35 +67,15 @@ class UploadCog(commands.Cog, name="Upload"):
|
||||
def register_commands(self):
|
||||
"""Register all commands for this cog"""
|
||||
|
||||
# --- Text Captcha Command ---
|
||||
text_captcha_command = app_commands.Command(
|
||||
name="textcaptcha",
|
||||
description="Generate a text captcha for file uploads",
|
||||
callback=self.upload_text_captcha_callback,
|
||||
parent=self.upload_group
|
||||
)
|
||||
self.upload_group.add_command(text_captcha_command)
|
||||
|
||||
# --- Image Captcha Command ---
|
||||
image_captcha_command = app_commands.Command(
|
||||
name="imagecaptcha",
|
||||
description="Generate an image captcha for file uploads",
|
||||
callback=self.upload_image_captcha_callback,
|
||||
parent=self.upload_group
|
||||
)
|
||||
self.upload_group.add_command(image_captcha_command)
|
||||
|
||||
# --- Upload File Command ---
|
||||
# --- Upload File Command (Interactive) ---
|
||||
upload_file_command = app_commands.Command(
|
||||
name="file",
|
||||
description="Upload a file with captcha verification",
|
||||
callback=self.upload_file_callback,
|
||||
description="Upload a file, interactively solving an image captcha",
|
||||
callback=self.upload_file_interactive_callback, # New callback name
|
||||
parent=self.upload_group
|
||||
)
|
||||
app_commands.describe(
|
||||
file="The file to upload",
|
||||
captcha_id="The ID of the captcha challenge",
|
||||
captcha_solution="The solution to the captcha challenge",
|
||||
expires_after="Time in seconds until the file expires (default: 86400 - 24 hours)"
|
||||
)(upload_file_command)
|
||||
self.upload_group.add_command(upload_file_command)
|
||||
@ -111,111 +112,61 @@ class UploadCog(commands.Cog, name="Upload"):
|
||||
print(f"Error making API request to {url}: {e}")
|
||||
raise
|
||||
|
||||
async def upload_text_captcha_callback(self, interaction: discord.Interaction):
|
||||
"""Generate a text captcha for file uploads"""
|
||||
await interaction.response.defer(ephemeral=True)
|
||||
async def upload_file_interactive_callback(self, interaction: discord.Interaction,
|
||||
file: discord.Attachment,
|
||||
expires_after: Optional[int] = 86400):
|
||||
"""Upload a file, interactively solving an image captcha"""
|
||||
await interaction.response.defer(ephemeral=False) # Defer the initial response
|
||||
|
||||
try:
|
||||
# Make API request to generate text captcha
|
||||
captcha_data = await self._make_api_request("GET", "/api/captcha/text")
|
||||
|
||||
# Store captcha ID in cache
|
||||
captcha_id = captcha_data.get("captcha_id")
|
||||
user_id = interaction.user.id
|
||||
self.captcha_cache[user_id] = captcha_id
|
||||
|
||||
# Create embed with captcha information
|
||||
embed = discord.Embed(
|
||||
title="Text Captcha Challenge",
|
||||
description=captcha_data.get("message", "Please solve the captcha challenge"),
|
||||
color=discord.Color.blue()
|
||||
)
|
||||
embed.add_field(name="Captcha ID", value=f"`{captcha_id}`", inline=False)
|
||||
embed.add_field(name="Challenge", value=f"`{captcha_data.get('challenge', 'N/A')}`", inline=False)
|
||||
|
||||
# Add instructions for using the captcha
|
||||
instructions = (
|
||||
"**How to use this captcha:**\n"
|
||||
"1. Copy the Captcha ID above\n"
|
||||
"2. Type the challenge text as your solution\n"
|
||||
"3. Use the `/upload file` command with your file, the Captcha ID, and your solution"
|
||||
)
|
||||
embed.add_field(name="Instructions", value=instructions, inline=False)
|
||||
|
||||
embed.set_footer(text="This captcha is valid for 10 minutes")
|
||||
|
||||
await interaction.followup.send(embed=embed, ephemeral=True)
|
||||
except Exception as e:
|
||||
await interaction.followup.send(f"Error generating text captcha: {e}", ephemeral=True)
|
||||
|
||||
async def upload_image_captcha_callback(self, interaction: discord.Interaction):
|
||||
"""Generate an image captcha for file uploads"""
|
||||
await interaction.response.defer(ephemeral=True)
|
||||
|
||||
try:
|
||||
# Make API request to generate image captcha
|
||||
# 1. Generate Image Captcha
|
||||
captcha_data = await self._make_api_request("GET", "/api/captcha/image")
|
||||
|
||||
# Store captcha ID in cache
|
||||
captcha_id = captcha_data.get("captcha_id")
|
||||
user_id = interaction.user.id
|
||||
self.captcha_cache[user_id] = captcha_id
|
||||
|
||||
# Create embed with captcha information
|
||||
embed = discord.Embed(
|
||||
title="Image Captcha Challenge",
|
||||
description=captcha_data.get("message", "Please solve the captcha challenge"),
|
||||
color=discord.Color.blue()
|
||||
)
|
||||
embed.add_field(name="Captcha ID", value=f"`{captcha_id}`", inline=False)
|
||||
|
||||
# Extract the base64 image data
|
||||
image_data = captcha_data.get("image", "")
|
||||
|
||||
if not captcha_id or not image_data:
|
||||
raise Exception("Failed to retrieve captcha ID or image data.")
|
||||
|
||||
if image_data.startswith("data:image/png;base64,"):
|
||||
image_data = image_data.replace("data:image/png;base64,", "")
|
||||
|
||||
# Convert base64 to bytes
|
||||
image_bytes = io.BytesIO(base64.b64decode(image_data))
|
||||
image_bytes.seek(0)
|
||||
image_bytes = io.BytesIO(base64.b64decode(image_data))
|
||||
image_bytes.seek(0)
|
||||
captcha_image_file = discord.File(fp=image_bytes, filename="captcha.png")
|
||||
|
||||
# Create file attachment
|
||||
file = discord.File(fp=image_bytes, filename="captcha.png")
|
||||
embed.set_image(url="attachment://captcha.png")
|
||||
# 2. Send Captcha and Prompt for Solution via Modal
|
||||
embed = discord.Embed(
|
||||
title="Image Captcha Challenge",
|
||||
description="Please solve the captcha challenge to upload your file.",
|
||||
color=discord.Color.blue()
|
||||
)
|
||||
embed.add_field(name="Captcha ID", value=f"`{captcha_id}`", inline=False)
|
||||
embed.set_image(url="attachment://captcha.png")
|
||||
embed.set_footer(text="This captcha is valid for 10 minutes. Enter the text from the image.")
|
||||
|
||||
# Add instructions for using the captcha
|
||||
instructions = (
|
||||
"**How to use this captcha:**\n"
|
||||
"1. Copy the Captcha ID above\n"
|
||||
"2. Type the text shown in the image as your solution\n"
|
||||
"3. Use the `/upload file` command with your file, the Captcha ID, and your solution"
|
||||
)
|
||||
embed.add_field(name="Instructions", value=instructions, inline=False)
|
||||
# Send the captcha image and instructions
|
||||
await interaction.followup.send(embed=embed, file=captcha_image_file, ephemeral=True)
|
||||
|
||||
embed.set_footer(text="This captcha is valid for 10 minutes")
|
||||
# Create and send the modal
|
||||
modal = CaptchaModal(captcha_id=captcha_id, captcha_image_file=captcha_image_file)
|
||||
await interaction.followup.send_modal(modal)
|
||||
|
||||
await interaction.followup.send(embed=embed, file=file, ephemeral=True)
|
||||
else:
|
||||
embed.add_field(name="Error", value="Invalid image data received", inline=False)
|
||||
await interaction.followup.send(embed=embed, ephemeral=True)
|
||||
except Exception as e:
|
||||
await interaction.followup.send(f"Error generating image captcha: {e}", ephemeral=True)
|
||||
# Wait for the modal submission
|
||||
timed_out = await modal.wait()
|
||||
|
||||
async def upload_file_callback(self, interaction: discord.Interaction,
|
||||
file: discord.Attachment,
|
||||
captcha_id: str,
|
||||
captcha_solution: str,
|
||||
expires_after: Optional[int] = 86400):
|
||||
"""Upload a file with captcha verification"""
|
||||
await interaction.response.defer(ephemeral=False)
|
||||
if timed_out:
|
||||
await interaction.followup.send("Captcha solution timed out. Please try again.", ephemeral=True)
|
||||
return
|
||||
|
||||
try:
|
||||
# Validate input
|
||||
captcha_solution = modal.solution.value
|
||||
|
||||
# 3. Proceed with File Upload
|
||||
if not file:
|
||||
await interaction.followup.send("Please provide a file to upload", ephemeral=True)
|
||||
return
|
||||
|
||||
if not captcha_id or not captcha_solution:
|
||||
await interaction.followup.send("Please provide both captcha ID and solution", ephemeral=True)
|
||||
if not captcha_solution:
|
||||
await interaction.followup.send("Captcha solution was not provided.", ephemeral=True)
|
||||
return
|
||||
|
||||
# Download the file
|
||||
@ -260,9 +211,12 @@ class UploadCog(commands.Cog, name="Upload"):
|
||||
# Add clickable link
|
||||
embed.description += f"\n\n[Click here to download]({file_url})"
|
||||
|
||||
# Send the final upload success message to the original interaction channel
|
||||
await interaction.followup.send(embed=embed)
|
||||
|
||||
except Exception as e:
|
||||
await interaction.followup.send(f"Error uploading file: {e}", ephemeral=True)
|
||||
# If an error occurs during captcha generation or upload, send an ephemeral error message
|
||||
await interaction.followup.send(f"Error during file upload process: {e}", ephemeral=True)
|
||||
|
||||
async def setup(bot: commands.Bot):
|
||||
"""Add the UploadCog to the bot."""
|
||||
|
Loading…
x
Reference in New Issue
Block a user