mirror of
https://codeberg.org/icewind/ugc-scaper.git
synced 2026-06-03 18:24:10 +02:00
historical seasons
This commit is contained in:
parent
43bfba6307
commit
e20a9cdae9
8 changed files with 3064 additions and 3 deletions
17
examples/seasons.rs
Normal file
17
examples/seasons.rs
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
use main_error::MainResult;
|
||||||
|
use ugc_scraper::UgcClient;
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> MainResult {
|
||||||
|
let client = UgcClient::new();
|
||||||
|
let modes = client.previous_seasons().await?;
|
||||||
|
for mode in modes {
|
||||||
|
println!("{}", mode.mode);
|
||||||
|
|
||||||
|
for season in mode.seasons {
|
||||||
|
println!(" {}: {}", season.id, season.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
14
src/data.rs
14
src/data.rs
|
|
@ -173,3 +173,17 @@ pub enum MatchResult {
|
||||||
},
|
},
|
||||||
ByeWeek,
|
ByeWeek,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
|
||||||
|
pub struct Seasons {
|
||||||
|
pub mode: String,
|
||||||
|
pub seasons: Vec<Season>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
|
||||||
|
pub struct Season {
|
||||||
|
pub id: String,
|
||||||
|
pub name: String,
|
||||||
|
}
|
||||||
|
|
|
||||||
18
src/lib.rs
18
src/lib.rs
|
|
@ -3,9 +3,9 @@ mod error;
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub mod parser;
|
pub mod parser;
|
||||||
|
|
||||||
use crate::data::{MembershipHistory, Player, RosterHistory, Team, TeamSeason};
|
use crate::data::{MembershipHistory, Player, RosterHistory, Seasons, Team, TeamSeason};
|
||||||
use crate::parser::{
|
use crate::parser::{
|
||||||
Parser, PlayerDetailsParser, PlayerParser, TeamMatchesParser, TeamParser,
|
Parser, PlayerDetailsParser, PlayerParser, SeasonsParser, TeamMatchesParser, TeamParser,
|
||||||
TeamRosterHistoryParser,
|
TeamRosterHistoryParser,
|
||||||
};
|
};
|
||||||
pub use error::*;
|
pub use error::*;
|
||||||
|
|
@ -23,6 +23,7 @@ pub struct UgcClient {
|
||||||
team_parser: TeamParser,
|
team_parser: TeamParser,
|
||||||
team_roster_history_parser: TeamRosterHistoryParser,
|
team_roster_history_parser: TeamRosterHistoryParser,
|
||||||
team_matches_parser: TeamMatchesParser,
|
team_matches_parser: TeamMatchesParser,
|
||||||
|
seasons_parser: SeasonsParser,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// "API client" for ugc by scraping the website
|
/// "API client" for ugc by scraping the website
|
||||||
|
|
@ -35,6 +36,7 @@ impl UgcClient {
|
||||||
team_parser: TeamParser::new(),
|
team_parser: TeamParser::new(),
|
||||||
team_roster_history_parser: TeamRosterHistoryParser::new(),
|
team_roster_history_parser: TeamRosterHistoryParser::new(),
|
||||||
team_matches_parser: TeamMatchesParser::new(),
|
team_matches_parser: TeamMatchesParser::new(),
|
||||||
|
seasons_parser: SeasonsParser::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -116,4 +118,16 @@ impl UgcClient {
|
||||||
.await?;
|
.await?;
|
||||||
self.team_matches_parser.parse(&body)
|
self.team_matches_parser.parse(&body)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get all historical seasons by game mode
|
||||||
|
pub async fn previous_seasons(&self) -> Result<Vec<Seasons>> {
|
||||||
|
let body = self
|
||||||
|
.client
|
||||||
|
.get("https://www.ugcleague.com")
|
||||||
|
.send()
|
||||||
|
.await?
|
||||||
|
.text()
|
||||||
|
.await?;
|
||||||
|
self.seasons_parser.parse(&body)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,12 +6,14 @@ use time::macros::format_description;
|
||||||
|
|
||||||
mod player;
|
mod player;
|
||||||
mod player_details;
|
mod player_details;
|
||||||
|
mod seasons;
|
||||||
mod team;
|
mod team;
|
||||||
mod team_matches;
|
mod team_matches;
|
||||||
mod team_roster_history;
|
mod team_roster_history;
|
||||||
|
|
||||||
pub use player::*;
|
pub use player::*;
|
||||||
pub use player_details::*;
|
pub use player_details::*;
|
||||||
|
pub use seasons::*;
|
||||||
pub use team::*;
|
pub use team::*;
|
||||||
pub use team_matches::*;
|
pub use team_matches::*;
|
||||||
pub use team_roster_history::*;
|
pub use team_roster_history::*;
|
||||||
|
|
|
||||||
84
src/parser/seasons.rs
Normal file
84
src/parser/seasons.rs
Normal file
|
|
@ -0,0 +1,84 @@
|
||||||
|
use super::Parser;
|
||||||
|
use crate::data::{Season, Seasons};
|
||||||
|
use crate::parser::{select_text, ElementExt};
|
||||||
|
use crate::{ParseError, Result};
|
||||||
|
use scraper::{Html, Selector};
|
||||||
|
|
||||||
|
const SELECTOR_MENU: &str = ".sub-menu";
|
||||||
|
const SELECTOR_NAME: &str = ".mega-menu-sub-title";
|
||||||
|
const SELECTOR_SEASON_LINK: &str = "ul[id$=\"seasons\"] a[href^=\"rankings_\"]";
|
||||||
|
|
||||||
|
pub struct SeasonsParser {
|
||||||
|
selector_menu: Selector,
|
||||||
|
selector_name: Selector,
|
||||||
|
selector_link: Selector,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for SeasonsParser {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SeasonsParser {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
SeasonsParser {
|
||||||
|
selector_menu: Selector::parse(SELECTOR_MENU).unwrap(),
|
||||||
|
selector_name: Selector::parse(SELECTOR_NAME).unwrap(),
|
||||||
|
selector_link: Selector::parse(SELECTOR_SEASON_LINK).unwrap(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parser for SeasonsParser {
|
||||||
|
type Output = Vec<Seasons>;
|
||||||
|
|
||||||
|
fn parse(&self, document: &str) -> Result<Self::Output> {
|
||||||
|
let document = Html::parse_document(document);
|
||||||
|
|
||||||
|
document
|
||||||
|
.select(&self.selector_menu)
|
||||||
|
.filter(|item| item.select(&self.selector_link).next().is_some())
|
||||||
|
.filter(|item| item.select(&self.selector_name).next().is_some())
|
||||||
|
.map(|item| {
|
||||||
|
let name = select_text(item, &self.selector_name)
|
||||||
|
.ok_or(ParseError::EmptyText {
|
||||||
|
role: "game mode name",
|
||||||
|
selector: SELECTOR_NAME,
|
||||||
|
})?
|
||||||
|
.trim_end_matches(" Menu");
|
||||||
|
|
||||||
|
let seasons = item
|
||||||
|
.select(&self.selector_link)
|
||||||
|
.map(|link| {
|
||||||
|
let text = link
|
||||||
|
.first_text()
|
||||||
|
.ok_or(ParseError::EmptyText {
|
||||||
|
role: "season name",
|
||||||
|
selector: SELECTOR_SEASON_LINK,
|
||||||
|
})?
|
||||||
|
.trim_end_matches(" Final Standings")
|
||||||
|
.trim_end_matches(" Final Rank")
|
||||||
|
.trim_end_matches(" Final Ranks");
|
||||||
|
let link = link.attr("href").ok_or(ParseError::EmptyText {
|
||||||
|
role: "season link",
|
||||||
|
selector: SELECTOR_SEASON_LINK,
|
||||||
|
})?;
|
||||||
|
let id = link
|
||||||
|
.trim_start_matches("rankings_")
|
||||||
|
.trim_end_matches(".cfm");
|
||||||
|
Ok(Season {
|
||||||
|
name: text.to_string(),
|
||||||
|
id: id.to_string(),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect::<Result<_>>()?;
|
||||||
|
|
||||||
|
Ok(Seasons {
|
||||||
|
mode: name.to_string(),
|
||||||
|
seasons,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<_>>>()
|
||||||
|
}
|
||||||
|
}
|
||||||
2377
tests/data/index.html
Normal file
2377
tests/data/index.html
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -1,7 +1,7 @@
|
||||||
use insta::assert_json_snapshot;
|
use insta::assert_json_snapshot;
|
||||||
use std::fs::read_to_string;
|
use std::fs::read_to_string;
|
||||||
use ugc_scraper::parser::{
|
use ugc_scraper::parser::{
|
||||||
Parser, PlayerDetailsParser, PlayerParser, TeamMatchesParser, TeamParser,
|
Parser, PlayerDetailsParser, PlayerParser, SeasonsParser, TeamMatchesParser, TeamParser,
|
||||||
TeamRosterHistoryParser,
|
TeamRosterHistoryParser,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -52,3 +52,11 @@ fn test_parse_team_matches_html() {
|
||||||
let parsed = parser.parse(&body).unwrap();
|
let parsed = parser.parse(&body).unwrap();
|
||||||
assert_json_snapshot!(parsed);
|
assert_json_snapshot!(parsed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_seasons_html() {
|
||||||
|
let body = read_to_string("tests/data/index.html").unwrap();
|
||||||
|
let parser = SeasonsParser::new();
|
||||||
|
let parsed = parser.parse(&body).unwrap();
|
||||||
|
assert_json_snapshot!(parsed);
|
||||||
|
}
|
||||||
|
|
|
||||||
545
tests/snapshots/snapshot__parse_seasons_html.snap
Normal file
545
tests/snapshots/snapshot__parse_seasons_html.snap
Normal file
|
|
@ -0,0 +1,545 @@
|
||||||
|
---
|
||||||
|
source: tests/snapshot.rs
|
||||||
|
expression: parsed
|
||||||
|
---
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"mode": "Highlander",
|
||||||
|
"seasons": [
|
||||||
|
{
|
||||||
|
"id": "tf2h_season39",
|
||||||
|
"name": "HL Season 39"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "tf2h_season38",
|
||||||
|
"name": "HL Season 38"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "tf2h_season37",
|
||||||
|
"name": "HL Season 37"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "tf2h_season36",
|
||||||
|
"name": "HL Season 36"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "tf2h_season35",
|
||||||
|
"name": "HL Season 35"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "tf2h_season34",
|
||||||
|
"name": "HL Season 34"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "tf2h_season33",
|
||||||
|
"name": "HL Season 33"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "tf2h_season32",
|
||||||
|
"name": "HL Season 32"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "tf2h_season31",
|
||||||
|
"name": "HL Season 31"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "tf2h_season30",
|
||||||
|
"name": "HL Season 30"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "tf2h_season29",
|
||||||
|
"name": "HL Season 29"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "tf2h_season28",
|
||||||
|
"name": "HL Season 28"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "tf2h_season27",
|
||||||
|
"name": "HL Season 27"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "tf2h_season26",
|
||||||
|
"name": "HL Season 26"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "tf2h_season25",
|
||||||
|
"name": "HL Season 25"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "tf2h_season24",
|
||||||
|
"name": "HL Season 24"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "tf2h_season23",
|
||||||
|
"name": "HL Season 23"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "tf2h_season22",
|
||||||
|
"name": "HL Season 22"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "tf2h_season21",
|
||||||
|
"name": "HL Season 21"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "tf2h_season20",
|
||||||
|
"name": "HL Season 20"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "tf2h_season19",
|
||||||
|
"name": "HL Season 19"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "tf2h_season18",
|
||||||
|
"name": "HL Season 18"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "tf2h_season17",
|
||||||
|
"name": "HL Season 17"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "tf2h_season16",
|
||||||
|
"name": "HL Season 16"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "tf2h_season15",
|
||||||
|
"name": "HL Season 15"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "tf2h_season14",
|
||||||
|
"name": "HL Season 14"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "tf2h_season13",
|
||||||
|
"name": "HL Season 13"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "tf2h_season12",
|
||||||
|
"name": "HL Season 12"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "tf2h_season11",
|
||||||
|
"name": "HL Season 11"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "tf2h_season10",
|
||||||
|
"name": "Season 10"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "tf2h_season9",
|
||||||
|
"name": "Season 9"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "tf2h_season8",
|
||||||
|
"name": "Season 8"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mode": "TF2 6v6 League",
|
||||||
|
"seasons": [
|
||||||
|
{
|
||||||
|
"id": "tf26_season40",
|
||||||
|
"name": "Season 40"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "tf26_season39",
|
||||||
|
"name": "Season 39"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "tf26_season38",
|
||||||
|
"name": "Season 38"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "tf26_season37",
|
||||||
|
"name": "Season 37"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "tf26_season36",
|
||||||
|
"name": "Season 36"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "tf26_season35",
|
||||||
|
"name": "Season 35"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "tf26_season34",
|
||||||
|
"name": "Season 34"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "tf26_season33",
|
||||||
|
"name": "Season 33"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "tf26_season32",
|
||||||
|
"name": "Season 32"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "tf26_season31",
|
||||||
|
"name": "Season 31"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "tf26_season30",
|
||||||
|
"name": "Season 30"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "tf26_season29",
|
||||||
|
"name": "Season 29"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "tf26_season28",
|
||||||
|
"name": "Season 28"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "tf26_season27",
|
||||||
|
"name": "Season 27"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "tf26_season26",
|
||||||
|
"name": "Season 26"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "tf26_season25",
|
||||||
|
"name": "Season 25"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "tf26_season24",
|
||||||
|
"name": "Season 24"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "tf26_season23",
|
||||||
|
"name": "Season 23"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "tf26_season22",
|
||||||
|
"name": "Season 22"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "tf26_season21",
|
||||||
|
"name": "Season 21"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "tf26_season20",
|
||||||
|
"name": "Season 20"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "tf26_season19",
|
||||||
|
"name": "Season 19"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "tf26_season18",
|
||||||
|
"name": "Season 18"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "tf26_season17",
|
||||||
|
"name": "Season 17"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "tf26_season16",
|
||||||
|
"name": "Season 16"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "tf26_season15",
|
||||||
|
"name": "Season 15"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mode": "TF2 4v4 League",
|
||||||
|
"seasons": [
|
||||||
|
{
|
||||||
|
"id": "tf24_season28",
|
||||||
|
"name": "Season 28"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "tf24_season27",
|
||||||
|
"name": "Season 27"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "tf24_season26",
|
||||||
|
"name": "Season 26"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "tf24_season25",
|
||||||
|
"name": "Season 25"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "tf24_season24",
|
||||||
|
"name": "Season 24"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "tf24_season23",
|
||||||
|
"name": "Season 23"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "tf24_season22",
|
||||||
|
"name": "Season 22"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "tf24_season21",
|
||||||
|
"name": "Season 21"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "tf24_season20",
|
||||||
|
"name": "Season 20"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "tf24_season19",
|
||||||
|
"name": "Season 19"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "tf24_season18",
|
||||||
|
"name": "Season 18"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "tf24_season17",
|
||||||
|
"name": "Season 17"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "tf24_season16",
|
||||||
|
"name": "Season 16"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "tf24_season15",
|
||||||
|
"name": "Season 15"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "tf24_season14",
|
||||||
|
"name": "Season 14"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "tf24_season13",
|
||||||
|
"name": "Season 13"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "tf24_season12",
|
||||||
|
"name": "Season 12"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "tf24_season11",
|
||||||
|
"name": "Season 11"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "tf24_season10",
|
||||||
|
"name": "Season 10"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "tf24_season9",
|
||||||
|
"name": "Season 9"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "tf24_season8",
|
||||||
|
"name": "Season 8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "tf24_season7",
|
||||||
|
"name": "Season 7"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "tf24_season6",
|
||||||
|
"name": "Season 6"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "tf24_season5",
|
||||||
|
"name": "Season 5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "tf24_season4",
|
||||||
|
"name": "Season 4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "tf24_season3",
|
||||||
|
"name": "Season 3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "tf24_season2",
|
||||||
|
"name": "Season 2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "tf24_season1",
|
||||||
|
"name": "Season 1"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mode": "TF2 Ultiduo League",
|
||||||
|
"seasons": [
|
||||||
|
{
|
||||||
|
"id": "tf22_season10",
|
||||||
|
"name": "Season 10"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "tf22_season9",
|
||||||
|
"name": "Season 9"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "tf22_season8",
|
||||||
|
"name": "Season 8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "tf22_season7",
|
||||||
|
"name": "Season 7"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "tf22_season6",
|
||||||
|
"name": "Season 6"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "tf22_season5",
|
||||||
|
"name": "Season 5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "tf22_season4",
|
||||||
|
"name": "Season 4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "tf22_season3",
|
||||||
|
"name": "Season 3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "tf22_season2",
|
||||||
|
"name": "Season 2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "tf22_season1",
|
||||||
|
"name": "Season 1"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mode": "ASIA Highlander",
|
||||||
|
"seasons": [
|
||||||
|
{
|
||||||
|
"id": "atf2h_season38",
|
||||||
|
"name": "ASIA HL Season 38"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "atf2h_season37",
|
||||||
|
"name": "ASIA HL Season 37"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "atf2h_season36",
|
||||||
|
"name": "ASIA HL Season 36"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "atf2h_season35",
|
||||||
|
"name": "ASIA HL Season 35"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "atf2h_season34",
|
||||||
|
"name": "ASIA HL Season 34"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "atf2h_season33",
|
||||||
|
"name": "ASIA HL Season 33"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "atf2h_season32",
|
||||||
|
"name": "ASIA HL Season 32"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mode": "ASIA 6v6",
|
||||||
|
"seasons": [
|
||||||
|
{
|
||||||
|
"id": "atf26_season40",
|
||||||
|
"name": "ASIA 6v6 Season 40"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "atf26_season39",
|
||||||
|
"name": "ASIA 6v6 Season 39"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "atf26_season38",
|
||||||
|
"name": "ASIA 6v6 Season 38"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "atf26_season37",
|
||||||
|
"name": "ASIA 6v6 Season 37"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mode": "Overwatch",
|
||||||
|
"seasons": [
|
||||||
|
{
|
||||||
|
"id": "ow_season19",
|
||||||
|
"name": "OW2 Season 1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ow_season18",
|
||||||
|
"name": "OW Season 18"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ow_season17",
|
||||||
|
"name": "OW Season 17"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ow_season16",
|
||||||
|
"name": "OW Season 16"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ow_season15",
|
||||||
|
"name": "OW Season 15"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ow_season14",
|
||||||
|
"name": "OW Season 14"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ow_season13",
|
||||||
|
"name": "OW Season 13"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ow_season12",
|
||||||
|
"name": "OW Season 12"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ow_season11",
|
||||||
|
"name": "OW Season 11"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ow_season10",
|
||||||
|
"name": "OW Season 10"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ow_season9",
|
||||||
|
"name": "OW Season 9"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ow_season8",
|
||||||
|
"name": "OW Season 8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ow_season7",
|
||||||
|
"name": "OW Season 7"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ow_season6",
|
||||||
|
"name": "OW Season 6"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ow_season5",
|
||||||
|
"name": "OW Season 5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ow_season4",
|
||||||
|
"name": "OW Season 4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ow_season3",
|
||||||
|
"name": "OW Season 3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ow_season2",
|
||||||
|
"name": "OW Season 2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ow_season1",
|
||||||
|
"name": "OW Season 1"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
Loading…
Add table
Add a link
Reference in a new issue