style: 统一 Ruff 代码格式
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
"""应用宝 QQ 二维码登录(纯 Python,无浏览器自动化)。"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
@@ -15,7 +16,12 @@ from http.cookiejar import CookieJar
|
||||
from pathlib import Path
|
||||
from urllib.error import HTTPError
|
||||
from urllib.parse import urlencode
|
||||
from urllib.request import HTTPRedirectHandler, HTTPCookieProcessor, Request, build_opener
|
||||
from urllib.request import (
|
||||
HTTPRedirectHandler,
|
||||
HTTPCookieProcessor,
|
||||
Request,
|
||||
build_opener,
|
||||
)
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
OPEN_APPID = "102033112"
|
||||
@@ -60,8 +66,15 @@ class Client:
|
||||
self.jar = CookieJar()
|
||||
self.opener = build_opener(NoRedirect, HTTPCookieProcessor(self.jar))
|
||||
|
||||
def request(self, url: str, *, method: str = "GET", body: bytes | None = None,
|
||||
referer: str = "", headers: dict[str, str] | None = None) -> Response:
|
||||
def request(
|
||||
self,
|
||||
url: str,
|
||||
*,
|
||||
method: str = "GET",
|
||||
body: bytes | None = None,
|
||||
referer: str = "",
|
||||
headers: dict[str, str] | None = None,
|
||||
) -> Response:
|
||||
request_headers = {"User-Agent": USER_AGENT, "Accept": "*/*"}
|
||||
if referer:
|
||||
request_headers["Referer"] = referer
|
||||
@@ -155,7 +168,9 @@ def write_session(path: Path, cookies: dict[str, str]) -> None:
|
||||
document["login_type"] = cookies.get("logintype", "QC")
|
||||
document["login_updated_at"] = int(time.time())
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
fd, temporary = tempfile.mkstemp(prefix=".mall-session-", suffix=".tmp", dir=path.parent)
|
||||
fd, temporary = tempfile.mkstemp(
|
||||
prefix=".mall-session-", suffix=".tmp", dir=path.parent
|
||||
)
|
||||
try:
|
||||
with os.fdopen(fd, "w", encoding="utf-8") as handle:
|
||||
json.dump(document, handle, ensure_ascii=False, indent=2)
|
||||
@@ -172,7 +187,9 @@ def write_session(path: Path, cookies: dict[str, str]) -> None:
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description="应用宝 QQ 扫码登录(纯 Python)")
|
||||
parser.add_argument("--session", type=Path, default=ROOT / "config/mall-session.json")
|
||||
parser.add_argument(
|
||||
"--session", type=Path, default=ROOT / "config/mall-session.json"
|
||||
)
|
||||
parser.add_argument("--qr", type=Path, default=ROOT / "config/qq-login.jpg")
|
||||
parser.add_argument("--timeout", type=float, default=300, help="二维码等待秒数")
|
||||
parser.add_argument("--interval", type=float, default=3, help="轮询间隔秒数")
|
||||
@@ -183,8 +200,12 @@ def main() -> int:
|
||||
client = Client()
|
||||
state = secrets.token_urlsafe(14)
|
||||
show_query = {
|
||||
"which": "Login", "display": "pc", "response_type": "code", "client_id": OPEN_APPID,
|
||||
"redirect_uri": CALLBACK, "state": state,
|
||||
"which": "Login",
|
||||
"display": "pc",
|
||||
"response_type": "code",
|
||||
"client_id": OPEN_APPID,
|
||||
"redirect_uri": CALLBACK,
|
||||
"state": state,
|
||||
}
|
||||
show_url = f"{GRAPH_SHOW}?{urlencode(show_query)}"
|
||||
show = client.request(show_url, referer="https://m.yyb.qq.com/")
|
||||
@@ -192,11 +213,18 @@ def main() -> int:
|
||||
raise RuntimeError(f"QQ OAuth 页面请求失败: HTTP {show.status}")
|
||||
|
||||
xlogin_query = {
|
||||
"appid": PT_APPID, "daid": PT_DAID, "style": "33", "login_text": "登录",
|
||||
"hide_title_bar": "1", "hide_border": "1", "target": "self", "s_url": LOGIN_JUMP,
|
||||
"appid": PT_APPID,
|
||||
"daid": PT_DAID,
|
||||
"style": "33",
|
||||
"login_text": "登录",
|
||||
"hide_title_bar": "1",
|
||||
"hide_border": "1",
|
||||
"target": "self",
|
||||
"s_url": LOGIN_JUMP,
|
||||
"pt_3rd_aid": OPEN_APPID,
|
||||
"pt_feedback_link": f"https://support.qq.com/products/77942?customInfo=.appid{OPEN_APPID}",
|
||||
"theme": "2", "verify_theme": "",
|
||||
"theme": "2",
|
||||
"verify_theme": "",
|
||||
}
|
||||
xlogin_url = f"{XLOGIN}?{urlencode(xlogin_query)}"
|
||||
xlogin = client.request(xlogin_url, referer=show_url)
|
||||
@@ -207,9 +235,16 @@ def main() -> int:
|
||||
raise RuntimeError("QQ 登录页未写入 pt_login_sig")
|
||||
|
||||
qr_query = {
|
||||
"appid": PT_APPID, "e": "2", "l": "M", "s": "3", "d": "72", "v": "4",
|
||||
"t": str(secrets.randbelow(1_000_000) / 1_000_000), "daid": PT_DAID,
|
||||
"pt_3rd_aid": OPEN_APPID, "u1": LOGIN_JUMP,
|
||||
"appid": PT_APPID,
|
||||
"e": "2",
|
||||
"l": "M",
|
||||
"s": "3",
|
||||
"d": "72",
|
||||
"v": "4",
|
||||
"t": str(secrets.randbelow(1_000_000) / 1_000_000),
|
||||
"daid": PT_DAID,
|
||||
"pt_3rd_aid": OPEN_APPID,
|
||||
"u1": LOGIN_JUMP,
|
||||
}
|
||||
qr_url = f"{QR_SHOW}?{urlencode(qr_query)}"
|
||||
image = client.request(qr_url, referer=xlogin_url)
|
||||
@@ -228,10 +263,23 @@ def main() -> int:
|
||||
o1v_id = secrets.token_hex(16)
|
||||
while time.monotonic() < deadline:
|
||||
poll_query = {
|
||||
"u1": LOGIN_JUMP, "ptqrtoken": str(ptqr_token(qrsig)), "ptredirect": "0", "h": "1", "t": "1",
|
||||
"g": "1", "from_ui": "1", "ptlang": "2052", "action": f"0-0-{int(time.time() * 1000)}",
|
||||
"js_ver": "26071711", "js_type": "1", "login_sig": login_sig, "pt_uistyle": "40",
|
||||
"aid": PT_APPID, "daid": PT_DAID, "pt_3rd_aid": OPEN_APPID, "o1vId": o1v_id,
|
||||
"u1": LOGIN_JUMP,
|
||||
"ptqrtoken": str(ptqr_token(qrsig)),
|
||||
"ptredirect": "0",
|
||||
"h": "1",
|
||||
"t": "1",
|
||||
"g": "1",
|
||||
"from_ui": "1",
|
||||
"ptlang": "2052",
|
||||
"action": f"0-0-{int(time.time() * 1000)}",
|
||||
"js_ver": "26071711",
|
||||
"js_type": "1",
|
||||
"login_sig": login_sig,
|
||||
"pt_uistyle": "40",
|
||||
"aid": PT_APPID,
|
||||
"daid": PT_DAID,
|
||||
"pt_3rd_aid": OPEN_APPID,
|
||||
"o1vId": o1v_id,
|
||||
"pt_js_version": "c1987b96",
|
||||
}
|
||||
poll = client.request(f"{QR_POLL}?{urlencode(poll_query)}", referer=xlogin_url)
|
||||
@@ -252,7 +300,9 @@ def main() -> int:
|
||||
|
||||
check_sig = client.request(callback, referer=xlogin_url)
|
||||
login_jump = check_sig.location()
|
||||
if check_sig.status != 302 or not login_jump.startswith("https://graph.qq.com/oauth2.0/login_jump"):
|
||||
if check_sig.status != 302 or not login_jump.startswith(
|
||||
"https://graph.qq.com/oauth2.0/login_jump"
|
||||
):
|
||||
raise RuntimeError("QQ check_sig 未跳转到 OAuth login_jump")
|
||||
jump = client.request(login_jump, referer=callback)
|
||||
if jump.status != 200:
|
||||
@@ -262,8 +312,10 @@ def main() -> int:
|
||||
if not p_skey:
|
||||
raise RuntimeError("QQ check_sig 未写入 p_skey")
|
||||
authorize = client.request(
|
||||
"https://graph.qq.com/oauth2.0/authorize", method="POST",
|
||||
body=urlencode(authorize_params(state, p_skey)).encode("utf-8"), referer=login_jump,
|
||||
"https://graph.qq.com/oauth2.0/authorize",
|
||||
method="POST",
|
||||
body=urlencode(authorize_params(state, p_skey)).encode("utf-8"),
|
||||
referer=login_jump,
|
||||
headers={"Content-Type": "application/x-www-form-urlencoded"},
|
||||
)
|
||||
oauth_callback = authorize.location()
|
||||
@@ -276,12 +328,16 @@ def main() -> int:
|
||||
if not cookies.get("openid") or not cookies.get("accesstoken"):
|
||||
raise RuntimeError("YYB QQ OAuth 回调未写入 openid/accesstoken")
|
||||
login_type = cookies.get("logintype", "QC")
|
||||
info = client.request(USER_INFO, headers={
|
||||
"Ual-Access-Login-Type": login_type_header(login_type),
|
||||
"Ual-Access-Access-Token": cookies["accesstoken"],
|
||||
"Ual-Access-Openid": cookies["openid"],
|
||||
"Origin": "https://m.yyb.qq.com", "Referer": "https://m.yyb.qq.com/",
|
||||
})
|
||||
info = client.request(
|
||||
USER_INFO,
|
||||
headers={
|
||||
"Ual-Access-Login-Type": login_type_header(login_type),
|
||||
"Ual-Access-Access-Token": cookies["accesstoken"],
|
||||
"Ual-Access-Openid": cookies["openid"],
|
||||
"Origin": "https://m.yyb.qq.com",
|
||||
"Referer": "https://m.yyb.qq.com/",
|
||||
},
|
||||
)
|
||||
if info.status != 200:
|
||||
raise RuntimeError(f"YYB QQ 登录态校验失败: HTTP {info.status}")
|
||||
try:
|
||||
|
||||
Reference in New Issue
Block a user