~goorzhel/sota-slack-spotter

ref: b52cec66b2f63aa4cd638dff3c0aeaccf0de241b sota-slack-spotter/src/sota/alert.rs -rw-r--r-- 3.1 KiB
b52cec66 — Antonio Gurgel Use serde_with to resolve ""/None discrepancy 4 months ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
//! Statements of planned activations.

use std::{
    fmt::{Display, Error, Formatter},
    hash::{Hash, Hasher},
};

use anyhow::{anyhow, Result};
use reqwest::blocking::get;
use serde::{Deserialize, Serialize};
use serde_with::serde_as;
use time::{format_description::parse as parse_format, OffsetDateTime};

use crate::callsign::Callsign;

use super::SOTAItem;

const ALERTS_URL: &str = "https://api2.sota.org.uk/api/alerts";

/// Fetch alerts from SOTA API.
pub fn all_alerts() -> Result<Vec<Alert>> {
    let response = get(ALERTS_URL)?;
    if !response.status().is_success() {
        return Err(anyhow!("Got {} from API", response.status()));
    }
    Ok(response.json()?)
}

/// A SOTA alert.
///
/// An alert is something like a future spot: it states that the named operator
/// plans to activate a summit at the given time.
#[derive(Debug, Clone, Deserialize, Serialize, Eq)]
#[serde_as]
#[serde(rename_all = "camelCase")]
pub struct Alert {
    pub activating_callsign: Callsign,
    pub association_code: String,
    pub summit_code: String,
    pub summit_details: String,
    /// Freeform description of planned bands/modes.
    pub frequency: String,
    /// The time the alert was posted.
    #[serde(with = "time::serde::rfc3339")]
    pub time_stamp: OffsetDateTime,
    /// The time for which the activation is planned.
    #[serde(with = "time::serde::rfc3339")]
    pub date_activated: OffsetDateTime,
    #[serde_as(as = "NoneAsEmptyString")]
    pub comments: Option<String>,
}

impl SOTAItem for Alert {
    fn callsign(&self) -> &Callsign {
        &self.activating_callsign
    }

    fn is_too_old(&self) -> bool {
        let now = OffsetDateTime::now_utc();
        self.date_activated < now
    }

    fn format_timestamp(&self) -> String {
        let time = self
            .date_activated
            .format(&parse_format("[year]-[month]-[day] [hour]:[minute]").unwrap())
            .unwrap();
        format!("{}Z", time)
    }
}

impl Display for Alert {
    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
        write!(
            f,
            "{} will activate {}/{}\n- {}\n- {}\n - {}",
            self.activating_callsign,
            self.association_code,
            self.summit_code,
            self.summit_details,
            self.format_timestamp(),
            self.frequency,
        )?;
        if self.comments.is_some() {
            write!(f, "\n- \"{}\"", self.comments.as_ref().unwrap())?;
        }
        Ok(())
    }
}

impl Hash for Alert {
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.activating_callsign.hash(state);
        self.association_code.hash(state);
        self.summit_code.hash(state);
        self.frequency.hash(state);
        self.date_activated.hash(state);
    }
}

impl PartialEq for Alert {
    fn eq(&self, other: &Self) -> bool {
        self.activating_callsign == other.activating_callsign
            && self.association_code == other.association_code
            && self.summit_code == other.summit_code
            && self.frequency == other.frequency
            && self.time_stamp == other.time_stamp
    }
}