Skill: taoj2025/wechat-content-pipeline (version 1.0.1)

--- name: wechat-content-pipeline description: 公众号内容发布自动化技能。整合用户自有工具，实现内容生成→配图→三步预检→草稿箱直接发布的全流程自动化。内置敏感词过滤、重复检测、格式优化，每篇文章自动配图5张以上。 version: 1.1.0 author: TJMtaotao tags: [公众号, 内容发布, 自动化, 图文并茂, 敏感词过滤, 自动配图] --- # 公众号内容发布自动化技能 ## 功能概述 一款企业级公众号内容发布自动化工具，实现： - ✅ AI智能选题与内容生成 - ✅ **自动配图（每篇文章5张以上）** - ✅ 三步预检（排版+敏感词+重复检测） - ✅ 直接发布到公众号草稿箱 - ✅ 全流程微信通知 ## 工具矩阵 ### 内容生成（优先级排序） | 工具 | 用途 | 状态 | |------|------|------| | **MiniMax API** | AI内容+图片生成 | ✅ 已配置（50次/天） | | **illustrated-ppt** | 图文并茂PPT/文章 | ✅ 已安装 | | **free-image-skill** | 免费图库配图（自动下载） | ✅ 已安装 | | **multi-search-engine** | 热点追踪 | ✅ 已安装 | | **news-aggregator-skill** | 资讯聚合 | ✅ 已安装 | ### 发布工具 | 工具 | 用途 | 状态 | |------|------|------| | **yaniw-wechat-publisher** | 公众号直接发布 | ✅ 已安装 | ## 工作流架构 ``` ┌────────────────────────────────────────────────────────────────────┐ │ 公众号内容发布全流程自动化 │ ├────────────────────────────────────────────────────────────────────┤ │ │ │ ┌────────────┐ ┌────────────┐ ┌────────────┐ ┌────────┐ │ │ │ ①选题 │───▶│ ②生成 │───▶│ ③配图 │───▶│ ④预检 │ │ │ │ │ │ │ │ │ │ │ │ │ │ 热点追踪 │ │ AI写稿 │ │自动下载 │ │排版优化│ │ │ │ 素材搜集 │ │ 内容创作 │ │5张+/篇 │ │敏感词 │ │ │ │ 确定主题 │ │ │ │智能匹配 │ │重复检测│ │ │ └────────────┘ └────────────┘ └────────────┘ └────┬───┘ │ │ │ │ │ ▼ │ │ ┌────────┐ │ │ │⑤发布 │ │ │ │草稿箱 │ │ │ │微信通知│ │ │ └────────┘ │ │ │ │ 预检失败 ──────────────────────────────────▶ 人工审核队列 │ │ │ └────────────────────────────────────────────────────────────────────┘ ``` ## 核心流程详解 ### ① 选题规划 ``` 输入: 热点话题 / 指定主题 输出: 当日选题清单（8篇） 工具: multi-search-engine + news-aggregator-skill ``` ### ② 内容生成 ``` 输入: 选题 输出: 完整文章（Markdown格式） 工具: MiniMax API / illustrated-ppt ``` ### ③ 自动配图（核心特性） ``` 输入: 文章内容 输出: 每篇文章5张以上配图，自动插入正文 配图策略: • 每篇文章: 5-8张配图（根据内容长度动态调整） • 图片来源: unsplash / pexels / pixabay（免费图库自动下载） • 配图位置: 与内容段落匹配，图文交替排布 • 备选方案: MiniMax API（额度充足时优先AI生成） 配图规则: • 封面图: 蓝色科技风格，1张 • 内容图: 3-7张，与段落内容相关 • 图片说明: 每张图片下方添加简短说明 ``` ### ④ 三步预检（必过） ``` ┌──────────────────────────────────────┐ │ 步骤1: 排版优化 │ │ • 标题层级检查 │ │ • 段落格式检查 │ │ • 字数达标检查（≥500字） │ ├──────────────────────────────────────┤ │ 步骤2: 敏感词检测 ⚠️ 重点 │ │ • 政治敏感词过滤 │ │ • 色情暴力内容过滤 │ │ • 虚假信息过滤 │ │ • 广告法违规检测 │ ├──────────────────────────────────────┤ │ 步骤3: 重复检测 │ │ • 与已发布内容比对 │ │ • 内容指纹查重 │ └──────────────────────────────────────┘ 结果: 通过 → 发布 / 失败 → 人工审核 ``` ### ⑤ 发布 ``` 输入: 预检通过的文章（含配图） 输出: 草稿箱 + 微信通知 工具: yaniw-wechat-publisher ``` ## 自动配图详细说明 ### 配图数量标准 | 文章类型 | 最少配图 | 推荐配图 | 说明 | |----------|----------|----------|------| | 短文（<1000字） | 5张 | 5-6张 | 基础配图 | | 中等（1000-2000字） | 6张 | 6-7张 | 增加图表 | | 长文（>2000字） | 7张 | 8张以上 | 深度内容 | ### 配图位置规则 ``` 文章结构: ┌────────────────────────────────────┐ │ 封面图片（蓝色科技风） │ ├────────────────────────────────────┤ │ 标题 │ ├────────────────────────────────────┤ │ 导语/引言 │ ├────────────────────────────────────┤ │ [图片1] + 图片说明 │ │ 第一段正文 │ ├────────────────────────────────────┤ │ 第二段正文 │ │ [图片2] + 图片说明 │ ├────────────────────────────────────┤ │ ...（图文交替，比例约1:2） │ ├────────────────────────────────────┤ │ 总结 │ ├────────────────────────────────────┤ │ [AI生成标识] │ │ 作者：TJMtaotao │ │ 发表于：MEITUSTYLE │ └────────────────────────────────────┘ ``` ### 图片下载流程 ```python # 自动配图脚本逻辑 def auto_download_images(article_path, keywords): """ 1. 分析文章内容，提取关键词 2. 根据关键词搜索免费图库 3. 自动下载图片到文章目录 4. 在正文中插入图片引用 """ # 优先使用free-image-skill下载 # 备选：MiniMax API生成 pass ``` ## 使用命令 ```bash # 完整流程（选题→生成→配图→预检→发布） python3 scripts/run_pipeline.py --topic 深度学习 --count 8 # 分步执行 python3 scripts/run_pipeline.py --step generate # 生成内容 python3 scripts/run_pipeline.py --step images # 下载配图（自动匹配） python3 scripts/run_pipeline.py --step check # 三步预检 python3 scripts/run_pipeline.py --step publish # 发布 # 指定主题和数量 python3 scripts/run_pipeline.py --topic Python数据分析 --count 4 # 仅配图（针对已生成的文章） python3 scripts/run_pipeline.py --step images ``` ## 定时任务 ```bash # 每天19:30自动执行完整流程 30 19 * * * cd /root/.openclaw/workspace/skills/wechat-content-pipeline && \ export MINIMAX_API_KEY="YOUR_MINIMAX_API_KEY" && \ python3 scripts/run_pipeline.py --topic 深度学习 --count 8 ``` ## 输出规范 | 项目 | 规范 | |------|------| | 文章格式 | Markdown | | **配图数量** | **≥5张/篇（自动确保）** | | 配图位置 | 封面1张 + 内容3-7张（图文交替） | | 配图来源 | 免费图库（自动下载）/ MiniMax AI | | 署名 | TJMtaotao | | AI标识 | 末尾添加"本文由AI辅助创作" | | 封面风格 | 蓝色科技风 | ## 敏感词过滤说明 ⚠️ **所有发布内容必须通过敏感词检测** 过滤范围： - 政治敏感词 - 色情暴力内容 - 虚假信息 - 广告法违规词 - 侵权内容 检测失败 → 进入人工审核队列，等待确认后发布 ## MiniMax API额度 | 项目 | 说明 | |------|------| | 额度 | 50次/天 | | 重置时间 | 每日00:00（GMT+8） | | 备用方案 | 免费图库自动切换（无额度限制） | ## 目录结构 ``` wechat-content-pipeline/ ├── SKILL.md ← 本文件 ├── scripts/ │ ├── run_pipeline.py ← 主流程脚本 │ └── auto_images.py ← 自动配图脚本（自动下载+插入） ├── articles/ ← 文章目录（自动创建） │ ├── 01-文章标题/ │ │ ├── 正文.md ← 文章内容（含图片引用） │ │ └── images/ ← 配图目录（自动下载） │ └── ... ├── .env ← API配置 └── references/ └── config.md ← 配置说明 ``` ## 文章目录结构（自动生成） ``` articles/ └── 01-文章标题/ ├── 正文.md ← Markdown格式，含图片引用 └── images/ ← 自动下载的图片 ├── cover.jpg ← 封面图 ├── 001.jpg ← 内容图1 ├── 002.jpg ← 内容图2 └── ... ```

