1use std::collections::HashMap;
2
3use anyhow::{Context, Ok};
4
5use journey::InternalJourneyRequest;
6pub use journey::{
7 Journey, JourneyRequest, JourneyResults, JourneySegment, JourneysSolution, Stop,
8};
9use login::{LoginRequestBody, LoginResponse};
10use reqwest::Client;
11pub use station::{Station, StationRealtime, StationTrainRealtime};
12use station::{StationCode, StationLabel};
13pub use train::{Disruption, TrainRealtime, TrainSchedule, TrainStation};
14
15static LOGIN_ENDPOINT: &str = "https://big.ntvspa.it/BIG/v7/Rest/SessionManager.svc/Login";
16static STATION_LIST_ENDPOINT: &str = "https://italoinviaggio.italotreno.it/it/stazione";
17static STATION_REALTIME_ENDPOINT: &str =
18 "https://italoinviaggio.italotreno.it/api/RicercaStazioneService?&CodiceStazione=";
19static TRAIN_REALTIME_ENDPOINT: &str =
20 "https://italoinviaggio.italotreno.it/api/RicercaTrenoService?&TrainNumber=";
21
22static SEARCH_SOLUTIONS: &str =
23 "https://big.ntvspa.it/BIG/v7/Rest/BookingManager.svc/GetAvailableTrains";
24
25mod journey;
26mod login;
27mod station;
28mod train;
29
30#[derive(Default)]
35pub struct ItaloApi {
36 signature: Option<LoginResponse>,
37 client: Client,
38}
39
40impl ItaloApi {
41 fn is_initialized(&self) -> bool {
42 self.signature.is_some()
43 }
44
45 async fn init(&mut self) -> anyhow::Result<()> {
46 self.signature = Some(
47 self.client
48 .post(LOGIN_ENDPOINT)
49 .json(&LoginRequestBody::default())
50 .send()
51 .await?
52 .json()
53 .await?,
54 );
55 Ok(())
56 }
57
58 pub async fn station_list(&self) -> anyhow::Result<Vec<Station>> {
62 let res = self
63 .client
64 .get(STATION_LIST_ENDPOINT)
65 .send()
66 .await?
67 .text()
68 .await?;
69
70 let raw_lists = res
71 .split_once("ItaloInViaggio.Resources.stationList = ")
72 .context("stationList not found")?
73 .1
74 .split_once("ItaloInViaggio.Resources.stationCoding = ")
75 .context("stationCoding not found")?;
76
77 let label_list: Vec<StationLabel> =
78 serde_json::from_str(raw_lists.0.trim_end().trim_end_matches(';'))?;
79
80 let code_list: Vec<StationCode> = serde_json::from_str(
81 raw_lists
82 .1
83 .split_once("ItaloInViaggio.Resources.localizzation")
84 .context("localization not found")?
85 .0
86 .trim_end()
87 .trim_end_matches(';'),
88 )?;
89
90 let label_map = label_list
91 .iter()
92 .map(|elem| (elem.value(), elem.label()))
93 .collect::<HashMap<_, _>>();
94
95 Ok(code_list
96 .iter()
97 .map(|elem| {
98 Station::new(
99 elem.code().to_owned(),
100 elem.url_coding().to_owned(),
101 label_map
102 .get(elem.code())
103 .unwrap_or(&&"".to_string())
104 .to_string(),
105 )
106 })
107 .filter(|elem| !elem.name().is_empty())
108 .collect())
109 }
110
111 pub async fn station_realtime(&self, station: Station) -> anyhow::Result<StationRealtime> {
113 Ok(self
114 .client
115 .get(STATION_REALTIME_ENDPOINT.to_string() + station.code())
116 .send()
117 .await?
118 .json()
119 .await?)
120 }
121
122 pub async fn train_realtime(&self, train_code: &str) -> anyhow::Result<TrainRealtime> {
124 Ok(self
125 .client
126 .get(TRAIN_REALTIME_ENDPOINT.to_string() + train_code)
127 .send()
128 .await?
129 .json()
130 .await?)
131 }
132
133 pub async fn find_journeys(
135 &mut self,
136 journey: &JourneyRequest,
137 ) -> anyhow::Result<JourneyResults> {
138 match self.is_initialized() {
139 true => Ok(()),
140 false => self.init().await,
141 }?;
142
143 Ok(self
144 .client
145 .post(SEARCH_SOLUTIONS)
146 .json(&InternalJourneyRequest::new(
147 self.signature.as_deref().unwrap(),
148 2,
149 journey,
150 ))
151 .send()
152 .await?
153 .json()
154 .await?)
155 }
156}
157
158#[cfg(test)]
159mod tests {
160 use std::ops::Add;
161
162 use chrono::{Duration, Utc};
163
164 use super::*;
165
166 #[tokio::test]
167 async fn it_works() {
168 let mut api = ItaloApi::default();
169 assert!(!api.is_initialized());
170
171 assert!(api.init().await.is_ok());
172 assert!(api.is_initialized());
173
174 let stations = api.station_list().await;
175 println!("{:?}", stations);
176 println!();
177 assert!(stations.is_ok_and(|f| f.len() > 0));
178
179 let station_realtime = api
180 .station_realtime(Station::new(
181 "MC_".to_string(),
182 "milano-centrale".to_string(),
183 "Milano Centrale".to_string(),
184 ))
185 .await;
186 println!("{:?}", station_realtime);
187 println!();
188 assert!(station_realtime
189 .is_ok_and(|f| f.arrival_board().len() > 0 && f.departure_board().len() > 0));
190
191 let train_realtime = api.train_realtime("8158").await;
192 println!("{:?}", train_realtime);
193 println!();
194
195 let start_station = Station::new(
196 "NAC".to_string(),
197 "napoli-centrale".to_string(),
198 "Napoli Centrale".to_string(),
199 );
200 let end_station = Station::new(
201 "MC_".to_string(),
202 "milano-centrale".to_string(),
203 "Milano Centrale".to_string(),
204 );
205
206 let solutions = api
207 .find_journeys(
208 &&JourneyRequest::default()
209 .set_departure_station(start_station)
210 .set_arrival_station(end_station)
211 .set_interval_start_date_time(Utc::now())
212 .set_interval_end_date_time(Utc::now().add(Duration::hours(5)))
213 .set_override_interval_time_restriction(true),
214 )
215 .await;
216
217 println!("{:?}", solutions);
218 println!();
219 assert!(solutions.is_ok());
220 }
221}