feat(auth): print a scannable ASCII QR in the terminal
WSL and --no-browser never showed the PNG, and placeholder Chinese cookies crashed urllib latin-1 headers before the code was issued.
This commit is contained in:
1 parent
12732f6316
commit
2641ba6f87
5 files changed
+183
-11
No files matched your search
@@ -45,12 +45,30 @@ def strip_identity(cookie: str) -> str:
|
||||
return "; ".join(kept)
|
||||
|
||||
|
||||
def _is_latin1(value: str) -> bool:
|
||||
try:
|
||||
value.encode("latin-1")
|
||||
except UnicodeEncodeError:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def cookie_header(cookie: str, *, auth: bool = False) -> str:
|
||||
"""生成可放入 HTTP 头的 Cookie。占位中文、缺 ``=`` 的片段和非 latin-1 字段都会丢掉。"""
|
||||
source = cookie if auth else strip_identity(cookie)
|
||||
return "; ".join(
|
||||
f"{key}={value}"
|
||||
for key, value in parse_cookie(source)
|
||||
if _is_latin1(key) and _is_latin1(value)
|
||||
)
|
||||
|
||||
|
||||
def make_headers(cfg: dict, auth: bool = False, *, referer: str | None = None) -> dict[str, str]:
|
||||
"""构造与 ``data/raw-request.txt`` 对齐的 B 站 Web 请求头。"""
|
||||
return {
|
||||
"accept": "*/*",
|
||||
"accept-language": "zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7",
|
||||
"cookie": cfg["cookie"] if auth else strip_identity(cfg["cookie"]),
|
||||
"cookie": cookie_header(str(cfg.get("cookie") or ""), auth=auth),
|
||||
"dnt": "1",
|
||||
"origin": "https://space.bilibili.com",
|
||||
"priority": "u=1, i",
|
||||
|
||||
@@ -40,6 +40,47 @@ import qrcodegen # noqa: E402
|
||||
|
||||
BASE = "https://api.bilibili.com"
|
||||
PASSPORT = "https://passport.bilibili.com"
|
||||
_QR_BLOCKS = {
|
||||
(False, False): " ",
|
||||
(True, False): "▀",
|
||||
(False, True): "▄",
|
||||
(True, True): "█",
|
||||
}
|
||||
_QR_ASCII_STYLE = "\033[48;5;231m\033[38;5;16m"
|
||||
_QR_ASCII_RESET = "\033[0m"
|
||||
|
||||
|
||||
def render_qr_ascii(text: str, *, border: int = 2) -> str:
|
||||
"""把登录链接编成终端半块字符二维码(两行模块合成一行)。"""
|
||||
if border < 0:
|
||||
raise ValueError("border 必须 >= 0")
|
||||
qr = qrcodegen.QrCode.encode_text(text, qrcodegen.QrCode.Ecc.MEDIUM)
|
||||
size = qr.get_size()
|
||||
dim = size + border * 2
|
||||
|
||||
def dark(x: int, y: int) -> bool:
|
||||
return qr.get_module(x - border, y - border)
|
||||
|
||||
lines: list[str] = []
|
||||
for y in range(0, dim, 2):
|
||||
row: list[str] = []
|
||||
bottom_y = y + 1
|
||||
for x in range(dim):
|
||||
top = dark(x, y)
|
||||
bottom = dark(x, bottom_y) if bottom_y < dim else False
|
||||
row.append(_QR_BLOCKS[(top, bottom)])
|
||||
lines.append("".join(row))
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def format_qr_ascii(text: str, *, border: int = 2, color: bool | None = None) -> str:
|
||||
"""终端二维码;TTY 下加白底黑字,方便深色终端扫码。"""
|
||||
body = render_qr_ascii(text, border=border)
|
||||
if color is None:
|
||||
color = sys.stdout.isatty()
|
||||
if not color:
|
||||
return body
|
||||
return "\n".join(f"{_QR_ASCII_STYLE}{line}{_QR_ASCII_RESET}" for line in body.split("\n"))
|
||||
|
||||
|
||||
def write_qr_png(text: str, path: str | Path, scale: int = 8, border: int = 4) -> Path:
|
||||
@@ -200,7 +241,7 @@ class AuthService:
|
||||
def run(self, *, timeout: int, no_browser: bool) -> int:
|
||||
raw = self._load_raw()
|
||||
credentials = raw["bilibili"][self.account]
|
||||
print(f"[auth] 为 {self.account} 账号申请扫码登录 ...")
|
||||
print(f"[auth] 为 {self.account} 账号申请扫码登录 ...", flush=True)
|
||||
generated, _ = self._request(
|
||||
credentials, f"{PASSPORT}/x/passport-login/web/qrcode/generate?source=main-fe-header", auth=False
|
||||
)
|
||||
@@ -209,14 +250,18 @@ class AuthService:
|
||||
if generated.get("code") != 0 or not key or not login_url:
|
||||
print(f"[auth] 申请二维码失败: code={generated.get('code')} {generated.get('message')}")
|
||||
return 2
|
||||
login_url = str(login_url)
|
||||
qr_path = Path(tempfile.gettempdir()) / f"bili-fav-{self.account}-{key[:8]}.png"
|
||||
write_qr_png(str(login_url), qr_path)
|
||||
print(f"[auth] 二维码已生成: {qr_path}")
|
||||
write_qr_png(login_url, qr_path)
|
||||
print(f"[auth] 二维码已生成: {qr_path}", flush=True)
|
||||
print("[auth] 请使用哔哩哔哩 App 扫描下方二维码", flush=True)
|
||||
print(format_qr_ascii(login_url), flush=True)
|
||||
print(f"[auth] 登录链接: {login_url}", flush=True)
|
||||
if not no_browser:
|
||||
try:
|
||||
open_path(qr_path)
|
||||
except Exception as exc: # noqa: BLE001
|
||||
print(f"[auth] 打开图片失败: {exc},请手动打开二维码")
|
||||
print(f"[auth] 打开图片失败: {exc},请手动扫描终端二维码")
|
||||
deadline, last = time.time() + timeout, None
|
||||
while time.time() < deadline:
|
||||
try:
|
||||
|
||||
Reference in new issue
Block a user