兼容虎牙四段账号格式

支持虎牙号、密码、手机号、验证码链接导入,并补充美国等国际手机号格式处理。
This commit is contained in:
yml2213
2026-07-06 11:45:42 +08:00
parent 72e7947a39
commit 45dd7e5a74
6 changed files with 203 additions and 25 deletions
+131 -4
View File
@@ -36,6 +36,108 @@ SMS_CODE_URL = "https://udblgn.huya.com/web/v2/smsCode"
SMS_LOGIN_URL = "https://udblgn.huya.com/web/v2/smsLogin" SMS_LOGIN_URL = "https://udblgn.huya.com/web/v2/smsLogin"
DF_TOKEN_URL = "https://df.huya.com/web/df/token" DF_TOKEN_URL = "https://df.huya.com/web/df/token"
DF_COLLECT_URL = "https://df.huya.com/web/df/collect" DF_COLLECT_URL = "https://df.huya.com/web/df/collect"
HUYA_COUNTRY_CALLING_CODES = frozenset({
"1",
"7",
"20",
"27",
"30",
"31",
"32",
"33",
"34",
"36",
"39",
"40",
"41",
"43",
"44",
"45",
"46",
"47",
"48",
"49",
"52",
"55",
"60",
"61",
"62",
"63",
"64",
"65",
"66",
"81",
"82",
"84",
"86",
"90",
"91",
"92",
"93",
"94",
"95",
"98",
"212",
"213",
"216",
"218",
"234",
"254",
"351",
"352",
"353",
"354",
"355",
"356",
"357",
"358",
"359",
"370",
"371",
"372",
"373",
"374",
"375",
"376",
"377",
"378",
"380",
"381",
"382",
"385",
"386",
"387",
"389",
"420",
"421",
"852",
"853",
"855",
"856",
"886",
"960",
"961",
"962",
"963",
"964",
"965",
"966",
"967",
"968",
"971",
"972",
"973",
"974",
"975",
"976",
"977",
"992",
"993",
"994",
"995",
"996",
"998",
})
@dataclass @dataclass
@@ -52,13 +154,36 @@ class HuyaSmsCodeResult:
request_id: str = "" request_id: str = ""
def _split_country_calling_code(digits: str) -> tuple[str, str]:
"""从国际号码中拆出国家/地区区号。"""
for size in (3, 2, 1):
code = digits[:size]
if code in HUYA_COUNTRY_CALLING_CODES and len(digits) > size:
return code, digits[size:]
return "", digits
def _format_huya_international_phone(code: str, national_number: str) -> str:
"""转换成虎牙短信接口使用的三位区号格式。"""
return f"{code.zfill(3)}{national_number}"
def normalize_huya_phone(phone: str) -> str: def normalize_huya_phone(phone: str) -> str:
"""归一化虎牙短信接口手机号格式。""" """归一化虎牙短信接口手机号格式,兼容国内号和显式国际区号"""
raw = str(phone or "").strip() raw = str(phone or "").strip()
digits = "".join(ch for ch in raw if ch.isdigit()) digits = "".join(ch for ch in raw if ch.isdigit())
if raw.startswith("+1") and len(digits) == 11: if not digits:
return f"001{digits[1:]}" return ""
if digits.startswith("001") and len(digits) == 13: if raw.startswith("+"):
code, national_number = _split_country_calling_code(digits)
if code:
return _format_huya_international_phone(code, national_number)
if digits.startswith("00") and len(digits) > 4:
code, national_number = _split_country_calling_code(digits[2:])
if code:
return _format_huya_international_phone(code, national_number)
return digits
if digits.startswith("0") and len(digits) > 11:
return digits return digits
if digits.startswith("086"): if digits.startswith("086"):
return digits return digits
@@ -66,6 +191,8 @@ def normalize_huya_phone(phone: str) -> str:
return f"0{digits}" return f"0{digits}"
if len(digits) == 11: if len(digits) == 11:
return f"086{digits}" return f"086{digits}"
if len(digits) == 10:
return f"001{digits}"
return digits return digits
+19 -6
View File
@@ -6,6 +6,7 @@ import json
import re import re
from dataclasses import dataclass from dataclasses import dataclass
from datetime import datetime from datetime import datetime
from urllib.parse import urlparse
import requests import requests
@@ -42,24 +43,36 @@ class SmsPollResult:
def parse_sms_lines(text: str) -> list[SmsLine]: def parse_sms_lines(text: str) -> list[SmsLine]:
"""解析手机号池,每行格式:手机号----查询URL。""" """解析手机号池,兼容手机号----查询URL、账号----密码----手机号----查询URL。"""
rows: list[SmsLine] = [] rows: list[SmsLine] = []
for raw_line in (text or "").splitlines(): for raw_line in (text or "").splitlines():
line = raw_line.strip() line = raw_line.strip()
if not line or line.startswith("#"): if not line or line.startswith("#"):
continue continue
parts = [part.strip() for part in line.split("----", 1)] parts = [part.strip() for part in line.split("----")]
if len(parts) != 2 or not parts[0] or not parts[1]: if len(parts) >= 4 and _looks_like_url("----".join(parts[3:])):
phone, url = parts[2], "----".join(parts[3:]).strip()
elif len(parts) >= 2 and _looks_like_url("----".join(parts[1:])):
phone, url = parts[0], "----".join(parts[1:]).strip()
else:
continue
if not phone or not url:
continue continue
rows.append(SmsLine( rows.append(SmsLine(
phone=parts[0], phone=phone,
url=parts[1], url=url,
provider=detect_provider(parts[1]), provider=detect_provider(url),
raw=line, raw=line,
)) ))
return rows return rows
def _looks_like_url(value: str) -> bool:
"""判断文本是否像 URL。"""
parsed = urlparse(str(value or "").strip())
return parsed.scheme in {"http", "https"} and bool(parsed.netloc)
def detect_provider(url: str) -> str: def detect_provider(url: str) -> str:
"""根据 URL 判断接码平台类型。""" """根据 URL 判断接码平台类型。"""
text = (url or "").lower() text = (url or "").lower()
+1 -1
View File
@@ -351,7 +351,7 @@ def create_auto_register_batch(
_require_huya_perm(current, "huya:import") _require_huya_perm(current, "huya:import")
sms_lines = parse_sms_lines(req.text) sms_lines = parse_sms_lines(req.text)
if not sms_lines: if not sms_lines:
raise HTTPException(status_code=400, detail="没有识别到有效手机号,格式为:手机号----短信查询URL") raise HTTPException(status_code=400, detail="没有识别到有效手机号,格式为:手机号----短信查询URL 或 虎牙号----密码----手机号----短信查询URL")
proxy_config = db.query(ProxyConfig).first() if req.use_proxy else None proxy_config = db.query(ProxyConfig).first() if req.use_proxy else None
runner = huya_register_registry.create( runner = huya_register_registry.create(
+48 -5
View File
@@ -2,7 +2,9 @@
import csv import csv
import uuid import uuid
from dataclasses import dataclass
from datetime import datetime, timezone from datetime import datetime, timezone
from urllib.parse import urlparse
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
@@ -94,8 +96,25 @@ def parse_huya_cookie_line(line: str) -> dict | None:
} }
def split_huya_password_line(line: str) -> tuple[str, str, str] | None: @dataclass
"""拆分虎牙账号密码行,返回账号、密码、预置 Cookie。""" class HuyaPasswordLine:
"""虎牙账号密码导入行。"""
username: str
password: str
cookie: str = ""
phone: str = ""
sms_url: str = ""
def _looks_like_url(value: str) -> bool:
"""判断文本是否像 URL。"""
parsed = urlparse(str(value or "").strip())
return parsed.scheme in {"http", "https"} and bool(parsed.netloc)
def split_huya_password_line(line: str) -> HuyaPasswordLine | None:
"""拆分虎牙账号密码行,兼容账号----密码----手机号----验证码链接。"""
raw = (line or "").strip() raw = (line or "").strip()
if not raw or raw.startswith("#"): if not raw or raw.startswith("#"):
return None return None
@@ -116,10 +135,29 @@ def split_huya_password_line(line: str) -> tuple[str, str, str] | None:
username = parts[0].strip() username = parts[0].strip()
password = parts[1].strip() password = parts[1].strip()
cookie = "----".join(part.strip() for part in parts[2:] if part.strip()) cookie = ""
phone = ""
sms_url = ""
extra = [part.strip() for part in parts[2:] if part.strip()]
if len(extra) >= 2 and _looks_like_url(extra[1]):
phone = extra[0]
sms_url = extra[1]
cookie = "----".join(extra[2:])
elif extra and _looks_like_url(extra[-1]) and len(extra) >= 2:
phone = extra[-2]
sms_url = extra[-1]
cookie = "----".join(extra[:-2])
elif extra:
cookie = "----".join(extra)
if not username or not password: if not username or not password:
return None return None
return username, password, cookie return HuyaPasswordLine(
username=username,
password=password,
cookie=cookie,
phone=phone,
sms_url=sms_url,
)
def import_huya_password_accounts(db: Session, text: str, tag: str = "") -> tuple[int, int]: def import_huya_password_accounts(db: Session, text: str, tag: str = "") -> tuple[int, int]:
@@ -135,7 +173,9 @@ def import_huya_password_accounts(db: Session, text: str, tag: str = "") -> tupl
skipped += 1 skipped += 1
continue continue
username, password, cookie = parsed username = parsed.username
password = parsed.password
cookie = parsed.cookie
account = db.query(HuyaAccount).filter(HuyaAccount.username == username).first() account = db.query(HuyaAccount).filter(HuyaAccount.username == username).first()
if account is None: if account is None:
account = HuyaAccount( account = HuyaAccount(
@@ -144,6 +184,7 @@ def import_huya_password_accounts(db: Session, text: str, tag: str = "") -> tupl
username=username, username=username,
account_password=password, account_password=password,
cookie=normalize_huya_cookie(cookie) if cookie else "", cookie=normalize_huya_cookie(cookie) if cookie else "",
game_phone=parsed.phone,
tag=tag, tag=tag,
status="password_imported", status="password_imported",
) )
@@ -152,6 +193,8 @@ def import_huya_password_accounts(db: Session, text: str, tag: str = "") -> tupl
account.account_password = password account.account_password = password
if cookie: if cookie:
account.cookie = normalize_huya_cookie(cookie) account.cookie = normalize_huya_cookie(cookie)
if parsed.phone:
account.game_phone = parsed.phone
if tag: if tag:
account.tag = tag account.tag = tag
if not account.cookie: if not account.cookie:
+2 -2
View File
@@ -716,7 +716,7 @@ export default function HuyaAccountsPage() {
rows={10} rows={10}
value={passwordImportText} value={passwordImportText}
onChange={(e) => setPasswordImportText(e.target.value)} onChange={(e) => setPasswordImportText(e.target.value)}
placeholder="每行一条:账号----密码;如需预置 Cookie:账号----密码----Cookie" placeholder="每行一条:账号----密码;也支持:虎牙号----密码----手机号----验证码链接"
disabled={passwordImporting} disabled={passwordImporting}
/> />
<Select <Select
@@ -730,7 +730,7 @@ export default function HuyaAccountsPage() {
disabled={passwordImporting} disabled={passwordImporting}
/> />
<Paragraph type="secondary" style={{ marginBottom: 0 }}> <Paragraph type="secondary" style={{ marginBottom: 0 }}>
</Paragraph> </Paragraph>
</Space> </Space>
</Modal> </Modal>
+2 -7
View File
@@ -97,12 +97,7 @@ function readStoredBatchId() {
} }
export default function HuyaRegisterPage() { export default function HuyaRegisterPage() {
const initialFormRef = useRef<StoredRegisterForm | null>(null); const [initialForm] = useState(readStoredForm);
if (initialFormRef.current === null) {
initialFormRef.current = readStoredForm();
}
const initialForm = initialFormRef.current;
const [text, setText] = useState(initialForm.text); const [text, setText] = useState(initialForm.text);
const [tag, setTag] = useState(initialForm.tag); const [tag, setTag] = useState(initialForm.tag);
const [concurrency, setConcurrency] = useState(initialForm.concurrency); const [concurrency, setConcurrency] = useState(initialForm.concurrency);
@@ -311,7 +306,7 @@ export default function HuyaRegisterPage() {
rows={9} rows={9}
value={text} value={text}
onChange={(event) => setText(event.target.value)} onChange={(event) => setText(event.target.value)}
placeholder="手机号----短信查询URL" placeholder="手机号----短信查询URL;也支持:虎牙号----密码----手机号----验证码链接"
disabled={isRunning} disabled={isRunning}
/> />
<Row gutter={[12, 12]}> <Row gutter={[12, 12]}>