Skip to main content

tuemensa_lib/
mensa.rs

1extern crate serde_derive;
2use chrono::{Datelike, Local};
3use serde::{Deserialize, Serialize};
4#[cfg(feature = "ureq")]
5use ureq::Agent;
6
7pub enum MensaName {
8    Wilhelmstrasse,
9    Morgenstelle,
10    PrinzKarl,
11}
12
13#[allow(dead_code)]
14pub trait Mealplan {
15    fn id(&self) -> &str;
16    fn name(&self) -> &str;
17    fn today(&self) -> (String, Vec<&Menu>);
18    fn nth(&self, days: u8, vegetarian: bool) -> Option<(String, Vec<&Menu>)>;
19}
20
21fn get_nth_date(days: u8) -> Option<chrono::DateTime<Local>> {
22    //if days > 7 {
23    //    return None;
24    //}
25
26    if let Some(dt) = Local::now().checked_add_days(chrono::Days::new(days as u64)) {
27        return match dt.weekday() {
28            chrono::Weekday::Sat => dt.checked_add_days(chrono::Days::new(2)),
29            chrono::Weekday::Sun => dt.checked_add_days(chrono::Days::new(1)),
30            _ => Some(dt),
31        };
32    }
33    None
34}
35
36#[derive(Debug, Serialize, Deserialize)]
37pub struct Mensa {
38    #[serde(alias = "621")] // Morgenstelle
39    #[serde(alias = "623")] // Prinz Karl
40    #[serde(rename = "611")] // Wilhelmstraße
41    canteen: Canteen,
42}
43
44impl Mensa {
45    #[cfg(feature = "small")]
46    pub fn from(name: MensaName) -> anyhow::Result<Mensa> {
47        let canteen_id = match name {
48            MensaName::Wilhelmstrasse => 611,
49            MensaName::Morgenstelle => 621,
50            MensaName::PrinzKarl => 623,
51        };
52
53        let url =
54            format!("https://www.my-stuwe.de//wp-json/mealplans/v1/canteens/{canteen_id}?lang=de");
55
56        let agent: Agent = ureq::AgentBuilder::new()
57            .timeout_read(Duration::from_secs(5))
58            .timeout_write(Duration::from_secs(5))
59            .build();
60        Ok(agent.get(&url).call()?.into_json::<Mensa>()?)
61    }
62
63    #[cfg(feature = "android")]
64    pub fn from(name: MensaName) -> anyhow::Result<Mensa> {
65        let canteen_id = match name {
66            MensaName::Wilhelmstrasse => 611,
67            MensaName::Morgenstelle => 621,
68            MensaName::PrinzKarl => 623,
69        };
70
71        let url =
72            format!("https://www.my-stuwe.de//wp-json/mealplans/v1/canteens/{canteen_id}?lang=de");
73
74        let client = reqwest::blocking::Client::new();
75        Ok(client.get(url).send()?.json()?)
76    }
77
78    fn _print(&self) {
79        println!("{:#?}", self);
80    }
81}
82
83impl Mealplan for Mensa {
84    fn id(&self) -> &str {
85        &self.canteen.canteen_id
86    }
87
88    fn name(&self) -> &str {
89        &self.canteen.canteen
90    }
91
92    fn today(&self) -> (String, Vec<&Menu>) {
93        let local = format!("{}", Local::now().format("%Y-%m-%d"));
94        let menus = self
95            .canteen
96            .menus
97            .iter()
98            .filter(|&x| x.menu_date == local)
99            .collect();
100        (local, menus)
101    }
102
103    fn nth(&self, days: u8, vegetarian: bool) -> Option<(String, Vec<&Menu>)> {
104        match get_nth_date(days) {
105            Some(dt) => {
106                let local = format!("{}", dt.format("%Y-%m-%d"));
107                if vegetarian {
108                    let menus = self
109                        .canteen
110                        .menus
111                        .iter()
112                        .filter(|&x| x.menu_date == local && x.menu_line.contains("veg"))
113                        .collect();
114                    Some((local, menus))
115                } else {
116                    let menus = self
117                        .canteen
118                        .menus
119                        .iter()
120                        .filter(|&x| x.menu_date == local)
121                        .collect();
122                    Some((local, menus))
123                }
124            }
125            _ => None,
126        }
127    }
128}
129
130#[derive(Debug, Serialize, Deserialize)]
131pub struct Canteen {
132    #[serde(rename = "canteenId")]
133    canteen_id: String,
134    canteen: String,
135    menus: Vec<Menu>,
136}
137
138#[derive(Debug, Serialize, Deserialize)]
139pub struct Menu {
140    id: String,
141    #[serde(rename = "menuLine")]
142    menu_line: String,
143    photo: Photo,
144    #[serde(rename = "studentPrice")]
145    student_price: String,
146    #[serde(rename = "guestPrice")]
147    guest_price: String,
148    #[serde(rename = "pupilPrice")]
149    pupil_price: String,
150    #[serde(rename = "menuDate")]
151    menu_date: String,
152    menu: Vec<String>,
153    meats: Vec<String>,
154    icons: Vec<String>,
155    #[serde(rename = "filtersInclude")]
156    filters_include: Vec<FiltersInclude>,
157    allergens: Vec<String>,
158    additives: Vec<String>,
159}
160
161impl Menu {
162    pub fn _print(&self) {
163        println!("{:#?}", self);
164    }
165
166    pub fn print_short_info(&self) {
167        println!(
168            "{}: {}, {}€",
169            self.menu_line,
170            self.menu.join(", "),
171            self.student_price
172        );
173    }
174
175    pub fn print_very_short_info(&self) {
176        if let Some(name) = self.menu.first() {
177            println!("{}", name);
178        }
179    }
180
181    pub fn get_short_info(&self) -> (&str, String, &str) {
182        (&self.menu_line, self.menu.join(", "), &self.student_price)
183    }
184}
185
186#[derive(Debug, Serialize, Deserialize)]
187pub struct Photo {
188    thumbnail: String,
189    medium: String,
190    large: String,
191    full: String,
192}
193
194#[derive(Debug, Serialize, Deserialize)]
195pub enum FiltersInclude {
196    F,
197    #[serde(rename = "Vegan")]
198    FiltersIncludeVegan,
199    G,
200    #[serde(rename = "mensaVital")]
201    MensaVital,
202    R,
203    S,
204    V,
205    #[serde(rename = "vegan")]
206    Vegan,
207}