Skip to content

Commit ed170b7

Browse files
Merge pull request #2160 from elementary-data/devin/1773995331-truncate-teams-webhook-payload
Truncate oversized Teams webhook payloads to prevent HTTP 413 errors
2 parents 31e7528 + 97195a2 commit ed170b7

File tree

1 file changed

+59
-3
lines changed

1 file changed

+59
-3
lines changed

elementary/messages/messaging_integrations/teams_webhook.py

Lines changed: 59 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1+
import json
12
from datetime import datetime, timezone
23
from http import HTTPStatus
3-
from typing import Any, Optional
4+
from typing import Any, Dict, List, Optional
45

56
import requests
67
from ratelimit import limits, sleep_and_retry
@@ -25,6 +26,7 @@
2526

2627
Channel: TypeAlias = Optional[str]
2728
ONE_SECOND = 1
29+
TEAMS_PAYLOAD_SIZE_LIMIT = 27 * 1024
2830

2931

3032
class TeamsWebhookHttpError(MessagingIntegrationError):
@@ -36,8 +38,8 @@ def __init__(self, response: requests.Response):
3638
)
3739

3840

39-
def send_adaptive_card(webhook_url: str, card: dict) -> requests.Response:
40-
payload = {
41+
def _build_payload(card: dict) -> dict:
42+
return {
4143
"type": "message",
4244
"attachments": [
4345
{
@@ -48,6 +50,60 @@ def send_adaptive_card(webhook_url: str, card: dict) -> requests.Response:
4850
],
4951
}
5052

53+
54+
def _truncation_notice_item() -> Dict[str, Any]:
55+
return {
56+
"type": "TextBlock",
57+
"text": "_... Content truncated due to message size limits._",
58+
"wrap": True,
59+
"isSubtle": True,
60+
}
61+
62+
63+
def _minimal_card(card: dict) -> dict:
64+
return {
65+
**card,
66+
"body": [
67+
{
68+
"type": "TextBlock",
69+
"text": "Alert content too large to display in Teams.",
70+
"wrap": True,
71+
"weight": "bolder",
72+
}
73+
],
74+
}
75+
76+
77+
def _truncate_card(card: dict) -> dict:
78+
body: List[Dict[str, Any]] = list(card.get("body", []))
79+
if not body:
80+
return card
81+
82+
while len(body) > 1:
83+
payload = _build_payload({**card, "body": body + [_truncation_notice_item()]})
84+
if len(json.dumps(payload)) <= TEAMS_PAYLOAD_SIZE_LIMIT:
85+
break
86+
body.pop()
87+
88+
truncated = {**card, "body": body + [_truncation_notice_item()]}
89+
if len(json.dumps(_build_payload(truncated))) > TEAMS_PAYLOAD_SIZE_LIMIT:
90+
return _minimal_card(card)
91+
return truncated
92+
93+
94+
def send_adaptive_card(webhook_url: str, card: dict) -> requests.Response:
95+
payload = _build_payload(card)
96+
payload_json = json.dumps(payload)
97+
if len(payload_json) > TEAMS_PAYLOAD_SIZE_LIMIT:
98+
logger.warning(
99+
"Teams webhook payload size (%d bytes) exceeds limit (%d bytes), "
100+
"truncating card body",
101+
len(payload_json),
102+
TEAMS_PAYLOAD_SIZE_LIMIT,
103+
)
104+
card = _truncate_card(card)
105+
payload = _build_payload(card)
106+
51107
response = requests.post(
52108
webhook_url,
53109
json=payload,

0 commit comments

Comments
 (0)