From 8572c8c5e0cf79fdacdea4e87ff58c6c997ffc2c Mon Sep 17 00:00:00 2001 From: Angiin Date: Fri, 27 Feb 2026 15:52:44 +0800 Subject: [PATCH] feat: add long article publish mode to post-to-xhs skill MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add "写长文" workflow with template selection support: - cdp_publish.py: new commands (long-article, select-template, click-next-step) - publish_pipeline.py: add --mode parameter (image-text / long-article) - SKILL.md: document long article flow (B.1-B.5 steps) - publish-workflow.md: add long article selectors, CLI usage, detailed steps --- skills/post-to-xhs/SKILL.md | 66 +++- .../references/publish-workflow.md | 107 ++++- .../account_manager.cpython-312.pyc | Bin 12940 -> 0 bytes .../__pycache__/cdp_publish.cpython-312.pyc | Bin 29625 -> 0 bytes .../chrome_launcher.cpython-312.pyc | Bin 13052 -> 0 bytes .../image_downloader.cpython-312.pyc | Bin 7120 -> 0 bytes skills/post-to-xhs/scripts/cdp_publish.py | 374 +++++++++++++++++- .../post-to-xhs/scripts/publish_pipeline.py | 39 +- 8 files changed, 562 insertions(+), 24 deletions(-) delete mode 100644 skills/post-to-xhs/scripts/__pycache__/account_manager.cpython-312.pyc delete mode 100644 skills/post-to-xhs/scripts/__pycache__/cdp_publish.cpython-312.pyc delete mode 100644 skills/post-to-xhs/scripts/__pycache__/chrome_launcher.cpython-312.pyc delete mode 100644 skills/post-to-xhs/scripts/__pycache__/image_downloader.cpython-312.pyc diff --git a/skills/post-to-xhs/SKILL.md b/skills/post-to-xhs/SKILL.md index 61a1d9d..f9f4d97 100644 --- a/skills/post-to-xhs/SKILL.md +++ b/skills/post-to-xhs/SKILL.md @@ -1,25 +1,30 @@ --- name: post-to-xhs description: > - 小红书内容发布技能。支持两种输入方式:(1) 用户提供完整内容和图片/图片URL,直接发布; - (2) 用户提供网页URL,自动提取内容和图片,适当总结后发布。如果从URL提取不到图片, - 提示用户手动下载并提供。适用于任何类型的内容发布。 + 小红书内容发布技能。支持两种发布模式:(1) 上传图文模式 - 图片+短文;(2) 写长文模式 - 长篇文章+排版模板。 + 支持两种输入方式:用户提供完整内容和图片/图片URL,直接发布;或提供网页URL,自动提取内容和图片。 + 用户说"发长文"时使用长文模式,否则默认图文模式。 --- # 小红书内容发布 -根据用户输入自动判断发布方式,简化发布流程。 +根据用户输入自动判断发布方式和发布模式,简化发布流程。 + +## 发布模式 + +- **上传图文**(默认):图片 + 短文,适合日常分享 +- **写长文**:长篇文章 + 排版模板选择,适合深度内容。用户明确说"发长文"时使用 ## 工作流程 ``` 用户输入 │ - ├─ 完整内容 + 图片/图片URL → 直接进入发布流程 + ├─ 完整内容 + 图片/图片URL → 判断模式 → 发布流程 │ └─ 网页 URL → WebFetch 提取内容和图片 │ - ├─ 有图片 → 适当总结内容 → 发布流程 + ├─ 有图片 → 适当总结内容 → 判断模式 → 发布流程 │ └─ 无图片 → 提示用户手动下载图片 │ @@ -116,7 +121,9 @@ AskUserQuestion 示例: 将标题和正文写入临时 UTF-8 文本文件。不要在 `python -c` 中内联中文文本。 -### 4.4 运行 Pipeline +### 4.4 运行发布(根据模式分流) + +#### A. 上传图文模式(默认) 根据用户选择的模式执行发布脚本: @@ -144,11 +151,44 @@ python ... --images "C:\path\to\image.jpg" - `READY_TO_PUBLISH` (exit code 0) → 根据模式进入下一步 - Exit code 2 → 报告错误 -### 4.5 用户预览确认(仅有窗口模式) +#### B. 写长文模式 -**仅当用户选择有窗口模式时**,使用 `AskUserQuestion` 请用户在浏览器中检查预览,确认后再发布。 +**Step B.1 — 填写长文内容 + 一键排版:** -无头模式跳过此步骤,直接进入 4.6。 +```bash +python "C:\Users\admin\AI\.claude\skills\post-to-xhs\scripts\cdp_publish.py" long-article --title-file title.txt --content-file content.txt +``` + +可选 `--images img1.jpg img2.jpg` 插入图片到编辑器中。 + +输出中包含 `TEMPLATES: [...]` JSON 数组,为可用的排版模板名称列表。 + +**Step B.2 — 让用户选择模板:** + +使用 `AskUserQuestion` 将模板名称作为选项展示给用户选择(从 TEMPLATES 输出中解析)。 + +**Step B.3 — 选择模板:** + +```bash +python "C:\Users\admin\AI\.claude\skills\post-to-xhs\scripts\cdp_publish.py" select-template --name "用户选择的模板名" +``` + +**Step B.4 — 点击下一步并填写发布页正文描述:** + +```bash +python "C:\Users\admin\AI\.claude\skills\post-to-xhs\scripts\cdp_publish.py" click-next-step --content-file content.txt +``` + +注意:发布页有独立的正文描述编辑器,必须通过 `--content` 或 `--content-file` 传入内容填写。 +如果正文超过 1000 字,应压缩到 800 字左右再填入,保持语义不变。 + +**Step B.5 — 用户预览确认并发布:** 进入下方 4.5 步骤。 + +### 4.5 用户预览确认(仅有窗口模式 / 长文模式) + +**仅当用户选择有窗口模式或使用长文模式时**,使用 `AskUserQuestion` 请用户在浏览器中检查预览,确认后再发布。 + +无头模式的图文发布跳过此步骤,直接进入 4.6。 ### 4.6 点击发布 @@ -164,8 +204,10 @@ python "C:\Users\admin\AI\.claude\skills\post-to-xhs\scripts\cdp_publish.py" cli ## 重要提示 -- **绝不自动发布** - 必须在 Step 4.4 获得用户确认 -- **图片必须有** - 小红书发布必须有图片,没有图片不能发布 +- **绝不自动发布** - 必须获得用户确认 +- **图片要求** - 上传图文模式必须有图片;写长文模式图片可选 +- **长文模式** - 必须让用户选择模板,不要自动选择 +- **正文描述** - 长文模式的发布页有独立正文描述框,超过 1000 字需压缩到 800 字左右 - **无头模式**:使用 `--headless` 参数自动化发布。如需登录,脚本自动切换到有窗口模式 - 如果页面结构变化导致选择器失效,参考 `references/publish-workflow.md` 更新 diff --git a/skills/post-to-xhs/references/publish-workflow.md b/skills/post-to-xhs/references/publish-workflow.md index d708dc3..bdeddb1 100644 --- a/skills/post-to-xhs/references/publish-workflow.md +++ b/skills/post-to-xhs/references/publish-workflow.md @@ -10,10 +10,16 @@ ## 流程概览 +**上传图文模式**: ``` 生成文案 → 用户确认 → 启动 Chrome → 检查登录 → 导航发布页 → 上传图片 → 填写标题 → 填写正文 → 用户确认发布 ``` +**写长文模式**: +``` +生成文案 → 用户确认 → 启动 Chrome → 检查登录 → 导航发布页 → 点击"写长文"tab → 点击"新的创作" → 填写标题 → 填写正文 → 一键排版 → 用户选择模板 → 下一步 → 填写发布页正文描述 → 用户确认发布 +``` + ## 详细步骤 ### 1. 启动 / 连接 Chrome @@ -78,6 +84,64 @@ - 用户确认后,脚本点击发布按钮 - 或用户选择手动点击发布按钮 +## 写长文模式详细步骤 + +### 1-2. 启动 Chrome 和检查登录 + +同上传图文模式。 + +### 3. 导航到发布页并点击"写长文"tab + +脚本: `scripts/cdp_publish.py` → `_click_long_article_tab()` + +- 导航到 `https://creator.xiaohongshu.com/publish/publish` +- 在 `div.creator-tab` 中查找文本为"写长文"的 tab 并点击 + +### 4. 点击"新的创作" + +脚本: `scripts/cdp_publish.py` → `_click_new_creation()` + +- 在页面中查找包含"新的创作"文本的元素并点击 +- 等待长文编辑器页面加载 + +### 5. 填写长文标题 + +脚本: `scripts/cdp_publish.py` → `_fill_long_title()` + +- 定位 `textarea.d-text[placeholder="输入标题"]` 元素 +- 使用 `HTMLTextAreaElement.prototype.value` 的 native setter 设置值 +- 触发 `input` 和 `change` 事件 + +### 6. 填写长文正文 + +同上传图文模式的正文填写(TipTap/ProseMirror 编辑器)。 + +### 7. 一键排版 + +脚本: `scripts/cdp_publish.py` → `_click_auto_format()` + +- 查找并点击"一键排版"按钮 +- 等待模板列表加载 + +### 8. 模板选择 + +脚本: `scripts/cdp_publish.py` → `get_template_names()` + `select_template(name)` + +- `get_template_names()` 从 `.template-card .template-title` 获取所有模板名称 +- `select_template(name)` 点击指定名称的模板卡片 +- 已选中的模板卡片 class 为 `template-card selected` + +### 9. 下一步并填写发布页描述 + +脚本: `scripts/cdp_publish.py` → `click_next_and_prepare_publish(content)` + +- 点击"下一步"按钮进入发布预览页 +- 发布页有独立的正文描述编辑器(`div.tiptap.ProseMirror`),需要单独填入内容 + +### 10. 用户确认并发布 + +同上传图文模式。 + ## DOM 选择器参考 > **注意**: 小红书前端可能随时更新,以下选择器基于编写时的页面结构。如果自动化失败,需要在浏览器 DevTools 中重新抓取选择器,并更新 `cdp_publish.py` 中的 `SELECTORS` 字典。 @@ -85,9 +149,16 @@ | 元素 | 主选择器 | 备选选择器 | 说明 | |---|---|---|---| | 图片上传 | `input.upload-input` | `input[type="file"]` | 隐藏的文件输入,通过 CDP 直接操作 | -| 标题输入 | `input[placeholder*="填写标题"]` | `input.d-text` | 标题输入框 | +| 标题输入(图文) | `input[placeholder*="填写标题"]` | `input.d-text` | 图文模式标题输入框 | +| 标题输入(长文) | `textarea.d-text[placeholder="输入标题"]` | - | 长文模式 textarea 标题 | | 正文编辑 | `div.tiptap.ProseMirror` | `div.ProseMirror[contenteditable="true"]` | TipTap/ProseMirror 富文本编辑器 | -| 发布按钮 | 文本匹配"发布"(`button` + `.d-button-content .d-text`) | - | 通过遍历按钮文本定位 | +| 发布按钮 | 文本匹配"发布" | - | 通过遍历按钮文本定位 | +| 写长文 tab | 文本匹配"写长文"(`div.creator-tab`) | - | 长文模式入口 | +| 新的创作按钮 | 文本匹配"新的创作" | - | 长文编辑器入口 | +| 一键排版按钮 | 文本匹配"一键排版" | - | 触发模板选择 | +| 模板卡片 | `.template-card` | `.template-card.selected`(已选) | 排版模板列表 | +| 模板名称 | `.template-card .template-title` | - | 模板卡片内的名称 span | +| 下一步按钮 | 文本匹配"下一步" | - | 模板选择后进入发布页 | | 登录检测 | URL 包含 "login" | `.user-info, .creator-header` | 重定向检测 + DOM 元素检测 | ## 选择器维护指南 @@ -140,7 +211,7 @@ python publish_pipeline.py --headless --title "标题" --content "正文" --imag - 退出码 1 + `NOT_LOGGED_IN` = 未登录,需扫码(无头模式下会自动切换到有窗口模式) - 退出码 2 = 其他错误 -### 方式 B: 分步调用 +### 方式 B: 分步调用(图文模式) ```bash # 1. 启动 Chrome(可选 --headless) @@ -162,6 +233,36 @@ python cdp_publish.py click-publish python cdp_publish.py --headless publish --title "标题" --content-file body.txt --images img1.jpg ``` +### 方式 C: 分步调用(长文模式) + +```bash +# 1. 启动 Chrome +python chrome_launcher.py + +# 2. 检查登录 +python cdp_publish.py check-login + +# 3. 填写长文 + 一键排版(输出包含 TEMPLATES JSON) +python cdp_publish.py long-article --title-file title.txt --content-file content.txt + +# 4. 选择模板 +python cdp_publish.py select-template --name "模板名称" + +# 5. 下一步 + 填写发布页正文描述 +python cdp_publish.py click-next-step --content-file content.txt + +# 6. 用户确认后点击发布 +python cdp_publish.py click-publish +``` + +### 方式 D: Pipeline 长文模式 + +```bash +# 长文模式(图片可选) +python publish_pipeline.py --mode long-article --title-file title.txt --content-file content.txt +python publish_pipeline.py --mode long-article --title "标题" --content "正文" --images img1.jpg +``` + ### 账号管理 ```bash diff --git a/skills/post-to-xhs/scripts/__pycache__/account_manager.cpython-312.pyc b/skills/post-to-xhs/scripts/__pycache__/account_manager.cpython-312.pyc deleted file mode 100644 index c7d6826458ada66b193314b8a4b30325532f1360..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12940 zcmcgSZA=_TmfbTw-#vT`Fnk&sj17bNhz)Tt#&$3m+p&$k24B{!28df z8&*WhDzI|S_;yboltdm&xeJ!93oES@PN%y|HY@Gkhjcw2yi7WrF4AVD&9B2Dd%KEs z(!Hwgo*wW_KK^VWx@)Rmy?XWPy;twOs(*Jn?F5vccfS_+r#%GmTl7OKR<*Kq(?Ae+ z2$mQoSduja$zc+|4Z{ZfHVzx{n;NFzZ48C$Xf@99fK=+*o26LO1EbP5Oy|IqjXIh&KL8G(GVJ^g{S4z(O0Uf4cunRnrOsM% zV6DvpYt`nX(Zt$VJL`BrDX_zZEIsUEov#tYMQk2dJYrz;U!#Ug*aBQHgu0t$SQlsJ zO3R@Oz?6It2VitV%PzoD3NU3Em?}oh zY&l@6W^3`c3a$d?`3x3zK)j)e4x0|iG)<_V%)j)7{ z4^&2w#EfCa;HmTdA1to@+*D8uwD|n~@Ki`-CVU~^D91A+VV?PZz!x41hen05DQ0r& ze00wl~87Px8BFtYS(W~;Nb#jJ5oxcGDhGM!yz6tIq^MjTaUoZd@?8dL7 zFMUH-g+jIWH^2r$BjIlcDR5#7%Z>P8(XZ#CB-3Zl_x7Caxp1McXQ)Ree<+(zhQp&lPBtrxD;H%*%msC!Ws5qdUzI-mLVAX* z$W|9T?hvACWe8$iwGkLfeTb@7f}W8N4VuP+6dOHMWcP`XFa;7Ij&a(egV1n(5QrIu zy8-JYv<}^e8REJ(d~LvElnr4)wodr2aR4gF_TKXY{ijcQ`%YiHPi&z$LB zp>V~=P6R?%dQM+y^#^@ZEO$k?76=A~E2CTpWG2`W;--a`X+9uw{1w5^2PQ@6qljjP zz{oByhw1bxto6dcd@+o)8~%j?c+3%>QRdgryn5!R_W00ms3zV8jk1~N#3??sjdvFK zfO+?0;94f{bp007TpUmH4tl9&9AVf|L~WlU9;j=Ksxs`b&|VwqOg@o3|~nC7>P0g|=6Wf%mG z?Wl08kTXiCOe9VmIRj(Lp{M85r&(-X+r7mu|{gn zhJCDNE(B%-ES@Hl$Pl&4B>$CZN*rnF!~z-ehgmSF1AjoHe*TZK-~atPzyJB){qENL z_l>d%TMF5s+Djxavfa;fU`ko9PxKh%d{wW!sgO(j4Jzy8BlfB4ghHB0@bfwC2D6qfvSB0jkC+~dN-tFLU7R^FMpJrghd zN%W6K!quH9Jp3h1R6X+<&7{g3Hfn0`PA^U0jV?tWo?AP7K3Q`iRbBVQLha7mG@487 zPfRx2yh+$h=07^1vs8HaEBOD@X9dOU1@(!7`gca9{MIiB(pLB}U9f3{_g8`k2z<|V zd@u1KS#rGG_@Ubd^z2&_I<3}V3Pnkk{mOpLFpl@1NWAGz~t|DE= zCDYFB&@oa>>+E=1Q?-5s5hbD|^2{EZH4la)SPS~obO5ZSWvArNSkjWaYtVO{(07o1+hTo;TsE!v z9$Py>BOBI?4Cz5IepcVkyzAVam3w<7xA&K@k}y3Jj}i=D{SJ z-ZFv+;4z~_H-L-zTAwJXnRF6~i3)?=Rx^4I5*|xL#%{(*PQ)AOc4v*zH&o;n`k2wc zbuR4|fj@|Rr5=71oH|rPC6n7-nybbj-nTNRN0@&yiLaoST z@^$F34?UXE0}11O1OFVVBk|oPx%tr5n_;B+!vJvtLA?QwIpWJ=g04t9D(Ct>wG>H~ z$`qKe8@F#r%>K3ZlgZLk^E9-_I^y<3<-UYv-)E)!=INB9G$zJhNYu0?9BrFsqM~t| zXuJF6OPK**@VNh+>&r~N>nHI zDEvTGx=>KVqJJDJSw$a2Ijh){x=?FE2x4T2)N2ximYNBVWL;jo9v&m+J%0#>#=Q5C z3U@R1jUvR{9orm#1UjHu2)p0`w!u-3*_(3Y-|AfGl#2JPH5^Vlx+SVxnQ*2#Mw7)8 zJ_VJm69!!e9U)cSC1(uyw-IX;>WNFHM$J$jQ^b!2ZBW&ouN7xzh@XN$W`q%@{C-Xl zMy7(zOusKE;6_z&88*xbp$1WLU==aO0^E;zHw1A>Q*Z>da z$l8*lKu%q02IiyKD+Uw5FiTVk=n}?8dyNPAhz6!$L2%t>$;yMNit2d7Qri=osnq@l!en!78i~@j%{+p3&7H$?hzZAh z@PizrOWK^3!D?ldNTkh>Zo}G5@Sxo+NJNmFAZNju*G<}ZdRI>4CbG$yh|_)uM&FBP zDG~7!m+^Esp|t>{;-;x|qaFDMDL^m+lu=RRczV~?duWoME$OFygV4sP%}m|@k>`y| z)Syf6j0wV&UtqcIf&7Gzf$&w6)~p*jlDIDBQYi2WSS?Ldv2kyNy%#V*)&}`~fsp{m zdoW_W|D$wA7t+eFBjaBPM-!~^+)5caU?U>pG{yE!p1!42_<<9LJj*cF9%0X1aX))A*=V<s3^&IMveSI5s4Ts*)j*?X=T))zz`U<5%^Nbw4}4|<8B>BtLd;P zjzf{0p6}2y5i^#IOvG>ONDD9GOef>}dS6{p@g6k+j8zVtY$XSY(I8aYWvL4}@yL;B zhMi9ct6wu6bJZo*Lx=1NuZ*%y<-;N*pQl1B1EB`E@~SiSln?Yd5<0vM@;y)+f}}@! z1C)(Hl-3AwH$>RpU`v*%Yut>WxJzkU06V0B(kf=iZ;OE-?1~d2&vCNt#0@`()1e+p zu>=EXC~OI^5kUJ4;)$BvQ09;}1DE0J-@?D}Kk2Kg0^cEs$7qTLD0ZZHF&;Z}NH zyme#YhE(QB<~KdEG(8knI#%t8_TFS$->=C>ZKnb5QhYxO*xZ>EU>HtV%VNcnwK^NZjk>+>h|7H|6^Z8V_2!;Lb5F9lH`&matUEDJNzUqo zwdOO`%X@ltb4EFe#3h$_ghvSYfXKd1|y_lXxFWC3+H0{*66)YtOQ-~r!Or; zK{-yAr04<&H#RLeUxG4Z9~nw|%ZZQ5ZM}PpAJvoa+!k-Acj>}QBk>@rS zn(`taQd3x{k>lyT7zR@dcz78G3!qKNyT?AAIl`a-2Ith3P*<4b{IIwAwY{$u?>qyF zTrkj&?}djN%*d2KJUN3y8fd|r2$q_Y03skGmlm1=6xpcTsTU2$UxCm6hTxF=WP~-_ zg{BBxaV4_rOE_xR9eWdwy-7#oTwkiF6t)BRt7lW?yW+*~mfb6pnhqyxyA$Qz&|a0G z+|YCG%-4iO^)T)?E(b~$7$TLuW1RmQ3SxBAqUG8#e;}4a$_QafnhDz?lss+fN?MY? z3c!!yU#Ngb#^hnLipEHe-4eC?Yl4stu<>xZO0q-{GSWGsR+P6^mmZQjIfMge(U4;S z1;ojaEf|5!n)RVgi+sU(%MJx6iaHeRV#aJvBuleS9SU$|hstB~w?Snev4Abqp&&e> za*XKGp795Y_q1QDaV-QI#fOyrHEWKVbKt7dp>pEd z#a8Q3vzDkOE3O)r(V+kr`E|a*tTk%Q=G3zm?AeUyNw>5?vPEqIEv8Sz*;-8kVJ9at z+UwZeZ&GYMyGMQRWgFh4-Ztsb8L*9dYt#k-aRW%IJ!)q?$eZE~Y*UD2_su%=9`KDu z-xS>Nt2yJBW9LXWJrvNEW2SaI(|;Dz9u2^y&v1~=C(S-{HsC1OfMYpFe?A9>9`<;4%D<(|_x_pJxB{9W()gjjwHaw|G_x7Pb7Fu;&Q7E<~!$XS2k&(NRE&lKXT!vvG4-E%V)psbT^gPPw(`jJj}#7$h0|#^99=}{Ofn*l zjJr9J(j4aqk?PDo=pYPwp#+>7B1h0!ltw+eIi;M&JQa!KyIs2D9*m~f))gMBBcXi?T4Tq6~iSQwpFGEAYm9Wb{}{~CK=h(gYtl~rBAtb1nv8ui5zJ8 z&IJcqGTDSfeVmw!*fdfbBGv|UuM0+xI4>x-j!@d6i-|Dyp)eB+kD_~F_}oxG8f<3x zsZdv>E`7<%&rW*POK#|bSDGhG#Eeh`LXiS|4?;i9Y={)Xz}u6PNXpSw5B>sLcYA}H zMgh{Q)!x0nPzX{Sihyc7WEvudhSrD~F%RgrV77HJIK2_Esod*gWaDK{kPQRjh^<$- z3IZ_bDx_?=6uJhf%us}GU=VtoL1~SYsa@0KAc+A^G;q`??opagEW zc=-wVf@5gZ2qKGLkk_FwS;!j#o8$M(G<-vs-{4vne+?_9fKqx8lj}uE=};5CK!~4* zkN*w-0tG3HIpPzl=u>COy0bpvte5r=NY47C^E@1N*bCO}H3@r7{PJqCWUooukIwZ% zCdOoaec;uB#e)*lJ~yyNbwHD|;MTyxKZ}#Pxt)Mq~i#L{FHT_DoIc!izCwR!xB}pMs=s#BzNOV zRjTp?oSm55UlOG0B)MrN@(OQVUAP+SM@X(FokxIt%DzsOCaBVwIet~5O1DvZL~1*Q zC_TM{(mqVpiS(4D%4VtkGJ+h=g#1g%a}h%he(ES(cT^=DRY^xRIth2Y9(^^sc>R%O z*G3WZlVkJNkMm16=t9W_38v;0?b@(97u_-AA|302BO-t7rRCb?(<}bvV^ZGXHETC~ zo*%oJ|4C8hd~d35Pdxaju6?O})ir+>kSq?y+_!%aFM^Zc`1j(%@^km1E4|X*&Q)@? z`2#^ZbVhQWeN3NwVj_yGfF<_4TU85Hv64r&>J5i`QN$OF>fdd;*RzH zK`%0jXr=H&%M*OQvbf^XP#tTw^VyXavfcxkKq|SyG9>GmEwad;`=i{n_0Q` zfm?E&dQ6}G47JwBhu*z>?{fT%)b_mO>iG}jf7<`m{+J%zfb~7KaBA^;Z=Ctm=ENkG zJ+jqqILc%7cRfp<_|W>E&cvS1HOHatMA;=*`(wHTBR{ax@&2Ko9a`D9dQoyc|4ZRl zkq;w}>5Ex#yC2zVHu7uY)VsEOHb_0rQy)9>5#oNhi(p>rj63RTt2c|m#jM?)gF~x$JXfQF@ zN0zt`I;35vB-iOR`UU(EcsjN;7LTr6PF5e2b|F?grjKvu&~j9Ab^o&cS6v@=J*Ee; zOk&p~+ujXFbxgcFvoy2j@Bn71xOsW#z03D6FP~ZAB^Pu&{N0dBdq(4eb_`0A8w;X>B~Jopla5Gp%&V-mbB zbmv96nS#7LeA^1VFgQ?F65!~%3fNhZ$5&DCbqSezIUEl1$g1)zdLT2c*k^2gvEfBI zIBfIK3=wHS2@1KD6IT@O%hv9RFgq3Gj`Dv4KT#%4cpaW_SB@l~81hJJiy=t*6T@nqkyn zdXiU39*Q?Ex8G~oB%r$5zJ*?!-A3|AY;cQ!*Cy>C3*!Dw0$$7P7J6+~6_Bn)G1hlG zvPD4k$=))uU=cvzr6GgrYR498d2+ChEQl3t67X7P@oQDwLcca&Ag%DJeUpG!ymPtl w-q9^o-E08qJJG@3(+P5=aBmh}SM45Uh{{h=*X2*fj!nJg7%vw4)hq_Xtp1 zvaxS-77i(-*a_P6`c@lP#oAg@kt?n;cb7|cFR6-sdANmy5o4U-x^?RIKe2F?gk06# z=lko|%rIg(Cvnv^#c#TQ@8|dZ`|2-qb6p&sfxT~s4_xH9|3M%6uN(4_|sgT=OCU}KBAzvsE3WXw}SSS%ng)*W1T~}YZ zP|;T*RQ6R0Ree=LbzilWJH!cV-sXh0q1+E`YLEKXu-J8o<@LVztrgb4&GoGd@j?xo zKc|%ot@}_Lf`uEij#+EDP;MP3+AQ1|?)sU_+!fmy?y|*KJNlu6<6P|>Cz~z=rI5gX zAsmcdj70~fi=+I==!HmFx)>7uE?0Xj8V&WwB|aYGgM9l%F*Y3H!%-<7jP{54OJO6f zBXp@Z7K=#yNii0W^~WNt#ArM=9E^utT9x7GARiRt;r>WS;$zX2RvS)rcKG;yF%*o) zM7}>1jjJ6v*2RxX!NE|Ai^o&T9|%g}e!f38JRFP)5{n!ei=*#+zc3Qe8uE{f@%c!^tPWr zUD`slX>*8mz{rO&9nYCaV;NFro>o+QYWdhmD9T?DW0$3n$PZu-yn2df#hz2G7%~?) zW6iZZ_F548DHeM(jOD?e;f+F45Sv*m_MAqf%i(zcMPtvTZy(x}sf}z_$D)pzMiLEO zet~vy=;C@Sl0GlVzqLGN;e*(d*nk7!!BG)MR~x|djzUln^orS*XGb6s4oZC6v97kB z^B4iL;DEnaN5#-^>{2MT#^ipYIyDQXTkoFBbu4^A42omPr+EP2B>C9l^o+#Bcrsi# zsRJXIxA68R_3^d3IZ-F2d~yZuB*SNEZ(j?wr))2>9e7#fKL`$Il!!WoM8 z#{>Z0L{>-?W1=MIY~8lgzXkuchO_E8PHw&!kB>+#&CMF@^6xFd(-T*zP0ka-m;72{Q#^QK)Ps?Ia{C*f{PFBBzx^iwrgC98Ix-sfkB&rQ zL7|C#XmMxbV0jRdcXFeD z6tD_C9EdgY#+V4vgv3VvY=0yuN$3w{syXj-CA{h$pne2UX`(3gJ3t8uk2>xE>vbYO z`702N#1n3|8MWlRqz{(r)w&&^e#gW_Vd^)_Q=~0u;KFD;9*e5Y=CPxIYK3q=Vc<`R z@c5J{tWRmgpMu)s`tjd*D1x5HM6uoELL3M{j)=cL+nCj7OE^JXbh4>!U_T6Hk4C9> zA_D+ZFY5FJBO^La`n!GhgfkF`0xSmt30EL691}((^zI1+-Wm-?w3O^XK#27R0v~XX zsRvJwwzr%+jY*cy1%=^o^jurlIX@Rj82#=kX$eO5)z;683H4dObd67MzHs=W#wGhm&U^WDhw8tb#R^CD=mQ z19rjwwxiD}I6^rgR|Se7E$eN2pIhLCY~*o-vXIA#JRUU$={fky6Ir4-&Rz4r)^HF1gP=LCuLLuG@k*^5vMR+e3YJ_5zQi4(-Kv6G< zP=p{v(W@-2R~5)xE>yCdRd}xus@eM*yjKcqk-sX3Lw}HiQp~by&(N!NsK0v5?W;`` zClQtgD3=@CG{R8QiA~xPATg72tHdRCMZ$hDCdCtW!VLYiEA@vqCw)KOi{K_VZW-qS zdKx!w#h1QE!KUC#V;3-Yl+&nbjPqH$MN0J9#SQooHzG(_FH4E+z_2tJ2n*QGb}1AY zFq0)J4$L6dP+n}>a5Nkb1mrbIBQVD6*OLWgmewM;$}Ja{Puj2N=s$&}_{v@`L*#FH z*j`IyiJ|zY80|L(fpWx`u^}sLyU{>~5S9LXn-ehnA!ER1I#O)kaZFg^w6WPE*gw=- z=-|%p;kXHF4j0D;Q=cJygXv%>0Dt_E|U*IdTvsJ_Nu27*VMz= zxv|_@E^hFK;924m_HnC_%{ZIZc!6Gn;2gJO6gl6q>HP-3l`Xg;Im50AhlR6pR)i#EgwAh1Vp{|dA1q27KcOK}Vd6Vo{NX{3)&a2bM&3JUkqVjmFuomp7bE0)gjwz-=NgiE0XKb%M%lkjs3H zAnU;SfeONDRe3?b-w&cBHx!l7ZlIs>hjLkaFdB`;kvrIL0C`j;TU+>UpVvG^35OVr z4u%p=G4$3bs+JNqj4zQd2E$S)0LnRl=HjDL!cId-@C_hpHjD8V)=MiSYIIa6PnT|e9Ly{`AWX7|j66z`5n z+k>*|nOA@J%H*LHt2_6s_;JOL z+)B@B<+az9(_c_(&n#BgaEIUrgIGUe5DO|v(U)W*mS!33-5jE^H_AS{B99f8<@2P6ZA zC%PUO9ueX`G@alQ#g{`DdSd-=hT`^7C3yOjc3 zO}Vl=mIN^`fa%u(so&=o4^Y0B5hPp;mJxS}_8{>dN#0JO>nPJPb zIln?xK12WzQ&@Vhuy&!acCm27WcK~)+Qt0+?;M(XYxb@0jlMrR_u6+SZf7ml@B8h7 znM0EfC4c`f+|@rTshk4XD8Bj9^h+}(v)09;+IvO*g(APw{L0;;w&kj7WzF`5svUQV zcdR(L(kcL({PG*)*T$Ef<)=kRiI$rFby-!*Ap zadMvg8#}+TbGfMO=I-g;-`sO^|MY&PcK7XVi$(hs&%Q@loTp&9sC=eidY4kYZJ}tp z;@SR*w^S+HzUbYdICuQhXZ2jcD;9}P&hJ#Tue1N59YG2}u_K8Al;6h-sJ@H<#XPzM zQY3fWI0h!*_*ya%gj_gsR(-QuK92-4a)OdHKt~c$l|-D3*?kUqW4nq`Q{<>vd`9KI zu5Wd}ytzF#8WDKHp0Nw@AVil&B0EncO$tUKT)?rBtJ4s-hO|{uTHcs36{s9^wkF7> zTDq~3Phu)n#-{~uiJZ$)00Kcs^b*+`B_IOEay}bFqw1E=LMFic6hc_+K`yyE<9^mE zx``apaRk`I1ts?idprGG_5v<#p{52oo4|Ggr(cqcF`@HirpEKV|e;*i=e{3^jtoC4?odfV?i_ z6q*^{M=_}e0cO#i$BrCM-l1!UZXCaM{Q8OI(#pw>-#Ub2>?vYX)1Al~4nasRB(g?= zVsKbWVWemSv$^^k9Y-S~{aNi0jdNElLv$$99>FSu>hHIjY!~J_Lq2d5ws(P5#;u|Q zOdD`Av2&}ei@}$ejm2~H_e}V9+&GBl(au?|_UCXZ6ZWKb3@$})8T~c5D2AyGZpF+Y zIP@N5o(IO~)PrttT;jxnB!HDY4qX>v=VSo&H4E^5rdFI`PNx}qxa*_79NE_sB22B( z^-OJ|iNT4zWIGmw@Mt9Ne_GcKxTEdCkl^RrHBqxFK~>SVU!=Z3vSD*_yyyrLVIzj2wtoVLZn1B&Os zL#OA4=Nq0G%Yw7ww>--w6*tdKpPPGafk^lJ?t;9r5?~C9Tl}-VYty$3VarUwPq3TYYE^RD z*x*7I+stc|@L@KD0Mw(j2h6HE`C<0o_Uf{PubTE;tlBx&BUZb1|Pw;Xu9EWUp z7+k6N2Ia_>MlTS}Dw2>;b{*+4w!2#hse(nurLv|_?Etc|9j%g%b`_6OV`_aa(wD?T zf!Kv1iD0D3hUNtH?#XL2Z)!dLW0aHLLclQV&C2OYrP{w-S+~4q{c<(`DBHf(^_a64 zx+k+%TwG~2+qAT)N}48J%kGjJ`>ySq@h!UR=Cbd&n{g&)`sQ4J=>5PuKk?DfV#BLS z{VAoYXR)wX@$@d|RowXUwJ*;MF6M1ioEzC@WQ2RlE;P`}yXe$SE>5_PJ+$+ZSKC4W zIn(YisH|0U09}k*!WMo1f^IXYu_5B<)1J5?&eHc0)Z`9dmb|w+cqu%HO~5u!|EMUE zdY1?^P)o$J42Ca3JxMGLZ7SLjOh-wu>KQ^zt4ot<%I=e32>hWaiE`vz_7T-uW0ThH zbBIKDsk?-*pV*2Z;XT=Qs55ZvL|aGT^|r2F5D;KyIaWj>@ymT5{=R?q^j!Pwu_fo$2WslK_T6>Xu+>hX zwP?pOaPvR$vKqLFQHNZFoBc>62HpF4DK7eJ0ILZWfsy>)1Gohfw z9}GsMkl$#7Nd=!4L!BtZF8d=fQqIJp{)>>~$W>|lgLWox1c23nq+wSfUvi~p>ORrS zA3JgAP-h3<)lJQug!Ua|vD6Pq0Uaag^r4O<6B72R@gZvaG!7?gwcTUP$^(=OBn!i!TzY-kqKN&FC?XynJ?iQXv>)zlKT3SOXdE#y zhCJ--(SR<8#1GUnmT+qjfN?mlaGv z7fXE$Zr=k>!HU&dup6dlLFRq}|?Da1>n?YWvq(PPd%bRd2SwH}hol^xb&jMt@ znk-Xa1tc5v^r|4)poCY=0eTh^d*{1W6Kt>2nnTD*&zXcqTpwyE1?SZ5^T?vHuiKf? zhf2Mwh9AT@n{Zs69NKV*?+-yXrP`4s9X}`(0q1EGs`eiQ&2>`?^8m@UsT+|sOj&h< z6KeOV4PtWH+Z~Euj)`yTn4n7kX2v52RKu5c)lNmO?12uJ&<0EF0I7R0CY}((ICLTj zN8}A!1C4ZOBaoA`?2VF24eH+@)3ai%RKlZW=>^#$i3BX=O=*2kvXRj;tA;H&P{h6( zI7-JvUqU4*m?)R^HlTG)uE|KSq~v)-Rj5SE6Azc;Rr0sbADrKHd%NP@zvO&H!$e9^ z?UJ+3_}BuBLR_Bh#PTOGW0B2ZS~@aHEuAyCWA_7&zCK~YaFL! z^hSp$X1Ri`>QWD(dGHwK z6607EaTI~P;dAUxnpK2#J`G{1h=VZ;z%pcl_N!wpzpGnAJK`nkR~nkROmXZCUBO$* zxyhr3Ripw7p}~+`mo&pBW=YEhWhWrOT&5M;y z3+|?ekQ;Sh>sG1`EP2`=;N!@(BU5i(KMo|cVzuNRvHUNaEywljVM${#lc2JpH32bh z(0qcGqz0hU%orPsAw9K0vLlQ3fMSC{R&Wp{CuDucpvZ{%l=4j73$!Ve!)anJh;@y_ zAsFl;`eM+JndC%qLnD)fQ(Qjx=mB)7os1(H_NY%bRI1nc19fi_Hem)mo5_wxH*DZx z!xHk3Hlms|XiJsSZhy8;U<;9srAVEUUp0;)5pxX#&~y@!*5Kp6l#)WJmtg7$rbXgg zd05Ddl2N7~npJVEClrA`Hzu}4A`SI&yxzCxnX+^;8>oUXN^jYNpS`Rue9jE=Upw(|AzAJwl`oT{L1Q*maA+K{1tl!+c) zj|8&#*+Ec-iD!oPY>ZaP0FlF~wM_Vu8<+DFpSM?-Tizo03ib$D#;D~H`HHa z{Kep<5NwZ0uMN)drPM|51w+XO7!n#86Gxh}9=JqDta>I*)a>L;uvH zlB%S5GT}^(0;57bS>i$Rk=dKiMdp*x}30DVe1Y-KJB-VkS1OzAwfS3AyX~U zn(SiPcbXZRs<2yP3OS;2$jB6w3RJ>8&Jcs7a%1XghMt*iGIl#cJ;9AU zEDlFTi16u7Sk5IZ&2o8aA+g5S_4oUOyIs|MwY*bBI z3B3kKMqtd3$?~~WN{V)C!UhDT%Ff=VF_UgzRWld_Si*h!q$x+sQRQggpt6>cgew{o zhl7!@91;>XU`<$0W3FNKm&m5+#tWN9Ca9H&h#(xp9@S9D8&JEg!ca-B%?vwJGVLUw z$s@SR{UWat{Jc{5%95vT*;5D=`Ae@@7E50F{?WO&rh=q)pS92S-!0uVe{|BNl)SGh!Iq_X9^IQXL^VmL)V6sicO2&&6Bp}!jh?h>CH12 z7Ygeqvyog>JemEVqGopALdCWpbSe8zF6?-9vHaBJVN!ZPp`q~Gmn$JTt^;TFviC7( zE6tlc2;Qn}&CQ|dq1p1et&62ilZO&{rT6noR`R%t9?PRb6=Z{OEI4m5;j~JfGLCH1 za_O4ol69!FN#)iw7~Z|)JfXwzJ$Ie!{^_$r7Ou3-A`xx)&5HIt_Jo7kI;VhglIAjw zZ6BsGU7$gljY=rZ1iMKb0$fmj_GD5<#toUV2`-YrB{Kj@`qnIF0Y0Jl26Ess0>x}b z$|jMhjEJ>duQCB*rb}@qq5xRWTyqYq*~JVZ-6oNo0xAsy9t+soF{_Ik+@#)+F4BCoBzx9dcApsTyL^VDl?$3%upn0$B_?dUI3!0f&pD*AYbA_%_%rQt52 z03%q)JVru7fHlYf7wfHxCET$5ibT}3RRqULIY_-C{ShkH6g5qAq51atQ*%Re=WZ7% zUMOky>Ch5lvf57o$6FUFw*FwZ(%QYS{lsGVNx*R!es)iHD;xHIw@}!(AC{;?gEah*#pI^FC%T>H;d9snqD^tq$E_z!PXR9iICc`1RBAFRp zA9)5|n{e5Ldw|_3B59L|eo@_CoMff!5tz6k>Dz9QzB=qSBw>0PGKgdlHp|2y2JC(= z*=de}!R|~oYgj=uW;X;JDX=?9z)d1vE<#n0Cyk19x>>}1yWCW{7q_s#L65plZI~4p!1oF}6!QB^uVoZ!d zLX=iMD1%E|9VSvvvS=C_O58zcdmF#~bR;MeBWj96LaF6RV6s2%*amMl=D% z9ssoGw{bB(OxWh^`PH*!lAoAedl4y$4&10Q|H!Cxv7vtN$i8}hbJ7qYBEfh4-ex2` zr_WRs31vu#^GqlBCVuPlL@}BBvG;eff5u;yt|N>`Fs0 zO6~TAiXA@)E3fn}ynK4G{I%y}n98;9ZF*ka(!^D~Zh5kW;g|i3-d7aoE6%@c97;p9X@E^PGh+F9nC8JJ39gGr#&l_h=2pZ| ztgMcO8H=ahb4WO`z)X)KP6BME+;m8IHD!p_lhvettVg<6ns>1=B~n>ys*+tx&fO_k z)>NC22{F@^WNqXjK3ngbkykk9rk0g_Zcn>~STt7$^d)P`NncXV^d&oB?9U{LyYG5h zJ}ImuDcl}Y2&bY=yNZffNRlunJ==?Zt#(CXRkMFvN{V5K#{~Kr6uKEDYtL253aP4* z3_O&1B?E}r;K$4nn#TXk6p4gVX^+XLu^a|>Wo8>UMg|hXycU;adS~ub(~J82QD)m1$-SrK)b2;?K~@ zC#h(RTEw$hW_foTZd}7i5#^&$_>dxkEHddva+uufK<%Kf21u|Z*}k8dpvdVKCG9Fz z20bt+q7cl-NbaNAb{h7OV(3yBQZ~&BQg?cEAp|-HN@bPmBOMYfO592JCdi~wJW7Gd zMUd)MoT1?NDENH}NVWxM2FzTT$*C9@K?;vF^>4P;)+%?lv{bsfEBz))!_Z_SmtUNI zX@XQ3bZ_gm#lqJW&+Dot0;Z1N`pS~CUITENrDIO+J!j2=vj*l|EZaS2)q=BX=ID~M zLHjOWaF)-MEIHR{-<1o_%9))cySHm?UucL{G2!cJjoj-h>T0{DciAwBlyUVkbXKxM33RkE{1Z*#$YsiQuD-f&mH$ zbgEi;W@&1_0QYC2Op#Y9_!AkZc96s)J9hJrdh2;xP88##C$xo>~@VeRJI z<&QYKqhh89!Z=68{C3#!J1SO+^BiX_Gs5hScZMEw^!})()bY|hGU4Z=+Q;(abXASud*Jq8D$gyjagP*ILlRof0=J2}m1xvYO z-z++SpOqbJ9Br0a)J4Ca?c3<^u6)tL`I;W(G&)=l^9olS_{F*xmaJsco0BW8oT;5Y zGh4h+vf&A3Qm2b%svrN2hY$%vW&Z(a0A2~I3Y z0O#y-i#sg?KFQx8^BYL5;6Ys(yCzRi+(0voGS{pnXqSfl8e3?hE zMp&CP0@w{?>|6Q>GUrMkLFQb~jv#Zs7ahSmfj38h`N%xZ^+L@H3Q??ya{TGE~g z58_MHBJnBIvkP-!z^k1R&v5QUF7soPYqaAX_rgkv5E*-fT^|BF&;v;lmlVHSX!*8X z*dy!}TG{VDVgI-7rbG%PtXan^W*y^RsIcoXp1koqp^X$HqE$E$wFvDK`DO{EZKgD} z4SaR5FV@m{etJvSeemhStukI88}COLZzsl^H&HO0KLLaX-#rB=LD(?uK`a6Nv`2?j z;yJUI!okcnKsI;CRO6(~#9_2sn7NcbN2<l3MH1bEMRGBy(-(!_nkEq^#YsRm&Xz zjxD}XANmlf+S8s}-TF+R7g$I286x^G?Ga9x{atl+n%U}%V|7kqbxJ3S*vb@T?jK^w zD@WR+_eZQunyb}U=G9ea9d?N{?nQB zs$fqV+J`C5Xs&n<0uIJ~Gi+O#sCdkHU`SCBtJ-Em+=l6Z~zD>76;AYHfeJ%vL_ZQ^&f zznK4bKD8d-Q?GifOZRdvw>?`C-N#G6|47D@`klqJD&^c15ASj{lUhSfMf0ET&0TgU zIc=8<+S1*#C+xISWj zmd*<}9&{INOfG^M4N9{^W=wQh4}74Ko+9ng$!pVlfYLmq>{>qZLrN1I(57FK%Wy$M z|45o+_#5n|xwGxvwk2VKFM~EVKlpqpk=>#828mm@$UB>UwI|^UO98E03CkuXq)s^4 zGT;Ew%Icj=LV#;?V{Q zAFZR&7P*G!Gyhc#1LW~?mb!Q4{7!Py4V8=r0_)`hG_0OS)xD!cPF@Kpg;eKH7J}Tg zVf10hu6k`hptEbW`V23ylv7^_T%fboQ+2fV`WzY6lxx#(22#h-P6LuV`aygK<|mb z$; zfwuPc6Q{d-)mY!AgcAq@`aJkL*+T;-R!{r^)#il;kzG!j*-zoS@3O=Ub#|WVM8IuxO zQXD?}#e|(uviR2&?^OYqem2Fwp%P3*L!^$X==m;X^k`_7tn1*!l#l{>G7AduKn%U> zh`QReVu>sjeTyGZ4NO6FmEKHc9%+|+72*=bn$tu+`!IRv>gWYH@MC6ta5G9~1@wby zf$!9SI7}aaIthE)w9o`#5!6EgAIo@4cr@^YSrXq&niv{88*3s*TY5B{qmn3aaxStV zTLvT&{gFa9lwi{)Dr@7X6`-a1=qUS zGq;Ns*Sba5zN_uf9XXs|@BV7{RI5^d`fB%*{WU~+3vRq|?Twj(itqKwHx_fhkla?z zTnDRftJ%DJu9rn^GoqC8hWR?B^)!oq&4`|H&DGwvv&dF7lgn<>&+Jwb}07JCHodL>PyrzqR_IIODU_LFIM)Vbwr;wqNn7!qWL(B+-nYS zzv6q121x2wZOl8B_32S9O5J(N^@foPzBHE0&(h=;(d3@DK+C3;N0o;tGuQ!H>o>_AzXXD>FU%Brtor;sMZ+O?;^!}zLcN10GH6y$iekVM0cFw63 zZe8+hqsZg$H_zAo(D!}cV$EJ*OGg8Y8PEf;r1oZj^w7c&F}c(?(grO z-+cQNt~dU<^cV8S@?FoVRa)}j$?^Z(x%0kz?M(c=v3JInn4cr1xN)xc56^sXX71>` zs1&v=dG;hXVT@*OTq)f9=iC3H<>QvSp6=8pj18ca?OVv+2miy9&R@98XB*}Z-F4H# zDqiv^^T6r8;k@RYD!A_cB)@Jkf8*7z73&_yx@9<6-Ft2COzRzY!?LI3o@dR1XU&pl z-E#T*d*w|Flu*e}J2iR!hk#%M+WepzsOX!kE-|eABPa75vn*`C)Fs zjdRz|DQmVYWZy^Ym)EX)Z{VGQZ(n>b z`c8Clt$*s!KiIg^{l6%xL{my>$6sy#Nz0F0?&iPtAiwCQbJ{sm@GbYlc>MVkU;%NbTrAB(A5@YF4NHnKXKe(Le-MP9#?TlWi3bsL$|C~~_3!d5~&xRy-C25r@g*A6QwZ?Qe-N|kGmsQuW6&z z=#9}!L;9T7o2P7vTx@o&b3R<1FJ+oc5;^dzvDJ3+$I=XddjXkap*Poy%n#9+tw*X8Xgc+J|MGkJcCDTCe7;G;oDQSG~&x zg$TTPSKZ6_%zKh$v233l1A?<`{|9GJmc{jmqwup!o~YHbZsmf5vwJ4xJGP4ZcIR&# x1U9r+{^QC-5$7rS3`jBezSD!bapXRg3Ncq7J6hTcxIZg!v{%^vtlWa|{{rqU6A1tS diff --git a/skills/post-to-xhs/scripts/__pycache__/chrome_launcher.cpython-312.pyc b/skills/post-to-xhs/scripts/__pycache__/chrome_launcher.cpython-312.pyc deleted file mode 100644 index 34c3ef2f96bd6b1e53664e9811c74116b383185d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13052 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 CGm)SG diff --git a/skills/post-to-xhs/scripts/__pycache__/image_downloader.cpython-312.pyc b/skills/post-to-xhs/scripts/__pycache__/image_downloader.cpython-312.pyc deleted file mode 100644 index 9d340a8cdaab235ac7d18886674c108825ff19a0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7120 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 bool: + """Wait for template cards to appear after clicking auto-format.""" + print("[cdp_publish] Waiting for template cards to load...") + selector = SELECTORS["template_card"] + + for attempt in range(TEMPLATE_WAIT): + found = self._evaluate( + f"document.querySelectorAll('{selector}').length" + ) + if found and found > 0: + print(f"[cdp_publish] Found {found} template card(s).") + return True + time.sleep(1) + + print("[cdp_publish] Warning: No template cards found within timeout.") + return False + + def get_template_names(self) -> list[str]: + """Get the list of available template names from the page.""" + selector = SELECTORS["template_card"] + names = self._evaluate(f""" + (function() {{ + var cards = document.querySelectorAll('{selector}'); + var names = []; + for (var i = 0; i < cards.length; i++) {{ + var title = cards[i].querySelector('.template-title'); + names.push(title ? title.textContent.trim() : 'Template ' + i); + }} + return names; + }})(); + """) + return names or [] + + def select_template(self, name: str) -> bool: + """Select a template by clicking the card with the matching name.""" + print(f"[cdp_publish] Selecting template: {name}...") + selector = SELECTORS["template_card"] + + clicked = self._evaluate(f""" + (function() {{ + var cards = document.querySelectorAll('{selector}'); + for (var i = 0; i < cards.length; i++) {{ + var title = cards[i].querySelector('.template-title'); + if (title && title.textContent.trim() === {json.dumps(name)}) {{ + cards[i].click(); + return true; + }} + }} + return false; + }})(); + """) + + if clicked: + print(f"[cdp_publish] Template '{name}' selected.") + time.sleep(ACTION_INTERVAL) + else: + print(f"[cdp_publish] Warning: Template '{name}' not found.") + + return bool(clicked) + + def _click_next_step(self): + """Click the '下一步' button.""" + print("[cdp_publish] Clicking '下一步' button...") + btn_text = SELECTORS["next_step_btn_text"] + + clicked = self._evaluate(f""" + (function() {{ + var elems = document.querySelectorAll( + 'button, [role="button"], span, div, a, [class*="btn"]' + ); + for (var i = 0; i < elems.length; i++) {{ + if (elems[i].textContent.trim() === '{btn_text}') {{ + elems[i].click(); + return true; + }} + }} + return false; + }})(); + """) + + if not clicked: + raise CDPError( + f"Could not find '{btn_text}' button. " + "The page structure may have changed." + ) + + print("[cdp_publish] '下一步' button clicked.") + time.sleep(PAGE_LOAD_WAIT) + + def publish_long_article( + self, + title: str, + content: str, + image_paths: list[str] | None = None, + ) -> list[str]: + """ + Execute the full long article publish workflow: + 1. Navigate to creator publish page + 2. Click '写长文' tab + 3. Click '新的创作' button + 4. Fill title (textarea) + 5. Fill content (TipTap editor) + 6. (Optional) Insert images into editor + 7. Click '一键排版' + 8. Wait for templates + + Returns list of available template names for the caller to + present to the user for selection. + + Args: + title: Article title + content: Article body text (paragraphs separated by newlines) + image_paths: Optional list of local file paths to images + """ + if not self.ws: + raise CDPError("Not connected. Call connect() first.") + + # Step 1: Navigate to publish page + self._navigate(XHS_CREATOR_URL) + time.sleep(2) + + # Step 2: Click '写长文' tab + self._click_long_article_tab() + + # Step 3: Click '新的创作' + self._click_new_creation() + + # Step 4: Fill title + self._fill_long_title(title) + + # Step 5: Fill content + self._fill_content(content) + + # Step 6: Upload images into editor (if provided) + if image_paths: + print(f"[cdp_publish] Inserting {len(image_paths)} image(s) into editor...") + for img_path in image_paths: + normalized = img_path.replace("\\", "/") + self._evaluate(f""" + (function() {{ + var editor = document.querySelector('{SELECTORS["content_editor"]}'); + if (!editor) return false; + var img = document.createElement('img'); + img.src = 'file:///{normalized}'; + editor.appendChild(img); + editor.dispatchEvent(new Event('input', {{ bubbles: true }})); + return true; + }})(); + """) + time.sleep(ACTION_INTERVAL) + + # Step 7: Click '一键排版' + self._click_auto_format() + + # Step 8: Wait for templates and return names + self._wait_for_templates() + template_names = self.get_template_names() + + print( + "\n[cdp_publish] Templates loaded.\n" + " Available templates: " + ", ".join(template_names) + "\n" + ) + return template_names + + def click_next_and_prepare_publish(self, content: str = ""): + """After user selects a template, click '下一步' and fill the publish page description.""" + self._click_next_step() + + # The publish page has a separate content editor for the post description + if content: + time.sleep(ACTION_INTERVAL) + self._fill_content(content) + + print( + "\n[cdp_publish] Ready to publish.\n" + " Please review in the browser before confirming publish.\n" + ) + + # ------------------------------------------------------------------ + # Main publish workflow (image-text mode) # ------------------------------------------------------------------ def publish( @@ -532,6 +843,23 @@ def main(): p_pub.add_argument("--content-file", default=None, help="Read content from file") p_pub.add_argument("--images", nargs="+", required=True) + # long-article - long article mode + p_long = sub.add_parser("long-article", help="Fill long article content with auto-format and template selection") + p_long.add_argument("--title", default=None) + p_long.add_argument("--title-file", default=None, help="Read title from file") + p_long.add_argument("--content", default=None) + p_long.add_argument("--content-file", default=None, help="Read content from file") + p_long.add_argument("--images", nargs="+", default=None, help="Optional image file paths") + + # select-template - select a template by name + p_tpl = sub.add_parser("select-template", help="Select a long article template by name") + p_tpl.add_argument("--name", required=True, help="Template name to select") + + # click-next-step - click next step button (for long article after template selection) + p_next = sub.add_parser("click-next-step", help="Click '下一步' button after template selection") + p_next.add_argument("--content", default=None, help="Post description text") + p_next.add_argument("--content-file", default=None, help="Read post description from file") + # click-publish - just click the publish button on current page sub.add_parser("click-publish", help="Click publish button on already-filled page") @@ -648,6 +976,48 @@ def main(): publisher._click_publish() print("PUBLISH_STATUS: PUBLISHED") + elif args.command == "long-article": + title = args.title + if args.title_file: + with open(args.title_file, encoding="utf-8") as f: + title = f.read().strip() + if not title: + print("Error: --title or --title-file required.", file=sys.stderr) + sys.exit(1) + + content = args.content + if args.content_file: + with open(args.content_file, encoding="utf-8") as f: + content = f.read().strip() + if not content: + print("Error: --content or --content-file required.", file=sys.stderr) + sys.exit(1) + + publisher.connect() + template_names = publisher.publish_long_article( + title=title, content=content, image_paths=args.images, + ) + # Print template names as JSON for programmatic consumption + print("TEMPLATES: " + json.dumps(template_names, ensure_ascii=False)) + print("LONG_ARTICLE_STATUS: TEMPLATE_SELECTION") + + elif args.command == "select-template": + publisher.connect(target_url_prefix="https://creator.xiaohongshu.com/publish") + if publisher.select_template(args.name): + print(f"TEMPLATE_SELECTED: {args.name}") + else: + print(f"Error: Template '{args.name}' not found.", file=sys.stderr) + sys.exit(1) + + elif args.command == "click-next-step": + content = getattr(args, 'content', None) + if getattr(args, 'content_file', None): + with open(args.content_file, encoding="utf-8") as f: + content = f.read().strip() + publisher.connect(target_url_prefix="https://creator.xiaohongshu.com/publish") + publisher.click_next_and_prepare_publish(content=content or "") + print("LONG_ARTICLE_STATUS: READY_TO_PUBLISH") + elif args.command == "click-publish": publisher.connect(target_url_prefix="https://creator.xiaohongshu.com/publish") publisher._click_publish() diff --git a/skills/post-to-xhs/scripts/publish_pipeline.py b/skills/post-to-xhs/scripts/publish_pipeline.py index b83ae9f..d393efa 100644 --- a/skills/post-to-xhs/scripts/publish_pipeline.py +++ b/skills/post-to-xhs/scripts/publish_pipeline.py @@ -21,6 +21,10 @@ Usage: # Use local image files instead of URLs python publish_pipeline.py --title "标题" --content "正文" --images img1.jpg img2.jpg + # Long article mode (images optional) + python publish_pipeline.py --mode long-article --title "标题" --content "正文" + python publish_pipeline.py --mode long-article --title "标题" --content "正文" --images img1.jpg + Exit codes: 0 = success (READY_TO_PUBLISH or PUBLISHED) 1 = not logged in (NOT_LOGGED_IN) - headless auto-fallback will restart headed @@ -65,8 +69,16 @@ def main(): content_group.add_argument("--content", help="Article body text") content_group.add_argument("--content-file", help="Read content from UTF-8 file") - # Images - img_group = parser.add_mutually_exclusive_group(required=True) + # Mode + parser.add_argument( + "--mode", + choices=["image-text", "long-article"], + default="image-text", + help="Publish mode: 'image-text' (default) or 'long-article'", + ) + + # Images (required for image-text, optional for long-article) + img_group = parser.add_mutually_exclusive_group(required=False) img_group.add_argument( "--image-urls", nargs="+", help="Image URLs to download" ) @@ -169,7 +181,7 @@ def main(): if not image_paths: print("Error: All image downloads failed.", file=sys.stderr) sys.exit(2) - else: + elif args.images: image_paths = args.images # Verify local files exist for p in image_paths: @@ -177,20 +189,33 @@ def main(): print(f"Error: Image file not found: {p}", file=sys.stderr) sys.exit(2) print(f"[pipeline] Step 3: Using {len(image_paths)} local image(s).") + elif args.mode == "image-text": + print("Error: Images are required for image-text mode. Use --image-urls or --images.", file=sys.stderr) + sys.exit(2) + else: + print("[pipeline] Step 3: No images (optional for long-article mode).") # --- Step 4: Fill form --- print("[pipeline] Step 4: Filling form...") try: - publisher.publish(title=title, content=content, image_paths=image_paths) - print("FILL_STATUS: READY_TO_PUBLISH") + if args.mode == "long-article": + publisher.publish_long_article( + title=title, + content=content, + image_paths=image_paths or None, + ) + print("LONG_ARTICLE_STATUS: TEMPLATE_SELECTION") + else: + publisher.publish(title=title, content=content, image_paths=image_paths) + print("FILL_STATUS: READY_TO_PUBLISH") except CDPError as e: print(f"Error during form fill: {e}", file=sys.stderr) if downloader: downloader.cleanup() sys.exit(2) - # --- Step 5: Publish (optional) --- - if args.auto_publish: + # --- Step 5: Publish (optional, image-text mode only) --- + if args.auto_publish and args.mode == "image-text": print("[pipeline] Step 5: Clicking publish button...") try: publisher._click_publish()