Fix: Resolve webhook send timeout context error
ISSUE ===== When using the manual webhook message feature via API, the following error occurred: - 'Timeout context manager should be used inside a task' - 'NoneType' object is not iterable (when sending without files) The error happened because Discord.py's webhook operations were being awaited directly in the FastAPI endpoint context, rather than within a task running in the bot's event loop. SOLUTION ======== Refactored /manual/send-webhook endpoint to properly handle async operations: 1. Moved webhook creation inside task function - get_or_create_webhooks_for_channel() now runs in send_webhook_message() - All Discord operations (webhook selection, sending) happen inside the task - Follows same pattern as working /manual/send endpoint 2. Fixed file parameter handling - Changed from 'files=discord_files if discord_files else None' - To conditional: only pass files parameter when list is non-empty - Discord.py's webhook.send() cannot iterate over None, requires list or omit 3. Maintained proper file reading - File content still read in endpoint context (before form closes) - File data passed to task as pre-read byte arrays - Prevents form closure issues TECHNICAL DETAILS ================= - Discord.py HTTP operations use timeout context managers - Context managers must run inside bot's event loop (via create_task) - FastAPI endpoint context is separate from bot's event loop - Solution: Wrap all Discord API calls in async task function - Pattern: Read files → Create task → Task handles Discord operations TESTING ======= - Manual webhook sending now works without timeout errors - Both personas (Miku/Evil) send correctly - File attachments work properly - Messages without files send correctly
This commit is contained in:
40
bot/api.py
40
bot/api.py
@@ -866,15 +866,6 @@ async def manual_send_webhook(
|
||||
if persona not in ["miku", "evil"]:
|
||||
return {"status": "error", "message": "Invalid persona. Must be 'miku' or 'evil'"}
|
||||
|
||||
# Get or create webhooks for this channel
|
||||
webhooks = await get_or_create_webhooks_for_channel(channel)
|
||||
if not webhooks:
|
||||
return {"status": "error", "message": "Failed to create webhooks for this channel"}
|
||||
|
||||
# Select the appropriate webhook
|
||||
webhook = webhooks["evil_miku"] if persona == "evil" else webhooks["miku"]
|
||||
display_name = get_evil_miku_display_name() if persona == "evil" else get_miku_display_name()
|
||||
|
||||
# Read file content immediately before the request closes
|
||||
file_data = []
|
||||
for file in files:
|
||||
@@ -891,24 +882,43 @@ async def manual_send_webhook(
|
||||
# Use create_task to avoid timeout context manager error
|
||||
async def send_webhook_message():
|
||||
try:
|
||||
# Get or create webhooks for this channel (inside the task)
|
||||
webhooks = await get_or_create_webhooks_for_channel(channel)
|
||||
if not webhooks:
|
||||
print(f"❌ Failed to create webhooks for channel #{channel.name}")
|
||||
return
|
||||
|
||||
# Select the appropriate webhook
|
||||
webhook = webhooks["evil_miku"] if persona == "evil" else webhooks["miku"]
|
||||
display_name = get_evil_miku_display_name() if persona == "evil" else get_miku_display_name()
|
||||
|
||||
# Prepare files for webhook
|
||||
discord_files = []
|
||||
for file_info in file_data:
|
||||
discord_files.append(discord.File(io.BytesIO(file_info['content']), filename=file_info['filename']))
|
||||
|
||||
# Send via webhook with display name
|
||||
await webhook.send(
|
||||
content=message,
|
||||
username=display_name,
|
||||
files=discord_files if discord_files else None,
|
||||
wait=True
|
||||
)
|
||||
if discord_files:
|
||||
await webhook.send(
|
||||
content=message,
|
||||
username=display_name,
|
||||
files=discord_files,
|
||||
wait=True
|
||||
)
|
||||
else:
|
||||
await webhook.send(
|
||||
content=message,
|
||||
username=display_name,
|
||||
wait=True
|
||||
)
|
||||
|
||||
persona_name = "Evil Miku" if persona == "evil" else "Hatsune Miku"
|
||||
print(f"✅ Manual webhook message sent as {persona_name} to #{channel.name}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Failed to send webhook message: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
globals.client.loop.create_task(send_webhook_message())
|
||||
return {"status": "ok", "message": f"Webhook message queued for sending as {persona}"}
|
||||
|
||||
Reference in New Issue
Block a user