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