ugc_scraper/parser/
mod.rs

1use crate::{ParseError, Result};
2use scraper::{ElementRef, Selector};
3use steamid_ng::SteamID;
4use time::format_description::FormatItem;
5use time::macros::format_description;
6
7mod map_history;
8mod match_page;
9mod player;
10mod player_details;
11mod seasons;
12mod team;
13mod team_lookup;
14mod team_matches;
15mod team_roster_history;
16mod transactions;
17
18pub use map_history::*;
19pub use match_page::*;
20pub use player::*;
21pub use player_details::*;
22pub use seasons::*;
23pub use team::*;
24pub use team_lookup::*;
25pub use team_matches::*;
26pub use team_roster_history::*;
27pub use transactions::*;
28
29pub trait Parser {
30    type Output;
31    fn parse(&self, document: &str) -> Result<Self::Output>;
32}
33
34trait ElementExt<'a> {
35    fn first_text(&self) -> Option<&'a str>;
36}
37
38impl<'a> ElementExt<'a> for ElementRef<'a> {
39    fn first_text(&self) -> Option<&'a str> {
40        self.text().map(str::trim).find(|s| !s.is_empty())
41    }
42}
43
44fn select_text<'a>(el: ElementRef<'a>, selector: &Selector) -> Option<&'a str> {
45    el.select(selector)
46        .next()
47        .and_then(|item| item.text().find(|s| !s.trim().is_empty()))
48        .map(str::trim)
49}
50
51fn select_last_text<'a>(el: ElementRef<'a>, selector: &Selector) -> Option<&'a str> {
52    el.select(selector)
53        .next()
54        .and_then(|item| item.text().last())
55        .map(str::trim)
56}
57
58const DATE_FORMAT: &[FormatItem<'static>] =
59    format_description!("[month padding:none]/[day padding:none]/[year]");
60const MEMBER_DATE_FORMAT: &[FormatItem<'static>] = format_description!(
61    "[month repr:short] [day padding:none], [year]\n/\n[hour padding:none]:[minute] [period]\n(ET)"
62);
63const MEMBER_DATE_ALT_FORMAT: &[FormatItem<'static>] =
64    format_description!("[month repr:short] [day padding:none], [year]");
65const ROSTER_HISTORY_DATE_FORMAT: &[FormatItem<'static>] =
66    format_description!("[month repr:short] [day padding:none], [year]");
67
68fn team_id_from_link(link: &str) -> Result<u32, ParseError> {
69    link.rsplit_once('=')
70        .and_then(|part| part.1.parse().ok())
71        .ok_or_else(|| ParseError::InvalidLink {
72            link: link.to_string(),
73            role: "team id",
74        })
75}
76
77fn match_id_from_link(link: &str) -> Result<u32, ParseError> {
78    link.rsplit_once('=')
79        .and_then(|part| part.1.parse().ok())
80        .ok_or_else(|| ParseError::InvalidLink {
81            link: link.to_string(),
82            role: "match id",
83        })
84}
85
86fn steam_id_from_link(link: &str) -> Result<SteamID, ParseError> {
87    link.rsplit_once('=')
88        .and_then(|part| part.1.parse::<u64>().ok())
89        .ok_or_else(|| ParseError::InvalidLink {
90            link: link.to_string(),
91            role: "user id",
92        })
93        .map(SteamID::from)
94}