RITFIT Adcode 邮件扫描 → Base 记录 → 飞书通知
两种触发方式,共用同一套"提取 → 校验 → 产品匹配 → 去重 → 写入"逻辑,只是输入来源和输出方式不同:
- Mode 0 — 每日自动扫描邮件(cron 09:00 BJT,job "Daily adcode email scan"):自动查吴双飞书
邮箱过去24小时收到的邮件,逐封判断是否含 KOL adcode 信息,提取后走完整流程,最后把这次扫描
到的所有结果汇总成一条消息推送到她的飞书私信。这是默认/主要工作方式,不需要吴双手动粘
贴任何东西。
- Mode A — 手动模式:吴双随时把邮件原文或裸链接直接粘贴进对话,触发同样的提取流程,当场在
对话里回复结果(不走
mcp__claw__notify,因为她本人就在对话里)。用于临时补充处理 Mode 0 没
覆盖到的邮箱/邮件(比如她自己转发过来的一封邮件)。
Base 配置(已用 lark-cli base +field-list 验证,2026-07-17)
目标表:Content Log(子表单)
| 项目 |
值 |
| Base Token |
Dj0BbQHGKaiaFssqeWucIBIen8g |
| Table ID |
tblP5ZPXXzKXibYU |
| View |
vewAw5Jshe |
本技能常规写以下 7 个字段,其余一律不写、不改(2026-07-24 新增 Month/Deliverables/Posted?/Posting Date,吴双明确要求扫描adcode时顺手一起填):
| 字段名 |
字段 ID |
类型 |
说明 |
| Adcode |
fldabyan83 |
text |
必填 |
| Post URL |
fldqba9rTE |
text(url 样式) |
必填(有例外,见下方"缺 Post URL 的不完整记录也写入 Base"规则) |
| Product |
fldeNkyvNH |
link → tblYFQZrZUIHRtjR |
选填,匹配不到就留空 + 消息里标注 |
| Month |
fldvKrtDCj |
select(选项:Jan/Feb/Mar/Apr/May/Jun/Jul/Aug/Sep/Oct/Nov/Dec,英文三字母缩写) |
收到这条 adcode 对应邮件的日期所在的月份(不是扫描时间,是邮件本身的收件日期);Mode A 手动粘贴场景没有邮件收件时间,用吴双粘贴当天的日期。 |
| Deliverables |
fldXx9xrSL |
text |
按平台自动判断:Post URL 是 instagram.com/... → 填 1 reel;是 tiktok.com/.../vt.tiktok.com/... → 填 1 TK。缺 Post URL 时看正文/上下文里是否明确提到平台(如提到"IG"/"Instagram"/"Reel" 或 "TikTok"/"TK"),能判断就按同样规则填,判断不了就留空(消息里标注 ⚠️平台未判断,Deliverable留空)。 |
| Posted? |
fldwFfqCvJ |
checkbox |
只要这条记录写入 Base(不管完整记录还是仅 Adcode 的不完整记录),就勾选 true —— 收到 adcode 就说明内容已经发布了。 |
| Posting Date |
fldwNfFAKS |
datetime(格式 yyyy/MM/dd) |
收到这条 adcode 对应邮件的收件日期(跟 Month 用的是同一个日期来源);Mode A 场景用吴双粘贴当天的日期。 |
条件写入(仅限"Adcode 有、Post URL 缺"这一种不完整记录,2026-07-22 吴双明确要求新增):
| 字段名 |
字段 ID |
类型 |
说明 |
| KOL Name |
fldVYkDzgG |
link → tblBqCCxHRtFvS9E(ALL KOLs 表) |
默认不写。只有当一条记录因为缺 Post URL 而以"仅有 Adcode"的不完整形式写入 Base 时,才尝试识别网红身份并写入这个字段,方便吴双日后回去核对/补链接。识别不到就不写,消息里文字标注网红名字即可。识别规则见下方"KOL 身份识别"。 |
只读展示,不写入:
| 字段名 |
字段 ID |
类型 |
说明 |
| Campaign |
fldeRQOZ8E |
select(World Cup / Independence / Member / BFCM / Pink) |
本技能不写这个字段,但如果记录已有 Campaign 值(比如之前已存在的记录,或吴双/别的流程手动填过),推送消息时要把它带出来展示,见"输出"一节。 |
⚠️ 不要写的字段(表里还有,但和 adcode 提取无关,是别的流程用的):Pay Rate、Payment Date、Payment Status、KOL Contract、Contract Campaign Lookup、Payment done?、父记录 4。KOL Name 不在这份"绝对不碰"名单里 —— 它只在上面那一种特定场景(缺 Post URL 的不完整记录)才会被尝试写入,其余场景(完整记录、去重命中的已存在记录等)仍然完全不碰,继续留给别的流程处理。Month/Deliverables/Posted?/Posting Date 从 2026-07-24 起改为本技能常规写入字段(见上表),不再属于"不要写"名单。
产品匹配表:Product 表(link 目标)
| 项目 |
值 |
| Table ID |
tblYFQZrZUIHRtjR |
| 主字段(匹配用) |
Product Name(fldqX8WDeZ,text) |
| 辅助匹配字段 |
Chinese Name(flduWQ0bDl)、SKU(flddvgHEIT) |
KOL 匹配表:ALL KOLs 表(link 目标,仅缺 Post URL 场景使用)
| 项目 |
值 |
| Table ID |
tblBqCCxHRtFvS9E |
| 匹配用字段 |
KOL Name(fldq03deU6,text) |
File structure(本地状态/日志)
| 路径 |
用途 |
F:\Cherry Studio\Andy\Data\_outputs\ritfit-adcode-outputs\last-scan.md |
记录上次 Mode 0 扫描到的截止时间(ISO8601),下次运行的起点 |
F:\Cherry Studio\Andy\Data\_outputs\ritfit-adcode-outputs\daily-log-DD-MM-YYYY.md |
每次 Mode 0 运行都写一条:扫描窗口、扫了几封邮件、几封候选、最终几条完整/信息不全/已存在跳过/产品未匹配 |
Mode A(手动粘贴)不写这两个文件,只在对话里回复。
Mode 0 — 每日自动扫描邮件
邮箱身份
扫描时间窗口(状态文件,避免漏扫/重复扫)
- 读
last-scan.md 拿上次记录的 last_scan_end;文件不存在或读取失败,就用"当前时间 − 24小时"
当起点(兜底,保证覆盖"过去24小时"这个需求)。
scan_start = last_scan_end,scan_end = 本次运行开始时的当前时间。
- 不管这次扫描结果如何(有没有找到 adcode 邮件),运行结束时把
scan_end 写回 last-scan.md
作为下次起点 —— 这样即使运行时间有偏差,也不会漏扫或重复扫同一封邮件。
拉邮件
lark-cli mail +triage --filter '{"folder":"inbox","time_range":{"start_time":"<scan_start>","end_time":"<scan_end>"}}' --mailbox me --as user --max 100 --format json
如果返回 has_more=true,用返回的 page_token 续页(search:/list: 两种前缀不能混用),把这个
时间窗口内的邮件拉完。
不加 --query 关键词过滤 —— 直接拉这个时间窗口内 inbox 的全部邮件,交给下一步人工判断,避
免因为 KOL 措辞五花八门("adcode"/"ad code"/"add code"/西班牙语"código"等)而用关键词误漏。
逐封判断 + 读正文
对每封邮件:
- 用
lark-cli mail +message --message-id <id> --html=false --as user --format json 拿纯文本
正文(body_plain_text)。
- 判断这封邮件是否疑似"KOL发来的、包含帖子链接或adcode信息"的邮件——明显不相关的(内部通知、
账单、无关广告、纯感谢但完全没提到任何 code/链接的)直接跳过,不算候选,不进入下面的提取流程。
- 疑似相关的,进入下一节"提取"(和 Mode A 共用同一套逻辑)。
提取 / 校验 / 匹配 / 去重 / 写入(Mode 0 和 Mode A 共用)
Mode 0 里"一封邮件正文"和 Mode A 里"吴双粘贴的一段文本"走的是同一套逻辑,下面统一说"输入内容"。
第一步:提取
输入内容可能是:
- 一整段邮件原文(可能包含多个 KOL、多条链接、混着中英文/西语说明);
- 直接甩几条裸链接(IG/TikTok),可能附带 adcode 文字,也可能不附带。
对输入内容里能识别出的每一条 IG/TikTok 链接,尝试在同一段/同一行/紧邻上下文里找到对应的:
- Post URL:匹配
instagram.com/...、tiktok.com/...、vt.tiktok.com/... 等格式的完整链接。
- Adcode:通常紧跟在"adcode"、"ad code"、"add code"、"code"、"促销码"、"折扣码"、"优惠码"等
关键词后面的字母数字组合;也可能就是一段独立的大写/数字混合短字符串。没有明确标签时,按上下文
最贴近该链接的候选词处理。
- 产品名候选:文本里出现的产品词(如 "M2"、"Gazelle Pro"、"Gorilla"、"20lb dumbbells" 等),
不要求精确拼写,后面会做匹配。
一条链接 = 一条待处理记录。如果同一段输入里有多个链接,拆成多条,分别提取三个字段。如果输入里
完全没有链接,但有明显的 adcode 值(如"我发视频提到产品了,给你adcode: XXX"这种只发code没发链接
的情况),仍然按"缺 Post URL"的不完整记录处理,不要因为没有链接就整个跳过不提取。
第二步:必填字段校验
规则(2026-07-22 更新,缺 Post URL 的处理方式已改变):
- Adcode 缺(不管 Post URL 有没有)→ 这条记录不算候选,不写入 Base,列到"⚠️信息不全"分组,并注明
缺了什么、原始文本片段是什么(方便吴双回去核对)。
- Adcode 有、Post URL 也有 → 完整记录,进入第三步产品匹配,正常走完整流程写入。
- Adcode 有、Post URL 缺 → 不再算"信息不全跳过不写"。改为"仅 Adcode 不完整记录",同样写入
Base(Post URL 字段留空),额外走一次"KOL 身份识别"(见下方),写入后在推送消息里单独标注
⚠️缺 Post URL,提醒吴双回去看/要链接,不再进入"⚠️信息不全"分组(那个分组现在专指"缺 Adcode"
的记录)。
- 说明到这一步就停,不要额外提议"要不要帮你起草回复邮件问对方要链接"之类的下一步动作
(2026-07-17 吴双明确要求)。缺 Post URL 就是缺 Post URL,标注清楚即可,要不要联系 KOL 补链接
由吴双自己决定。
KOL 身份识别(仅适用于"Adcode 有、Post URL 缺"的不完整记录)
- 从这条记录的来源文本里找网红身份:优先用邮件发件人显示名(From 姓名),没有明显发件人信息时用
邮件签名/正文里提到的人名(如"Terrell and Jarius"这种落款)。
- 用这个名字去 ALL KOLs 表做模糊搜索:
lark-cli base +record-search --base-token Dj0BbQHGKaiaFssqeWucIBIen8g --table-id tblBqCCxHRtFvS9E --search-field "KOL Name" --keyword "<提取到的人名>" --as user
- 对返回结果做精确比对(trim 后完全相等才算命中,不能看到有结果就认为匹配上,参考 jdy-kol-sync /
ritfit-kol-register 同款去重规则)。
- 匹配成功 → 记下该记录的
record_id,写入时 KOL Name 字段用 link 格式:
{"id": "<record_id>"}。
- 匹配失败(没搜到 / 搜到多条无法判断)→
KOL Name 字段留空,不写入这个字段,推送消息里仍
用文字形式标注识别到的人名,并加 ⚠️网红未匹配 Base 记录。
- 如果连"识别到的人名"这一步都做不到(邮件里完全看不出是谁),消息里网红那一行显示
⚠️未能识别发件人,KOL Name 字段不写。
第三步:产品匹配
- 查询 Product 表:
lark-cli base +record-search --base-token Dj0BbQHGKaiaFssqeWucIBIen8g --table-id tblYFQZrZUIHRtjR --search-field "Product Name" --keyword "<提取到的产品名>" --as user
如果没搜到,再用 Chinese Name 试一次(有些产品吴双可能用中文提到)。
- 匹配规则:忽略大小写、忽略空格/连字符差异(如 "GazellePro" ≈ "Gazelle Pro" ≈ "Gazelle-Pro" 都
算匹配同一个)。只接受能明确对应到一条产品记录的匹配;如果搜出多条容易混淆的结果,当作未
匹配处理,别瞎猜。
- 匹配成功 → 记下该记录的
record_id,写入时用 link 格式:{"id": "<record_id>"}。
- 匹配失败(没搜到 / 搜到多条无法判断)→ Product 字段留空,不写入这个字段,消息里该条记录
标注
⚠️产品未匹配(原文:"<提取到的产品名候选>")。
第四步:去重检查
写入前,先按 Adcode 查一遍 Base,避免同一封邮件被处理两次导致重复记录(这一步对"完整记录"和"仅
Adcode 不完整记录"都适用,只要 Adcode 相同就按同一条处理):
lark-cli base +record-search --base-token Dj0BbQHGKaiaFssqeWucIBIen8g --table-id tblP5ZPXXzKXibYU --search-field "Adcode" --keyword "<Adcode>" --as user
如果搜到完全相同的 Adcode(且 Post URL 也一致,或者两边都是"仅 Adcode"没有 Post URL),跳过写入,
视为已存在,消息里标注 ↩️已存在,跳过。
⚠️ 已知限制(暂不自动处理,记录在案供后续决定要不要补):如果一条"仅 Adcode"的不完整记录之前已经
写入 Base,后来同一个 Adcode 又出现在新邮件里且这次带了 Post URL,目前逻辑只会判定"Adcode 已存
在,跳过",不会自动回填 Post URL 补全那条旧记录。如果吴双需要这种"自动补全"能力,需要另外明确
要求再加(类似 jdy-shipping-track 的 B2.5 实时补写逻辑)。
顺手把这条已存在记录当前的 Campaign(fldeRQOZ8E)字段值记下来 —— 如果 +record-search
返回结果里已经带了完整字段就直接读,没带全的话用 +record-get --record-id <id> --field-id Campaign --as user
单独查一次。留着给"输出"一节推送用:有值就展示,没值(比如还是空的)就不展示这一行。
新建的记录(没被去重跳过的)因为本技能不写 Campaign,所以肯定是空的,不用查,直接跳过展示。
第五步:写入 Base
对每条通过校验(且未被去重跳过)的记录:
先算好 Month / Deliverables / Posted? / Posting Date 这四个值(2026-07-24 新增):
- 收件日期:取这条记录来源邮件的收件日期(Mode 0 用
+triage/+message 返回结果里的时间字段,
不是 scan_end;Mode A 手动粘贴场景没有邮件收件时间,用吴双粘贴当下的日期)。
Posting Date = 收件日期,格式 yyyy/MM/dd。
Month = 收件日期对应的月份三字母缩写(Jan~`Dec`),对应 Base 里 Month 字段的现成选项。
Posted? = 固定 true(只要这条记录要写入 Base,不管完整还是仅 Adcode 不完整,都算已发布)。
Deliverables:
- Post URL 是
instagram.com/... → 1 reel
- Post URL 是
tiktok.com/... 或 vt.tiktok.com/... → 1 TK
- 缺 Post URL 时,看正文/上下文能不能明确判断平台(提到"IG"/"Instagram"/"Reel" 按 IG 算;提到
"TikTok"/"TK" 按 TikTok 算),能判断就按上面规则填,判断不了就留空 + 消息标注
⚠️平台未判断,Deliverable留空。
完整记录(Adcode + Post URL 都有):
lark-cli base +record-upsert --base-token Dj0BbQHGKaiaFssqeWucIBIen8g --table-id tblP5ZPXXzKXibYU \
--json '{"Adcode":"<adcode>","Post URL":"<url>","Product":[{"id":"<product_record_id>"}],"Month":"<Jan~Dec>","Deliverables":"<1 reel 或 1 TK>","Posted?":true,"Posting Date":"<yyyy/MM/dd>"}' --as user
产品未匹配时,--json 里不要带 Product 这个 key(留空,不要传空数组占位,避免覆盖成不确定的值)。
Deliverables 判断不了平台时,--json 里不要带这个 key。
仅 Adcode 不完整记录(缺 Post URL,2026-07-22 新增):
lark-cli base +record-upsert --base-token Dj0BbQHGKaiaFssqeWucIBIen8g --table-id tblP5ZPXXzKXibYU \
--json '{"Adcode":"<adcode>","KOL Name":[{"id":"<kol_record_id>"}],"Month":"<Jan~Dec>","Posted?":true,"Posting Date":"<yyyy/MM/dd>"}' --as user
不要带 Post URL 这个 key(留空,不要传空字符串占位)。KOL Name 只有识别成功时才带,识别失败就不带
这个 key(同样不要传空数组占位)。如果这条记录里恰好也提取到了产品名候选并匹配成功,也可以一并带上
Product(不冲突,可选)。Deliverables 同样能判断平台才带,判断不了就不带这个 key。
+record-upsert 不传 --record-id 就一定是新建,不会自动查重,所以第四步的去重检查不能省。
写入是逐条串行执行,出错就记录下来继续下一条,不要中断整批处理。
输出
消息内容格式统一(日期用 YYYY-MM-DD;Campaign 这一行只有在该记录已有 Campaign 值时才出现,
没有就整行不显示,不留占位符):
📌 [YYYY-MM-DD] Today's adcode
1. 链接: https://...
Adcode: XXXXX
产品: M2
2. 链接: https://...
Adcode: YYYYY
产品: ⚠️产品未匹配(原文:"GazelePro")
3. 链接: https://...
Adcode: ZZZZZ
产品: Gorilla
Campaign: Independence
↩️已存在,跳过(Base 中已有相同 Adcode)
4. Adcode: WWWWW
网红: Terrell and Jarius
⚠️缺 Post URL,已写入 Base(网红已标注),麻烦回邮件跟对方要一下链接
5. Adcode: VVVVV
网红: ⚠️未能识别发件人
⚠️缺 Post URL,已写入 Base,麻烦回邮件跟对方要一下链接
⚠️信息不全,未写入 Base:
- 原文片段:"...只有链接没有adcode..." → 缺 Adcode
规则:
- 完整且成功写入的记录按顺序编号列出,产品字段正常显示匹配到的
Product Name。
- 未匹配到产品的记录仍然写入(Adcode + Post URL),但产品那一行显示
⚠️产品未匹配(原文:"...")。
- 被去重跳过的记录也列出来,但加
↩️已存在,跳过 标注,方便吴双知道这条不是新写入的。
- Campaign:只有第四步查到该记录已有非空 Campaign 值时才加一行
Campaign: <值>(紧跟在产品
行后面);新建的记录本技能不写 Campaign,天生是空的,不显示这一行。
- 仅 Adcode 不完整记录(缺 Post URL,2026-07-22 新增):不再放进"⚠️信息不全"分组,改为按顺序
编号列在正常列表里,格式是"Adcode + 网红 + ⚠️缺 Post URL 提示",没有"链接"这一行(因为确实没有)。
网红那一行:识别成功显示 KOL 姓名;识别到人名但 Base 里没匹配上显示该人名 +
⚠️网红未匹配 Base 记录;连人名都识别不到显示 ⚠️未能识别发件人。
- "⚠️信息不全,未写入 Base"分组现在专指"缺 Adcode"的记录(不管 Post URL 有没有),这类记录依旧
不写入 Base,单独放在消息末尾,不占前面的编号。
Mode A(手动粘贴)的输出方式
直接在当前对话里用上面的格式回复,不走 mcp__claw__notify(吴双本人就在对话里,不需要额外推送)。
如果这批粘贴内容里一条有效链接/adcode都没提取到,直接回复"没有在这段内容里找到可识别的 IG/TikTok
链接或adcode,麻烦确认一下格式。"不写 Base。
Mode 0(每日自动扫描)的输出方式
- 把这次扫描窗口内所有候选邮件提取出的记录,汇总成一条消息,用
mcp__claw__notify 推送
(自动路由到吴双已连接的飞书私信,不需要手动查 open_id)。
- 如果这个时间窗口里一封候选邮件都没有(没有任何邮件看起来像 adcode 相关),不发送通知,
避免每天骚扰;但仍然照常更新
last-scan.md 并写本地日志。
- 标题里的日期用
scan_end 对应的日期。
- 无论是否发送通知,都要写
daily-log-DD-MM-YYYY.md:记录扫描窗口(scan_start ~ scan_end)、
扫了多少封邮件、多少封是候选、最终几条完整/信息不全/已存在跳过/产品未匹配,方便回溯"今天到底
扫了没、扫到了什么"。
Scheduling
| Job 名称 |
Schedule |
说明 |
| Daily adcode email scan |
0 9 * * *(每天,不分工作日/周末,09:00 BJT) |
运行 Mode 0 |
不要重复创建这个 cron job。如果吴双要求调整时间/频率,先用 mcp__claw__cron 的 remove 删掉旧的
再 add 新的(该工具没有 update 动作)。
边界 / 不做的事
KOL Name 只在"Adcode 有、Post URL 缺"这一种不完整记录场景下才尝试写入,其余场景(完整记录、
去重命中的已存在记录)一律不碰、不建、不改 KOL 关联(那是别的流程的事)。
- 不写 Pay Rate / Payment Date / Payment Status / KOL Contract / Payment done? 这些财务与合同字段。
Month/Deliverables/Posted?/Posting Date 从 2026-07-24 起改为常规写入字段(见"写入 Base"一节),不再属于不写清单。
- Campaign 只读不写:本技能从不设置这个字段,但推送消息时如果记录已有 Campaign 值就带出来展示。
- 不新建 Product 表或 ALL KOLs 表里的记录 —— 匹配不到就是匹配不到,标注给人看,不自动新建产品/
网红条目(避免库被写脏)。
- Mode 0 不做关键词粗筛(不加
--query),直接拉时间窗口内全部邮件,人工判断哪些是候选,避免因
为措辞差异漏掉真正的 adcode 邮件。
- Mode 0 发现"缺 Adcode"的信息不全、或产品/网红未匹配时,只标注,不主动提议起草回复邮件或联系
KOL。
- 已写入 Base 的"仅 Adcode"不完整记录,如果后续同一 Adcode 又出现带 Post URL 的新邮件,目前不会
自动回填补全旧记录(见第四步"已知限制"),需要吴双另外提出才加这个能力。
1---2name: ritfit-adcode3description: 每天早上9点自动扫描吴双(Christina)飞书邮箱过去24小时收到的邮件,从中提取 KOL adcode 信息 (Post URL + Adcode + 产品),匹配到 Feishu Base 产品库,把完整记录连同 Month/Deliverables/ Posted?/Posting Date 一起写入 Content Log 子表单,并把格式化清单推送到吴双的飞书私信。也支持 吴双随时手动粘贴邮件原文/原始 IG/TikTok 链接触发同样的提取流程,当场在对话里回复。触发词: "adcode"、"今天的adcode"、"扫一下adcode邮件"、"帮我记录这些adcode"、"这是今天的红人链接"、 "extract adcode"、"处理一下这批adcode",以及 cron job "Daily adcode email scan"。4---56# RITFIT Adcode 邮件扫描 → Base 记录 → 飞书通知78两种触发方式,共用同一套"提取 → 校验 → 产品匹配 → 去重 → 写入"逻辑,只是输入来源和输出方式不同:9101. **Mode 0 — 每日自动扫描邮件**(cron 09:00 BJT,job "Daily adcode email scan"):自动查吴双飞书11 邮箱过去24小时收到的邮件,逐封判断是否含 KOL adcode 信息,提取后走完整流程,最后把这次扫描12 到的所有结果汇总成**一条**消息推送到她的飞书私信。**这是默认/主要工作方式,不需要吴双手动粘13 贴任何东西。**142. **Mode A — 手动模式**:吴双随时把邮件原文或裸链接直接粘贴进对话,触发同样的提取流程,当场在15 对话里回复结果(不走 `mcp__claw__notify`,因为她本人就在对话里)。用于临时补充处理 Mode 0 没16 覆盖到的邮箱/邮件(比如她自己转发过来的一封邮件)。1718---1920## Base 配置(已用 `lark-cli base +field-list` 验证,2026-07-17)2122### 目标表:Content Log(子表单)23| 项目 | 值 |24|------|-----|25| Base Token | `Dj0BbQHGKaiaFssqeWucIBIen8g` |26| Table ID | `tblP5ZPXXzKXibYU` |27| View | `vewAw5Jshe` |2829**本技能常规写以下 7 个字段,其余一律不写、不改**(2026-07-24 新增 Month/Deliverables/Posted?/Posting Date,吴双明确要求扫描adcode时顺手一起填):3031| 字段名 | 字段 ID | 类型 | 说明 |32|--------|---------|------|------|33| Adcode | `fldabyan83` | text | 必填 |34| Post URL | `fldqba9rTE` | text(url 样式) | 必填(有例外,见下方"缺 Post URL 的不完整记录也写入 Base"规则) |35| Product | `fldeNkyvNH` | **link** → `tblYFQZrZUIHRtjR` | 选填,匹配不到就留空 + 消息里标注 |36| Month | `fldvKrtDCj` | select(选项:`Jan`/`Feb`/`Mar`/`Apr`/`May`/`Jun`/`Jul`/`Aug`/`Sep`/`Oct`/`Nov`/`Dec`,英文三字母缩写) | **收到这条 adcode 对应邮件的日期**所在的月份(不是扫描时间,是邮件本身的收件日期);Mode A 手动粘贴场景没有邮件收件时间,用吴双粘贴当天的日期。 |37| Deliverables | `fldXx9xrSL` | text | 按平台自动判断:Post URL 是 `instagram.com/...` → 填 `1 reel`;是 `tiktok.com/...`/`vt.tiktok.com/...` → 填 `1 TK`。缺 Post URL 时看正文/上下文里是否明确提到平台(如提到"IG"/"Instagram"/"Reel" 或 "TikTok"/"TK"),能判断就按同样规则填,判断不了就留空(消息里标注 `⚠️平台未判断,Deliverable留空`)。 |38| Posted? | `fldwFfqCvJ` | checkbox | 只要这条记录写入 Base(不管完整记录还是仅 Adcode 的不完整记录),就勾选 `true` —— 收到 adcode 就说明内容已经发布了。 |39| Posting Date | `fldwNfFAKS` | datetime(格式 `yyyy/MM/dd`) | 收到这条 adcode 对应邮件的收件日期(跟 Month 用的是同一个日期来源);Mode A 场景用吴双粘贴当天的日期。 |4041**条件写入(仅限"Adcode 有、Post URL 缺"这一种不完整记录,2026-07-22 吴双明确要求新增)**:4243| 字段名 | 字段 ID | 类型 | 说明 |44|--------|---------|------|------|45| KOL Name | `fldVYkDzgG` | **link** → `tblBqCCxHRtFvS9E`(ALL KOLs 表) | 默认不写。只有当一条记录因为缺 Post URL 而以"仅有 Adcode"的不完整形式写入 Base 时,才尝试识别网红身份并写入这个字段,方便吴双日后回去核对/补链接。识别不到就不写,消息里文字标注网红名字即可。识别规则见下方"KOL 身份识别"。 |4647**只读展示,不写入**:4849| 字段名 | 字段 ID | 类型 | 说明 |50|--------|---------|------|------|51| Campaign | `fldeRQOZ8E` | select(World Cup / Independence / Member / BFCM / Pink) | 本技能不写这个字段,但如果记录已有 Campaign 值(比如之前已存在的记录,或吴双/别的流程手动填过),推送消息时要把它带出来展示,见"输出"一节。 |5253⚠️ 不要写的字段(表里还有,但和 adcode 提取无关,是别的流程用的):`Pay Rate`、`Payment Date`、`Payment Status`、`KOL Contract`、`Contract Campaign Lookup`、`Payment done?`、`父记录 4`。**`KOL Name` 不在这份"绝对不碰"名单里** —— 它只在上面那一种特定场景(缺 Post URL 的不完整记录)才会被尝试写入,其余场景(完整记录、去重命中的已存在记录等)仍然完全不碰,继续留给别的流程处理。`Month`/`Deliverables`/`Posted?`/`Posting Date` 从 2026-07-24 起改为本技能常规写入字段(见上表),不再属于"不要写"名单。5455### 产品匹配表:Product 表(link 目标)56| 项目 | 值 |57|------|-----|58| Table ID | `tblYFQZrZUIHRtjR` |59| 主字段(匹配用) | `Product Name`(`fldqX8WDeZ`,text) |60| 辅助匹配字段 | `Chinese Name`(`flduWQ0bDl`)、`SKU`(`flddvgHEIT`) |6162### KOL 匹配表:ALL KOLs 表(link 目标,仅缺 Post URL 场景使用)63| 项目 | 值 |64|------|-----|65| Table ID | `tblBqCCxHRtFvS9E` |66| 匹配用字段 | `KOL Name`(`fldq03deU6`,text) |6768---6970## File structure(本地状态/日志)7172| 路径 | 用途 |73|------|------|74| `F:\Cherry Studio\Andy\Data\_outputs\ritfit-adcode-outputs\last-scan.md` | 记录上次 Mode 0 扫描到的截止时间(ISO8601),下次运行的起点 |75| `F:\Cherry Studio\Andy\Data\_outputs\ritfit-adcode-outputs\daily-log-DD-MM-YYYY.md` | 每次 Mode 0 运行都写一条:扫描窗口、扫了几封邮件、几封候选、最终几条完整/信息不全/已存在跳过/产品未匹配 |7677Mode A(手动粘贴)不写这两个文件,只在对话里回复。7879---8081## Mode 0 — 每日自动扫描邮件8283### 邮箱身份84- 用 `lark-cli mail` 系列命令,`--as user --mailbox me`,即吴双本人登录飞书邮箱账号本身,不需要85 额外指定邮箱地址。86- 第一次跑之前(或怀疑身份不对时)用以下命令确认 `me` 确实是吴双本人:87 ```88 lark-cli mail user_mailboxes profile --params '{"user_mailbox_id":"me"}' --as user89 ```9091### 扫描时间窗口(状态文件,避免漏扫/重复扫)921. 读 `last-scan.md` 拿上次记录的 `last_scan_end`;文件不存在或读取失败,就用"当前时间 − 24小时"93 当起点(兜底,保证覆盖"过去24小时"这个需求)。942. `scan_start = last_scan_end`,`scan_end = 本次运行开始时的当前时间`。953. 不管这次扫描结果如何(有没有找到 adcode 邮件),运行结束时把 `scan_end` 写回 `last-scan.md`96 作为下次起点 —— 这样即使运行时间有偏差,也不会漏扫或重复扫同一封邮件。9798### 拉邮件99```100lark-cli mail +triage --filter '{"folder":"inbox","time_range":{"start_time":"<scan_start>","end_time":"<scan_end>"}}' --mailbox me --as user --max 100 --format json101```102如果返回 `has_more=true`,用返回的 `page_token` 续页(`search:`/`list:` 两种前缀不能混用),把这个103时间窗口内的邮件拉完。104105**不加 `--query` 关键词过滤** —— 直接拉这个时间窗口内 inbox 的全部邮件,交给下一步人工判断,避106免因为 KOL 措辞五花八门("adcode"/"ad code"/"add code"/西班牙语"código"等)而用关键词误漏。107108### 逐封判断 + 读正文109对每封邮件:1101. 用 `lark-cli mail +message --message-id <id> --html=false --as user --format json` 拿纯文本111 正文(`body_plain_text`)。1122. 判断这封邮件是否疑似"KOL发来的、包含帖子链接或adcode信息"的邮件——明显不相关的(内部通知、113 账单、无关广告、纯感谢但完全没提到任何 code/链接的)直接跳过,不算候选,不进入下面的提取流程。1143. 疑似相关的,进入下一节"提取"(和 Mode A 共用同一套逻辑)。115116---117118## 提取 / 校验 / 匹配 / 去重 / 写入(Mode 0 和 Mode A 共用)119120Mode 0 里"一封邮件正文"和 Mode A 里"吴双粘贴的一段文本"走的是同一套逻辑,下面统一说"输入内容"。121122### 第一步:提取123124输入内容可能是:125- 一整段邮件原文(可能包含多个 KOL、多条链接、混着中英文/西语说明);126- 直接甩几条裸链接(IG/TikTok),可能附带 adcode 文字,也可能不附带。127128对输入内容里能识别出的**每一条 IG/TikTok 链接**,尝试在同一段/同一行/紧邻上下文里找到对应的:129- **Post URL**:匹配 `instagram.com/...`、`tiktok.com/...`、`vt.tiktok.com/...` 等格式的完整链接。130- **Adcode**:通常紧跟在"adcode"、"ad code"、"add code"、"code"、"促销码"、"折扣码"、"优惠码"等131 关键词后面的字母数字组合;也可能就是一段独立的大写/数字混合短字符串。没有明确标签时,按上下文132 最贴近该链接的候选词处理。133- **产品名候选**:文本里出现的产品词(如 "M2"、"Gazelle Pro"、"Gorilla"、"20lb dumbbells" 等),134 不要求精确拼写,后面会做匹配。135136一条链接 = 一条待处理记录。如果同一段输入里有多个链接,拆成多条,分别提取三个字段。如果输入里137完全没有链接,但有明显的 adcode 值(如"我发视频提到产品了,给你adcode: XXX"这种只发code没发链接138的情况),仍然按"缺 Post URL"的不完整记录处理,不要因为没有链接就整个跳过不提取。139140### 第二步:必填字段校验141142**规则(2026-07-22 更新,缺 Post URL 的处理方式已改变)**:143144- **Adcode 缺**(不管 Post URL 有没有)→ 这条记录不算候选,不写入 Base,列到"⚠️信息不全"分组,并注明145 缺了什么、原始文本片段是什么(方便吴双回去核对)。146- **Adcode 有、Post URL 也有** → 完整记录,进入第三步产品匹配,正常走完整流程写入。147- **Adcode 有、Post URL 缺** → 不再算"信息不全跳过不写"。改为"仅 Adcode 不完整记录",**同样写入148 Base**(Post URL 字段留空),额外走一次"KOL 身份识别"(见下方),写入后在推送消息里单独标注149 `⚠️缺 Post URL`,提醒吴双回去看/要链接,不再进入"⚠️信息不全"分组(那个分组现在专指"缺 Adcode"150 的记录)。151- **说明到这一步就停,不要额外提议"要不要帮你起草回复邮件问对方要链接"之类的下一步动作**152 (2026-07-17 吴双明确要求)。缺 Post URL 就是缺 Post URL,标注清楚即可,要不要联系 KOL 补链接153 由吴双自己决定。154155### KOL 身份识别(仅适用于"Adcode 有、Post URL 缺"的不完整记录)1561571. 从这条记录的来源文本里找网红身份:优先用邮件发件人显示名(From 姓名),没有明显发件人信息时用158 邮件签名/正文里提到的人名(如"Terrell and Jarius"这种落款)。1592. 用这个名字去 ALL KOLs 表做模糊搜索:160 ```161 lark-cli base +record-search --base-token Dj0BbQHGKaiaFssqeWucIBIen8g --table-id tblBqCCxHRtFvS9E --search-field "KOL Name" --keyword "<提取到的人名>" --as user162 ```1633. 对返回结果做精确比对(trim 后完全相等才算命中,不能看到有结果就认为匹配上,参考 jdy-kol-sync /164 ritfit-kol-register 同款去重规则)。165 - **匹配成功** → 记下该记录的 `record_id`,写入时 `KOL Name` 字段用 link 格式:166 `{"id": "<record_id>"}`。167 - **匹配失败**(没搜到 / 搜到多条无法判断)→ `KOL Name` 字段留空,不写入这个字段,推送消息里仍168 用文字形式标注识别到的人名,并加 `⚠️网红未匹配 Base 记录`。1694. 如果连"识别到的人名"这一步都做不到(邮件里完全看不出是谁),消息里网红那一行显示170 `⚠️未能识别发件人`,`KOL Name` 字段不写。171172### 第三步:产品匹配1731741. 查询 Product 表:175 ```176 lark-cli base +record-search --base-token Dj0BbQHGKaiaFssqeWucIBIen8g --table-id tblYFQZrZUIHRtjR --search-field "Product Name" --keyword "<提取到的产品名>" --as user177 ```178 如果没搜到,再用 `Chinese Name` 试一次(有些产品吴双可能用中文提到)。1792. 匹配规则:忽略大小写、忽略空格/连字符差异(如 "GazellePro" ≈ "Gazelle Pro" ≈ "Gazelle-Pro" 都180 算匹配同一个)。只接受能明确对应到**一条**产品记录的匹配;如果搜出多条容易混淆的结果,当作未181 匹配处理,别瞎猜。1823. **匹配成功** → 记下该记录的 `record_id`,写入时用 link 格式:`{"id": "<record_id>"}`。1834. **匹配失败**(没搜到 / 搜到多条无法判断)→ Product 字段留空,不写入这个字段,消息里该条记录184 标注 `⚠️产品未匹配(原文:"<提取到的产品名候选>")`。185186### 第四步:去重检查187188写入前,先按 Adcode 查一遍 Base,避免同一封邮件被处理两次导致重复记录(这一步对"完整记录"和"仅189Adcode 不完整记录"都适用,只要 Adcode 相同就按同一条处理):190```191lark-cli base +record-search --base-token Dj0BbQHGKaiaFssqeWucIBIen8g --table-id tblP5ZPXXzKXibYU --search-field "Adcode" --keyword "<Adcode>" --as user192```193如果搜到完全相同的 Adcode(且 Post URL 也一致,或者两边都是"仅 Adcode"没有 Post URL),跳过写入,194视为已存在,消息里标注 `↩️已存在,跳过`。195196⚠️ 已知限制(暂不自动处理,记录在案供后续决定要不要补):如果一条"仅 Adcode"的不完整记录之前已经197写入 Base,后来同一个 Adcode 又出现在新邮件里且这次带了 Post URL,目前逻辑只会判定"Adcode 已存198在,跳过",不会自动回填 Post URL 补全那条旧记录。如果吴双需要这种"自动补全"能力,需要另外明确199要求再加(类似 jdy-shipping-track 的 B2.5 实时补写逻辑)。200201顺手把这条已存在记录当前的 **Campaign**(`fldeRQOZ8E`)字段值记下来 —— 如果 `+record-search`202返回结果里已经带了完整字段就直接读,没带全的话用 `+record-get --record-id <id> --field-id Campaign --as user`203单独查一次。留着给"输出"一节推送用:有值就展示,没值(比如还是空的)就不展示这一行。204205新建的记录(没被去重跳过的)因为本技能不写 Campaign,所以肯定是空的,不用查,直接跳过展示。206207### 第五步:写入 Base208209对每条通过校验(且未被去重跳过)的记录:210211**先算好 Month / Deliverables / Posted? / Posting Date 这四个值(2026-07-24 新增)**:212- **收件日期**:取这条记录来源邮件的收件日期(Mode 0 用 `+triage`/`+message` 返回结果里的时间字段,213 不是 `scan_end`;Mode A 手动粘贴场景没有邮件收件时间,用吴双粘贴当下的日期)。214- `Posting Date` = 收件日期,格式 `yyyy/MM/dd`。215- `Month` = 收件日期对应的月份三字母缩写(`Jan`~`Dec`),对应 Base 里 Month 字段的现成选项。216- `Posted?` = 固定 `true`(只要这条记录要写入 Base,不管完整还是仅 Adcode 不完整,都算已发布)。217- `Deliverables`:218 - Post URL 是 `instagram.com/...` → `1 reel`219 - Post URL 是 `tiktok.com/...` 或 `vt.tiktok.com/...` → `1 TK`220 - 缺 Post URL 时,看正文/上下文能不能明确判断平台(提到"IG"/"Instagram"/"Reel" 按 IG 算;提到221 "TikTok"/"TK" 按 TikTok 算),能判断就按上面规则填,判断不了就留空 + 消息标注222 `⚠️平台未判断,Deliverable留空`。223224**完整记录(Adcode + Post URL 都有)**:225```226lark-cli base +record-upsert --base-token Dj0BbQHGKaiaFssqeWucIBIen8g --table-id tblP5ZPXXzKXibYU \227 --json '{"Adcode":"<adcode>","Post URL":"<url>","Product":[{"id":"<product_record_id>"}],"Month":"<Jan~Dec>","Deliverables":"<1 reel 或 1 TK>","Posted?":true,"Posting Date":"<yyyy/MM/dd>"}' --as user228```229产品未匹配时,`--json` 里不要带 `Product` 这个 key(留空,不要传空数组占位,避免覆盖成不确定的值)。230`Deliverables` 判断不了平台时,`--json` 里不要带这个 key。231232**仅 Adcode 不完整记录(缺 Post URL,2026-07-22 新增)**:233```234lark-cli base +record-upsert --base-token Dj0BbQHGKaiaFssqeWucIBIen8g --table-id tblP5ZPXXzKXibYU \235 --json '{"Adcode":"<adcode>","KOL Name":[{"id":"<kol_record_id>"}],"Month":"<Jan~Dec>","Posted?":true,"Posting Date":"<yyyy/MM/dd>"}' --as user236```237不要带 `Post URL` 这个 key(留空,不要传空字符串占位)。`KOL Name` 只有识别成功时才带,识别失败就不带238这个 key(同样不要传空数组占位)。如果这条记录里恰好也提取到了产品名候选并匹配成功,也可以一并带上239`Product`(不冲突,可选)。`Deliverables` 同样能判断平台才带,判断不了就不带这个 key。240241`+record-upsert` 不传 `--record-id` 就一定是新建,不会自动查重,所以第四步的去重检查不能省。242243写入是逐条串行执行,出错就记录下来继续下一条,不要中断整批处理。244245---246247## 输出248249消息内容格式统一(日期用 `YYYY-MM-DD`;**Campaign 这一行只有在该记录已有 Campaign 值时才出现,250没有就整行不显示,不留占位符**):251252```253📌 [YYYY-MM-DD] Today's adcode2542551. 链接: https://...256 Adcode: XXXXX257 产品: M22582592. 链接: https://...260 Adcode: YYYYY261 产品: ⚠️产品未匹配(原文:"GazelePro")2622633. 链接: https://...264 Adcode: ZZZZZ265 产品: Gorilla266 Campaign: Independence267 ↩️已存在,跳过(Base 中已有相同 Adcode)2682694. Adcode: WWWWW270 网红: Terrell and Jarius271 ⚠️缺 Post URL,已写入 Base(网红已标注),麻烦回邮件跟对方要一下链接2722735. Adcode: VVVVV274 网红: ⚠️未能识别发件人275 ⚠️缺 Post URL,已写入 Base,麻烦回邮件跟对方要一下链接276277⚠️信息不全,未写入 Base:278- 原文片段:"...只有链接没有adcode..." → 缺 Adcode279```280281规则:282- 完整且成功写入的记录按顺序编号列出,产品字段正常显示匹配到的 `Product Name`。283- 未匹配到产品的记录仍然写入(Adcode + Post URL),但产品那一行显示 `⚠️产品未匹配(原文:"...")`。284- 被去重跳过的记录也列出来,但加 `↩️已存在,跳过` 标注,方便吴双知道这条不是新写入的。285- **Campaign**:只有第四步查到该记录已有非空 Campaign 值时才加一行 `Campaign: <值>`(紧跟在产品286 行后面);新建的记录本技能不写 Campaign,天生是空的,不显示这一行。287- **仅 Adcode 不完整记录(缺 Post URL,2026-07-22 新增)**:不再放进"⚠️信息不全"分组,改为按顺序288 编号列在正常列表里,格式是"Adcode + 网红 + ⚠️缺 Post URL 提示",没有"链接"这一行(因为确实没有)。289 网红那一行:识别成功显示 KOL 姓名;识别到人名但 Base 里没匹配上显示该人名 + `⚠️网红未匹配 Base290 记录`;连人名都识别不到显示 `⚠️未能识别发件人`。291- "⚠️信息不全,未写入 Base"分组现在**专指"缺 Adcode"的记录**(不管 Post URL 有没有),这类记录依旧292 不写入 Base,单独放在消息末尾,不占前面的编号。293294### Mode A(手动粘贴)的输出方式295直接在当前对话里用上面的格式回复,不走 `mcp__claw__notify`(吴双本人就在对话里,不需要额外推送)。296如果这批粘贴内容里一条有效链接/adcode都没提取到,直接回复"没有在这段内容里找到可识别的 IG/TikTok297链接或adcode,麻烦确认一下格式。"不写 Base。298299### Mode 0(每日自动扫描)的输出方式300- 把这次扫描窗口内**所有候选邮件**提取出的记录,汇总成**一条**消息,用 `mcp__claw__notify` 推送301 (自动路由到吴双已连接的飞书私信,不需要手动查 open_id)。302- 如果这个时间窗口里**一封候选邮件都没有**(没有任何邮件看起来像 adcode 相关),**不发送通知**,303 避免每天骚扰;但仍然照常更新 `last-scan.md` 并写本地日志。304- 标题里的日期用 `scan_end` 对应的日期。305- 无论是否发送通知,都要写 `daily-log-DD-MM-YYYY.md`:记录扫描窗口(`scan_start` ~ `scan_end`)、306 扫了多少封邮件、多少封是候选、最终几条完整/信息不全/已存在跳过/产品未匹配,方便回溯"今天到底307 扫了没、扫到了什么"。308309---310311## Scheduling312313| Job 名称 | Schedule | 说明 |314|---------|----------|------|315| Daily adcode email scan | `0 9 * * *`(每天,不分工作日/周末,09:00 BJT) | 运行 Mode 0 |316317不要重复创建这个 cron job。如果吴双要求调整时间/频率,先用 `mcp__claw__cron` 的 `remove` 删掉旧的318再 `add` 新的(该工具没有 update 动作)。319320---321322## 边界 / 不做的事323324- `KOL Name` 只在"Adcode 有、Post URL 缺"这一种不完整记录场景下才尝试写入,其余场景(完整记录、325 去重命中的已存在记录)一律不碰、不建、不改 KOL 关联(那是别的流程的事)。326- 不写 Pay Rate / Payment Date / Payment Status / KOL Contract / Payment done? 这些财务与合同字段。`Month`/`Deliverables`/`Posted?`/`Posting Date` 从 2026-07-24 起改为常规写入字段(见"写入 Base"一节),不再属于不写清单。327- Campaign 只读不写:本技能从不设置这个字段,但推送消息时如果记录已有 Campaign 值就带出来展示。328- 不新建 Product 表或 ALL KOLs 表里的记录 —— 匹配不到就是匹配不到,标注给人看,不自动新建产品/329 网红条目(避免库被写脏)。330- Mode 0 不做关键词粗筛(不加 `--query`),直接拉时间窗口内全部邮件,人工判断哪些是候选,避免因331 为措辞差异漏掉真正的 adcode 邮件。332- Mode 0 发现"缺 Adcode"的信息不全、或产品/网红未匹配时,只标注,不主动提议起草回复邮件或联系333 KOL。334- 已写入 Base 的"仅 Adcode"不完整记录,如果后续同一 Adcode 又出现带 Post URL 的新邮件,目前**不会335 自动回填**补全旧记录(见第四步"已知限制"),需要吴双另外提出才加这个能力。