fix(huya): 优化账号密码导入交互并将导入类型严格限制为四段格式
- 交互优化:将账号标签选择器移至弹窗顶部,避免下拉列表向下展开时遮挡底部的'确定'/'导入'按钮 - 格式限制:严格限制仅允许'账号----密码----手机号----接码链接'四段格式导入 - 前后端校验:前端逐行校验格式并提示具体错误行号,后端限制 require_four_segments=True 兜底
This commit is contained in:
@@ -76,6 +76,23 @@ def test_import_password_accounts_persists_sms_url():
|
||||
assert added_account.tag == "测试接码"
|
||||
|
||||
|
||||
def test_import_password_accounts_strictly_four_segments():
|
||||
"""测试账号密码导入默认严格限制仅允许四段格式。"""
|
||||
mock_db = MagicMock()
|
||||
mock_db.query().filter().first.return_value = None
|
||||
|
||||
# 两段、三段会被跳过;仅四段有效行会被导入
|
||||
text = (
|
||||
"user1----pass1\n"
|
||||
"user2----pass2----13800138000\n"
|
||||
"user3----pass3----+14504409332----http://194.163.45.144/api/get_sms?key=xxx\n"
|
||||
"user4----pass4----not_a_url\n"
|
||||
)
|
||||
created, skipped = import_huya_password_accounts(mock_db, text, tag="四段测试")
|
||||
assert created == 1
|
||||
assert skipped == 3
|
||||
|
||||
|
||||
def test_solver_strategy_with_and_without_sms_url():
|
||||
"""测试在遇到 sms_auth2 时,有接码链接不抛异常,无接码链接抛出短信异常。"""
|
||||
url = "https://aq.huya.com/p/safe_auth/sms_auth2.html?param=xyz&mobile=14504409332"
|
||||
|
||||
@@ -693,11 +693,18 @@ def import_password_accounts(
|
||||
db: Session = Depends(get_db),
|
||||
current: User = Depends(get_current_user),
|
||||
):
|
||||
"""导入虎牙账号密码,只入库不立即登录。"""
|
||||
"""导入虎牙账号密码,只入库不立即登录(仅允许四段格式)。"""
|
||||
_require_huya_perm(current, "huya:import")
|
||||
count, skipped = import_huya_password_accounts(db, req.text, req.tag)
|
||||
count, skipped = import_huya_password_accounts(
|
||||
db, req.text, req.tag, require_four_segments=True
|
||||
)
|
||||
if count == 0 and skipped > 0:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="导入失败:当前仅允许四段格式(账号----密码----手机号----接码链接)",
|
||||
)
|
||||
return {
|
||||
"message": f"导入/更新 {count} 个虎牙账号,跳过 {skipped} 条",
|
||||
"message": f"成功导入/更新 {count} 个虎牙账号,跳过 {skipped} 条",
|
||||
"success": True,
|
||||
"count": count,
|
||||
"skipped": skipped,
|
||||
|
||||
@@ -136,8 +136,13 @@ def _looks_like_url(value: str) -> bool:
|
||||
return parsed.scheme in {"http", "https"} and bool(parsed.netloc)
|
||||
|
||||
|
||||
def split_huya_password_line(line: str) -> HuyaPasswordLine | None:
|
||||
"""拆分虎牙账号密码行,兼容账号----密码----手机号----验证码链接。"""
|
||||
def split_huya_password_line(
|
||||
line: str, require_four_segments: bool = False
|
||||
) -> HuyaPasswordLine | None:
|
||||
"""拆分虎牙账号密码行,兼容账号----密码----手机号----验证码链接。
|
||||
|
||||
若 require_four_segments 为 True,则严格限制必须包含手机号和有效接码链接。
|
||||
"""
|
||||
raw = (line or "").strip()
|
||||
if not raw or raw.startswith("#"):
|
||||
return None
|
||||
@@ -174,6 +179,10 @@ def split_huya_password_line(line: str) -> HuyaPasswordLine | None:
|
||||
cookie = "----".join(extra)
|
||||
if not username or not password:
|
||||
return None
|
||||
if require_four_segments and (
|
||||
not phone or not sms_url or not _looks_like_url(sms_url)
|
||||
):
|
||||
return None
|
||||
return HuyaPasswordLine(
|
||||
username=username,
|
||||
password=password,
|
||||
@@ -184,15 +193,23 @@ def split_huya_password_line(line: str) -> HuyaPasswordLine | None:
|
||||
|
||||
|
||||
def import_huya_password_accounts(
|
||||
db: Session, text: str, tag: str = ""
|
||||
db: Session,
|
||||
text: str,
|
||||
tag: str = "",
|
||||
require_four_segments: bool = True,
|
||||
) -> tuple[int, int]:
|
||||
"""导入虎牙账号密码,返回 (导入/更新数, 跳过数)。"""
|
||||
"""导入虎牙账号密码,返回 (导入/更新数, 跳过数)。
|
||||
|
||||
默认仅允许四段格式(账号----密码----手机号----接码链接)。
|
||||
"""
|
||||
created_or_updated = 0
|
||||
skipped = 0
|
||||
tag = (tag or "").strip()
|
||||
|
||||
for line in (text or "").splitlines():
|
||||
parsed = split_huya_password_line(line)
|
||||
parsed = split_huya_password_line(
|
||||
line, require_four_segments=require_four_segments
|
||||
)
|
||||
if not parsed:
|
||||
if line.strip():
|
||||
skipped += 1
|
||||
|
||||
@@ -278,10 +278,36 @@ export default function HuyaAccountsPage() {
|
||||
};
|
||||
|
||||
const handleImportPasswordAccounts = async () => {
|
||||
if (!passwordImportText.trim()) {
|
||||
const rawLines = passwordImportText
|
||||
.split('\n')
|
||||
.map((l) => l.trim())
|
||||
.filter((l) => l && !l.startsWith('#'));
|
||||
if (rawLines.length === 0) {
|
||||
message.warning('请先粘贴虎牙账号密码');
|
||||
return;
|
||||
}
|
||||
const invalidLines: number[] = [];
|
||||
rawLines.forEach((line, idx) => {
|
||||
const parts = line.includes('----')
|
||||
? line.split('----').map((p) => p.trim())
|
||||
: line.split(/\s+/).map((p) => p.trim());
|
||||
if (
|
||||
parts.length < 4 ||
|
||||
!parts[0] ||
|
||||
!parts[1] ||
|
||||
!parts[2] ||
|
||||
!parts[3] ||
|
||||
!/^https?:\/\//i.test(parts[3])
|
||||
) {
|
||||
invalidLines.push(idx + 1);
|
||||
}
|
||||
});
|
||||
if (invalidLines.length > 0) {
|
||||
message.error(
|
||||
`格式错误:仅允许四段格式(账号----密码----手机号----接码链接)。第 ${invalidLines.slice(0, 5).join('、')}${invalidLines.length > 5 ? ` 等共 ${invalidLines.length}` : ''} 行不符合要求`
|
||||
);
|
||||
return;
|
||||
}
|
||||
setPasswordImporting(true);
|
||||
try {
|
||||
const tag = passwordImportTag.length > 0 ? passwordImportTag[passwordImportTag.length - 1].trim() : '';
|
||||
@@ -946,27 +972,41 @@ export default function HuyaAccountsPage() {
|
||||
closable={!passwordImporting}
|
||||
width={720}
|
||||
>
|
||||
<Space orientation="vertical" style={{ width: '100%' }} size={12}>
|
||||
<TextArea
|
||||
rows={10}
|
||||
value={passwordImportText}
|
||||
onChange={(e) => setPasswordImportText(e.target.value)}
|
||||
placeholder={`每行一条格式:\n1. 账号----密码\n2. 账号----密码----手机号----接码链接\n例如:hy_299112483----aa778899----+14504409332----http://194.163.45.144/api/get_sms?key=c16fe33219924363fa362ed69dc6fd4f`}
|
||||
disabled={passwordImporting}
|
||||
<Space orientation="vertical" style={{ width: '100%' }} size={14}>
|
||||
<Alert
|
||||
type="info"
|
||||
showIcon
|
||||
message="仅支持四段格式导入:账号----密码----手机号----接码链接"
|
||||
description="手机号与接码链接将同步保存入库。后续 App 密码登录如触发短信二次验证(sms_auth2),系统将自动从接码链接提取验证码完成全自动无人值守过验。"
|
||||
/>
|
||||
<Select
|
||||
mode="tags"
|
||||
style={{ width: '100%' }}
|
||||
placeholder="可选,保存到账号标签"
|
||||
maxCount={1}
|
||||
value={passwordImportTag}
|
||||
onChange={setPasswordImportTag}
|
||||
options={tags.map((tag) => ({ value: tag, label: tag }))}
|
||||
disabled={passwordImporting}
|
||||
/>
|
||||
<Paragraph type="secondary" style={{ marginBottom: 0 }}>
|
||||
四段格式:<code>账号----密码----手机号----接码链接</code>。接码链接与手机号将自动保存入库。后续 App 密码登录如触发短信二次验证(sms_auth2),系统将自动从接码链接获取验证码完成全自动过验!
|
||||
</Paragraph>
|
||||
<div>
|
||||
<div style={{ marginBottom: 6, fontWeight: 500 }}>
|
||||
账号标签 <span style={{ color: '#8c8c8c', fontWeight: 'normal', fontSize: 12 }}>(可选,批量设置账号标签)</span>
|
||||
</div>
|
||||
<Select
|
||||
mode="tags"
|
||||
style={{ width: '100%' }}
|
||||
placeholder="输入或选择账号标签"
|
||||
maxCount={1}
|
||||
value={passwordImportTag}
|
||||
onChange={setPasswordImportTag}
|
||||
options={tags.map((tag) => ({ value: tag, label: tag }))}
|
||||
disabled={passwordImporting}
|
||||
placement="bottomLeft"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<div style={{ marginBottom: 6, fontWeight: 500 }}>
|
||||
账号数据 <span style={{ color: '#ff4d4f', fontSize: 12 }}>* 仅允许四段格式(账号----密码----手机号----接码链接)</span>
|
||||
</div>
|
||||
<TextArea
|
||||
rows={9}
|
||||
value={passwordImportText}
|
||||
onChange={(e) => setPasswordImportText(e.target.value)}
|
||||
placeholder={`每行一条四段格式:\n账号----密码----手机号----接码链接\n\n例如:\nhy_299112483----aa778899----+14504409332----http://194.163.45.144/api/get_sms?key=c16fe33219924363fa362ed69dc6fd4f`}
|
||||
disabled={passwordImporting}
|
||||
/>
|
||||
</div>
|
||||
</Space>
|
||||
</Modal>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user