ptcg-tools/src/downloader/card_info.rs

232 lines
6.3 KiB
Rust
Raw Normal View History

2025-04-28 19:02:32 -04:00
//! 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")
}