4b6817381b
CI (OpenClaw E2E) / openclaw test (push) Has been cancelled
CI / coverage-report (push) Has been cancelled
CI / test-kubernetes (push) Has been cancelled
CI / should-run-thorough (push) Has been cancelled
CI / test-thorough (cloudwatch-demo) (push) Has been cancelled
CI / test-thorough (flink-ecs) (push) Has been cancelled
CI / test-thorough (upstream-lambda) (push) Has been cancelled
CI / test-thorough (prefect-ecs-fargate) (push) Has been cancelled
Release / build-binaries (zip, opensre.exe, onefile, windows-latest, windows-x64) (push) Has been cancelled
Benchmark image — build + push to ECR (any adapter) / build + push (push) Has been cancelled
CI / quality (ubuntu-latest) (push) Has been cancelled
CI / test (tools-runtime) (push) Has been cancelled
CI / test (e2e-general) (push) Has been cancelled
CI / test (cli-runtime) (push) Has been cancelled
CI / test (e2e-provider-and-openclaw) (push) Has been cancelled
CI / test (integrations-and-misc) (push) Has been cancelled
Release / verify (push) Has been cancelled
Release / build-python-dist (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, macos-15-intel, darwin-x64) (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, macos-latest, darwin-arm64) (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04, linux-x64) (push) Has been cancelled
Release / publish-release (push) Has been cancelled
Release / publish-main-release (push) Has been cancelled
Interactive Shell Live (PR + post-merge) / turn-checks (no-LLM) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Interactive Shell Live (PR + post-merge) / turn-live shard ${{ matrix.shard_index }} (push) Has been cancelled
Release / prepare (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04-arm, linux-arm64) (push) Has been cancelled
Synthetic Deterministic Tests / Synthetic offline (deterministic) (push) Has been cancelled
98 lines
3.8 KiB
Python
98 lines
3.8 KiB
Python
"""Discord delivery helper - posts investigation findings to Discord API."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
from typing import Any
|
|
|
|
from platform.common.truncation import truncate
|
|
from platform.notifications.delivery_errors import extract_http_error
|
|
from platform.notifications.delivery_transport import post_json
|
|
from platform.notifications.limits import MAX_MESSAGE_SIZE
|
|
from platform.notifications.redaction import redact_token
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
def _discord_auth_headers(bot_token: str) -> dict[str, str]:
|
|
# ``Content-Type: application/json`` is set automatically by httpx when
|
|
# the request uses the ``json=`` kwarg, so we only need to add auth.
|
|
return {"Authorization": f"Bot {bot_token}"}
|
|
|
|
|
|
def post_discord_message(
|
|
channel_id: str,
|
|
embeds: list[dict[str, Any]],
|
|
bot_token: str,
|
|
content: str = "",
|
|
) -> tuple[bool, str, str]:
|
|
"""Call discord channels api to post message on channel.
|
|
|
|
Returns True on success, False on expected failures.
|
|
"""
|
|
logger.debug("[discord] post message params channel_id: %s", channel_id)
|
|
response = post_json(
|
|
url=f"https://discord.com/api/v10/channels/{channel_id}/messages",
|
|
payload={"content": content, "embeds": embeds},
|
|
headers=_discord_auth_headers(bot_token),
|
|
)
|
|
if not response.ok:
|
|
safe_error = redact_token(response.error, bot_token)
|
|
logger.warning("[discord] post message exception: %s", safe_error)
|
|
return False, safe_error, ""
|
|
if response.status_code not in (200, 201):
|
|
logger.warning("[discord] post message failed: %s", response.status_code)
|
|
error_message = extract_http_error(response.data, response.status_code, response.text)
|
|
safe_error = redact_token(error_message, bot_token)
|
|
logger.warning("[discord] post message failed: %s", safe_error)
|
|
return False, safe_error, ""
|
|
message_id = str(response.data.get("id") or "")
|
|
return True, "", message_id
|
|
|
|
|
|
def create_discord_thread(
|
|
channel_id: str,
|
|
message_id: str,
|
|
name: str,
|
|
bot_token: str,
|
|
) -> tuple[bool, str, str]:
|
|
"""Call discord channels api to create a thread.
|
|
|
|
Returns True on success, False on expected failures.
|
|
"""
|
|
response = post_json(
|
|
url=f"https://discord.com/api/v10/channels/{channel_id}/messages/{message_id}/threads",
|
|
payload={"name": name, "auto_archive_duration": 1440},
|
|
headers=_discord_auth_headers(bot_token),
|
|
)
|
|
if not response.ok:
|
|
safe_error = redact_token(response.error, bot_token)
|
|
logger.warning("[discord] create thread exception: %s", safe_error)
|
|
return False, safe_error, ""
|
|
if response.status_code not in (200, 201):
|
|
error_message = extract_http_error(response.data, response.status_code, response.text)
|
|
safe_error = redact_token(error_message, bot_token)
|
|
logger.warning("[discord] create thread failed: %s", safe_error)
|
|
return False, safe_error, ""
|
|
thread_id = str(response.data.get("id") or "")
|
|
return True, "", thread_id
|
|
|
|
|
|
_EMBED_TITLE_LIMIT = 256
|
|
_EMBED_DESCRIPTION_LIMIT = MAX_MESSAGE_SIZE
|
|
|
|
|
|
def send_discord_report(report: str, discord_ctx: dict[str, Any]) -> tuple[bool, str]:
|
|
channel_id: str = str(discord_ctx.get("channel_id") or "")
|
|
thread_id: str = str(discord_ctx.get("thread_id") or "")
|
|
bot_token: str = str(discord_ctx.get("bot_token") or "")
|
|
embed = {
|
|
"title": truncate("Investigation Complete", _EMBED_TITLE_LIMIT, suffix="…"),
|
|
"color": 15158332,
|
|
"description": truncate(report, _EMBED_DESCRIPTION_LIMIT, suffix="…"),
|
|
"footer": {"text": "OpenSRE Investigation"},
|
|
}
|
|
target = thread_id if thread_id else channel_id
|
|
post_message_success, error, _ = post_discord_message(target, [embed], bot_token)
|
|
return (True, "") if post_message_success else (False, error)
|