From 2b8b5952c1e42f2556a4e6a40bb9dfd29205ceb4 Mon Sep 17 00:00:00 2001 From: karuo Date: Tue, 31 Mar 2026 19:29:21 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=94=84=20=E5=8D=A1=E8=8B=A5AI=20=E5=90=8C?= =?UTF-8?q?=E6=AD=A5=202026-03-31=2019:29=20|=20=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=EF=BC=9ACursor=E8=A7=84=E5=88=99=E3=80=81=E6=B0=B4=E6=BA=AA?= =?UTF-8?q?=E6=95=B4=E7=90=86=E5=BD=92=E6=A1=A3=E3=80=81=E5=8D=A1=E6=9C=A8?= =?UTF-8?q?=E3=80=81=E8=BF=90=E8=90=A5=E4=B8=AD=E6=9E=A2=E5=B7=A5=E4=BD=9C?= =?UTF-8?q?=E5=8F=B0=20|=20=E6=8E=92=E9=99=A4=20>20MB:=2014=20=E4=B8=AA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .cursor/rules/karuo-ai.mdc | 1 + .../2026-03-31_Vercel与v0_mail.tm注册_OTP与团队创建.md | 20 ++ .../视频切片/脚本/append_seo_keyword_tail.py | 102 +++++++- .../木叶_视频内容/视频切片/脚本/soul_enhance.py | 15 +- .../视频切片/脚本/soul_slice_pipeline.py | 36 ++- .../木根_逆向分析/全网AI自动注册/SKILL.md | 12 + .../全网AI自动注册/脚本/vercel_mailtm_signup_loop.py | 227 ++++++++++++++++++ 运营中枢/工作台/gitea_push_log.md | 1 + 运营中枢/工作台/代码管理.md | 1 + 9 files changed, 407 insertions(+), 8 deletions(-) create mode 100644 03_卡木(木)/木根_逆向分析/全网AI自动注册/脚本/vercel_mailtm_signup_loop.py diff --git a/.cursor/rules/karuo-ai.mdc b/.cursor/rules/karuo-ai.mdc index 47b34693..30338830 100644 --- a/.cursor/rules/karuo-ai.mdc +++ b/.cursor/rules/karuo-ai.mdc @@ -46,6 +46,7 @@ alwaysApply: true - **卡若**:仅接手 **Agent 物理上不能代劳** 的动作:短信/支付/人脸/企业 SSO、不可逆删除的最终确认、需本人法律意思表示的操作、以及 **第三方强人机校验**(如 Turnstile、必须逐格输入的 OTP、仅人类可过的风控)。 - **当用户明确说「方案定了我能百分百完成」**:Agent 必须交付 **可勾选清单**(步骤序、每步做什么、成功长什么样、失败改走哪条 B 方案),**不得**用空话代替步骤。 - **浏览器 / 注册类**:Agent 仍须完成 **收信(mail.tm/IMAP)、解析邮件链接、打开正确 URL、轮询验证码**;若页面因多输入框/人机验证拒绝自动化,须在清单中写清 **为何** 需卡若手动及 **具体操作**(例如「6 位验证码逐格输入,勿整段粘贴」)。 +- **Vercel / v0 + mail.tm 取码循环**:**不要跟单邮箱死磕**。本轮收不到「Sign-up Verification」或明显走错登录页时,**放弃该 mail.tm、换新邮箱再注册**;可执行 `03_卡木(木)/木根_逆向分析/全网AI自动注册/脚本/vercel_mailtm_signup_loop.py`(见该目录 `SKILL.md` 与 `水溪_整理归档/经验库/待沉淀/2026-03-31_Vercel与v0_mail.tm注册_OTP与团队创建.md`)。 ### 经验沉淀(出了问题要入账 · 可复用) diff --git a/02_卡人(水)/水溪_整理归档/经验库/待沉淀/2026-03-31_Vercel与v0_mail.tm注册_OTP与团队创建.md b/02_卡人(水)/水溪_整理归档/经验库/待沉淀/2026-03-31_Vercel与v0_mail.tm注册_OTP与团队创建.md index a9575d51..243715a0 100644 --- a/02_卡人(水)/水溪_整理归档/经验库/待沉淀/2026-03-31_Vercel与v0_mail.tm注册_OTP与团队创建.md +++ b/02_卡人(水)/水溪_整理归档/经验库/待沉淀/2026-03-31_Vercel与v0_mail.tm注册_OTP与团队创建.md @@ -23,3 +23,23 @@ - Turnstile / 企业策略可能拦截自动化或一次性域名。 - 客观无法代劳时:向卡若交付 **可勾选清单**(含「逐格 OTP」等),符合 `.cursor/rules/karuo-ai.mdc` 问题自治分工。 + +## 循环策略(取码不行就换邮箱 · 做通) + +**原则**:不要跟**同一封收不到码 / 只有 Attempted Sign-in / OTP 总失败**的邮箱死磕——**超时无「Sign-up Verification」→ 放弃该 mail.tm → 脚本自动生成下一邮箱 → 继续注册**。 + +**脚本(木根)**:`03_卡木(木)/木根_逆向分析/全网AI自动注册/脚本/vercel_mailtm_signup_loop.py` + +```bash +cd "/Users/karuo/Documents/个人/卡若AI/03_卡木(木)/木根_逆向分析/全网AI自动注册/脚本" +# 每轮打印新邮箱 + 注册链接;你在浏览器点 Continue with Email;收不到注册验证码则自动下一轮 +python3 vercel_mailtm_signup_loop.py --max-rounds 8 --poll-timeout 180 + +# 需要先手动点发码再轮询时: +python3 vercel_mailtm_signup_loop.py --pause-enter --poll-timeout 240 + +# 每轮结果落盘备查: +python3 vercel_mailtm_signup_loop.py --out-jsonl ./vercel_mailtm_rounds.jsonl +``` + +说明:脚本**忽略**环境变量 `MAILTM_ADDRESS` / `MAILTM_PASSWORD`,每轮都是**全新** mail.tm,避免固定邮箱卡死。 diff --git a/03_卡木(木)/木叶_视频内容/视频切片/脚本/append_seo_keyword_tail.py b/03_卡木(木)/木叶_视频内容/视频切片/脚本/append_seo_keyword_tail.py index 01ff45ca..327fb2a5 100644 --- a/03_卡木(木)/木叶_视频内容/视频切片/脚本/append_seo_keyword_tail.py +++ b/03_卡木(木)/木叶_视频内容/视频切片/脚本/append_seo_keyword_tail.py @@ -14,6 +14,7 @@ from __future__ import annotations import argparse +import json import os import re import subprocess @@ -36,6 +37,10 @@ _BASE_RGB = (26, 28, 34) # 与字同系深灰蓝 _WASH_ALPHA = int(255 * 0.05) # 整屏极淡底雾(约 95% 透) _TITLE_ALPHA = int(255 * 0.09) _BODY_ALPHA = int(255 * 0.11) +# 第二页底部「轻行动」略亮于正文,仍属低调(普通人能扫到,不抢 SEO 藏词主责) +_ACTION_ALPHA = int(255 * 0.22) + +_STATE_NAME = ".soul_seo_tail_state.json" def load_keywords(path: Path) -> list[str]: @@ -114,6 +119,40 @@ def probe_size_rate(main_mp4: Path) -> tuple[int, int, int]: return width, height, sr +def probe_duration_sec(mp4: Path) -> float: + r = subprocess.run( + [ + "ffprobe", + "-v", + "error", + "-show_entries", + "format=duration", + "-of", + "default=noprint_wrappers=1:nokey=1", + str(mp4), + ], + capture_output=True, + text=True, + ) + try: + return float((r.stdout or "").strip() or 0.0) + except ValueError: + return 0.0 + + +def load_tail_state(state_path: Path) -> dict: + if not state_path.is_file(): + return {} + try: + return json.loads(state_path.read_text(encoding="utf-8")) + except (OSError, json.JSONDecodeError): + return {} + + +def save_tail_state(state_path: Path, state: dict) -> None: + state_path.write_text(json.dumps(state, ensure_ascii=False, indent=2), encoding="utf-8") + + def render_keyword_page_subtle_rgba( width: int, height: int, @@ -121,8 +160,13 @@ def render_keyword_page_subtle_rgba( png_path: Path, font_path: Path | None, page_label: str, + footer_line: str | None = None, + footer_alpha: int | None = None, ) -> None: - """全透明底 + 极淡同色系雾 + 同色系略亮字(低对比、高透明)。""" + """全透明底 + 极淡同色系雾 + 同色系略亮字(低对比、高透明)。 + + footer_line:可选,多画在底部(用于第二页轻引导,不替代正片 cta_ending)。 + """ if Image is None: raise RuntimeError("需要安装 Pillow: pip install pillow") img = Image.new("RGBA", (width, height), (0, 0, 0, 0)) @@ -134,8 +178,12 @@ def render_keyword_page_subtle_rgba( try: title_font = ImageFont.truetype(str(fp), 26) body_font = ImageFont.truetype(str(fp), 20) + try: + foot_font = ImageFont.truetype(str(fp), 18) + except OSError: + foot_font = body_font except OSError: - title_font = body_font = ImageFont.load_default() + title_font = body_font = foot_font = ImageFont.load_default() # 标题:与底同系,仅略亮 + 极低 alpha tr, tg, tb = _BASE_RGB @@ -154,6 +202,15 @@ def render_keyword_page_subtle_rgba( y += 32 if y > height - 36: break + fl = (footer_line or "").strip() + if fl: + fa = int(footer_alpha) if footer_alpha is not None else _ACTION_ALPHA + ar, ag, ab = min(tr + 18, 255), min(tg + 20, 255), min(tb + 24, 255) + bbox = draw.textbbox((0, 0), fl, font=foot_font) + tw = bbox[2] - bbox[0] + fx = max(24, (width - tw) // 2) + fy = height - 46 + draw.text((fx, fy), fl, fill=(ar, ag, ab, fa), font=foot_font) img.save(str(png_path), "PNG") @@ -286,6 +343,21 @@ def main() -> None: nargs="*", help="可选:只处理这些文件名(相对于 --dir),不填则处理目录下全部 .mp4", ) + ap.add_argument( + "--ignore-state", + action="store_true", + help="忽略状态文件、不做「已含尾帧」跳过(仅当你已换回无尾帧母片时再开,否则会叠双尾帧)", + ) + ap.add_argument( + "--action-line", + default="点头像进房 · 每晚派对直播", + help="第二页底部轻引导(略亮于藏词正文);设为空字符串可关", + ) + ap.add_argument( + "--no-action-line", + action="store_true", + help="不绘制第二页底部引导(仍保留两页 SEO 藏词)", + ) args = ap.parse_args() words = load_keywords(args.keywords) font_try = args.font @@ -297,6 +369,9 @@ def main() -> None: break d = args.dir.resolve() + state_path = d / _STATE_NAME + state: dict = load_tail_state(state_path) + if args.files: mp4s = [] for f in args.files: @@ -311,10 +386,22 @@ def main() -> None: return dur_each = args.duration / max(1, args.pages) + page2_footer = "" + if not args.no_action_line: + page2_footer = (args.action_line or "").strip() for main in mp4s: idx = clip_index_from_name(main.name) try: + dur_before = probe_duration_sec(main) + if ( + not args.ignore_state + and main.name in state + and dur_before > 1.0 + and abs(float(state[main.name]) - dur_before) < 1.05 + ): + print(f"⏭ 已含尾帧(时长与状态一致,跳过防叠双尾): {main.name}") + continue w, h, sr = probe_size_rate(main) block_a, block_b = pick_two_blocks(words, idx, args.per_page) with tempfile.TemporaryDirectory(prefix="seo_tail_") as td: @@ -329,7 +416,13 @@ def main() -> None: w, h, block_a, png_a, font_try, "搜索关键词 1/2" ) render_keyword_page_subtle_rgba( - w, h, block_b, png_b, font_try, "搜索关键词 2/2" + w, + h, + block_b, + png_b, + font_try, + "搜索关键词 2/2", + footer_line=page2_footer or None, ) make_tail_clip_from_rgba_overlay( png_a, ta, dur_each, w, h, sr @@ -352,6 +445,9 @@ def main() -> None: tails.append(one) concat_videos_list(main, [main, *tails]) + new_d = probe_duration_sec(main) + state[main.name] = round(new_d, 2) + save_tail_state(state_path, state) nkw = len(block_a) + len(block_b) print( f"✅ 已加关键字尾帧×{args.pages}: {main.name}(序号 {idx},共 {nkw} 词," diff --git a/03_卡木(木)/木叶_视频内容/视频切片/脚本/soul_enhance.py b/03_卡木(木)/木叶_视频内容/视频切片/脚本/soul_enhance.py index fd244d08..d891ccf1 100644 --- a/03_卡木(木)/木叶_视频内容/视频切片/脚本/soul_enhance.py +++ b/03_卡木(木)/木叶_视频内容/视频切片/脚本/soul_enhance.py @@ -500,8 +500,10 @@ MIN_SILENCE_TRIM_TOTAL_SEC = 0.12 COVER_TITLE_MAX_CJK = 6 # 封面优先用 hook_3sec(吸睛高光句),可略长于纯标题字数上限 COVER_HOOK_MAX_CJK = 16 -CTA_END_MIN_SEC = 2.0 -CTA_END_MAX_SEC = 3.8 +CTA_END_MIN_SEC = 2.2 +CTA_END_MAX_SEC = 4.5 +# 最后一条口播字幕后,CTA 再停留一小段(多为静音),方便读完 + 过渡到无声 SEO 尾帧 +CTA_HOLD_AFTER_VOICE_SEC = 0.32 # ============ 工具函数 ============ @@ -1872,14 +1874,19 @@ def append_cta_ending_subtitle( if anchor_end < CTA_END_MIN_SEC + float(subtitle_overlay_start) * 0.5: return sub_images - span = min(CTA_END_MAX_SEC, max(CTA_END_MIN_SEC, anchor_end * 0.08)) + span = min(CTA_END_MAX_SEC, max(CTA_END_MIN_SEC, anchor_end * 0.11)) cta_start = max(float(subtitle_overlay_start), anchor_end - span) if cta_start >= anchor_end - 0.15: cta_start = max(0.0, anchor_end - CTA_END_MIN_SEC) + # 收口:CTA 结束略晚于最后一条语音字幕结束(不超过片长),便于「读完再断声」 + cta_end = min(anchor_end + float(CTA_HOLD_AFTER_VOICE_SEC), dur - 0.04) + if cta_end <= cta_start + 0.2: + cta_end = min(anchor_end, dur - 0.02) + img_path = os.path.join(temp_dir, "sub_cta_ending.png") create_subtitle_image(cta, out_w, out_h, img_path) - sub_images.append({"path": img_path, "start": cta_start, "end": anchor_end}) + sub_images.append({"path": img_path, "start": cta_start, "end": cta_end}) sub_images.sort(key=lambda x: (float(x["start"]), float(x["end"]))) return sub_images diff --git a/03_卡木(木)/木叶_视频内容/视频切片/脚本/soul_slice_pipeline.py b/03_卡木(木)/木叶_视频内容/视频切片/脚本/soul_slice_pipeline.py index 7e56ce93..317a5a14 100644 --- a/03_卡木(木)/木叶_视频内容/视频切片/脚本/soul_slice_pipeline.py +++ b/03_卡木(木)/木叶_视频内容/视频切片/脚本/soul_slice_pipeline.py @@ -4,7 +4,7 @@ Soul 切片一体化流水线 视频制作(封面/Hook格式)+ 视频切片 -流程:转录 → 字幕转简体 → 高光识别(AI) → 批量切片 → 增强(封面+字幕+CTA) → 快速混剪(可选) +流程:转录 → 字幕转简体 → 高光识别(AI) → 批量切片 → 增强(封面+字幕+CTA) → [two-folders 默认] SEO 两页尾帧 → 快速混剪(可选) """ import argparse import atexit @@ -179,6 +179,16 @@ def main(): action="store_true", help="传给 soul_enhance:去静音参数更温和", ) + parser.add_argument( + "--no-seo-tail", + action="store_true", + help="成片后不自动跑 append_seo_keyword_tail(默认 --two-folders 且出成片时会拼 SEO 两页)", + ) + parser.add_argument( + "--seo-tail-force", + action="store_true", + help="SEO 忽略 .soul_seo_tail_state.json(须已换回无尾帧母片,否则会叠双尾帧)", + ) args = parser.parse_args() if getattr(args, "ops_short", False): @@ -412,6 +422,30 @@ def main(): ] run(montage_cmd, "生成快速混剪", timeout=600, check=False) + # 5. 成片 SEO 尾帧(两页藏词 + 第二页轻引导):与 soul_enhance 分离,默认在 two-folders 成片后执行 + run_seo_tail = ( + use_two_folders + and not getattr(args, "slices_only", False) + and not getattr(args, "no_seo_tail", False) + and enhanced_count > 0 + ) + kw_file = SKILL_DIR / "参考资料" / "视频尾帧_SEO关键词200.txt" + if run_seo_tail and kw_file.is_file(): + seo_cmd = [ + sys.executable, + str(SCRIPT_DIR / "append_seo_keyword_tail.py"), + "--dir", + str(enhanced_dir), + "--keywords", + str(kw_file), + ] + if getattr(args, "seo_tail_force", False): + seo_cmd.append("--ignore-state") + seo_timeout = max(900, 180 * max(1, enhanced_count)) + run(seo_cmd, "成片末尾 SEO 尾帧(×2 静帧,无声)", timeout=seo_timeout, check=False) + elif run_seo_tail and not kw_file.is_file(): + print(f" ⚠ 跳过 SEO 尾帧:未找到词表 {kw_file}", flush=True) + print() print("=" * 60) print("✅ 流水线完成") diff --git a/03_卡木(木)/木根_逆向分析/全网AI自动注册/SKILL.md b/03_卡木(木)/木根_逆向分析/全网AI自动注册/SKILL.md index 17cd68cb..c0c944c2 100644 --- a/03_卡木(木)/木根_逆向分析/全网AI自动注册/SKILL.md +++ b/03_卡木(木)/木根_逆向分析/全网AI自动注册/SKILL.md @@ -68,6 +68,11 @@ python3 auto_register.py add-key -p cerebras -k csk-xxx # 手动添加 # ====== 网关健康检查 ====== python3 ../../运营中枢/scripts/karuo_ai_gateway/key_health_check.py + +# ====== Vercel / v0(mail.tm 多轮:取码失败换新邮箱)====== +python3 vercel_mailtm_signup_loop.py --max-rounds 8 --poll-timeout 180 +python3 vercel_mailtm_signup_loop.py --pause-enter --poll-timeout 240 # 先回车再轮询 +python3 vercel_mailtm_signup_loop.py --out-jsonl ./vercel_mailtm_rounds.jsonl ``` ### 自动化全链路 @@ -109,6 +114,13 @@ python3 ../../运营中枢/scripts/karuo_ai_gateway/key_health_check.py 6. 验证 Key 可用性 → 存入 key_pool.db 7. 网关下次请求自动读取新 Key +### Vercel / v0 邮箱注册(mail.tm 循环) + +1. **不要单邮箱死磕**:登录页试邮只会收到「Attempted Sign-in」+ signup 链接;**注册页** `Continue with Email` 才会收到 **Sign-up Verification** 六位码。 +2. **循环**:本轮 `poll-timeout` 内无注册验证邮件 → **放弃该 mail.tm** → 脚本 `vercel_mailtm_signup_loop.py` 自动生成下一邮箱 → 重复,直到取到码或达 `--max-rounds`。 +3. OTP 建议**逐格输入**;团队 URL 需全局唯一。 +4. 经验与命令见 `水溪_整理归档/经验库/待沉淀/2026-03-31_Vercel与v0_mail.tm注册_OTP与团队创建.md`。 + ### 新增平台支持 1. 在 `providers/` 创建 `平台名_provider.py`,继承 `BaseProvider` diff --git a/03_卡木(木)/木根_逆向分析/全网AI自动注册/脚本/vercel_mailtm_signup_loop.py b/03_卡木(木)/木根_逆向分析/全网AI自动注册/脚本/vercel_mailtm_signup_loop.py new file mode 100644 index 00000000..abb9ed28 --- /dev/null +++ b/03_卡木(木)/木根_逆向分析/全网AI自动注册/脚本/vercel_mailtm_signup_loop.py @@ -0,0 +1,227 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Vercel / v0 同账号:mail.tm 多轮注册循环。 + +策略:本轮在超时内拿不到「注册验证码邮件」→ 放弃该邮箱 → 自动再建一个新 mail.tm → 继续下一轮。 +不跟单封死磕;与 config 里 fixed mail.tm 无关(脚本内不读 MAILTM_* 固定账号)。 + +人工配合:每轮打印注册链接后,在浏览器打开并对该邮箱走「注册页 → Continue with Email」。 +""" + +from __future__ import annotations + +import argparse +import json +import os +import random +import re +import string +import sys +import time +import urllib.parse +from datetime import datetime, timezone +from pathlib import Path + +import httpx + +MAILTM_API = "https://api.mail.tm" +RE_OTP = re.compile(r"(? None: + os.environ.pop("MAILTM_ADDRESS", None) + os.environ.pop("MAILTM_PASSWORD", None) + + +def create_fresh_mailtm() -> tuple[str, str, str]: + """返回 (email, password, bearer_token)。""" + t = 30 + r = httpx.get(f"{MAILTM_API}/domains", timeout=t) + r.raise_for_status() + domains = r.json().get("hydra:member", []) + if not domains: + raise RuntimeError("mail.tm 无可用域名") + domain = domains[0]["domain"] + prefix = "".join(random.choices(string.ascii_lowercase + string.digits, k=14)) + email = f"{prefix}@{domain}" + password = "".join(random.choices(string.ascii_letters + string.digits, k=18)) + r = httpx.post( + f"{MAILTM_API}/accounts", + json={"address": email, "password": password}, + timeout=t, + ) + if r.status_code not in (200, 201): + raise RuntimeError(f"mail.tm 创建失败: {r.status_code} {r.text[:120]}") + tr = httpx.post( + f"{MAILTM_API}/token", + json={"address": email, "password": password}, + timeout=t, + ) + tr.raise_for_status() + token = tr.json().get("token") or "" + if not token: + raise RuntimeError("mail.tm token 为空") + return email, password, token + + +def poll_signup_verification_code( + token: str, + timeout_sec: int, + interval_sec: float, +) -> tuple[str | None, str]: + """ + 只认「注册验证」类邮件里的 6 位码。 + 返回 (code, reason);code 为 None 时 reason 为 TIMEOUT / NO_SIGNUP_MAIL。 + """ + headers = {"Authorization": f"Bearer {token}"} + deadline = time.time() + timeout_sec + seen_subjects: set[str] = set() + + while time.time() < deadline: + try: + r = httpx.get(f"{MAILTM_API}/messages", headers=headers, timeout=20) + r.raise_for_status() + for msg in r.json().get("hydra:member", []): + mid = msg.get("id") or "" + sub = (msg.get("subject") or "").strip() + if not mid or not sub: + continue + key = f"{mid}:{sub}" + if key in seen_subjects: + continue + seen_subjects.add(key) + + dr = httpx.get(f"{MAILTM_API}/messages/{mid}", headers=headers, timeout=20) + dr.raise_for_status() + data = dr.json() + text = data.get("text") or "" + html = data.get("html") + if isinstance(html, list) and html: + text = text + "\n" + str(html[0]) + elif isinstance(html, str): + text = text + "\n" + html + blob = sub + "\n" + text + sl = sub.lower() + + # 仅注册验证;登录试邮 / 无账号说明 不算成功取码 + if "attempted" in sl and "sign-in" in sl: + continue + if "sign-up verification" in sl or "signup verification" in sl: + m = RE_OTP.search(blob) + if m and m.group(1) != "666666": + return m.group(1), "OK" + # 主题即「123456 - …」且正文含 sign up / verify your email + if "vercel" in sl and ( + "sign up" in blob.lower() or "sign-up" in blob.lower() + ): + m = RE_OTP.search(sub) or RE_OTP.search(blob) + if m and m.group(1) != "666666": + return m.group(1), "OK" + except Exception as e: + print(f"[poll] 异常(将重试): {e}", file=sys.stderr) + time.sleep(interval_sec) + + return None, "TIMEOUT" + + +def main() -> int: + ap = argparse.ArgumentParser(description="Vercel 注册 mail.tm 多轮循环(取码失败换新邮箱)") + ap.add_argument("--max-rounds", type=int, default=8, help="最多换几轮邮箱") + ap.add_argument("--poll-timeout", type=int, default=180, help="每轮等待注册验证码秒数") + ap.add_argument("--interval", type=float, default=3.0, help="轮询间隔秒") + ap.add_argument( + "--pause-enter", + action="store_true", + help="打印链接后等待按回车再开始收信(方便你先点 Continue with Email)", + ) + ap.add_argument( + "--out-jsonl", + type=str, + default="", + help="每轮结果追加写入 JSONL(路径)", + ) + ap.add_argument( + "--ref", + type=str, + default="https://v0.app/ref/WQ9P9N", + help="邀请链接(仅打印参考)", + ) + args = ap.parse_args() + + out_path = Path(args.out_jsonl).expanduser() if args.out_jsonl else None + + print("=== Vercel / v0 · mail.tm 注册循环 ===", flush=True) + print("规则:本轮超时无「注册验证码」→ 放弃该邮箱 → 自动下一邮箱。\n", flush=True) + + for rnd in range(1, args.max_rounds + 1): + _clear_mailtm_env() + print(f"--- 第 {rnd}/{args.max_rounds} 轮 ---", flush=True) + try: + email, password, token = create_fresh_mailtm() + except Exception as e: + print(f"[失败] 无法创建 mail.tm: {e}", flush=True) + rec = { + "round": rnd, + "ok": False, + "reason": "MAILTM_CREATE_FAIL", + "error": str(e), + "ts": datetime.now(timezone.utc).isoformat(), + } + if out_path: + out_path.parent.mkdir(parents=True, exist_ok=True) + with open(out_path, "a", encoding="utf-8") as f: + f.write(json.dumps(rec, ensure_ascii=False) + "\n") + continue + + enc = urllib.parse.quote(email, safe="") + signup = f"https://vercel.com/signup?email={enc}" + print(f"邮箱: {email}", flush=True) + print(f"mail.tm 密码(查信用): {password}", flush=True) + print(f"注册页: {signup}", flush=True) + print(f"邀请参考: {args.ref}", flush=True) + print( + "请在浏览器打开「注册页」,对该邮箱点 Continue with Email 发码。\n", + flush=True, + ) + + if args.pause_enter: + input("准备好后按回车开始轮询收信…") + + code, reason = poll_signup_verification_code( + token, args.poll_timeout, args.interval + ) + rec = { + "round": rnd, + "email": email, + "mailtm_password": password, + "signup_url": signup, + "code": code, + "poll_reason": reason, + "ok": code is not None, + "ts": datetime.now(timezone.utc).isoformat(), + } + if out_path: + out_path.parent.mkdir(parents=True, exist_ok=True) + with open(out_path, "a", encoding="utf-8") as f: + f.write(json.dumps(rec, ensure_ascii=False) + "\n") + + if code: + print(f"\n[成功] 注册验证码: {code}", flush=True) + print( + "下一步:在 Vercel 注册页逐格输入 OTP,完成团队创建;再打开 v0 邀请链接。\n", + flush=True, + ) + return 0 + + print( + f"\n[放弃本轮] 未收到注册验证码({reason}),已丢弃该邮箱,进入下一轮。\n", + flush=True, + ) + + print("[结束] 已达最大轮数仍未取到注册验证码。", flush=True) + return 2 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/运营中枢/工作台/gitea_push_log.md b/运营中枢/工作台/gitea_push_log.md index c068086f..fb9d47de 100644 --- a/运营中枢/工作台/gitea_push_log.md +++ b/运营中枢/工作台/gitea_push_log.md @@ -500,3 +500,4 @@ | 2026-03-31 18:12:24 | 🔄 卡若AI 同步 2026-03-31 18:12 | 更新:金仓、卡木、运营中枢工作台 | 排除 >20MB: 14 个 | | 2026-03-31 18:15:25 | 🔄 卡若AI 同步 2026-03-31 18:15 | 更新:金仓、金盾、卡木、总索引与入口、运营中枢工作台 | 排除 >20MB: 14 个 | | 2026-03-31 19:04:30 | 🔄 卡若AI 同步 2026-03-31 19:04 | 更新:金仓、卡木、总索引与入口、运营中枢工作台 | 排除 >20MB: 14 个 | +| 2026-03-31 19:20:26 | 🔄 卡若AI 同步 2026-03-31 19:20 | 更新:Cursor规则、金仓、水溪整理归档、总索引与入口、运营中枢工作台 | 排除 >20MB: 14 个 | diff --git a/运营中枢/工作台/代码管理.md b/运营中枢/工作台/代码管理.md index 74d86567..32dee440 100644 --- a/运营中枢/工作台/代码管理.md +++ b/运营中枢/工作台/代码管理.md @@ -503,3 +503,4 @@ | 2026-03-31 18:12:24 | 成功 | 成功 | 🔄 卡若AI 同步 2026-03-31 18:12 | 更新:金仓、卡木、运营中枢工作台 | 排除 >20MB: 14 个 | [仓库](http://open.quwanzhi.com:13000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:13000/fnvtk/karuo-ai/wiki) | | 2026-03-31 18:15:25 | 成功 | 成功 | 🔄 卡若AI 同步 2026-03-31 18:15 | 更新:金仓、金盾、卡木、总索引与入口、运营中枢工作台 | 排除 >20MB: 14 个 | [仓库](http://open.quwanzhi.com:13000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:13000/fnvtk/karuo-ai/wiki) | | 2026-03-31 19:04:30 | 成功 | 成功 | 🔄 卡若AI 同步 2026-03-31 19:04 | 更新:金仓、卡木、总索引与入口、运营中枢工作台 | 排除 >20MB: 14 个 | [仓库](http://open.quwanzhi.com:13000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:13000/fnvtk/karuo-ai/wiki) | +| 2026-03-31 19:20:26 | 成功 | 成功 | 🔄 卡若AI 同步 2026-03-31 19:20 | 更新:Cursor规则、金仓、水溪整理归档、总索引与入口、运营中枢工作台 | 排除 >20MB: 14 个 | [仓库](http://open.quwanzhi.com:13000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:13000/fnvtk/karuo-ai/wiki) |