Compare commits

..

9 Commits

Author SHA1 Message Date
Jim Liu 宝玉 bd5745f837 chore: release v1.116.5 2026-05-14 01:49:35 -05:00
Jim Liu 宝玉 cb26732559 refactor(baoyu-post-to-wechat): harden Telegram QR notification
- Add 10s timeout to Telegram fetch so unreachable api.telegram.org
  doesn't block waitForLogin indefinitely.
- Move 2s QR-render wait inside sendQrToTelegram, after the env-var
  early return, so the no-op path doesn't pay the delay.
- Use viewport screenshot (captureBeyondViewport: false) as fallback
  to reduce payload size and keep the QR scannable.
2026-05-14 01:45:58 -05:00
孙斌锋 9baf570caa feat(baoyu-post-to-wechat): send WeChat login QR code to Telegram (#150)
When `TELEGRAM_BOT_TOKEN` and `TELEGRAM_CHAT_ID` env vars are set,
the browser login flow automatically sends the WeChat QR code image
to the configured Telegram chat so headless / remote runs don't need
a screen to scan the code.

Strategy (in priority order):
1. Extract QR img.src from known DOM selectors
2. If URL-based src: re-fetch inside Chrome to carry session cookies
3. Fallback: full-page CDP screenshot

Feature is fully opt-in: skipped silently when env vars are absent.

Co-authored-by: Before SUN <beforesun@BeforedeMac-mini.local>
2026-05-14 01:21:45 -05:00
Jim Liu 宝玉 f99815cec9 chore: release v1.116.4 2026-05-14 01:10:54 -05:00
Jim Liu 宝玉 8f0663d515 refactor(baoyu-wechat-summary): streamline roast version prompts 2026-05-14 01:10:51 -05:00
Jim Liu 宝玉 20ebf6126c chore: release v1.116.3 2026-05-13 22:33:29 -05:00
Jim Liu 宝玉 64db328e61 docs: replace Claude Code references with Agent in READMEs 2026-05-13 22:33:16 -05:00
Jim Liu 宝玉 a3819b8e30 chore: release v1.116.2 2026-05-13 22:30:54 -05:00
Jim Liu 宝玉 234c2a832b docs(baoyu-wechat-summary): update example group name in SKILL.md 2026-05-13 22:30:38 -05:00
9 changed files with 186 additions and 117 deletions
+1 -1
View File
@@ -6,7 +6,7 @@
},
"metadata": {
"description": "Skills shared by Baoyu for improving daily work efficiency",
"version": "1.116.1"
"version": "1.116.5"
},
"plugins": [
{
+23
View File
@@ -2,6 +2,29 @@
English | [中文](./CHANGELOG.zh.md)
## 1.116.5 - 2026-05-14
### Features
- `baoyu-post-to-wechat`: send WeChat login QR code to Telegram when `TELEGRAM_BOT_TOKEN` and `TELEGRAM_CHAT_ID` env vars are set, enabling headless / remote login flows (by @beforesun)
### Refactor
- `baoyu-post-to-wechat`: harden Telegram QR notification — add 10s fetch timeout, defer the 2s QR-render wait until env vars are configured, and use viewport screenshot as fallback
## 1.116.4 - 2026-05-14
### Refactor
- `baoyu-wechat-summary`: streamline roast version prompts in output-formats.md (99 → 23 lines), add roast-specific profile usage bullets to SKILL.md Round 2
## 1.116.3 - 2026-05-13
### Documentation
- Replace Claude Code references with generic Agent wording in READMEs to reflect multi-agent support (Claude Code, Codex, etc.)
## 1.116.2 - 2026-05-13
### Documentation
- `baoyu-wechat-summary`: update example group name in SKILL.md
## 1.116.1 - 2026-05-13
### Features
+23
View File
@@ -2,6 +2,29 @@
[English](./CHANGELOG.md) | 中文
## 1.116.5 - 2026-05-14
### 新功能
- `baoyu-post-to-wechat`:当设置 `TELEGRAM_BOT_TOKEN``TELEGRAM_CHAT_ID` 环境变量时,自动将微信登录二维码发送到 Telegram,支持无显示器/远程登录场景 (by @beforesun)
### 重构
- `baoyu-post-to-wechat`:加固 Telegram QR 通知逻辑 —— 增加 10 秒 fetch 超时、未配置环境变量时不再无谓等待 2 秒、回退截图改为视口范围以减小体积
## 1.116.4 - 2026-05-14
### 重构
- `baoyu-wechat-summary`:精简毒舌版提示词(99 → 23 行),在 SKILL.md Round 2 中增加 roast 专用的画像使用指引
## 1.116.3 - 2026-05-13
### 文档
- README 中将 Claude Code 替换为通用的 Agent 表述,体现多 Agent 支持(Claude Code、Codex 等)
## 1.116.2 - 2026-05-13
### 文档
- `baoyu-wechat-summary`:更新 SKILL.md 中的示例群名
## 1.116.1 - 2026-05-13
### 新功能
+8 -8
View File
@@ -2,7 +2,7 @@
English | [中文](./README.zh.md)
Skills shared by Baoyu for improving daily work efficiency with Claude Code.
Skills shared by Baoyu for improving daily work efficiency with AI Agents (Claude Code, Codex, etc.).
## Prerequisites
@@ -40,7 +40,7 @@ Publishing to ClawHub releases the published skill under `MIT-0`, per ClawHub's
### Register as Plugin Marketplace
Run the following command in Claude Code:
Run the following command in the Agent:
```bash
/plugin marketplace add JimLiu/baoyu-skills
@@ -64,7 +64,7 @@ Run the following command in Claude Code:
**Option 3: Ask the Agent**
Simply tell Claude Code:
Simply tell the Agent:
> Please install Skills from github.com/JimLiu/baoyu-skills
@@ -80,7 +80,7 @@ The marketplace now exposes a single plugin so each skill is registered exactly
To update skills to the latest version:
1. Run `/plugin` in Claude Code
1. Run `/plugin` in the Agent
2. Switch to **Marketplaces** tab (use arrow keys or Tab)
3. Select **baoyu-skills**
4. Choose **Update marketplace**
@@ -579,7 +579,7 @@ Plain text input is treated as a regular post. Markdown files are treated as X A
```bash
# Post with text
/baoyu-post-to-x "Hello from Claude Code!"
/baoyu-post-to-x "Hello from AI Agent!"
# Post with images
/baoyu-post-to-x "Check this out" --image photo.png
@@ -1118,16 +1118,16 @@ Summarize WeChat group chat highlights into a structured digest. Extracts topics
```bash
# Summarize a group's recent messages
/baoyu-wechat-summary 消失的大叔 最近 1
/baoyu-wechat-summary 相亲相爱一家人 最近 1
# Weekly summary
/baoyu-wechat-summary AI 技术群 最近 7
# Incremental (since last digest)
/baoyu-wechat-summary 消失的大叔
/baoyu-wechat-summary 相亲相爱一家人
# Roast version
/baoyu-wechat-summary 消失的大叔 最近 3 天 毒舌版
/baoyu-wechat-summary 相亲相爱一家人 最近 3 天 毒舌版
```
**Requirements**:
+8 -8
View File
@@ -2,7 +2,7 @@
[English](./README.md) | 中文
宝玉分享的 Claude Code 技能集,提升日常工作效率。
宝玉分享的 AI Agent 技能集(适用于 Claude Code、Codex 等),提升日常工作效率。
## 前置要求
@@ -40,7 +40,7 @@ clawhub install baoyu-markdown-to-html
### 注册插件市场
Claude Code 中运行:
Agent 中运行:
```bash
/plugin marketplace add JimLiu/baoyu-skills
@@ -64,7 +64,7 @@ clawhub install baoyu-markdown-to-html
**方式三:告诉 Agent**
直接告诉 Claude Code
直接告诉 Agent
> 请帮我安装 github.com/JimLiu/baoyu-skills 中的 Skills
@@ -80,7 +80,7 @@ clawhub install baoyu-markdown-to-html
更新技能到最新版本:
1.Claude Code 中运行 `/plugin`
1.Agent 中运行 `/plugin`
2. 切换到 **Marketplaces** 标签页(使用方向键或 Tab
3. 选择 **baoyu-skills**
4. 选择 **Update marketplace**
@@ -579,7 +579,7 @@ clawhub install baoyu-markdown-to-html
```bash
# 发布文字
/baoyu-post-to-x "Hello from Claude Code!"
/baoyu-post-to-x "Hello from AI Agent!"
# 发布带图片
/baoyu-post-to-x "看看这个" --image photo.png
@@ -1118,16 +1118,16 @@ AI 驱动的生成后端。
```bash
# 总结群最近消息
/baoyu-wechat-summary 消失的大叔 最近 1
/baoyu-wechat-summary 相亲相爱一家人 最近 1
# 周报
/baoyu-wechat-summary AI 技术群 最近 7
# 增量模式(从上次摘要继续)
/baoyu-wechat-summary 消失的大叔
/baoyu-wechat-summary 相亲相爱一家人
# 毒舌版
/baoyu-wechat-summary 消失的大叔 最近 3 天 毒舌版
/baoyu-wechat-summary 相亲相爱一家人 最近 3 天 毒舌版
```
**前置要求**
+1 -1
View File
@@ -245,7 +245,7 @@ Files created:
|-------|-----|
| Missing API credentials | Follow guided setup in Step 2 |
| Access token error | Verify credentials valid and not expired |
| Not logged in (browser) | First run opens browser — scan QR to log in |
| Not logged in (browser) | First run opens browser — scan QR to log in. Set `TELEGRAM_BOT_TOKEN` + `TELEGRAM_CHAT_ID` to receive the QR image via Telegram |
| Chrome not found | Set `WECHAT_BROWSER_CHROME_PATH` |
| Title/summary missing | Use auto-generation or provide manually |
| No cover image | Add frontmatter cover or place `imgs/cover.png` in article directory |
@@ -31,7 +31,98 @@ interface ArticleOptions {
cdpPort?: number;
}
async function sendQrToTelegram(session: ChromeSession): Promise<void> {
const botToken = process.env.TELEGRAM_BOT_TOKEN;
const chatId = process.env.TELEGRAM_CHAT_ID;
if (!botToken || !chatId) return;
// Wait for QR to render before extracting
await sleep(2000);
try {
// Try to extract QR image from DOM first (avoids full-page screenshot noise)
const domResult = await session.cdp.send<{ result: { value: string } }>('Runtime.evaluate', {
expression: `
(function() {
const selectors = [
'.login__type__container__scan img',
'.login_img img',
'#login_container img',
'.qrcode img',
'img[src*="qrcode"]',
'img[src*="login"]',
];
for (const sel of selectors) {
const el = document.querySelector(sel);
if (el?.src && !el.src.startsWith('data:,')) return el.src.startsWith('data:') ? el.src : 'url:' + el.src;
}
const canvas = document.querySelector('canvas');
if (canvas) try { return canvas.toDataURL('image/png'); } catch {}
return '';
})()
`,
returnByValue: true,
}, { sessionId: session.sessionId });
const raw = (domResult.result.value as string) ?? '';
let imgBuffer: Buffer;
if (raw.startsWith('data:image')) {
imgBuffer = Buffer.from(raw.split(',')[1] ?? '', 'base64');
} else if (raw.startsWith('url:')) {
// Fetch inside Chrome to carry WeChat session cookies
const imgUrl = raw.slice(4);
const inBrowserFetch = await session.cdp.send<{ result: { value: string } }>('Runtime.evaluate', {
expression: `
(async () => {
const resp = await fetch(${JSON.stringify(imgUrl)}, { credentials: 'include' });
const buf = await resp.arrayBuffer();
const bytes = new Uint8Array(buf);
let b = '';
for (let i = 0; i < bytes.length; i++) b += String.fromCharCode(bytes[i]);
return btoa(b);
})()
`,
returnByValue: true,
awaitPromise: true,
}, { sessionId: session.sessionId });
imgBuffer = Buffer.from((inBrowserFetch.result.value as string) ?? '', 'base64');
} else {
// Fallback: viewport screenshot (smaller than full-page; QR is usually in viewport)
const screenshotResp = await session.cdp.send<{ data: string }>(
'Page.captureScreenshot', { format: 'png', captureBeyondViewport: false }, { sessionId: session.sessionId }
);
imgBuffer = Buffer.from(screenshotResp.data ?? '', 'base64');
}
const boundary = `tgboundary${Date.now()}`;
const parts: Buffer[] = [
Buffer.from(`--${boundary}\r\nContent-Disposition: form-data; name="chat_id"\r\n\r\n${chatId}\r\n`),
Buffer.from(`--${boundary}\r\nContent-Disposition: form-data; name="caption"\r\n\r\nWeChat QR code — please scan to log in\r\n`),
Buffer.from(`--${boundary}\r\nContent-Disposition: form-data; name="photo"; filename="qr.png"\r\nContent-Type: image/png\r\n\r\n`),
imgBuffer,
Buffer.from(`\r\n--${boundary}--\r\n`),
];
const tgResp = await fetch(`https://api.telegram.org/bot${botToken}/sendPhoto`, {
method: 'POST',
headers: { 'Content-Type': `multipart/form-data; boundary=${boundary}` },
body: Buffer.concat(parts),
signal: AbortSignal.timeout(10_000),
});
const tgJson = await tgResp.json() as { ok: boolean; description?: string };
if (tgJson.ok) {
console.log('[wechat] QR code sent to Telegram.');
} else {
console.error('[wechat] Telegram send failed:', tgJson.description);
}
} catch (err) {
console.error('[wechat] Failed to send QR to Telegram:', err);
}
}
async function waitForLogin(session: ChromeSession, timeoutMs = 120_000): Promise<boolean> {
// Notify via Telegram if configured (no-op when env vars absent)
await sendQrToTelegram(session);
const start = Date.now();
while (Date.now() - start < timeoutMs) {
const url = await evaluate<string>(session, 'window.location.href');
@@ -557,7 +648,8 @@ export async function postArticle(options: ArticleOptions): Promise<void> {
const url = await evaluate<string>(session, 'window.location.href');
if (!url.includes('/cgi-bin/')) {
console.log('[wechat] Not logged in. Please scan QR code...');
const hasTelegram = !!(process.env.TELEGRAM_BOT_TOKEN && process.env.TELEGRAM_CHAT_ID);
console.log(`[wechat] Not logged in. Please scan QR code...${hasTelegram ? ' (sending to Telegram)' : ''}`);
const loggedIn = await waitForLogin(session);
if (!loggedIn) throw new Error('Login timeout');
}
+10 -3
View File
@@ -286,6 +286,13 @@ The `imgs/` directory exists as an **extension point**: a user (or a future wx-c
- Callback past quotes ("继上次'要不要买 moderna'之后,这次又...")
- Don't sacrifice current material to force a callback.
**Roast pass — profile usage extras** (only when generating the roast version):
- 历史槽点可做 callback joke
- Running gag 可以升级和迭代
- 历史毒舌语录可以引用或翻新
- 但当期素材优先,不要为了 callback 硬凑
**Writing order**: write the body categories first, then the opening overview based on the finished body (so the hook is accurate).
Detailed structure, voice, formatting rules, and content guidelines are in [references/output-formats.md](references/output-formats.md). Load that file now if not already loaded.
@@ -325,8 +332,8 @@ Always reflects only the most recent normal digest. Overwrite on each run when `
```json
{
"group_id": "12345678901@chatroom",
"group_name": "消失的大叔",
"folder": "12345678901@chatroom-消失的大叔",
"group_name": "相亲相爱一家人",
"folder": "12345678901@chatroom-相亲相爱一家人",
"last_digest": {
"file": "2026-03-12.md",
"date_range": "2026-03-12",
@@ -393,7 +400,7 @@ Full procedure in [references/profiles.md](references/profiles.md).
```
{data_root}/ # default: {project_root}/wechat/
└── {group_id}-{group_name}/ # e.g. 12345678901@chatroom-消失的大叔/
└── {group_id}-{group_name}/ # e.g. 12345678901@chatroom-相亲相爱一家人/
├── history.json # last digest pointer (fast)
├── history-digests.jsonl # append-only archive
├── 2026-03-12.md # normal digest, single date
@@ -130,103 +130,27 @@ No date, no signature, no version number.
## 2. Roast version (毒舌版)
The roast version mirrors the normal version's structure but inverts the tone. Generate only when `include_roast=true` (configured via EXTEND.md `default_version` or triggered by request keywords like 毒舌/roast/挑衅).
Roast 版基于普通版的话题骨架和素材,用毒舌、尖锐、挑衅的风格重写。整体结构与普通版相同(统计区块、开头概览、群友画像、正文分类、结尾),但风格完全不同。痛点部分省略。仅当 `include_roast=true` 时生成。标题加 "毒舌版" 后缀。
### 2.1 Structural parity
风格要求:
- 你是一位以尖锐和挑衅风格著称的专业评论员
- 对每个群友的行为、言论进行犀利点评,不怕让人尴尬
- 发言排行旁给每个人加一句毒舌备注(括号内)
- 群友画像改为「不留情面版」,放大每个人的槽点和矛盾之处
- 开头概览用更戏谑的口吻,突出荒诞和讽刺
- 正文话题标题可以改得更损
- 引用原话时配上辛辣点评
- 结尾改为:本简报由一个没有感情的 AI 自动生成,如有冒犯,概不负责
```
[Title line — adds "毒舌版" suffix]
[📊 Stats block — each leaderboard row gets a roast comment]
[Opening summary — absurd recap, sarcastic]
[群友画像 — 不留情面版]
[Categorized body — louder, more brutal category titles]
[Fixed footer — roast version]
```
注意:毒舌但不恶毒,调侃但不人身攻击。目标是让群友看了会笑,而不是生气。具体红线:
- 只嘲讽群里的公开行为,不碰外貌、体重、健康、家庭、私人关系
- 不用时间戳推断作息或时区(服务器时间不等于本地时间)
- 不做医学/心理诊断类玩笑(「这位需要看医生」「典型 ADHD」)
- 不揣测对方未主动公开的身份属性(性取向、宗教、政治立场)
- 嘲讽观点本身,不嘲讽发言的权利(「这个观点错得离谱」可以,「连这都不懂还敢发言」不行)
- 如果某人本期没有槽点(3+ 条但都很中性),给一句温和调侃即可,不要硬凑
Pain-point section is **dropped** in the roast version.
### 2.2 Title line
Form: `{群名} 群聊精华 · {日期} · 毒舌版`
Example: `相亲相爱一家人 群聊精华 · 2026-03-12 · 毒舌版`
### 2.3 Statistics block (roast)
- Same `📊 消息统计: 共 N 条消息` opener.
- Leaderboard: each row gets a parenthetical roast comment.
- Form: `{排名}. {昵称}: {消息数} 条 ({一句毒舌评语})`
Example:
```
📊 消息统计: 共 387 条消息
1. 蛙总: 92 条 (一个人撑起了空头的体面)
2. 老王: 58 条 (主要功能是给蛙总当反例)
3. 阿喵: 41 条 (发的链接比发的话还多)
```
### 2.4 Opening summary (roast)
- 1-2 paragraphs, sarcastic recap tone.
- Highlight the day's most ridiculous beat (a failed prediction, a heated argument over something trivial, a wild flex).
- Reference 2-3 of the roast category titles in the prose.
### 2.5 群友画像 (roast — 不留情面版)
- Same per-user entry format as normal, but with the role tag dialed up.
- Amplify quirks, contradictions, fail moments visible in the messages.
- 2-5 bullets with `•`. Each bullet is a roastable observation backed by a direct quote.
- Don't invent — every roast must trace back to something the person actually said in the batch (or in the loaded roast profile from prior batches).
Example:
```
群友画像
蛙总(做空美股钉子户,三周亏损不改其志)
• 今天第 47 次预测 SPY 见顶,给出的理由是"我感觉"
• 被老王翻出上周聊天记录后嘴硬:「上周的不算,那是市场不理性」
• 收盘前突然安静,疑似刚看完账户
```
### 2.6 Categorized body (roast)
- Same 3-6 category structure.
- Titles can be louder, more brutal, mock-headline style.
- Examples of acceptable roast titles:
- `蛙总做空翻车: 一个人对抗整个美股`
- `老王再次试图当人生导师, 群友集体打哈欠`
- `阿喵又分享了一篇没人读完的长文`
- Bodies still preserve real quotes and traceability — sarcasm is in framing, not fabrication.
### 2.7 Footer (roast)
Fixed line, last in file:
```
本简报由一个没有感情的 AI 自动生成,如有冒犯,概不负责
```
### 2.8 Red lines (non-negotiable)
These rules override style; violate them and the roast becomes harmful. Write the spicy version first, then audit against this list and rewrite anything that crosses a line.
- **Mock public group behavior only.** Never appearance, weight, body, health, mental state, family, relationships (unless openly group-discussed at the person's own initiative), finances beyond what they publicly mention.
- **调侃 ≠ 人身攻击。** Readers should laugh at the situation, not feel sorry for the target. If you can't think of a joke that doesn't read as cruel, drop the bullet.
- **No timestamp-based sleep/timezone jokes.** Server time ≠ recipient's local time; also implies surveillance. Forbidden: `凌晨 3 点还在群里发言,怕是没睡好`, `这位是哪个时区的`, etc.
- **No medical/psychological speculation.** Even joking diagnoses (`这位需要看医生`, `典型 ADHD`) are out.
- **No outing.** Don't infer identity attributes the person hasn't volunteered (orientation, religion, politics beyond direct quotes).
- **Roast the take, not the person's right to have a take.** `这个观点错得离谱` is fine. `连这都不懂还敢发言` is not.
If a target hasn't said anything roastable in this batch (3+ messages but all neutral), give them a one-line affectionate ribbing and move on. Don't manufacture conflict.
### 2.9 Writing order
1. Draft the spicy version freely — don't self-censor mid-sentence.
2. After the body is complete, do a separate audit pass against §2.8.
3. Rewrite or delete any line that crosses a red line.
4. Then read the whole thing once more for tone calibration: is it fun, or is it mean?
**写作顺序:** 先放开写最狠的版本,写完再回头检查红线。不要边写边自我审查,那样只会写出温吞水。
---
@@ -345,5 +269,5 @@ Before writing the digest file, mentally walk through:
6. Every quote uses 「」 and is traceable to a real message?
7. Links inline and complete?
8. No markdown bold/heading/link syntax leaked through?
9. (Roast only) Every roast bullet would pass the §2.8 red-line audit?
9. (Roast only) Every roast bullet would pass the §2 红线 audit?
10. Footer line exact match?