Replace repository with grok-reg-protocol-cpa project

Overwrite previous grok-keygen contents with the local registration/CPA protocol codebase (secrets and runtime data excluded).
This commit is contained in:
chaos committed 2026-07-11 17:25:37 +08:00
commit 6f7c949681
33 files changed
+11125

No files matched your search

+240
View File
@@ -0,0 +1,240 @@
#!/usr/bin/env python3
"""TabPool — per-thread Chromium with proper lifecycle.
Interface:
TabPool.init(options_factory) → save options factory (no browser yet)
TabPool.get_tab() → get/create current thread browser tab
TabPool.clear_session() → wipe cookies/storage; keep process warm
TabPool.release_tab() → quit current thread browser + drop registry
TabPool.shutdown() → quit all known browsers
Notes:
- One Chromium per worker thread (cookie isolation).
- Prefer clear_session() between accounts; release_tab() only on errors / GC.
- _all_browsers is pruned on release to avoid zombie list growth.
"""
from __future__ import annotations
import threading
from typing import Any
class TabPool:
"""Per-thread Chromium instance manager."""
_options_factory = None
_options_lock = threading.Lock()
_thread_local = threading.local()
_all_browsers: list[Any] = []
_all_browsers_lock = threading.Lock()
# ── public ──
@classmethod
def init(cls, browser_options_or_factory, log_callback=None):
"""Save options object or factory. Callable → fresh options each create."""
with cls._options_lock:
if callable(browser_options_or_factory):
cls._options_factory = browser_options_or_factory
else:
# Shared options object: auto_port will NOT re-allocate.
cls._options_factory = lambda: browser_options_or_factory
if log_callback:
log_callback("[*] TabPool 已初始化浏览器选项模板")
@classmethod
def _create_browser(cls):
from DrissionPage import Chromium
with cls._options_lock:
factory = cls._options_factory
if factory is None:
return None
options = factory()
browser = Chromium(options)
with cls._all_browsers_lock:
cls._all_browsers.append(browser)
return browser
@classmethod
def _unregister(cls, browser) -> None:
if browser is None:
return
with cls._all_browsers_lock:
try:
cls._all_browsers = [b for b in cls._all_browsers if b is not browser]
except Exception:
pass
@classmethod
def get_tab(cls, url=None):
"""Return current thread tab; create Chromium on first use."""
tab = getattr(cls._thread_local, "tab", None)
if tab is not None:
return tab
browser = cls._create_browser()
if browser is None:
raise RuntimeError("TabPool not initialized — call init() first")
tab_ids = browser.tab_ids
if tab_ids:
tab = browser.get_tab(tab_ids[0])
else:
tab = browser.new_tab()
cls._thread_local.browser = browser
cls._thread_local.tab = tab
cls._thread_local.served = 0
return tab
@classmethod
def sync_tab(cls):
"""Point thread-local tab at the browser's latest tab."""
browser = getattr(cls._thread_local, "browser", None)
if browser is None:
return
tabs = browser.tab_ids
if tabs:
cls._thread_local.tab = browser.get_tab(tabs[-1])
@classmethod
def clear_session(cls, log_callback=None) -> bool:
"""Clear cookies/storage and blank the page; keep Chromium process.
Returns True if session was cleared on a live browser; False if no browser.
"""
browser = getattr(cls._thread_local, "browser", None)
tab = getattr(cls._thread_local, "tab", None)
if browser is None:
return False
ok = True
try:
if tab is not None:
try:
tab.get("about:blank")
except Exception:
pass
for js in (
"try{localStorage.clear()}catch(e){}",
"try{sessionStorage.clear()}catch(e){}",
"try{indexedDB.databases&&indexedDB.databases().then(ds=>ds.forEach(d=>indexedDB.deleteDatabase(d.name)))}catch(e){}",
):
try:
tab.run_js(js)
except Exception:
pass
# Best-effort cookie wipe (API varies by DrissionPage version)
cleared = False
for target in (tab, browser):
if target is None or cleared:
continue
for attr_path in (
("set", "cookies", "clear"),
("cookies", "clear"),
):
try:
obj = target
for name in attr_path[:-1]:
obj = getattr(obj, name)
fn = getattr(obj, attr_path[-1])
fn()
cleared = True
break
except Exception:
continue
if not cleared:
try:
# Fallback: drop all cookies via CDP-ish helper if present
cks = browser.cookies()
if isinstance(cks, list):
for c in cks:
try:
browser.set.cookies.remove(c) # type: ignore[attr-defined]
except Exception:
pass
except Exception:
ok = False
# Prefer a single clean tab
try:
tabs = list(browser.tab_ids or [])
if len(tabs) > 1:
keep = tabs[0]
for tid in tabs[1:]:
try:
browser.get_tab(tid).close()
except Exception:
pass
cls._thread_local.tab = browser.get_tab(keep)
elif tabs:
cls._thread_local.tab = browser.get_tab(tabs[0])
except Exception:
cls.sync_tab()
if log_callback:
served = int(getattr(cls._thread_local, "served", 0) or 0)
log_callback(f"[*] 浏览器会话已清理(复用进程, served={served})")
return ok
except Exception as exc:
if log_callback:
log_callback(f"[!] clear_session 失败: {exc}")
return False
@classmethod
def mark_served(cls) -> int:
n = int(getattr(cls._thread_local, "served", 0) or 0) + 1
cls._thread_local.served = n
return n
@classmethod
def served_count(cls) -> int:
return int(getattr(cls._thread_local, "served", 0) or 0)
@classmethod
def release_tab(cls):
"""Quit current thread Chromium and unregister it."""
browser = getattr(cls._thread_local, "browser", None)
if browser is not None:
try:
browser.quit(del_data=True)
except TypeError:
try:
browser.quit()
except Exception:
pass
except Exception:
pass
cls._unregister(browser)
cls._thread_local.browser = None
cls._thread_local.tab = None
cls._thread_local.served = 0
@classmethod
def refresh_tab(cls):
"""Full recycle: quit + new browser."""
cls.release_tab()
return cls.get_tab()
@classmethod
def shutdown(cls):
"""Quit every browser we still track."""
cls.release_tab()
with cls._all_browsers_lock:
browsers = list(cls._all_browsers)
cls._all_browsers.clear()
for b in browsers:
try:
b.quit(del_data=True)
except TypeError:
try:
b.quit()
except Exception:
pass
except Exception:
pass
@classmethod
def live_count(cls) -> int:
with cls._all_browsers_lock:
return len(cls._all_browsers)
@classmethod
def get_browser(cls):
return getattr(cls._thread_local, "browser", None)