mirror of
https://codeberg.org/icewind/ugc-scaper.git
synced 2026-06-03 10:14:11 +02:00
updates
This commit is contained in:
parent
5a6d7006e0
commit
fb383652c8
22 changed files with 3161 additions and 3071 deletions
1897
Cargo.lock
generated
1897
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
14
Cargo.toml
14
Cargo.toml
|
|
@ -1,20 +1,20 @@
|
||||||
[package]
|
[package]
|
||||||
name = "ugc-scraper"
|
name = "ugc-scraper"
|
||||||
version = "0.5.0"
|
version = "0.6.0"
|
||||||
edition = "2021"
|
edition = "2024"
|
||||||
rust-version = "1.71.1"
|
rust-version = "1.88.0"
|
||||||
description = "Scraper for ugcleague.com"
|
description = "Scraper for ugcleague.com"
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
homepage = "https://codeberg.org/icewind/ugc-scaper"
|
homepage = "https://codeberg.org/icewind/ugc-scaper"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
tokio = "1.44.2"
|
tokio = "1.44.2"
|
||||||
reqwest = "0.12.15"
|
reqwest = "0.13.2"
|
||||||
scraper = "0.23.1"
|
scraper = "0.26.0"
|
||||||
thiserror = "2.0.3"
|
thiserror = "2.0.3"
|
||||||
time = { version = "0.3.41", features = ["parsing", "macros"] }
|
time = { version = "0.3.41", features = ["parsing", "macros"] }
|
||||||
steamid-ng = "1.0.0"
|
steamid-ng = "3.0.0"
|
||||||
ugc-scraper-types = { version = "0.2.0", path = "./types" }
|
ugc-scraper-types = { version = "0.3.0", path = "./types" }
|
||||||
regex = "1.11.1"
|
regex = "1.11.1"
|
||||||
tracing = "0.1.41"
|
tracing = "0.1.41"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ use ugc_scraper::{Result, SteamID, UgcClient};
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<()> {
|
async fn main() -> Result<()> {
|
||||||
let client = UgcClient::new();
|
let client = UgcClient::new();
|
||||||
let id = SteamID::from(76561198024494988);
|
let id = SteamID::from_steam64(76561198024494988).unwrap();
|
||||||
let player = client.player(id).await?;
|
let player = client.player(id).await?;
|
||||||
println!("{}", player.name);
|
println!("{}", player.name);
|
||||||
for team in player.teams {
|
for team in player.teams {
|
||||||
|
|
|
||||||
1692
api-server/Cargo.lock
generated
1692
api-server/Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -1,15 +1,16 @@
|
||||||
[package]
|
[package]
|
||||||
name = "ugc-api-server"
|
name = "ugc-api-server"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2024"
|
||||||
|
rust-version = "1.88.0"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
tokio = { version = "1.44.2", features = ["macros", "rt-multi-thread", "rt", "signal"] }
|
tokio = { version = "1.44.2", features = ["macros", "rt-multi-thread", "rt", "signal"] }
|
||||||
main_error = "0.1.2"
|
main_error = "0.1.2"
|
||||||
ugc-scraper = { version = "*", path = ".." }
|
ugc-scraper = { version = "*", path = ".." }
|
||||||
#ugc-scraper = "0.5.0"
|
#ugc-scraper = "0.6.0"
|
||||||
axum = "0.8.3"
|
axum = "0.8.3"
|
||||||
steamid-ng = "1.0.0"
|
steamid-ng = "3.0.0"
|
||||||
thiserror = "2.0.12"
|
thiserror = "2.0.12"
|
||||||
tracing = "0.1.41"
|
tracing = "0.1.41"
|
||||||
tracing-subscriber = { version = "0.3.19", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3.19", features = ["env-filter"] }
|
||||||
|
|
@ -7,7 +7,7 @@ use std::env::var;
|
||||||
use std::net::Ipv4Addr;
|
use std::net::Ipv4Addr;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use steamid_ng::{SteamID, SteamIDError};
|
use steamid_ng::{SteamID, SteamIDParseError};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use tokio::net::TcpListener;
|
use tokio::net::TcpListener;
|
||||||
use tokio::signal;
|
use tokio::signal;
|
||||||
|
|
@ -24,7 +24,7 @@ struct AppState {
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
enum ApiError {
|
enum ApiError {
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
SteamId(#[from] SteamIDError),
|
SteamId(#[from] SteamIDParseError),
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
Scrape(#[from] ScrapeError),
|
Scrape(#[from] ScrapeError),
|
||||||
#[error("malformed request")]
|
#[error("malformed request")]
|
||||||
|
|
@ -92,7 +92,7 @@ async fn player(
|
||||||
Path(id): Path<String>,
|
Path(id): Path<String>,
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
) -> Result<impl IntoResponse, ApiError> {
|
) -> Result<impl IntoResponse, ApiError> {
|
||||||
let steam_id = SteamID::try_from(id.as_str())?;
|
let steam_id = SteamID::from_str(id.as_str())?;
|
||||||
debug!(player = steam_id.steam3(), "requesting player");
|
debug!(player = steam_id.steam3(), "requesting player");
|
||||||
let response = state.client.player(steam_id).await?;
|
let response = state.client.player(steam_id).await?;
|
||||||
Ok(Json(response))
|
Ok(Json(response))
|
||||||
|
|
@ -103,7 +103,7 @@ async fn player_history(
|
||||||
Path(id): Path<String>,
|
Path(id): Path<String>,
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
) -> Result<impl IntoResponse, ApiError> {
|
) -> Result<impl IntoResponse, ApiError> {
|
||||||
let steam_id = SteamID::try_from(id.as_str())?;
|
let steam_id = SteamID::from_str(id.as_str())?;
|
||||||
debug!(player = steam_id.steam3(), "requesting player history");
|
debug!(player = steam_id.steam3(), "requesting player history");
|
||||||
let response = state.client.player_team_history(steam_id).await?;
|
let response = state.client.player_team_history(steam_id).await?;
|
||||||
Ok(Json(response))
|
Ok(Json(response))
|
||||||
|
|
|
||||||
1761
archiver/Cargo.lock
generated
1761
archiver/Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -1,18 +1,18 @@
|
||||||
[package]
|
[package]
|
||||||
name = "archiver"
|
name = "archiver"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2024"
|
||||||
|
rust-version = "1.88.0"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
# ugc-scraper-types = "0.1.2"
|
ugc-scraper-types = { version = "0.3.0", path = "../types", features = ["sqlx"] }
|
||||||
ugc-scraper-types = { version = "0.2.0", path = "../types", features = ["sqlx"] }
|
reqwest = { version = "0.13.2", features = ["json"] }
|
||||||
reqwest = { version = "0.12.15", features = ["json"] }
|
|
||||||
clap = { version = "4.5.35", features = ["derive"] }
|
clap = { version = "4.5.35", features = ["derive"] }
|
||||||
tracing = "0.1.41"
|
tracing = "0.1.41"
|
||||||
tracing-subscriber = "0.3.19"
|
tracing-subscriber = "0.3.19"
|
||||||
serde = { version = "1.0.219", features = ["derive"] }
|
serde = { version = "1.0.219", features = ["derive"] }
|
||||||
serde_json = "1.0.140"
|
serde_json = "1.0.140"
|
||||||
toml = "0.8.20"
|
toml = "1.1.0"
|
||||||
secretfile = "0.1.0"
|
secretfile = "0.1.0"
|
||||||
tokio = { version = "1.44.2", features = ["macros", "rt-multi-thread"] }
|
tokio = { version = "1.44.2", features = ["macros", "rt-multi-thread"] }
|
||||||
sqlx = { version = "0.8.3", features = ["postgres", "runtime-tokio", "time"] }
|
sqlx = { version = "0.8.3", features = ["postgres", "runtime-tokio", "time"] }
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,20 @@
|
||||||
|
use crate::steam_id_from_u64;
|
||||||
use futures_util::stream::TryStreamExt;
|
use futures_util::stream::TryStreamExt;
|
||||||
use serde::{Serialize, Serializer};
|
use serde::{Serialize, Serializer};
|
||||||
use sqlx::postgres::PgConnectOptions;
|
use sqlx::postgres::PgConnectOptions;
|
||||||
use sqlx::{query, query_as, Error, Executor, PgPool, Postgres};
|
use sqlx::{Error, Executor, PgPool, Postgres, query, query_as};
|
||||||
use std::ops::Range;
|
use std::ops::Range;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
use time::Date;
|
||||||
use time::format_description::FormatItem;
|
use time::format_description::FormatItem;
|
||||||
use time::macros::format_description;
|
use time::macros::format_description;
|
||||||
use time::parsing::Parsed;
|
use time::parsing::Parsed;
|
||||||
use time::Date;
|
|
||||||
use tokio_stream::Stream;
|
use tokio_stream::Stream;
|
||||||
use tracing::{debug, error, warn};
|
use tracing::{debug, error, warn};
|
||||||
use ugc_scraper_types::{
|
use ugc_scraper_types::{
|
||||||
serde_steam_id_as_string, Class, GameMode, MapHistory, MatchInfo, Membership, MembershipRole,
|
Class, GameMode, MapHistory, MatchInfo, Membership, MembershipRole, NameChange, Player, Record,
|
||||||
NameChange, Player, Record, Region, RosterHistory, SteamID, Team, TeamRef, TeamSeason,
|
Region, RosterHistory, SteamID, Team, TeamRef, TeamSeason, serde_steam_id_as_string,
|
||||||
};
|
};
|
||||||
|
|
||||||
const MATCH_DATE_FORMAT: &[FormatItem<'static>] = format_description!(
|
const MATCH_DATE_FORMAT: &[FormatItem<'static>] = format_description!(
|
||||||
|
|
@ -251,7 +252,7 @@ impl Archive {
|
||||||
description: "getting player steam ids",
|
description: "getting player steam ids",
|
||||||
error,
|
error,
|
||||||
})
|
})
|
||||||
.map_ok(|map| (map.steam_id as u64).into())
|
.map_ok(|map| steam_id_from_u64(map.steam_id as u64))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_max_player(&self) -> Result<SteamID, ArchiveError> {
|
pub async fn get_max_player(&self) -> Result<SteamID, ArchiveError> {
|
||||||
|
|
@ -264,9 +265,9 @@ impl Archive {
|
||||||
error,
|
error,
|
||||||
})?
|
})?
|
||||||
{
|
{
|
||||||
Ok((row.max as u64).into())
|
Ok(steam_id_from_u64(row.max as u64))
|
||||||
} else {
|
} else {
|
||||||
Ok(0.into())
|
Ok(steam_id_from_u64(0))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -850,7 +851,7 @@ pub struct PlayerData {
|
||||||
impl From<PlayerDataRaw> for PlayerData {
|
impl From<PlayerDataRaw> for PlayerData {
|
||||||
fn from(player: PlayerDataRaw) -> Self {
|
fn from(player: PlayerDataRaw) -> Self {
|
||||||
PlayerData {
|
PlayerData {
|
||||||
steam_id: (player.steam_id as u64).into(),
|
steam_id: steam_id_from_u64(player.steam_id as u64),
|
||||||
name: player.name,
|
name: player.name,
|
||||||
avatar: player.avatar,
|
avatar: player.avatar,
|
||||||
country: player.country,
|
country: player.country,
|
||||||
|
|
@ -897,7 +898,7 @@ impl From<MembershipDataRaw> for MembershipData {
|
||||||
fn from(membership: MembershipDataRaw) -> Self {
|
fn from(membership: MembershipDataRaw) -> Self {
|
||||||
MembershipData {
|
MembershipData {
|
||||||
team_id: membership.team_id,
|
team_id: membership.team_id,
|
||||||
steam_id: (membership.steam_id as u64).into(),
|
steam_id: steam_id_from_u64(membership.steam_id as u64),
|
||||||
role: membership.role,
|
role: membership.role,
|
||||||
joined: membership.joined,
|
joined: membership.joined,
|
||||||
left: membership.left,
|
left: membership.left,
|
||||||
|
|
@ -930,7 +931,9 @@ fn parse_match_date(date: &str, year: i32) -> Date {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_old_match_date(date: &str) -> Result<Date, time::Error> {
|
fn parse_old_match_date(date: &str) -> Result<Date, time::Error> {
|
||||||
const MATCH_DATE_FORMAT_OLD: &[FormatItem<'static>] = format_description!("[weekday case_sensitive:false repr:short], [month padding:none]/[day padding:none]/[year repr:last_two]");
|
const MATCH_DATE_FORMAT_OLD: &[FormatItem<'static>] = format_description!(
|
||||||
|
"[weekday case_sensitive:false repr:short], [month padding:none]/[day padding:none]/[year repr:last_two]"
|
||||||
|
);
|
||||||
let mut parsed = Parsed::new();
|
let mut parsed = Parsed::new();
|
||||||
parsed.parse_items(date.as_bytes(), MATCH_DATE_FORMAT_OLD)?;
|
parsed.parse_items(date.as_bytes(), MATCH_DATE_FORMAT_OLD)?;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,8 +17,8 @@ use std::str::FromStr;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use tokio::time::sleep;
|
use tokio::time::sleep;
|
||||||
use tokio_stream::StreamExt;
|
use tokio_stream::StreamExt;
|
||||||
use tracing::{error, info, instrument, span, warn, Level};
|
use tracing::{Level, error, info, instrument, span, warn};
|
||||||
use ugc_scraper_types::GameMode;
|
use ugc_scraper_types::{GameMode, SteamID};
|
||||||
|
|
||||||
#[derive(Debug, Parser)]
|
#[derive(Debug, Parser)]
|
||||||
struct Args {
|
struct Args {
|
||||||
|
|
@ -258,8 +258,9 @@ async fn fixup_matches(client: &UgcClient, archive: &Archive) -> MainResult {
|
||||||
|
|
||||||
for season in matches.seasons.iter() {
|
for season in matches.seasons.iter() {
|
||||||
for season_match in season.matches.iter() {
|
for season_match in season.matches.iter() {
|
||||||
if let Some(match_id) = season_match.result.match_id() {
|
if let Some(match_id) = season_match.result.match_id()
|
||||||
if !archive.has_match(match_id).await? {
|
&& !archive.has_match(match_id).await?
|
||||||
|
{
|
||||||
warn!(match_id, "match not archived yet");
|
warn!(match_id, "match not archived yet");
|
||||||
sleep(Duration::from_millis(500)).await;
|
sleep(Duration::from_millis(500)).await;
|
||||||
if archive_match(client, archive, match_id).await.is_err() {
|
if archive_match(client, archive, match_id).await.is_err() {
|
||||||
|
|
@ -272,7 +273,6 @@ async fn fixup_matches(client: &UgcClient, archive: &Archive) -> MainResult {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
archive
|
archive
|
||||||
.update_match_details_from_team_matches(&matches.team, format, season)
|
.update_match_details_from_team_matches(&matches.team, format, season)
|
||||||
|
|
@ -388,3 +388,7 @@ async fn dump_data(archive: &Archive, data: Data, output: &str) -> MainResult {
|
||||||
writeln!(&mut output, "\n]")?;
|
writeln!(&mut output, "\n]")?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn steam_id_from_u64(id: u64) -> SteamID {
|
||||||
|
SteamID::from_steam64(id).unwrap_or_else(|_| SteamID::from_steam64(0).unwrap())
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
use main_error::MainResult;
|
use main_error::MainResult;
|
||||||
use std::env::args;
|
use std::env::args;
|
||||||
|
use std::str::FromStr;
|
||||||
use steamid_ng::SteamID;
|
use steamid_ng::SteamID;
|
||||||
use ugc_scraper::UgcClient;
|
use ugc_scraper::UgcClient;
|
||||||
|
|
||||||
|
|
@ -7,7 +8,7 @@ use ugc_scraper::UgcClient;
|
||||||
async fn main() -> MainResult {
|
async fn main() -> MainResult {
|
||||||
let client = UgcClient::new();
|
let client = UgcClient::new();
|
||||||
let id = args().nth(1).expect("no steam id provided");
|
let id = args().nth(1).expect("no steam id provided");
|
||||||
let id = SteamID::try_from(id.as_str()).expect("invalid steam id provided");
|
let id = SteamID::from_str(id.as_str()).expect("invalid steam id provided");
|
||||||
let player = client.player(id).await?;
|
let player = client.player(id).await?;
|
||||||
println!("{}", player.name);
|
println!("{}", player.name);
|
||||||
for team in player.teams {
|
for team in player.teams {
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ use ugc_scraper::{Result, SteamID, UgcClient};
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<()> {
|
async fn main() -> Result<()> {
|
||||||
let client = UgcClient::new();
|
let client = UgcClient::new();
|
||||||
let id = SteamID::from(76561198024494988);
|
let id = SteamID::from_steam64(76561198024494988).unwrap();
|
||||||
let player = client.player(id).await?;
|
let player = client.player(id).await?;
|
||||||
println!("{}", player.name);
|
println!("{}", player.name);
|
||||||
for team in player.teams {
|
for team in player.teams {
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,8 @@
|
||||||
}: let
|
}: let
|
||||||
inherit (lib.sources) sourceByRegex;
|
inherit (lib.sources) sourceByRegex;
|
||||||
inherit (builtins) fromTOML readFile;
|
inherit (builtins) fromTOML readFile;
|
||||||
src = sourceByRegex ./. ["Cargo.*" "((types|archiver|)/?(src|.sqlx)?)(/.*)?" "README.md"];
|
src = sourceByRegex ../. ["Cargo.*" "((types|archiver|)/?(src|.sqlx)?)(/.*)?" "README.md"];
|
||||||
version = (fromTOML (readFile archiver/Cargo.toml)).package.version;
|
version = (fromTOML (readFile ../archiver/Cargo.toml)).package.version;
|
||||||
in
|
in
|
||||||
rustPlatform.buildRustPackage rec {
|
rustPlatform.buildRustPackage rec {
|
||||||
pname = "ugc-api-archiver";
|
pname = "ugc-api-archiver";
|
||||||
|
|
@ -28,6 +28,6 @@ in
|
||||||
doCheck = false;
|
doCheck = false;
|
||||||
|
|
||||||
cargoLock = {
|
cargoLock = {
|
||||||
lockFile = ./archiver/Cargo.lock;
|
lockFile = ../archiver/Cargo.lock;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -2,7 +2,7 @@ use super::Parser;
|
||||||
use crate::data::{
|
use crate::data::{
|
||||||
CurrentSeasonMap, CurrentSeasonMapList, MapHistory, PreviousSeasonMap, PreviousSeasonMapList,
|
CurrentSeasonMap, CurrentSeasonMapList, MapHistory, PreviousSeasonMap, PreviousSeasonMapList,
|
||||||
};
|
};
|
||||||
use crate::parser::{select_text, ElementExt};
|
use crate::parser::{ElementExt, select_text};
|
||||||
use crate::{ParseError, Result};
|
use crate::{ParseError, Result};
|
||||||
use scraper::{Html, Selector};
|
use scraper::{Html, Selector};
|
||||||
use time::{Date, Month};
|
use time::{Date, Month};
|
||||||
|
|
@ -141,8 +141,8 @@ impl Parser for MapHistoryParser {
|
||||||
.filter(|child| child.value().is_element())
|
.filter(|child| child.value().is_element())
|
||||||
.count()
|
.count()
|
||||||
== 3
|
== 3
|
||||||
|
&& let Some(season) = prev_season.as_mut()
|
||||||
{
|
{
|
||||||
if let Some(season) = prev_season.as_mut() {
|
|
||||||
let week = select_text(row, &self.selector_previous_week).ok_or(
|
let week = select_text(row, &self.selector_previous_week).ok_or(
|
||||||
ParseError::ElementNotFound {
|
ParseError::ElementNotFound {
|
||||||
selector: SELECTOR_PREVIOUS_WEEK,
|
selector: SELECTOR_PREVIOUS_WEEK,
|
||||||
|
|
@ -168,7 +168,6 @@ impl Parser for MapHistoryParser {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if let Some(season) = prev_season {
|
if let Some(season) = prev_season {
|
||||||
previous.push(season);
|
previous.push(season);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,12 +35,17 @@ pub trait Parser {
|
||||||
|
|
||||||
trait ElementExt<'a> {
|
trait ElementExt<'a> {
|
||||||
fn first_text(&self) -> Option<&'a str>;
|
fn first_text(&self) -> Option<&'a str>;
|
||||||
|
fn last_text(&self) -> Option<&'a str>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> ElementExt<'a> for ElementRef<'a> {
|
impl<'a> ElementExt<'a> for ElementRef<'a> {
|
||||||
fn first_text(&self) -> Option<&'a str> {
|
fn first_text(&self) -> Option<&'a str> {
|
||||||
self.text().map(str::trim).find(|s| !s.is_empty())
|
self.text().map(str::trim).find(|s| !s.is_empty())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn last_text(&self) -> Option<&'a str> {
|
||||||
|
self.text().map(str::trim).filter(|s| !s.is_empty()).last()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn select_text<'a>(el: ElementRef<'a>, selector: &Selector) -> Option<&'a str> {
|
fn select_text<'a>(el: ElementRef<'a>, selector: &Selector) -> Option<&'a str> {
|
||||||
|
|
@ -103,7 +108,12 @@ fn steam_id_from_link(link: &str) -> Result<SteamID, ParseError> {
|
||||||
link: link.to_string(),
|
link: link.to_string(),
|
||||||
role: "user id",
|
role: "user id",
|
||||||
})
|
})
|
||||||
.map(SteamID::from)
|
.and_then(|id| {
|
||||||
|
SteamID::from_steam64(id).map_err(|_| ParseError::InvalidLink {
|
||||||
|
link: link.to_string(),
|
||||||
|
role: "user id",
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
static WHITESPACE_REGEX: OnceLock<Regex> = OnceLock::new();
|
static WHITESPACE_REGEX: OnceLock<Regex> = OnceLock::new();
|
||||||
|
|
|
||||||
|
|
@ -255,7 +255,8 @@ impl Parser for PlayerParser {
|
||||||
Ok(Player {
|
Ok(Player {
|
||||||
name,
|
name,
|
||||||
avatar,
|
avatar,
|
||||||
steam_id: SteamID::try_from(id.as_str()).unwrap_or_default(),
|
steam_id: SteamID::from_str(id.as_str())
|
||||||
|
.unwrap_or_else(|_| SteamID::from_steam64(0).unwrap()),
|
||||||
honors,
|
honors,
|
||||||
teams,
|
teams,
|
||||||
favorite_classes,
|
favorite_classes,
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
use super::{select_text_empty, whitespace_regex, ElementExt, Parser};
|
use super::{ElementExt, Parser, select_text_empty, whitespace_regex};
|
||||||
use crate::data::{Membership, NameChange, Record, Team};
|
use crate::data::{Membership, NameChange, Record, Team};
|
||||||
use crate::parser::{
|
use crate::parser::{
|
||||||
select_text, steam_id_from_link, DATE_FORMAT, MEMBER_DATE_ALT_FORMAT, MEMBER_DATE_FORMAT,
|
DATE_FORMAT, MEMBER_DATE_ALT_FORMAT, MEMBER_DATE_FORMAT, select_text, steam_id_from_link,
|
||||||
};
|
};
|
||||||
use crate::{ParseError, Result, ScrapeError};
|
use crate::{ParseError, Result, ScrapeError};
|
||||||
use scraper::{Html, Selector};
|
use scraper::{Html, Selector};
|
||||||
|
|
@ -183,11 +183,11 @@ impl Parser for TeamParser {
|
||||||
|
|
||||||
let timezone = select_text(root, &self.selector_team_timezone).map(String::from);
|
let timezone = select_text(root, &self.selector_team_timezone).map(String::from);
|
||||||
|
|
||||||
if let Some(timezone) = timezone.as_deref() {
|
if let Some(timezone) = timezone.as_deref()
|
||||||
if region.is_none() {
|
&& region.is_none()
|
||||||
|
{
|
||||||
region = Region::from_str(timezone).ok();
|
region = Region::from_str(timezone).ok();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if region.is_none() {
|
if region.is_none() {
|
||||||
region = format_text
|
region = format_text
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,7 @@ impl Parser for TeamLookupParser {
|
||||||
role: "team link",
|
role: "team link",
|
||||||
selector: SELECTOR_OPTION,
|
selector: SELECTOR_OPTION,
|
||||||
})?;
|
})?;
|
||||||
let text = option.first_text().ok_or(ParseError::EmptyText {
|
let text = option.last_text().ok_or(ParseError::EmptyText {
|
||||||
role: "team name",
|
role: "team name",
|
||||||
selector: SELECTOR_OPTION,
|
selector: SELECTOR_OPTION,
|
||||||
})?;
|
})?;
|
||||||
|
|
|
||||||
|
|
@ -3272,7 +3272,7 @@ expression: parsed
|
||||||
"id": 12121
|
"id": 12121
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "- - Combat Wombats",
|
"name": "- Combat Wombats",
|
||||||
"id": 22319
|
"id": 22319
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -6692,7 +6692,7 @@ expression: parsed
|
||||||
"id": 9512
|
"id": 9512
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "",
|
"name": "P R O M B O Y S",
|
||||||
"id": 9965
|
"id": 9965
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
||||||
753
types/Cargo.lock
generated
753
types/Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -1,14 +1,14 @@
|
||||||
[package]
|
[package]
|
||||||
name = "ugc-scraper-types"
|
name = "ugc-scraper-types"
|
||||||
version = "0.2.0"
|
version = "0.3.0"
|
||||||
edition = "2021"
|
edition = "2024"
|
||||||
rust-version = "1.71.1"
|
rust-version = "1.88.0"
|
||||||
description = "Scraper for ugcleague.com - data types"
|
description = "Scraper for ugcleague.com - data types"
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
homepage = "https://codeberg.org/icewind/ugc-scaper"
|
homepage = "https://codeberg.org/icewind/ugc-scaper"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
steamid-ng = "1.0.0"
|
steamid-ng = "3.0.0"
|
||||||
serde = { version = "1.0.215", features = ["derive"], optional = true }
|
serde = { version = "1.0.215", features = ["derive"], optional = true }
|
||||||
time = { version = "0.3.36", features = ["parsing", "macros"] }
|
time = { version = "0.3.36", features = ["parsing", "macros"] }
|
||||||
thiserror = "2.0.12"
|
thiserror = "2.0.12"
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
use serde::de::Error;
|
use serde::de::Error;
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
pub use steamid_ng::SteamID;
|
pub use steamid_ng::{SteamID, SteamIDParseError};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use time::error::Parse;
|
use time::error::Parse;
|
||||||
use time::format_description::FormatItem;
|
use time::format_description::FormatItem;
|
||||||
|
|
@ -680,8 +680,13 @@ pub struct Week<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MapHistory {
|
impl MapHistory {
|
||||||
pub fn weeks(&self, current_season_year: u16) -> impl Iterator<Item = Result<Week, Parse>> {
|
pub fn weeks<'a>(
|
||||||
const CURRENT_DATE_FORMAT: &[FormatItem<'static>] = format_description!("[weekday case_sensitive:false repr:short], [month repr:short] [day padding:none] [year]");
|
&'a self,
|
||||||
|
current_season_year: u16,
|
||||||
|
) -> impl Iterator<Item = Result<Week<'a>, Parse>> {
|
||||||
|
const CURRENT_DATE_FORMAT: &[FormatItem<'static>] = format_description!(
|
||||||
|
"[weekday case_sensitive:false repr:short], [month repr:short] [day padding:none] [year]"
|
||||||
|
);
|
||||||
|
|
||||||
let current_season = self.current.maps.iter().map(move |map| {
|
let current_season = self.current.maps.iter().map(move |map| {
|
||||||
Ok(Week {
|
Ok(Week {
|
||||||
|
|
@ -743,7 +748,9 @@ pub struct PreviousSeasonMap {
|
||||||
|
|
||||||
#[cfg(feature = "serde")]
|
#[cfg(feature = "serde")]
|
||||||
pub mod serde_steam_id_as_string {
|
pub mod serde_steam_id_as_string {
|
||||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
use serde::{de::Error, Deserialize, Deserializer, Serialize, Serializer};
|
||||||
|
use std::borrow::Cow;
|
||||||
|
use std::str::FromStr;
|
||||||
use steamid_ng::SteamID;
|
use steamid_ng::SteamID;
|
||||||
|
|
||||||
pub fn serialize<S: Serializer>(steam_id: &SteamID, serializer: S) -> Result<S::Ok, S::Error> {
|
pub fn serialize<S: Serializer>(steam_id: &SteamID, serializer: S) -> Result<S::Ok, S::Error> {
|
||||||
|
|
@ -756,6 +763,7 @@ pub mod serde_steam_id_as_string {
|
||||||
where
|
where
|
||||||
D: Deserializer<'de>,
|
D: Deserializer<'de>,
|
||||||
{
|
{
|
||||||
SteamID::deserialize(deserializer)
|
let s = <Cow<'de, str>>::deserialize(deserializer)?;
|
||||||
|
SteamID::from_str(&s).map_err(D::Error::custom)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue