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 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")] #[serde(alias = "623")] #[serde(rename = "611")] 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}