diff --git a/Cargo.toml b/Cargo.toml index 0cfe302..84b2eaa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,7 +27,7 @@ serde = { version = "1.0", features = ["derive"], default-features = false } regex = { version = "1.11", default-features = false, features = ["unicode-perl"] } # Used to generate the HTML page to easily preview your exported data -minijinja = { version = "2.11", default-features = false } +minijinja = { version = "2.11", features = ["serde"], default-features = false } # Download files reqwest = { version = "0.12" } @@ -38,7 +38,7 @@ zip = { version = "4.2", default-features = false } # Exporting propietary game data physis = { git = "https://github.com/redstrate/Physis" } -# Encoding the character archive to base64 so the browser can download it and decoding the base64 images from the c lient +# Encoding the character archive to base64 so the browser can download it and decoding the base64 images from the client base64 = { version = "0.22", default-features = false } # Not used directly by us, but to disable the "std" feature and is used by the scraper crate. diff --git a/src/bin/auracite/bridge.rs b/src/bin/auracite/bridge.rs index 008f855..38d9368 100644 --- a/src/bin/auracite/bridge.rs +++ b/src/bin/auracite/bridge.rs @@ -51,7 +51,7 @@ pub struct BackendRust {} impl bridge::Backend { pub fn archive_character_by_name( - mut self: Pin<&mut Self>, + self: Pin<&mut Self>, character_name: &QString, use_dalamud: bool, filename: &QString, @@ -83,7 +83,7 @@ impl bridge::Backend { } pub fn archive_character_by_id( - mut self: Pin<&mut Self>, + self: Pin<&mut Self>, character_id: &QString, use_dalamud: bool, filename: &QString, diff --git a/src/bin/auracite/main.rs b/src/bin/auracite/main.rs index d54c38c..6b34d35 100644 --- a/src/bin/auracite/main.rs +++ b/src/bin/auracite/main.rs @@ -66,7 +66,7 @@ fn main() { &QUrl::from(&QString::from("https://redstrate.com/rss-image.png")), )); - KAboutData::set_application_data(&*about_data); + KAboutData::set_application_data(&about_data); QGuiApplication::set_desktop_file_name(&QString::from("zone.xiv.auracite")); let mut command_line_parser = QCommandLineParser::default(); @@ -91,7 +91,7 @@ fn main() { command_line_parser.add_option(&dalamud_option); command_line_parser.process(&QStringList::from(&QList::from( - &args().map(|x| QString::from(x)).collect::>(), + &args().map(QString::from).collect::>(), ))); about_data .as_mut() @@ -102,15 +102,16 @@ fn main() { .value(&QString::from("name")) .to_string(); - println!("Downloading character data for {}...", character_name); + println!("Downloading character data for {character_name}..."); let id = search_character_blocking(&character_name).expect("Couldn't find character!"); archive_character_blocking( id, command_line_parser.is_set(&QString::from("dalamud")), - &format!("{}.zip", character_name), - ); + &format!("{character_name}.zip"), + ) + .expect("Failed to archive the requested character!"); return; } @@ -122,13 +123,14 @@ fn main() { .parse() .expect("Not a valid ID!"); - println!("Downloading character data for {}...", id); + println!("Downloading character data for {id}..."); archive_character_blocking( id, command_line_parser.is_set(&QString::from("dalamud")), - &format!("{}.zip", id), // TODO: give it the character's name - ); + &format!("{id}.zip"), // TODO: give it the character's name + ) + .expect("Failed to archive the requested character!"); return; } diff --git a/src/lib.rs b/src/lib.rs index 9400c07..ca373d1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,12 +10,11 @@ use crate::downloader::download; use crate::html::{create_character_html, create_plate_html}; use crate::parser::parse_search; use base64::prelude::*; -use data::{Appearance, Currencies}; +use data::Appearance; use package::Package; use physis::savedata::chardat; use regex::Regex; use reqwest::Url; -use serde::Deserialize; use std::io::Write; use std::time::{SystemTime, UNIX_EPOCH}; #[cfg(target_family = "wasm")] @@ -124,7 +123,7 @@ pub async fn archive_character(id: u64, use_dalamud: bool) -> Result, Ar LODESTONE_HOST }; - let char_page_url = Url::parse(&format!("{lodestone_host}/lodestone/character/{}/", id)) + let char_page_url = Url::parse(&format!("{lodestone_host}/lodestone/character/{id}/")) .map_err(|_| ArchiveError::UnknownError)?; let char_page = download(&char_page_url) .await @@ -135,8 +134,7 @@ pub async fn archive_character(id: u64, use_dalamud: bool) -> Result, Ar parser::parse_profile(&char_page, &mut char_data); let classjob_page_url = Url::parse(&format!( - "{lodestone_host}/lodestone/character/{}/class_job/", - id + "{lodestone_host}/lodestone/character/{id}/class_job/" )) .map_err(|_| ArchiveError::UnknownError)?; let classjob_page = download(&classjob_page_url) @@ -160,14 +158,14 @@ pub async fn archive_character(id: u64, use_dalamud: bool) -> Result, Ar } else { &char_data.portrait_url }; - let portrait_url = Url::parse(&portrait_url).map_err(|_| ArchiveError::UnknownError)?; + let portrait_url = Url::parse(portrait_url).map_err(|_| ArchiveError::UnknownError)?; let portrait = download(&portrait_url) .await .map_err(|_| ArchiveError::DownloadFailed(portrait_url.to_string()))?; zip.start_file("portrait.jpg", options)?; - zip.write_all(&*portrait)?; + zip.write_all(&portrait)?; } if !char_data.face_url.is_empty() { let face_url = if cfg!(target_family = "wasm") { @@ -175,26 +173,26 @@ pub async fn archive_character(id: u64, use_dalamud: bool) -> Result, Ar } else { &char_data.face_url }; - let face_url = Url::parse(&face_url).map_err(|_| ArchiveError::UnknownError)?; + let face_url = Url::parse(face_url).map_err(|_| ArchiveError::UnknownError)?; let face = download(&face_url) .await .map_err(|_| ArchiveError::DownloadFailed(face_url.to_string()))?; zip.start_file("face.jpg", options)?; - zip.write_all(&*face)?; + zip.write_all(&face)?; } if use_dalamud { - let dalamud_url = Url::parse(&"http://localhost:42072/package") - .map_err(|_| ArchiveError::UnknownError)?; + let dalamud_url = + Url::parse("http://localhost:42072/package").map_err(|_| ArchiveError::UnknownError)?; let package = download(&dalamud_url) .await .map_err(|_| ArchiveError::CouldNotConnectToDalamud)?; let package = String::from_utf8(package).map_err(|_| ArchiveError::ParsingError)?; // Remove BOM at the start let package = package.trim_start_matches("\u{feff}"); - let package: Package = serde_json::from_str(&package.trim_start()).unwrap(); + let package: Package = serde_json::from_str(package.trim_start()).unwrap(); // appearance data char_data.appearance = Some(Appearance { @@ -264,7 +262,7 @@ pub async fn archive_character(id: u64, use_dalamud: bool) -> Result, Ar zip.start_file("plate-portrait.png", options)?; zip.write_all( - &*BASE64_STANDARD + &BASE64_STANDARD .decode( package .portrait @@ -276,7 +274,7 @@ pub async fn archive_character(id: u64, use_dalamud: bool) -> Result, Ar if let Some(base_plate) = package.base_plate { zip.start_file("base-plate.png", options)?; zip.write_all( - &*BASE64_STANDARD + &BASE64_STANDARD .decode(base_plate.trim_start_matches("data:image/png;base64,")) .unwrap(), )?; @@ -285,7 +283,7 @@ pub async fn archive_character(id: u64, use_dalamud: bool) -> Result, Ar if let Some(pattern_overlay) = package.pattern_overlay { zip.start_file("pattern-overlay.png", options)?; zip.write_all( - &*BASE64_STANDARD + &BASE64_STANDARD .decode(pattern_overlay.trim_start_matches("data:image/png;base64,")) .unwrap(), )?; @@ -294,7 +292,7 @@ pub async fn archive_character(id: u64, use_dalamud: bool) -> Result, Ar if let Some(backing) = package.backing { zip.start_file("backing.png", options)?; zip.write_all( - &*BASE64_STANDARD + &BASE64_STANDARD .decode(backing.trim_start_matches("data:image/png;base64,")) .unwrap(), )?; @@ -303,7 +301,7 @@ pub async fn archive_character(id: u64, use_dalamud: bool) -> Result, Ar if let Some(top_border) = package.top_border { zip.start_file("top-border.png", options)?; zip.write_all( - &*BASE64_STANDARD + &BASE64_STANDARD .decode(top_border.trim_start_matches("data:image/png;base64,")) .unwrap(), )?; @@ -312,7 +310,7 @@ pub async fn archive_character(id: u64, use_dalamud: bool) -> Result, Ar if let Some(bottom_border) = package.bottom_border { zip.start_file("bottom-border.png", options)?; zip.write_all( - &*BASE64_STANDARD + &BASE64_STANDARD .decode(bottom_border.trim_start_matches("data:image/png;base64,")) .unwrap(), )?; @@ -321,7 +319,7 @@ pub async fn archive_character(id: u64, use_dalamud: bool) -> Result, Ar if let Some(portrait_frame) = package.portrait_frame { zip.start_file("portrait-frame.png", options)?; zip.write_all( - &*BASE64_STANDARD + &BASE64_STANDARD .decode(portrait_frame.trim_start_matches("data:image/png;base64,")) .unwrap(), )?; @@ -330,7 +328,7 @@ pub async fn archive_character(id: u64, use_dalamud: bool) -> Result, Ar if let Some(plate_frame) = package.plate_frame { zip.start_file("plate-frame.png", options)?; zip.write_all( - &*BASE64_STANDARD + &BASE64_STANDARD .decode(plate_frame.trim_start_matches("data:image/png;base64,")) .unwrap(), )?; @@ -339,7 +337,7 @@ pub async fn archive_character(id: u64, use_dalamud: bool) -> Result, Ar if let Some(accent) = package.accent { zip.start_file("accent.png", options)?; zip.write_all( - &*BASE64_STANDARD + &BASE64_STANDARD .decode(accent.trim_start_matches("data:image/png;base64,")) .unwrap(), )?; @@ -388,11 +386,11 @@ pub async fn archive_character(id: u64, use_dalamud: bool) -> Result, Ar }; zip.start_file("FFXIV_CHARA_01.dat", options)?; - zip.write_all(&*char_dat.write_to_buffer().unwrap())?; + zip.write_all(&char_dat.write_to_buffer().unwrap())?; // Stop the HTTP server let stop_url = - Url::parse(&"http://localhost:42072/stop").map_err(|_| ArchiveError::UnknownError)?; + Url::parse("http://localhost:42072/stop").map_err(|_| ArchiveError::UnknownError)?; // I'm intentionally ignoring the message because it doesn't matter if it fails - and it usually does let _ = download(&stop_url).await; } diff --git a/src/parser.rs b/src/parser.rs index 501b560..98fa8f2 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -17,16 +17,14 @@ pub fn parse_search(data: &str) -> String { let mut href = String::new(); for element in document.select(&Selector::parse(ENTRY_SELECTOR).unwrap()) { - if let Some(block_name) = element + if let Some(_) = element .select(&Selector::parse(ENTRY_NAME_SELECTOR).unwrap()) - .nth(0) - { - if let Some(block_name) = element + .next() + && let Some(block_name) = element .select(&Selector::parse("a.entry__link").unwrap()) - .nth(0) - { - href = block_name.attr("href").unwrap().parse().unwrap(); - } + .next() + { + href = block_name.attr("href").unwrap().parse().unwrap(); } } @@ -54,34 +52,35 @@ pub fn parse_profile(data: &str, char_data: &mut CharacterData) { if let Some(title) = document .select(&Selector::parse(TITLE_SELECTOR).unwrap()) - .nth(0) + .next() { char_data.title = Some(title.inner_html().as_str().to_string()); } + let world_re = Regex::new(r"(\w+)\s\[(\w+)\]").unwrap(); for element in document.select(&Selector::parse(WORLD_DATA_CENTER_SELECTOR).unwrap()) { - let re = Regex::new(r"(\w+)\s\[(\w+)\]").unwrap(); let inner_html = element.inner_html(); - let captures = re.captures(&inner_html).unwrap(); + let captures = world_re.captures(&inner_html).unwrap(); // TODO: use error char_data.world = WorldValue::try_from(captures.get(1).unwrap().as_str()).unwrap(); char_data.data_center = captures.get(2).unwrap().as_str().to_owned(); } + let block_re = Regex::new(r"([^<]+)
([^\/]+)\s\/\s(\W)").unwrap(); + let grand_re = Regex::new(r"([^\/]+)\s\/\s([^\/]+)").unwrap(); for element in document.select(&Selector::parse(CHARACTER_BLOCK_SELECTOR).unwrap()) { if let Some(block_title) = element .select(&Selector::parse(CHARACTER_BLOCK_TITLE_SELECTOR).unwrap()) - .nth(0) + .next() { let name = block_title.inner_html(); if name == "Race/Clan/Gender" { if let Some(block_name) = element .select(&Selector::parse(CHARACTER_BLOCK_NAME_SELECTOR).unwrap()) - .nth(0) + .next() { - let re = Regex::new(r"([^<]+)
([^\/]+)\s\/\s(\W)").unwrap(); let inner_html = block_name.inner_html(); - let captures = re.captures(&inner_html).unwrap(); + let captures = block_re.captures(&inner_html).unwrap(); char_data.race = RaceValue::try_from(captures.get(1).unwrap().as_str()).unwrap(); @@ -93,7 +92,7 @@ pub fn parse_profile(data: &str, char_data: &mut CharacterData) { } else if name == "City-state" { if let Some(block_name) = element .select(&Selector::parse(CHARACTER_BLOCK_NAME_SELECTOR).unwrap()) - .nth(0) + .next() { char_data.city_state = CityStateValue::try_from(block_name.inner_html().as_str()).unwrap(); @@ -106,29 +105,27 @@ pub fn parse_profile(data: &str, char_data: &mut CharacterData) { if let Some(block_name) = element .select(&Selector::parse(CHARACTER_BLOCK_NAME_SELECTOR).unwrap()) - .nth(0) + .next() { char_data.guardian = GuardianValue::try_from(block_name.inner_html().as_str()).unwrap(); } - } else if name == "Grand Company" { - if let Some(block_name) = element + } else if name == "Grand Company" + && let Some(block_name) = element .select(&Selector::parse(CHARACTER_BLOCK_NAME_SELECTOR).unwrap()) - .nth(0) - { - let re = Regex::new(r"([^\/]+)\s\/\s([^\/]+)").unwrap(); - let inner_html = block_name.inner_html(); - let captures = re.captures(&inner_html).unwrap(); + .next() + { + let inner_html = block_name.inner_html(); + let captures = grand_re.captures(&inner_html).unwrap(); - char_data.grand_company.name = captures.get(1).unwrap().as_str().to_string(); - char_data.grand_company.rank = captures.get(2).unwrap().as_str().to_string(); - } + char_data.grand_company.name = captures.get(1).unwrap().as_str().to_string(); + char_data.grand_company.rank = captures.get(2).unwrap().as_str().to_string(); } } if let Some(free_company) = element .select(&Selector::parse(FREE_COMPANY_SELECTOR).unwrap()) - .nth(0) + .next() { char_data.free_company = Some(free_company.inner_html().as_str().to_string()); } @@ -138,9 +135,9 @@ pub fn parse_profile(data: &str, char_data: &mut CharacterData) { char_data.face_url = element.attr("src").unwrap().parse().unwrap(); } - for element in document + if let Some(element) = document .select(&Selector::parse(PORTRAIT_IMG_SELECTOR).unwrap()) - .nth(0) + .next() { char_data.portrait_url = element.attr("src").unwrap().parse().unwrap(); } @@ -163,28 +160,28 @@ pub fn parse_profile(data: &str, char_data: &mut CharacterData) { ]; for (i, selector) in item_slot_selectors.iter().enumerate() { - if let Some(slot) = document.select(&Selector::parse(selector).unwrap()).nth(0) { - if let Some(item) = slot.select(&Selector::parse(".db-tooltip").unwrap()).nth(0) { - let parsed_item = parse_item_tooltip(&item); - let slot = match i { - 0 => &mut char_data.equipped.main_hand, - 1 => &mut char_data.equipped.off_hand, - 2 => &mut char_data.equipped.head, - 3 => &mut char_data.equipped.body, - 4 => &mut char_data.equipped.hands, - 5 => &mut char_data.equipped.legs, - 6 => &mut char_data.equipped.feet, - 7 => &mut char_data.equipped.earrings, - 8 => &mut char_data.equipped.necklace, - 9 => &mut char_data.equipped.bracelets, - 10 => &mut char_data.equipped.left_ring, - 11 => &mut char_data.equipped.right_ring, - 12 => &mut char_data.equipped.soul_crystal, - _ => panic!("Unexpected slot!"), - }; + if let Some(slot) = document.select(&Selector::parse(selector).unwrap()).next() + && let Some(item) = slot.select(&Selector::parse(".db-tooltip").unwrap()).next() + { + let parsed_item = parse_item_tooltip(&item); + let slot = match i { + 0 => &mut char_data.equipped.main_hand, + 1 => &mut char_data.equipped.off_hand, + 2 => &mut char_data.equipped.head, + 3 => &mut char_data.equipped.body, + 4 => &mut char_data.equipped.hands, + 5 => &mut char_data.equipped.legs, + 6 => &mut char_data.equipped.feet, + 7 => &mut char_data.equipped.earrings, + 8 => &mut char_data.equipped.necklace, + 9 => &mut char_data.equipped.bracelets, + 10 => &mut char_data.equipped.left_ring, + 11 => &mut char_data.equipped.right_ring, + 12 => &mut char_data.equipped.soul_crystal, + _ => panic!("Unexpected slot!"), + }; - *slot = parsed_item; - } + *slot = parsed_item; } } } @@ -201,15 +198,15 @@ pub fn parse_classjob(data: &str, char_data: &mut CharacterData) { for element in document.select(&Selector::parse(CLASSJOB_SELECTOR).unwrap()) { let level = element .select(&Selector::parse(CLASSJOB_LEVEL_SELECTOR).unwrap()) - .nth(0) + .next() .unwrap(); let name = element .select(&Selector::parse(CLASSJOB_NAME_SELECTOR).unwrap()) - .nth(0) + .next() .unwrap(); let exp_element = element .select(&Selector::parse(CLASSJOB_EXP_SELECTOR).unwrap()) - .nth(0) + .next() .unwrap(); let mut exp = None; @@ -234,7 +231,7 @@ pub fn parse_classjob(data: &str, char_data: &mut CharacterData) { fn parse_item_tooltip(element: &scraper::ElementRef<'_>) -> Option { if let Some(slot) = element .select(&Selector::parse(".db-tooltip__item__name").unwrap()) - .nth(0) + .next() { let mut text: String = slot.text().collect(); if text.contains("\u{e03c}") { diff --git a/src/value.rs b/src/value.rs index b6208ea..583d19c 100644 --- a/src/value.rs +++ b/src/value.rs @@ -283,7 +283,7 @@ impl TryFrom<&str> for NamedayValue { fn try_from(value: &str) -> Result { let re = Regex::new(r"(\d{1,2})[^\d]+(\d{1,2})").unwrap(); - let captures = re.captures(&value).unwrap(); + let captures = re.captures(value).unwrap(); Ok(Self { value: value.to_string(),