Skip to content

Commit

Permalink
(fix) adapt to the new format of game client 1.3
Browse files Browse the repository at this point in the history
  • Loading branch information
johnmave126 committed Feb 3, 2021
1 parent 78cc9db commit 1cec836
Show file tree
Hide file tree
Showing 4 changed files with 77 additions and 102 deletions.
100 changes: 48 additions & 52 deletions src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,9 @@ fn item_list_url(region: &str, lang: &str) -> anyhow::Result<Url> {
}

/// 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";
Expand Down Expand Up @@ -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`
Expand All @@ -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")]
Expand All @@ -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<Pool>,
/// metadata for items
items: HashMap<usize, Item>,
/// backing http client
client: ReqClient,
/// base query to use
Expand Down Expand Up @@ -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,
Expand All @@ -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<Vec<Pull<'_>>> {
pub async fn request_gacha_log(&self, pool: &Pool) -> anyhow::Result<Vec<Pull>> {
// set up additional queries
let query = vec![
("init_type".to_owned(), pool.key.clone()),
Expand Down Expand Up @@ -220,11 +225,26 @@ impl Client {
let page: Vec<Pull> = 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)
Expand Down Expand Up @@ -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<HashMap<usize, Item>> {
) -> anyhow::Result<(String, String)> {
pb.set_message("加载图鉴");
// get region/lang specific url
let url = item_list_url(&base_query.region, &base_query.lang)?;
Expand All @@ -305,44 +325,20 @@ impl Client {
.await?
.json::<Vec<GachaItem>>()
.await?;
let item_db: HashMap<usize, GachaItem> = 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
Expand Down
33 changes: 6 additions & 27 deletions src/data_type.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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,
Expand All @@ -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<H>(&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<Local>,
pub item: &'a Item,
pub item: Item,
}

/// information of a gacha pool
Expand Down
4 changes: 2 additions & 2 deletions src/report/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Pull>) -> Self;
fn new(log: &Vec<Pull>) -> Self;
/// Display report in the console, by default we use `write` to
/// display non-styled report
fn print(&self) {
Expand Down
42 changes: 21 additions & 21 deletions src/report/summary.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Rarity, StatsForRarity<'a>>,
pub stats_per_rarity: EnumMap<Rarity, StatsForRarity>,
/// stats of correspondent item type
pub stats_per_type: EnumMap<ItemType, StatsForType>,
}
Expand Down Expand Up @@ -52,7 +52,7 @@ impl StylizedString {
}
}

impl<'a> Summary<'a> {
impl Summary {
/// pretty print the summary
fn write_to<T: Write>(&self, output: &mut T, with_style: bool) -> io::Result<()> {
let stylizer: Box<dyn Fn(String) -> StylizedString> = if with_style {
Expand Down Expand Up @@ -179,8 +179,8 @@ impl<'a> Summary<'a> {
}
}

impl<'a> Report<'a> for Summary<'a> {
fn new(log: &'a Vec<Pull>) -> Self {
impl Report for Summary {
fn new(log: &Vec<Pull>) -> Self {
log.iter()
.fold(IntermediateSummary::default(), |mut summary, pull| {
summary.update(pull);
Expand All @@ -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<Rarity, IntermediateStatsForRarity<'a>>,
stats_per_rarity: EnumMap<Rarity, IntermediateStatsForRarity>,
/// stats of correspondent item type
stats_per_type: EnumMap<ItemType, StatsForType>,
}

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);
Expand All @@ -217,8 +217,8 @@ impl<'a> IntermediateSummary<'a> {
}
}

impl<'a> Into<Summary<'a>> for IntermediateSummary<'a> {
fn into(self) -> Summary<'a> {
impl Into<Summary> for IntermediateSummary {
fn into(self) -> Summary {
let mut stats_per_rarity = EnumMap::new();
stats_per_rarity.extend(
self.stats_per_rarity
Expand All @@ -235,36 +235,36 @@ impl<'a> Into<Summary<'a>> 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<Item, usize>,
}

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;
Expand All @@ -273,9 +273,9 @@ impl<'a> IntermediateStatsForRarity<'a> {
}
}

impl<'a> Into<StatsForRarity<'a>> for IntermediateStatsForRarity<'a> {
fn into(mut self) -> StatsForRarity<'a> {
let mut sorted_occurrence: Vec<(&'a Item, usize)> = self.occurrences.drain().collect();
impl Into<StatsForRarity> 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,
Expand Down

0 comments on commit 1cec836

Please sign in to comment.