WeChat Article Publisher Publish Markdown or HTML content to WeChat Official Account drafts via API, with automatic format conversion. Prerequisites - WECHAT API KEY environment variable set (from .env file) - Python 3.9+ - Authorized WeChat Official Account on wx.limyai.com Scripts Located in : wechat api.py WeChat API client for listing accounts and publishing articles: parse markdown.py Parse Markdown and extract structured data (optional, for advanced use): Workflow Strategy: "API-First Publishing" Unlike browser-based publishing, this skill uses direct API calls for reliable, fast publis…

, stripped):\n if current_block:\n blocks.append('\\n'.join(current_block))\n current_block = []\n blocks.append(stripped)\n continue\n\n current_block.append(line)\n\n if current_block:\n blocks.append('\\n'.join(current_block))\n\n # Handle unclosed code block\n if code_block_lines:\n blocks.append('___CODE_BLOCK_START___' + '\\n'.join(code_block_lines) + '___CODE_BLOCK_END___')\n\n return blocks\n\n\ndef extract_images_with_block_index(markdown: str, base_path: Path) -> tuple[list[dict], str, int]:\n \"\"\"Extract images with their block index position.\n\n Returns:\n (image_list, markdown_without_images, total_blocks)\n \"\"\"\n blocks = split_into_blocks(markdown)\n images = []\n clean_blocks = []\n\n img_pattern = re.compile(r'^!\\[([^\\]]*)\\]\\(([^)]+)\\)

WeChat Article Publisher Publish Markdown or HTML content to WeChat Official Account drafts via API, with automatic format conversion. Prerequisites - WECHAT API KEY environment variable set (from .env file) - Python 3.9+ - Authorized WeChat Official Account on wx.limyai.com Scripts Located in : wechat api.py WeChat API client for listing accounts and publishing articles: parse markdown.py Parse Markdown and extract structured data (optional, for advanced use): Workflow Strategy: "API-First Publishing" Unlike browser-based publishing, this skill uses direct API calls for reliable, fast publis…

)\n\n for i, block in enumerate(blocks):\n match = img_pattern.match(block.strip())\n if match:\n alt_text = match.group(1)\n img_path = match.group(2)\n\n # Resolve relative paths\n if not os.path.isabs(img_path):\n full_path = str(base_path / img_path)\n else:\n full_path = img_path\n\n # block_index is the index in clean_blocks (without images)\n # i.e., this image should be inserted after clean_blocks[block_index-1]\n block_index = len(clean_blocks)\n\n # Get context from previous block for reference\n after_text = \"\"\n if clean_blocks:\n prev_block = clean_blocks[-1].strip()\n # Get last line of previous block\n lines = [l for l in prev_block.split('\\n') if l.strip()]\n after_text = lines[-1][:80] if lines else \"\"\n\n images.append({\n \"path\": full_path,\n \"alt\": alt_text,\n \"block_index\": block_index,\n \"after_text\": after_text # Keep for reference/debugging\n })\n else:\n clean_blocks.append(block)\n\n clean_markdown = '\\n\\n'.join(clean_blocks)\n return images, clean_markdown, len(clean_blocks)\n\n\ndef extract_title(markdown: str) -> tuple[str, str]:\n \"\"\"Extract title from first H1, H2, or first non-empty line.\n\n Returns:\n (title, markdown_without_title): Title string and markdown with H1 title removed.\n If title is from H1, it's removed from markdown to avoid duplication.\n \"\"\"\n lines = markdown.strip().split('\\n')\n title = \"Untitled\"\n title_line_idx = None\n\n for idx, line in enumerate(lines):\n stripped = line.strip()\n if not stripped:\n continue\n # H1 - use as title and mark for removal\n if stripped.startswith('# '):\n title = stripped[2:].strip()\n title_line_idx = idx\n break\n # H2 - use as title but don't remove (it's a section header)\n if stripped.startswith('## '):\n title = stripped[3:].strip()\n break\n # First non-empty, non-image line\n if not stripped.startswith('!['):\n title = stripped[:100]\n break\n\n # Remove H1 title line from markdown to avoid duplication\n if title_line_idx is not None:\n lines.pop(title_line_idx)\n markdown = '\\n'.join(lines)\n\n return title, markdown\n\n\ndef markdown_to_html(markdown: str) -> str:\n \"\"\"Convert markdown to HTML for WeChat article publishing.\"\"\"\n html = markdown\n\n # Process code blocks first (marked with ___CODE_BLOCK_START___ and ___CODE_BLOCK_END___)\n # Convert to blockquote format for better compatibility\n def convert_code_block(match):\n code_content = match.group(1)\n lines = code_content.strip().split('\\n')\n # Join non-empty lines with \u003cbr> for display\n formatted = '\u003cbr>'.join(line for line in lines if line.strip())\n return f'\u003cblockquote>{formatted}\u003c/blockquote>'\n\n html = re.sub(r'___CODE_BLOCK_START___(.*?)___CODE_BLOCK_END___', convert_code_block, html, flags=re.DOTALL)\n\n # Headers (H2 only, H1 is title)\n html = re.sub(r'^## (.+)

WeChat Article Publisher Publish Markdown or HTML content to WeChat Official Account drafts via API, with automatic format conversion. Prerequisites - WECHAT API KEY environment variable set (from .env file) - Python 3.9+ - Authorized WeChat Official Account on wx.limyai.com Scripts Located in : wechat api.py WeChat API client for listing accounts and publishing articles: parse markdown.py Parse Markdown and extract structured data (optional, for advanced use): Workflow Strategy: "API-First Publishing" Unlike browser-based publishing, this skill uses direct API calls for reliable, fast publis…

, r'\u003ch2>\\1\u003c/h2>', html, flags=re.MULTILINE)\n html = re.sub(r'^### (.+)

WeChat Article Publisher Publish Markdown or HTML content to WeChat Official Account drafts via API, with automatic format conversion. Prerequisites - WECHAT API KEY environment variable set (from .env file) - Python 3.9+ - Authorized WeChat Official Account on wx.limyai.com Scripts Located in : wechat api.py WeChat API client for listing accounts and publishing articles: parse markdown.py Parse Markdown and extract structured data (optional, for advanced use): Workflow Strategy: "API-First Publishing" Unlike browser-based publishing, this skill uses direct API calls for reliable, fast publis…

, r'\u003ch3>\\1\u003c/h3>', html, flags=re.MULTILINE)\n\n # Bold\n html = re.sub(r'\\*\\*(.+?)\\*\\*', r'\u003cstrong>\\1\u003c/strong>', html)\n\n # Italic\n html = re.sub(r'\\*([^*]+)\\*', r'\u003cem>\\1\u003c/em>', html)\n\n # Links\n html = re.sub(r'\\[([^\\]]+)\\]\\(([^)]+)\\)', r'\u003ca href=\"\\2\">\\1\u003c/a>', html)\n\n # Blockquotes (regular markdown blockquotes, not code blocks)\n html = re.sub(r'^> (.+)

WeChat Article Publisher Publish Markdown or HTML content to WeChat Official Account drafts via API, with automatic format conversion. Prerequisites - WECHAT API KEY environment variable set (from .env file) - Python 3.9+ - Authorized WeChat Official Account on wx.limyai.com Scripts Located in : wechat api.py WeChat API client for listing accounts and publishing articles: parse markdown.py Parse Markdown and extract structured data (optional, for advanced use): Workflow Strategy: "API-First Publishing" Unlike browser-based publishing, this skill uses direct API calls for reliable, fast publis…

, r'\u003cblockquote>\\1\u003c/blockquote>', html, flags=re.MULTILINE)\n\n # Unordered lists\n html = re.sub(r'^- (.+)

WeChat Article Publisher Publish Markdown or HTML content to WeChat Official Account drafts via API, with automatic format conversion. Prerequisites - WECHAT API KEY environment variable set (from .env file) - Python 3.9+ - Authorized WeChat Official Account on wx.limyai.com Scripts Located in : wechat api.py WeChat API client for listing accounts and publishing articles: parse markdown.py Parse Markdown and extract structured data (optional, for advanced use): Workflow Strategy: "API-First Publishing" Unlike browser-based publishing, this skill uses direct API calls for reliable, fast publis…

, r'\u003cli>\\1\u003c/li>', html, flags=re.MULTILINE)\n\n # Ordered lists\n html = re.sub(r'^\\d+\\. (.+)

WeChat Article Publisher Publish Markdown or HTML content to WeChat Official Account drafts via API, with automatic format conversion. Prerequisites - WECHAT API KEY environment variable set (from .env file) - Python 3.9+ - Authorized WeChat Official Account on wx.limyai.com Scripts Located in : wechat api.py WeChat API client for listing accounts and publishing articles: parse markdown.py Parse Markdown and extract structured data (optional, for advanced use): Workflow Strategy: "API-First Publishing" Unlike browser-based publishing, this skill uses direct API calls for reliable, fast publis…

, r'\u003cli>\\1\u003c/li>', html, flags=re.MULTILINE)\n\n # Wrap consecutive \u003cli> in \u003cul>\n html = re.sub(r'((?:\u003cli>.*?\u003c/li>\\n?)+)', r'\u003cul>\\1\u003c/ul>', html)\n\n # Paragraphs - split by double newlines\n parts = html.split('\\n\\n')\n processed_parts = []\n\n for part in parts:\n part = part.strip()\n if not part:\n continue\n # Skip if already a block element\n if part.startswith(('\u003ch2>', '\u003ch3>', '\u003cblockquote>', '\u003cul>', '\u003col>')):\n processed_parts.append(part)\n else:\n # Wrap in paragraph, convert single newlines to \u003cbr>\n part = part.replace('\\n', '\u003cbr>')\n processed_parts.append(f'\u003cp>{part}\u003c/p>')\n\n return ''.join(processed_parts)\n\n\ndef parse_markdown_file(filepath: str) -> dict:\n \"\"\"Parse a markdown file and return structured data.\"\"\"\n path = Path(filepath)\n base_path = path.parent\n\n with open(filepath, 'r', encoding='utf-8') as f:\n content = f.read()\n\n # Extract title first (and remove H1 from markdown)\n title, content = extract_title(content)\n\n # Extract images with block indices\n images, clean_markdown, total_blocks = extract_images_with_block_index(content, base_path)\n\n # Convert to HTML\n html = markdown_to_html(clean_markdown)\n\n # Separate cover image from content images\n cover_image = images[0][\"path\"] if images else None\n content_images = images[1:] if len(images) > 1 else []\n\n # Adjust block_index for content images (subtract 1 since cover image is removed)\n # The first content image's block_index was calculated including cover image's position\n\n return {\n \"title\": title,\n \"cover_image\": cover_image,\n \"content_images\": content_images,\n \"html\": html,\n \"total_blocks\": total_blocks,\n \"source_file\": str(path.absolute())\n }\n\n\ndef main():\n parser = argparse.ArgumentParser(description='Parse Markdown for WeChat article publishing')\n parser.add_argument('file', help='Markdown file to parse')\n parser.add_argument('--output', choices=['json', 'html'], default='json',\n help='Output format (default: json)')\n parser.add_argument('--html-only', action='store_true',\n help='Output only HTML content')\n\n args = parser.parse_args()\n\n if not os.path.exists(args.file):\n print(f\"Error: File not found: {args.file}\", file=sys.stderr)\n sys.exit(1)\n\n result = parse_markdown_file(args.file)\n\n if args.html_only:\n print(result['html'])\n elif args.output == 'json':\n print(json.dumps(result, ensure_ascii=False, indent=2))\n else:\n print(result['html'])\n\n\nif __name__ == '__main__':\n main()\n","content_type":"text/x-python; charset=utf-8","language":"python","size":10232,"content_sha256":"11cc8a14015747e73e966b51c8534785d48ebb6d356da0c3ddf2e123f33fb870"},{"filename":"scripts/wechat_api.py","content":"#!/usr/bin/env python3\n\"\"\"\nWeChat Official Account API client for publishing articles.\n\nThis script provides functions to interact with the WeChat API:\n- List authorized WeChat official accounts\n- Publish articles to WeChat drafts\n\nAuthentication:\n The API key is read from the WECHAT_API_KEY environment variable.\n You can set it in a .env file in the project root.\n\nUsage:\n # List accounts\n python wechat_api.py list-accounts\n\n # Publish article\n python wechat_api.py publish --appid \u003cwechat_appid> --title \"Title\" --content \"Content\"\n\n # Publish from markdown file\n python wechat_api.py publish --appid \u003cwechat_appid> --markdown /path/to/article.md\n\n # Publish from HTML file (with formatting preserved)\n python wechat_api.py publish --appid \u003cwechat_appid> --html /path/to/article.html\n\n # Publish as \"小绿书\" (image-text mode)\n python wechat_api.py publish --appid \u003cwechat_appid> --markdown /path/to/article.md --type newspic\n\nAPI Documentation:\n Base URL: https://wx.limyai.com/api/openapi\n Authentication: X-API-Key header\n\"\"\"\n\nimport argparse\nimport json\nimport os\nimport sys\nfrom pathlib import Path\nfrom typing import Optional\nfrom urllib.request import Request, urlopen\nfrom urllib.error import HTTPError, URLError\n\n# API configuration\nAPI_BASE_URL = \"https://wx.limyai.com/api/openapi\"\n\n\ndef load_env_file(env_path: Optional[str] = None) -> None:\n \"\"\"\n Load environment variables from .env file.\n\n Args:\n env_path: Optional path to .env file. If not provided, searches in\n current directory and parent directories.\n \"\"\"\n if env_path:\n env_file = Path(env_path)\n else:\n # Search for .env file in current and parent directories\n current = Path.cwd()\n env_file = None\n for _ in range(5): # Search up to 5 levels\n candidate = current / \".env\"\n if candidate.exists():\n env_file = candidate\n break\n current = current.parent\n\n if env_file and env_file.exists():\n with open(env_file, \"r\", encoding=\"utf-8\") as f:\n for line in f:\n line = line.strip()\n if line and not line.startswith(\"#\") and \"=\" in line:\n key, value = line.split(\"=\", 1)\n key = key.strip()\n value = value.strip().strip('\"').strip(\"'\")\n if key and value:\n os.environ.setdefault(key, value)\n\n\ndef get_api_key() -> str:\n \"\"\"\n Get the WeChat API key from environment variable.\n\n Returns:\n The API key string.\n\n Raises:\n SystemExit: If API key is not found.\n \"\"\"\n # Try to load from .env file first\n load_env_file()\n\n api_key = os.environ.get(\"WECHAT_API_KEY\")\n if not api_key:\n print(\"Error: WECHAT_API_KEY environment variable not set.\", file=sys.stderr)\n print(\"Please set it in your .env file or environment.\", file=sys.stderr)\n sys.exit(1)\n return api_key\n\n\ndef make_api_request(endpoint: str, data: Optional[dict] = None) -> dict:\n \"\"\"\n Make a POST request to the WeChat API.\n\n Args:\n endpoint: API endpoint path (e.g., '/wechat-accounts')\n data: Optional JSON data to send in request body\n\n Returns:\n JSON response as dictionary\n\n Raises:\n SystemExit: On API errors\n \"\"\"\n url = f\"{API_BASE_URL}{endpoint}\"\n api_key = get_api_key()\n\n headers = {\n \"X-API-Key\": api_key,\n \"Content-Type\": \"application/json\",\n }\n\n body = json.dumps(data).encode(\"utf-8\") if data else b\"{}\"\n\n try:\n request = Request(url, data=body, headers=headers, method=\"POST\")\n with urlopen(request, timeout=60) as response:\n response_data = json.loads(response.read().decode(\"utf-8\"))\n return response_data\n except HTTPError as e:\n error_body = e.read().decode(\"utf-8\") if e.fp else \"\"\n try:\n error_json = json.loads(error_body)\n error_msg = error_json.get(\"error\", error_body)\n error_code = error_json.get(\"code\", \"UNKNOWN\")\n print(f\"API Error ({error_code}): {error_msg}\", file=sys.stderr)\n except json.JSONDecodeError:\n print(f\"HTTP Error {e.code}: {error_body or e.reason}\", file=sys.stderr)\n sys.exit(1)\n except URLError as e:\n print(f\"Network Error: {e.reason}\", file=sys.stderr)\n sys.exit(1)\n except Exception as e:\n print(f\"Error: {e}\", file=sys.stderr)\n sys.exit(1)\n\n\ndef list_accounts() -> dict:\n \"\"\"\n Get list of authorized WeChat official accounts.\n\n Returns:\n API response containing accounts list\n\n Response format:\n {\n \"success\": true,\n \"data\": {\n \"accounts\": [\n {\n \"name\": \"公众号名称\",\n \"wechatAppid\": \"wx1234567890\",\n \"username\": \"gh_abc123\",\n \"avatar\": \"https://...\",\n \"type\": \"subscription\",\n \"verified\": true,\n \"status\": \"active\",\n \"lastAuthTime\": \"2024-01-01T00:00:00.000Z\",\n \"createdAt\": \"2024-01-01T00:00:00.000Z\"\n }\n ],\n \"total\": 1\n }\n }\n \"\"\"\n return make_api_request(\"/wechat-accounts\")\n\n\ndef publish_article(\n wechat_appid: str,\n title: str,\n content: str,\n summary: Optional[str] = None,\n cover_image: Optional[str] = None,\n author: Optional[str] = None,\n content_format: str = \"markdown\",\n article_type: str = \"news\",\n) -> dict:\n \"\"\"\n Publish an article to WeChat official account drafts.\n\n Args:\n wechat_appid: WeChat AppID of the target account\n title: Article title (max 64 characters)\n content: Article content (Markdown or HTML)\n summary: Article summary (max 120 characters, optional)\n cover_image: Cover image URL (optional)\n author: Author name (optional)\n content_format: Content format - 'markdown' (default) or 'html'\n article_type: Article type - 'news' (default) or 'newspic' (小绿书)\n\n Returns:\n API response containing publication result\n\n Response format (success):\n {\n \"success\": true,\n \"data\": {\n \"publicationId\": \"uuid-here\",\n \"materialId\": \"uuid-here\",\n \"mediaId\": \"wechat-media-id\",\n \"status\": \"published\",\n \"message\": \"文章已成功发布到公众号草稿箱\"\n }\n }\n\n Response format (failure):\n {\n \"success\": false,\n \"error\": \"错误信息\",\n \"code\": \"ERROR_CODE\"\n }\n \"\"\"\n data = {\n \"wechatAppid\": wechat_appid,\n \"title\": title,\n \"content\": content,\n \"contentFormat\": content_format,\n \"articleType\": article_type,\n }\n\n if summary:\n data[\"summary\"] = summary\n if cover_image:\n data[\"coverImage\"] = cover_image\n if author:\n data[\"author\"] = author\n\n return make_api_request(\"/wechat-publish\", data)\n\n\ndef parse_markdown_for_wechat(filepath: str) -> dict:\n \"\"\"\n Parse a markdown file and extract data for WeChat publishing.\n\n Args:\n filepath: Path to the markdown file\n\n Returns:\n Dictionary containing:\n - title: Article title\n - content: Article content (markdown)\n - cover_image: First image URL/path (if any)\n - summary: First paragraph as summary (truncated to 120 chars)\n \"\"\"\n path = Path(filepath)\n if not path.exists():\n print(f\"Error: File not found: {filepath}\", file=sys.stderr)\n sys.exit(1)\n\n with open(filepath, \"r\", encoding=\"utf-8\") as f:\n content = f.read()\n\n # Extract title from H1\n title = \"Untitled\"\n lines = content.strip().split(\"\\n\")\n content_start = 0\n\n for idx, line in enumerate(lines):\n stripped = line.strip()\n if not stripped:\n continue\n if stripped.startswith(\"# \"):\n title = stripped[2:].strip()\n content_start = idx + 1\n break\n elif stripped.startswith(\"## \"):\n title = stripped[3:].strip()\n break\n elif not stripped.startswith(\"![\"):\n title = stripped[:64]\n break\n\n # Get content without H1 title\n content_lines = lines[content_start:]\n markdown_content = \"\\n\".join(content_lines).strip()\n\n # Extract first image as cover\n import re\n cover_image = None\n img_pattern = re.compile(r\"!\\[[^\\]]*\\]\\(([^)]+)\\)\")\n img_match = img_pattern.search(markdown_content)\n if img_match:\n cover_image = img_match.group(1)\n # If relative path, make absolute\n if cover_image and not cover_image.startswith((\"http://\", \"https://\")):\n cover_path = path.parent / cover_image\n if cover_path.exists():\n cover_image = str(cover_path.absolute())\n\n # Extract summary from first paragraph\n summary = None\n for line in content_lines:\n stripped = line.strip()\n if stripped and not stripped.startswith((\"#\", \"!\", \">\", \"-\", \"*\", \"`\")):\n summary = stripped[:120]\n break\n\n return {\n \"title\": title,\n \"content\": markdown_content,\n \"cover_image\": cover_image,\n \"summary\": summary,\n \"source_file\": str(path.absolute()),\n }\n\n\ndef parse_html_for_wechat(filepath: str) -> dict:\n \"\"\"\n Parse an HTML file and extract data for WeChat publishing.\n\n Extracts title from \u003ctitle> tag, \u003ch1> tag, or first heading.\n Uses \u003cbody> content or full content if no body tag.\n\n Args:\n filepath: Path to the HTML file\n\n Returns:\n Dictionary containing:\n - title: Article title\n - content: Article content (HTML)\n - cover_image: First image URL/path (if any)\n - summary: Text from first paragraph (truncated to 120 chars)\n \"\"\"\n import re\n\n path = Path(filepath)\n if not path.exists():\n print(f\"Error: File not found: {filepath}\", file=sys.stderr)\n sys.exit(1)\n\n with open(filepath, \"r\", encoding=\"utf-8\") as f:\n content = f.read()\n\n # Extract title from \u003ctitle> tag first\n title = \"Untitled\"\n title_match = re.search(r\"\u003ctitle[^>]*>([^\u003c]+)\u003c/title>\", content, re.IGNORECASE)\n if title_match:\n title = title_match.group(1).strip()\n else:\n # Try \u003ch1> tag\n h1_match = re.search(r\"\u003ch1[^>]*>([^\u003c]+)\u003c/h1>\", content, re.IGNORECASE)\n if h1_match:\n title = h1_match.group(1).strip()\n\n # Truncate title to 64 characters (WeChat limit)\n title = title[:64]\n\n # Extract body content if present\n body_match = re.search(r\"\u003cbody[^>]*>(.*?)\u003c/body>\", content, re.IGNORECASE | re.DOTALL)\n if body_match:\n html_content = body_match.group(1).strip()\n else:\n # Use full content, but try to remove html/head tags\n html_content = re.sub(r\"\u003chtml[^>]*>|\u003c/html>\", \"\", content, flags=re.IGNORECASE)\n html_content = re.sub(r\"\u003chead[^>]*>.*?\u003c/head>\", \"\", html_content, flags=re.IGNORECASE | re.DOTALL)\n html_content = re.sub(r\"\u003c!DOCTYPE[^>]*>\", \"\", html_content, flags=re.IGNORECASE)\n html_content = html_content.strip()\n\n # Extract first image as cover\n cover_image = None\n img_match = re.search(r'\u003cimg[^>]+src=[\"\\']([^\"\\']+)[\"\\']', html_content, re.IGNORECASE)\n if img_match:\n cover_image = img_match.group(1)\n # If relative path, make absolute\n if cover_image and not cover_image.startswith((\"http://\", \"https://\", \"data:\")):\n cover_path = path.parent / cover_image\n if cover_path.exists():\n cover_image = str(cover_path.absolute())\n\n # Extract summary from first \u003cp> tag\n summary = None\n p_match = re.search(r\"\u003cp[^>]*>([^\u003c]+)\", html_content, re.IGNORECASE)\n if p_match:\n # Remove HTML tags and get plain text\n summary_text = re.sub(r\"\u003c[^>]+>\", \"\", p_match.group(1))\n summary = summary_text.strip()[:120]\n\n return {\n \"title\": title,\n \"content\": html_content,\n \"cover_image\": cover_image,\n \"summary\": summary,\n \"source_file\": str(path.absolute()),\n }\n\n\ndef main():\n \"\"\"Main entry point for CLI.\"\"\"\n parser = argparse.ArgumentParser(\n description=\"WeChat Official Account API Client\"\n )\n subparsers = parser.add_subparsers(dest=\"command\", required=True)\n\n # List accounts command\n list_parser = subparsers.add_parser(\n \"list-accounts\",\n help=\"List authorized WeChat official accounts\"\n )\n\n # Publish command\n publish_parser = subparsers.add_parser(\n \"publish\",\n help=\"Publish article to WeChat drafts\"\n )\n publish_parser.add_argument(\n \"--appid\",\n required=True,\n help=\"WeChat AppID of target account\"\n )\n publish_parser.add_argument(\n \"--title\",\n help=\"Article title (max 64 characters)\"\n )\n publish_parser.add_argument(\n \"--content\",\n help=\"Article content (Markdown or HTML)\"\n )\n publish_parser.add_argument(\n \"--markdown\",\n help=\"Path to markdown file (alternative to --title and --content)\"\n )\n publish_parser.add_argument(\n \"--html\",\n help=\"Path to HTML file (alternative to --markdown, auto-sets format to html)\"\n )\n publish_parser.add_argument(\n \"--summary\",\n help=\"Article summary (max 120 characters)\"\n )\n publish_parser.add_argument(\n \"--cover\",\n help=\"Cover image URL\"\n )\n publish_parser.add_argument(\n \"--author\",\n help=\"Author name\"\n )\n publish_parser.add_argument(\n \"--format\",\n choices=[\"markdown\", \"html\"],\n default=\"markdown\",\n help=\"Content format (default: markdown)\"\n )\n publish_parser.add_argument(\n \"--type\",\n choices=[\"news\", \"newspic\"],\n default=\"news\",\n help=\"Article type: news (default) or newspic (小绿书)\"\n )\n\n args = parser.parse_args()\n\n if args.command == \"list-accounts\":\n result = list_accounts()\n print(json.dumps(result, ensure_ascii=False, indent=2))\n\n elif args.command == \"publish\":\n # Determine content format based on input\n content_format = args.format\n\n # Get content from file or direct input\n if args.html:\n # HTML file takes precedence, auto-set format to html\n parsed = parse_html_for_wechat(args.html)\n title = args.title or parsed[\"title\"]\n content = parsed[\"content\"]\n summary = args.summary or parsed[\"summary\"]\n cover = args.cover or parsed[\"cover_image\"]\n content_format = \"html\" # Always use html format for HTML files\n elif args.markdown:\n parsed = parse_markdown_for_wechat(args.markdown)\n title = args.title or parsed[\"title\"]\n content = parsed[\"content\"]\n summary = args.summary or parsed[\"summary\"]\n cover = args.cover or parsed[\"cover_image\"]\n else:\n if not args.title or not args.content:\n print(\n \"Error: Either --markdown, --html, or both --title and --content required\",\n file=sys.stderr\n )\n sys.exit(1)\n title = args.title\n content = args.content\n summary = args.summary\n cover = args.cover\n\n result = publish_article(\n wechat_appid=args.appid,\n title=title,\n content=content,\n summary=summary,\n cover_image=cover,\n author=args.author,\n content_format=content_format,\n article_type=args.type,\n )\n print(json.dumps(result, ensure_ascii=False, indent=2))\n\n\nif __name__ == \"__main__\":\n main()\n","content_type":"text/x-python; charset=utf-8","language":"python","size":16078,"content_sha256":"29d5e5216295483e7acf5dca9b4c1d8590bf48d91867e578b191c4e737479a76"}],"content_json":{"type":"doc","content":[{"type":"heading","attrs":{"level":1},"content":[{"text":"WeChat Article Publisher","type":"text"}]},{"type":"paragraph","content":[{"text":"Publish Markdown or HTML content to WeChat Official Account drafts via API, with automatic format conversion.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Prerequisites","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"WECHAT_API_KEY environment variable set (from .env file)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Python 3.9+","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Authorized WeChat Official Account on wx.limyai.com","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Scripts","type":"text"}]},{"type":"paragraph","content":[{"text":"Located in ","type":"text"},{"text":"~/.claude/skills/wechat-article-publisher/scripts/","type":"text","marks":[{"type":"code_inline"}]},{"text":":","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"wechat_api.py","type":"text"}]},{"type":"paragraph","content":[{"text":"WeChat API client for listing accounts and publishing articles:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# List authorized accounts\npython wechat_api.py list-accounts\n\n# Publish from markdown file\npython wechat_api.py publish --appid \u003cwechat_appid> --markdown /path/to/article.md\n\n# Publish from HTML file (preserves formatting)\npython wechat_api.py publish --appid \u003cwechat_appid> --html /path/to/article.html\n\n# Publish with custom options\npython wechat_api.py publish --appid \u003cappid> --markdown /path/to/article.md --type newspic","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"parse_markdown.py","type":"text"}]},{"type":"paragraph","content":[{"text":"Parse Markdown and extract structured data (optional, for advanced use):","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"python parse_markdown.py \u003cmarkdown_file> [--output json|html]","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Workflow","type":"text"}]},{"type":"paragraph","content":[{"text":"Strategy: \"API-First Publishing\"","type":"text","marks":[{"type":"strong"}]}]},{"type":"paragraph","content":[{"text":"Unlike browser-based publishing, this skill uses direct API calls for reliable, fast publishing.","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Load WECHAT_API_KEY from environment","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"List available WeChat accounts (if user hasn't specified)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Detect file format (Markdown or HTML) and parse accordingly","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Call publish API to create draft in WeChat","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Report success with draft details","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Supported File Formats:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":".md","type":"text","marks":[{"type":"code_inline"}]},{"text":" files → Parsed as Markdown, converted by WeChat API","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":".html","type":"text","marks":[{"type":"code_inline"}]},{"text":" files → Sent as HTML, formatting preserved","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Step-by-Step Guide","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Step 1: Check API Key","type":"text"}]},{"type":"paragraph","content":[{"text":"Before any operation, verify the API key is available:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# Check if .env file exists and contains WECHAT_API_KEY\ncat .env | grep WECHAT_API_KEY","type":"text"}]},{"type":"paragraph","content":[{"text":"If not set, remind user to:","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Copy ","type":"text"},{"text":".env.example","type":"text","marks":[{"type":"code_inline"}]},{"text":" to ","type":"text"},{"text":".env","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Set their ","type":"text"},{"text":"WECHAT_API_KEY","type":"text","marks":[{"type":"code_inline"}]},{"text":" value","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Step 2: List Available Accounts","type":"text"}]},{"type":"paragraph","content":[{"text":"Get the list of authorized WeChat accounts:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"python ~/.claude/skills/wechat-article-publisher/scripts/wechat_api.py list-accounts","type":"text"}]},{"type":"paragraph","content":[{"text":"Output example:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"json"},"content":[{"text":"{\n \"success\": true,\n \"data\": {\n \"accounts\": [\n {\n \"name\": \"我的公众号\",\n \"wechatAppid\": \"wx1234567890\",\n \"username\": \"gh_abc123\",\n \"type\": \"subscription\",\n \"verified\": true,\n \"status\": \"active\"\n }\n ],\n \"total\": 1\n }\n}","type":"text"}]},{"type":"paragraph","content":[{"text":"Important","type":"text","marks":[{"type":"strong"}]},{"text":":","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"If only one account, use it automatically","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"If multiple accounts, ask user to choose","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Note the ","type":"text"},{"text":"wechatAppid","type":"text","marks":[{"type":"code_inline"}]},{"text":" for publishing","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Step 3: Publish Article","type":"text"}]},{"type":"paragraph","content":[{"text":"For Markdown files:","type":"text","marks":[{"type":"strong"}]}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"python ~/.claude/skills/wechat-article-publisher/scripts/wechat_api.py publish \\\n --appid \u003cwechatAppid> \\\n --markdown /path/to/article.md","type":"text"}]},{"type":"paragraph","content":[{"text":"For HTML files (preserves formatting):","type":"text","marks":[{"type":"strong"}]}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"python ~/.claude/skills/wechat-article-publisher/scripts/wechat_api.py publish \\\n --appid \u003cwechatAppid> \\\n --html /path/to/article.html","type":"text"}]},{"type":"paragraph","content":[{"text":"For 小绿书 (image-text mode):","type":"text","marks":[{"type":"strong"}]}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"python ~/.claude/skills/wechat-article-publisher/scripts/wechat_api.py publish \\\n --appid \u003cwechatAppid> \\\n --markdown /path/to/article.md \\\n --type newspic","type":"text"}]},{"type":"paragraph","content":[{"text":"Success response:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"json"},"content":[{"text":"{\n \"success\": true,\n \"data\": {\n \"publicationId\": \"uuid-here\",\n \"materialId\": \"uuid-here\",\n \"mediaId\": \"wechat-media-id\",\n \"status\": \"published\",\n \"message\": \"文章已成功发布到公众号草稿箱\"\n }\n}","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Step 4: Report Result","type":"text"}]},{"type":"paragraph","content":[{"text":"After successful publishing:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Confirm the draft was created","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Remind user to review and publish manually in WeChat admin panel","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Provide any relevant IDs for reference","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"API Reference","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Authentication","type":"text"}]},{"type":"paragraph","content":[{"text":"All API requests require the ","type":"text"},{"text":"X-API-Key","type":"text","marks":[{"type":"code_inline"}]},{"text":" header:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"X-API-Key: WECHAT_API_KEY","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Get Accounts List","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"POST https://wx.limyai.com/api/openapi/wechat-accounts","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Publish Article","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"POST https://wx.limyai.com/api/openapi/wechat-publish","type":"text"}]},{"type":"paragraph","content":[{"text":"Parameters:","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Parameter","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Type","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Required","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Description","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"wechatAppid","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"string","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Yes","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"WeChat AppID","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"title","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"string","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Yes","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Article title (max 64 chars)","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"content","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"string","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Yes","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Article content (Markdown/HTML)","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"summary","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"string","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"No","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Article summary (max 120 chars)","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"coverImage","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"string","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"No","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Cover image URL","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"author","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"string","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"No","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Author name","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"contentFormat","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"string","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"No","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"'markdown' (default) or 'html'","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"articleType","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"string","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"No","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"'news' (default) or 'newspic'","type":"text"}]}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Error Codes","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Code","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Description","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"API_KEY_MISSING","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"API key not provided","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"API_KEY_INVALID","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"API key invalid","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"ACCOUNT_NOT_FOUND","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Account not found or unauthorized","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"ACCOUNT_TOKEN_EXPIRED","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Account authorization expired","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"INVALID_PARAMETER","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Invalid parameter","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"WECHAT_API_ERROR","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"WeChat API call failed","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"INTERNAL_ERROR","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Server error","type":"text"}]}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Critical Rules","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"NEVER auto-publish","type":"text","marks":[{"type":"strong"}]},{"text":" - Only save to drafts, user publishes manually","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Check API key first","type":"text","marks":[{"type":"strong"}]},{"text":" - Fail fast if not configured","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"List accounts first","type":"text","marks":[{"type":"strong"}]},{"text":" - User may have multiple accounts","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Handle errors gracefully","type":"text","marks":[{"type":"strong"}]},{"text":" - Show clear error messages","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Preserve original content","type":"text","marks":[{"type":"strong"}]},{"text":" - Don't modify user's markdown unnecessarily","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Supported Formats","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Markdown Files (.md)","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"H1 header (# ) → Article title","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"H2/H3 headers (##, ###) → Section headers","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Bold (","type":"text"},{"text":"text","type":"text","marks":[{"type":"strong"}]},{"text":")","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Italic (","type":"text"},{"text":"text","type":"text","marks":[{"type":"em"}]},{"text":")","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Links ","type":"text"},{"text":"text","type":"text","marks":[{"type":"link","attrs":{"href":"url","title":null}}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Blockquotes (> )","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Code blocks (","type":"text"},{"text":"...","type":"text","marks":[{"type":"code_inline"}]},{"text":")","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Lists (- or 1.)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Images ","type":"text"},{"type":"image","attrs":{"alt":"alt","src":"url","title":null,"source":null,"layoutClass":null}},{"text":" → Auto-uploaded to WeChat","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"HTML Files (.html)","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"\u003ctitle>","type":"text","marks":[{"type":"code_inline"}]},{"text":" or ","type":"text"},{"text":"\u003ch1>","type":"text","marks":[{"type":"code_inline"}]},{"text":" → Article title","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"All HTML formatting preserved (styles, tables, etc.)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"\u003cimg>","type":"text","marks":[{"type":"code_inline"}]},{"text":" tags → Images auto-uploaded to WeChat","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"First ","type":"text"},{"text":"\u003cp>","type":"text","marks":[{"type":"code_inline"}]},{"text":" → Auto-extracted as summary","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Supports inline styles and rich formatting","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"HTML Title Extraction Priority:","type":"text","marks":[{"type":"strong"}]}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"\u003ctitle>","type":"text","marks":[{"type":"code_inline"}]},{"text":" tag content","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"First ","type":"text"},{"text":"\u003ch1>","type":"text","marks":[{"type":"code_inline"}]},{"text":" tag content","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"\"Untitled\" as fallback","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"HTML Content Extraction:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"If ","type":"text"},{"text":"\u003cbody>","type":"text","marks":[{"type":"code_inline"}]},{"text":" exists, uses body content","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Otherwise, strips ","type":"text"},{"text":"\u003chtml>","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"\u003chead>","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"\u003c!DOCTYPE>","type":"text","marks":[{"type":"code_inline"}]},{"text":" and uses remaining content","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Article Types","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"news (普通文章)","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Standard WeChat article format","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Full Markdown/HTML support","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Rich text with images","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"newspic (小绿书/图文消息)","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Image-focused format (like Instagram posts)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Maximum 20 images extracted from content","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Text content limited to 1000 characters","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Images auto-uploaded to WeChat","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Example Flow","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Markdown File","type":"text"}]},{"type":"paragraph","content":[{"text":"User: \"把 ~/articles/ai-tools.md 发布到微信公众号\"","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# Step 1: Verify API key\ncat .env | grep WECHAT_API_KEY\n\n# Step 2: List accounts\npython ~/.claude/skills/wechat-article-publisher/scripts/wechat_api.py list-accounts\n\n# Step 3: Publish (assuming single account with appid wx1234567890)\npython ~/.claude/skills/wechat-article-publisher/scripts/wechat_api.py publish \\\n --appid wx1234567890 \\\n --markdown ~/articles/ai-tools.md\n\n# Step 4: Report\n# \"文章已成功发布到公众号草稿箱!请登录微信公众平台预览并发布。\"","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"HTML File","type":"text"}]},{"type":"paragraph","content":[{"text":"User: \"把这个HTML文章发布到公众号:~/articles/newsletter.html\"","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# Step 1: Verify API key\ncat .env | grep WECHAT_API_KEY\n\n# Step 2: List accounts\npython ~/.claude/skills/wechat-article-publisher/scripts/wechat_api.py list-accounts\n\n# Step 3: Publish HTML (auto-detects format)\npython ~/.claude/skills/wechat-article-publisher/scripts/wechat_api.py publish \\\n --appid wx1234567890 \\\n --html ~/articles/newsletter.html\n\n# Step 4: Report\n# \"文章已成功发布到公众号草稿箱!HTML格式已保留。请登录微信公众平台预览并发布。\"","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Error Handling","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"API Key Not Found","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"Error: WECHAT_API_KEY environment variable not set.","type":"text"}]},{"type":"paragraph","content":[{"text":"Solution","type":"text","marks":[{"type":"strong"}]},{"text":": Ask user to set up ","type":"text"},{"text":".env","type":"text","marks":[{"type":"code_inline"}]},{"text":" file with their API key.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Account Not Found","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"Error: ACCOUNT_NOT_FOUND - 公众号不存在或未授权","type":"text"}]},{"type":"paragraph","content":[{"text":"Solution","type":"text","marks":[{"type":"strong"}]},{"text":": Ask user to authorize their account on wx.limyai.com.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Token Expired","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"Error: ACCOUNT_TOKEN_EXPIRED - 公众号授权已过期","type":"text"}]},{"type":"paragraph","content":[{"text":"Solution","type":"text","marks":[{"type":"strong"}]},{"text":": Ask user to re-authorize on wx.limyai.com.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"WeChat API Error","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"Error: WECHAT_API_ERROR - 微信接口调用失败","type":"text"}]},{"type":"paragraph","content":[{"text":"Solution","type":"text","marks":[{"type":"strong"}]},{"text":": May be temporary issue, retry or check WeChat service status.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Best Practices","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Why use API instead of browser automation?","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Reliability","type":"text","marks":[{"type":"strong"}]},{"text":": Direct API calls are more stable than browser automation","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Speed","type":"text","marks":[{"type":"strong"}]},{"text":": No browser startup, page loading, or UI interactions","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Simplicity","type":"text","marks":[{"type":"strong"}]},{"text":": Single command to publish","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Portability","type":"text","marks":[{"type":"strong"}]},{"text":": Works on any system with Python (no macOS-only dependencies)","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Content Guidelines","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Images","type":"text","marks":[{"type":"strong"}]},{"text":": Use public URLs when possible; local images will be uploaded","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Title","type":"text","marks":[{"type":"strong"}]},{"text":": Keep under 64 characters","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Summary","type":"text","marks":[{"type":"strong"}]},{"text":": Auto-extracted from first paragraph if not provided","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Cover","type":"text","marks":[{"type":"strong"}]},{"text":": First image in markdown becomes cover if not specified","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Workflow Efficiency","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"Minimal workflow (1 command):\n- list-accounts → get appid → publish → done\n\nFull workflow (with verification):\n1. Check .env → list accounts → confirm with user\n2. Publish with options → report result","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Troubleshooting","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Q: How do I get a WECHAT_API_KEY?","type":"text"}]},{"type":"paragraph","content":[{"text":"A: Register and authorize your WeChat account at wx.limyai.com to get your API key.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Q: Can I publish to multiple accounts?","type":"text"}]},{"type":"paragraph","content":[{"text":"A: Yes, use ","type":"text"},{"text":"list-accounts","type":"text","marks":[{"type":"code_inline"}]},{"text":" to see all authorized accounts, then specify the target ","type":"text"},{"text":"--appid","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Q: Images not showing in WeChat?","type":"text"}]},{"type":"paragraph","content":[{"text":"A: Ensure images are accessible URLs. Local images are auto-uploaded but may fail if path is incorrect.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Q: Title is too long?","type":"text"}]},{"type":"paragraph","content":[{"text":"A: WeChat limits titles to 64 characters. The script will use the first 64 chars of H1.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Q: What's the difference between news and newspic?","type":"text"}]},{"type":"paragraph","content":[{"text":"A: ","type":"text"},{"text":"news","type":"text","marks":[{"type":"code_inline"}]},{"text":" is standard article format; ","type":"text"},{"text":"newspic","type":"text","marks":[{"type":"code_inline"}]},{"text":" (小绿书) is image-focused with limited text.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"新手上路(用户不知道该输入什么时,走这里)","type":"text"}]},{"type":"paragraph","content":[{"text":"触发","type":"text","marks":[{"type":"strong"}]},{"text":":","type":"text"},{"text":"/公众号发布 新手","type":"text","marks":[{"type":"code_inline"}]},{"text":"、「这个怎么用」「第一次用」「能干嘛」「带我走一遍」, 以及用户输入了技能名却没有给任何任务的时候。","type":"text"}]},{"type":"paragraph","content":[{"text":"这个模式的铁律:","type":"text"},{"text":"不假设、不索取","type":"text","marks":[{"type":"strong"}]},{"text":"。用户可能什么都没准备, 不要一上来就问他要文件、要 API key、要具体需求。按下面四步走:","type":"text"}]},{"type":"paragraph","content":[{"text":"一、先说清楚这是什么(三句话以内)","type":"text","marks":[{"type":"strong"}]}]},{"type":"paragraph","content":[{"text":"一句话:","type":"text"},{"text":"把 Markdown 文章直接发进公众号草稿箱","type":"text","marks":[{"type":"strong"}]},{"text":",图片自动上传。 走的是微信官方接口,不是模拟点击,所以稳定。 发的是草稿,最终点「发表」的还是你。","type":"text"}]},{"type":"paragraph","content":[{"text":"二、给编号选项,让他按回车就能继续","type":"text","marks":[{"type":"strong"}]}]},{"type":"paragraph","content":[{"text":"不要问开放式问题(「你想做什么?」对新手是负担)。给 3 个选项加一个默认:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"想先看哪个?(直接回车 = 1)\n 1. 先看看发出去长什么样(示例)\n 2. 把我的 Markdown 发成草稿\n 3. 先讲讲要准备什么","type":"text"}]},{"type":"paragraph","content":[{"text":"三、直接演示一遍,边做边解释","type":"text","marks":[{"type":"strong"}]}]},{"type":"paragraph","content":[{"text":"选完立刻做给他看,用","type":"text"},{"text":"示例数据","type":"text","marks":[{"type":"strong"}]},{"text":",不需要他提供任何东西。 每做完一步,加一行「💡 刚才发生了什么」,一句话说明这步的意义。","type":"text"}]},{"type":"paragraph","content":[{"text":"四、毕业","type":"text","marks":[{"type":"strong"}]}]},{"type":"paragraph","content":[{"text":"演示完只问一个是非题:「要不要用你自己的文章真跑一遍?」 答是就进正常流程;答否就告诉他随时回来输 ","type":"text"},{"text":"/公众号发布 新手","type":"text","marks":[{"type":"code_inline"}]},{"text":"。","type":"text"}]},{"type":"paragraph","content":[{"text":"关于前置条件","type":"text","marks":[{"type":"strong"}]},{"text":":这个技能需要 公众号的 API 凭据(写在 .env 里的 WECHAT_API_KEY)。 ","type":"text"},{"text":"新手模式下不要提前索取","type":"text","marks":[{"type":"strong"}]},{"text":"——先用示例数据演示完,到第四步真跑的时候再引导他配置, 并说清楚在哪配、怎么拿。新手最容易在这一步流失。","type":"text"}]}]},"metadata":{"date":"2026-11-02","name":"wechat-article-publisher","author":"@skillopedia","source":{"stars":157,"repo_name":"wechat-article-publisher-skill","origin_url":"https://github.com/iamzifei/wechat-article-publisher-skill/blob/HEAD/skills/wechat-article-publisher/SKILL.md","repo_owner":"iamzifei","body_sha256":"740f8785ef295784de00367beec13564bf0d8905a6b79a74cb8ff79c415343d4","cluster_key":"e37bf71bffcb72e8138fc6af72d2896553ead163a70e694b4b0b7af5b05d79f0","clean_bundle":{"format":"clean-skill-bundle-v1","source":"iamzifei/wechat-article-publisher-skill/skills/wechat-article-publisher/SKILL.md","text_bytes":26310,"source_commit":"d3508c44248a196189ef63408c03f18b6b549c31","skills_sh_rank":7288,"attachment_count":2,"text_attachments":2,"attachment_storage":"skill_attachments","binary_attachments":0},"cluster_size":1,"skill_md_path":"skills/wechat-article-publisher/SKILL.md","import_metadata":{"date":"2026-11-02","author":"@skillopedia","version":"v1","category":"integrations-apis","category_label":"Integrations"},"exact_dupes_collapsed_into_this":0},"version":"v1","category":"integrations-apis","import_tag":"skills-sh-top20k-2026-11","description":"Publish Markdown/HTML articles to WeChat Official Account (微信公众号) drafts via API 新手引导:输入 `/公众号发布 新手`、「这个怎么用」「第一次用」「能干嘛」时,走 SKILL.md 的〈新手上路〉,不要直接开始干活。"}},"renderedAt":1793769949258}

WeChat Article Publisher Publish Markdown or HTML content to WeChat Official Account drafts via API, with automatic format conversion. Prerequisites - WECHAT API KEY environment variable set (from .env file) - Python 3.9+ - Authorized WeChat Official Account on wx.limyai.com Scripts Located in : wechat api.py WeChat API client for listing accounts and publishing articles: parse markdown.py Parse Markdown and extract structured data (optional, for advanced use): Workflow Strategy: "API-First Publishing" Unlike browser-based publishing, this skill uses direct API calls for reliable, fast publis…