Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

v0.4.1 #35

Merged
merged 7 commits into from
Jul 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading