Skill: huuuwnnn-droid/wechat-mp-plus (version 1.0.0)

# Skill: wechat-mp-publisher 微信公众号自动化管理。支持草稿发布、菜单管理、自动回复查询、Markdown排版。 ## Triggers - "发布公众号文章", "微信公众号", "公众号草稿", "公众号菜单" - "wechat mp", "publish article", "mp draft" ## Prerequisites - 环境变量 `WECHAT_MP_APP_ID` 和 `WECHAT_MP_APP_SECRET`，或 `.secrets/wechat_mp.env` 文件 - Python 3.7+ ## Usage 所有脚本位于 `scripts/` 目录。工作目录应为 skill 根目录。 ### 一键发布（最常用） 输入一个 Markdown 文件 + 封面图 → 自动完成图片上传、HTML转换、创建草稿。 ```bash cd skills/wechat-mp-publisher python3 scripts/publish.py <markdown文件> <封面图> <标题> [作者] [摘要] [主题] ``` 主题可选：`default`（清爽简约）、`elegant`（文艺范）、`dark`（暗色科技） ### 草稿管理 ```bash # 获取Token（测试连通性） python3 scripts/wechat_mp.py token # 上传图片素材（永久） python3 scripts/wechat_mp.py upload <图片路径> # 上传文内图片 python3 scripts/wechat_mp.py upload-article-image <图片路径> # 创建草稿 python3 scripts/wechat_mp.py draft-create '{"title":"标题","author":"作者","content":"<p>内容HTML</p>","thumb_media_id":"xxx","digest":"摘要"}' # 列出草稿 python3 scripts/wechat_mp.py draft-list [offset] [count] # 删除草稿 python3 scripts/wechat_mp.py draft-delete <media_id> ``` ### 菜单管理 ```bash # 查询菜单 python3 scripts/menu.py get # 创建菜单 python3 scripts/menu.py create '{"button":[{"type":"view","name":"官网","url":"https://example.com"}]}' # 删除菜单 python3 scripts/menu.py delete ``` ### 自动回复查询 ```bash python3 scripts/wechat_mp.py autoreply ``` ### Markdown转HTML ```bash python3 scripts/md2html.py <markdown文件> [主题名] ``` ## Notes - Token自动缓存2小时，无需手动管理 - 所有API错误有中文提示 - 图片支持永久素材上传和文内图片上传两种模式

path: README.md
content: # wechat-mp-publisher

微信公众号自动化管理 OpenClaw Skill - 草稿发布、菜单管理、自动回复、Markdown排版

## 功能

- **草稿箱管理**：上传素材、创建/列出/删除草稿
- **自定义菜单**：查询/创建/删除菜单
- **自动回复查询**：查看关注回复和关键词回复规则
- **Markdown排版**：Markdown转微信HTML，内置3套主题（default/elegant/dark）
- **一键发布**：Markdown文件 → 图片上传 → HTML转换 → 创建草稿

## 配置

设置环境变量：

```bash
export WECHAT_MP_APP_ID="你的AppID"
export WECHAT_MP_APP_SECRET="你的AppSecret"
```

或在 `.secrets/wechat_mp.env` 中配置：

```
WECHAT_MP_APP_ID=你的AppID
WECHAT_MP_APP_SECRET=你的AppSecret
```

## 快速开始

```bash
# 获取Token
python3 scripts/wechat_mp.py token

# 一键发布
python3 scripts/publish.py article.md cover.jpg "文章标题" "作者" "摘要" elegant

# 菜单管理
python3 scripts/menu.py get
python3 scripts/menu.py delete

# 查看自动回复
python3 scripts/wechat_mp.py autoreply

# Markdown转HTML
python3 scripts/md2html.py article.md dark
```

## 主题

| 主题 | 说明 |
|------|------|
| default | 清爽简约，微信绿色调 |
| elegant | 文艺范，衬线字体+红色点缀 |
| dark | 暗色科技感，适合技术文章 |

## 依赖

- Python 3.7+
- `markdown` 库（脚本自动安装）

## 作者

huuuwnnn-droid


path: package.json
content: {
  "name": "wechat-mp-publisher",
  "version": "1.0.0",
  "description": "微信公众号自动化管理 OpenClaw Skill - 草稿发布、菜单管理、自动回复、Markdown排版",
  "author": "huuuwnnn-droid",
  "license": "MIT",
  "keywords": ["wechat", "mp", "公众号", "openclaw", "skill"]
}


path: scripts/md2html.py
content: #!/usr/bin/env python3
"""Markdown转微信公众号兼容HTML，内置多套主题"""

import os
import re
import sys

# 自动安装 markdown 库
try:
    import markdown
except ImportError:
    import subprocess
    subprocess.check_call([sys.executable, "-m", "pip", "install", "markdown", "-q", "--break-system-packages"])
    import markdown

THEMES_DIR = os.path.join(os.path.dirname(__file__), "..", "themes")
AVAILABLE_THEMES = ["default", "elegant", "dark"]


def load_theme(name="default"):
    """加载主题CSS"""
    if name not in AVAILABLE_THEMES:
        print(f"⚠️ 未知主题 '{name}'，使用 default", file=sys.stderr)
        name = "default"
    path = os.path.join(THEMES_DIR, f"{name}.css")
    if not os.path.exists(path):
        return ""
    with open(path) as f:
        return f.read()


def md_to_html(md_text, theme="default"):
    """将Markdown转为微信公众号兼容HTML（内联样式）"""
    # 转换markdown
    html = markdown.markdown(
        md_text,
        extensions=["extra", "codehilite", "toc", "nl2br"],
        extension_configs={"codehilite": {"css_class": "code"}},
    )
    css = load_theme(theme)
    # 微信不支持 <style> 标签，需要内联样式
    # 这里我们把CSS包裹在section里，微信编辑器会保留内联style
    wrapped = f'<section style="max-width:100%;box-sizing:border-box;word-wrap:break-word;">\n'
    if css:
        # 将CSS规则转为内联style的方式：用section包裹并在首部嵌入style
        # 微信草稿接口实际支持<style>在content中
        wrapped += f"<style>{css}</style>\n"
    wrapped += html
    wrapped += "\n</section>"
    return wrapped


def convert_file(md_path, theme="default"):
    """转换文件"""
    with open(md_path, encoding="utf-8") as f:
        md_text = f.read()
    return md_to_html(md_text, theme)


def find_local_images(md_text):
    """找出markdown中的本地图片路径"""
    # ![alt](path) 格式
    pattern = r'!\[([^\]]*)\]\(([^)]+)\)'
    images = []
    for alt, src in re.findall(pattern, md_text):
        if not src.startswith(("http://", "https://", "data:")):
            images.append(src)
    return images


def replace_image_urls(md_text, url_map):
    """替换markdown中的图片路径为新URL"""
    def replacer(m):
        alt = m.group(1)
        src = m.group(2)
        new_url = url_map.get(src, src)
        return f"![{alt}]({new_url})"
    return re.sub(r'!\[([^\]]*)\]\(([^)]+)\)', replacer, md_text)


def main():
    if len(sys.argv) < 2:
        print("用法: md2html.py <markdown文件> [主题名]")
        print(f"可用主题: {', '.join(AVAILABLE_THEMES)}")
        sys.exit(1)
    md_path = sys.argv[1]
    theme = sys.argv[2] if len(sys.argv) > 2 else "default"
    html = convert_file(md_path, theme)
    print(html)


if __name__ == "__main__":
    main()


path: scripts/menu.py
content: #!/usr/bin/env python3
"""菜单管理 - 查询、创建、删除自定义菜单"""

import json
import sys
from wechat_mp import get_access_token, _http_get, _http_post_json, _api_error


def get_menu():
    """查询当前菜单"""
    [REDACTED_SECRET])
    url = f"https://api.weixin.qq.com/cgi-bin/get_current_selfmenu_info?access_token={token}"
    data = _http_get(url)
    if "errcode" in data and data["errcode"] != 0:
        _api_error(data)
        return None
    is_open = data.get("is_menu_open", 0)
    print(f"📋 菜单状态: {'已开启' if is_open else '未开启'}")
    info = data.get("selfmenu_info", {})
    buttons = info.get("button", [])
    for btn in buttons:
        name = btn.get("name", "未命名")
        btype = btn.get("type", "")
        sub = btn.get("sub_button", {}).get("list", [])
        if sub:
            print(f"  📁 {name}")
            for s in sub:
                print(f"    - {s.get('name', '')} [{s.get('type', '')}]")
        else:
            print(f"  🔘 {name} [{btype}]")
    return data


def create_menu(menu_data):
    """创建菜单。menu_data: {"button": [...]}"""
    [REDACTED_SECRET])
    url = f"https://api.weixin.qq.com/cgi-bin/menu/create?access_token={token}"
    data = _http_post_json(url, menu_data)
    if _api_error(data):
        return False
    print("✅ 菜单创建成功")
    return True


def delete_menu():
    """删除菜单"""
    [REDACTED_SECRET])
    url = f"https://api.weixin.qq.com/cgi-bin/menu/delete?access_token={token}"
    data = _http_get(url)
    if _api_error(data):
        return False
    print("✅ 菜单已删除")
    return True


def main():
    if len(sys.argv) < 2:
        print("用法: menu.py <命令> [参数]")
        print("命令: get, create <json>, delete")
        print()
        print("示例JSON:")
        print(json.dumps({"button": [
            {"type": "click", "name": "今日歌曲", "key": "V1001_TODAY_MUSIC"},
            {"type": "view", "name": "官网", "url": "https://example.com"},
            {"name": "更多", "sub_button": [
                {"type": "click", "name": "帮助", "key": "HELP"},
            ]},
        ]}, ensure_ascii=False, indent=2))
        sys.exit(1)
    cmd = sys.argv[1]
    if cmd == "get":
        get_menu()
    elif cmd == "create":
        menu_data = json.loads(sys.argv[2])
        create_menu(menu_data)
    elif cmd == "delete":
        delete_menu()
    else:
        print(f"未知命令: {cmd}", file=sys.stderr)
        sys.exit(1)


if __name__ == "__main__":
    main()


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