feat: add jobs
This commit is contained in:
parent
76066c56f6
commit
9a8588b283
|
@ -6,16 +6,21 @@ use crate::{
|
||||||
CityState,
|
CityState,
|
||||||
Gender,
|
Gender,
|
||||||
GrandCompanyInfo,
|
GrandCompanyInfo,
|
||||||
|
Job,
|
||||||
|
JobInfo,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
use ffxiv_types::{World, Race, Clan, Guardian};
|
use ffxiv_types::{World, Race, Clan, Guardian};
|
||||||
|
|
||||||
use scraper::Html;
|
use scraper::{Html, ElementRef};
|
||||||
|
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
use std::str::FromStr;
|
use std::{
|
||||||
|
collections::BTreeMap,
|
||||||
|
str::FromStr,
|
||||||
|
};
|
||||||
|
|
||||||
selectors!(
|
selectors!(
|
||||||
PROFILE_FACE => ".frame__chara__face > img";
|
PROFILE_FACE => ".frame__chara__face > img";
|
||||||
|
@ -30,6 +35,11 @@ selectors!(
|
||||||
PROFILE_GRAND_COMPANY => "div.character-block:nth-of-type(4) > .character-block__box > .character-block__name";
|
PROFILE_GRAND_COMPANY => "div.character-block:nth-of-type(4) > .character-block__box > .character-block__name";
|
||||||
PROFILE_FREE_COMPANY => ".character__freecompany__name > h4 > a";
|
PROFILE_FREE_COMPANY => ".character__freecompany__name > h4 > a";
|
||||||
PROFILE_TEXT => ".character__selfintroduction";
|
PROFILE_TEXT => ".character__selfintroduction";
|
||||||
|
|
||||||
|
PROFILE_CLASS => "ul.character__job > li";
|
||||||
|
CLASS_NAME => ".character__job__name";
|
||||||
|
CLASS_LEVEL => ".character__job__level";
|
||||||
|
CLASS_EXP => ".character__job__exp";
|
||||||
);
|
);
|
||||||
|
|
||||||
pub fn parse(id: u64, html: &str) -> Result<Character> {
|
pub fn parse(id: u64, html: &str) -> Result<Character> {
|
||||||
|
@ -51,6 +61,8 @@ pub fn parse(id: u64, html: &str) -> Result<Character> {
|
||||||
|
|
||||||
let profile_text = plain_parse(&html, &*PROFILE_TEXT)?.trim().to_string();
|
let profile_text = plain_parse(&html, &*PROFILE_TEXT)?.trim().to_string();
|
||||||
|
|
||||||
|
let jobs = parse_jobs(&html)?;
|
||||||
|
|
||||||
let face = parse_face(&html)?;
|
let face = parse_face(&html)?;
|
||||||
let portrait = parse_portrait(&html)?;
|
let portrait = parse_portrait(&html)?;
|
||||||
|
|
||||||
|
@ -68,6 +80,7 @@ pub fn parse(id: u64, html: &str) -> Result<Character> {
|
||||||
grand_company,
|
grand_company,
|
||||||
free_company_id,
|
free_company_id,
|
||||||
profile_text,
|
profile_text,
|
||||||
|
jobs,
|
||||||
face,
|
face,
|
||||||
portrait,
|
portrait,
|
||||||
})
|
})
|
||||||
|
@ -181,3 +194,50 @@ fn parse_portrait(html: &Html) -> Result<Url> {
|
||||||
.ok_or_else(|| Error::invalid_content("img src attribute", None))
|
.ok_or_else(|| Error::invalid_content("img src attribute", None))
|
||||||
.and_then(|x| Url::parse(x).map_err(Error::InvalidUrl))
|
.and_then(|x| Url::parse(x).map_err(Error::InvalidUrl))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn parse_jobs(html: &Html) -> Result<BTreeMap<Job, JobInfo>> {
|
||||||
|
let mut jobs = BTreeMap::new();
|
||||||
|
|
||||||
|
for job in html.select(&*PROFILE_CLASS) {
|
||||||
|
let (job, info) = parse_job(job)?;
|
||||||
|
jobs.insert(job, info);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(jobs)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_job<'a>(elem: ElementRef<'a>) -> Result<(Job, JobInfo)> {
|
||||||
|
let job = crate::logic::plain_parse_elem(elem, &*CLASS_NAME)
|
||||||
|
.and_then(|x| Job::parse(&x).ok_or_else(|| Error::invalid_content("valid job", Some(&x))))?;
|
||||||
|
|
||||||
|
let level_str = crate::logic::plain_parse_elem(elem, &*CLASS_LEVEL)?;
|
||||||
|
let level: Option<u8> = match level_str.as_str() {
|
||||||
|
"-" => None,
|
||||||
|
x => Some(x.parse().map_err(Error::InvalidNumber)?),
|
||||||
|
};
|
||||||
|
|
||||||
|
let exp_str = crate::logic::plain_parse_elem(elem, &*CLASS_EXP)?;
|
||||||
|
let mut exp_split = exp_str.split(" / ");
|
||||||
|
|
||||||
|
let first_exp = exp_split.next().unwrap(); // must have first element
|
||||||
|
let experience: Option<u64> = match first_exp {
|
||||||
|
"-" | "--" => None,
|
||||||
|
x => Some(x.parse().map_err(Error::InvalidNumber)?),
|
||||||
|
};
|
||||||
|
|
||||||
|
let second_exp = exp_split
|
||||||
|
.next()
|
||||||
|
.ok_or_else(|| Error::invalid_content("experience split by ` / `", Some(&exp_str)))?;
|
||||||
|
let next_level_experience: Option<u64> = match second_exp {
|
||||||
|
"-" | "--" => None,
|
||||||
|
x => Some(x.parse().map_err(Error::InvalidNumber)?),
|
||||||
|
};
|
||||||
|
|
||||||
|
let info = JobInfo {
|
||||||
|
level,
|
||||||
|
experience,
|
||||||
|
next_level_experience,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok((job, info))
|
||||||
|
}
|
||||||
|
|
|
@ -4,6 +4,8 @@ use ffxiv_types::{World, Race, Clan, Guardian};
|
||||||
|
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub struct Character {
|
pub struct Character {
|
||||||
pub id: u64,
|
pub id: u64,
|
||||||
|
@ -24,6 +26,8 @@ pub struct Character {
|
||||||
|
|
||||||
pub profile_text: String,
|
pub profile_text: String,
|
||||||
|
|
||||||
|
pub jobs: BTreeMap<Job, JobInfo>,
|
||||||
|
|
||||||
#[serde(with = "url_serde")]
|
#[serde(with = "url_serde")]
|
||||||
pub face: Url,
|
pub face: Url,
|
||||||
#[serde(with = "url_serde")]
|
#[serde(with = "url_serde")]
|
||||||
|
@ -36,6 +40,13 @@ pub struct GrandCompanyInfo {
|
||||||
pub rank: String,
|
pub rank: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub struct JobInfo {
|
||||||
|
pub level: Option<u8>,
|
||||||
|
pub experience: Option<u64>,
|
||||||
|
pub next_level_experience: Option<u64>,
|
||||||
|
}
|
||||||
|
|
||||||
ffxiv_enum!(Gender {
|
ffxiv_enum!(Gender {
|
||||||
Male => "♂",
|
Male => "♂",
|
||||||
Female => "♀",
|
Female => "♀",
|
||||||
|
@ -46,3 +57,45 @@ ffxiv_enum!(CityState {
|
||||||
LimsaLominsa => "Limsa Lominsa",
|
LimsaLominsa => "Limsa Lominsa",
|
||||||
UlDah => "Ul'dah",
|
UlDah => "Ul'dah",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ffxiv_enum!(
|
||||||
|
#[derive(PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
Job {
|
||||||
|
Gladiator => "Gladiator",
|
||||||
|
Paladin => "Paladin",
|
||||||
|
Marauder => "Marauder",
|
||||||
|
Warrior => "Warrior",
|
||||||
|
DarkKnight => "Dark Knight",
|
||||||
|
Conjurer => "Conjurer",
|
||||||
|
WhiteMage => "White Mage",
|
||||||
|
Scholar => "Scholar",
|
||||||
|
Astrologian => "Astrologian",
|
||||||
|
Pugilist => "Pugilist",
|
||||||
|
Monk => "Monk",
|
||||||
|
Lancer => "Lancer",
|
||||||
|
Dragoon => "Dragoon",
|
||||||
|
Rogue => "Rogue",
|
||||||
|
Ninja => "Ninja",
|
||||||
|
Samurai => "Samurai",
|
||||||
|
Archer => "Archer",
|
||||||
|
Bard => "Bard",
|
||||||
|
Machinist => "Machinist",
|
||||||
|
Thaumaturge => "Thaumaturge",
|
||||||
|
BlackMage => "Black Mage",
|
||||||
|
Arcanist => "Arcanist",
|
||||||
|
Summoner => "Summoner",
|
||||||
|
RedMage => "Red Mage",
|
||||||
|
|
||||||
|
Carpenter => "Carpenter",
|
||||||
|
Blacksmith => "Blacksmith",
|
||||||
|
Armorer => "Armorer",
|
||||||
|
Goldsmith => "Goldsmith",
|
||||||
|
Leatherworker => "Leatherworker",
|
||||||
|
Weaver => "Weaver",
|
||||||
|
Alchemist => "Alchemist",
|
||||||
|
Culinarian => "Culinarian",
|
||||||
|
Miner => "Miner",
|
||||||
|
Botanist => "Botanist",
|
||||||
|
Fisher => "Fisher",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
Loading…
Reference in New Issue