mirror of
https://codeberg.org/icewind/ugc-scaper.git
synced 2026-06-03 18:24:10 +02:00
map history current season
This commit is contained in:
parent
7f85e52c71
commit
02ba1b2b9f
13 changed files with 19626 additions and 6 deletions
38
src/data.rs
38
src/data.rs
|
|
@ -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,
|
||||
}
|
||||
|
|
|
|||
19
src/lib.rs
19
src/lib.rs
|
|
@ -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
112
src/parser/map_history.rs
Normal 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(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -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
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
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
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
5563
tests/data/map_9v9.html
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
78
tests/snapshots/snapshot__parse_maps_2_html.snap
Normal file
78
tests/snapshots/snapshot__parse_maps_2_html.snap
Normal 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": []
|
||||
}
|
||||
84
tests/snapshots/snapshot__parse_maps_4_html.snap
Normal file
84
tests/snapshots/snapshot__parse_maps_4_html.snap
Normal 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": []
|
||||
}
|
||||
84
tests/snapshots/snapshot__parse_maps_6_html.snap
Normal file
84
tests/snapshots/snapshot__parse_maps_6_html.snap
Normal 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": []
|
||||
}
|
||||
78
tests/snapshots/snapshot__parse_maps_9_html.snap
Normal file
78
tests/snapshots/snapshot__parse_maps_9_html.snap
Normal 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": []
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue