mirror of
https://github.com/redstrate/Auracite.git
synced 2025-06-30 17:47: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 base64::prelude::*;
|
||||||
use data::{Appearance, Currencies};
|
use data::{Appearance, Currencies};
|
||||||
use package::Package;
|
use package::Package;
|
||||||
use physis::race::{Gender, Race, Tribe};
|
|
||||||
use physis::savedata::chardat;
|
use physis::savedata::chardat;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use reqwest::Url;
|
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()))?;
|
.map_err(|_| ArchiveError::DownloadFailed(char_page_url.to_string()))?;
|
||||||
let char_page = String::from_utf8(char_page).map_err(|_| ArchiveError::ParsingError)?;
|
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
|
// 2 MiB, for one JSON and two images
|
||||||
let mut buf = Vec::new();
|
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 FACE_IMG_SELECTOR: &str = ".frame__chara__face > img";
|
||||||
const PORTRAIT_IMG_SELECTOR: &str = ".character__detail__image > a > img";
|
const PORTRAIT_IMG_SELECTOR: &str = ".character__detail__image > a > img";
|
||||||
const NAMEDAY_SELECTOR: &str = ".character-block__birth";
|
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 FREE_COMPANY_SELECTOR: &str = ".character__freecompany__name > h4 > a";
|
||||||
const TITLE_SELECTOR: &str = ".frame__chara__title";
|
const TITLE_SELECTOR: &str = ".frame__chara__title";
|
||||||
|
|
||||||
/// Parses the HTML from `data` and returns `CharacterData`. The data may be incomplete.
|
/// 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 document = Html::parse_document(data);
|
||||||
|
|
||||||
let mut char_data = CharacterData::default();
|
|
||||||
|
|
||||||
for element in document.select(&Selector::parse(CHARACTER_NAME_SELECTOR).unwrap()) {
|
for element in document.select(&Selector::parse(CHARACTER_NAME_SELECTOR).unwrap()) {
|
||||||
char_data.name = element.inner_html();
|
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();
|
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
|
// TODO: support facewear
|
||||||
let item_slot_selectors = [
|
let item_slot_selectors = [
|
||||||
".icon-c--0", // Main Hand
|
".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> {
|
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,
|
pub name: String,
|
||||||
/// Level of the class or job.
|
/// Level of the class or job.
|
||||||
pub level: i32,
|
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)]
|
#[derive(Default, Serialize)]
|
||||||
|
|
Loading…
Add table
Reference in a new issue