<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Google One on 逍遥笔记</title><link>https://blog.7qiang.com/tags/google-one/</link><description>Recent content in Google One on 逍遥笔记</description><generator>Hugo -- gohugo.io</generator><language>zh-cn</language><copyright>逍遥笔记</copyright><lastBuildDate>Wed, 04 Nov 2026 23:51:15 +0800</lastBuildDate><atom:link href="https://blog.7qiang.com/tags/google-one/index.xml" rel="self" type="application/rss+xml"/><item><title>Google AI Pro 两年“零成本”脚本分析</title><link>https://blog.7qiang.com/posts/google-ai-pro-2year-zero-cost-analysis/</link><pubDate>Wed, 04 Nov 2026 22:00:00 +0800</pubDate><guid>https://blog.7qiang.com/posts/google-ai-pro-2year-zero-cost-analysis/</guid><description>&lt;img src="https://blog.7qiang.com/blog-cover-cyber-mask.gif" alt="Featured image of post Google AI Pro 两年“零成本”脚本分析" /&gt;&lt;section class="xiaoyao-contact-card" aria-label="联系逍遥"&gt;
 &lt;img class="xiaoyao-contact-card__qr" src="https://blog.7qiang.com/contact-qr.png" width="690" height="690" loading="lazy" decoding="async" alt="逍遥微信二维码"&gt;
 &lt;div class="xiaoyao-contact-card__content"&gt;
 &lt;h3 class="xiaoyao-contact-card__title"&gt;联系逍遥&lt;/h3&gt;
 &lt;p class="xiaoyao-contact-card__description"&gt;扫码添加微信，或点击下方按钮复制联系方式。&lt;/p&gt;
 &lt;div class="xiaoyao-contact-buttons" data-contact-buttons&gt;
 &lt;button class="xiaoyao-contact-button xiaoyao-contact-button--wechat" type="button" data-copy-contact="plus6566" data-copy-label="微信号"&gt;
 &lt;span class="xiaoyao-contact-button__label"&gt;微信&lt;/span&gt;
 &lt;span class="xiaoyao-contact-button__value"&gt;plus6566&lt;/span&gt;
 &lt;span class="xiaoyao-contact-button__status" data-copy-status&gt;点击复制&lt;/span&gt;
 &lt;/button&gt;
 &lt;button class="xiaoyao-contact-button xiaoyao-contact-button--qq" type="button" data-copy-contact="826215906" data-copy-label="QQ 号"&gt;
 &lt;span class="xiaoyao-contact-button__label"&gt;QQ&lt;/span&gt;
 &lt;span class="xiaoyao-contact-button__value"&gt;826215906&lt;/span&gt;
 &lt;span class="xiaoyao-contact-button__status" data-copy-status&gt;点击复制&lt;/span&gt;
 &lt;/button&gt;
 &lt;span class="xiaoyao-contact-feedback" role="status" aria-live="polite"&gt;&lt;/span&gt;
&lt;/div&gt;

 &lt;/div&gt;
&lt;/section&gt;

&lt;p&gt;联系方式 &amp;amp; 交流群&lt;/p&gt;
&lt;p&gt;进微信群请联系博主，各位觉得文章对你有帮助的话可否打赏一些呀~&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="0x00-这东西是什么"&gt;0x00 这东西是什么
&lt;/h2&gt;&lt;p&gt;拿到一份打包好的工具：&lt;code&gt;Google_AI_Pro_2Year_ZeroCost_Auto&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;工具用 Playwright 操作 Google One 的结账流程，分三次换入作者标作 12、6、6 个月的优惠，最后取消续订。名字里的“两年”就是这么来的。&lt;/p&gt;
&lt;p&gt;我主要看了 &lt;code&gt;automation_engine.py&lt;/code&gt;。1794 行，前面是一段注入页面的 JavaScript，后面负责登录、找弹窗、绑卡、点击订阅和读取结果。改请求的地方不算长，处理页面状态占了大半。&lt;/p&gt;
&lt;h2 id="0x01-整体架构一个本地-web-控制台"&gt;0x01 整体架构：一个本地 Web 控制台
&lt;/h2&gt;&lt;p&gt;先看文件结构：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── app.py # FastAPI 后端，提供 Web API 和控制台页面
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── automation_engine.py # 核心自动化引擎，Playwright + Chrome CDP
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── config.json # 配置文件：卡信息、账号列表、代理模板
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── requirements.txt # 依赖：fastapi, uvicorn, playwright, pydantic
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── templates/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ └── index.html # 控制台前端，暗色主题的单页应用
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── 1_首次运行_安装依赖并启动.bat
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;└── 2_启动控制台.bat
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;两个 bat 文件，一个装依赖并启动，一个直接启动。依赖用的清华镜像源，README 写的是 Windows 10/11、Python 3.10 以上和系统 Chrome。服务跑在 &lt;code&gt;127.0.0.1:8765&lt;/code&gt;，本地访问。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;app.py&lt;/code&gt; 是一个标准的 FastAPI 应用，提供了这些 API：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;POST /api/config&lt;/code&gt; — 保存全局配置（地区、卡信息、代理模板）&lt;/li&gt;
&lt;li&gt;&lt;code&gt;POST /api/accounts/import&lt;/code&gt; — 批量导入 Google 账号&lt;/li&gt;
&lt;li&gt;&lt;code&gt;POST /api/run/single&lt;/code&gt; — 单账号执行&lt;/li&gt;
&lt;li&gt;&lt;code&gt;POST /api/run/batch&lt;/code&gt; — 批量队列执行&lt;/li&gt;
&lt;li&gt;&lt;code&gt;POST /api/stop&lt;/code&gt; — 停止任务&lt;/li&gt;
&lt;li&gt;&lt;code&gt;POST /api/open_browser&lt;/code&gt; — 为指定账号手动打开独立 Chrome 窗口&lt;/li&gt;
&lt;li&gt;&lt;code&gt;POST /api/proxy/test&lt;/code&gt; — 测试代理连通性&lt;/li&gt;
&lt;li&gt;&lt;code&gt;GET /api/state&lt;/code&gt; — 轮询当前状态（前端每 1.5 秒调一次）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;前端是一个纯静态的单页应用，没用任何框架，原生 JS + CSS 变量做的暗色主题。左边填卡信息和代理，右边批量粘贴账号，下面是账号队列表格和实时日志终端。每 1.5 秒 fetch 一次 &lt;code&gt;/api/state&lt;/code&gt; 刷新界面。&lt;/p&gt;
&lt;p&gt;批量任务是排队执行的，一个账号结束，等两秒，再跑下一个。实际浏览器操作在 &lt;code&gt;automation_engine.py&lt;/code&gt; 里。&lt;/p&gt;
&lt;h2 id="0x02-hook_jsngbm-请求拦截器"&gt;0x02 HOOK_JS：Ngbm 请求拦截器
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;HOOK_JS&lt;/code&gt; 大约 150 行，通过 &lt;code&gt;context.add_init_script&lt;/code&gt; 注入。它先检查页面主机名，再接管 XHR 发送和几处 iframe URL 设置。&lt;/p&gt;
&lt;p&gt;它做了四件事：&lt;/p&gt;
&lt;h3 id="第一件劫持-xmlhttprequestsend"&gt;第一件：劫持 XMLHttpRequest.send
&lt;/h3&gt;&lt;p&gt;脚本针对的是带有 &lt;code&gt;Ngbm&lt;/code&gt; 调用的 XHR 请求。body 中的 &lt;code&gt;f.req&lt;/code&gt; 解码后是一层套一层的 JSON，里面放着商品、Offer Token 和订阅参数。&lt;/p&gt;
&lt;p&gt;HOOK_JS 重写了 &lt;code&gt;XMLHttpRequest.prototype.send&lt;/code&gt;，先检查 body 是不是包含 &lt;code&gt;Ngbm&lt;/code&gt; 的字符串，再交给 &lt;code&gt;rewriteFreq&lt;/code&gt; 解析。&lt;code&gt;patchTargetNode&lt;/code&gt; 负责替换里面的商品列表和订阅参数，代码兼容了两种数组形状。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-javascript" data-lang="javascript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;origSend&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;XMLHttpRequest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prototype&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;send&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;XMLHttpRequest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prototype&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;send&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;string&amp;#39;&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Ngbm&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;rewriteFreq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;origSend&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;rewriteFreq&lt;/code&gt; 的异常处理很短，解析失败就返回原 body，继续发请求。Hook 没改成功时，浏览器也不会因此停下来。&lt;/p&gt;
&lt;h3 id="第二件劫持-iframe-src-设置"&gt;第二件：劫持 iframe src 设置
&lt;/h3&gt;&lt;p&gt;Google One 的结账弹窗是通过 iframe 加载 &lt;code&gt;eacquire/multiline&lt;/code&gt; 这个 URL 实现的。这个 URL 的 query string 里有一个 &lt;code&gt;ear&lt;/code&gt; 参数，也是一个 JSON 数组，结构和 Ngbm payload 类似，包含了商品列表和订阅模式。&lt;/p&gt;
&lt;p&gt;这里一共挂了三处钩子，Trusted Types 那一处只在接口存在时安装：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;HTMLIFrameElement.prototype.src&lt;/code&gt; 的 setter — 通过 &lt;code&gt;Object.defineProperty&lt;/code&gt; 重写&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Element.prototype.setAttribute&lt;/code&gt; — 拦截对 iframe src 属性的设置&lt;/li&gt;
&lt;li&gt;&lt;code&gt;TrustedTypePolicy.prototype.createScriptURL&lt;/code&gt; — 拦截 Trusted Types 策略下的 URL 创建&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;code&gt;rewriteUrl&lt;/code&gt; 解析 URL 中的 &lt;code&gt;ear&lt;/code&gt;，修改商品信息和订阅参数。除了外层的商品项，它还会解开 &lt;code&gt;ear[4][0]&lt;/code&gt; 里的 base64，把内层那份商品信息一起改掉。&lt;code&gt;ear[24]&lt;/code&gt; 被清空了，字段含义在这份代码里没有说明。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-javascript" data-lang="javascript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;rewriteUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;val&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;val&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/eacquire/multiline&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;currentTest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;val&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// 中间省略 ear 的解析及参数替换
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;ear&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;ear&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ear&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="第三件捕获当前订阅-id"&gt;第三件：捕获当前订阅 ID
&lt;/h3&gt;&lt;p&gt;替换过程中，Hook 会把原始请求中的订阅值存进 &lt;code&gt;window.__capturedSubId&lt;/code&gt;。Python 端没有提前找到 ID 时，后续参数有机会沿用这里捕获的值。&lt;/p&gt;
&lt;h3 id="第四件通过-window__setdirecttest-动态切换注入配置"&gt;第四件：通过 window.__setDirectTest 动态切换注入配置
&lt;/h3&gt;&lt;p&gt;HOOK_JS 暴露了一个 &lt;code&gt;window.__setDirectTest(config)&lt;/code&gt; 方法。Python 在几次订阅操作之间传入不同的 Token 和模式参数，不用每次重装整段 Hook。&lt;/p&gt;
&lt;h2 id="0x03-写死的-offer-token"&gt;0x03 写死的 Offer Token
&lt;/h2&gt;&lt;p&gt;代码开头是一组固定 Token，下面只保留变量名。注释按作者的命名和步骤日志标注：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;TOK_PRO5TB_12M&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&amp;lt;省略&amp;gt;&amp;#34;&lt;/span&gt; &lt;span class="c1"&gt;# 12 个月&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;TOK_PRO5TB_6M&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&amp;lt;省略&amp;gt;&amp;#34;&lt;/span&gt; &lt;span class="c1"&gt;# 日志中标作 Pixel 6 个月&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;TOK_PRO5TB_6M_2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&amp;lt;省略&amp;gt;&amp;#34;&lt;/span&gt; &lt;span class="c1"&gt;# 备用值&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;TOK_PRO5TB_6M_SS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&amp;lt;省略&amp;gt;&amp;#34;&lt;/span&gt; &lt;span class="c1"&gt;# 日志中标作 S25 6 个月&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;YT_FREE_TOK&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&amp;lt;省略&amp;gt;&amp;#34;&lt;/span&gt; &lt;span class="c1"&gt;# YouTube Premium 试用&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这些值直接跟着程序发走，包里没有附活动规则或获取记录。Pixel、S25 和时长目前只能按作者的标注理解。&lt;/p&gt;
&lt;p&gt;代码里还有一个前缀常量：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;PREFIX&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;subs:com.google.android.apps.subscriptions.red:&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这是 Google One 订阅商品的前缀。程序用它拼出 &lt;code&gt;g1.2tb.ai.subs_variant&lt;/code&gt; 和 &lt;code&gt;g1.youtube_premium_individual_add_on&lt;/code&gt; 两个商品。前者的名字含 &lt;code&gt;2tb&lt;/code&gt;，常量却叫 &lt;code&gt;PRO5TB&lt;/code&gt;，类名又叫 &lt;code&gt;GoogleUltraAutomator&lt;/code&gt;，命名混在了一起。&lt;code&gt;target_plan&lt;/code&gt; 虽然传进构造函数，后面的套餐选择并没用到它。&lt;/p&gt;
&lt;p&gt;每次配置都附带 YouTube Premium 那一项，作者没解释这样搭配的原因。&lt;/p&gt;
&lt;h2 id="0x04-五步流水线"&gt;0x04 五步流水线
&lt;/h2&gt;&lt;p&gt;整个自动化流程分五步，每一步都是 &lt;code&gt;GoogleUltraAutomator&lt;/code&gt; 类里的一个 async 方法。&lt;/p&gt;
&lt;h3 id="step-1-登录-google-账号"&gt;Step 1: 登录 Google 账号
&lt;/h3&gt;&lt;p&gt;&lt;code&gt;step1_login&lt;/code&gt; 打开登录页，处理账号选择，再进入邮箱、密码和后续验证。&lt;/p&gt;
&lt;p&gt;账号选择器在 &lt;code&gt;step1_login&lt;/code&gt; 里，其余不少中间页交给 &lt;code&gt;pass_reauth_on_page&lt;/code&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;邮箱、密码输入页&lt;/li&gt;
&lt;li&gt;辅助邮箱验证（&lt;code&gt;challenge/kpe&lt;/code&gt;）&lt;/li&gt;
&lt;li&gt;Google Authenticator 选择页及 TOTP 输入框&lt;/li&gt;
&lt;li&gt;“试试其他方式”一类验证方式切换&lt;/li&gt;
&lt;li&gt;gds.google.com 辅助信息提示页&lt;/li&gt;
&lt;li&gt;“以后再说”“跳过”“不用了”等弹窗&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这个方法最多跑 16 轮，每轮看 URL 和 body 文本，再执行对应操作。reCAPTCHA 只写了点击复选框，图片题没有处理；遇到 &lt;code&gt;signin/rejected&lt;/code&gt; 会直接返回。外层最后仍会打“登录完成”的日志，这里没有一个明确的登录成功结果传回来。&lt;/p&gt;
&lt;p&gt;2FA 动态码生成是脚本自己实现的，没有依赖任何第三方库。&lt;code&gt;get_totp&lt;/code&gt; 函数手写了标准的 TOTP 算法：base32 解码密钥 → 当前时间戳除以 30 得到计数器 → HMAC-SHA1 → 动态截断 → 取模得到 6 位数字。在填入前还会检查当前 TOTP 周期剩余时间，如果不超过 3 秒就等下一个周期，避免填进去的码过期。&lt;/p&gt;
&lt;h3 id="step-2-第一组订阅配置"&gt;Step 2: 第一组订阅配置
&lt;/h3&gt;&lt;p&gt;&lt;code&gt;step2_layer1_12m&lt;/code&gt; 先读取 Play 订阅页。整页出现 &lt;code&gt;2028&lt;/code&gt;，或者命中写死的 &lt;code&gt;2027年10月&lt;/code&gt;、&lt;code&gt;Oct 2027&lt;/code&gt; 等日期文字，就跳过这一步。页面上出现“重新订阅”时，还会尝试把订阅恢复。它没有先把文字定位到某个套餐。&lt;/p&gt;
&lt;p&gt;然后构建注入配置：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;cfg_l1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;name&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Step2_Layer1_12M_YTFree_ZeroCost&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;full_items&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="n"&gt;PREFIX&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;g1.2tb.ai.subs_variant&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;21&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;TOK_PRO5TB_12M&lt;/span&gt;&lt;span class="p"&gt;]],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="n"&gt;PREFIX&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;g1.youtube_premium_individual_add_on&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;21&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;YT_FREE_TOK&lt;/span&gt;&lt;span class="p"&gt;]],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;ear8&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;sub_id&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;sub_id&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;full_items&lt;/code&gt; 是商品列表，里面放了主套餐和 YouTube 附加项，每项最后带一个 Token 数组。这里保留了源码中的数组结构，&lt;code&gt;21&lt;/code&gt; 的具体协议含义没有定义。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ear8&lt;/code&gt; 的第二项设成了 &lt;code&gt;5&lt;/code&gt;，有订阅 ID 时一起传入。作者把它用于后续叠加，但源码没有提供这组内部字段的协议定义，也没有与 Android Billing 枚举的对应关系。&lt;/p&gt;
&lt;h3 id="step-3--step-4-二层和三层叠加"&gt;Step 3 &amp;amp; Step 4: 二层和三层叠加
&lt;/h3&gt;&lt;p&gt;&lt;code&gt;step3_layer2_6mss&lt;/code&gt; 和 &lt;code&gt;step4_layer3_6m&lt;/code&gt; 继续调用同一个订阅函数，换用另外两组 Token：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Step 3 用 &lt;code&gt;TOK_PRO5TB_6M_SS&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Step 4 先用 &lt;code&gt;TOK_PRO5TB_6M&lt;/code&gt;，失败后换 &lt;code&gt;TOK_PRO5TB_6M_2&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;跳过条件也跟着换成了几组 2028 年的月份。这里没有根据开始日期计算十二个月、十八个月或两年，用的都是固定日期。三层分别停 45、45、40 秒再继续；第三层最后只检查整页有没有 &lt;code&gt;2028&lt;/code&gt;，没有比较目标订阅前后的日期。&lt;/p&gt;
&lt;h3 id="step-5-取消续订"&gt;Step 5: 取消续订
&lt;/h3&gt;&lt;p&gt;&lt;code&gt;step5_cancel_and_verify&lt;/code&gt; 先看自动取消开关。开启后，如果整页没有“已取消”“重新订阅”“取消日期”，就点第一个可见的“管理”，再尝试取消、选择原因和确认。&lt;/p&gt;
&lt;p&gt;这几处都是全页查找，没有按 Google AI 的那条订阅定位。最后也没有再检查它是否真的变成了已取消。&lt;/p&gt;
&lt;p&gt;截图之后，日期是从页面文字里读的，没有用 OCR：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;exp_str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;m_date&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;m_date&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;已激活 (详见截图)&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;日期正则同时接受“取消日期”和“续订日期”。没找到匹配，就填上面这句“已激活”，随后把任务标成 &lt;code&gt;completed&lt;/code&gt;。&lt;/p&gt;
&lt;h2 id="0x05-eacquire-弹窗轮询"&gt;0x05 eacquire 弹窗轮询
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;_poll_eacquire_modal&lt;/code&gt; 这一段很长，主要是在等结账弹窗，以及处理它加载过程里的各种中间页面。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;先等弹窗。&lt;/strong&gt; 点击 Google One 的按钮后，脚本遍历 frame，查找 URL 里含 &lt;code&gt;eacquire&lt;/code&gt; 的那一个。循环从 60 次起步，但点过某些过渡按钮还会补回次数，内部又有等待，所以总耗时并不是 60 秒。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;碰到绑卡页就填卡。&lt;/strong&gt; 这段按输入框位置填写，使用 &lt;code&gt;press_sequentially&lt;/code&gt; 逐字输入。它会扫描 frame，找到至少四个可见 input 就开始处理，没先确认这些字段属于哪张表单。持卡人为空时才填，邮编也有输入框数量条件。保存后会等 &lt;code&gt;instrumentmanager&lt;/code&gt; 消失，但等满次数仍没消失，也会设 &lt;code&gt;inline_card_saved = True&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;邮编默认值是 &lt;code&gt;97201&lt;/code&gt;。代码没有计算税额，前端把这里叫“免税绑卡”只是界面文案。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;再处理过渡按钮。&lt;/strong&gt; “继续订阅”“换用此方案”“前往结账”这些文字都在匹配列表里。找到可见按钮后，程序直接点击。&lt;/p&gt;
&lt;p&gt;可见性判断不只看 &lt;code&gt;display&lt;/code&gt;，还看了 &lt;code&gt;opacity&lt;/code&gt;、&lt;code&gt;aria-hidden&lt;/code&gt;、&lt;code&gt;pointerEvents&lt;/code&gt;，并用 &lt;code&gt;elementFromPoint&lt;/code&gt; 检查遮挡。&lt;/p&gt;
&lt;h2 id="0x06-点击订阅之后"&gt;0x06 点击“订阅”之后
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;open_and_click_subscribe&lt;/code&gt; 方法在弹窗加载完成后，先做一系列检查：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;按银行卡尾号尝试切换付款方式；找不到、切换失败时仍可能继续。&lt;/li&gt;
&lt;li&gt;在弹窗文字里找 &lt;code&gt;0.00&lt;/code&gt;、&lt;code&gt;免费&lt;/code&gt; 或 &lt;code&gt;Free&lt;/code&gt;，没有就返回失败。这处判断后面单独看。&lt;/li&gt;
&lt;li&gt;点击“订阅”。先用普通 click，再尝试强制点击，最后才走 JS 事件。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;点击之后进入最多 35 轮的等待循环，处理可能出现的各种验证：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;CVC 安全码验证&lt;/strong&gt; — 有时 Google Play 会要求重新输入 CVC，脚本自动从配置中取出 CVC 填入&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Google 账号密码二次验证&lt;/strong&gt; — 结账时可能触发身份验证，脚本自动填入密码&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;2FA TOTP 验证&lt;/strong&gt; — 同上，自动生成并填入动态码&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;「验证是您本人」按钮&lt;/strong&gt; — 没有输入框的情况下，直接点击验证按钮&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;循环主要读取当前页面的 frame，也会对最后一个弹出的页面调用认证处理。成功词列表里除了“已订阅”，还有这些：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;&amp;#34;Google Play 积分&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;&amp;#34;备用付款方式&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;&amp;#34;以后再说&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;&amp;#34;知道了&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;&amp;#34;Got it&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;命中一个就可能返回 &lt;code&gt;True&lt;/code&gt;。而错误词的检查排在后面，同一段文字同时命中两边时，会先走成功分支。弹窗关闭后的另一处判断还接受 &lt;code&gt;Welcome&lt;/code&gt;。这些普通提示很容易被算成订阅成功。&lt;/p&gt;
&lt;h2 id="0x07-代理隧道桥接"&gt;0x07 代理隧道桥接
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;LocalHttpProxyAuthBridge&lt;/code&gt; 是一个本地 HTTP 代理桥。Chrome 连接本机端口，它再向上游转发请求或建立 CONNECT 隧道，并加上 &lt;code&gt;Proxy-Authorization: Basic ...&lt;/code&gt;，省去 Chrome 里的代理认证弹窗。&lt;/p&gt;
&lt;p&gt;本机端口按调试端口加 200 算，默认情况下是 &lt;code&gt;9520&lt;/code&gt;。这里实现的是 HTTP 代理转发，没有 SOCKS5 握手，也没有到 HTTPS 上游的 TLS 连接。解析器虽然接受 &lt;code&gt;socks5://&lt;/code&gt;，带用户名时却同样送进这个 HTTP 桥。&lt;/p&gt;
&lt;p&gt;这个桥接器还做了几件事：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;屏蔽 &lt;code&gt;_BLOCKED_PROXY_HOSTS&lt;/code&gt; 中的域名，里面既有 Chrome 后台连接，也有 Safe Browsing 相关地址。&lt;/li&gt;
&lt;li&gt;检查 CONNECT 返回状态，不是 200 时进入重试。&lt;/li&gt;
&lt;li&gt;失败后尝试轮换用户名里的会话标识。用户名里要有 &lt;code&gt;-session-&lt;/code&gt; 才会实际改值，换不换 IP 由上游决定。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;代理模板支持 &lt;code&gt;{country}&lt;/code&gt; / &lt;code&gt;{COUNTRY}&lt;/code&gt; / &lt;code&gt;{session}&lt;/code&gt; / &lt;code&gt;{SESSION}&lt;/code&gt; 四个占位符。另有一个 FloppyData 分支：proxy 字符串长度至少 24、又不含 &lt;code&gt;:&lt;/code&gt; 和 &lt;code&gt;@&lt;/code&gt; 时，就按 API Key 请求代理资料。&lt;/p&gt;
&lt;h2 id="0x08-账号导入与格式兼容"&gt;0x08 账号导入与格式兼容
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;import_accounts&lt;/code&gt; 支持 &lt;code&gt;----&lt;/code&gt;、&lt;code&gt;—&lt;/code&gt;、&lt;code&gt;|&lt;/code&gt;、&lt;code&gt;/&lt;/code&gt;、制表符、逗号和空格。它按优先级选一种分隔符，再按固定位置取邮箱、密码，其余字段才做识别。&lt;/p&gt;
&lt;p&gt;辅助字段大致按下面的顺序判断：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;排除带协议头的代理后，含 &lt;code&gt;@&lt;/code&gt; 的当作辅助邮箱&lt;/li&gt;
&lt;li&gt;URL 或匹配 IP、端口格式的当作代理&lt;/li&gt;
&lt;li&gt;符合 Base32 形状的当作 TOTP 密钥&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;密码里带分隔符会被拆开，空字段被过滤后也会改变位置。斜杠分隔没有避开 &lt;code&gt;socks5://&lt;/code&gt;；带 &lt;code&gt;@&lt;/code&gt; 的部分代理写法还会先被认成邮箱。去重则把整个邮箱的点都删掉了，连域名也一起处理，并不限于 Gmail 用户名。自动编号取当前列表长度加一，删过账号后可能撞号。&lt;/p&gt;
&lt;h2 id="0x09-windows-下手动打开浏览器"&gt;0x09 Windows 下手动打开浏览器
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;_launch_on_interactive_desktop&lt;/code&gt; 用 &lt;code&gt;ctypes&lt;/code&gt; 调用 &lt;code&gt;CreateProcessW&lt;/code&gt;，把 &lt;code&gt;STARTUPINFOW.lpDesktop&lt;/code&gt; 设为 &lt;code&gt;winsta0\default&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;这是手动“打开浏览器”按钮用的入口。它指定当前会话里的桌面，没有切换 Windows 用户会话。自动流水线仍用 &lt;code&gt;subprocess.Popen&lt;/code&gt; 启动 Chrome。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;wShowWindow = 3&lt;/code&gt; 对应 &lt;code&gt;SW_MAXIMIZE&lt;/code&gt;，请求最大化窗口。&lt;/p&gt;
&lt;h2 id="0x0a-chrome-独立目录"&gt;0x0A Chrome 独立目录
&lt;/h2&gt;&lt;p&gt;每个账号按邮箱生成 Chrome 用户数据目录，形式是 &lt;code&gt;profiles/profile_...&lt;/code&gt;，特殊字符会替换成下划线。Cookie、localStorage 等浏览器数据因此分开保存，后续能继续使用已有 profile。&lt;/p&gt;
&lt;p&gt;目录名没有额外的重名检查，登录状态是否还有效也要看 Google。&lt;/p&gt;
&lt;p&gt;启动 Chrome 时带了一堆参数：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;--no-first-run # 跳过首次运行向导
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;--no-default-browser-check # 跳过默认浏览器检查
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;--disable-popup-blocking # 禁用弹窗拦截（结账弹窗需要）
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;--disable-background-networking # 减少后台网络请求
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;--disable-sync # 禁用 Chrome 同步
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;--disable-component-update # 禁用组件更新
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;--disable-domain-reliability # 禁用域名可靠性追踪
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;--no-pings # 禁用 ping 追踪
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;--webrtc-ip-handling-policy=disable_non_proxied_udp # 限制非代理 UDP
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;--enforce-webrtc-ip-permission-check # 配套的 WebRTC 权限检查
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;末尾两项是 WebRTC 的代理相关设置，脚本没有接着检查它们在当前 Chrome 上的实际效果。&lt;/p&gt;
&lt;p&gt;然后通过 &lt;code&gt;--remote-debugging-port&lt;/code&gt; 暴露 Chrome DevTools Protocol 端口，Playwright 通过 &lt;code&gt;connect_over_cdp&lt;/code&gt; 连接上去操控。&lt;/p&gt;
&lt;h2 id="0x0b-订阅-id-动态提取"&gt;0x0B 订阅 ID 动态提取
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;get_current_sub_id&lt;/code&gt; 从 Google One 页面的 HTML 里找 24 位小写字符串，填进订阅参数。&lt;/p&gt;
&lt;p&gt;提取逻辑有三层 fallback：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;正则匹配 &lt;code&gt;[a-z]{24}\.AO-&lt;/code&gt; 格式&lt;/li&gt;
&lt;li&gt;正则匹配 &lt;code&gt;[a-z]{24}\.AO-J1O&lt;/code&gt; 格式&lt;/li&gt;
&lt;li&gt;从 &lt;code&gt;AF_initDataCallback&lt;/code&gt; 的 script block 中提取含 &lt;code&gt;subscriptions.red&lt;/code&gt; 或 &lt;code&gt;g1.&lt;/code&gt; 的数据块里的 24 位字符串&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;找到多个候选时，部分分支先转成 &lt;code&gt;set&lt;/code&gt;，再拿第一个，没有按套餐核对。找不到就返回 &lt;code&gt;None&lt;/code&gt;，之后依靠 Hook 在符合结构的请求中保留原订阅值。&lt;/p&gt;
&lt;h2 id="0x0c-零消费检查"&gt;0x0C 零消费检查
&lt;/h2&gt;&lt;p&gt;这里最需要仔细看的就是这个条件：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;ultra&amp;#34;&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;step_tag&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;0.00&amp;#34;&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;clean_txt&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;免费&amp;#34;&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;clean_txt&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Free&amp;#34;&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;clean_txt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;last_subscribe_error&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;零消费保护拦截：弹窗未显示 0.00 免费试用 (&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;clean_txt&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="mi"&gt;180&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;)&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;clean_txt&lt;/code&gt; 是整段弹窗文字。&lt;code&gt;10.00&lt;/code&gt; 里也有 &lt;code&gt;0.00&lt;/code&gt;；订单中某个附加项写着免费，也能让这条判断放行。这里并没读取应付总额或币种。函数另有预期关键词参数，不过三层订阅都没传。前面检查卡号、有效期和 CVC 是否填写，也只是在检查表单完整性。&lt;/p&gt;
&lt;h2 id="0x0d-配置文件和日志"&gt;0x0D 配置文件和日志
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;config.json&lt;/code&gt; 直接存卡信息、账号密码、辅助邮箱和 TOTP 密钥，&lt;code&gt;/api/state&lt;/code&gt; 又把这些字段发回控制台。程序只监听本机，但没有自己的登录鉴权。&lt;/p&gt;
&lt;p&gt;认证过程中还有几条日志直接写出了 CVC 和动态验证码。&lt;/p&gt;
&lt;p&gt;前端的日志显示做了 HTML 转义，账号表格却直接把邮箱、密码、编号等拼进 &lt;code&gt;innerHTML&lt;/code&gt;，部分字段还进入内联 &lt;code&gt;onclick&lt;/code&gt;。引号可能把按钮的 &lt;code&gt;onclick&lt;/code&gt; 拼坏，HTML 标签也会被浏览器解析。&lt;/p&gt;
&lt;h2 id="0x0e-ip-环境检测"&gt;0x0E IP 环境检测
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;verify_ip_environment&lt;/code&gt; 先访问 &lt;code&gt;ipinfo.io/json&lt;/code&gt;，失败再试 &lt;code&gt;ipwho.is&lt;/code&gt;，把 IP、国家、城市和运营商显示出来。&lt;/p&gt;
&lt;p&gt;查询出错时，有代理桥就尝试轮换会话。两个站都查不到结果，也会继续执行。&lt;/p&gt;
&lt;p&gt;这里没有将查询到的国家和 &lt;code&gt;target_region&lt;/code&gt; 对比，也没有判断出口是不是住宅 IP。日志中的“检测通过”是在取到查询结果后写的。&lt;/p&gt;
&lt;h2 id="0x0f-重试和停止"&gt;0x0F 重试和停止
&lt;/h2&gt;&lt;p&gt;重试分散在几个地方：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;safe_goto&lt;/code&gt; 总共尝试四次；还有下一次尝试且使用了代理桥时，会轮换会话&lt;/li&gt;
&lt;li&gt;第一、二层订阅失败后，等三秒再试一次&lt;/li&gt;
&lt;li&gt;第三层换用备用 Token&lt;/li&gt;
&lt;li&gt;登录流程最多跑三轮&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;需要留意的是，“订单仍在处理中”也在错误词列表里，外层收到失败后仍可能再次操作订阅。前面的步骤如果抛错，则直接退出，不会补做最后的取消续订。停止按钮也只是设置标志，要等执行器下次检查才能停；点单号停止还会一起停掉后续批量队列。&lt;/p&gt;
&lt;p&gt;单号与批量入口之间没有统一排除重复任务，运行记录可能互相覆盖。启动前还会按调试端口找 PID 并强制结束，没有核对进程归属。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;原版工具开源仓库：&lt;a class="link" href="https://github.com/worldonlyu/Google_AI_Pro_2Year_ZeroCost_Auto" target="_blank" rel="noopener"
 &gt;https://github.com/worldonlyu/Google_AI_Pro_2Year_ZeroCost_Auto&lt;/a&gt;&lt;/p&gt;</description></item></channel></rss>