Install
openclaw skills install @cxbjames/daily-biotech-briefing生成每日生物医药早报,包含 GitHub Trending 日榜和5个生物医学方向(干细胞治疗、外泌体治疗、质谱诊断、拉曼诊断、卵巢癌检测)的最新微信文章摘要。每天早上10点通过 cron 触发,发送到用户 QQ 邮箱。使用 web_fetch 抓取 github.com/trending 和 wechat-article-search 技能搜索微信文章,curl 阅读正文。
openclaw skills install @cxbjames/daily-biotech-briefingGenerate and send a daily biotech morning briefing to the user's QQ email.
wechat-article-search skill installed (with cheerio)qqmail skill installed (QQMAIL_USER, QQMAIL_AUTH_CODE configured)web_search to find biotech articles. All WeChat articles MUST come from wechat-article-search only.web_fetch on non-WeChat URLs (news sites, blogs, journals, etc.) for article content.| Source | Allowed? | Notes |
|---|---|---|
web_fetch on github.com/trending | ✅ | Only for GitHub section |
search_wechat_curl.sh JSON output | ✅ | The ONLY source for all 5 topic sections |
web_search for any biotech content | ❌ | NEVER |
web_fetch on non-GitHub URLs | ❌ | NEVER |
| Model knowledge / training data | ❌ | NEVER use to "fill in" content |
When composing the email body, verify EVERY article entry:
https://mp.weixin.qq.com/ (resolved from sogou via resolve_urls.py)datetime field is within the last 48 hoursFetch the GitHub trending daily page:
web_fetch url="https://github.com/trending" maxChars=20000
Parse the top 15 repos from the markdown output. For each repo:
[owner/repo](https://github.com/owner/repo)🚨 CRITICAL: Use search_wechat_curl.sh only. DO NOT use node search_wechat.js — it is BLOCKED by Sogou anti-spider (Node.js TLS fingerprint detected).
See topics.md for exact commands and keyword configurations.
🔄 多关键词轮换策略(Topic 3 质谱诊断 & Topic 4 拉曼诊断)
These niche topics need multiple keyword variants. See topics.md for the full merge script.
| Topic | Keywords |
|---|---|
| 质谱诊断 | 临床质谱, 质谱 临床 检测 |
| 拉曼诊断 | 拉曼 临床 检测, SERS 检测 临床, 表面增强拉曼 检测 |
Wait 3-5 seconds between each search. Curl-based searches are less likely to trigger anti-spider but still need spacing.
If a topic returns 0 articles → mark as empty. Use "今日无相关前沿动态".
Do NOT rely on eyeballing datetimes. You MUST run this exact Python script on every topic's JSON. The script prints every article with its datetime so you can verify the filter is working, and discards articles with missing/bogus datetimes.
import json
from datetime import datetime, timedelta, timezone
TZ = timezone(timedelta(hours=8))
# Load articles
with open('/tmp/wx_<topic>.json') as f:
data = json.load(f)
now = datetime.now(TZ)
cutoff = now - timedelta(hours=48)
print(f"Current time: {now.isoformat()}")
print(f"Cutoff (48h ago): {cutoff.isoformat()}")
print(f"Total articles in file: {len(data['articles'])}")
print()
recent = []
for a in data['articles']:
dt_str = a.get('datetime', '')
# Skip articles with no datetime or clearly wrong datetime
if not dt_str or len(dt_str) < 16:
print(f" ❌ SKIP (no/bad datetime: '{dt_str}'): {a.get('title','?')}")
continue
# Parse datetime in Asia/Shanghai timezone
try:
dt = datetime.strptime(dt_str, '%Y-%m-%d %H:%M:%S').replace(tzinfo=TZ)
except ValueError:
print(f" ❌ SKIP (parse failed: '{dt_str}'): {a.get('title','?')}")
continue
# Check against cutoff
age = now - dt
age_hours = age.total_seconds() / 3600
status = "✅" if dt >= cutoff else f"❌ OLD ({age_hours:.0f}h ago)"
print(f" {status} [{dt_str}] {a.get('title','?')}")
if dt >= cutoff:
recent.append(a)
print(f"\n✅ After 48h filter: {len(recent)} articles kept out of {len(data['articles'])}")
print(f"❌ Discarded: {len(data['articles']) - len(recent)} articles")
Verify the output before proceeding. If you see articles from last month/last year being kept, STOP — the filter is broken and must be fixed before continuing.
Apply filter rules from filter-rules.md using this Python script:
import json, re
REJECT_PATTERNS = [
r'震惊', r'不为人知', r'你绝对想不到', r'太神奇了',
r'!!', r'!!',
r'让.*年轻', r'逆龄', r'冻龄', r'返老还童',
r'点击购买', r'限时优惠', r'免费领取',
r'^什么是', r'^一文读懂', r'^科普', r'^带你了解',
]
with open('/tmp/wx_<topic>.json') as f:
data = json.load(f)
# recent = [...from step 3...]
filtered = []
for a in recent:
title = a.get('title', '')
summary = a.get('summary', '')
text = title + ' ' + summary
if any(re.search(p, text) for p in REJECT_PATTERNS):
print(f" ❌ REJECT: {title}")
continue
filtered.append(a)
print(f" ✅ KEEP: {title}")
print(f"\n✅ After content filter: {len(filtered)} articles")
filtered = filtered[:5] # top 5
If filtered is empty → this topic is EMPTY. Proceed to next topic. Do NOT search for content elsewhere.
⚠️ Sogou redirect links are AES-encrypted. Must resolve to real mp.weixin.qq.com URLs before including in email.
Use the resolve_urls.py script which gets a fresh SNUID cookie from v.sogou.com, then parses JS redirects to extract real WeChat URLs.
python3 ~/.openclaw/workspace/skills/wechat-article-search/scripts/resolve_urls.py \
/tmp/wx_<topic>.json > /tmp/wx_<topic>_resolved.json
The resolved JSON will have a url_mp field for each article with the real mp.weixin.qq.com link.
If resolution fails → this is a hard blocker. Report to user. Do NOT send email with broken sogou links.
⚠️ Sogou redirect links CANNOT be resolved to mp.weixin.qq.com URLs. The sogou redirect service also triggers anti-spider. Use the search result summaries as the content source.
For each filtered article, compose a 2-3 sentence summary in Chinese based on the title + summary fields from the JSON output. The search summaries are detailed enough to create informative briefs.
For the article link, use the sogou redirect URL from the search results (users can open it in a browser where the CAPTCHA is manually solvable). Format as:
📎 [原文链接](https://weixin.sogou.com/link?url=...)
Follow the email template in email-template.md.
Critical rules:
title + summary fieldsurl_mp link: 📎 https://mp.weixin.qq.com/s/...> 今日无相关前沿动态Before sending, run this Python validation:
import re
body = """<paste full email body here>"""
# Check all non-GitHub URLs are mp.weixin.qq.com
urls = re.findall(r'https?://[^\s\u4e00-\u9fff\u3000-\u303f\uff00-\uffef\)]+', body)
for u in urls:
if 'github.com' in u:
continue
if 'mp.weixin.qq.com' not in u:
print(f"❌ NON-WECHAT URL FOUND: {u[:100]}")
print("REMOVE this entry before sending!")
exit(1)
print("✅ All article links are mp.weixin.qq.com (GitHub links OK)")
# Check every topic section has either articles or "无动态"
topics = ['干细胞治疗', '外泌体治疗', '质谱诊断', '拉曼诊断', '卵巢癌检测']
for topic in topics:
idx = body.find(f'🧬 {topic}')
if idx == -1:
print(f"⚠️ Missing topic section: {topic}")
continue
next_idx = min(
[body.find(f'🧬 {t}', idx + 1) for t in topics if body.find(f'🧬 {t}', idx + 1) > 0] + [len(body)]
)
section = body[idx:next_idx]
has_article = '📄' in section
has_empty = '今日无相关前沿动态' in section
if has_article:
print(f"✅ {topic}: has articles")
elif has_empty:
print(f"ℹ️ {topic}: correctly marked empty")
else:
print(f"❌ {topic}: no articles AND no '无动态' notice!")
print("\n✅ Validation complete")
If validation shows any ❌ → FIX THEM before sending. Don't force-send a broken email.
Send promptly — links expire in ~15 minutes.
python3 ~/.openclaw/workspace/skills/qqmail/scripts/qqmail.py send \
--to "$QQMAIL_USER" \
--subject "生物医药每日早报|YYYY-MM-DD" \
--body "<full_email_body>"
🚨 CRITICAL: 收件人硬性约束
$QQMAIL_USER 环境变量作为收件人echo "收件人: $QQMAIL_USER" 确认环境变量已加载$QQMAIL_USER 为空,STOP — 不要发送发送后必须验证邮件已到达收件箱:
export $(grep -v '^#' ~/.openclaw/.env | xargs)
python3 ~/.openclaw/workspace/skills/qqmail/scripts/qqmail.py inbox --limit 5
检查前 5 封邮件中是否存在刚刚发送的"生物医药每日早报|YYYY-MM-DD"。
$QQMAIL_USER 为空导致无法发送 → 记录为失败,不伪造成功Include in email footer:
⚠️ About search links:
summary field (detailed enough for briefing)search_wechat_curl.sh (NOT node search_wechat.js)