From 1cec836a4b0b72a8486f4add4d5ff2917eea7900 Mon Sep 17 00:00:00 2001 From: Youmu Date: Tue, 2 Feb 2021 22:42:16 -0500 Subject: [PATCH] (fix) adapt to the new format of game client 1.3 --- src/client.rs | 100 ++++++++++++++++++++---------------------- src/data_type.rs | 33 +++----------- src/report/mod.rs | 4 +- src/report/summary.rs | 42 +++++++++--------- 4 files changed, 77 insertions(+), 102 deletions(-) diff --git a/src/client.rs b/src/client.rs index 9534ed2..aa7bd3f 100644 --- a/src/client.rs +++ b/src/client.rs @@ -45,7 +45,9 @@ fn item_list_url(region: &str, lang: &str) -> anyhow::Result { } /// ID for "The Stringless", used to identify the local identifier for weapon -const WEAPON_ID: usize = 15405; +const WEAPON_ID: &str = "15405"; +/// ID for "Venti", used to identify the local identifier for character +const CHARACTER_ID: &str = "1022"; /// The user-agent to use const USER_AGENT: &str = "Mozilla/5.0 (Windows NT 6.1; Unity 3D; ZFBrowser 2.1.0; Genshin Impact 1.2.0_1565149_1627898) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.96 Safari/537.36"; @@ -92,10 +94,11 @@ struct GachaResult { #[serde_as(as = "DisplayFromStr")] uid: usize, gacha_type: String, - #[serde_as(as = "DisplayFromStr")] - item_id: usize, count: String, time: String, + #[serde(flatten)] + item: GachaItem, + lang: String, } /// Payload for endpoint `getGachaLog` @@ -113,8 +116,7 @@ struct GachaResultPage { #[serde_as] #[derive(Debug, Serialize, Deserialize)] struct GachaItem { - #[serde_as(as = "DisplayFromStr")] - item_id: usize, + item_id: String, name: String, item_type: String, #[serde_as(as = "DisplayFromStr")] @@ -124,10 +126,12 @@ struct GachaItem { /// A client used to query Genshin gacha info #[derive(Debug)] pub struct Client { + /// identifier for a weapon + weapon_identifier: String, + /// identifier for a character + character_identifier: String, /// metadata for pools pools: Vec, - /// metadata for items - items: HashMap, /// backing http client client: ReqClient, /// base query to use @@ -168,13 +172,14 @@ impl Client { let pools_task = Self::request_pools(&client, &base_query, &base_url, pools_pb); let items_task = Self::request_items(&client, &base_query, items_pb); let progress_task = spawn_blocking(move || mp.join()); - let (pools, items, _) = tokio::join!(pools_task, items_task, progress_task); + let (pools, identifiers, _) = tokio::join!(pools_task, items_task, progress_task); let pools = pools.context("加载卡池列表失败")?; - let items = items.context("加载图鉴失败")?; + let (weapon_identifier, character_identifier) = identifiers.context("加载图鉴失败")?; Ok(Self { + weapon_identifier, + character_identifier, pools, - items, client, base_query, base_url, @@ -187,7 +192,7 @@ impl Client { } /// Get a chronological log of all the pulls from `pool` - pub async fn request_gacha_log(&self, pool: &Pool) -> anyhow::Result>> { + pub async fn request_gacha_log(&self, pool: &Pool) -> anyhow::Result> { // set up additional queries let query = vec![ ("init_type".to_owned(), pool.key.clone()), @@ -220,11 +225,26 @@ impl Client { let page: Vec = page .list .into_iter() - .filter_map(|pull| { - self.items.get(&pull.item_id).map(|item| Pull { - time: Local.datetime_from_str(&pull.time, "%Y-%m-%d %T").unwrap(), - item, - }) + .map(|pull| Pull { + time: Local.datetime_from_str(&pull.time, "%Y-%m-%d %T").unwrap(), + item: { + let rarity = match pull.item.rank_type { + 5 => Rarity::Five, + 4 => Rarity::Four, + 3 => Rarity::Three, + _ => unreachable!("图鉴中含有范围外的稀有度"), + }; + let item_type = if pull.item.item_type == self.weapon_identifier { + ItemType::Weapon + } else { + ItemType::Character + }; + Item { + name: pull.item.name, + rarity, + item_type, + } + }, }) .collect(); Ok::<_, anyhow::Error>(page) @@ -290,12 +310,12 @@ impl Client { .collect()) } - /// Get a list of items in the game + /// Get the identifier for weapon and character async fn request_items( client: &ReqClient, base_query: &BaseQuery, pb: ProgressBar, - ) -> anyhow::Result> { + ) -> anyhow::Result<(String, String)> { pb.set_message("加载图鉴"); // get region/lang specific url let url = item_list_url(&base_query.region, &base_query.lang)?; @@ -305,44 +325,20 @@ impl Client { .await? .json::>() .await?; - let item_db: HashMap = item_list - .into_iter() - .map(|item| (item.item_id, item)) - .collect(); - - // determine the identifier for all the weapons in the current language setting - let weapon_identifier = item_db - .get(&WEAPON_ID) + let weapon_identifier = item_list + .iter() + .find(|item| item.item_id == WEAPON_ID) .ok_or(anyhow!("内置的绝弦ID已过期,无法建立图鉴"))? .item_type .clone(); - // convert api response to our type - let item_db = item_db - .into_iter() - .map(|(id, item)| { - let rarity = match item.rank_type { - 5 => Rarity::Five, - 4 => Rarity::Four, - 3 => Rarity::Three, - _ => unreachable!("图鉴中含有范围外的稀有度"), - }; - let item_type = if item.item_type == weapon_identifier { - ItemType::Weapon - } else { - ItemType::Character - }; - ( - id, - Item { - name: item.name, - rarity, - item_type, - }, - ) - }) - .collect(); + let character_identifier = item_list + .iter() + .find(|item| item.item_id == CHARACTER_ID) + .ok_or(anyhow!("内置的温迪ID已过期,无法建立图鉴"))? + .item_type + .clone(); pb.finish_with_message("已加载图鉴"); - Ok(item_db) + Ok((weapon_identifier, character_identifier)) } /// Get response from Genshin API server diff --git a/src/data_type.rs b/src/data_type.rs index 4ef8ab2..2620d77 100644 --- a/src/data_type.rs +++ b/src/data_type.rs @@ -1,13 +1,9 @@ -use std::{ - fmt, - hash::{Hash, Hasher}, - ptr, -}; +use std::{fmt, hash::Hash}; use chrono::{DateTime, Local}; use enum_map::Enum; -#[derive(Debug, Enum, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Debug, Enum, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum ItemType { Weapon, Character, @@ -22,7 +18,7 @@ impl fmt::Display for ItemType { } } -#[derive(Debug, Enum, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Debug, Enum, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum Rarity { Three, Four, @@ -40,35 +36,18 @@ impl fmt::Display for Rarity { } /// information of an item -#[derive(Debug)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Item { pub name: String, pub item_type: ItemType, pub rarity: Rarity, } -impl<'a> Hash for &'a Item { - fn hash(&self, hasher: &mut H) - where - H: Hasher, - { - ptr::hash(*self as *const Item, hasher); - } -} - -impl<'a> PartialEq for &'a Item { - fn eq(&self, other: &Self) -> bool { - (*self as *const Item) == (*other as *const Item) - } -} - -impl<'a> Eq for &'a Item {} - /// result of a single gacha #[derive(Debug)] -pub struct Pull<'a> { +pub struct Pull { pub time: DateTime, - pub item: &'a Item, + pub item: Item, } /// information of a gacha pool diff --git a/src/report/mod.rs b/src/report/mod.rs index 3e5b1c0..367725f 100644 --- a/src/report/mod.rs +++ b/src/report/mod.rs @@ -5,9 +5,9 @@ use std::io::{self, Write}; use crate::data_type::Pull; /// Trait for generating analysis on gacha log -pub trait Report<'a> { +pub trait Report { /// Creating the report from a list of pulls - fn new(log: &'a Vec) -> Self; + fn new(log: &Vec) -> Self; /// Display report in the console, by default we use `write` to /// display non-styled report fn print(&self) { diff --git a/src/report/summary.rs b/src/report/summary.rs index 69b17fb..7c97e12 100644 --- a/src/report/summary.rs +++ b/src/report/summary.rs @@ -15,11 +15,11 @@ use crate::{ /// Contains a summary of basic stats regarding a gacha log #[derive(Debug)] -pub struct Summary<'a> { +pub struct Summary { /// total number of pulls pub len: usize, /// stats of correspondent rarity - pub stats_per_rarity: EnumMap>, + pub stats_per_rarity: EnumMap, /// stats of correspondent item type pub stats_per_type: EnumMap, } @@ -52,7 +52,7 @@ impl StylizedString { } } -impl<'a> Summary<'a> { +impl Summary { /// pretty print the summary fn write_to(&self, output: &mut T, with_style: bool) -> io::Result<()> { let stylizer: Box StylizedString> = if with_style { @@ -179,8 +179,8 @@ impl<'a> Summary<'a> { } } -impl<'a> Report<'a> for Summary<'a> { - fn new(log: &'a Vec) -> Self { +impl Report for Summary { + fn new(log: &Vec) -> Self { log.iter() .fold(IntermediateSummary::default(), |mut summary, pull| { summary.update(pull); @@ -198,17 +198,17 @@ impl<'a> Report<'a> for Summary<'a> { /// Intermediate summary while folding #[derive(Debug, Default)] -struct IntermediateSummary<'a> { +struct IntermediateSummary { /// total number of pulls len: usize, /// stats of correspondent rarity - stats_per_rarity: EnumMap>, + stats_per_rarity: EnumMap, /// stats of correspondent item type stats_per_type: EnumMap, } -impl<'a> IntermediateSummary<'a> { - fn update(&mut self, pull: &'a Pull) { +impl IntermediateSummary { + fn update(&mut self, pull: &Pull) { self.len += 1; for (rarity, stats) in self.stats_per_rarity.iter_mut() { stats.update(rarity, pull); @@ -217,8 +217,8 @@ impl<'a> IntermediateSummary<'a> { } } -impl<'a> Into> for IntermediateSummary<'a> { - fn into(self) -> Summary<'a> { +impl Into for IntermediateSummary { + fn into(self) -> Summary { let mut stats_per_rarity = EnumMap::new(); stats_per_rarity.extend( self.stats_per_rarity @@ -235,36 +235,36 @@ impl<'a> Into> for IntermediateSummary<'a> { /// Statistics classified by rarity #[derive(Default, Debug)] -pub struct StatsForRarity<'a> { +pub struct StatsForRarity { /// total pulls in this rarity pub num: usize, pub current_streak: usize, pub longest_streak: usize, pub current_drought: usize, pub longest_drought: usize, - pub sorted_occurrence: Vec<(&'a Item, usize)>, + pub sorted_occurrence: Vec<(Item, usize)>, } /// Intermediate statistics classified by rarity #[derive(Default, Debug)] -struct IntermediateStatsForRarity<'a> { +struct IntermediateStatsForRarity { /// total pulls in this rarity num: usize, current_streak: usize, longest_streak: usize, current_drought: usize, longest_drought: usize, - occurrences: HashMap<&'a Item, usize>, + occurrences: HashMap, } -impl<'a> IntermediateStatsForRarity<'a> { - fn update(&mut self, rarity: Rarity, pull: &'a Pull) { +impl<'a> IntermediateStatsForRarity { + fn update(&mut self, rarity: Rarity, pull: &Pull) { if rarity == pull.item.rarity { self.num += 1; self.current_streak += 1; self.longest_streak = cmp::max(self.current_streak, self.longest_streak); self.current_drought = 0; - *self.occurrences.entry(pull.item).or_insert(0) += 1; + *self.occurrences.entry(pull.item.clone()).or_insert(0) += 1; } else { self.current_streak = 0; self.current_drought += 1; @@ -273,9 +273,9 @@ impl<'a> IntermediateStatsForRarity<'a> { } } -impl<'a> Into> for IntermediateStatsForRarity<'a> { - fn into(mut self) -> StatsForRarity<'a> { - let mut sorted_occurrence: Vec<(&'a Item, usize)> = self.occurrences.drain().collect(); +impl Into for IntermediateStatsForRarity { + fn into(mut self) -> StatsForRarity { + let mut sorted_occurrence: Vec<(Item, usize)> = self.occurrences.drain().collect(); sorted_occurrence.sort_by_key(|(_, cnt)| cmp::Reverse(*cnt)); StatsForRarity { num: self.num,