path: .env
content: # 环境变量配置（请用户自行填写）
MINIMAX_API_KEY=YOUR_MINIMAX_API_KEY_HERE


path: scripts/auto_images.py
content: #!/usr/bin/env python3
"""
自动配图脚本 v1.1
功能：
1. 读取每篇文章内容
2. 根据关键词搜索免费图库
3. 自动下载图片到文章目录
4. 在正文中插入图片引用
5. 确保每篇文章≥5张配图
"""

import os
import sys
import json
import re
import time
import hashlib
import requests
from pathlib import Path

# 文章目录
ARTICLES_DIR = Path(__file__).parent.parent / "articles"

# 免费图库（可用的）
FREE_IMAGE_SOURCES = [
    "https://picsum.photos/800/600",      # Picsum免费图库
    "https://picsum.photos/800/400",      # 横向
    "https://picsum.photos/600/800",      # 纵向
]

# 每篇文章最少配图数量
MIN_IMAGES_PER_ARTICLE = 5

def get_article_keywords(article_path):
    """从文章中提取关键词"""
    with open(article_path, 'r', encoding='utf-8') as f:
        content = f.read()
    
    # 提取标题作为主要关键词
    title_match = re.search(r'^#\s+(.+)$', content, re.MULTILINE)
    title = title_match.group(1) if title_match else ""
    
    # 提取小标题作为次要关键词
    headers = re.findall(r'^##\s+(.+)$', content, re.MULTILINE)
    
    # 提取技术术语作为关键词
    tech_terms = re.findall(r'\b(Python|深度学习|神经网络|CNN|RNN|Transformer|PyTorch|TensorFlow|AI|机器学习|数据|模型|训练)\b', content)
    
    keywords = [title] + headers[:3] + list(set(tech_terms))[:5]
    return [k for k in keywords if k], content

def generate_image_url(keyword, index):
    """生成图片URL（使用Picsum）"""
    # 使用关键词的hash生成不同的随机种子
    seed_str = f"{keyword}_{index}"
    seed = int(hashlib.md5(seed_str.encode()).hexdigest()[:8], 16) % 1000
    
    # 随机选择横向或纵向
    if index % 3 == 0:
        width, height = 800, 600
    elif index % 3 == 1:
        width, height = 800, 400
    else:
        width, height = 600, 800
    
    return f"https://picsum.photos/seed/{seed}/{width}/{height}"

def download_image(url, save_path):
    """下载图片"""
    try:
        headers = {
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
        }
        response = requests.get(url, headers=headers, timeout=30, allow_redirects=True)
        if response.status_code == 200 and len(response.content) > 10000:
            with open(save_path, 'wb') as f:
                f.write(response.content)
            return True
    except Exception as e:
        print(f"  下载失败: {e}")
    return False

def insert_images_to_article(article_path, images_info):
    """将图片插入文章内容"""
    with open(article_path, 'r', encoding='utf-8') as f:
        content = f.read()
    
    # 构建图片引用
    image_refs = []
    for i, img in enumerate(images_info, 1):
        keyword_short = img["keyword"][:30] if len(img["keyword"]) > 30 else img["keyword"]
        ref = f'\n![{keyword_short}](images/{img["filename"]})\n'
        image_refs.append(ref)
    
    # 在段落之间插入图片（每隔2-3个段落插入一张）
    paragraphs = content.split('\n\n')
    new_content = []
    img_idx = 0
    inserted_count = 0
    
    for i, para in enumerate(paragraphs):
        new_content.append(para)
        # 每隔2个段落插入一张图片
        if (i + 1) % 2 == 0 and img_idx < len(image_refs):
            new_content.append(image_refs[img_idx])
            img_idx += 1
            inserted_count += 1
    
    # 如果图片数量不足，在结尾添加剩余图片
    while img_idx < len(image_refs):
        new_content.append(image_refs[img_idx])
        img_idx += 1
        inserted_count += 1
    
    # 添加AI生成标识（如果没有的话）
    if '本文由AI辅助创作' not in content:
        new_content.append('\n\n---\n**本文由AI辅助创作**\n**作者：TJMtaotao**\n**发表于：MEITUSTYLE**\n')
    
    return '\n\n'.join(new_content), inserted_count

def process_article(article_dir):
    """处理单篇文章"""
    article_path = article_dir / "正文.md"
    images_dir = article_dir / "images"
    
    if not article_path.exists():
        print(f"  ⚠️ 文章不存在: {article_path}")
        return False
    
    # 确保图片目录存在
    images_dir.mkdir(exist_ok=True)
    
    print(f"\n  📄 处理文章: {article_dir.name}")
    
    # 提取关键词
    keywords, content = get_article_keywords(article_path)
    print(f"  🔍 关键词: {', '.join(keywords[:5])}")
    
    # 计算需要下载的图片数量
    existing_images = list(images_dir.glob("*.jpg")) + list(images_dir.glob("*.png")) + list(images_dir.glob("*.jpeg"))
    needed_images = max(0, MIN_IMAGES_PER_ARTICLE - len(existing_images))
    
    print(f"  📊 已有图片: {len(existing_images)}, 需要下载: {needed_images}")
    
    if needed_images == 0:
        print(f"  ✅ 已满足配图要求")
        return True
    
    # 下载图片
    downloaded = []
    for i in range(needed_images):
        # 使用关键词和索引生成不同的图片URL
        keyword = keywords[i % len(keywords)] if keywords else "ai"
        url = generate_image_url(keyword, i)
        
        filename = f"image_{i+1:03d}.jpg"
        save_path = images_dir / filename
        
        print(f"  📥 下载图片 {i+1}/{needed_images}: {keyword[:20]}...")
        
        if download_image(url, save_path):
            print(f"  ✅ 成功: {filename}")
            downloaded.append({
                "keyword": keyword,
                "filename": filename,
                "source": "picsum"
            })
            time.sleep(0.3)  # 避免请求过快
        else:
            print(f"  ⚠️ 下载失败，使用替代URL...")
            # 尝试不带seed的URL
            alt_url = FREE_IMAGE_SOURCES[i % len(FREE_IMAGE_SOURCES)]
            if download_image(alt_url, save_path):
                downloaded.append({
                    "keyword": keyword,
                    "filename": filename,
                    "source": "picsum"
                })
                print(f"  ✅ 替代成功: {filename}")
    
    # 更新文章内容
    if downloaded:
        updated_content, inserted = insert_images_to_article(article_path, downloaded)
        with open(article_path, 'w', encoding='utf-8') as f:
            f.write(updated_content)
        print(f"  ✅ 文章已更新，插入 {inserted} 张图片")
    else:
        print(f"  ⚠️ 未能下载到图片")
    
    return len(downloaded) > 0

def main():
    """主流程"""
    print("=" * 60)
    print("🚀 公众号文章自动配图工具 v1.1")
    print("=" * 60)
    
    if not ARTICLES_DIR.exists():
        print(f"❌ 文章目录不存在: {ARTICLES_DIR}")
        return
    
    # 获取所有文章目录
    article_dirs = sorted([d for d in ARTICLES_DIR.iterdir() if d.is_dir()])
    
    print(f"\n📁 发现 {len(article_dirs)} 篇文章")
    print(f"📋 每篇文章最少配图: {MIN_IMAGES_PER_ARTICLE} 张")
    
    success_count = 0
    total_images = 0
    
    for article_dir in article_dirs:
        try:
            if process_article(article_dir):
                success_count += 1
                # 统计图片数量
                images_dir = article_dir / "images"
                img_count = len(list(images_dir.glob("*.jpg")) + list(images_dir.glob("*.png")) + list(images_dir.glob("*.jpeg")))
                total_images += img_count
        except Exception as e:
            print(f"  ❌ 处理失败: {e}")
    
    print("\n" + "=" * 60)
    print(f"📊 完成！成功处理 {success_count}/{len(article_dirs)} 篇文章")
    print(f"📸 共下载图片: {total_images} 张")
    print("=" * 60)

if __name__ == "__main__":
    main()

path: scripts/run_pipeline.py
content: #!/usr/bin/env python3
"""
公众号内容发布自动化 - 主流程脚本
整合用户自有工具实现内容生成→预检→发布全流程
"""

import os
import sys
import json
import argparse
from datetime import datetime
from typing import List, Dict

# ========== 用户自有工具 ==========
# MiniMax API (已配置)
MINIMAX_API_KEY = os.environ.get("MINIMAX_API_KEY", "")
API_URL = "https://api.minimaxi.com/v1/image_generation"

# ========== 配置 ==========
TOPIC_TEMPLATES = {
    "深度学习": ["神经网络基础", "CNN详解", "RNN实战", "Transformer原理", 
                "PyTorch教程", "模型优化", "深度学习前沿", "实战案例"],
    "Python": ["基础语法", "数据结构", "函数编程", "面向对象", 
               "文件操作", "异常处理", "模块管理", "项目实践"],
    "大数据": ["Hadoop入门", "Spark实战", "Flink流处理", "数据湖架构",
              "实时计算", "离线分析", "数据治理", "项目案例"],
    "AI应用": ["AI工具使用", "提示词工程", "AI绘图", "AI写作",
              "AI视频", "AI办公", "AI学习", "AI变现"],
}

DEFAULT_TOPIC = "深度学习"
DEFAULT_COUNT = 8

# ========== 工具函数 ==========

def generate_content(topic: str, subtitle: str = "理论课") -> Dict:
    """使用MiniMax API生成内容"""
    print(f"📝 正在生成内容: {topic}")
    
    # 这里调用 illustrated-ppt 逻辑
    content = {
        "title": f"{topic}基础课程",
        "subtitle": subtitle,
        "chapters": TOPIC_TEMPLATES.get(topic, TOPIC_TEMPLATES["深度学习"]),
        "author": "TJMtaotao",
        "date": datetime.now().strftime("%Y年%m月%d日")
    }
    
    return content

def check_sensitive(text: str) -> Dict:
    """敏感词检测"""
    # 这里调用敏感词检测API
    sensitive_words = ["敏感词1", "敏感词2"]  # 需配置
    found = [w for w in sensitive_words if w in text]
    
    return {
        "passed": len(found) == 0,
        "found": found
    }

def check_duplication(title: str) -> Dict:
    """重复检测"""
    # 这里比对已发布内容
    published_titles = []  # 需配置数据库
    is_duplicate = title in published_titles
    
    return {
        "passed": not is_duplicate,
        "reason": "与已发布内容重复" if is_duplicate else ""
    }

def check_format(content: str) -> Dict:
    """格式检查"""
    issues = []
    
    if len(content) < 500:
        issues.append("内容过短")
    if "##" not in content:
        issues.append("缺少小标题")
    if content.count("\n\n") < 3:
        issues.append("段落不足")
    
    return {
        "passed": len(issues) == 0,
        "issues": issues
    }

def three_step_check(content: str, title: str) -> Dict:
    """三步预检"""
    print("🔍 执行三步预检...")
    
    results = {
        "format": check_format(content),
        "sensitive": check_sensitive(content),
        "duplicate": check_duplication(title)
    }
    
    all_passed = all(r["passed"] for r in results.values())
    
    if not all_passed:
        failed = [k for k, v in results.items() if not v["passed"]]
        print(f"❌ 预检失败: {', '.join(failed)}")
    else:
        print("✅ 预检全部通过")
    
    return {
        "passed": all_passed,
        "details": results
    }

def publish_to_wechat_draft(content: Dict) -> bool:
    """直接发布到公众号草稿箱"""
    print("📋 正在发布到公众号草稿箱...")
    # 这里调用公众号发布API（跳过腾讯文档中转）
    print("✅ 公众号草稿箱发布成功")
    return True

def send_notification(message: str):
    """发送微信通知"""
    print(f"📱 通知: {message}")

def add_to_review_queue(item: Dict, reason: str):
    """加入人工审核队列"""
    print(f"⚠️ 进入人工审核队列: {reason}")
    print(f"   标题: {item.get('title', 'N/A')}")

# ========== 主流程 ==========

def run_pipeline(topic: str, count: int, steps: List[str] = None):
    """
    运行完整工作流
    
    Args:
        topic: 内容主题
        count: 生成数量
        steps: 指定步骤，如 ["generate", "check", "publish"]
    """
    if steps is None:
        steps = ["generate", "check", "publish"]
    
    print("=" * 50)
    print(f"🚀 公众号内容发布自动化 | 主题: {topic} | 数量: {count}")
    print("=" * 50)
    
    results = []
    
    # 生成内容
    if "generate" in steps:
        print("\n📝 步骤1: 内容生成")
        print("-" * 30)
        
        content = generate_content(topic)
        
        for i in range(count):
            print(f"  [{i+1}/{count}] 已生成: {content['chapters'][i]}")
        
        results.append({"step": "generate", "success": True})
    
    # 三步预检
    if "check" in steps:
        print("\n🔍 步骤2: 三步预检")
        print("-" * 30)
        
        check_result = three_step_check(
            content.get("title", ""), 
            content.get("title", "")
        )
        
        if not check_result["passed"]:
            add_to_review_queue(content, "预检失败")
        
        results.append({"step": "check", "success": check_result["passed"]})
    
    # 发布
    if "publish" in steps:
        print("\n📤 步骤3: 发布")
        print("-" * 30)
        
        draft_ok = publish_to_wechat_draft(content)
        
        if doc_ok and draft_ok:
            send_notification(f"✅ {topic} 内容已发布到草稿箱")
        
        results.append({
            "step": "publish", 
            "success": doc_ok and draft_ok
        })
    
    # 汇总
    print("\n" + "=" * 50)
    print("📊 执行结果汇总")
    print("=" * 50)
    
    for r in results:
        status = "✅ 成功" if r["success"] else "❌ 失败"
        print(f"  {r['step']}: {status}")
    
    return results

# ========== 命令行接口 ==========

def main():
    parser = argparse.ArgumentParser(description="公众号内容发布自动化")
    parser.add_argument("--topic", default=DEFAULT_TOPIC, help="内容主题")
    parser.add_argument("--count", type=int, default=DEFAULT_COUNT, help="生成数量")
    parser.add_argument("--step", nargs="+", choices=["generate", "check", "publish"],
                      help="指定执行步骤")
    
    args = parser.parse_args()
    
    # 检查API Key
    if not MINIMAX_API_KEY:
        print("⚠️  请设置 MINIMAX_API_KEY 环境变量")
        print("   export MINIMAX_API_KEY='your-api-key'")
    
    run_pipeline(args.topic, args.count, args.step)

if __name__ == "__main__":
    main()

path: references/config.md
content: # 公众号内容发布自动化 - 用户工具配置

## 已配置的工具

### API Keys (需设置环境变量)

```bash
# MiniMax API - 主要图片生成
export MINIMAX_API_KEY="sk-cp-xxx"

# 腾讯云 - 腾讯文档/混元
export TENCENT_SECRET_ID="xxx"
export TENCENT_SECRET_KEY="xxx"
```

### 技能列表

| 技能名 | 版本 | 用途 |
|--------|------|------|
| illustrated-ppt | - | 图文并茂生成 |
| course-ppt-generator | 1.0.2 | 课程PPT |
| ai-ppt-generator | 1.1.5 | AI PPT助手 |
| hunyuan-image | 1.0.3 | 混元生图 |
| free-image-skill | 1.0.0 | 免费图片 |
| yaniw-wechat-publisher | 1.1.0 | 公众号发布 |
| wechat-article-crayon | 1.0.1 | 排版美化 |
| tavily-search | 1.0.0 | 网络搜索 |
| multi-search-engine | 2.1.3 | 多引擎搜索 |
| news-aggregator-skill | 0.1.0 | 资讯聚合 |

## 工作流配置

### 定时任务 (每日08:00)

```bash
# 编辑 crontab
crontab -e

# 添加
0 8 * * * cd /root/.openclaw/workspace/skills/wechat-content-pipeline && python3 scripts/run_pipeline.py --topic 深度学习 --count 8
```

### 腾讯文档配置

1. 获取 Folder Token
2. 配置权限
3. 设置中转路径

### 公众号配置

1. AppID: wx90d7cc689e706a15
2. 配置IP白名单
3. 设置草稿箱权限

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