1use codoseo_core::check::Severity;
4use serde_json::{Value, json};
5
6use crate::message::{AlertItem, AlertMessage, fit_lines, one_line, short_url, truncate};
7
8const SECTION_BUDGET: usize = 2800;
10
11fn emoji(s: Severity) -> &'static str {
12 match s {
13 Severity::Critical => ":red_circle:",
14 Severity::Warning => ":large_orange_circle:",
15 Severity::Notice => ":large_blue_circle:",
16 }
17}
18
19fn escape(s: &str) -> String {
22 s.replace('&', "&")
23 .replace('<', "<")
24 .replace('>', ">")
25}
26
27fn line(item: &AlertItem) -> String {
28 let mut out = format!("{} *{}*", emoji(item.severity), escape(&item.kind_label));
29 if let Some(url) = &item.url {
30 out.push_str(&format!(
31 " <{}|{}>",
32 escape(url),
33 escape(&short_url(url, 70))
34 ));
35 }
36 let change = item.change_text();
37 if !change.is_empty() {
38 out.push_str(&format!(": {}", escape(&truncate(&one_line(&change), 80))));
39 }
40 out
41}
42
43pub fn payload(msg: &AlertMessage) -> Value {
44 let mut blocks = vec![json!({
45 "type": "header",
46 "text": {"type": "plain_text", "text": truncate(&msg.headline, 150)},
47 })];
48 let mut more = msg.more as usize;
49 if !msg.items.is_empty() {
50 let lines: Vec<String> = msg.items.iter().map(line).collect();
51 let (text, left_out) = fit_lines(&lines, SECTION_BUDGET);
52 more += left_out;
53 blocks.push(json!({"type": "section", "text": {"type": "mrkdwn", "text": text}}));
54 }
55 if more > 0 {
56 blocks.push(json!({
57 "type": "context",
58 "elements": [{"type": "mrkdwn", "text": format!("and {more} more")}],
59 }));
60 }
61 blocks.push(json!({
62 "type": "actions",
63 "elements": [{
64 "type": "button",
65 "text": {"type": "plain_text", "text": "Open in CodoSEO"},
66 "url": msg.site_url,
67 }],
68 }));
69 json!({"text": escape(&msg.headline), "blocks": blocks})
70}