mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-04-25 00:51:20 +00:00
feat(send_message): add native media attachment support for Discord
Previously send_message only supported media delivery for Telegram. Discord users received a warning that media was omitted. - Add media_files parameter to _send_discord() - Upload media via Discord multipart/form-data API (files[0] field) - Handle Discord in _send_to_platform() same way as Telegram block - Remove Discord from generic chunk loop (now handled above) - Update error/warning strings to mention telegram and discord
This commit is contained in:
parent
1c4d3216d3
commit
4bcb2f2d26
1 changed files with 74 additions and 14 deletions
|
|
@ -387,11 +387,28 @@ async def _send_to_platform(platform, pconfig, chat_id, message, thread_id=None,
|
|||
if platform == Platform.WEIXIN:
|
||||
return await _send_weixin(pconfig, chat_id, message, media_files=media_files)
|
||||
|
||||
# --- Non-Telegram platforms ---
|
||||
# --- Discord: special handling for media attachments ---
|
||||
if platform == Platform.DISCORD:
|
||||
last_result = None
|
||||
for i, chunk in enumerate(chunks):
|
||||
is_last = (i == len(chunks) - 1)
|
||||
result = await _send_discord(
|
||||
pconfig.token,
|
||||
chat_id,
|
||||
chunk,
|
||||
media_files=media_files if is_last else [],
|
||||
thread_id=thread_id,
|
||||
)
|
||||
if isinstance(result, dict) and result.get("error"):
|
||||
return result
|
||||
last_result = result
|
||||
return last_result
|
||||
|
||||
# --- Non-Telegram/Discord platforms ---
|
||||
if media_files and not message.strip():
|
||||
return {
|
||||
"error": (
|
||||
f"send_message MEDIA delivery is currently only supported for telegram; "
|
||||
f"send_message MEDIA delivery is currently only supported for telegram and discord; "
|
||||
f"target {platform.value} had only media attachments"
|
||||
)
|
||||
}
|
||||
|
|
@ -399,14 +416,12 @@ async def _send_to_platform(platform, pconfig, chat_id, message, thread_id=None,
|
|||
if media_files:
|
||||
warning = (
|
||||
f"MEDIA attachments were omitted for {platform.value}; "
|
||||
"native send_message media delivery is currently only supported for telegram"
|
||||
"native send_message media delivery is currently only supported for telegram and discord"
|
||||
)
|
||||
|
||||
last_result = None
|
||||
for chunk in chunks:
|
||||
if platform == Platform.DISCORD:
|
||||
result = await _send_discord(pconfig.token, chat_id, chunk, thread_id=thread_id)
|
||||
elif platform == Platform.SLACK:
|
||||
if platform == Platform.SLACK:
|
||||
result = await _send_slack(pconfig.token, chat_id, chunk)
|
||||
elif platform == Platform.WHATSAPP:
|
||||
result = await _send_whatsapp(pconfig.extra, chat_id, chunk)
|
||||
|
|
@ -571,13 +586,16 @@ async def _send_telegram(token, chat_id, message, media_files=None, thread_id=No
|
|||
return _error(f"Telegram send failed: {e}")
|
||||
|
||||
|
||||
async def _send_discord(token, chat_id, message, thread_id=None):
|
||||
async def _send_discord(token, chat_id, message, thread_id=None, media_files=None):
|
||||
"""Send a single message via Discord REST API (no websocket client needed).
|
||||
|
||||
Chunking is handled by _send_to_platform() before this is called.
|
||||
|
||||
When thread_id is provided, the message is sent directly to that thread
|
||||
via the /channels/{thread_id}/messages endpoint.
|
||||
|
||||
Media files are uploaded one-by-one via multipart/form-data after the
|
||||
text message is sent (same pattern as Telegram).
|
||||
"""
|
||||
try:
|
||||
import aiohttp
|
||||
|
|
@ -592,14 +610,56 @@ async def _send_discord(token, chat_id, message, thread_id=None):
|
|||
url = f"https://discord.com/api/v10/channels/{thread_id}/messages"
|
||||
else:
|
||||
url = f"https://discord.com/api/v10/channels/{chat_id}/messages"
|
||||
headers = {"Authorization": f"Bot {token}", "Content-Type": "application/json"}
|
||||
auth_headers = {"Authorization": f"Bot {token}"}
|
||||
media_files = media_files or []
|
||||
last_data = None
|
||||
warnings = []
|
||||
|
||||
async with aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(total=30), **_sess_kw) as session:
|
||||
async with session.post(url, headers=headers, json={"content": message}, **_req_kw) as resp:
|
||||
if resp.status not in (200, 201):
|
||||
body = await resp.text()
|
||||
return _error(f"Discord API error ({resp.status}): {body}")
|
||||
data = await resp.json()
|
||||
return {"success": True, "platform": "discord", "chat_id": chat_id, "message_id": data.get("id")}
|
||||
# Send text message (skip if empty and media is present)
|
||||
if message.strip() or not media_files:
|
||||
headers = {**auth_headers, "Content-Type": "application/json"}
|
||||
async with session.post(url, headers=headers, json={"content": message}, **_req_kw) as resp:
|
||||
if resp.status not in (200, 201):
|
||||
body = await resp.text()
|
||||
return _error(f"Discord API error ({resp.status}): {body}")
|
||||
last_data = await resp.json()
|
||||
|
||||
# Send each media file as a separate multipart upload
|
||||
for media_path, _is_voice in media_files:
|
||||
if not os.path.exists(media_path):
|
||||
warning = f"Media file not found, skipping: {media_path}"
|
||||
logger.warning(warning)
|
||||
warnings.append(warning)
|
||||
continue
|
||||
try:
|
||||
form = aiohttp.FormData()
|
||||
filename = os.path.basename(media_path)
|
||||
with open(media_path, "rb") as f:
|
||||
form.add_field("files[0]", f, filename=filename)
|
||||
async with session.post(url, headers=auth_headers, data=form, **_req_kw) as resp:
|
||||
if resp.status not in (200, 201):
|
||||
body = await resp.text()
|
||||
warning = _sanitize_error_text(f"Failed to send media {media_path}: Discord API error ({resp.status}): {body}")
|
||||
logger.error(warning)
|
||||
warnings.append(warning)
|
||||
continue
|
||||
last_data = await resp.json()
|
||||
except Exception as e:
|
||||
warning = _sanitize_error_text(f"Failed to send media {media_path}: {e}")
|
||||
logger.error(warning)
|
||||
warnings.append(warning)
|
||||
|
||||
if last_data is None:
|
||||
error = "No deliverable text or media remained after processing"
|
||||
if warnings:
|
||||
return {"error": error, "warnings": warnings}
|
||||
return {"error": error}
|
||||
|
||||
result = {"success": True, "platform": "discord", "chat_id": chat_id, "message_id": last_data.get("id")}
|
||||
if warnings:
|
||||
result["warnings"] = warnings
|
||||
return result
|
||||
except Exception as e:
|
||||
return _error(f"Discord send failed: {e}")
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue