@sovovs/bycli 2.1.24 → 2.1.25
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/cli-manifest.json +8 -4
- package/clis/weixin/_wechat/article-fallback-policy.js +46 -0
- package/clis/weixin/_wechat/article-index.js +14 -0
- package/clis/weixin/_wechat/article-link.js +88 -0
- package/clis/weixin/_wechat/publish-records.js +14 -3
- package/clis/weixin/_wechat/sogou-fallback.js +223 -0
- package/clis/weixin/_wechat/sogou-search.js +143 -0
- package/clis/weixin/articles.js +37 -7
- package/clis/weixin/create-draft.js +120 -41
- package/clis/weixin/download-publish-data.js +46 -4
- package/clis/weixin/download.js +14 -100
- package/clis/weixin/save-articles.js +53 -10
- package/clis/weixin/search.js +9 -113
- package/package.json +1 -1
package/clis/weixin/search.js
CHANGED
|
@@ -1,30 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { EmptyResultError } from '@sovovs/bycli/errors';
|
|
2
2
|
import { cli, Strategy } from '@sovovs/bycli/registry';
|
|
3
|
+
import {
|
|
4
|
+
normalizePositiveInteger,
|
|
5
|
+
searchSogouArticlePage,
|
|
6
|
+
} from './_wechat/sogou-search.js';
|
|
3
7
|
|
|
4
8
|
const SOGOU_WEIXIN_DOMAIN = 'weixin.sogou.com';
|
|
5
9
|
const DEFAULT_PAGE = 1;
|
|
6
10
|
const DEFAULT_LIMIT = 10;
|
|
7
11
|
const MAX_LIMIT = 10;
|
|
8
12
|
|
|
9
|
-
function normalizePositiveInteger(value, name, defaultValue, maxValue) {
|
|
10
|
-
if (value === undefined || value === null) return defaultValue;
|
|
11
|
-
const text = String(value).trim();
|
|
12
|
-
if (!/^\d+$/.test(text)) {
|
|
13
|
-
throw new ArgumentError(
|
|
14
|
-
`weixin sougousearch --${name} must be a positive integer`,
|
|
15
|
-
`Pass --${name} as a whole number${maxValue ? ` from 1 to ${maxValue}` : ' greater than 0'}.`,
|
|
16
|
-
);
|
|
17
|
-
}
|
|
18
|
-
const parsed = Number(text);
|
|
19
|
-
if (!Number.isSafeInteger(parsed) || parsed < 1 || (maxValue && parsed > maxValue)) {
|
|
20
|
-
throw new ArgumentError(
|
|
21
|
-
`weixin sougousearch --${name} is out of range`,
|
|
22
|
-
`Pass --${name} as a whole number${maxValue ? ` from 1 to ${maxValue}` : ' greater than 0'}.`,
|
|
23
|
-
);
|
|
24
|
-
}
|
|
25
|
-
return parsed;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
13
|
function normalizePage(page) {
|
|
29
14
|
return normalizePositiveInteger(page, 'page', DEFAULT_PAGE);
|
|
30
15
|
}
|
|
@@ -33,63 +18,6 @@ function normalizeLimit(limit) {
|
|
|
33
18
|
return normalizePositiveInteger(limit, 'limit', DEFAULT_LIMIT, MAX_LIMIT);
|
|
34
19
|
}
|
|
35
20
|
|
|
36
|
-
function buildSearchUrl(query, pageNo) {
|
|
37
|
-
const searchUrl = new URL('https://weixin.sogou.com/weixin');
|
|
38
|
-
searchUrl.searchParams.set('query', query);
|
|
39
|
-
searchUrl.searchParams.set('type', '2');
|
|
40
|
-
searchUrl.searchParams.set('page', String(pageNo));
|
|
41
|
-
searchUrl.searchParams.set('ie', 'utf8');
|
|
42
|
-
return searchUrl.toString();
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function buildExtractSearchResultsEvaluate() {
|
|
46
|
-
return String.raw`(() => {
|
|
47
|
-
const clean = (value) => {
|
|
48
|
-
return (value || '')
|
|
49
|
-
.replace(/\s+/g, ' ')
|
|
50
|
-
.replace(/<!--red_beg-->|<!--red_end-->/g, '')
|
|
51
|
-
.replace(/document\.write\(timeConvert\('\d+'\)\)/g, '')
|
|
52
|
-
.trim();
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
const absolutize = (href) => {
|
|
56
|
-
if (!href) return '';
|
|
57
|
-
try {
|
|
58
|
-
return new URL(href, window.location.origin).toString();
|
|
59
|
-
} catch {
|
|
60
|
-
return href;
|
|
61
|
-
}
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
const bodyText = clean(document.body && document.body.innerText);
|
|
65
|
-
const blocked = /验证码|安全验证|异常访问|访问过于频繁|请输入验证码/.test(bodyText);
|
|
66
|
-
const empty = /没有找到相关的微信文章|未找到相关|暂无相关|没有找到/.test(bodyText)
|
|
67
|
-
|| Boolean(document.querySelector('.no-result, .no_result, .s-noresult'));
|
|
68
|
-
const cards = Array.from(document.querySelectorAll('.news-list li'));
|
|
69
|
-
const extracted = cards.map((item) => {
|
|
70
|
-
const linkEl = item.querySelector('h3 a[href]');
|
|
71
|
-
const summaryEl = item.querySelector('p.txt-info');
|
|
72
|
-
const accountEl = item.querySelector('.s-p .all-time-y2');
|
|
73
|
-
const timeEl = item.querySelector('.s-p .s2');
|
|
74
|
-
return {
|
|
75
|
-
title: clean(linkEl && linkEl.textContent),
|
|
76
|
-
account: clean(accountEl && accountEl.textContent),
|
|
77
|
-
url: absolutize(linkEl && linkEl.getAttribute('href')),
|
|
78
|
-
summary: clean(summaryEl && summaryEl.textContent),
|
|
79
|
-
publish_time: clean(timeEl && timeEl.textContent),
|
|
80
|
-
};
|
|
81
|
-
});
|
|
82
|
-
const rows = extracted.filter((row) => row.title && row.url);
|
|
83
|
-
|
|
84
|
-
return {
|
|
85
|
-
blocked,
|
|
86
|
-
empty,
|
|
87
|
-
invalidCount: extracted.length - rows.length,
|
|
88
|
-
rows,
|
|
89
|
-
};
|
|
90
|
-
})()`;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
21
|
export const weixinSearchCommand = cli({
|
|
94
22
|
site: 'weixin',
|
|
95
23
|
name: 'sougousearch',
|
|
@@ -106,50 +34,20 @@ export const weixinSearchCommand = cli({
|
|
|
106
34
|
columns: ['rank', 'page', 'title', 'account', 'url', 'summary', 'publish_time'],
|
|
107
35
|
func: async (page, kwargs) => {
|
|
108
36
|
const query = String(kwargs.query ?? '').trim();
|
|
109
|
-
if (!query) {
|
|
110
|
-
throw new ArgumentError('A search query is required.', 'Pass a non-empty keyword to search Weixin articles via Sogou.');
|
|
111
|
-
}
|
|
112
|
-
|
|
113
37
|
const pageNo = normalizePage(kwargs.page);
|
|
114
38
|
const limit = normalizeLimit(kwargs.limit);
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
let payload;
|
|
118
|
-
try {
|
|
119
|
-
await page.goto(searchUrl);
|
|
120
|
-
await page.wait(2);
|
|
121
|
-
payload = await page.evaluate(buildExtractSearchResultsEvaluate());
|
|
122
|
-
} catch (error) {
|
|
123
|
-
const detail = error instanceof Error ? error.message : String(error);
|
|
124
|
-
throw new CommandExecutionError('weixin sougousearch failed while loading Sogou results', detail);
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
if (!payload || typeof payload !== 'object' || !Array.isArray(payload.rows)) {
|
|
128
|
-
throw new CommandExecutionError('weixin sougousearch returned an unreadable browser payload', 'Sogou Weixin may have changed its result page structure.');
|
|
129
|
-
}
|
|
130
|
-
if (payload.blocked) {
|
|
131
|
-
throw new CommandExecutionError('Sogou Weixin blocked this search request', 'Open weixin.sogou.com in Chrome and complete any verification before retrying.');
|
|
132
|
-
}
|
|
133
|
-
if (payload.invalidCount > 0) {
|
|
134
|
-
throw new CommandExecutionError('Sogou Weixin returned article cards without required title or URL', 'The result page structure may have changed; refusing to return a partial result set.');
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
const rows = payload.rows;
|
|
138
|
-
if (rows.length === 0 && payload.empty) {
|
|
39
|
+
const result = await searchSogouArticlePage(page, { query, pageNo });
|
|
40
|
+
if (result.state === 'empty') {
|
|
139
41
|
throw new EmptyResultError('weixin sougousearch', 'Try a different keyword or a different page number.');
|
|
140
42
|
}
|
|
141
|
-
|
|
142
|
-
throw new CommandExecutionError('weixin sougousearch did not expose article result cards', 'Sogou Weixin may have changed its selectors or returned a transient shell page.');
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
return rows.slice(0, limit).map((row, index) => ({
|
|
43
|
+
return result.rows.slice(0, limit).map((row, index) => ({
|
|
146
44
|
rank: (pageNo - 1) * 10 + index + 1,
|
|
147
45
|
page: pageNo,
|
|
148
46
|
title: row.title,
|
|
149
47
|
account: row.account,
|
|
150
48
|
url: row.url,
|
|
151
49
|
summary: row.summary,
|
|
152
|
-
publish_time: row.
|
|
50
|
+
publish_time: row.publishTime,
|
|
153
51
|
}));
|
|
154
52
|
},
|
|
155
53
|
});
|
|
@@ -158,6 +56,4 @@ export const __test__ = {
|
|
|
158
56
|
MAX_LIMIT,
|
|
159
57
|
normalizePage,
|
|
160
58
|
normalizeLimit,
|
|
161
|
-
buildSearchUrl,
|
|
162
|
-
buildExtractSearchResultsEvaluate,
|
|
163
59
|
};
|