import { Ban, Lock, Clock, ExternalLink, Copy, FileText, Terminal, ChevronDown, ChevronRight } from 'lucide-react'; import { Account } from '../../types/account'; import { formatDate } from '../../utils/format'; import { useTranslation, Trans } from 'react-i18next'; import ModalDialog from '../common/ModalDialog'; import { useState } from 'react'; import { showToast } from '../common/ToastContainer'; interface AccountErrorDialogProps { account: Account | null; onClose: () => void; } export default function AccountErrorDialog({ account, onClose }: AccountErrorDialogProps) { const [showRaw, setShowRaw] = useState(false); const [showGuide, setShowGuide] = useState(false); const { t } = useTranslation(); if (!account) return null; const isForbidden = !!account.quota?.is_forbidden; const isDisabled = Boolean(account.disabled); const isProxyDisabled = account.proxy_disabled; const isValidationBlocked = account.validation_blocked; const rawReason = account.validation_blocked_reason || account.disabled_reason || account.quota?.forbidden_reason || account.proxy_disabled_reason || ''; // 深度解析解析错误消息 const extractErrorMessage = (raw: string) => { const trimmed = raw.trim(); if (!trimmed) return raw; try { const parsed = JSON.parse(trimmed); let innerParsed = null; if (typeof parsed?.error === 'string') { try { innerParsed = JSON.parse(parsed.error); } catch (_) { } } // 按照优先级尝试提取消息 const msg = innerParsed?.error?.message || parsed?.error?.message || (Array.isArray(parsed?.error?.details) ? parsed.error.details[0]?.message : null) || parsed?.message || raw; return String(msg); } catch (_) { // 不处理 } return raw; }; const extractActionInfo = (raw: string): { url: string | null, label: string | null } => { if (account.validation_url) { return { url: account.validation_url, label: null }; } const trimmed = raw.trim(); try { const parsed = JSON.parse(trimmed); // Google API 返回的链接通常在 metadata 中 const metadata = parsed?.error?.details?.[0]?.metadata; let url = metadata?.appeal_url || metadata?.validation_url || parsed?.validation_url || parsed?.appeal_url; let label = metadata?.appeal_url_link_text || metadata?.validation_url_link_text || parsed?.appeal_url_link_text || parsed?.validation_url_link_text; if (!url && typeof parsed?.error === 'string') { try { const innerParsed = JSON.parse(parsed.error); const innerMeta = innerParsed?.error?.details?.[0]?.metadata; url = innerMeta?.appeal_url || innerMeta?.validation_url; label = innerMeta?.appeal_url_link_text || innerMeta?.validation_url_link_text; } catch (_) { } } if (url) return { url: String(url), label: label ? String(label) : null }; } catch (_) { } // 最后降级到正则匹配 const urlRegex = /https:\/\/[^\s"']+/g; const match = raw.match(urlRegex); if (match) { let extracted = match[0]; extracted = extracted.replace(/\\u0026/g, '&').replace(/\\"/g, '').replace(/\\/g, ''); if (extracted.endsWith(',')) { extracted = extracted.slice(0, -1); } return { url: extracted, label: null }; } return { url: null, label: null }; }; const message = extractErrorMessage(rawReason); const { url: actionUrl, label: actionLabel } = extractActionInfo(rawReason); // 识别错误类型 const isViolation = rawReason.toLowerCase().includes('terms of service') || rawReason.toLowerCase().includes('violation'); const isVerificationNeeded = !isViolation && (rawReason.toLowerCase().includes('verify your account') || !!account.validation_url); // 复制功能 const handleCopyUrl = (url: string) => { navigator.clipboard.writeText(url); showToast(t('accounts.validation_url_copied', '验证链接已复制到剪贴板'), 'success'); }; const handleCopyText = (text: string, msg: string) => { navigator.clipboard.writeText(text); showToast(msg, 'success'); }; const renderMessageWithLinks = (text: string) => { const urlRegex = /(https?:\/\/[^\s]+)/g; const parts = text.split(urlRegex); return parts.map((part, i) => { if (part.match(urlRegex)) { return ( e.stopPropagation()} > {t('accounts.click_to_verify', '点击去验证')} ); } return part; }); }; return (
{/* Account Info */}
{account.email}
{/* Status */}
{isForbidden && !isViolation && !isVerificationNeeded && !isValidationBlocked && ( {t('accounts.status.forbidden')} )} {isViolation && ( {t('accounts.status.violation_blocked', '由于违规被禁用')} )} {isDisabled && ( {t('accounts.status.disabled')} )} {isProxyDisabled && ( {t('accounts.status.proxy_disabled')} )} {(isValidationBlocked || isVerificationNeeded) && ( {t('accounts.status.validation_required', '账号需验证')} )}
{/* Reason */}
{showRaw ? (
{rawReason}
) : ( message ? renderMessageWithLinks(message) : t('common.unknown') )}
{/* Action Buttons for Verification / Appeal */} {actionUrl && !showRaw && (
{actionLabel || (isViolation ? t('accounts.go_to_appeal', '前往申诉') : t('accounts.click_to_verify', '点击去验证'))}
)} {/* Terminal Fix Guide */} {(isForbidden || isVerificationNeeded) && !showRaw && (
{showGuide && (

{t('accounts.fix_guide.step1_desc', '打开终端(Terminal),执行以下命令告诉 Google "是我本人",可解决部分 403 拦截:')}

gcloud auth login --update-adc
  • }}>按回车执行,提示继续时输入
  • {t('accounts.fix_guide.step1_li2')}
  • }}>看到 即大功告成!

{t('accounts.fix_guide.step2_title', '🧹 如果无效(清除缓存重来)')}

  1. {t('accounts.fix_guide.step2_li1_prefix', '先执行清除命令退出旧认证:')}
    gcloud auth revoke {account.email || 'your-email@gmail.com'}
  2. {t('accounts.fix_guide.step2_li2_prefix', '再执行登录:')}gcloud auth login --update-adc

{t('accounts.fix_guide.tips_title', '💡 常见建议')}

)}
)}
{/* Time */}
{t('accounts.error_time')}: {account.disabled_at ? formatDate(account.disabled_at) : (account.quota?.last_updated ? formatDate(account.quota.last_updated) : t('common.unknown'))}
); }