mirror of
https://github.com/redstrate/Auracite.git
synced 2025-06-30 09:37:45 +00:00
Begin extracting classjob ids, current and max EXP
This commit is contained in:
parent
a2922d043d
commit
aa7c38634d
3 changed files with 122 additions and 32 deletions
16
src/lib.rs
16
src/lib.rs
|
@ -12,7 +12,6 @@ use crate::parser::parse_search;
|
|||
use base64::prelude::*;
|
||||
use data::{Appearance, Currencies};
|
||||
use package::Package;
|
||||
use physis::race::{Gender, Race, Tribe};
|
||||
use physis::savedata::chardat;
|
||||
use regex::Regex;
|
||||
use reqwest::Url;
|
||||
|
@ -132,7 +131,20 @@ pub async fn archive_character(id: u64, use_dalamud: bool) -> Result<Vec<u8>, Ar
|
|||
.map_err(|_| ArchiveError::DownloadFailed(char_page_url.to_string()))?;
|
||||
let char_page = String::from_utf8(char_page).map_err(|_| ArchiveError::ParsingError)?;
|
||||
|
||||
let mut char_data = parser::parse_lodestone(&char_page);
|
||||
let mut char_data = CharacterData::default();
|
||||
parser::parse_profile(&char_page, &mut char_data);
|
||||
|
||||
let classjob_page_url = Url::parse(&format!(
|
||||
"{lodestone_host}/lodestone/character/{}/class_job/",
|
||||
id
|
||||
))
|
||||
.map_err(|_| ArchiveError::UnknownError)?;
|
||||
let classjob_page = download(&classjob_page_url)
|
||||
.await
|
||||
.map_err(|_| ArchiveError::DownloadFailed(classjob_page_url.to_string()))?;
|
||||
let char_page = String::from_utf8(classjob_page).map_err(|_| ArchiveError::ParsingError)?;
|
||||
|
||||
parser::parse_classjob(&char_page, &mut char_data);
|
||||
|
||||
// 2 MiB, for one JSON and two images
|
||||
let mut buf = Vec::new();
|
||||
|
|
|
@ -41,16 +41,13 @@ const CHARACTER_BLOCK_NAME_SELECTOR: &str = ".character-block__name";
|
|||
const FACE_IMG_SELECTOR: &str = ".frame__chara__face > img";
|
||||
const PORTRAIT_IMG_SELECTOR: &str = ".character__detail__image > a > img";
|
||||
const NAMEDAY_SELECTOR: &str = ".character-block__birth";
|
||||
const CLASSJOB_SELECTOR: &str = ".character__level__list > ul > li";
|
||||
const FREE_COMPANY_SELECTOR: &str = ".character__freecompany__name > h4 > a";
|
||||
const TITLE_SELECTOR: &str = ".frame__chara__title";
|
||||
|
||||
/// Parses the HTML from `data` and returns `CharacterData`. The data may be incomplete.
|
||||
pub fn parse_lodestone(data: &str) -> CharacterData {
|
||||
pub fn parse_profile(data: &str, char_data: &mut CharacterData) {
|
||||
let document = Html::parse_document(data);
|
||||
|
||||
let mut char_data = CharacterData::default();
|
||||
|
||||
for element in document.select(&Selector::parse(CHARACTER_NAME_SELECTOR).unwrap()) {
|
||||
char_data.name = element.inner_html();
|
||||
}
|
||||
|
@ -148,31 +145,6 @@ pub fn parse_lodestone(data: &str) -> CharacterData {
|
|||
char_data.portrait_url = element.attr("src").unwrap().parse().unwrap();
|
||||
}
|
||||
|
||||
for element in document.select(&Selector::parse(CLASSJOB_SELECTOR).unwrap()) {
|
||||
let img = element.first_child().unwrap();
|
||||
let name = img
|
||||
.value()
|
||||
.as_element()
|
||||
.unwrap()
|
||||
.attr("data-tooltip")
|
||||
.unwrap();
|
||||
|
||||
// ignore "-" and other invalid level values
|
||||
if let Ok(level) = element
|
||||
.last_child()
|
||||
.unwrap()
|
||||
.value()
|
||||
.as_text()
|
||||
.unwrap()
|
||||
.parse::<i32>()
|
||||
{
|
||||
char_data.classjob_levels.push(ClassJobValue {
|
||||
name: name.to_string(),
|
||||
level,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: support facewear
|
||||
let item_slot_selectors = [
|
||||
".icon-c--0", // Main Hand
|
||||
|
@ -215,8 +187,48 @@ pub fn parse_lodestone(data: &str) -> CharacterData {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
char_data
|
||||
const CLASSJOB_SELECTOR: &str = ".character__job > li";
|
||||
const CLASSJOB_LEVEL_SELECTOR: &str = ".character__job__level";
|
||||
const CLASSJOB_NAME_SELECTOR: &str = ".character__job__name";
|
||||
const CLASSJOB_EXP_SELECTOR: &str = ".character__job__exp";
|
||||
|
||||
/// Parses the HTML from `data` and returns `CharacterData`. The data may be incomplete.
|
||||
pub fn parse_classjob(data: &str, char_data: &mut CharacterData) {
|
||||
let document = Html::parse_document(data);
|
||||
|
||||
for element in document.select(&Selector::parse(CLASSJOB_SELECTOR).unwrap()) {
|
||||
let level = element
|
||||
.select(&Selector::parse(CLASSJOB_LEVEL_SELECTOR).unwrap())
|
||||
.nth(0)
|
||||
.unwrap();
|
||||
let name = element
|
||||
.select(&Selector::parse(CLASSJOB_NAME_SELECTOR).unwrap())
|
||||
.nth(0)
|
||||
.unwrap();
|
||||
let exp_element = element
|
||||
.select(&Selector::parse(CLASSJOB_EXP_SELECTOR).unwrap())
|
||||
.nth(0)
|
||||
.unwrap();
|
||||
|
||||
let mut exp = None;
|
||||
let mut max_exp = None;
|
||||
if let Some((exp_text, max_exp_text)) = exp_element.inner_html().split_once(" / ") {
|
||||
exp = exp_text.replace(",", "").parse().ok();
|
||||
max_exp = max_exp_text.replace(",", "").parse().ok();
|
||||
}
|
||||
|
||||
// skip levels that are -, which means they don't even have the classjob
|
||||
if let Ok(level) = level.inner_html().parse() {
|
||||
let mut class_job_value =
|
||||
ClassJobValue::try_from(name.inner_html().as_str()).unwrap_or_default();
|
||||
class_job_value.level = level;
|
||||
class_job_value.exp = exp;
|
||||
class_job_value.max_exp = max_exp;
|
||||
char_data.classjob_levels.push(class_job_value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_item_tooltip(element: &scraper::ElementRef<'_>) -> Option<ItemValue> {
|
||||
|
|
66
src/value.rs
66
src/value.rs
|
@ -299,6 +299,72 @@ pub struct ClassJobValue {
|
|||
pub name: String,
|
||||
/// Level of the class or job.
|
||||
pub level: i32,
|
||||
/// The EXP of the job, can be None to indicate either: the job isn't unlocked or that they have max EXP.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub exp: Option<i32>,
|
||||
/// The maximum EXP of the job, can be None to indicate either: the job isn't unlocked or that they have max EXP.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub max_exp: Option<i32>,
|
||||
/// Internal ID of the class or job.
|
||||
pub value: i32,
|
||||
}
|
||||
|
||||
impl TryFrom<&str> for ClassJobValue {
|
||||
type Error = ArchiveError;
|
||||
|
||||
fn try_from(name: &str) -> Result<Self, ArchiveError> {
|
||||
let value = match name {
|
||||
"Gladiator" => 1,
|
||||
"Pugilist" => 2,
|
||||
"Marauder" => 3,
|
||||
"Lancer" => 4,
|
||||
"Archer" => 5,
|
||||
"Conjurer" => 6,
|
||||
"Thaumaturge" => 7,
|
||||
"Carpenter" => 8,
|
||||
"Blacksmith" => 9,
|
||||
"Armorer" => 10,
|
||||
"Goldsmith" => 11,
|
||||
"Leatherworker" => 12,
|
||||
"Weaver" => 13,
|
||||
"Alchemist" => 14,
|
||||
"Culinarian" => 15,
|
||||
"Miner" => 16,
|
||||
"Botanist" => 17,
|
||||
"Fisher" => 18,
|
||||
"Paladin" => 19,
|
||||
"Monk" => 20,
|
||||
"Warrior" => 21,
|
||||
"Dragoon" => 22,
|
||||
"Bard" => 23,
|
||||
"White Mage" => 24,
|
||||
"Black Mage" => 25,
|
||||
"Arcanist" => 26,
|
||||
"Summoner" => 27,
|
||||
"Scholar" => 28,
|
||||
"Rogue" => 29,
|
||||
"Ninja" => 30,
|
||||
"Machinist" => 31,
|
||||
"Dark Knight" => 32,
|
||||
"Astrologian" => 33,
|
||||
"Samurai" => 34,
|
||||
"Red Mage" => 35,
|
||||
"Blue Mage" => 36,
|
||||
"Gunbreaker" => 37,
|
||||
"Dancer" => 38,
|
||||
"Reaper" => 39,
|
||||
"Sage" => 40,
|
||||
"Viper" => 41,
|
||||
"Pictomancer" => 42,
|
||||
_ => return Err(ArchiveError::ParsingError),
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
name: name.to_string(),
|
||||
value,
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Serialize)]
|
||||
|
|
Loading…
Add table
Reference in a new issue