pacsea/sources/status/
mod.rs1use crate::state::ArchStatusColor;
4use std::fmt::Write;
5
6mod api;
8mod html;
10pub mod translate;
12mod utils;
14
15use api::{parse_status_api_summary, parse_uptimerobot_api};
16use html::{is_aur_down_in_monitors, parse_arch_status_from_html};
17use utils::{extract_aur_today_percent, extract_aur_today_rect_color, severity_max};
18
19type Result<T> = super::Result<T>;
21
22#[allow(clippy::missing_const_for_fn)]
36pub async fn fetch_arch_status_text() -> Result<(String, ArchStatusColor)> {
37 let api_url = "https://status.archlinux.org/api/v2/summary.json";
39 let api_result =
40 tokio::task::spawn_blocking(move || crate::util::curl::curl_json(api_url)).await;
41
42 if let Ok(Ok(v)) = api_result {
43 let (mut text, mut color, suffix) = parse_status_api_summary(&v);
44
45 if let Ok(Ok(html)) = tokio::task::spawn_blocking(|| {
47 crate::util::curl::curl_text("https://status.archlinux.org")
48 })
49 .await
50 {
51 if is_aur_down_in_monitors(&html) {
54 let aur_pct_opt = extract_aur_today_percent(&html);
55 let aur_pct_suffix = aur_pct_opt
56 .map(|p| format!(" — AUR today: {p}%"))
57 .unwrap_or_default();
58 let text = format!("Status: AUR Down{aur_pct_suffix}");
59 return Ok((text, ArchStatusColor::IncidentSevereToday));
60 }
61
62 let aur_pct_opt = extract_aur_today_percent(&html);
64 if let Some(p) = aur_pct_opt {
65 let _ = write!(text, " — AUR today: {p}%");
66 }
67
68 if let Some(rect_color) = extract_aur_today_rect_color(&html) {
71 let api_says_operational = matches!(
73 v.get("components")
74 .and_then(|c| c.as_array())
75 .and_then(|arr| arr.iter().find(|c| {
76 c.get("name")
77 .and_then(|n| n.as_str())
78 .is_some_and(|n| n.to_lowercase().contains("aur"))
79 }))
80 .and_then(|c| c.get("status").and_then(|s| s.as_str())),
81 Some("operational")
82 );
83
84 if api_says_operational
85 && matches!(
86 rect_color,
87 ArchStatusColor::IncidentToday | ArchStatusColor::IncidentSevereToday
88 )
89 {
90 color = rect_color;
92 let text_lower = text.to_lowercase();
94 let pct_suffix = if text_lower.contains("aur today") {
95 String::new() } else {
97 aur_pct_opt
98 .map(|p| format!(" — AUR today: {p}%"))
99 .unwrap_or_default()
100 };
101 match rect_color {
102 ArchStatusColor::IncidentSevereToday
103 if !text_lower.contains("outage") && !text_lower.contains("issues") =>
104 {
105 text = format!("AUR issues detected (see status){pct_suffix}");
106 }
107 ArchStatusColor::IncidentToday
108 if !text_lower.contains("degraded")
109 && !text_lower.contains("outage")
110 && !text_lower.contains("issues") =>
111 {
112 text = format!("AUR degraded (see status){pct_suffix}");
113 }
114 _ => {}
115 }
116 } else {
117 color = severity_max(color, rect_color);
119 }
120 }
121 }
122
123 if let Some(sfx) = suffix
124 && !text.to_lowercase().contains(&sfx.to_lowercase())
125 {
126 text = format!("{text} {sfx}");
127 }
128
129 return Ok((text, color));
130 }
131
132 let uptimerobot_api_url = "https://status.archlinux.org/api/getMonitorList/vmM5ruWEAB";
134 let uptimerobot_result =
135 tokio::task::spawn_blocking(move || crate::util::curl::curl_json(uptimerobot_api_url))
136 .await;
137
138 if let Ok(Ok(v)) = uptimerobot_result
139 && let Some((mut text, mut color)) = parse_uptimerobot_api(&v)
140 {
141 if let Ok(Ok(html)) = tokio::task::spawn_blocking(|| {
144 crate::util::curl::curl_text("https://status.archlinux.org")
145 })
146 .await
147 && is_aur_down_in_monitors(&html)
148 {
149 let aur_pct_opt = extract_aur_today_percent(&html);
150 let aur_pct_suffix = aur_pct_opt
151 .map(|p| format!(" — AUR today: {p}%"))
152 .unwrap_or_default();
153 text = format!("Status: AUR Down{aur_pct_suffix}");
154 color = ArchStatusColor::IncidentSevereToday;
155 }
156 return Ok((text, color));
157 }
158
159 let url = "https://status.archlinux.org";
161 let body = tokio::task::spawn_blocking(move || crate::util::curl::curl_text(url)).await??;
162
163 let (text, color) = parse_arch_status_from_html(&body);
166 Ok((text, color))
169}