
Wechat Article Extractor
- 3.7k installs
- 112 repo stars
- Updated February 19, 2026
- freestylefly/wechat-article-extractor-skill
Parsed WeChat article metadata and HTML content in structured JSON format with account identifiers.
About
WeChat Article Extractor parses mp.weixin.qq.com URLs and HTML to extract article metadata (title, author, publish time, cover image), account information (name, avatar, alias), and HTML content from WeChat Official Account posts. Developers use it to integrate WeChat content into data collection workflows, convert articles to structured formats, and handle error cases including deleted content, expired links, rate limits, and account migrations. The tool supports multiple article types (post, video, image, voice, text, repost) and provides configurable options for content extraction depth, link following, tag extraction, and repost metadata inclusion. Response data includes account identifiers, article links, and raw metadata when needed. Extract article metadata: title, author, description, publish time, cover image from WeChat URLs. Parse account info: name, avatar, alias, description, biz ID, QR code from Official Accounts. Support multiple article types: post, video, image, voice, text, repost with type detection.
- Extract article metadata: title, author, description, publish time, cover image from WeChat URLs
- Parse account info: name, avatar, alias, description, biz ID, QR code from Official Accounts
- Support multiple article types: post, video, image, voice, text, repost with type detection
- Handle 16+ error cases: deleted content, expired links, rate limiting, account migration, copyright blocks
- Return structured response with account data, article content (HTML), link parameters, and publish timestamps
Wechat Article Extractor by the numbers
- 3,671 all-time installs (skills.sh)
- +29 installs in the week ending Aug 2, 2026 (Skillselion tracking)
- Ranked #166 of 4,348 Backend & APIs skills by installs in the Skillselion catalog
- Security screen: MEDIUM risk (skills.sh audit)
- Data as of Aug 2, 2026 (Skillselion catalog sync)
wechat-article-extractor capabilities & compatibility
- Capabilities
- parse wechat article urls and extract metadata · extract account information from official accoun · handle article type detection and routing · follow account migration links · extract embedded mp.weixin links within articles · return structured json with 20+ fields · handle rate limiting and access restrictions
- Use cases
- web scraping · data analysis · documentation · orchestration
- Runs
- Runs locally
- Pricing
- Free
npx skills add https://github.com/freestylefly/wechat-article-extractor-skill --skill wechat-article-extractorAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 3.7k |
|---|---|
| repo stars | ★ 112 |
| Security audit | 1 / 3 scanners passed |
| Last updated | February 19, 2026 |
| Repository | freestylefly/wechat-article-extractor-skill ↗ |
What it does
Parse WeChat Official Account article URLs to extract metadata, content, and account information for integration into data pipelines.
Who is it for?
Content aggregation, data pipelines, research automation, cross-platform publishing workflows, WeChat data archival.
Skip if: Real-time notifications, user-facing WeChat clients, bypassing WeChat security restrictions.
When should I use this skill?
User provides WeChat article URL or HTML and needs structured extraction, or building content ingestion workflows.
What you get
Structured article data (metadata, content, account info) ready for database storage, search indexing, or downstream processing.
- Structured article JSON
- Account metadata object
By the numbers
- Supports 6 article types: post, video, image, voice, text, repost
- Handles 16 specific error codes covering access, deletion, rate limits, and account states
- Returns 20+ fields per article including account ID, biz parameter, raw metadata options
Files
WeChat Article Extractor
Extract metadata and content from WeChat Official Account (微信公众号) articles.
Capabilities
- Parse WeChat article URLs (
mp.weixin.qq.com) - Extract article metadata: title, author, description, publish time
- Extract account info: name, avatar, alias, description
- Get article content (HTML)
- Get cover image URL
- Support multiple article types: post, video, image, voice, text, repost
- Handle various error cases: deleted content, expired links, access limits
Usage
Basic Extraction from URL
const { extract } = require('./scripts/extract.js');
const result = await extract('https://mp.weixin.qq.com/s?__biz=...');
// Returns: { done: true, code: 0, data: {...} }Extraction from HTML
const html = await fetch(url).then(r => r.text());
const result = await extract(html, { url: sourceUrl });Options
const result = await extract(url, {
shouldReturnContent: true, // Return HTML content (default: true)
shouldReturnRawMeta: false, // Return raw metadata (default: false)
shouldFollowTransferLink: true, // Follow migrated account links (default: true)
shouldExtractMpLinks: false, // Extract embedded mp.weixin links (default: false)
shouldExtractTags: false, // Extract article tags (default: false)
shouldExtractRepostMeta: false // Extract repost source info (default: false)
});Response Format
Success Response
{
done: true,
code: 0,
data: {
// Account info
account_name: "公众号名称",
account_alias: "微信号",
account_avatar: "头像URL",
account_description: "功能介绍",
account_id: "原始ID",
account_biz: "biz参数",
account_biz_number: 1234567890,
account_qr_code: "二维码URL",
// Article info
msg_title: "文章标题",
msg_desc: "文章摘要",
msg_content: "HTML内容",
msg_cover: "封面图URL",
msg_author: "作者",
msg_type: "post", // post|video|image|voice|text|repost
msg_has_copyright: true,
msg_publish_time: Date,
msg_publish_time_str: "2024/01/15 10:30:00",
// Link params
msg_link: "文章链接",
msg_source_url: "阅读原文链接",
msg_sn: "sn参数",
msg_mid: 1234567890,
msg_idx: 1
}
}Error Response
{
done: false,
code: 1001,
msg: "无法获取文章信息"
}Error Codes
| Code | Message | Description |
|---|---|---|
| 1000 | 文章获取失败 | General failure |
| 1001 | 无法获取文章信息 | Missing title or publish time |
| 1002 | 请求失败 | HTTP request failed |
| 1003 | 响应为空 | Empty response |
| 1004 | 访问过于频繁 | Rate limited |
| 1005 | 脚本解析失败 | Script parsing error |
| 1006 | 公众号已迁移 | Account migrated |
| 2001 | 请提供文章内容或链接 | Missing input |
| 2002 | 链接已过期 | Link expired |
| 2003 | 内容涉嫌侵权 | Content removed (copyright) |
| 2004 | 无法获取迁移后的链接 | Migration link failed |
| 2005 | 内容已被发布者删除 | Content deleted by author |
| 2006 | 内容因违规无法查看 | Content blocked |
| 2007 | 内容发送失败 | Failed to send |
| 2008 | 系统出错 | System error |
| 2009 | 不支持的链接 | Unsupported URL |
| 2010 | 内容获取失败 | Content fetch failed |
| 2011 | 涉嫌过度营销 | Marketing/spam content |
| 2012 | 账号已被屏蔽 | Account blocked |
| 2013 | 账号已自主注销 | Account deleted |
| 2014 | 内容被投诉 | Content reported |
| 2015 | 账号处于迁移流程中 | Account migrating |
| 2016 | 冒名侵权 | Impersonation |
Dependencies
Required npm packages:
cheerio- HTML parsingdayjs- Date formattingrequest-promise- HTTP requestsqs- Query string parsinglodash.unescape- HTML entities
Notes
- Handles various WeChat page structures and anti-scraping measures
- Automatically detects article type from page content
- Supports extracting from Sogou WeChat search results (
weixin.sogou.com) - Some fields may be null depending on article type and page structure
# Dependencies
node_modules/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
package-lock.json
yarn.lock
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Coverage directory used by tools like istanbul
coverage/
# nyc test coverage
.nyc_output
# Grunt intermediate storage
.grunt
# Bower dependency directory
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
.env.local
.env.*.local
# IDE
.idea/
.vscode/
*.swp
*.swo
*~
.DS_Store
# Logs
logs
*.log
# Temporary files
tmp/
temp/
*.tmp
const cheerio = require('cheerio');
const fs = require('fs');
const htmlContent = fs.readFileSync('/Users/canghe/.claude/projects/-Users-canghe/1a7db960-136f-49cc-a1fc-983884c098fd/tool-results/b97eb13.txt', 'utf8');
const data = JSON.parse(htmlContent);
const $ = cheerio.load(data.msg_content, { decodeEntities: false });
let markdown = `---
title: "${data.msg_title}"
author: "${data.msg_author || data.account_name}"
date: "${data.msg_publish_time_str}"
source: "${data.account_name}"
original_url: "${data.msg_link}"
---
# ${data.msg_title}
**作者**: ${data.msg_author || data.account_name}
**发布时间**: ${data.msg_publish_time_str}
**公众号**: ${data.account_name}
**原文链接**: ${data.msg_link}
---
`;
function processElement(elem, isRoot = false) {
const $elem = $(elem);
const tagName = elem.tagName?.toLowerCase();
if (!tagName) return;
if (tagName === 'h2') {
markdown += '\n## ' + $elem.text().trim() + '\n\n';
} else if (tagName === 'h3') {
markdown += '\n### ' + $elem.text().trim() + '\n\n';
} else if (tagName === 'p') {
// Check for nested elements
let text = '';
$elem.contents().each((i, child) => {
if (child.type === 'text') {
text += child.data;
} else if (child.type === 'tag') {
const $child = $(child);
if (child.tagName === 'strong' || child.tagName === 'b' || child.tagName === 'span') {
const childText = $child.text().trim();
if (childText.startsWith('「') && childText.endsWith('」')) {
text += '**' + childText + '**';
} else {
text += childText;
}
} else if (child.tagName === 'br') {
text += '\n';
} else {
text += $child.text();
}
}
});
text = text.trim();
if (text) markdown += text + '\n\n';
} else if (tagName === 'blockquote') {
const text = $elem.text().trim();
if (text) markdown += '> ' + text + '\n\n';
} else if (tagName === 'ol') {
$elem.children('li').each((i, li) => {
const text = $(li).text().trim();
if (text) markdown += (i + 1) + '. ' + text + '\n';
});
markdown += '\n';
} else if (tagName === 'ul') {
$elem.children('li').each((i, li) => {
const text = $(li).text().trim();
if (text) markdown += '- ' + text + '\n';
});
markdown += '\n';
} else if (tagName === 'img') {
const src = $elem.attr('data-src') || $elem.attr('src');
if (src) markdown += '\n\n\n';
} else if (tagName === 'code') {
const text = $elem.text().trim();
if (text) markdown += '`' + text + '`';
} else if (tagName === 'pre' || (tagName === 'section' && $elem.hasClass('code-snippet__fix'))) {
const codeText = $elem.find('code').text() || $elem.text();
if (codeText.trim()) {
markdown += '\n```\n' + codeText.trim() + '\n```\n\n';
}
} else if (tagName === 'section' || tagName === 'div' || tagName === 'center') {
// 递归处理子元素
$elem.children().each((i, child) => {
processElement(child);
});
}
}
// 处理主要内容
const mainSection = $('section[data-plugin="note-to-mp"]');
if (mainSection.length) {
mainSection.children().each((i, child) => {
processElement(child, true);
});
} else {
// 如果没有找到主section,处理body
$('body').children().each((i, child) => {
processElement(child, true);
});
}
// 清理多余的空行
markdown = markdown.replace(/\n{3,}/g, '\n\n');
// 保存文件
const outputPath = '/Users/canghe/Downloads/wechat/爆肝2天用GLM5开发OpenClaw接入微信bot.md';
fs.writeFileSync(outputPath, markdown, 'utf8');
console.log('文件已保存到:', outputPath);
console.log('文件大小:', (markdown.length / 1024).toFixed(2), 'KB');
{
"name": "wechat-article-extractor",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"dependencies": {
"cheerio": "^1.2.0",
"dayjs": "^1.11.19",
"lodash.unescape": "^4.0.1",
"qs": "^6.15.0",
"request-promise": "^4.2.6"
}
},
"node_modules/ajv": {
"version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"license": "MIT",
"peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
"json-schema-traverse": "^0.4.1",
"uri-js": "^4.2.2"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/epoberezkin"
}
},
"node_modules/asn1": {
"version": "0.2.6",
"resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz",
"integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"safer-buffer": "~2.1.0"
}
},
"node_modules/assert-plus": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
"integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=0.8"
}
},
"node_modules/asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
"license": "MIT",
"peer": true
},
"node_modules/aws-sign2": {
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz",
"integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==",
"license": "Apache-2.0",
"peer": true,
"engines": {
"node": "*"
}
},
"node_modules/aws4": {
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.2.tgz",
"integrity": "sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==",
"license": "MIT",
"peer": true
},
"node_modules/bcrypt-pbkdf": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
"integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==",
"license": "BSD-3-Clause",
"peer": true,
"dependencies": {
"tweetnacl": "^0.14.3"
}
},
"node_modules/bluebird": {
"version": "3.7.2",
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
"integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==",
"license": "MIT"
},
"node_modules/boolbase": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
"integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==",
"license": "ISC"
},
"node_modules/call-bind-apply-helpers": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/call-bound": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
"integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.2",
"get-intrinsic": "^1.3.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/caseless": {
"version": "0.12.0",
"resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
"integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==",
"license": "Apache-2.0",
"peer": true
},
"node_modules/cheerio": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.2.0.tgz",
"integrity": "sha512-WDrybc/gKFpTYQutKIK6UvfcuxijIZfMfXaYm8NMsPQxSYvf+13fXUJ4rztGGbJcBQ/GF55gvrZ0Bc0bj/mqvg==",
"license": "MIT",
"dependencies": {
"cheerio-select": "^2.1.0",
"dom-serializer": "^2.0.0",
"domhandler": "^5.0.3",
"domutils": "^3.2.2",
"encoding-sniffer": "^0.2.1",
"htmlparser2": "^10.1.0",
"parse5": "^7.3.0",
"parse5-htmlparser2-tree-adapter": "^7.1.0",
"parse5-parser-stream": "^7.1.2",
"undici": "^7.19.0",
"whatwg-mimetype": "^4.0.0"
},
"engines": {
"node": ">=20.18.1"
},
"funding": {
"url": "https://github.com/cheeriojs/cheerio?sponsor=1"
}
},
"node_modules/cheerio-select": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz",
"integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==",
"license": "BSD-2-Clause",
"dependencies": {
"boolbase": "^1.0.0",
"css-select": "^5.1.0",
"css-what": "^6.1.0",
"domelementtype": "^2.3.0",
"domhandler": "^5.0.3",
"domutils": "^3.0.1"
},
"funding": {
"url": "https://github.com/sponsors/fb55"
}
},
"node_modules/combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
"license": "MIT",
"peer": true,
"dependencies": {
"delayed-stream": "~1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/core-util-is": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
"integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==",
"license": "MIT",
"peer": true
},
"node_modules/css-select": {
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz",
"integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==",
"license": "BSD-2-Clause",
"dependencies": {
"boolbase": "^1.0.0",
"css-what": "^6.1.0",
"domhandler": "^5.0.2",
"domutils": "^3.0.1",
"nth-check": "^2.0.1"
},
"funding": {
"url": "https://github.com/sponsors/fb55"
}
},
"node_modules/css-what": {
"version": "6.2.2",
"resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz",
"integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==",
"license": "BSD-2-Clause",
"engines": {
"node": ">= 6"
},
"funding": {
"url": "https://github.com/sponsors/fb55"
}
},
"node_modules/dashdash": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
"integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==",
"license": "MIT",
"peer": true,
"dependencies": {
"assert-plus": "^1.0.0"
},
"engines": {
"node": ">=0.10"
}
},
"node_modules/dayjs": {
"version": "1.11.19",
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.19.tgz",
"integrity": "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==",
"license": "MIT"
},
"node_modules/delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/dom-serializer": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
"integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==",
"license": "MIT",
"dependencies": {
"domelementtype": "^2.3.0",
"domhandler": "^5.0.2",
"entities": "^4.2.0"
},
"funding": {
"url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
}
},
"node_modules/domelementtype": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
"integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/fb55"
}
],
"license": "BSD-2-Clause"
},
"node_modules/domhandler": {
"version": "5.0.3",
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz",
"integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==",
"license": "BSD-2-Clause",
"dependencies": {
"domelementtype": "^2.3.0"
},
"engines": {
"node": ">= 4"
},
"funding": {
"url": "https://github.com/fb55/domhandler?sponsor=1"
}
},
"node_modules/domutils": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz",
"integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==",
"license": "BSD-2-Clause",
"dependencies": {
"dom-serializer": "^2.0.0",
"domelementtype": "^2.3.0",
"domhandler": "^5.0.3"
},
"funding": {
"url": "https://github.com/fb55/domutils?sponsor=1"
}
},
"node_modules/dunder-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.1",
"es-errors": "^1.3.0",
"gopd": "^1.2.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/ecc-jsbn": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
"integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==",
"license": "MIT",
"peer": true,
"dependencies": {
"jsbn": "~0.1.0",
"safer-buffer": "^2.1.0"
}
},
"node_modules/encoding-sniffer": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.1.tgz",
"integrity": "sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw==",
"license": "MIT",
"dependencies": {
"iconv-lite": "^0.6.3",
"whatwg-encoding": "^3.1.1"
},
"funding": {
"url": "https://github.com/fb55/encoding-sniffer?sponsor=1"
}
},
"node_modules/entities": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
"license": "BSD-2-Clause",
"engines": {
"node": ">=0.12"
},
"funding": {
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
"node_modules/es-define-property": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-errors": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-object-atoms": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/extend": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
"integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
"license": "MIT",
"peer": true
},
"node_modules/extsprintf": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
"integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==",
"engines": [
"node >=0.6.0"
],
"license": "MIT",
"peer": true
},
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
"license": "MIT",
"peer": true
},
"node_modules/fast-json-stable-stringify": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
"integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
"license": "MIT",
"peer": true
},
"node_modules/forever-agent": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
"integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==",
"license": "Apache-2.0",
"peer": true,
"engines": {
"node": "*"
}
},
"node_modules/form-data": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz",
"integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.6",
"mime-types": "^2.1.12"
},
"engines": {
"node": ">= 0.12"
}
},
"node_modules/function-bind": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/get-intrinsic": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.2",
"es-define-property": "^1.0.1",
"es-errors": "^1.3.0",
"es-object-atoms": "^1.1.1",
"function-bind": "^1.1.2",
"get-proto": "^1.0.1",
"gopd": "^1.2.0",
"has-symbols": "^1.1.0",
"hasown": "^2.0.2",
"math-intrinsics": "^1.1.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/get-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
"license": "MIT",
"dependencies": {
"dunder-proto": "^1.0.1",
"es-object-atoms": "^1.0.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/getpass": {
"version": "0.1.7",
"resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
"integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==",
"license": "MIT",
"peer": true,
"dependencies": {
"assert-plus": "^1.0.0"
}
},
"node_modules/gopd": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/har-schema": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz",
"integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==",
"license": "ISC",
"peer": true,
"engines": {
"node": ">=4"
}
},
"node_modules/har-validator": {
"version": "5.1.5",
"resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz",
"integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==",
"deprecated": "this library is no longer supported",
"license": "MIT",
"peer": true,
"dependencies": {
"ajv": "^6.12.3",
"har-schema": "^2.0.0"
},
"engines": {
"node": ">=6"
}
},
"node_modules/has-symbols": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/hasown": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
"license": "MIT",
"dependencies": {
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/htmlparser2": {
"version": "10.1.0",
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.1.0.tgz",
"integrity": "sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==",
"funding": [
"https://github.com/fb55/htmlparser2?sponsor=1",
{
"type": "github",
"url": "https://github.com/sponsors/fb55"
}
],
"license": "MIT",
"dependencies": {
"domelementtype": "^2.3.0",
"domhandler": "^5.0.3",
"domutils": "^3.2.2",
"entities": "^7.0.1"
}
},
"node_modules/htmlparser2/node_modules/entities": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz",
"integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==",
"license": "BSD-2-Clause",
"engines": {
"node": ">=0.12"
},
"funding": {
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
"node_modules/http-signature": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
"integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"assert-plus": "^1.0.0",
"jsprim": "^1.2.2",
"sshpk": "^1.7.0"
},
"engines": {
"node": ">=0.8",
"npm": ">=1.3.7"
}
},
"node_modules/iconv-lite": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
"license": "MIT",
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3.0.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/is-typedarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
"integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==",
"license": "MIT",
"peer": true
},
"node_modules/isstream": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
"integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==",
"license": "MIT",
"peer": true
},
"node_modules/jsbn": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
"integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==",
"license": "MIT",
"peer": true
},
"node_modules/json-schema": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz",
"integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==",
"license": "(AFL-2.1 OR BSD-3-Clause)",
"peer": true
},
"node_modules/json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
"license": "MIT",
"peer": true
},
"node_modules/json-stringify-safe": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
"integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==",
"license": "ISC",
"peer": true
},
"node_modules/jsprim": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz",
"integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==",
"license": "MIT",
"peer": true,
"dependencies": {
"assert-plus": "1.0.0",
"extsprintf": "1.3.0",
"json-schema": "0.4.0",
"verror": "1.10.0"
},
"engines": {
"node": ">=0.6.0"
}
},
"node_modules/lodash": {
"version": "4.17.23",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz",
"integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==",
"license": "MIT"
},
"node_modules/lodash.unescape": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/lodash.unescape/-/lodash.unescape-4.0.1.tgz",
"integrity": "sha512-DhhGRshNS1aX6s5YdBE3njCCouPgnG29ebyHvImlZzXZf2SHgt+J08DHgytTPnpywNbO1Y8mNUFyQuIDBq2JZg==",
"license": "MIT"
},
"node_modules/math-intrinsics": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mime-types": {
"version": "2.1.35",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"license": "MIT",
"peer": true,
"dependencies": {
"mime-db": "1.52.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/nth-check": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
"integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==",
"license": "BSD-2-Clause",
"dependencies": {
"boolbase": "^1.0.0"
},
"funding": {
"url": "https://github.com/fb55/nth-check?sponsor=1"
}
},
"node_modules/oauth-sign": {
"version": "0.9.0",
"resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz",
"integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==",
"license": "Apache-2.0",
"peer": true,
"engines": {
"node": "*"
}
},
"node_modules/object-inspect": {
"version": "1.13.4",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
"integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/parse5": {
"version": "7.3.0",
"resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz",
"integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==",
"license": "MIT",
"dependencies": {
"entities": "^6.0.0"
},
"funding": {
"url": "https://github.com/inikulin/parse5?sponsor=1"
}
},
"node_modules/parse5-htmlparser2-tree-adapter": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz",
"integrity": "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==",
"license": "MIT",
"dependencies": {
"domhandler": "^5.0.3",
"parse5": "^7.0.0"
},
"funding": {
"url": "https://github.com/inikulin/parse5?sponsor=1"
}
},
"node_modules/parse5-parser-stream": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/parse5-parser-stream/-/parse5-parser-stream-7.1.2.tgz",
"integrity": "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==",
"license": "MIT",
"dependencies": {
"parse5": "^7.0.0"
},
"funding": {
"url": "https://github.com/inikulin/parse5?sponsor=1"
}
},
"node_modules/parse5/node_modules/entities": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz",
"integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==",
"license": "BSD-2-Clause",
"engines": {
"node": ">=0.12"
},
"funding": {
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
"node_modules/performance-now": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
"integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==",
"license": "MIT",
"peer": true
},
"node_modules/psl": {
"version": "1.15.0",
"resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz",
"integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==",
"license": "MIT",
"dependencies": {
"punycode": "^2.3.1"
},
"funding": {
"url": "https://github.com/sponsors/lupomontero"
}
},
"node_modules/punycode": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/qs": {
"version": "6.15.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz",
"integrity": "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==",
"license": "BSD-3-Clause",
"dependencies": {
"side-channel": "^1.1.0"
},
"engines": {
"node": ">=0.6"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/request": {
"version": "2.88.2",
"resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz",
"integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==",
"deprecated": "request has been deprecated, see https://github.com/request/request/issues/3142",
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"aws-sign2": "~0.7.0",
"aws4": "^1.8.0",
"caseless": "~0.12.0",
"combined-stream": "~1.0.6",
"extend": "~3.0.2",
"forever-agent": "~0.6.1",
"form-data": "~2.3.2",
"har-validator": "~5.1.3",
"http-signature": "~1.2.0",
"is-typedarray": "~1.0.0",
"isstream": "~0.1.2",
"json-stringify-safe": "~5.0.1",
"mime-types": "~2.1.19",
"oauth-sign": "~0.9.0",
"performance-now": "^2.1.0",
"qs": "~6.5.2",
"safe-buffer": "^5.1.2",
"tough-cookie": "~2.5.0",
"tunnel-agent": "^0.6.0",
"uuid": "^3.3.2"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/request-promise": {
"version": "4.2.6",
"resolved": "https://registry.npmjs.org/request-promise/-/request-promise-4.2.6.tgz",
"integrity": "sha512-HCHI3DJJUakkOr8fNoCc73E5nU5bqITjOYFMDrKHYOXWXrgD/SBaC7LjwuPymUprRyuF06UK7hd/lMHkmUXglQ==",
"deprecated": "request-promise has been deprecated because it extends the now deprecated request package, see https://github.com/request/request/issues/3142",
"license": "ISC",
"dependencies": {
"bluebird": "^3.5.0",
"request-promise-core": "1.1.4",
"stealthy-require": "^1.1.1",
"tough-cookie": "^2.3.3"
},
"engines": {
"node": ">=0.10.0"
},
"peerDependencies": {
"request": "^2.34"
}
},
"node_modules/request-promise-core": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.4.tgz",
"integrity": "sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw==",
"license": "ISC",
"dependencies": {
"lodash": "^4.17.19"
},
"engines": {
"node": ">=0.10.0"
},
"peerDependencies": {
"request": "^2.34"
}
},
"node_modules/request/node_modules/qs": {
"version": "6.5.5",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.5.5.tgz",
"integrity": "sha512-mzR4sElr1bfCaPJe7m8ilJ6ZXdDaGoObcYR0ZHSsktM/Lt21MVHj5De30GQH2eiZ1qGRTO7LCAzQsUeXTNexWQ==",
"license": "BSD-3-Clause",
"peer": true,
"engines": {
"node": ">=0.6"
}
},
"node_modules/safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT",
"peer": true
},
"node_modules/safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"license": "MIT"
},
"node_modules/side-channel": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
"integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
"object-inspect": "^1.13.3",
"side-channel-list": "^1.0.0",
"side-channel-map": "^1.0.1",
"side-channel-weakmap": "^1.0.2"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/side-channel-list": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
"integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
"object-inspect": "^1.13.3"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/side-channel-map": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
"integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
"license": "MIT",
"dependencies": {
"call-bound": "^1.0.2",
"es-errors": "^1.3.0",
"get-intrinsic": "^1.2.5",
"object-inspect": "^1.13.3"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/side-channel-weakmap": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
"integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
"license": "MIT",
"dependencies": {
"call-bound": "^1.0.2",
"es-errors": "^1.3.0",
"get-intrinsic": "^1.2.5",
"object-inspect": "^1.13.3",
"side-channel-map": "^1.0.1"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/sshpk": {
"version": "1.18.0",
"resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz",
"integrity": "sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"asn1": "~0.2.3",
"assert-plus": "^1.0.0",
"bcrypt-pbkdf": "^1.0.0",
"dashdash": "^1.12.0",
"ecc-jsbn": "~0.1.1",
"getpass": "^0.1.1",
"jsbn": "~0.1.0",
"safer-buffer": "^2.0.2",
"tweetnacl": "~0.14.0"
},
"bin": {
"sshpk-conv": "bin/sshpk-conv",
"sshpk-sign": "bin/sshpk-sign",
"sshpk-verify": "bin/sshpk-verify"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/stealthy-require": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz",
"integrity": "sha512-ZnWpYnYugiOVEY5GkcuJK1io5V8QmNYChG62gSit9pQVGErXtrKuPC55ITaVSukmMta5qpMU7vqLt2Lnni4f/g==",
"license": "ISC",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/tough-cookie": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz",
"integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==",
"license": "BSD-3-Clause",
"dependencies": {
"psl": "^1.1.28",
"punycode": "^2.1.1"
},
"engines": {
"node": ">=0.8"
}
},
"node_modules/tunnel-agent": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
"integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==",
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"safe-buffer": "^5.0.1"
},
"engines": {
"node": "*"
}
},
"node_modules/tweetnacl": {
"version": "0.14.5",
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
"integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==",
"license": "Unlicense",
"peer": true
},
"node_modules/undici": {
"version": "7.22.0",
"resolved": "https://registry.npmjs.org/undici/-/undici-7.22.0.tgz",
"integrity": "sha512-RqslV2Us5BrllB+JeiZnK4peryVTndy9Dnqq62S3yYRRTj0tFQCwEniUy2167skdGOy3vqRzEvl1Dm4sV2ReDg==",
"license": "MIT",
"engines": {
"node": ">=20.18.1"
}
},
"node_modules/uri-js": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
"integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
"license": "BSD-2-Clause",
"peer": true,
"dependencies": {
"punycode": "^2.1.0"
}
},
"node_modules/uuid": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
"integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==",
"deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.",
"license": "MIT",
"peer": true,
"bin": {
"uuid": "bin/uuid"
}
},
"node_modules/verror": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
"integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==",
"engines": [
"node >=0.6.0"
],
"license": "MIT",
"peer": true,
"dependencies": {
"assert-plus": "^1.0.0",
"core-util-is": "1.0.2",
"extsprintf": "^1.2.0"
}
},
"node_modules/whatwg-encoding": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz",
"integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==",
"deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation",
"license": "MIT",
"dependencies": {
"iconv-lite": "0.6.3"
},
"engines": {
"node": ">=18"
}
},
"node_modules/whatwg-mimetype": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz",
"integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==",
"license": "MIT",
"engines": {
"node": ">=18"
}
}
}
}
{
"dependencies": {
"cheerio": "^1.2.0",
"dayjs": "^1.11.19",
"lodash.unescape": "^4.0.1",
"qs": "^6.15.0",
"request-promise": "^4.2.6"
}
}
WeChat Article Extractor
 
一个 Claude Code Skill,用于提取微信公众号文章的元数据和内容。支持多种文章类型,包括图文、视频、图片集、语音和转载文章。当用户需要提供微信公众号文章链接(mp.weixin.qq.com)时,Claude 会自动触发此 Skill 来提取文章信息。
功能特性
- 解析微信公众号文章 URL (
mp.weixin.qq.com) - 提取文章元数据:标题、作者、摘要、发布时间
- 获取公众号信息:名称、头像、微信号、功能介绍
- 提取文章内容(HTML 格式)
- 获取封面图片 URL
- 支持多种文章类型:图文、视频、图片集、语音、纯文字、转载
- 处理各种异常情况:内容删除、链接过期、访问限制、账号迁移等
- 支持搜狗微信搜索结果的解析 (
weixin.sogou.com) - 可选提取文章标签和内嵌链接
安装
这是一个 Claude Code Skill,可以通过以下方式安装:
通过 Claude Code 安装(推荐)
在 Claude Code 中运行:
/skill install wechat-article-extractor或指定目录安装:
/skill install /path/to/wechat-article-extractor-skill手动克隆安装
git clone https://github.com/yourusername/wechat-article-extractor-skill.git
cd wechat-article-extractor-skill
npm install然后在 Claude Code 中将该目录作为 Skill 加载。
使用方法
基本用法 - 从 URL 提取
const { extract } = require('./scripts/extract.js');
async function main() {
const url = 'https://mp.weixin.qq.com/s?__biz=...&mid=...&idx=...&sn=...';
const result = await extract(url);
if (result.done) {
console.log('文章标题:', result.data.msg_title);
console.log('公众号:', result.data.account_name);
console.log('发布时间:', result.data.msg_publish_time_str);
} else {
console.error('提取失败:', result.msg);
}
}
main();从 HTML 内容提取
如果你已经获取了页面 HTML,可以直接传入:
const { extract } = require('./scripts/extract.js');
async function main() {
const html = await fetch(url).then(r => r.text());
const result = await extract(html, { url: sourceUrl });
console.log(result);
}
main();高级选项
const result = await extract(url, {
shouldReturnContent: true, // 返回 HTML 内容(默认:true)
shouldReturnRawMeta: false, // 返回原始元数据(默认:false)
shouldFollowTransferLink: true, // 自动跟随迁移后的公众号链接(默认:true)
shouldExtractMpLinks: false, // 提取内嵌的微信公众号链接(默认:false)
shouldExtractTags: false, // 提取文章标签(默认:false)
shouldExtractRepostMeta: false // 提取转载来源信息(默认:false)
});响应格式
成功响应
{
done: true,
code: 0,
data: {
// 公众号信息
account_name: "公众号名称",
account_alias: "微信号",
account_avatar: "头像URL",
account_description: "功能介绍",
account_id: "原始ID",
account_biz: "biz参数",
account_biz_number: 1234567890,
account_qr_code: "二维码URL",
// 文章信息
msg_title: "文章标题",
msg_desc: "文章摘要",
msg_content: "HTML内容",
msg_cover: "封面图URL",
msg_author: "作者",
msg_type: "post", // post|video|image|voice|text|repost
msg_has_copyright: true,
msg_publish_time: Date,
msg_publish_time_str: "2024/01/15 10:30:00",
// 链接参数
msg_link: "文章链接",
msg_source_url: "阅读原文链接",
msg_sn: "sn参数",
msg_mid: 1234567890,
msg_idx: 1
}
}错误响应
{
done: false,
code: 1001,
msg: "无法获取文章信息"
}错误代码表
| 代码 | 错误信息 | 说明 |
|---|---|---|
| 1000 | 文章获取失败 | 一般性失败 |
| 1001 | 无法获取文章信息 | 缺少标题或发布时间 |
| 1002 | 请求失败 | HTTP 请求失败 |
| 1003 | 响应为空 | 空响应 |
| 1004 | 访问过于频繁 | 被限流 |
| 1005 | 脚本解析失败 | 页面脚本解析错误 |
| 1006 | 公众号已迁移 | 账号已迁移,包含新链接 |
| 2001 | 请提供文章内容或链接 | 缺少输入参数 |
| 2002 | 链接已过期 | 链接已失效 |
| 2003 | 内容涉嫌侵权 | 内容因侵权被移除 |
| 2004 | 无法获取迁移后的链接 | 迁移链接获取失败 |
| 2005 | 内容已被发布者删除 | 作者已删除内容 |
| 2006 | 内容因违规无法查看 | 内容被平台屏蔽 |
| 2007 | 内容发送失败 | 发送失败 |
| 2008 | 系统出错 | 系统错误 |
| 2009 | 不支持的链接 | URL 格式不支持 |
| 2010 | 内容获取失败 | 内容获取失败 |
| 2011 | 涉嫌过度营销 | 营销/垃圾内容 |
| 2012 | 账号已被屏蔽 | 账号被封禁 |
| 2013 | 账号已自主注销 | 账号已注销 |
| 2014 | 内容被投诉 | 内容被举报 |
| 2015 | 账号处于迁移流程中 | 账号正在迁移 |
| 2016 | 冒名侵权 | 冒充侵权 |
支持的文章类型
| 类型 | 说明 | msg_type |
|---|---|---|
| 图文 | 普通图文文章 | post |
| 视频 | 视频内容 | video |
| 图片集 | 多张图片展示 | image |
| 语音 | 音频内容 | voice |
| 纯文字 | 无标题文字内容 | text |
| 转载 | 转载他人文章 | repost |
项目结构
wechat-article-extractor-skill/
├── scripts/
│ ├── extract.js # 核心提取逻辑
│ └── errors.js # 错误代码定义
├── SKILL.md # Skill 定义文件(Claude Skill 格式,包含触发条件和描述)
├── package.json # 项目配置
└── README.md # 本文件依赖项
cheerio- 服务端 HTML 解析dayjs- 日期格式化request-promise- HTTP 请求qs- 查询字符串解析lodash.unescape- HTML 实体解码
注意事项
1. 频率限制: 频繁请求可能会导致 IP 被暂时封禁,建议添加适当的延迟 2. 页面结构: 微信页面结构可能会变化,如遇问题请检查是否为最新版本 3. Cookie: 某些文章可能需要登录才能访问完整内容 4. 反爬措施: 请遵守微信的使用条款,合理使用本工具
示例应用
批量提取文章
const { extract } = require('./scripts/extract.js');
async function batchExtract(urls) {
const results = [];
for (const url of urls) {
try {
const result = await extract(url);
if (result.done) {
results.push({
title: result.data.msg_title,
author: result.data.account_name,
publishTime: result.data.msg_publish_time_str
});
}
// 添加延迟避免被限流
await new Promise(r => setTimeout(r, 1000));
} catch (err) {
console.error(`提取失败: ${url}`, err.message);
}
}
return results;
}
const urls = [
'https://mp.weixin.qq.com/s?__biz=...',
'https://mp.weixin.qq.com/s?__biz=...'
];
batchExtract(urls).then(console.log);保存为 Markdown
const { extract } = require('./scripts/extract.js');
const fs = require('fs');
async function saveAsMarkdown(url, filename) {
const result = await extract(url);
if (!result.done) {
console.error('提取失败:', result.msg);
return;
}
const { data } = result;
const markdown = `
# ${data.msg_title}
> 作者: ${data.msg_author || data.account_name}
> 公众号: ${data.account_name}
> 发布时间: ${data.msg_publish_time_str}
${data.msg_content}
---
原文链接: [${data.msg_link}](${data.msg_link})
`;
fs.writeFileSync(filename, markdown);
console.log(`已保存: ${filename}`);
}
saveAsMarkdown('https://mp.weixin.qq.com/s?__biz=...', 'article.md');许可证
MIT
贡献
欢迎提交 Issue 和 Pull Request!
更新日志
v1.0.0
- 初始版本发布
- 支持基本的文章信息提取
- 支持多种文章类型
- 完善的错误处理机制
module.exports = {
1000: '文章获取失败',
1001: '无法获取文章信息',
1002: '请求失败',
1003: '响应为空',
1004: '访问过于频繁',
1005: '脚本解析失败',
1006: '公众号已迁移',
2001: '请提供文章内容或链接',
2002: '链接已过期',
2003: '内容涉嫌侵权,无法查看',
2004: '无法获取迁移后的链接',
2005: '内容已被发布者删除',
2006: '内容因违规无法查看',
2007: '内容发送失败无法查看',
2008: '系统出错',
2009: '不支持的链接',
2010: '内容获取失败',
2011: '由用户投诉并经平台审核,涉嫌过度营销、骚扰用户',
2012: '此帐号已被屏蔽,内容无法查看',
2013: '此帐号已自主注销,内容无法查看',
2014: '此内容被投诉且经审核确认存在不实信息',
2015: '此帐号处于帐号迁移流程中',
2016: '由用户投诉并经平台审核,涉嫌冒名侵权'
};
const qs = require('qs');
const dayjs = require('dayjs');
const request = require('request-promise');
const cheerio = require('cheerio');
const unescape = require('lodash.unescape');
const errors = require('./errors');
const defaultConfig = {
shouldReturnRawMeta: false,
shouldReturnContent: true,
shouldFollowTransferLink: true,
shouldExtractMpLinks: false,
shouldExtractTags: false,
shouldExtractRepostMeta: false
};
function getError(code) {
return { done: false, code, msg: errors[code] };
}
function normalizeUrl(url = '') {
const parts = url.replace(/&/g, '&').split('?');
const querys = qs.stringify(qs.parse(parts[1]));
return querys ? `${parts[0]}?${querys}` : parts[0];
}
function getParameterByName(name, url) {
name = name.replace(/[\[\]]/g, '\\$&');
const regex = new RegExp('[?&]' + name + '(=([^&#]*)|&|#|$)');
const results = regex.exec(url);
if (!results) return null;
if (!results[2]) return '';
return decodeURIComponent(results[2].replace(/\+/g, ' '));
}
function parseUrlParams(url) {
if (!url) return {};
const rs = require('querystring').parse(url.replace(/&/g, '&').split('?')[1]);
return {
mid: rs.mid * 1,
idx: rs.idx * 1,
sn: rs.sn,
biz: rs.__biz
};
}
async function extract(input, options = {}) {
const config = Object.assign({}, defaultConfig, options);
const {
shouldReturnRawMeta,
shouldReturnContent,
shouldFollowTransferLink,
shouldExtractMpLinks,
shouldExtractTags,
shouldExtractRepostMeta
} = config;
if (!input) return getError(2001);
let paramType = 'HTML';
let url = options.url ? normalizeUrl(options.url) : null;
let rawUrl = null;
let html = input;
let type = 'post';
let hasCopyright = false;
// Handle URL input
if (/^http/.test(input)) {
const normalized = normalizeUrl(input);
if (!/https?:\/\/mp\.weixin\.qq\.com/.test(normalized) &&
!/https?:\/\/weixin\.sogou\.com/.test(normalized)) {
return getError(2009);
}
paramType = 'URL';
rawUrl = normalized;
if (!url) url = normalized;
const host = /weixin\.sogou\.com/.test(normalized) ? 'weixin.sogou.com' : 'mp.weixin.qq.com';
try {
html = await request({
uri: normalized,
method: 'GET',
headers: {
'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
'Host': host
}
});
} catch (e) {
return getError(1002);
}
} else {
html = input.replace(/\\n/g, '');
}
if (!html) return getError(1003);
// Check for error pages
if (html.includes('访问过于频繁') && !html.includes('js_content')) {
return paramType === 'URL' ? getError(1004) : getError(2010);
}
if (html.includes('链接已过期') && !html.includes('js_content')) return getError(2002);
if (html.includes('被投诉且经审核涉嫌侵权,无法查看')) return getError(2003);
if (html.includes('该公众号已迁移')) {
const match = html.match(/var\stransferTargetLink\s=\s'(.*?)';/);
if (match && match[1]) {
if (shouldFollowTransferLink) {
return await extract(match[1]);
}
return { ...getError(1006), url: match[1] };
}
return getError(2004);
}
if (html.includes('该内容已被发布者删除')) return getError(2005);
if (html.includes('此内容因违规无法查看')) return getError(2006);
if (html.includes('此内容发送失败无法查看')) return getError(2007);
if (html.includes('由用户投诉并经平台审核,涉嫌过度营销')) return getError(2011);
if (html.includes('此帐号已被屏蔽') && !html.includes('id="js_content"')) return getError(2012);
if (html.includes('此帐号已自主注销') && !html.includes('id="js_content"')) return getError(2013);
if (!html.includes('id="js_content"') && html.includes('此帐号处于帐号迁移流程中')) return getError(2015);
if (html.includes('page_rumor') && !html.includes('id="js_content"')) return getError(2014);
if (html.includes('投诉类型') && html.includes('冒名侵权')) return getError(2016);
if (!html.includes('id="js_content"') && !html.includes('id=\\"js_content\\"')) {
if (html.includes('cover_url')) {
type = 'image';
} else {
return getError(1000);
}
}
// Prepare HTML
html = html.replace('>微信号', ' id="append-account-alias">微信号')
.replace('>功能介绍', ' id="append-account-desc">功能介绍')
.replace(/\n\s+<script/g, '\n\n<script');
const $ = cheerio.load(html, { decodeEntities: false });
// Detect type and copyright
if ($('#copyright_logo')?.text().includes('原创')) hasCopyright = true;
if (/video/.test($('body').attr('class'))) type = 'video';
if ($('#js_content > #img_list').length) type = 'image';
if ($('#js_share_content').length) type = 'repost';
if ($('.page_share_audio').length || $('#voice_parent').length) type = 'voice';
if (/share_media_text/.test(html)) type = 'text';
// Check for expired link or system error
if ($('.weui-msg .weui-msg__title').text().trim() === '链接已过期') return getError(2002);
if ($('.global_error_msg.warn').text().trim().includes('系统出错')) return getError(2008);
// Extract basic info
const basic = {
accountName: $('.profile_nickname').text() || null,
accountBiz: null,
accountBizNumber: null,
accountId: null,
accountAvatar: null
};
const accountAliasPrev = $('#append-account-alias');
let accountAlias = accountAliasPrev.siblings('span').text() || null;
const accountDescPrev = $('#append-account-desc');
let accountDesc = accountDescPrev.siblings('span').text() || null;
if (!accountDesc) {
const $accountDesc = $('.profile_meta_value');
if ($accountDesc[1]) {
try {
const text = $accountDesc[1].children[0].data;
if (text?.length > 10) accountDesc = text;
} catch (e) {}
}
}
const post = {
msg_has_copyright: hasCopyright,
msg_content: shouldReturnContent ? $('#js_content').html() : null
};
// Extract author
try {
const author = $("meta[name='author']").attr('content');
if (author) post.msg_author = author;
} catch (e) {
const $author = $('#js_author_name');
if ($author.length) {
const info = $author.text().trim();
if (info) post.msg_author = info;
}
}
// Extract from scripts
const scripts = html.match(/<script[\s\S]*?>([\s\S]*?)<\/script>/gi) || [];
const extra = { biz: null, sn: null, mid: null, idx: null, msg_title: null, user_name: null, nick_name: null, hd_head_img: null };
let picturePageInfoList = null;
for (const script of scripts) {
// Picture type
if (script.includes('picture_page_info_list') && script.includes('https://mmbiz.qpic.cn')) {
try {
const lines = script.split('\n');
const code = lines.slice(1, lines.length - 2).join('\n').trim()
.replace(/^\(function\(\) {/, '').replace(/}\)\(\);$/, '');
const fn = new Function(`var x = {}; ${code.replace(/window\./g, 'x.').replace('//g', '/\\n/g')}\nreturn x;`);
const result = fn();
if (result.picture_page_info_list) picturePageInfoList = result.picture_page_info_list;
} catch (e) {}
}
// Voice type
if (type === 'voice' && script.includes('voiceid')) {
const lines = script.split(/\n|\r/).filter(one => one.includes('voiceid'))
.sort((a, b) => a.length > b.length ? -1 : 1);
if (lines.length) {
const val = lines[0].replace(/['"|:,voiceid\s]/g, '');
if (val) post.msg_source_url = `https://res.wx.qq.com/voice/getvoice?mediaid=${val.trim()}`;
}
}
// Extract extra fields
for (const field of Object.keys(extra)) {
const reg = new RegExp(`var\\s+${field}\\s*=`);
if (reg.test(script) && !extra[field]) {
try {
const line = script.split('\n').filter(one => reg.test(one))[0];
const fn = new Function(`${line}\nreturn ${field};`);
extra[field] = fn();
} catch (e) {}
}
if (!extra[field]) {
const reg2 = new RegExp(`window\\.${field}\\s*=`);
if (reg2.test(script)) {
try {
const line = script.split('\n').filter(one => reg2.test(one))[0];
const fn = new Function(`window = {}; ${line}\nreturn window.${field};`);
extra[field] = fn();
} catch (e) {}
}
}
}
// Data from d object (video/image/voice)
if ((type === 'image' || type === 'voice') && script.includes('d.title =')) {
try {
const lines = script.split('\n').filter(line => !!line.trim());
const codeLines = lines.filter((line, index) =>
/d\./.test(line) || (lines[index - 1] && lines[index - 1].includes('d.') && !line.includes('}'))
);
let code = `var d = {}; function getXmlValue(path) { return false; }\n` +
codeLines.join('\n').replace('var d = _g.cgiData;', 'var d = {}') +
'\nreturn d;';
code = `var _g = {}; ${code}`;
const fn = new Function(code);
const data = fn();
basic.accountName = data.nick_name;
basic.accountAvatar = data.hd_head_img;
basic.accountId = data.user_name;
if (!basic.accountBiz && data.biz) {
basic.accountBiz = data.biz;
basic.accountBizNumber = Buffer.from(data.biz, 'base64').toString() * 1;
}
post.msg_title = data.title;
post.msg_desc = null;
post.msg_cover = null;
post.msg_link = data.msg_link || null;
post.msg_sn = data.sn || null;
post.msg_idx = data.idx ? data.idx * 1 : null;
post.msg_mid = data.mid ? data.mid * 1 : null;
if (type === 'video') {
const vidMatch = html.match(/vid\s*:\s*'(.*?)'/);
if (vidMatch) data.vid = vidMatch[1];
post.msg_cover = $("meta[property='og:image']").attr('content');
}
if (type === 'video' || type === 'voice') {
post.msg_content = $("meta[name='description']").attr('content');
}
if (data.create_time) {
post.msg_publish_time = new Date(data.create_time * 1000);
post.msg_publish_time_str = dayjs(post.msg_publish_time).format('YYYY/MM/DD HH:mm:ss');
}
if (shouldReturnRawMeta) post.raw_data = data;
} catch (e) {
return getError(1005);
}
}
// Post/repost type
if ((type === 'post' || type === 'repost') && script.includes('var msg_link =')) {
try {
const lines = script.split('\n');
let code = lines.slice(1, lines.length - 1)
.filter(line => !line.includes('var title'))
.map(line => {
if (/var\s+msg_desc/.test(line)) {
line = line.replace(/`/g, "'").replace(/"/g, '`');
}
return line;
}).join('\n');
code = `var window = { location: { protocol: 'https' } };
var document = {
addEventListener: function() {},
getElementById: function() {
return { classList: { remove: function() {}, add: function() {} } };
}
};
var location = { protocol: "https" };\n${code}`;
const vars = code.match(/var\s(.*?)\s=/g)?.map(key => key.split(' ')[1]).filter(k => k !== 'window') || [];
let rs = ';\nvar rs = {';
vars.forEach(key => {
rs += `"${key}": typeof ${key} !== 'undefined' ? ${key} : null,`;
});
rs += '}\nreturn rs;';
const stringProto = `String.prototype.html = function(encode) {
var replace = ["'", "'", """, '"', " ", " ", ">", ">", "<", "<", "¥", "¥", "&", "&"];
var replaceReverse = ["&", "&", "¥", "¥", "<", "<", ">", ">", " ", " ", '"', """, "'", "'"];
var target = encode ? replaceReverse : replace;
for (var i = 0, str = this; i < target.length; i += 2) {
str = str.replace(new RegExp(target[i], 'g'), target[i + 1]);
}
return str;
};`;
const fn = new Function(stringProto + code + rs);
const data = fn();
// Fallback for biz
if (!basic.accountBiz) {
const reg = new RegExp(`var\\s+biz\\s*=`);
const matched = html.split('\n').find(line => reg.test(line) && line.length > 10);
if (matched) {
try {
const bizFn = new Function(`${matched}; return biz;`);
const rs = bizFn();
if (rs) {
basic.accountBiz = rs;
basic.accountBizNumber = Buffer.from(rs, 'base64').toString() * 1;
}
} catch (e) {}
}
}
['msg_title', 'msg_desc', 'msg_link', 'msg_source_url'].forEach(key => {
post[key] = data[key] || null;
});
post.msg_cover = data.msg_cdn_url;
post.msg_article_type = data._ori_article_type || null;
post.msg_publish_time = new Date(data.ct * 1000);
post.msg_publish_time_str = dayjs(post.msg_publish_time).format('YYYY/MM/DD HH:mm:ss');
if (shouldReturnRawMeta) post.raw_data = data;
basic.accountId = data.user_name;
basic.accountAvatar = data.ori_head_img_url;
if (!basic.accountName && data.nickname) basic.accountName = data.nickname;
} catch (e) {
return getError(1005);
}
}
}
// Set extracted extra fields
if (extra.biz) {
basic.accountBiz = extra.biz;
basic.accountBizNumber = Buffer.from(extra.biz, 'base64').toString() * 1;
}
post.msg_sn = extra.sn || post.msg_sn || null;
post.msg_idx = extra.idx ? extra.idx * 1 : post.msg_idx || null;
post.msg_mid = extra.mid ? extra.mid * 1 : post.msg_mid || null;
// Fallback for missing fields
if (!post.msg_publish_time) {
const date = $('#post-date').text() || $('#publish_time').text();
if (date) post.msg_publish_time = new Date(date);
}
if (!post.msg_publish_time && html.includes('.ct')) {
const line = html.split('\n').find(one => one.includes('.ct'));
const matched = /'(\d+)'/g.exec(line);
if (matched && matched[1]?.length >= 10) {
post.msg_publish_time = new Date(matched[1] * 1000);
}
}
if (!post.msg_title) {
const title = $('.rich_media_title').text();
if (title) post.msg_title = title.trim();
}
// Set account info from extra
if (!basic.accountId && extra.user_name) basic.accountId = extra.user_name;
if (!basic.accountName && extra.nick_name) basic.accountName = extra.nick_name;
if (!basic.accountAvatar && extra.hd_head_img) basic.accountAvatar = extra.hd_head_img;
if (!basic.accountName && $('.wx_follow_nickname')) {
const name = $('.wx_follow_nickname').text();
if (name) basic.accountName = name.trim();
}
// Build result
const data = {
account_name: basic.accountName,
account_alias: accountAlias,
account_avatar: basic.accountAvatar?.length > 10 ? basic.accountAvatar : null,
account_description: accountDesc,
account_id: basic.accountId,
account_biz: basic.accountBiz,
account_biz_number: basic.accountBizNumber,
account_qr_code: `https://open.weixin.qq.com/qr/code?username=${basic.accountId || accountAlias}`,
...post,
msg_type: type
};
// Clean empty strings to null
for (const key in data) {
if (data[key] === '') data[key] = null;
}
// Handle text type (no title)
if (!data.msg_title && type === 'post') {
data.msg_type = 'text';
const title = $("meta[property='og:title']").attr('content');
const desc = $("meta[property='og:description']").attr('content');
if (title) {
data.msg_title = title;
const rawContent = $('#js_panel_like_title').html();
data.msg_content = rawContent ? rawContent.trim().replace(/\n/g, '<br/>') : title;
}
if (!title && desc) data.msg_title = desc;
}
// Fallback for time
if (!data.msg_publish_time) {
const matched = html.match(/d\.ct\s*=\s*"(\d+)"/);
if (matched && matched[1]) {
data.msg_publish_time = new Date(matched[1] * 1000);
data.msg_publish_time_str = dayjs(data.msg_publish_time).format('YYYY/MM/DD HH:mm:ss');
}
}
// Fallback for link params
if (!data.msg_mid || !data.msg_link) {
let linkUrl = options?.url || rawUrl || $("meta[property='og:url']").attr('content');
if (linkUrl && /^http/.test(linkUrl) && /mid/.test(linkUrl) && /__biz/.test(linkUrl)) {
linkUrl = linkUrl.replace(/&/g, '&');
if (!data.msg_link) data.msg_link = linkUrl;
if (!data.msg_mid) data.msg_mid = getParameterByName('mid', linkUrl);
if (!data.msg_idx) data.msg_idx = getParameterByName('idx', linkUrl);
if (!data.msg_sn) data.msg_sn = getParameterByName('sn', linkUrl);
}
}
// Unescape title
if (data.msg_title) data.msg_title = unescape(data.msg_title);
// Handle video content
if (data.msg_type === 'video') {
if (!data.msg_content) {
data.msg_content = data.msg_title;
} else {
data.msg_content = data.msg_content
.replace(/\\x26/g, '&')
.replace(/\\x0a/g, '<br/>');
}
}
// Final fallback for title
if (!data.msg_title) {
const title = $("meta[property='og:title']").attr('content');
if (title) data.msg_title = title;
}
// Fallback for description
if (!data.msg_desc) {
data.msg_desc = $("meta[property='og:description']").attr('content') ||
$("meta[name='description']").attr('content');
}
if (!data.msg_desc && data.msg_content) {
const text = data.msg_content.replace(/<[^>]+>/g, '').replace(/\s+/g, ' ').trim();
if (text.length > 0) {
data.msg_desc = text.substring(0, 140) + (text.length > 140 ? '...' : '');
}
}
// Handle script in content
if (data.msg_content?.includes('<script') && data.msg_content.includes('script>') && data.msg_content.includes('nonce=')) {
const desc = $("meta[property='og:description']").attr('content');
if (desc) data.msg_content = desc;
}
// Validate result
if (!data.msg_title || !data.msg_publish_time) return getError(1001);
// Text type fallback
if (type === 'text' && !data.msg_content && data.msg_title) {
data.msg_content = data.msg_title;
}
// Image type with picture_page_info_list
if (picturePageInfoList) {
data.msg_type = 'image';
data.msg_content = `${data.msg_title}<br>`;
for (const one of picturePageInfoList) {
data.msg_content += `<img src="${one.cdn_url}" style="max-width:100%"/><br><br>`;
}
}
// Extract mp links
if (shouldExtractMpLinks) {
const mpLinks = [];
$('a').each((i, ele) => {
const href = $(ele).attr('href');
if (href?.includes('mp.weixin.qq.com')) {
mpLinks.push({ title: $(ele).text(), href });
}
});
data.mp_links_count = mpLinks.length;
data.mp_links = mpLinks;
}
// Extract tags
if (shouldExtractTags) {
const tags = [];
$('.article-tag__item-wrp').each((i, ele) => {
const $this = $(ele);
try {
const tagUrl = $this.attr('data-url');
const name = $this.find('.article-tag__item').text();
let count = $this.find('.article-tag__item-num').text();
if (name) {
if (!count && tags.length === 0) {
const $count = $('.article-tag-card__right');
if ($count.length) count = $count.text().replace('个', '');
}
tags.push({
id: getParameterByName('album_id', tagUrl) || getParameterByName('tag_id', tagUrl) || null,
url: tagUrl,
name: name.replace(/^#/, ''),
count: count?.replace(/\D/g, '') * 1 || 0
});
}
} catch (e) {}
});
data.tags = tags;
}
// Extract repost meta
if (shouldExtractRepostMeta && html.includes('copyright_info') && html.includes('original_primary_nickname')) {
const name = $('.original_primary_nickname').text();
if (name) data.repost_meta = { account_name: name };
}
// Clean up link
if (data.msg_link?.includes('&')) {
data.msg_link = data.msg_link.replace(/&/g, '&');
}
return { code: 0, done: true, data };
}
module.exports = { extract };
Related skills
Forks & variants (2)
Wechat Article Extractor has 2 known copies in the catalog totaling 142 installs. They canonicalize to this original listing.
- skills.volces.com - 141 installs
- freestylefly - 1 installs
How it compares
Choose wechat-article-extractor over generic web-fetch skills when the source is specifically mp.weixin.qq.com Official Account content with Chinese platform field semantics.
FAQ
What WeChat URL formats are supported?
Supports mp.weixin.qq.com article URLs (standard format) and Sogou WeChat search results (weixin.sogou.com). Requires valid __biz parameter and message ID.
How are error cases like deleted or blocked content handled?
Tool returns specific error codes (e.g. 2005 for deleted, 2006 for blocked, 2002 for expired) and done:false flag. Caller can log, retry, or skip based on code.
Can I extract reposts or only original articles?
Supports repost articles (msg_type: 'repost'). Use shouldExtractRepostMeta option to extract original source information from reposts.
Is Wechat Article Extractor safe to install?
skills.sh reports 1 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.