cargo_mate/
admin_msg.rs

1use anyhow::{Context, Result};
2use chrono::{DateTime, Utc, Duration};
3use colored::*;
4use serde::{Deserialize, Serialize};
5use std::fs;
6use std::path::PathBuf;
7#[derive(Debug, Serialize, Deserialize)]
8struct AdminMessageResponse {
9    success: bool,
10    has_message: bool,
11    rate_limited: Option<bool>,
12    message: Option<AdminMessage>,
13}
14#[derive(Debug, Serialize, Deserialize)]
15struct AdminMessage {
16    id: i32,
17    title: String,
18    content: String,
19    #[serde(rename = "type")]
20    message_type: String,
21    priority: String,
22}
23#[derive(Debug, Serialize, Deserialize)]
24struct MessageCheckRecord {
25    last_checks: Vec<DateTime<Utc>>,
26    seen_message_ids: Vec<i32>,
27}
28impl Default for MessageCheckRecord {
29    fn default() -> Self {
30        Self {
31            last_checks: Vec::new(),
32            seen_message_ids: Vec::new(),
33        }
34    }
35}
36fn get_message_record_path() -> PathBuf {
37    dirs::home_dir()
38        .unwrap_or_else(|| PathBuf::from("."))
39        .join(".shipwreck")
40        .join("admin_msg_record.json")
41}
42fn load_check_record() -> Result<MessageCheckRecord> {
43    let path = get_message_record_path();
44    if !path.exists() {
45        return Ok(MessageCheckRecord::default());
46    }
47    let content = fs::read_to_string(&path)?;
48    let record: MessageCheckRecord = serde_json::from_str(&content)?;
49    Ok(record)
50}
51fn save_check_record(record: &MessageCheckRecord) -> Result<()> {
52    let path = get_message_record_path();
53    if let Some(parent) = path.parent() {
54        fs::create_dir_all(parent)?;
55    }
56    let json = serde_json::to_string_pretty(record)?;
57    fs::write(&path, json)?;
58    Ok(())
59}
60fn should_check_messages() -> Result<bool> {
61    let mut record = load_check_record()?;
62    let now = Utc::now();
63    let one_day_ago = now - Duration::hours(24);
64    record.last_checks.retain(|check| *check > one_day_ago);
65    if record.last_checks.len() >= 3 {
66        return Ok(false);
67    }
68    if let Some(last_check) = record.last_checks.last() {
69        let four_hours_ago = now - Duration::hours(4);
70        if *last_check > four_hours_ago {
71            return Ok(false);
72        }
73    }
74    Ok(true)
75}
76fn record_message_check() -> Result<()> {
77    let mut record = load_check_record()?;
78    record.last_checks.push(Utc::now());
79    save_check_record(&record)?;
80    Ok(())
81}
82fn has_seen_message(message_id: i32) -> Result<bool> {
83    let record = load_check_record()?;
84    Ok(record.seen_message_ids.contains(&message_id))
85}
86fn mark_message_seen(message_id: i32) -> Result<()> {
87    let mut record = load_check_record()?;
88    if !record.seen_message_ids.contains(&message_id) {
89        record.seen_message_ids.push(message_id);
90        save_check_record(&record)?;
91    }
92    Ok(())
93}
94fn get_user_id() -> Result<String> {
95    let user_id_path = dirs::home_dir()
96        .unwrap_or_else(|| PathBuf::from("."))
97        .join(".shipwreck")
98        .join("user.id");
99    if user_id_path.exists() {
100        fs::read_to_string(&user_id_path)
101            .map(|s| s.trim().to_string())
102            .context("Failed to read user ID")
103    } else {
104        Ok(format!("CM-USER-{}", Utc::now().timestamp()))
105    }
106}
107pub async fn check_and_display_message() -> Result<()> {
108    if !should_check_messages()? {
109        return Ok(());
110    }
111    record_message_check()?;
112    let user_id = get_user_id()?;
113    let version = env!("CARGO_PKG_VERSION");
114    let client = reqwest::Client::new();
115    let response = client
116        .get("https://cargo.do/api/admin/msg")
117        .query(&[("user_id", user_id.as_str()), ("version", version)])
118        .timeout(std::time::Duration::from_secs(5))
119        .send()
120        .await;
121    match response {
122        Ok(resp) if resp.status().is_success() => {
123            if let Ok(data) = resp.json::<AdminMessageResponse>().await {
124                if data.success && data.has_message {
125                    if let Some(message) = data.message {
126                        if !has_seen_message(message.id)? {
127                            display_admin_message(&message);
128                            mark_message_seen(message.id)?;
129                            let _ = client
130                                .post("https://cargo.do/api/admin/msg")
131                                .form(
132                                    &[
133                                        ("user_id", user_id.as_str()),
134                                        ("message_id", &message.id.to_string()),
135                                        ("mark_seen", "true"),
136                                    ],
137                                )
138                                .timeout(std::time::Duration::from_secs(2))
139                                .send()
140                                .await;
141                        }
142                    }
143                }
144            }
145        }
146        _ => {}
147    }
148    Ok(())
149}
150fn display_admin_message(message: &AdminMessage) {
151    println!();
152    let border_color = match message.message_type.as_str() {
153        "error" => "red",
154        "warning" => "yellow",
155        "success" => "green",
156        _ => "cyan",
157    };
158    match message.priority.as_str() {
159        "critical" => {
160            println!(
161                "{}",
162                "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
163                .red().bold()
164            );
165            println!("{} {}", "🚨".red(), message.title.red().bold());
166            println!(
167                "{}",
168                "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
169                .red().bold()
170            );
171        }
172        "urgent" => {
173            println!(
174                "{}",
175                "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
176                .yellow()
177            );
178            println!("{} {}", "⚠️ ".yellow(), message.title.yellow().bold());
179            println!(
180                "{}",
181                "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
182                .yellow()
183            );
184        }
185        _ => {
186            let line = "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━";
187            match border_color {
188                "red" => println!("{}", line.red()),
189                "yellow" => println!("{}", line.yellow()),
190                "green" => println!("{}", line.green()),
191                _ => println!("{}", line.cyan()),
192            }
193            let icon = match message.message_type.as_str() {
194                "error" => "❌",
195                "warning" => "⚠️",
196                "success" => "✅",
197                _ => "ℹ️",
198            };
199            println!("{} {}", icon, message.title.bold());
200            match border_color {
201                "red" => println!("{}", line.red()),
202                "yellow" => println!("{}", line.yellow()),
203                "green" => println!("{}", line.green()),
204                _ => println!("{}", line.cyan()),
205            }
206        }
207    }
208    println!();
209    for line in message.content.lines() {
210        println!("  {}", line);
211    }
212    println!();
213    match message.priority.as_str() {
214        "critical" => {
215            println!(
216                "{}",
217                "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
218                .red().bold()
219            )
220        }
221        "urgent" => {
222            println!(
223                "{}",
224                "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
225                .yellow()
226            )
227        }
228        _ => {
229            let line = "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━";
230            match border_color {
231                "red" => println!("{}", line.red()),
232                "yellow" => println!("{}", line.yellow()),
233                "green" => println!("{}", line.green()),
234                _ => println!("{}", line.cyan()),
235            }
236        }
237    }
238    println!();
239}
240pub async fn force_check_message() -> Result<()> {
241    let mut record = load_check_record()?;
242    record.last_checks.clear();
243    save_check_record(&record)?;
244    check_and_display_message().await
245}