mirror of
https://codeberg.org/icewind/ugc-scaper.git
synced 2026-06-03 18:24:10 +02:00
map history
This commit is contained in:
parent
b70f2ee336
commit
d4311dc9df
10 changed files with 377 additions and 19 deletions
|
|
@ -6,8 +6,8 @@ use std::str::FromStr;
|
|||
use thiserror::Error;
|
||||
use tokio_stream::Stream;
|
||||
use ugc_scraper_types::{
|
||||
GameMode, MatchInfo, Membership, MembershipRole, NameChange, Record, Region, RosterHistory,
|
||||
Team,
|
||||
Class, GameMode, MapHistory, MatchInfo, Membership, MembershipRole, NameChange, Player, Record,
|
||||
Region, RosterHistory, SteamID, Team,
|
||||
};
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
|
|
@ -21,6 +21,8 @@ pub enum ArchiveError {
|
|||
description: &'static str,
|
||||
error: sqlx::Error,
|
||||
},
|
||||
#[error("Error while parsing dates for {format}")]
|
||||
DateFormat { format: GameMode },
|
||||
}
|
||||
|
||||
pub struct Archive {
|
||||
|
|
@ -200,6 +202,38 @@ impl Archive {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn get_players_ids(
|
||||
&self,
|
||||
min: SteamID,
|
||||
) -> impl Stream<Item = Result<SteamID, ArchiveError>> + use<'_> {
|
||||
query!(
|
||||
"select distinct steam_id from membership_history where steam_id > $1 order by steam_id asc",
|
||||
u64::from(min) as i64,
|
||||
)
|
||||
.fetch(&self.pool)
|
||||
.map_err(|error| ArchiveError::Query {
|
||||
description: "getting player steam ids",
|
||||
error,
|
||||
})
|
||||
.map_ok(|map| (map.steam_id as u64).into())
|
||||
}
|
||||
|
||||
pub async fn get_max_player(&self) -> Result<SteamID, ArchiveError> {
|
||||
if let Some(row) =
|
||||
query!("select steam_id as max from players order by steam_id desc limit 1;")
|
||||
.fetch_optional(&self.pool)
|
||||
.await
|
||||
.map_err(|error| ArchiveError::Query {
|
||||
description: "getting latest team membership history",
|
||||
error,
|
||||
})?
|
||||
{
|
||||
Ok((row.max as u64).into())
|
||||
} else {
|
||||
Ok(0.into())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_no_region_teams(&self) -> impl Stream<Item = Result<u32, ArchiveError>> + use<'_> {
|
||||
query!("select id from teams where region IS NULL and format != 'eights' order by id desc")
|
||||
.fetch(&self.pool)
|
||||
|
|
@ -345,4 +379,109 @@ impl Archive {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn store_player(&self, player: Player) -> Result<(), ArchiveError> {
|
||||
let mut transaction = self
|
||||
.pool
|
||||
.begin()
|
||||
.await
|
||||
.map_err(|error| ArchiveError::Query {
|
||||
description: "beginning player transaction",
|
||||
error,
|
||||
})?;
|
||||
|
||||
query!(
|
||||
"INSERT INTO players (
|
||||
steam_id, name, avatar, favorite_classes, country
|
||||
) VALUES ($1, $2, $3, $4, $5)",
|
||||
u64::from(player.steam_id) as i64,
|
||||
player.name,
|
||||
player.avatar,
|
||||
player.favorite_classes as Vec<Class>,
|
||||
player.country,
|
||||
)
|
||||
.execute(&mut *transaction)
|
||||
.await
|
||||
.map_err(|error| ArchiveError::Query {
|
||||
description: "inserting player",
|
||||
error,
|
||||
})?;
|
||||
|
||||
for honors in player.honors.iter() {
|
||||
query!(
|
||||
"INSERT INTO player_honors (
|
||||
steam_id, team_id, season, division, format
|
||||
) VALUES ($1, $2, $3, $4, $5)",
|
||||
u64::from(player.steam_id) as i64,
|
||||
honors.team.id as i32,
|
||||
honors.season as i16,
|
||||
honors.division,
|
||||
honors.format as GameMode,
|
||||
)
|
||||
.execute(&mut *transaction)
|
||||
.await
|
||||
.map_err(|error| ArchiveError::Query {
|
||||
description: "inserting player honors",
|
||||
error,
|
||||
})?;
|
||||
}
|
||||
|
||||
transaction
|
||||
.commit()
|
||||
.await
|
||||
.map_err(|error| ArchiveError::Query {
|
||||
description: "commiting player transaction",
|
||||
error,
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn store_map_history(
|
||||
&self,
|
||||
format: GameMode,
|
||||
maps: &MapHistory,
|
||||
) -> Result<(), ArchiveError> {
|
||||
let mut transaction = self
|
||||
.pool
|
||||
.begin()
|
||||
.await
|
||||
.map_err(|error| ArchiveError::Query {
|
||||
description: "beginning map history transaction",
|
||||
error,
|
||||
})?;
|
||||
|
||||
// who knows, the website doesn't say
|
||||
let current_season_year = 2024;
|
||||
|
||||
for week in maps.weeks(current_season_year) {
|
||||
let week = week.map_err(|_| ArchiveError::DateFormat { format })?;
|
||||
query!(
|
||||
"INSERT INTO maps (
|
||||
format, season, week, date, map
|
||||
) VALUES ($1, $2, $3, $4, $5)",
|
||||
format as GameMode,
|
||||
week.season as i32,
|
||||
week.week as i32,
|
||||
week.date,
|
||||
week.map,
|
||||
)
|
||||
.execute(&mut *transaction)
|
||||
.await
|
||||
.map_err(|error| ArchiveError::Query {
|
||||
description: "inserting map history",
|
||||
error,
|
||||
})?;
|
||||
}
|
||||
|
||||
transaction
|
||||
.commit()
|
||||
.await
|
||||
.map_err(|error| ArchiveError::Query {
|
||||
description: "commiting map history transaction",
|
||||
error,
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use reqwest::{Client, ClientBuilder, Error, Response, StatusCode};
|
|||
use serde::de::DeserializeOwned;
|
||||
use thiserror::Error;
|
||||
use ugc_scraper_types::{
|
||||
GameMode, MapHistory, MatchInfo, MembershipHistory, Player, RosterHistory, Team,
|
||||
GameMode, MapHistory, MatchInfo, MembershipHistory, Player, RosterHistory, SteamID, Team,
|
||||
TeamRosterData, TeamSeasonMatch, Transaction,
|
||||
};
|
||||
|
||||
|
|
@ -65,11 +65,14 @@ impl UgcClient {
|
|||
self.send_request(Endpoint::TeamMatches { id }).await
|
||||
}
|
||||
|
||||
pub async fn get_player(&self, id: u32) -> Result<Player, UgcClientError> {
|
||||
pub async fn get_player(&self, id: SteamID) -> Result<Player, UgcClientError> {
|
||||
self.send_request(Endpoint::Player { id }).await
|
||||
}
|
||||
|
||||
pub async fn get_player_history(&self, id: u32) -> Result<MembershipHistory, UgcClientError> {
|
||||
pub async fn get_player_history(
|
||||
&self,
|
||||
id: SteamID,
|
||||
) -> Result<MembershipHistory, UgcClientError> {
|
||||
self.send_request(Endpoint::PlayerHistory { id }).await
|
||||
}
|
||||
|
||||
|
|
@ -88,8 +91,8 @@ impl UgcClient {
|
|||
#[derive(Debug, Copy, Clone)]
|
||||
pub enum Endpoint {
|
||||
Match { id: u32 },
|
||||
Player { id: u32 },
|
||||
PlayerHistory { id: u32 },
|
||||
Player { id: SteamID },
|
||||
PlayerHistory { id: SteamID },
|
||||
Transactions { format: GameMode },
|
||||
Team { id: u32 },
|
||||
TeamRoster { id: u32 },
|
||||
|
|
@ -101,8 +104,10 @@ impl Endpoint {
|
|||
pub fn build_url(&self, api_url: &str) -> String {
|
||||
match self {
|
||||
Endpoint::Match { id } => format!("{}/match/{id}", api_url),
|
||||
Endpoint::Player { id } => format!("{}/player/{id}", api_url),
|
||||
Endpoint::PlayerHistory { id } => format!("{}/player/{id}/history", api_url),
|
||||
Endpoint::Player { id } => format!("{}/player/{}", api_url, u64::from(*id)),
|
||||
Endpoint::PlayerHistory { id } => {
|
||||
format!("{}/player/{}/history", api_url, u64::from(*id))
|
||||
}
|
||||
Endpoint::Transactions { format } => format!("{}/transactions/{format}", api_url),
|
||||
Endpoint::Team { id } => format!("{}/team/{id}", api_url),
|
||||
Endpoint::TeamRoster { id } => format!("{}/team/{id}/roster", api_url),
|
||||
|
|
|
|||
|
|
@ -9,10 +9,12 @@ use clap::{Parser, Subcommand};
|
|||
use main_error::MainResult;
|
||||
use std::path::PathBuf;
|
||||
use std::pin::pin;
|
||||
use std::str::FromStr;
|
||||
use std::time::Duration;
|
||||
use tokio::time::sleep;
|
||||
use tokio_stream::StreamExt;
|
||||
use tracing::{error, info, span, warn, Level};
|
||||
use ugc_scraper_types::GameMode;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
struct Args {
|
||||
|
|
@ -25,9 +27,11 @@ struct Args {
|
|||
#[derive(Debug, Subcommand)]
|
||||
enum Command {
|
||||
Matches,
|
||||
Players,
|
||||
Teams,
|
||||
FixupTeams,
|
||||
MembershipHistory,
|
||||
MapHistory { format: String },
|
||||
}
|
||||
|
||||
const LAST_MATCH: u32 = 117047;
|
||||
|
|
@ -54,6 +58,13 @@ async fn main() -> MainResult {
|
|||
Command::MembershipHistory => {
|
||||
archive_team_roster_history(&client, &archive).await?;
|
||||
}
|
||||
Command::Players => {
|
||||
archive_players(&client, &archive).await?;
|
||||
}
|
||||
Command::MapHistory { format } => {
|
||||
let format = GameMode::from_str(&format)?;
|
||||
archive_map_history(&client, &archive, format).await?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -162,6 +173,43 @@ async fn fixup_teams(client: &UgcClient, archive: &Archive) -> MainResult {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
async fn archive_players(client: &UgcClient, archive: &Archive) -> MainResult {
|
||||
let last = archive.get_max_player().await?;
|
||||
let mut ids = pin!(archive.get_players_ids(last));
|
||||
|
||||
while let Some(Ok(steam_id)) = ids.next().await {
|
||||
let _span = span!(
|
||||
Level::INFO,
|
||||
"archive_player",
|
||||
steam_id = u64::from(steam_id)
|
||||
)
|
||||
.entered();
|
||||
match client.get_player(steam_id).await.check_not_found() {
|
||||
Ok(Some(player)) => {
|
||||
info!("storing player");
|
||||
archive.store_player(player).await?;
|
||||
// panic!();
|
||||
}
|
||||
Ok(None) => {
|
||||
warn!("player not found");
|
||||
}
|
||||
Err(e) => {
|
||||
error!("error fetching player: {:?}", e);
|
||||
panic!();
|
||||
}
|
||||
}
|
||||
sleep(Duration::from_millis(500)).await;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn archive_map_history(client: &UgcClient, archive: &Archive, mode: GameMode) -> MainResult {
|
||||
let history = client.get_maps(mode).await?;
|
||||
archive.store_map_history(mode, &history).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
trait NotFoundResultExt<T>: Sized {
|
||||
fn check_not_found(self) -> Result<Option<T>, UgcClientError>;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue