dsh-ab-wechat-scrape 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +330 -0
- package/cordis.patch.yml +8 -0
- package/lib/clean.d.ts +32 -0
- package/lib/clean.d.ts.map +1 -0
- package/lib/clean.js +193 -0
- package/lib/clean.js.map +1 -0
- package/lib/document.d.ts +27 -0
- package/lib/document.d.ts.map +1 -0
- package/lib/document.js +79 -0
- package/lib/document.js.map +1 -0
- package/lib/fetch.d.ts +84 -0
- package/lib/fetch.d.ts.map +1 -0
- package/lib/fetch.js +238 -0
- package/lib/fetch.js.map +1 -0
- package/lib/filename.d.ts +36 -0
- package/lib/filename.d.ts.map +1 -0
- package/lib/filename.js +92 -0
- package/lib/filename.js.map +1 -0
- package/lib/html.d.ts +114 -0
- package/lib/html.d.ts.map +1 -0
- package/lib/html.js +402 -0
- package/lib/html.js.map +1 -0
- package/lib/index.d.ts +219 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +2523 -0
- package/lib/index.js.map +1 -0
- package/lib/list.d.ts +55 -0
- package/lib/list.d.ts.map +1 -0
- package/lib/list.js +191 -0
- package/lib/list.js.map +1 -0
- package/lib/markdown.d.ts +20 -0
- package/lib/markdown.d.ts.map +1 -0
- package/lib/markdown.js +250 -0
- package/lib/markdown.js.map +1 -0
- package/lib/render.d.ts +103 -0
- package/lib/render.d.ts.map +1 -0
- package/lib/render.js +209 -0
- package/lib/render.js.map +1 -0
- package/lib/store.d.ts +50 -0
- package/lib/store.d.ts.map +1 -0
- package/lib/store.js +173 -0
- package/lib/store.js.map +1 -0
- package/lib/types.d.ts +133 -0
- package/lib/types.d.ts.map +1 -0
- package/lib/types.js +8 -0
- package/lib/types.js.map +1 -0
- package/package.json +91 -0
- package/tsconfig.json +30 -0
- package/tsdown.config.ts +18 -0
package/lib/html.d.ts
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure page parsing for mp.weixin.qq.com articles: HTML entity and inline-script
|
|
3
|
+
* string decoding, metadata lookup, balanced body extraction, interstitial
|
|
4
|
+
* redirect resolution, and the detection of block, verification, and withdrawn
|
|
5
|
+
* pages. No I/O and no DOM library, so tests drive every rule directly.
|
|
6
|
+
* @module @deepseek-ai/dsh-ab-wechat-scrape/html
|
|
7
|
+
*/
|
|
8
|
+
import type { Extraction } from './types.ts';
|
|
9
|
+
/**
|
|
10
|
+
* Decode named and numeric HTML entities.
|
|
11
|
+
* @param text - text carrying entity references.
|
|
12
|
+
* @returns the same text with every known reference replaced.
|
|
13
|
+
*/
|
|
14
|
+
export declare function decodeEntities(text: string): string;
|
|
15
|
+
/**
|
|
16
|
+
* Decode the string-literal escapes WeChat embeds in its inline scripts.
|
|
17
|
+
* @param raw - the literal body, without its surrounding quotes.
|
|
18
|
+
* @returns the decoded text.
|
|
19
|
+
*/
|
|
20
|
+
export declare function decodeJsString(raw: string): string;
|
|
21
|
+
/**
|
|
22
|
+
* Read one opening tag's attributes into a lookup keyed by lower-case name.
|
|
23
|
+
* @param tag - the opening tag text.
|
|
24
|
+
* @returns decoded attribute values; the last occurrence of a name wins.
|
|
25
|
+
*/
|
|
26
|
+
export declare function parseAttributes(tag: string): Map<string, string>;
|
|
27
|
+
/**
|
|
28
|
+
* Read one meta tag's content by its property or name.
|
|
29
|
+
* @param html - complete page HTML.
|
|
30
|
+
* @param key - the property or name to match, case-insensitive.
|
|
31
|
+
* @returns the decoded content, or an empty string when no such tag exists.
|
|
32
|
+
*/
|
|
33
|
+
export declare function metaContent(html: string, key: string): string;
|
|
34
|
+
/**
|
|
35
|
+
* Read one inline-script variable by name. The page assigns both bare literals
|
|
36
|
+
* and wrapped ones (htmlDecode("..."), "....html(false)"), so the whole
|
|
37
|
+
* right-hand expression is read and its first quoted literal decoded.
|
|
38
|
+
* @param html - complete page HTML.
|
|
39
|
+
* @param variable - the variable to read, case-sensitive.
|
|
40
|
+
* @returns the decoded value, or an empty string when the page carries none.
|
|
41
|
+
*/
|
|
42
|
+
export declare function jsString(html: string, variable: string): string;
|
|
43
|
+
/**
|
|
44
|
+
* Extract the inner HTML of the first element carrying one id. Tag depth is
|
|
45
|
+
* counted, so nested elements of the same name cannot end the slice early.
|
|
46
|
+
* @param html - complete page HTML.
|
|
47
|
+
* @param id - the id attribute value the opening tag must carry.
|
|
48
|
+
* @returns the inner HTML, or an empty string when no such element exists.
|
|
49
|
+
*/
|
|
50
|
+
export declare function elementById(html: string, id: string): string;
|
|
51
|
+
/**
|
|
52
|
+
* Decode entities, drop tags, and collapse whitespace into readable text.
|
|
53
|
+
* @param html - markup to flatten.
|
|
54
|
+
* @returns the element text with every run of whitespace collapsed to one space.
|
|
55
|
+
*/
|
|
56
|
+
export declare function textOf(html: string): string;
|
|
57
|
+
/**
|
|
58
|
+
* Resolve the real article URL an interstitial or short link hands the browser.
|
|
59
|
+
* @param html - the page that came back for the requested link.
|
|
60
|
+
* @returns an mp.weixin.qq.com article URL, or an empty string when none is named.
|
|
61
|
+
*/
|
|
62
|
+
export declare function findRedirectUrl(html: string): string;
|
|
63
|
+
/**
|
|
64
|
+
* Whether a link names an official-account article host.
|
|
65
|
+
* @param url - the candidate link.
|
|
66
|
+
* @returns true for an http(s) mp.weixin.qq.com link.
|
|
67
|
+
*/
|
|
68
|
+
export declare function isMpArticleUrl(url: string): boolean;
|
|
69
|
+
/**
|
|
70
|
+
* Whether a link names one official-account article rather than another page on
|
|
71
|
+
* the same host. A collection page lives under /mp/appmsgalbum and an article
|
|
72
|
+
* under /s, so the path decides.
|
|
73
|
+
* @param url - the candidate link.
|
|
74
|
+
* @returns true for an http(s) mp.weixin.qq.com /s link.
|
|
75
|
+
*/
|
|
76
|
+
export declare function isMpArticleLink(url: string): boolean;
|
|
77
|
+
/**
|
|
78
|
+
* The block-page marker a page carries.
|
|
79
|
+
* @param html - complete page HTML.
|
|
80
|
+
* @returns the marker found in the page head, or an empty string for a normal page.
|
|
81
|
+
*/
|
|
82
|
+
export declare function detectBlock(html: string): string;
|
|
83
|
+
/**
|
|
84
|
+
* The verification redirect a response URL names, which the body may not mention.
|
|
85
|
+
* @param url - the final response URL.
|
|
86
|
+
* @returns the matched URL fragment, or an empty string for a normal article URL.
|
|
87
|
+
*/
|
|
88
|
+
export declare function detectBlockUrl(url: string): string;
|
|
89
|
+
/**
|
|
90
|
+
* The withdrawal marker a page carries.
|
|
91
|
+
* @param html - complete page HTML.
|
|
92
|
+
* @returns the marker found in the page head, or an empty string for a page that
|
|
93
|
+
* is not reporting a withdrawn or removed article.
|
|
94
|
+
*/
|
|
95
|
+
export declare function detectUnavailable(html: string): string;
|
|
96
|
+
/**
|
|
97
|
+
* Collect the content image URLs in document order, preferring lazy data-src.
|
|
98
|
+
* @param bodyHtml - the article body HTML.
|
|
99
|
+
* @returns absolute http(s) image URLs, de-duplicated.
|
|
100
|
+
*/
|
|
101
|
+
export declare function extractImages(bodyHtml: string): string[];
|
|
102
|
+
/**
|
|
103
|
+
* Normalize a page timestamp to ISO 8601.
|
|
104
|
+
* @param raw - the page's own timestamp text.
|
|
105
|
+
* @returns an ISO timestamp, the unchanged text when it is not a time, or an empty string.
|
|
106
|
+
*/
|
|
107
|
+
export declare function toIsoTime(raw: string): string;
|
|
108
|
+
/**
|
|
109
|
+
* Parse one page into article facts, a block report, or an empty result.
|
|
110
|
+
* @param html - complete page HTML.
|
|
111
|
+
* @returns the extraction outcome the caller turns into the canonical value.
|
|
112
|
+
*/
|
|
113
|
+
export declare function extractArticle(html: string): Extraction;
|
|
114
|
+
//# sourceMappingURL=html.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"html.d.ts","sourceRoot":"","sources":["../src/html.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAoB,UAAU,EAAE,MAAM,YAAY,CAAA;AAoF9D;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAOnD;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAUlD;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAWhE;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAU7D;AAcD;;;;;;;GAOG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAU/D;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CAwB5D;AAED;;;;GAIG;AACH,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE3C;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CASpD;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAUnD;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAIpD;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAMhD;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAMlD;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAMtD;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,CAaxD;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAM7C;AAWD;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,CAsCvD"}
|
package/lib/html.js
ADDED
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure page parsing for mp.weixin.qq.com articles: HTML entity and inline-script
|
|
3
|
+
* string decoding, metadata lookup, balanced body extraction, interstitial
|
|
4
|
+
* redirect resolution, and the detection of block, verification, and withdrawn
|
|
5
|
+
* pages. No I/O and no DOM library, so tests drive every rule directly.
|
|
6
|
+
* @module @deepseek-ai/dsh-ab-wechat-scrape/html
|
|
7
|
+
*/
|
|
8
|
+
const NAMED_ENTITIES = {
|
|
9
|
+
amp: '&',
|
|
10
|
+
lt: '<',
|
|
11
|
+
gt: '>',
|
|
12
|
+
quot: '"',
|
|
13
|
+
apos: "'",
|
|
14
|
+
nbsp: ' ',
|
|
15
|
+
ensp: ' ',
|
|
16
|
+
emsp: ' ',
|
|
17
|
+
ldquo: '\u201c',
|
|
18
|
+
rdquo: '\u201d',
|
|
19
|
+
lsquo: '\u2018',
|
|
20
|
+
rsquo: '\u2019',
|
|
21
|
+
hellip: '\u2026',
|
|
22
|
+
mdash: '\u2014',
|
|
23
|
+
ndash: '\u2013',
|
|
24
|
+
middot: '\u00b7',
|
|
25
|
+
copy: '\u00a9',
|
|
26
|
+
};
|
|
27
|
+
/** Block-page markers WeChat serves instead of an article. */
|
|
28
|
+
const BLOCK_MARKERS = [
|
|
29
|
+
'\u73af\u5883\u5f02\u5e38',
|
|
30
|
+
'\u53bb\u9a8c\u8bc1',
|
|
31
|
+
'\u8bf7\u5728\u5fae\u4fe1\u5ba2\u6237\u7aef\u6253\u5f00\u94fe\u63a5',
|
|
32
|
+
'\u8bbf\u95ee\u8fc7\u4e8e\u9891\u7e41',
|
|
33
|
+
'\u64cd\u4f5c\u9891\u7e41',
|
|
34
|
+
'\u53c2\u6570\u9519\u8bef',
|
|
35
|
+
'\u5b8c\u6210\u9a8c\u8bc1',
|
|
36
|
+
'wappoc_appmsg',
|
|
37
|
+
'poc_token',
|
|
38
|
+
];
|
|
39
|
+
/** Response-URL shapes WeChat redirects a throttled or unverified request to. */
|
|
40
|
+
const BLOCK_URL_PATTERNS = [
|
|
41
|
+
/\/mp\/wappoc_appmsg/i,
|
|
42
|
+
/\/mp\/verify/i,
|
|
43
|
+
/\/mp\/captcha/i,
|
|
44
|
+
/poc_token=/i,
|
|
45
|
+
];
|
|
46
|
+
/**
|
|
47
|
+
* Page markers WeChat serves when an article is gone rather than withheld:
|
|
48
|
+
* the publisher withdrew it, a reviewer removed it, or the account itself was
|
|
49
|
+
* closed. Rendering cannot recover any of these, and none of them is an
|
|
50
|
+
* article, so a page carrying one is never saved.
|
|
51
|
+
*/
|
|
52
|
+
const UNAVAILABLE_MARKERS = [
|
|
53
|
+
'\u8be5\u5185\u5bb9\u5df2\u88ab\u53d1\u5e03\u8005\u5220\u9664',
|
|
54
|
+
'\u8be5\u5185\u5bb9\u56e0\u8fdd\u89c4\u65e0\u6cd5\u67e5\u770b',
|
|
55
|
+
'\u8be5\u5185\u5bb9\u5df2\u88ab\u5220\u9664',
|
|
56
|
+
'\u6b64\u5185\u5bb9\u56e0\u8fdd\u89c4\u65e0\u6cd5\u67e5\u770b',
|
|
57
|
+
'\u6b64\u5185\u5bb9\u5df2\u88ab\u53d1\u5e03\u8005\u5220\u9664',
|
|
58
|
+
'\u6b64\u5185\u5bb9\u53d1\u9001\u5931\u8d25\u65e0\u6cd5\u67e5\u770b',
|
|
59
|
+
'\u8be5\u8d26\u53f7\u5df2\u88ab\u5c4f\u853d',
|
|
60
|
+
'\u8be5\u516c\u4f17\u53f7\u5df2\u6ce8\u9500',
|
|
61
|
+
'\u6587\u7ae0\u5df2\u88ab\u5220\u9664',
|
|
62
|
+
'\u5185\u5bb9\u4e0d\u5b58\u5728',
|
|
63
|
+
'\u94fe\u63a5\u5df2\u5931\u6548',
|
|
64
|
+
];
|
|
65
|
+
/** One attribute of an opening tag, double- or single-quoted. */
|
|
66
|
+
const ATTRIBUTE = /([a-zA-Z_:][-a-zA-Z0-9_:.]*)\s*=\s*(?:"([^"]*)"|'([^']*)')/g;
|
|
67
|
+
/** One opening tag, closing tag, comment, or run of text. */
|
|
68
|
+
const TAG = /<([a-zA-Z][a-zA-Z0-9-]*)((?:"[^"]*"|'[^']*'|[^>"'])*?)(\/?)>/g;
|
|
69
|
+
/** The inline-script shapes that hand the browser the real article link. */
|
|
70
|
+
const REDIRECT_PATTERNS = [
|
|
71
|
+
/(?:var\s+)?msg_link\s*[:=]\s*"((?:\\[\s\S]|[^"\\])*)"/,
|
|
72
|
+
/(?:var\s+)?msg_link\s*[:=]\s*'((?:\\[\s\S]|[^'\\])*)'/,
|
|
73
|
+
/window\.location\.replace\(\s*"([^"]+)"\s*\)/,
|
|
74
|
+
/window\.location\.replace\(\s*'([^']+)'\s*\)/,
|
|
75
|
+
/location\.href\s*[:=]\s*"([^"]+)"/,
|
|
76
|
+
/location\.href\s*[:=]\s*'([^']+)'/,
|
|
77
|
+
];
|
|
78
|
+
/** One code point as a character, or the original text when it is out of range. */
|
|
79
|
+
function codePoint(code, fallback) {
|
|
80
|
+
return Number.isInteger(code) && code >= 0 && code <= 0x10ffff ? String.fromCodePoint(code) : fallback;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Decode named and numeric HTML entities.
|
|
84
|
+
* @param text - text carrying entity references.
|
|
85
|
+
* @returns the same text with every known reference replaced.
|
|
86
|
+
*/
|
|
87
|
+
export function decodeEntities(text) {
|
|
88
|
+
return text.replace(/&(#x[0-9a-f]+|#[0-9]+|[a-z]+);/gi, (match, body) => {
|
|
89
|
+
if (/^#x/i.test(body))
|
|
90
|
+
return codePoint(Number.parseInt(body.slice(2), 16), match);
|
|
91
|
+
if (body.startsWith('#'))
|
|
92
|
+
return codePoint(Number.parseInt(body.slice(1), 10), match);
|
|
93
|
+
const named = NAMED_ENTITIES[body.toLowerCase()];
|
|
94
|
+
return named === undefined ? match : named;
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Decode the string-literal escapes WeChat embeds in its inline scripts.
|
|
99
|
+
* @param raw - the literal body, without its surrounding quotes.
|
|
100
|
+
* @returns the decoded text.
|
|
101
|
+
*/
|
|
102
|
+
export function decodeJsString(raw) {
|
|
103
|
+
const unescaped = raw.replace(/\\(u[0-9a-f]{4}|x[0-9a-f]{2}|[\s\S])/gi, (match, escape) => {
|
|
104
|
+
const kind = escape.charAt(0);
|
|
105
|
+
if (kind === 'u' || kind === 'x')
|
|
106
|
+
return codePoint(Number.parseInt(escape.slice(1), 16), match);
|
|
107
|
+
if (escape === 'n')
|
|
108
|
+
return '\n';
|
|
109
|
+
if (escape === 'r')
|
|
110
|
+
return '\r';
|
|
111
|
+
if (escape === 't')
|
|
112
|
+
return '\t';
|
|
113
|
+
return escape;
|
|
114
|
+
});
|
|
115
|
+
return decodeEntities(unescaped);
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Read one opening tag's attributes into a lookup keyed by lower-case name.
|
|
119
|
+
* @param tag - the opening tag text.
|
|
120
|
+
* @returns decoded attribute values; the last occurrence of a name wins.
|
|
121
|
+
*/
|
|
122
|
+
export function parseAttributes(tag) {
|
|
123
|
+
const attributes = new Map();
|
|
124
|
+
ATTRIBUTE.lastIndex = 0;
|
|
125
|
+
let match;
|
|
126
|
+
while ((match = ATTRIBUTE.exec(tag)) !== null) {
|
|
127
|
+
const name = match[1];
|
|
128
|
+
const value = match[2] ?? match[3];
|
|
129
|
+
if (name === undefined || value === undefined)
|
|
130
|
+
continue;
|
|
131
|
+
attributes.set(name.toLowerCase(), decodeEntities(value).trim());
|
|
132
|
+
}
|
|
133
|
+
return attributes;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Read one meta tag's content by its property or name.
|
|
137
|
+
* @param html - complete page HTML.
|
|
138
|
+
* @param key - the property or name to match, case-insensitive.
|
|
139
|
+
* @returns the decoded content, or an empty string when no such tag exists.
|
|
140
|
+
*/
|
|
141
|
+
export function metaContent(html, key) {
|
|
142
|
+
const wanted = key.toLowerCase();
|
|
143
|
+
const tags = /<meta\b[^>]*>/gi;
|
|
144
|
+
let match;
|
|
145
|
+
while ((match = tags.exec(html)) !== null) {
|
|
146
|
+
const attributes = parseAttributes(match[0]);
|
|
147
|
+
const label = (attributes.get('property') ?? attributes.get('name') ?? '').toLowerCase();
|
|
148
|
+
if (label === wanted)
|
|
149
|
+
return attributes.get('content') ?? '';
|
|
150
|
+
}
|
|
151
|
+
return '';
|
|
152
|
+
}
|
|
153
|
+
/** Escape one literal so it can be embedded in a regular expression. */
|
|
154
|
+
function escapeRegExp(text) {
|
|
155
|
+
return text.replace(/[.*+?^(){}|[\]\\]/g, '\\$&');
|
|
156
|
+
}
|
|
157
|
+
/** The first quoted literal inside one script expression, decoded. */
|
|
158
|
+
function firstQuoted(expression) {
|
|
159
|
+
const match = /"((?:\\[\s\S]|[^"\\])*)"|'((?:\\[\s\S]|[^'\\])*)'/.exec(expression);
|
|
160
|
+
const value = match?.[1] ?? match?.[2];
|
|
161
|
+
return value === undefined ? '' : decodeJsString(value).trim();
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Read one inline-script variable by name. The page assigns both bare literals
|
|
165
|
+
* and wrapped ones (htmlDecode("..."), "....html(false)"), so the whole
|
|
166
|
+
* right-hand expression is read and its first quoted literal decoded.
|
|
167
|
+
* @param html - complete page HTML.
|
|
168
|
+
* @param variable - the variable to read, case-sensitive.
|
|
169
|
+
* @returns the decoded value, or an empty string when the page carries none.
|
|
170
|
+
*/
|
|
171
|
+
export function jsString(html, variable) {
|
|
172
|
+
// Minified library code reuses short names before the page assigns its own, so
|
|
173
|
+
// every assignment is scanned and the first one carrying a literal wins.
|
|
174
|
+
const pattern = new RegExp('\\bvar\\s+' + escapeRegExp(variable) + '\\s*=\\s*([^\\n;]*)', 'g');
|
|
175
|
+
let match;
|
|
176
|
+
while ((match = pattern.exec(html)) !== null) {
|
|
177
|
+
const value = firstQuoted(match[1] ?? '');
|
|
178
|
+
if (value !== '')
|
|
179
|
+
return value;
|
|
180
|
+
}
|
|
181
|
+
return '';
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Extract the inner HTML of the first element carrying one id. Tag depth is
|
|
185
|
+
* counted, so nested elements of the same name cannot end the slice early.
|
|
186
|
+
* @param html - complete page HTML.
|
|
187
|
+
* @param id - the id attribute value the opening tag must carry.
|
|
188
|
+
* @returns the inner HTML, or an empty string when no such element exists.
|
|
189
|
+
*/
|
|
190
|
+
export function elementById(html, id) {
|
|
191
|
+
TAG.lastIndex = 0;
|
|
192
|
+
let match;
|
|
193
|
+
while ((match = TAG.exec(html)) !== null) {
|
|
194
|
+
const name = match[1];
|
|
195
|
+
if (name === undefined)
|
|
196
|
+
continue;
|
|
197
|
+
if (parseAttributes(match[0]).get('id') !== id)
|
|
198
|
+
continue;
|
|
199
|
+
const element = name.toLowerCase();
|
|
200
|
+
const contentStart = match.index + match[0].length;
|
|
201
|
+
const walk = new RegExp('<' + element + '\\b|</' + element + '\\s*>', 'gi');
|
|
202
|
+
walk.lastIndex = contentStart;
|
|
203
|
+
let depth = 1;
|
|
204
|
+
let step;
|
|
205
|
+
while ((step = walk.exec(html)) !== null) {
|
|
206
|
+
if (step[0].startsWith('</')) {
|
|
207
|
+
depth -= 1;
|
|
208
|
+
if (depth === 0)
|
|
209
|
+
return html.slice(contentStart, step.index);
|
|
210
|
+
}
|
|
211
|
+
else {
|
|
212
|
+
depth += 1;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return html.slice(contentStart);
|
|
216
|
+
}
|
|
217
|
+
return '';
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Decode entities, drop tags, and collapse whitespace into readable text.
|
|
221
|
+
* @param html - markup to flatten.
|
|
222
|
+
* @returns the element text with every run of whitespace collapsed to one space.
|
|
223
|
+
*/
|
|
224
|
+
export function textOf(html) {
|
|
225
|
+
return decodeEntities(html.replace(/<[^>]*>/g, ' ')).replace(/\s+/g, ' ').trim();
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Resolve the real article URL an interstitial or short link hands the browser.
|
|
229
|
+
* @param html - the page that came back for the requested link.
|
|
230
|
+
* @returns an mp.weixin.qq.com article URL, or an empty string when none is named.
|
|
231
|
+
*/
|
|
232
|
+
export function findRedirectUrl(html) {
|
|
233
|
+
for (const pattern of REDIRECT_PATTERNS) {
|
|
234
|
+
const match = pattern.exec(html);
|
|
235
|
+
const raw = match?.[1];
|
|
236
|
+
if (raw === undefined || raw.length === 0)
|
|
237
|
+
continue;
|
|
238
|
+
const url = decodeJsString(raw).trim();
|
|
239
|
+
if (isMpArticleUrl(url))
|
|
240
|
+
return url;
|
|
241
|
+
}
|
|
242
|
+
return '';
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Whether a link names an official-account article host.
|
|
246
|
+
* @param url - the candidate link.
|
|
247
|
+
* @returns true for an http(s) mp.weixin.qq.com link.
|
|
248
|
+
*/
|
|
249
|
+
export function isMpArticleUrl(url) {
|
|
250
|
+
let parsed;
|
|
251
|
+
try {
|
|
252
|
+
parsed = new URL(url);
|
|
253
|
+
}
|
|
254
|
+
catch {
|
|
255
|
+
// A malformed link names no article page; the caller reports it as invalid.
|
|
256
|
+
return false;
|
|
257
|
+
}
|
|
258
|
+
return (parsed.protocol === 'https:' || parsed.protocol === 'http:')
|
|
259
|
+
&& (parsed.hostname === 'mp.weixin.qq.com' || parsed.hostname.endsWith('.mp.weixin.qq.com'));
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Whether a link names one official-account article rather than another page on
|
|
263
|
+
* the same host. A collection page lives under /mp/appmsgalbum and an article
|
|
264
|
+
* under /s, so the path decides.
|
|
265
|
+
* @param url - the candidate link.
|
|
266
|
+
* @returns true for an http(s) mp.weixin.qq.com /s link.
|
|
267
|
+
*/
|
|
268
|
+
export function isMpArticleLink(url) {
|
|
269
|
+
if (!isMpArticleUrl(url))
|
|
270
|
+
return false;
|
|
271
|
+
const path = new URL(url).pathname;
|
|
272
|
+
return path === '/s' || path.startsWith('/s/');
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* The block-page marker a page carries.
|
|
276
|
+
* @param html - complete page HTML.
|
|
277
|
+
* @returns the marker found in the page head, or an empty string for a normal page.
|
|
278
|
+
*/
|
|
279
|
+
export function detectBlock(html) {
|
|
280
|
+
const head = html.slice(0, 40000);
|
|
281
|
+
for (const marker of BLOCK_MARKERS) {
|
|
282
|
+
if (head.includes(marker))
|
|
283
|
+
return marker;
|
|
284
|
+
}
|
|
285
|
+
return '';
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* The verification redirect a response URL names, which the body may not mention.
|
|
289
|
+
* @param url - the final response URL.
|
|
290
|
+
* @returns the matched URL fragment, or an empty string for a normal article URL.
|
|
291
|
+
*/
|
|
292
|
+
export function detectBlockUrl(url) {
|
|
293
|
+
for (const pattern of BLOCK_URL_PATTERNS) {
|
|
294
|
+
const match = pattern.exec(url);
|
|
295
|
+
if (match !== null)
|
|
296
|
+
return match[0];
|
|
297
|
+
}
|
|
298
|
+
return '';
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* The withdrawal marker a page carries.
|
|
302
|
+
* @param html - complete page HTML.
|
|
303
|
+
* @returns the marker found in the page head, or an empty string for a page that
|
|
304
|
+
* is not reporting a withdrawn or removed article.
|
|
305
|
+
*/
|
|
306
|
+
export function detectUnavailable(html) {
|
|
307
|
+
const head = html.slice(0, 40000);
|
|
308
|
+
for (const marker of UNAVAILABLE_MARKERS) {
|
|
309
|
+
if (head.includes(marker))
|
|
310
|
+
return marker;
|
|
311
|
+
}
|
|
312
|
+
return '';
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Collect the content image URLs in document order, preferring lazy data-src.
|
|
316
|
+
* @param bodyHtml - the article body HTML.
|
|
317
|
+
* @returns absolute http(s) image URLs, de-duplicated.
|
|
318
|
+
*/
|
|
319
|
+
export function extractImages(bodyHtml) {
|
|
320
|
+
const images = [];
|
|
321
|
+
const seen = new Set();
|
|
322
|
+
const tags = /<img\b[^>]*>/gi;
|
|
323
|
+
let match;
|
|
324
|
+
while ((match = tags.exec(bodyHtml)) !== null) {
|
|
325
|
+
const attributes = parseAttributes(match[0]);
|
|
326
|
+
const src = attributes.get('data-src') ?? attributes.get('src') ?? '';
|
|
327
|
+
if (src === '' || src.startsWith('data:') || !/^https?:/i.test(src) || seen.has(src))
|
|
328
|
+
continue;
|
|
329
|
+
seen.add(src);
|
|
330
|
+
images.push(src);
|
|
331
|
+
}
|
|
332
|
+
return images;
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* Normalize a page timestamp to ISO 8601.
|
|
336
|
+
* @param raw - the page's own timestamp text.
|
|
337
|
+
* @returns an ISO timestamp, the unchanged text when it is not a time, or an empty string.
|
|
338
|
+
*/
|
|
339
|
+
export function toIsoTime(raw) {
|
|
340
|
+
const trimmed = raw.trim();
|
|
341
|
+
if (trimmed === '')
|
|
342
|
+
return '';
|
|
343
|
+
if (/^\d{9,11}$/.test(trimmed))
|
|
344
|
+
return new Date(Number.parseInt(trimmed, 10) * 1000).toISOString();
|
|
345
|
+
const parsed = new Date(trimmed);
|
|
346
|
+
return Number.isNaN(parsed.getTime()) ? trimmed : parsed.toISOString();
|
|
347
|
+
}
|
|
348
|
+
/** The first non-empty value, trimmed. */
|
|
349
|
+
function firstNonEmpty(values) {
|
|
350
|
+
for (const value of values) {
|
|
351
|
+
const trimmed = value.trim();
|
|
352
|
+
if (trimmed !== '')
|
|
353
|
+
return trimmed;
|
|
354
|
+
}
|
|
355
|
+
return '';
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* Parse one page into article facts, a block report, or an empty result.
|
|
359
|
+
* @param html - complete page HTML.
|
|
360
|
+
* @returns the extraction outcome the caller turns into the canonical value.
|
|
361
|
+
*/
|
|
362
|
+
export function extractArticle(html) {
|
|
363
|
+
const marker = detectBlock(html);
|
|
364
|
+
if (marker !== '')
|
|
365
|
+
return { kind: 'blocked', marker };
|
|
366
|
+
const bodyHtml = elementById(html, 'js_content');
|
|
367
|
+
// The withdrawal markers are only decisive on a page with no body: a live
|
|
368
|
+
// article that quotes one of them in its own prose still has its js_content.
|
|
369
|
+
if (bodyHtml.trim() === '') {
|
|
370
|
+
const reason = detectUnavailable(html);
|
|
371
|
+
if (reason !== '')
|
|
372
|
+
return { kind: 'unavailable', reason };
|
|
373
|
+
}
|
|
374
|
+
const title = firstNonEmpty([
|
|
375
|
+
metaContent(html, 'og:title'),
|
|
376
|
+
jsString(html, 'msg_title'),
|
|
377
|
+
textOf(elementById(html, 'activity-name')),
|
|
378
|
+
]);
|
|
379
|
+
const account = firstNonEmpty([
|
|
380
|
+
jsString(html, 'nickname'),
|
|
381
|
+
textOf(elementById(html, 'js_name')),
|
|
382
|
+
metaContent(html, 'og:site_name'),
|
|
383
|
+
]);
|
|
384
|
+
const author = firstNonEmpty([jsString(html, 'author'), metaContent(html, 'author')]);
|
|
385
|
+
const publishTime = firstNonEmpty([toIsoTime(jsString(html, 'ct')), textOf(elementById(html, 'publish_time'))]);
|
|
386
|
+
const digest = firstNonEmpty([jsString(html, 'msg_desc'), metaContent(html, 'description')]);
|
|
387
|
+
const cover = firstNonEmpty([jsString(html, 'msg_cdn_url'), metaContent(html, 'og:image')]);
|
|
388
|
+
if (bodyHtml.trim() === '' && title === '')
|
|
389
|
+
return { kind: 'empty' };
|
|
390
|
+
const article = {
|
|
391
|
+
title,
|
|
392
|
+
account,
|
|
393
|
+
author,
|
|
394
|
+
publishTime,
|
|
395
|
+
digest,
|
|
396
|
+
cover,
|
|
397
|
+
bodyHtml,
|
|
398
|
+
images: extractImages(bodyHtml),
|
|
399
|
+
};
|
|
400
|
+
return { kind: 'article', article };
|
|
401
|
+
}
|
|
402
|
+
//# sourceMappingURL=html.js.map
|
package/lib/html.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"html.js","sourceRoot":"","sources":["../src/html.ts"],"names":[],"mappings":"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"}
|