map history current season

This commit is contained in:
Robin Appelman 2023-11-21 19:40:25 +01:00
commit 02ba1b2b9f
13 changed files with 19626 additions and 6 deletions

View file

@ -265,3 +265,41 @@ impl FromStr for TransactionAction {
}
}
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct MapHistory {
pub current: CurrentSeasonMapList,
pub previous: Vec<PreviousSeasonMapList>,
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct CurrentSeasonMapList {
pub season: u8,
pub maps: Vec<CurrentSeasonMap>,
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct PreviousSeasonMapList {
pub season: u8,
pub maps: Vec<PreviousSeasonMapList>,
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct CurrentSeasonMap {
pub week: u8,
pub map: String,
pub date: String,
pub na_date: Option<String>,
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct PreviousSeasonMap {
pub week: u8,
pub map: String,
#[cfg_attr(feature = "serde", serde(with = "serde_date"))]
pub date: Date,
}

View file

@ -4,12 +4,12 @@ mod error;
pub mod parser;
use crate::data::{
GameMode, MatchInfo, MembershipHistory, Player, RosterHistory, Seasons, Team, TeamRef,
TeamSeason, Transaction,
GameMode, MapHistory, MatchInfo, MembershipHistory, Player, RosterHistory, Seasons, Team,
TeamRef, TeamSeason, Transaction,
};
use crate::parser::{
MatchPageParser, Parser, PlayerDetailsParser, PlayerParser, SeasonsParser, TeamLookupParser,
TeamMatchesParser, TeamParser, TeamRosterHistoryParser, TransactionParser,
MapHistoryParser, MatchPageParser, Parser, PlayerDetailsParser, PlayerParser, SeasonsParser,
TeamLookupParser, TeamMatchesParser, TeamParser, TeamRosterHistoryParser, TransactionParser,
};
pub use error::*;
use reqwest::redirect::Policy;
@ -30,6 +30,7 @@ pub struct UgcClient {
team_lookup_parser: TeamLookupParser,
match_page_parser: MatchPageParser,
transaction_parser: TransactionParser,
map_history_parser: MapHistoryParser,
}
/// "API client" for ugc by scraping the website
@ -46,6 +47,7 @@ impl UgcClient {
team_lookup_parser: TeamLookupParser::new(),
match_page_parser: MatchPageParser::new(),
transaction_parser: TransactionParser::new(),
map_history_parser: MapHistoryParser::new(),
}
}
@ -172,4 +174,13 @@ impl UgcClient {
let body = self.client.get(link).send().await?.text().await?;
self.transaction_parser.parse(&body)
}
pub async fn map_history(&self, format: GameMode) -> Result<MapHistory> {
let link = format!(
"https://www.ugcleague.com/rostertransactions_tf2{}_all.cfm",
format.letter()
);
let body = self.client.get(link).send().await?.text().await?;
self.map_history_parser.parse(&body)
}
}

112
src/parser/map_history.rs Normal file
View file

@ -0,0 +1,112 @@
use super::Parser;
use crate::data::{CurrentSeasonMap, CurrentSeasonMapList, MapHistory};
use crate::parser::select_text;
use crate::{ParseError, Result};
use scraper::{Html, Selector};
const SELECTOR_CURRENT_ROW: &str = "table.table.table-condensed.table-responsive tbody tr";
const SELECTOR_CURRENT_SEASON: &str = "div.row > div > div.white-row-small > h5:nth-child(2), div.row-fluid > div > div.white-row-small > h4:first-child+h5";
const SELECTOR_CURRENT_WEEK: &str = "td:nth-child(1)";
const SELECTOR_CURRENT_MAP: &str = "td:nth-child(2)";
const SELECTOR_CURRENT_DATE: &str = "td:nth-child(4) small";
const SELECTOR_CURRENT_DATE_ALT: &str = "td:nth-child(5) small";
const SELECTOR_PREVIOUS: &str = "table.table.table-condensed.table-bordered";
const SELECTOR_PREVIOUS_SEASON: &str = "tr.top-bar td h3.text-info";
pub struct MapHistoryParser {
selector_current_row: Selector,
selector_current_season: Selector,
selector_current_week: Selector,
selector_current_map: Selector,
selector_current_date: Selector,
selector_current_date_alt: Selector,
}
impl Default for MapHistoryParser {
fn default() -> Self {
Self::new()
}
}
impl MapHistoryParser {
pub fn new() -> Self {
MapHistoryParser {
selector_current_row: Selector::parse(SELECTOR_CURRENT_ROW).unwrap(),
selector_current_season: Selector::parse(SELECTOR_CURRENT_SEASON).unwrap(),
selector_current_week: Selector::parse(SELECTOR_CURRENT_WEEK).unwrap(),
selector_current_map: Selector::parse(SELECTOR_CURRENT_MAP).unwrap(),
selector_current_date: Selector::parse(SELECTOR_CURRENT_DATE).unwrap(),
selector_current_date_alt: Selector::parse(SELECTOR_CURRENT_DATE_ALT).unwrap(),
}
}
}
impl Parser for MapHistoryParser {
type Output = MapHistory;
fn parse(&self, document: &str) -> Result<Self::Output> {
let document = Html::parse_document(document);
let season = select_text(document.root_element(), &self.selector_current_season)
.ok_or(ParseError::ElementNotFound {
selector: SELECTOR_CURRENT_SEASON,
role: "current season number",
})?
.trim_start_matches("Season")
.trim();
let season: u8 = season.parse().map_err(|_| ParseError::InvalidText {
role: "current season number",
text: season.to_string(),
})?;
let current_weeks = document
.select(&self.selector_current_row)
.map(|row| {
let week = select_text(row, &self.selector_current_week).ok_or(
ParseError::ElementNotFound {
selector: SELECTOR_CURRENT_WEEK,
role: "current season week number",
},
)?;
let week: u8 = week.parse().map_err(|_| ParseError::InvalidText {
role: "current season week number",
text: season.to_string(),
})?;
let map = select_text(row, &self.selector_current_map)
.ok_or(ParseError::ElementNotFound {
selector: SELECTOR_CURRENT_MAP,
role: "current season map",
})?
.to_string();
let mut date = select_text(row, &self.selector_current_date)
.ok_or(ParseError::ElementNotFound {
selector: SELECTOR_CURRENT_MAP,
role: "current season map",
})?
.to_string();
let mut alt_date =
select_text(row, &self.selector_current_date_alt).map(String::from);
if let Some(global_date) = alt_date.take() {
alt_date = Some(date);
date = global_date;
}
Ok(CurrentSeasonMap {
week,
map,
date,
na_date: alt_date,
})
})
.collect::<Result<_>>()?;
Ok(MapHistory {
current: CurrentSeasonMapList {
season,
maps: current_weeks,
},
previous: Vec::new(),
})
}
}

View file

@ -4,6 +4,7 @@ use steamid_ng::SteamID;
use time::format_description::FormatItem;
use time::macros::format_description;
mod map_history;
mod match_page;
mod player;
mod player_details;
@ -14,6 +15,7 @@ mod team_matches;
mod team_roster_history;
mod transactions;
pub use map_history::*;
pub use match_page::*;
pub use player::*;
pub use player_details::*;

3060
tests/data/map_2v2.html Normal file

File diff suppressed because it is too large Load diff

4590
tests/data/map_4v4.html Normal file

File diff suppressed because it is too large Load diff

5888
tests/data/map_6v6.html Normal file

File diff suppressed because it is too large Load diff

5563
tests/data/map_9v9.html Normal file

File diff suppressed because it is too large Load diff

View file

@ -1,8 +1,8 @@
use insta::assert_json_snapshot;
use std::fs::read_to_string;
use ugc_scraper::parser::{
MatchPageParser, Parser, PlayerDetailsParser, PlayerParser, SeasonsParser, TeamLookupParser,
TeamMatchesParser, TeamParser, TeamRosterHistoryParser, TransactionParser,
MapHistoryParser, MatchPageParser, Parser, PlayerDetailsParser, PlayerParser, SeasonsParser,
TeamLookupParser, TeamMatchesParser, TeamParser, TeamRosterHistoryParser, TransactionParser,
};
#[test]
@ -108,3 +108,35 @@ fn test_parse_transaction_html() {
let parsed = parser.parse(&body).unwrap();
assert_json_snapshot!(parsed);
}
#[test]
fn test_parse_maps_9_html() {
let body = read_to_string("tests/data/map_9v9.html").unwrap();
let parser = MapHistoryParser::new();
let parsed = parser.parse(&body).unwrap();
assert_json_snapshot!(parsed);
}
#[test]
fn test_parse_maps_6_html() {
let body = read_to_string("tests/data/map_6v6.html").unwrap();
let parser = MapHistoryParser::new();
let parsed = parser.parse(&body).unwrap();
assert_json_snapshot!(parsed);
}
#[test]
fn test_parse_maps_4_html() {
let body = read_to_string("tests/data/map_4v4.html").unwrap();
let parser = MapHistoryParser::new();
let parsed = parser.parse(&body).unwrap();
assert_json_snapshot!(parsed);
}
#[test]
fn test_parse_maps_2_html() {
let body = read_to_string("tests/data/map_2v2.html").unwrap();
let parser = MapHistoryParser::new();
let parsed = parser.parse(&body).unwrap();
assert_json_snapshot!(parsed);
}

View file

@ -0,0 +1,78 @@
---
source: tests/snapshot.rs
expression: parsed
---
{
"current": {
"season": 12,
"maps": [
{
"week": 1,
"map": "ultiduo_lookout_b1",
"date": "Tue, Oct 10",
"na_date": null
},
{
"week": 2,
"map": "ultiduo_obsidiian_a10",
"date": "Tue, Oct 17",
"na_date": null
},
{
"week": 3,
"map": "ultiduo_baloo_v2",
"date": "Tue, Oct 24",
"na_date": null
},
{
"week": 5,
"map": "ultiduo_spytech_rc4",
"date": "Tue, Oct 31",
"na_date": null
},
{
"week": 6,
"map": "koth_ultiduo_r_b7",
"date": "Tue, Nov 07",
"na_date": null
},
{
"week": 7,
"map": "ultiduo_furnace_b2",
"date": "Tue, Nov 14",
"na_date": null
},
{
"week": 8,
"map": "ultiduo_gullywash_b2",
"date": "Tue, Nov 21",
"na_date": null
},
{
"week": 9,
"map": "ulti_fira_b2a",
"date": "Tue, Nov 28",
"na_date": null
},
{
"week": 10,
"map": "Best of 3 Maps",
"date": "Tue, Dec 05",
"na_date": null
},
{
"week": 11,
"map": "Best of 3 Maps",
"date": "Tue, Dec 12",
"na_date": null
},
{
"week": 12,
"map": "Best of 3 Maps",
"date": "Tue, Dec 19",
"na_date": null
}
]
},
"previous": []
}

View file

@ -0,0 +1,84 @@
---
source: tests/snapshot.rs
expression: parsed
---
{
"current": {
"season": 30,
"maps": [
{
"week": 1,
"map": "koth_harter_rc1",
"date": "Fri, Oct 13",
"na_date": null
},
{
"week": 2,
"map": "koth_maple_ridge_rc1",
"date": "Fri, Oct 20",
"na_date": null
},
{
"week": 3,
"map": "koth_badlands",
"date": "Fri, Oct 27",
"na_date": null
},
{
"week": 4,
"map": "koth_bagel_rc7",
"date": "Fri, Nov 03",
"na_date": null
},
{
"week": 5,
"map": "koth_jamram_rc1b",
"date": "Fri, Nov 10",
"na_date": null
},
{
"week": 6,
"map": "koth_cornyard_b2",
"date": "Fri, Nov 17",
"na_date": null
},
{
"week": 7,
"map": "koth_product_final",
"date": "Fri, Nov 24",
"na_date": null
},
{
"week": 8,
"map": "cp_warmfrost_rc1",
"date": "Fri, Dec 01",
"na_date": null
},
{
"week": 9,
"map": "Best of 3 Maps",
"date": "Fri, Dec 08",
"na_date": null
},
{
"week": 10,
"map": "Best of 3 Maps",
"date": "Fri, Dec 15",
"na_date": null
},
{
"week": 11,
"map": "Best of 3 Maps",
"date": "Fri, Dec 22",
"na_date": null
},
{
"week": 12,
"map": "Best of 3 Maps",
"date": "Fri, Dec 29",
"na_date": null
}
]
},
"previous": []
}

View file

@ -0,0 +1,84 @@
---
source: tests/snapshot.rs
expression: parsed
---
{
"current": {
"season": 43,
"maps": [
{
"week": 1,
"map": "koth_product_final",
"date": "Wed, Oct 11",
"na_date": null
},
{
"week": 2,
"map": "cp_metalworks_f5",
"date": "Wed, Oct 18",
"na_date": null
},
{
"week": 3,
"map": "cp_snakewater_final1",
"date": "Wed, Oct 25",
"na_date": null
},
{
"week": 4,
"map": "cp_process_f12",
"date": "Wed, Nov 01",
"na_date": null
},
{
"week": 5,
"map": "cp_gullywash_f9",
"date": "Wed, Nov 08",
"na_date": null
},
{
"week": 6,
"map": "cp_sunshine",
"date": "Wed, Nov 15",
"na_date": null
},
{
"week": 7,
"map": "koth_clearcut_b16a",
"date": "Wed, Nov 22",
"na_date": null
},
{
"week": 8,
"map": "cp_granary_pro_rc16a",
"date": "Wed, Nov 29",
"na_date": null
},
{
"week": 9,
"map": "Best of 3 Maps",
"date": "Wed, Dec 06",
"na_date": null
},
{
"week": 10,
"map": "Best of 3 Maps",
"date": "Wed, Dec 13",
"na_date": null
},
{
"week": 11,
"map": "Best of 3 Maps",
"date": "Wed, Dec 20",
"na_date": null
},
{
"week": 12,
"map": "Best of 3 Maps",
"date": "Wed, Dec 27",
"na_date": null
}
]
},
"previous": []
}

View file

@ -0,0 +1,78 @@
---
source: tests/snapshot.rs
expression: parsed
---
{
"current": {
"season": 41,
"maps": [
{
"week": 1,
"map": "koth_ashville_final",
"date": "Mon, Oct 09",
"na_date": "Sun, Oct 08"
},
{
"week": 2,
"map": "pl_upward_f10",
"date": "Mon, Oct 16",
"na_date": "Sun, Oct 15"
},
{
"week": 3,
"map": "koth_proot_b6b",
"date": "Mon, Oct 23",
"na_date": "Sun, Oct 22"
},
{
"week": 4,
"map": "pl_swiftwater_final1",
"date": "Mon, Oct 30",
"na_date": "Sun, Oct 29"
},
{
"week": 5,
"map": "koth_product_final",
"date": "Mon, Nov 06",
"na_date": "Sun, Nov 05"
},
{
"week": 6,
"map": "pl_vigil_rc10",
"date": "Mon, Nov 13",
"na_date": "Sun, Nov 12"
},
{
"week": 7,
"map": "koth_warmtic_f10",
"date": "Mon, Nov 20",
"na_date": "Sun, Nov 19"
},
{
"week": 8,
"map": "cp_steel_f12",
"date": "Mon, Nov 27",
"na_date": "Sun, Nov 26"
},
{
"week": 9,
"map": "Best of 3 Maps",
"date": "Mon, Dec 04",
"na_date": "Sun, Dec 03"
},
{
"week": 10,
"map": "Best of 3 Maps",
"date": "Mon, Dec 11",
"na_date": "Sun, Dec 10"
},
{
"week": 11,
"map": "Best of 3 Maps",
"date": "Mon, Dec 18",
"na_date": "Sun, Dec 17"
}
]
},
"previous": []
}