232 lines
6.3 KiB
Rust
232 lines
6.3 KiB
Rust
|
|
//! Download card information.
|
||
|
|
|
||
|
|
use anyhow::{Result, anyhow};
|
||
|
|
use reqwest::Client;
|
||
|
|
use scraper::{Html, Selector};
|
||
|
|
|
||
|
|
use crate::card::{CardInfo, CardKind, CardType, InnerCardInfo, SpecificInfo};
|
||
|
|
use crate::editions::EditionCode;
|
||
|
|
use crate::lang::Language;
|
||
|
|
|
||
|
|
pub async fn download_card_info(
|
||
|
|
client: Client,
|
||
|
|
lang: Language,
|
||
|
|
code: EditionCode,
|
||
|
|
number: u8,
|
||
|
|
override_slug: Option<&str>,
|
||
|
|
) -> Result<CardInfo> {
|
||
|
|
let url = format!(
|
||
|
|
"{}/{}/{number}/",
|
||
|
|
base_url(lang),
|
||
|
|
code.edition_num().to_lowercase()
|
||
|
|
);
|
||
|
|
let response = client.get(url).send().await?;
|
||
|
|
response.error_for_status_ref()?;
|
||
|
|
let (mut slug, inner) = parse_html(lang, code, response.text().await?)?;
|
||
|
|
if let Some(override_slug) = override_slug {
|
||
|
|
slug = override_slug.into()
|
||
|
|
}
|
||
|
|
Ok(CardInfo { slug, inner })
|
||
|
|
}
|
||
|
|
|
||
|
|
fn base_url(lang: Language) -> &'static str {
|
||
|
|
match lang {
|
||
|
|
Language::Es => "https://www.pokemon.com/el/jcc-pokemon/cartas-pokemon/series",
|
||
|
|
Language::En => todo!(),
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
fn parse_html(lang: Language, code: EditionCode, html: String) -> Result<(String, InnerCardInfo)> {
|
||
|
|
let html = Html::parse_document(&html);
|
||
|
|
let card = html
|
||
|
|
.select(&selector("div.full-card-information")?)
|
||
|
|
.next()
|
||
|
|
.ok_or(anyhow!("Couldn't find card info"))?;
|
||
|
|
let name = card
|
||
|
|
.select(&selector("h1")?)
|
||
|
|
.next()
|
||
|
|
.ok_or(anyhow!("Failed to get card name"))?
|
||
|
|
.inner_html();
|
||
|
|
let (kind, card_type) = parse_card_type(
|
||
|
|
lang,
|
||
|
|
card.select(&selector("div.card-type > h2")?)
|
||
|
|
.next()
|
||
|
|
.ok_or(anyhow!("Failed to get card type"))?
|
||
|
|
.inner_html(),
|
||
|
|
)?;
|
||
|
|
let slug = match kind {
|
||
|
|
CardKind::Pokemon => slugify_pokemon(lang, code, &name),
|
||
|
|
CardKind::Trainer | CardKind::Energy => slugify_unique(lang, &name),
|
||
|
|
};
|
||
|
|
let specific_info = match kind {
|
||
|
|
CardKind::Pokemon => SpecificInfo::PokemonInfo {},
|
||
|
|
CardKind::Trainer => {
|
||
|
|
let effect = card
|
||
|
|
.select(&selector("div.ability > pre > p")?)
|
||
|
|
.map(|e| e.inner_html())
|
||
|
|
.collect();
|
||
|
|
SpecificInfo::TrainerInfo { effect }
|
||
|
|
}
|
||
|
|
CardKind::Energy => SpecificInfo::EnergyInfo {},
|
||
|
|
};
|
||
|
|
|
||
|
|
Ok((
|
||
|
|
slug,
|
||
|
|
InnerCardInfo {
|
||
|
|
name,
|
||
|
|
kind,
|
||
|
|
card_type,
|
||
|
|
acespec: false,
|
||
|
|
tagteam: false,
|
||
|
|
future: false,
|
||
|
|
ancient: false,
|
||
|
|
specific_info,
|
||
|
|
},
|
||
|
|
))
|
||
|
|
}
|
||
|
|
|
||
|
|
fn selector(sel: &str) -> Result<Selector> {
|
||
|
|
Selector::parse(sel).map_err(|_| anyhow!("failed to parse selector"))
|
||
|
|
}
|
||
|
|
|
||
|
|
fn parse_card_type(lang: Language, text: String) -> Result<(CardKind, CardType)> {
|
||
|
|
let kind = if text.contains(trainer_pattern(lang)) || text.contains(tool_pattern(lang)) {
|
||
|
|
Ok(CardKind::Trainer)
|
||
|
|
} else if text.contains("Pokémon") {
|
||
|
|
Ok(CardKind::Pokemon)
|
||
|
|
} else if text.contains(energy_pattern(lang)) {
|
||
|
|
Ok(CardKind::Energy)
|
||
|
|
} else {
|
||
|
|
Err(anyhow!(
|
||
|
|
"Failed to get card kind (Pokemon, Trainer or Energy)"
|
||
|
|
))
|
||
|
|
}?;
|
||
|
|
|
||
|
|
let card_type = match kind {
|
||
|
|
CardKind::Pokemon => {
|
||
|
|
if text.contains(basic_pattern(lang)) {
|
||
|
|
Ok(CardType::Basic)
|
||
|
|
} else if text.contains(stage1_pattern(lang)) {
|
||
|
|
Ok(CardType::Stage1)
|
||
|
|
} else if text.contains(stage2_pattern(lang)) {
|
||
|
|
Ok(CardType::Stage2)
|
||
|
|
} else {
|
||
|
|
Err(anyhow!("Failed to get Pokemon type: {text}"))
|
||
|
|
}
|
||
|
|
}
|
||
|
|
CardKind::Trainer => {
|
||
|
|
if text.contains(item_pattern(lang)) {
|
||
|
|
Ok(CardType::Item)
|
||
|
|
} else if text.contains(tool_pattern(lang)) {
|
||
|
|
Ok(CardType::Tool)
|
||
|
|
} else if text.contains(stadium_pattern(lang)) {
|
||
|
|
Ok(CardType::Stadium)
|
||
|
|
} else if text.contains(supporter_pattern(lang)) {
|
||
|
|
Ok(CardType::Supporter)
|
||
|
|
} else {
|
||
|
|
Err(anyhow!("Failed to get Trainer type"))
|
||
|
|
}
|
||
|
|
}
|
||
|
|
CardKind::Energy => {
|
||
|
|
if text.contains(basic_pattern(lang)) {
|
||
|
|
Ok(CardType::Basic)
|
||
|
|
} else if text.contains(special_pattern(lang)) {
|
||
|
|
Ok(CardType::Special)
|
||
|
|
} else {
|
||
|
|
Err(anyhow!("Failed to get Pokemon type"))
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}?;
|
||
|
|
|
||
|
|
Ok((kind, card_type))
|
||
|
|
}
|
||
|
|
|
||
|
|
fn trainer_pattern(lang: Language) -> &'static str {
|
||
|
|
match lang {
|
||
|
|
Language::Es => "Entrenador",
|
||
|
|
Language::En => todo!(),
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
fn energy_pattern(lang: Language) -> &'static str {
|
||
|
|
match lang {
|
||
|
|
Language::Es => "Energía",
|
||
|
|
Language::En => todo!(),
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
fn basic_pattern(lang: Language) -> &'static str {
|
||
|
|
match lang {
|
||
|
|
Language::Es => "Básic",
|
||
|
|
Language::En => todo!(),
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
fn stage1_pattern(lang: Language) -> &'static str {
|
||
|
|
match lang {
|
||
|
|
Language::Es => "Fase 1",
|
||
|
|
Language::En => todo!(),
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
fn stage2_pattern(lang: Language) -> &'static str {
|
||
|
|
match lang {
|
||
|
|
Language::Es => "Fase 2",
|
||
|
|
Language::En => todo!(),
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
fn item_pattern(lang: Language) -> &'static str {
|
||
|
|
match lang {
|
||
|
|
Language::Es => "Objeto",
|
||
|
|
Language::En => todo!(),
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
fn tool_pattern(lang: Language) -> &'static str {
|
||
|
|
match lang {
|
||
|
|
Language::Es => "Herramienta",
|
||
|
|
Language::En => todo!(),
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
fn supporter_pattern(lang: Language) -> &'static str {
|
||
|
|
match lang {
|
||
|
|
Language::Es => "Partidario",
|
||
|
|
Language::En => todo!(),
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
fn stadium_pattern(lang: Language) -> &'static str {
|
||
|
|
match lang {
|
||
|
|
Language::Es => "Estadio",
|
||
|
|
Language::En => todo!(),
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
fn special_pattern(lang: Language) -> &'static str {
|
||
|
|
match lang {
|
||
|
|
Language::Es => "Especial",
|
||
|
|
Language::En => todo!(),
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
fn slugify_pokemon(lang: Language, code: EditionCode, name: &str) -> String {
|
||
|
|
format!("{}-{code}-{lang}", slugify(name))
|
||
|
|
}
|
||
|
|
|
||
|
|
fn slugify_unique(lang: Language, name: &str) -> String {
|
||
|
|
format!("{}-{lang}", slugify(name))
|
||
|
|
}
|
||
|
|
|
||
|
|
fn slugify(name: &str) -> String {
|
||
|
|
name.to_lowercase()
|
||
|
|
.replace("'s", "")
|
||
|
|
.replace(" ", "-")
|
||
|
|
.replace("á", "a")
|
||
|
|
.replace("é", "e")
|
||
|
|
.replace("í", "i")
|
||
|
|
.replace("ó", "o")
|
||
|
|
.replace("ú", "u")
|
||
|
|
}
|