diff --git a/src-tauri/src/account/helpers/import/hmcl.rs b/src-tauri/src/account/helpers/import/hmcl.rs index c807a2cb8..f826b8e2b 100644 --- a/src-tauri/src/account/helpers/import/hmcl.rs +++ b/src-tauri/src/account/helpers/import/hmcl.rs @@ -87,7 +87,26 @@ async fn microsoft_to_player( app: &AppHandle, acc: &HmclMicrosoftAccount, ) -> SJMCLResult { - let profile = fetch_minecraft_profile(app, acc.access_token.clone()).await?; + let profile_result = fetch_minecraft_profile(app, acc.access_token.clone()).await; + let profile = match profile_result { + Ok(p) => p, + Err(_) => { + return Ok( + PlayerInfo { + id: "".to_string(), + uuid: Uuid::from_str(&acc.uuid).map_err(|_| AccountError::ParseError)?, + name: acc.display_name.clone(), + player_type: PlayerType::Microsoft, + auth_account: None, + access_token: Some("%failed:access_token_expired%".to_string()), + refresh_token: Some(acc.refresh_token.clone()), + textures: load_preset_skin(app, PresetRole::Steve)?, + auth_server_url: None, + } + .with_generated_id(), + ); + } + }; let mut textures = vec![]; if let Some(skins) = &profile.skins { diff --git a/src/components/modals/import-account-info-modal.tsx b/src/components/modals/import-account-info-modal.tsx index 31320156d..962bca2cf 100644 --- a/src/components/modals/import-account-info-modal.tsx +++ b/src/components/modals/import-account-info-modal.tsx @@ -18,7 +18,7 @@ import { } from "@chakra-ui/react"; import React, { useCallback, useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; -import { LuTriangleAlert } from "react-icons/lu"; +import { LuCircleX, LuTriangleAlert } from "react-icons/lu"; import { BeatLoader } from "react-spinners"; import Empty from "@/components/common/empty"; import { OptionItemGroup } from "@/components/common/option-item"; @@ -31,7 +31,10 @@ import { useToast } from "@/contexts/toast"; import { ImportLauncherType, PlayerType } from "@/enums/account"; import { AuthServer, Player } from "@/models/account"; import { AccountService } from "@/services/account"; -import { generatePlayerDesc } from "@/utils/account"; +import { + EXPIRED_ACCESS_TOKEN_PLACEHOLDER, + generatePlayerDesc, +} from "@/utils/account"; interface ImportAccountInfoModalProps extends Omit { currAuthServers?: AuthServer[]; @@ -75,6 +78,8 @@ const ImportAccountInfoModal: React.FC = ({ const isThirdParty = (p: Player) => p.playerType === PlayerType.ThirdParty && !!p.authServer?.authUrl; + const isExpired = (p: Player) => + p.accessToken === EXPIRED_ACCESS_TOKEN_PLACEHOLDER; const handleRetrieveOtherLauncherAccountInfo = useCallback( (type: ImportLauncherType) => { @@ -99,9 +104,9 @@ const ImportAccountInfoModal: React.FC = ({ const pid = String((p as any).id); if (isThirdParty(p)) { nextPlayerChecked[pid] = - !!nextServerChecked[p.authServer!.authUrl]; + !!nextServerChecked[p.authServer!.authUrl] && !isExpired(p); } else { - nextPlayerChecked[pid] = true; + nextPlayerChecked[pid] = !isExpired(p); } }); setPlayerChecked(nextPlayerChecked); @@ -156,6 +161,7 @@ const ImportAccountInfoModal: React.FC = ({ const selectedPlayers = newPlayers.filter((p) => { const pid = String((p as any).id); if (!playerChecked[pid]) return false; + if (isExpired(p)) return false; if (isThirdParty(p)) { return !!serverChecked[p.authServer!.authUrl]; @@ -305,7 +311,7 @@ const ImportAccountInfoModal: React.FC = ({ setPlayerChecked((prev) => ({ ...prev, @@ -319,16 +325,31 @@ const ImportAccountInfoModal: React.FC = ({ /> ), - children: isInCurPlayers(p) && ( - + {isInCurPlayers(p) && ( + + + + + )} - > - - - - + {isExpired(p) && ( + + + + + + )} + ), }; })} diff --git a/src/locales/en.json b/src/locales/en.json index c8898b8fb..92bbbed5d 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -1149,7 +1149,8 @@ }, "tooltips": { "existingServer": "Existing auth server data with the same address will be overwritten.", - "existingPlayer": "Existing player data with the same UUID will be overwritten." + "existingPlayer": "Existing player data with the same UUID will be overwritten.", + "expired": "This player token has expired and cannot be imported." } }, "ImportModpackModal": { @@ -2611,4 +2612,4 @@ "title": "World Level Data - {{worldName}}" } } -} \ No newline at end of file +} diff --git a/src/locales/zh-Hans.json b/src/locales/zh-Hans.json index fc4a90e0d..d17af0926 100644 --- a/src/locales/zh-Hans.json +++ b/src/locales/zh-Hans.json @@ -1149,7 +1149,8 @@ }, "tooltips": { "existingServer": "已存在相同地址的认证服务器,导入将会覆盖数据", - "existingPlayer": "已存在相同 UUID 的角色,导入将会覆盖数据" + "existingPlayer": "已存在相同 UUID 的角色,导入将会覆盖数据", + "expired": "登录令牌已过期,无法导入此角色" } }, "ImportModpackModal": { @@ -2611,4 +2612,4 @@ "title": "世界基础数据 - {{worldName}}" } } -} \ No newline at end of file +} diff --git a/src/utils/account.ts b/src/utils/account.ts index c82df3d70..5fce870a7 100644 --- a/src/utils/account.ts +++ b/src/utils/account.ts @@ -2,6 +2,8 @@ import { t } from "i18next"; import { PlayerType } from "@/enums/account"; import { Player } from "@/models/account"; +export const EXPIRED_ACCESS_TOKEN_PLACEHOLDER = "%failed:access_token_expired%"; + export function isUuidValid(uuid: string) { return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test( uuid