dhl_api/lib.rs
1use anyhow::Result;
2use regex::Regex;
3use serde::Deserialize;
4
5const DHL_JSON_REGEXP: &str = r#".*initialState: JSON\.parse\((.+")\).*"#;
6
7/// Parcel history events (where did you come from, where did you go?)
8#[derive(Deserialize, Debug)]
9pub struct DHLPackageItemHistoryEvent {
10 /// Date
11 #[serde(rename = "datum")]
12 pub date: String,
13 /// Status text
14 #[serde(rename = "status")]
15 pub status: String,
16 /// If it was returned (?)
17 #[serde(rename = "ruecksendung")]
18 pub return_shipment: bool,
19 /// Location where this happened
20 #[serde(rename = "ort")]
21 pub location: Option<String>,
22}
23
24/// Parcel history
25#[derive(Deserialize, Debug)]
26pub struct DHLPackageItemHistory {
27 /// The parcel may or may not have made a few stops already, this Vec may contain them.
28 #[serde(rename = "events")]
29 pub events: Option<Vec<DHLPackageItemHistoryEvent>>,
30 /// Current state of the parcel
31 #[serde(rename = "aktuellerStatus")]
32 pub current_status: Option<String>,
33 /// Number of steps there are (or should be?) - might as well use `events.len()` 🤷♀️
34 #[serde(rename = "fortschritt")]
35 pub steps: u64,
36}
37
38/// Information why a parcel tracking code may not have been found.
39/// This is only set when no tracking information was found (yet?).
40#[derive(Deserialize, Debug)]
41pub struct DHLPackageNotFoundInfo {
42 /// If `true`, no data is available for this tracking code.
43 #[serde(rename = "keineDatenVerfuegbar")]
44 pub no_data_available: bool,
45 /// If `true`, this is probably not a DHL tracking code.
46 #[serde(rename = "keineDhlPaketSendung")]
47 pub not_a_dhl_package: bool,
48}
49
50/// Parcel item details
51#[derive(Deserialize, Debug)]
52pub struct DHLPackageItemDetails {
53 /// History of the parcel
54 #[serde(rename = "sendungsverlauf")]
55 pub history: DHLPackageItemHistory,
56 /// Destination country of the parcel
57 #[serde(rename = "zielland")]
58 pub destination_country: Option<String>,
59}
60
61/// Parcel item elements
62#[derive(Deserialize, Debug)]
63pub struct DHLPackageItem {
64 /// Tracking code, just for keeping track.
65 pub id: String,
66
67 /// (unsure what this means, if you know let me know ❤️)
68 #[serde(rename = "hasCompleteDetails")]
69 pub has_complete_details: bool,
70
71 /// Details for this parcel
72 #[serde(rename = "sendungsdetails")]
73 pub item_details: DHLPackageItemDetails,
74
75 /// If no parcel was found, this variable is **not `None`** and may contains reasons as to why.
76 /// Otherwise it should be `None`.
77 #[serde(rename = "sendungNichtGefunden")]
78 pub package_not_found: Option<DHLPackageNotFoundInfo>,
79}
80
81/// Root element, contains a vector with parcel items
82#[derive(Deserialize, Debug)]
83pub struct DHLPackageStatus {
84 /// The items returned for the query.
85 #[serde(rename = "sendungen")]
86 pub items: Vec<DHLPackageItem>,
87}
88
89fn find_and_derez_json(body: &str) -> Result<DHLPackageStatus, anyhow::Error> {
90 let rex = Regex::new(DHL_JSON_REGEXP).unwrap();
91
92 let caps = rex.captures(&body).unwrap();
93
94 let json_escaped = &caps[1];
95 let json = json_escaped.trim()[1..json_escaped.len() - 1].replace(r#"\""#, "\"");
96
97 //println!("{}", json);
98
99 let r: DHLPackageStatus = serde_json::from_str(&json)?;
100 Ok(r)
101}
102
103/// Returns a DHLPackageStatus struct. You should check if it actually has any data with: `DHLPackageStatus.items[0].package_not_found.has_some()`
104/// `DHLPackageItem.package_not_found` will only be set if no package was found for that tracking code.
105///
106/// # Arguments
107///
108/// * `package_id` - Tracking code as `&str` of the parcel you wish to query, usually a number but sometimes contains letters.
109///
110/// # Examples
111///
112/// ``` ignore
113/// use dhl_api::get_dhl_package_status;
114/// let status = get_dhl_package_status("123456789").await?;
115/// for item in status.items {
116/// if item.package_not_found.is_some() {
117/// // This item was not found
118///
119/// let why_not_found = item.package_not_found.unwrap();
120/// // if why_not_found.no_data_available { ...
121/// // if why_not_found.not_a_dhl_package { ...
122///
123/// continue;
124/// }
125///
126/// let tracking_code = &item.id;
127///
128/// if item.has_complete_details {
129/// let details = &item.item_details;
130///
131/// for event in &details.history.events.unwrap() {
132/// // Do whatever you need 🦈
133/// }
134/// }
135/// }
136/// ```
137pub async fn get_dhl_package_status(package_id: &str) -> Result<DHLPackageStatus, anyhow::Error> {
138 let my_url = format!("https://www.dhl.de/int-verfolgen/?lang=en&domain=de&lang=en&domain=de&lang=en&domain=de&lang=en&domain=de&piececode={}", package_id);
139 let body = reqwest::get(&my_url).await?.text().await?;
140
141 find_and_derez_json(&body)
142}
143
144/// Try to parse a HTML body and look for the shipping information, usually you need not to call this
145/// unless you wish to use your own HTTP client or backend URL or whatever other wizardry you're into.
146///
147/// # Arguments
148///
149/// `htmlbody` - HTML code to scan, check out the sourcecode to find the regexp and what it expects.
150pub fn get_dhl_package_from_html(htmlbody: &str) -> Result<DHLPackageStatus, anyhow::Error> {
151 find_and_derez_json(&htmlbody)
152}