From a6f9253e9bae9456e5664b63c8a568bc21286afe Mon Sep 17 00:00:00 2001 From: Aaron Liang <76561968+AaronL725@users.noreply.github.com> Date: Thu, 9 Jul 2026 22:33:47 +0800 Subject: [PATCH] chore: remove temporary proxy fix workflow --- .github/workflows/apply-proxy-fix.yml | 514 -------------------------- 1 file changed, 514 deletions(-) delete mode 100644 .github/workflows/apply-proxy-fix.yml diff --git a/.github/workflows/apply-proxy-fix.yml b/.github/workflows/apply-proxy-fix.yml deleted file mode 100644 index 3c8cb0a..0000000 --- a/.github/workflows/apply-proxy-fix.yml +++ /dev/null @@ -1,514 +0,0 @@ -name: Temporary Proxy Fix - -on: - push: - branches: - - main - paths: - - .github/apply-proxy-fix-trigger.txt - -permissions: - contents: write - -jobs: - apply-fix: - if: github.actor != 'github-actions[bot]' - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - ref: main - fetch-depth: 0 - - - name: Apply minimal proxy fix - shell: bash - run: | - set -euo pipefail - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - - python - <<'PY' - from pathlib import Path - - def replace_once(text, old, new, label): - if old not in text: - raise SystemExit(f"missing expected block: {label}") - return text.replace(old, new, 1) - - p = Path("grok_register_ttk.py") - s = p.read_text(encoding="utf-8-sig") - - s = replace_once( - s, - '''import json\n''', - '''import json\nimport base64\nimport select\nimport socket\nimport socketserver\nimport ssl\nimport urllib.parse\n''', - "proxy helper imports", - ) - - old_get_proxies = '''def get_proxies():\n proxy = config.get("proxy", "")\n if proxy:\n return {"http": proxy, "https": proxy}\n return {}\n''' - new_get_proxies = r'''def get_configured_proxy(): - return str(config.get("proxy", "") or "").strip() - - -def get_proxies(): - proxy = get_configured_proxy() - if proxy: - return {"http": proxy, "https": proxy} - return {} - - -def _parse_proxy_url(proxy): - raw = str(proxy or "").strip() - if not raw: - return None - if "://" not in raw: - raw = "http://" + raw - try: - return urllib.parse.urlsplit(raw) - except Exception: - return None - - -def _safe_proxy_port(parsed): - try: - return parsed.port - except Exception: - return None - - -def _proxy_has_auth(proxy): - parsed = _parse_proxy_url(proxy) - return bool(parsed and parsed.hostname and (parsed.username is not None or parsed.password is not None)) - - -def _strip_proxy_auth(proxy): - raw = str(proxy or "").strip() - parsed = _parse_proxy_url(raw) - if not parsed or not parsed.hostname: - return raw - host = parsed.hostname - if ":" in host and not host.startswith("["): - host = f"[{host}]" - port = _safe_proxy_port(parsed) - netloc = f"{host}:{port}" if port else host - stripped = urllib.parse.urlunsplit((parsed.scheme or "http", netloc, parsed.path, parsed.query, parsed.fragment)) - if "://" not in raw: - return stripped.split("://", 1)[1] - return stripped - - -def _proxy_endpoint_terms(proxy=None): - parsed = _parse_proxy_url(proxy or get_configured_proxy()) - if not parsed or not parsed.hostname: - return [] - terms = [parsed.hostname] - port = _safe_proxy_port(parsed) - if port: - terms.append(f"{parsed.hostname}:{port}") - terms.append(f"port {port}") - return [x.lower() for x in terms if x] - - -def is_proxy_connection_error(exc): - if not get_configured_proxy(): - return False - err = str(exc or "").lower() - if not err: - return False - if any(x in err for x in ("proxy", "tunnel", "socks")): - return True - connect_markers = ( - "could not connect", - "failed to connect", - "connection refused", - "connection reset", - "connect error", - "timed out", - "timeout", - ) - if any(x in err for x in connect_markers): - terms = _proxy_endpoint_terms() - if not terms or any(t in err for t in terms): - return True - return False - - -def page_has_proxy_error(page_obj): - try: - url = str(getattr(page_obj, "url", "") or "") - title = str(page_obj.run_js("return document.title || ''") or "") - body = str(page_obj.run_js("return document.body ? document.body.innerText.slice(0, 2000) : ''") or "") - except Exception: - return False - text = f"{url}\n{title}\n{body}".lower() - return any( - marker in text - for marker in ( - "err_proxy", - "proxy connection failed", - "proxy server", - "proxy authentication", - "tunnel connection failed", - "无法连接到代理服务器", - "代理服务器", - ) - ) - - -class _ReusableThreadingTCPServer(socketserver.ThreadingTCPServer): - allow_reuse_address = True - daemon_threads = True - - -def _proxy_recv_until_headers(sock, timeout=20, limit=65536): - sock.settimeout(timeout) - data = b"" - while b"\r\n\r\n" not in data and len(data) < limit: - chunk = sock.recv(4096) - if not chunk: - break - data += chunk - return data - - -def _proxy_relay(left, right, timeout=60): - left.settimeout(timeout) - right.settimeout(timeout) - sockets = [left, right] - while True: - readable, _, _ = select.select(sockets, [], [], timeout) - if not readable: - return - for sock in readable: - data = sock.recv(65536) - if not data: - return - peer = right if sock is left else left - peer.sendall(data) - - -class _LocalAuthProxyBridgeHandler(socketserver.BaseRequestHandler): - def handle(self): - bridge = self.server.bridge - upstream = None - try: - initial = _proxy_recv_until_headers(self.request, timeout=bridge.timeout) - if not initial: - return - first_line = initial.split(b"\r\n", 1)[0].decode("latin1", "ignore") - if first_line.upper().startswith("CONNECT "): - target = first_line.split()[1] - upstream = bridge.open_upstream() - req = [f"CONNECT {target} HTTP/1.1", f"Host: {target}"] - if bridge.auth_header: - req.append(f"Proxy-Authorization: Basic {bridge.auth_header}") - upstream.sendall(("\r\n".join(req) + "\r\n\r\n").encode("latin1")) - response = _proxy_recv_until_headers(upstream, timeout=bridge.timeout) - if response: - self.request.sendall(response) - status = response.split(b"\r\n", 1)[0] - if b" 200 " not in status: - return - _proxy_relay(self.request, upstream, timeout=bridge.relay_timeout) - else: - upstream = bridge.open_upstream() - upstream.sendall(bridge.inject_proxy_auth(initial)) - _proxy_relay(self.request, upstream, timeout=bridge.relay_timeout) - except Exception: - return - finally: - if upstream is not None: - try: - upstream.close() - except Exception: - pass - - -class LocalAuthProxyBridge: - def __init__(self, proxy_url): - parsed = _parse_proxy_url(proxy_url) - if not parsed or not parsed.hostname: - raise ValueError("认证代理地址格式无效") - if (parsed.scheme or "http").lower() not in ("http", "https"): - raise ValueError("Chromium 本地认证代理桥仅支持 http/https 上游代理") - self.upstream_scheme = (parsed.scheme or "http").lower() - self.upstream_host = parsed.hostname - self.upstream_port = _safe_proxy_port(parsed) or (443 if self.upstream_scheme == "https" else 80) - username = urllib.parse.unquote(parsed.username or "") - password = urllib.parse.unquote(parsed.password or "") - raw_auth = f"{username}:{password}".encode("utf-8") - self.auth_header = base64.b64encode(raw_auth).decode("ascii") if (username or password) else "" - self.timeout = 20 - self.relay_timeout = 90 - self.server = None - self.thread = None - self.local_proxy = "" - - def open_upstream(self): - sock = socket.create_connection((self.upstream_host, self.upstream_port), timeout=self.timeout) - if self.upstream_scheme == "https": - context = ssl.create_default_context() - sock = context.wrap_socket(sock, server_hostname=self.upstream_host) - sock.settimeout(self.timeout) - return sock - - def inject_proxy_auth(self, data): - if not self.auth_header or b"\r\n\r\n" not in data: - return data - if b"\r\nproxy-authorization:" in data.lower(): - return data - head, body = data.split(b"\r\n\r\n", 1) - auth_line = f"Proxy-Authorization: Basic {self.auth_header}".encode("latin1") - return head + b"\r\n" + auth_line + b"\r\n\r\n" + body - - def start(self): - self.server = _ReusableThreadingTCPServer(("127.0.0.1", 0), _LocalAuthProxyBridgeHandler) - self.server.bridge = self - port = self.server.server_address[1] - self.local_proxy = f"http://127.0.0.1:{port}" - self.thread = threading.Thread(target=self.server.serve_forever, daemon=True) - self.thread.start() - return self.local_proxy - - def stop(self): - if self.server is not None: - try: - self.server.shutdown() - self.server.server_close() - except Exception: - pass - self.server = None - self.thread = None - self.local_proxy = "" - - -def stop_browser_proxy_bridge(): - global browser_proxy_bridge - if browser_proxy_bridge is not None: - try: - browser_proxy_bridge.stop() - except Exception: - pass - browser_proxy_bridge = None - - -def prepare_browser_proxy(use_proxy=True, log_callback=None): - proxy = get_configured_proxy() - if not use_proxy or not proxy: - return "", None - if _proxy_has_auth(proxy): - parsed = _parse_proxy_url(proxy) - scheme = (parsed.scheme or "http").lower() if parsed else "" - if scheme in ("http", "https"): - bridge = LocalAuthProxyBridge(proxy) - browser_proxy = bridge.start() - if log_callback: - log_callback(f"[*] 已为 Chromium 启动本地认证代理桥: {browser_proxy}") - return browser_proxy, bridge - stripped = _strip_proxy_auth(proxy) - if log_callback: - log_callback("[!] Chromium 暂不直接支持该认证代理协议,已使用去认证代理地址,失败将回退直连") - return stripped, None - return proxy, None -''' - s = replace_once(s, old_get_proxies, new_get_proxies, "proxy helpers") - - old_browser_http = '''def create_browser_options():\n options = ChromiumOptions()\n options.auto_port()\n options.set_timeouts(base=1)\n if os.path.exists(EXTENSION_PATH):\n options.add_extension(EXTENSION_PATH)\n return options\n\n\ndef _build_request_kwargs(**kwargs):\n request_kwargs = dict(kwargs)\n proxies = request_kwargs.pop("proxies", None)\n if proxies is None:\n proxies = get_proxies()\n if proxies:\n request_kwargs["proxies"] = proxies\n request_kwargs.setdefault("timeout", 15)\n return request_kwargs\n\n\ndef http_get(url, **kwargs):\n try:\n return requests.get(url, **_build_request_kwargs(**kwargs))\n except Exception as exc:\n err = str(exc)\n # 代理不可用时自动回退为直连,避免整个流程直接失败\n if "127.0.0.1 port 7890" in err or "Could not connect to server" in err:\n retry_kwargs = dict(kwargs)\n retry_kwargs["proxies"] = {}\n return requests.get(url, **_build_request_kwargs(**retry_kwargs))\n raise\n\n\ndef http_post(url, **kwargs):\n try:\n return requests.post(url, **_build_request_kwargs(**kwargs))\n except Exception as exc:\n err = str(exc)\n if "127.0.0.1 port 7890" in err or "Could not connect to server" in err:\n retry_kwargs = dict(kwargs)\n retry_kwargs["proxies"] = {}\n return requests.post(url, **_build_request_kwargs(**retry_kwargs))\n raise\n''' - new_browser_http = r'''def apply_browser_proxy_option(options, proxy): - if not proxy: - return - if hasattr(options, "set_proxy"): - try: - options.set_proxy(proxy) - return - except Exception: - pass - if not hasattr(options, "set_argument"): - raise AttributeError("当前 DrissionPage ChromiumOptions 不支持设置浏览器代理") - try: - options.set_argument(f"--proxy-server={proxy}") - except TypeError: - options.set_argument("--proxy-server", proxy) - - -def create_browser_options(browser_proxy=""): - options = ChromiumOptions() - options.auto_port() - options.set_timeouts(base=1) - apply_browser_proxy_option(options, browser_proxy) - if os.path.exists(EXTENSION_PATH): - options.add_extension(EXTENSION_PATH) - return options - - -def _build_request_kwargs(**kwargs): - request_kwargs = dict(kwargs) - proxies = request_kwargs.pop("proxies", None) - if proxies is None: - proxies = get_proxies() - if proxies: - request_kwargs["proxies"] = proxies - request_kwargs.setdefault("timeout", 15) - return request_kwargs - - -def http_get(url, **kwargs): - request_kwargs = _build_request_kwargs(**kwargs) - try: - return requests.get(url, **request_kwargs) - except Exception as exc: - # 代理不可用时自动回退为直连,避免整个流程直接失败。 - if request_kwargs.get("proxies") and is_proxy_connection_error(exc): - retry_kwargs = dict(kwargs) - retry_kwargs["proxies"] = {} - return requests.get(url, **_build_request_kwargs(**retry_kwargs)) - raise - - -def http_post(url, **kwargs): - request_kwargs = _build_request_kwargs(**kwargs) - try: - return requests.post(url, **request_kwargs) - except Exception as exc: - if request_kwargs.get("proxies") and is_proxy_connection_error(exc): - retry_kwargs = dict(kwargs) - retry_kwargs["proxies"] = {} - return requests.post(url, **_build_request_kwargs(**retry_kwargs)) - raise -''' - s = replace_once(s, old_browser_http, new_browser_http, "browser options and HTTP proxy fallback") - - s = s.replace(", proxies={}", "") - - s = replace_once( - s, - '''browser = None\npage = None\n''', - '''browser = None\npage = None\nbrowser_proxy_bridge = None\nbrowser_started_with_proxy = False\n''', - "browser proxy globals", - ) - - old_start_stop = '''def start_browser(log_callback=None):\n global browser, page\n last_exc = None\n for attempt in range(1, 5):\n try:\n browser = Chromium(create_browser_options())\n tabs = browser.get_tabs()\n page = tabs[-1] if tabs else browser.new_tab()\n if log_callback and getattr(browser, "user_data_path", None):\n log_callback(f"[Debug] 当前浏览器资料目录: {browser.user_data_path}")\n if log_callback and attempt > 1:\n log_callback(f"[*] 浏览器第 {attempt} 次启动成功")\n return browser, page\n except Exception as exc:\n last_exc = exc\n if log_callback:\n log_callback(f"[Debug] 浏览器启动失败(第{attempt}/4次): {exc}")\n try:\n if browser is not None:\n browser.quit(del_data=True)\n except Exception:\n pass\n browser = None\n page = None\n time.sleep(min(1.5 * attempt, 4))\n raise Exception(f"浏览器启动失败,已重试4次: {last_exc}")\n\n\ndef stop_browser():\n global browser, page\n if browser is not None:\n try:\n browser.quit(del_data=True)\n except Exception:\n pass\n browser = None\n page = None\n\n\ndef restart_browser(log_callback=None):\n stop_browser()\n return start_browser(log_callback=log_callback)\n''' - new_start_stop = r'''def start_browser(log_callback=None, use_proxy=True): - global browser, page, browser_proxy_bridge, browser_started_with_proxy - last_exc = None - proxy_enabled = bool(use_proxy and get_configured_proxy()) - for attempt in range(1, 5): - bridge = None - try: - browser_proxy, bridge = prepare_browser_proxy(use_proxy=use_proxy, log_callback=log_callback) - browser = Chromium(create_browser_options(browser_proxy=browser_proxy)) - browser_proxy_bridge = bridge - browser_started_with_proxy = bool(browser_proxy) - tabs = browser.get_tabs() - page = tabs[-1] if tabs else browser.new_tab() - if log_callback and getattr(browser, "user_data_path", None): - log_callback(f"[Debug] 当前浏览器资料目录: {browser.user_data_path}") - if log_callback and get_configured_proxy(): - mode = "代理" if browser_started_with_proxy else "直连" - log_callback(f"[*] 浏览器网络模式: {mode}") - if log_callback and attempt > 1: - log_callback(f"[*] 浏览器第 {attempt} 次启动成功") - return browser, page - except Exception as exc: - last_exc = exc - if bridge is not None: - try: - bridge.stop() - except Exception: - pass - if log_callback: - mode = "代理" if proxy_enabled else "直连" - log_callback(f"[Debug] 浏览器{mode}启动失败(第{attempt}/4次): {exc}") - try: - if browser is not None: - browser.quit(del_data=True) - except Exception: - pass - browser = None - page = None - browser_proxy_bridge = None - browser_started_with_proxy = False - time.sleep(min(1.5 * attempt, 4)) - raise Exception(f"浏览器启动失败,已重试4次: {last_exc}") - - -def stop_browser(): - global browser, page, browser_started_with_proxy - if browser is not None: - try: - browser.quit(del_data=True) - except Exception: - pass - stop_browser_proxy_bridge() - browser = None - page = None - browser_started_with_proxy = False - - -def restart_browser(log_callback=None, use_proxy=True): - stop_browser() - return start_browser(log_callback=log_callback, use_proxy=use_proxy) -''' - s = replace_once(s, old_start_stop, new_start_stop, "browser lifecycle proxy handling") - - old_open_signup = '''def open_signup_page(log_callback=None, cancel_callback=None):\n global browser, page\n raise_if_cancelled(cancel_callback)\n if browser is None:\n start_browser()\n if log_callback:\n log_callback("[*] 浏览器已启动")\n try:\n page = browser.get_tab(0)\n page.get(SIGNUP_URL)\n except Exception as e:\n if log_callback:\n log_callback(f"[Debug] 打开URL异常: {e}")\n try:\n page = browser.new_tab(SIGNUP_URL)\n except Exception as e2:\n if log_callback:\n log_callback(f"[Debug] 创建新标签页异常: {e2}")\n restart_browser()\n page = browser.new_tab(SIGNUP_URL)\n page.wait.doc_loaded()\n sleep_with_cancel(2, cancel_callback)\n if log_callback:\n log_callback(f"[*] 当前URL: {page.url}")\n click_email_signup_button(\n log_callback=log_callback, cancel_callback=cancel_callback\n )\n''' - new_open_signup = r'''def open_signup_page(log_callback=None, cancel_callback=None): - global browser, page - raise_if_cancelled(cancel_callback) - if browser is None: - start_browser(log_callback=log_callback) - if log_callback: - log_callback("[*] 浏览器已启动") - - def _open_with_current_browser(): - global page - try: - page = browser.get_tab(0) - page.get(SIGNUP_URL) - except Exception as e: - if log_callback: - log_callback(f"[Debug] 打开URL异常: {e}") - page = browser.new_tab(SIGNUP_URL) - page.wait.doc_loaded() - - try: - _open_with_current_browser() - except Exception as e: - if browser_started_with_proxy and get_configured_proxy(): - if log_callback: - log_callback(f"[!] 浏览器代理访问注册页失败,自动回退直连: {e}") - restart_browser(log_callback=log_callback, use_proxy=False) - _open_with_current_browser() - else: - raise - - if browser_started_with_proxy and page_has_proxy_error(page): - if log_callback: - log_callback("[!] 浏览器页面显示代理错误,自动回退直连") - restart_browser(log_callback=log_callback, use_proxy=False) - _open_with_current_browser() - - sleep_with_cancel(2, cancel_callback) - if log_callback: - log_callback(f"[*] 当前URL: {page.url}") - click_email_signup_button( - log_callback=log_callback, cancel_callback=cancel_callback - ) -''' - s = replace_once(s, old_open_signup, new_open_signup, "signup page proxy fallback") - - p.write_text(s, encoding="utf-8-sig") - PY - - git add grok_register_ttk.py - if git diff --cached --quiet; then - echo "No proxy fix changes to commit" - git reset --quiet - else - git diff --cached --check - git commit -m "fix(proxy): route browser traffic through configured proxy" - fi - - git rm .github/workflows/apply-proxy-fix.yml .github/apply-proxy-fix-trigger.txt - git commit -m "chore: remove temporary proxy fix workflow" - git push origin HEAD:main