Skip to content

Commit

Permalink
Merge pull request #35 from lgou2w/next
Browse files Browse the repository at this point in the history
v0.4.1
  • Loading branch information
lgou2w committed Jul 13, 2024
2 parents 0b6e28c + f80a331 commit b5c6911
Show file tree
Hide file tree
Showing 17 changed files with 167 additions and 69 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "hoyo.gacha",
"version": "0.4.0",
"version": "0.4.1",
"description": "An unofficial tool for managing and analyzing your miHoYo gacha records",
"author": "lgou2w <[email protected]>",
"homepage": "https://github.com/lgou2w/HoYo.Gacha#readme",
Expand Down
2 changes: 1 addition & 1 deletion src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "hoyo_gacha"
version = "0.4.0"
version = "0.4.1"
edition = "2021"
authors = ["lgou2w"]
description = "An unofficial tool for managing and analyzing your miHoYo gacha records"
Expand Down
9 changes: 7 additions & 2 deletions src-tauri/src/commands.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
use crate::constants;
use crate::error::Result;
use std::time::Duration;

use reqwest::Client as Reqwest;
use serde::{Deserialize, Serialize};
use tauri::{Invoke, Runtime};

use crate::constants;
use crate::error::Result;

pub fn get_handlers<R: Runtime>() -> Box<dyn Fn(Invoke<R>) + Send + Sync> {
Box::new(tauri::generate_handler![
get_current_exe_dir,
Expand Down Expand Up @@ -67,6 +70,7 @@ async fn get_latest_version() -> Result<LatestVersion> {
Reqwest::builder()
.build()?
.get("https://hoyo-gacha.lgou2w.com/release/latest")
.timeout(Duration::from_secs(15))
.send()
.await?
.json::<LatestVersion>()
Expand All @@ -93,6 +97,7 @@ async fn update_app(latest_version: LatestVersion) -> Result<()> {
.build()?
.get("https://hoyo-gacha.lgou2w.com/release/download")
.query(&[("id", latest_version.id.to_string())])
.timeout(Duration::from_secs(15))
.send()
.await?
.error_for_status()?;
Expand Down
7 changes: 4 additions & 3 deletions src-tauri/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,6 @@ pub enum Error {
#[error(transparent)]
Db(#[from] sea_orm::error::DbErr),

#[error(transparent)]
SerdeJson(#[from] serde_json::Error),

#[error(transparent)]
Tauri(#[from] tauri::Error),

Expand Down Expand Up @@ -54,6 +51,9 @@ pub enum Error {
GachaRecordFetcherChannelJoin,

// UIGF & SRGF
#[error("{0}")]
UIGFOrSRGFSerdeJson(serde_json::Error),

#[error("UIGF or SRGF Mismatched UID: expected {expected:?}, actual {actual:?}")]
UIGFOrSRGFMismatchedUID { expected: String, actual: String },

Expand Down Expand Up @@ -95,6 +95,7 @@ impl_error_identifiers! {
GachaRecordRetcode => GACHA_RECORD_RETCODE,
GachaRecordFetcherChannelSend => GACHA_RECORD_FETCHER_CHANNEL_SEND,
GachaRecordFetcherChannelJoin => GACHA_RECORD_FETCHER_CHANNEL_JOIN,
UIGFOrSRGFSerdeJson => UIGF_OR_SRGF_SERDE_JSON,
UIGFOrSRGFMismatchedUID => UIGF_OR_SRGF_MISMATCHED_UID,
UIGFOrSRGFInvalidField => UIGF_OR_SRGF_INVALID_FIELD,
AccountAlreadyExists => ACCOUNT_ALREADY_EXISTS,
Expand Down
6 changes: 3 additions & 3 deletions src-tauri/src/gacha/srgf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,14 +66,14 @@ impl SRGF {
}

pub fn from_reader(reader: impl Read) -> Result<Self> {
Ok(serde_json::from_reader(reader)?)
serde_json::from_reader(reader).map_err(Error::UIGFOrSRGFSerdeJson)
}

pub fn to_writer(&self, writer: impl Write, pretty: bool) -> Result<()> {
if pretty {
Ok(serde_json::to_writer_pretty(writer, self)?)
serde_json::to_writer_pretty(writer, self).map_err(Error::UIGFOrSRGFSerdeJson)
} else {
Ok(serde_json::to_writer(writer, self)?)
serde_json::to_writer(writer, self).map_err(Error::UIGFOrSRGFSerdeJson)
}
}
}
Expand Down
6 changes: 3 additions & 3 deletions src-tauri/src/gacha/uigf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,14 +68,14 @@ impl UIGF {
}

pub fn from_reader(reader: impl Read) -> Result<Self> {
Ok(serde_json::from_reader(reader)?)
serde_json::from_reader(reader).map_err(Error::UIGFOrSRGFSerdeJson)
}

pub fn to_writer(&self, writer: impl Write, pretty: bool) -> Result<()> {
if pretty {
Ok(serde_json::to_writer_pretty(writer, self)?)
serde_json::to_writer_pretty(writer, self).map_err(Error::UIGFOrSRGFSerdeJson)
} else {
Ok(serde_json::to_writer(writer, self)?)
serde_json::to_writer(writer, self).map_err(Error::UIGFOrSRGFSerdeJson)
}
}
}
Expand Down
21 changes: 15 additions & 6 deletions src-tauri/src/gacha/utilities.rs
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,10 @@ pub(super) fn lookup_valid_cache_data_dir<P: AsRef<Path>>(game_data_dir: P) -> R

// Read webCaches directory
let web_caches_dir = game_data_dir.as_ref().join("webCaches");
if !web_caches_dir.exists() {
return Err(Error::WebCaches);
}

for entry in read_dir(&web_caches_dir)? {
let entry = entry?;
let entry_path = entry.path();
Expand Down Expand Up @@ -309,13 +313,18 @@ async fn request_gacha_url<T: Sized + DeserializeOwned>(
) -> Result<GachaResponse<T>> {
let response: GachaResponse<T> = reqwest.get(url).send().await?.json().await?;
if response.retcode != 0 {
match response.retcode {
-101 => Err(Error::TimeoutdGachaUrl),
-110 => Err(Error::VisitTooFrequentlyGachaUrl),
_ => Err(Error::GachaRecordRetcode {
retcode: response.retcode,
let retcode = response.retcode;
let message = &response.message;

if retcode == -101 || message.contains("authkey") || message.contains("auth key") {
Err(Error::TimeoutdGachaUrl)
} else if retcode == -110 || message.contains("visit too frequently") {
Err(Error::VisitTooFrequentlyGachaUrl)
} else {
Err(Error::GachaRecordRetcode {
retcode,
message: response.message,
}),
})
}
} else {
Ok(response)
Expand Down
2 changes: 1 addition & 1 deletion src-tauri/tauri.conf.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
},
"package": {
"productName": "HoYo_Gacha",
"version": "0.4.0"
"version": "0.4.1"
},
"tauri": {
"allowlist": {
Expand Down
4 changes: 2 additions & 2 deletions src/components/account/AccountMenuDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -281,13 +281,13 @@ function AccountMenuDialogForm (props: AccountMenuDialogFormProps) {
name="gameDataDir" label="游戏数据文件夹" type="text"
placeholder={'例如:' + FacetGameDataDirExamples[facet]}
variant="filled" size="small" margin="dense"
fullWidth required
fullWidth required multiline
disabled={busy}
error={!!errors.gameDataDir}
helperText={errors.gameDataDir?.message}
InputProps={{
...register('gameDataDir', {
required: '请选择游戏数据目录!'
required: '请选择游戏数据文件夹!'
}),
readOnly: true,
startAdornment: (
Expand Down
25 changes: 21 additions & 4 deletions src/components/common/VersionChecker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,14 +47,31 @@ export default function VersionChecker () {
}, [latestVersion.data, setBusy])

if (!version.data) {
return <Typography component="span" variant="body2" color="warning">版本更新不可用</Typography>
return (
<Typography component="span" variant="body2" color="warning">版本更新不可用</Typography>
)
}

if (latestVersion.isLoading) return <Typography component="span" variant="body2">加载中...</Typography>
if (latestVersion.isError) return <Typography component="span" variant="body2" color="error">检查最新版本失败</Typography>
if (latestVersion.isLoading) {
return (
<Typography component="span" variant="body2" color="secondary">检查更新中...</Typography>
)
}

if (latestVersion.isError) {
return (
<Typography component="span" variant="body2" color="error">
检查最新版本失败:{latestVersion.error.message}
</Typography>
)
}

const needUpdate = isNeedUpdate(version.data, latestVersion.data)
if (!needUpdate) return <Typography component="span" variant="body2" color="error">已是最新版本</Typography>
if (!needUpdate) {
return (
<Typography component="span" variant="body2" color="error">已是最新版本</Typography>
)
}

return (
<Button size="small" color="info"
Expand Down
51 changes: 26 additions & 25 deletions src/components/gacha/GachaItemView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@ import Typography from '@mui/material/Typography'
import GenshinUIRarity3Background from '@/assets/images/genshin/UI_Rarity_3_Background.png'
import GenshinUIRarity4Background from '@/assets/images/genshin/UI_Rarity_4_Background.png'
import GenshinUIRarity5Background from '@/assets/images/genshin/UI_Rarity_5_Background.png'
import { ItemTypeCategoryMappings } from './ItemTypeCategoryMappings'
import { lookupAssetIcon } from './icons'
import dayjs from '@/utilities/dayjs'

export interface GachaItemViewProps {
facet: AccountFacet
name: string
id: string
lang: string
itemName: string
itemId: undefined | null | string
itemType: string
rank: 3 | 4 | 5 | '3' | '4' | '5'
size: number
Expand All @@ -22,47 +24,46 @@ export interface GachaItemViewProps {
}

export default function GachaItemView (props: GachaItemViewProps) {
const { facet, name, id, itemType, rank, size, usedPity, restricted, time } = props

const category = ItemTypeCategoryMappings[itemType]
const icon = lookupAssetIcon(facet, category, id)

let src = icon?.[1]
if (!src) {
src = getRemoteResourceSrc(facet, category, id)
}
const { facet, lang, itemName, itemId, itemType, rank, size, usedPity, restricted, time } = props
const iconSrc = resolveItemIconSrc(facet, lang, itemName, itemId, itemType)

const title = !time
? name
: name + '\n' + dayjs(time).format('LLLL')
? itemName
: itemName + '\n' + dayjs(time).format('LLLL')

return (
<Box className={GachaItemViewCls} sx={GachaItemViewSx}
width={size} height={size}
data-facet={facet}
data-rank={rank}
data-restricted={restricted}
data-category={category}
title={title}
>
<img src={src} alt={name} />
<img src={iconSrc} alt={itemName} />
{usedPity && <Typography className={`${GachaItemViewCls}-used-pity`}>{usedPity}</Typography>}
{restricted && <Typography className={`${GachaItemViewCls}-restricted`}>限定</Typography>}
</Box>
)
}

const ItemTypeCategoryMappings: Record<string, 'character' | 'weapon' | 'bangboo'> = {
角色: 'character',
武器: 'weapon',
光锥: 'weapon',
代理人: 'character',
音擎: 'weapon',
邦布: 'bangboo'
}
function resolveItemIconSrc (facet: AccountFacet, lang: string, itemName: string, itemId: string | undefined | null, itemType: string): string | undefined {
const category = ItemTypeCategoryMappings[facet][lang]?.[itemType]
if (!category) {
console.error('Unable to resolve item icon category:', facet, lang, itemType, itemName, itemId)
return undefined
}

// HACK: GenshinImpact embedded icons are only available for zh-cn language, go to Remote Loading
if (facet === AccountFacet.Genshin && lang !== 'zh-cn') {
return `https://hoyo-gacha.lgou2w.com/static/${facet}/${lang}/${category}/cutted/${itemName}.png`
}

function getRemoteResourceSrc (facet: AccountFacet, category: string, itemIdOrName: string) {
return `https://hoyo-gacha.lgou2w.com/static/${facet}/${category}/cutted/${itemIdOrName}.png`
const embedded = lookupAssetIcon(facet, category, itemId || itemName)
if (embedded) {
return embedded[1]
} else {
return `https://hoyo-gacha.lgou2w.com/static/${facet}/${category}/cutted/${itemId || itemName}.png`
}
}

const GachaItemViewCls = 'gacha-item-view'
Expand Down
27 changes: 15 additions & 12 deletions src/components/gacha/GachaLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,17 +68,19 @@ export default function GachaLayout () {
if (error && (error instanceof Error || typeof error === 'object')) {
const msg = (error as { message: string }).message
const identifier = (error as { identifier?: string }).identifier
let knownMessage = identifier ? KnownErrorIdentifiers[identifier] : undefined
if (knownMessage) {
const knownMessage = identifier ? KnownErrorIdentifiers[identifier] : undefined
const appendMessage = Array.isArray(knownMessage)
let pretty = appendMessage ? knownMessage[0] : knownMessage
if (pretty && appendMessage) {
let index: number
if ((index = msg.indexOf(':')) !== -1) {
knownMessage += msg.substring(index + 1)
}
if (identifier === 'INTERNAL_CRATE') {
knownMessage += msg
pretty += msg.substring(index)
} else if (appendMessage) {
pretty += msg
}
}
message = knownMessage || msg

message = pretty || msg
} else if (error) {
message = String(error)
}
Expand Down Expand Up @@ -119,13 +121,14 @@ export default function GachaLayout () {
)
}

const KnownErrorIdentifiers: Record<string, string> = {
INTERNAL_CRATE: '内部错误:',
WEB_CACHES: '读取硬盘缓存失败:未找到正确目录!请尝试在游戏内打开抽卡历史记录界面!',
const KnownErrorIdentifiers: Record<string, string | [string]> = {
INTERNAL_CRATE: ['内部错误'],
WEB_CACHES: '读取硬盘缓存失败:未找到有效文件夹!请检查账号的游戏数据文件夹是否正确!',
ILLEGAL_GACHA_URL: '无效的抽卡链接!',
VACANT_GACHA_URL: '未找到有效的抽卡链接。请尝试在游戏内打开抽卡历史记录界面!',
TIMEOUTD_GACHA_URL: '抽卡链接已经过期失效。请重新在游戏内打开抽卡历史记录界面!',
VISIT_TOO_FREQUENTLY_GACHA_URL: '请求获取抽卡记录 API 速率过快!请稍等几秒后再次重试!',
UIGF_OR_SRGF_MISMATCHED_UID: '待导入的 UIGF 或 SRGF 数据 UID 与当前账号不匹配!',
UIGF_OR_SRGF_INVALID_FIELD: '待导入的 UIGF 或 SRGF 数据中存在无效的字段!'
UIGF_OR_SRGF_SERDE_JSON: ['待导入的 UIGF 或 SRGF 数据文件格式不正确'],
UIGF_OR_SRGF_MISMATCHED_UID: ['待导入的 UIGF 或 SRGF 数据 UID 与当前账号不匹配'],
UIGF_OR_SRGF_INVALID_FIELD: ['待导入的 UIGF 或 SRGF 数据中存在无效的字段']
}
Loading

0 comments on commit b5c6911

Please sign in to comment.