feat: enhance message sending to include file attachments in Discord API

This commit is contained in:
SLAR_Edge
2026-03-06 17:10:53 +08:00
parent 473ae5ef18
commit a25923b793

View File

@@ -75,7 +75,7 @@ class DiscordChannel(BaseChannel):
self._http = None self._http = None
async def send(self, msg: OutboundMessage) -> None: async def send(self, msg: OutboundMessage) -> None:
"""Send a message through Discord REST API.""" """Send a message through Discord REST API, including file attachments."""
if not self._http: if not self._http:
logger.warning("Discord HTTP client not initialized") logger.warning("Discord HTTP client not initialized")
return return
@@ -84,6 +84,11 @@ class DiscordChannel(BaseChannel):
headers = {"Authorization": f"Bot {self.config.token}"} headers = {"Authorization": f"Bot {self.config.token}"}
try: try:
# Send file attachments first
for media_path in msg.media or []:
await self._send_file(url, headers, media_path, reply_to=msg.reply_to)
# Send text content
chunks = split_message(msg.content or "", MAX_MESSAGE_LEN) chunks = split_message(msg.content or "", MAX_MESSAGE_LEN)
if not chunks: if not chunks:
return return
@@ -91,8 +96,8 @@ class DiscordChannel(BaseChannel):
for i, chunk in enumerate(chunks): for i, chunk in enumerate(chunks):
payload: dict[str, Any] = {"content": chunk} payload: dict[str, Any] = {"content": chunk}
# Only set reply reference on the first chunk # Only set reply reference on the first chunk (if no media was sent)
if i == 0 and msg.reply_to: if i == 0 and msg.reply_to and not msg.media:
payload["message_reference"] = {"message_id": msg.reply_to} payload["message_reference"] = {"message_id": msg.reply_to}
payload["allowed_mentions"] = {"replied_user": False} payload["allowed_mentions"] = {"replied_user": False}
@@ -123,6 +128,54 @@ class DiscordChannel(BaseChannel):
await asyncio.sleep(1) await asyncio.sleep(1)
return False return False
async def _send_file(
self,
url: str,
headers: dict[str, str],
file_path: str,
reply_to: str | None = None,
) -> bool:
"""Send a file attachment via Discord REST API using multipart/form-data."""
path = Path(file_path)
if not path.is_file():
logger.warning("Discord file not found, skipping: {}", file_path)
return False
if path.stat().st_size > MAX_ATTACHMENT_BYTES:
logger.warning("Discord file too large (>20MB), skipping: {}", path.name)
return False
payload_json: dict[str, Any] = {}
if reply_to:
payload_json["message_reference"] = {"message_id": reply_to}
payload_json["allowed_mentions"] = {"replied_user": False}
for attempt in range(3):
try:
with open(path, "rb") as f:
files = {"files[0]": (path.name, f, "application/octet-stream")}
data: dict[str, Any] = {}
if payload_json:
data["payload_json"] = json.dumps(payload_json)
response = await self._http.post(
url, headers=headers, files=files, data=data
)
if response.status_code == 429:
resp_data = response.json()
retry_after = float(resp_data.get("retry_after", 1.0))
logger.warning("Discord rate limited, retrying in {}s", retry_after)
await asyncio.sleep(retry_after)
continue
response.raise_for_status()
logger.info("Discord file sent: {}", path.name)
return True
except Exception as e:
if attempt == 2:
logger.error("Error sending Discord file {}: {}", path.name, e)
else:
await asyncio.sleep(1)
return False
async def _gateway_loop(self) -> None: async def _gateway_loop(self) -> None:
"""Main gateway loop: identify, heartbeat, dispatch events.""" """Main gateway loop: identify, heartbeat, dispatch events."""
if not self._ws: if not self._ws: