Skill: chenhao11235813/wechat-article-explainer (version 1.0.1)

--- name: wechat-article-explainer version: 1.0.1 description: 微信公众号文章通俗内容解释技能。当用户需要总结或解释微信公众号链接或url时使用这个技能。技能包含：根据用户提供的链接或URL，读取链接内容，用通俗的语言解释内容。 --- # 微信公众号文章通俗内容解释技能 ## 概述 这个 Skill 用于阅读和用通俗的语言总结微信公众号链接的内容。 ## 使用方法 当用户提供微信公众号文章链接时，按照以下步骤执行： ### 1. 验证链接格式 确认是有效的微信公众号链接，格式如下： - `https://mp.weixin.qq.com/s/xxx` - `https://mp.weixin.qq.com/s?xxx` ### 2. 执行 Python 工具获取文章内容 使用 `wechat_reader.py` 工具抓取文章： ```bash python3 scripts/wechat_reader.py "<文章链接>" ``` ### 3. 总结文章内容 根据获取的文章内容，提供通俗内容解释： - **文章标题**： - **作者/来源**： - **发布时间**： - **内容总结**（通俗内容解释）： ## 技术细节 - **Python 依赖**： - `requests`（基础 HTTP 请求） - `playwright`（浏览器模拟，更稳定） - **安装依赖**： ```bash pip install requests # 或使用浏览器模式 pip install playwright playwright install chromium ``` - **使用浏览器模式**：默认使用 Playwright，更稳定但需要安装浏览器 - **不使用浏览器**：`python3 wechat_reader.py <url> --no-browser` ## 注意事项 - 微信文章可能有防盗链限制，直接 HTTP 请求可能失败 - 浏览器模式更稳定但速度较慢 - 如果抓取失败，提示用户在微信中打开文章并复制内容

path: _meta.json
content: ownerId: kn718pat409d746z7rgdxawq9s82pv87
slug: wechat-article-explainer
version: 1.0.0
publishedAt: 1773226822424

path: requirements.txt
content: requests>=2.28.0
playwright>=1.40.0

path: README.md
content: # 微信公众号文章总结工具

## 项目结构

```
wechat-article-explainer/
├── scripts/wechat_reader.py        # Python 解析工具
├── SKILL.md #  Skill 定义
├── requirements.txt        # Python 依赖
└── README.md              # 说明文档
```

## 安装

```bash
# 安装 Python 依赖
pip install -r requirements.txt

# 安装 Playwright 浏览器（可选但推荐）
playwright install chromium
```

## 使用方法

### 命令行直接使用

```bash
# 使用浏览器模式（推荐，更稳定）
python3 wechat_reader.py "https://mp.weixin.qq.com/s/xxxx"

# 不使用浏览器
python3 wechat_reader.py "https://mp.weixin.qq.com/s/xxxx" --no-browser

# 输出到文件
python3 wechat_reader.py "https://mp.weixin.qq.com/s/xxxx" -o result.json
```

### Openclaw 中使用

1. 将 `SKILL.md` 放到 openclaw 的 skill 目录
2. 使用 `/wechat-article-explainer` 命令调用
3. 提供微信公众号链接，skill 会自动抓取并总结

## Skill 调用流程

1. 用户提供微信文章链接
2. Skill 调用 `wechat_reader.py` 抓取文章
3. 解析返回的 JSON 内容
4. 提供结构化总结

## 输出格式

```json
{
  "success": true,
  "url": "原始链接",
  "title": "文章标题",
  "author": "作者",
  "publish_date": "发布时间",
  "source": "来源公众号",
  "content": "文章内容"
}
```

## 注意事项

- 微信文章可能有防盗链限制
- 浏览器模式成功率更高
- 如遇抓取失败，建议用户在微信中打开文章并复制内容


path: scripts/wechat_reader.py
content: #!/usr/bin/env python3
"""
微信公众号文章读取工具
用于解析和提取微信公众号链接的内容
"""

import argparse
import json
import re
import sys
from pathlib import Path

# 尝试导入可选依赖
try:
    from playwright.sync_api import sync_playwright
    PLAYWRIGHT_AVAILABLE = True
except ImportError:
    PLAYWRIGHT_AVAILABLE = False

try:
    import requests
    REQUESTS_AVAILABLE = True
except ImportError:
    REQUESTS_AVAILABLE = False


def parse_wechat_url(url: str) -> dict:
    """
    解析微信 URL，提取关键信息

    Args:
        url: 微信公众号文章链接

    Returns:
        包含 URL 信息的字典
    """
    # 微信文章 URL 模式
    patterns = [
        r'https?://mp\.weixin\.qq\.com/s/([a-zA-Z0-9_-]+)',  # 直接文章 ID
        r'https?://mp\.weixin\.qq\.com/s\?([a-zA-Z0-9&=_-]+)',  # 带参数的 URL
        r'https?://mp\.weixin\.qq\.com/profile/([a-zA-Z0-9_-]+)',  # 公众号主页
    ]

    result = {
        "original_url": url,
        "type": "unknown",
        "article_id": None,
        "params": {}
    }

    # 提取文章 ID
    match = re.search(r'/s/([a-zA-Z0-9_-]+)', url)
    if match:
        result["type"] = "article"
        result["article_id"] = match.group(1)

    # 提取 URL 参数
    if '?' in url:
        query_string = url.split('?')[1]
        params = {}
        for param in query_string.split('&'):
            if '=' in param:
                key, value = param.split('=', 1)
                params[key] = value
        result["params"] = params

    return result


def fetch_article_with_playwright(url: str) -> dict:
    """
    使用 Playwright 浏览器模拟访问微信文章

    Args:
        url: 微信公众号文章链接

    Returns:
        包含文章内容的字典
    """
    if not PLAYWRIGHT_AVAILABLE:
        return {
            "success": False,
            "error": "Playwright not installed. Run: pip install playwright && playwright install chromium"
        }

    result = {
        "success": False,
        "url": url,
        "title": None,
        "content": None,
        "author": None,
        "publish_date": None,
        "source": None,
        "error": None
    }

    try:
        with sync_playwright() as p:
            browser = p.chromium.launch(headless=True)
            page = browser.new_page()

            # 设置更真实的浏览器环境
            page.set_extra_http_headers({
                "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
            })

            page.goto(url, wait_until="networkidle", timeout=30000)

            # 等待页面加载完成
            page.wait_for_timeout(3000)

            # 提取标题
            title_element = page.query_selector('#activity-name, .article_title, h1')
            if title_element:
                result["title"] = title_element.inner_text().strip()

            # 提取公众号来源 (作者和来源在同一区域)
            meta_elements = page.query_selector_all('.rich_media_meta')
            for el in meta_elements:
                text = el.inner_text().strip()
                if text:
                    # 判断是日期还是来源
                    if '年' in text and '月' in text:
                        result["publish_date"] = text
                    elif not result["source"]:
                        result["source"] = text

            # 提取作者（有时单独显示）
            author_element = page.query_selector('#js_author, .author_name, .author')
            if author_element:
                result["author"] = author_element.inner_text().strip()
            # 如果没有作者，用来源作为作者
            if not result["author"] and result["source"]:
                result["author"] = result["source"]

            # 提取发布时间（备选方案）
            if not result["publish_date"]:
                date_element = page.query_selector('#js_publish_time, .publish_time, .date')
                if date_element:
                    result["publish_date"] = date_element.inner_text().strip()

            # 提取文章内容
            content_element = page.query_selector('#js_content, .article_content, .content')
            if content_element:
                result["content"] = content_element.inner_text().strip()

            if result["title"] or result["content"]:
                result["success"] = True

            browser.close()

    except Exception as e:
        result["error"] = str(e)

    return result


def fetch_article_with_requests(url: str) -> dict:
    """
    使用 requests 直接请求微信文章（可能受防盗链限制）

    Args:
        url: 微信公众号文章链接

    Returns:
        包含文章内容的字典
    """
    if not REQUESTS_AVAILABLE:
        return {
            "success": False,
            "error": "requests not installed. Run: pip install requests"
        }

    result = {
        "success": False,
        "url": url,
        "title": None,
        "content": None,
        "error": None
    }

    headers = {
        "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
        "Referer": "https://mp.weixin.qq.com/",
    }

    try:
        response = requests.get(url, headers=headers, timeout=30)
        response.encoding = 'utf-8'

        if response.status_code == 200:
            html = response.text

            # 提取标题
            title_match = re.search(r'<h2[^>]*id=["\']?activity-name["\']?[^>]*>([^<]+)</h2>', html)
            if not title_match:
                title_match = re.search(r'<title>([^<]+)</title>', html)
            if title_match:
                result["title"] = title_match.group(1).strip()

            # 提取文章内容
            content_match = re.search(r'<div[^>]*id=["\']?js_content["\']?[^>]*>([\s\S]*?)</div>', html)
            if content_match:
                # 清理 HTML 标签
                content_html = content_match.group(1)
                content_text = re.sub(r'<[^>]+>', '', content_html)
                content_text = re.sub(r'\s+', ' ', content_text).strip()
                result["content"] = content_text
                result["success"] = True
        else:
            result["error"] = f"HTTP {response.status_code}"

    except Exception as e:
        result["error"] = str(e)

    return result


def fetch_article(url: str, use_browser: bool = True) -> dict:
    """
    获取微信文章内容

    Args:
        url: 微信公众号文章链接
        use_browser: 是否使用浏览器模式（更稳定但更慢）

    Returns:
        包含文章内容的字典
    """
    # 先解析 URL
    url_info = parse_wechat_url(url)
    print(f"解析 URL: {json.dumps(url_info, ensure_ascii=False, indent=2)}", file=sys.stderr)

    if use_browser and PLAYWRIGHT_AVAILABLE:
        return fetch_article_with_playwright(url)
    else:
        return fetch_article_with_requests(url)


def main():
    """主函数"""
    parser = argparse.ArgumentParser(description="微信公众号文章读取工具")
    parser.add_argument("url", help="微信公众号文章链接")
    parser.add_argument("--no-browser", action="store_true", help="不使用浏览器，直接用 HTTP 请求")
    parser.add_argument("--output", "-o", help="输出文件路径 (JSON 格式)")

    args = parser.parse_args()

    # 获取文章
    result = fetch_article(args.url, use_browser=not args.no_browser)

    # 输出结果
    if args.output:
        with open(args.output, 'w', encoding='utf-8') as f:
            json.dump(result, f, ensure_ascii=False, indent=2)
        print(f"结果已保存到: {args.output}", file=sys.stderr)
    else:
        print(json.dumps(result, ensure_ascii=False, indent=2))

    # 返回状态码
    sys.exit(0 if result.get("success") else 1)


if __name__ == "__main__":
    main()

path: .clawhub/origin.json
content: version: 1
registry: https://clawhub.ai
slug: wechat-article-explainer
installedVersion: 1.0.0
installedAt: 1773227236775

Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (MEDIUM)