//! 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 { 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::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") }