@jackwener/opencli 1.7.4 → 1.7.6
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/README.md +76 -51
- package/README.zh-CN.md +78 -62
- package/cli-manifest.json +4558 -2979
- package/clis/antigravity/serve.js +71 -25
- package/clis/baidu-scholar/search.js +87 -0
- package/clis/baidu-scholar/search.test.js +23 -0
- package/clis/bilibili/video.js +61 -0
- package/clis/bilibili/video.test.js +81 -0
- package/clis/deepseek/ask.js +94 -0
- package/clis/deepseek/ask.test.js +73 -0
- package/clis/deepseek/history.js +25 -0
- package/clis/deepseek/new.js +20 -0
- package/clis/deepseek/read.js +22 -0
- package/clis/deepseek/status.js +24 -0
- package/clis/deepseek/utils.js +291 -0
- package/clis/deepseek/utils.test.js +37 -0
- package/clis/eastmoney/_secid.js +78 -0
- package/clis/eastmoney/announcement.js +52 -0
- package/clis/eastmoney/convertible.js +73 -0
- package/clis/eastmoney/etf.js +65 -0
- package/clis/eastmoney/holders.js +78 -0
- package/clis/eastmoney/index-board.js +96 -0
- package/clis/eastmoney/kline.js +87 -0
- package/clis/eastmoney/kuaixun.js +54 -0
- package/clis/eastmoney/longhu.js +67 -0
- package/clis/eastmoney/money-flow.js +78 -0
- package/clis/eastmoney/northbound.js +57 -0
- package/clis/eastmoney/quote.js +107 -0
- package/clis/eastmoney/rank.js +94 -0
- package/clis/eastmoney/sectors.js +76 -0
- package/clis/google-scholar/search.js +58 -0
- package/clis/google-scholar/search.test.js +23 -0
- package/clis/gov-law/commands.test.js +39 -0
- package/clis/gov-law/recent.js +22 -0
- package/clis/gov-law/search.js +41 -0
- package/clis/gov-law/shared.js +51 -0
- package/clis/gov-policy/commands.test.js +27 -0
- package/clis/gov-policy/recent.js +47 -0
- package/clis/gov-policy/search.js +48 -0
- package/clis/jianyu/search.js +139 -3
- package/clis/jianyu/search.test.js +25 -0
- package/clis/jianyu/shared/procurement-detail.js +15 -0
- package/clis/jianyu/shared/procurement-detail.test.js +12 -0
- package/clis/nowcoder/companies.js +23 -0
- package/clis/nowcoder/creators.js +27 -0
- package/clis/nowcoder/detail.js +61 -0
- package/clis/nowcoder/experience.js +36 -0
- package/clis/nowcoder/hot.js +24 -0
- package/clis/nowcoder/jobs.js +21 -0
- package/clis/nowcoder/notifications.js +29 -0
- package/clis/nowcoder/papers.js +40 -0
- package/clis/nowcoder/practice.js +37 -0
- package/clis/nowcoder/recommend.js +30 -0
- package/clis/nowcoder/referral.js +39 -0
- package/clis/nowcoder/salary.js +40 -0
- package/clis/nowcoder/search.js +49 -0
- package/clis/nowcoder/suggest.js +33 -0
- package/clis/nowcoder/topics.js +27 -0
- package/clis/nowcoder/trending.js +25 -0
- package/clis/twitter/list-add.js +337 -0
- package/clis/twitter/list-add.test.js +15 -0
- package/clis/twitter/list-remove.js +297 -0
- package/clis/twitter/list-remove.test.js +14 -0
- package/clis/twitter/list-tweets.js +185 -0
- package/clis/twitter/list-tweets.test.js +108 -0
- package/clis/twitter/lists.js +134 -47
- package/clis/twitter/lists.test.js +105 -38
- package/clis/twitter/shared.js +7 -2
- package/clis/twitter/tweets.js +218 -0
- package/clis/twitter/tweets.test.js +125 -0
- package/clis/wanfang/search.js +66 -0
- package/clis/wanfang/search.test.js +23 -0
- package/clis/web/read.js +1 -1
- package/clis/weixin/download.js +3 -2
- package/clis/xiaohongshu/publish.js +149 -28
- package/clis/xiaohongshu/publish.test.js +319 -6
- package/clis/xiaoyuzhou/download.js +8 -4
- package/clis/xiaoyuzhou/download.test.js +23 -13
- package/clis/xiaoyuzhou/episode.js +9 -4
- package/clis/xiaoyuzhou/podcast-episodes.js +15 -11
- package/clis/xiaoyuzhou/podcast.js +9 -4
- package/clis/xiaoyuzhou/utils.js +0 -40
- package/clis/xiaoyuzhou/utils.test.js +15 -75
- package/clis/youtube/channel.js +35 -0
- package/clis/zsxq/dynamics.js +1 -1
- package/clis/zsxq/utils.js +6 -3
- package/clis/zsxq/utils.test.js +31 -0
- package/dist/src/browser/base-page.d.ts +14 -4
- package/dist/src/browser/base-page.js +35 -25
- package/dist/src/browser/bridge.d.ts +1 -0
- package/dist/src/browser/bridge.js +1 -1
- package/dist/src/browser/cdp.d.ts +1 -0
- package/dist/src/browser/cdp.js +13 -4
- package/dist/src/browser/compound.d.ts +59 -0
- package/dist/src/browser/compound.js +112 -0
- package/dist/src/browser/compound.test.js +175 -0
- package/dist/src/browser/daemon-client.d.ts +6 -4
- package/dist/src/browser/daemon-client.js +6 -1
- package/dist/src/browser/daemon-client.test.js +40 -1
- package/dist/src/browser/dom-snapshot.d.ts +7 -0
- package/dist/src/browser/dom-snapshot.js +83 -5
- package/dist/src/browser/dom-snapshot.test.js +65 -0
- package/dist/src/browser/extract.d.ts +69 -0
- package/dist/src/browser/extract.js +132 -0
- package/dist/src/browser/extract.test.js +129 -0
- package/dist/src/browser/find.d.ts +76 -0
- package/dist/src/browser/find.js +179 -0
- package/dist/src/browser/find.test.js +120 -0
- package/dist/src/browser/html-tree.d.ts +75 -0
- package/dist/src/browser/html-tree.js +112 -0
- package/dist/src/browser/html-tree.test.d.ts +1 -0
- package/dist/src/browser/html-tree.test.js +181 -0
- package/dist/src/browser/network-cache.d.ts +48 -0
- package/dist/src/browser/network-cache.js +66 -0
- package/dist/src/browser/network-cache.test.d.ts +1 -0
- package/dist/src/browser/network-cache.test.js +58 -0
- package/dist/src/browser/network-key.d.ts +22 -0
- package/dist/src/browser/network-key.js +66 -0
- package/dist/src/browser/network-key.test.d.ts +1 -0
- package/dist/src/browser/network-key.test.js +49 -0
- package/dist/src/browser/page.d.ts +14 -4
- package/dist/src/browser/page.js +48 -7
- package/dist/src/browser/page.test.js +97 -0
- package/dist/src/browser/shape-filter.d.ts +52 -0
- package/dist/src/browser/shape-filter.js +101 -0
- package/dist/src/browser/shape-filter.test.d.ts +1 -0
- package/dist/src/browser/shape-filter.test.js +101 -0
- package/dist/src/browser/shape.d.ts +23 -0
- package/dist/src/browser/shape.js +95 -0
- package/dist/src/browser/shape.test.d.ts +1 -0
- package/dist/src/browser/shape.test.js +82 -0
- package/dist/src/browser/target-errors.d.ts +14 -1
- package/dist/src/browser/target-errors.js +13 -0
- package/dist/src/browser/target-errors.test.js +39 -6
- package/dist/src/browser/target-resolver.d.ts +57 -10
- package/dist/src/browser/target-resolver.js +195 -75
- package/dist/src/browser/target-resolver.test.js +80 -5
- package/dist/src/cli.js +849 -267
- package/dist/src/cli.test.js +961 -90
- package/dist/src/commanderAdapter.d.ts +0 -1
- package/dist/src/commanderAdapter.js +2 -16
- package/dist/src/commanderAdapter.test.js +1 -1
- package/dist/src/completion-shared.js +2 -5
- package/dist/src/daemon.js +8 -0
- package/dist/src/download/article-download.d.ts +1 -0
- package/dist/src/download/article-download.js +3 -0
- package/dist/src/download/article-download.test.d.ts +1 -0
- package/dist/src/download/article-download.test.js +39 -0
- package/dist/src/execution.js +7 -2
- package/dist/src/execution.test.js +54 -0
- package/dist/src/main.js +16 -0
- package/dist/src/plugin.d.ts +1 -8
- package/dist/src/plugin.js +1 -27
- package/dist/src/plugin.test.js +1 -59
- package/dist/src/registry.d.ts +1 -0
- package/dist/src/registry.js +3 -2
- package/dist/src/registry.test.js +22 -0
- package/dist/src/types.d.ts +32 -8
- package/package.json +1 -1
- package/clis/twitter/lists-parser.js +0 -77
- package/clis/twitter/lists.d.ts +0 -5
- package/dist/src/cascade.d.ts +0 -46
- package/dist/src/cascade.js +0 -135
- package/dist/src/explore.d.ts +0 -99
- package/dist/src/explore.js +0 -402
- package/dist/src/generate-verified.d.ts +0 -105
- package/dist/src/generate-verified.js +0 -696
- package/dist/src/generate-verified.test.js +0 -925
- package/dist/src/generate.d.ts +0 -46
- package/dist/src/generate.js +0 -117
- package/dist/src/record.d.ts +0 -96
- package/dist/src/record.js +0 -657
- package/dist/src/record.test.js +0 -293
- package/dist/src/skill-generate.d.ts +0 -30
- package/dist/src/skill-generate.js +0 -75
- package/dist/src/skill-generate.test.js +0 -173
- package/dist/src/synthesize.d.ts +0 -97
- package/dist/src/synthesize.js +0 -208
- /package/dist/src/{generate-verified.test.d.ts → browser/compound.test.d.ts} +0 -0
- /package/dist/src/{record.test.d.ts → browser/extract.test.d.ts} +0 -0
- /package/dist/src/{skill-generate.test.d.ts → browser/find.test.d.ts} +0 -0
|
@@ -7,6 +7,13 @@ const RETRYABLE_DETAIL_ERROR_PATTERNS = [
|
|
|
7
7
|
/cannot find context with specified id/i,
|
|
8
8
|
/\[taxonomy=empty_result\]/i,
|
|
9
9
|
];
|
|
10
|
+
const DETAIL_AUTH_CHALLENGE_PATTERNS = [
|
|
11
|
+
/请在下图依次点击/i,
|
|
12
|
+
/验证码/i,
|
|
13
|
+
/请完成验证/i,
|
|
14
|
+
/验证登录/i,
|
|
15
|
+
/登录即可获得更多浏览权限/i,
|
|
16
|
+
];
|
|
10
17
|
function isRetryableDetailError(error) {
|
|
11
18
|
const message = error instanceof Error
|
|
12
19
|
? cleanText(error.message)
|
|
@@ -61,6 +68,14 @@ export async function runProcurementDetail(page, { url, site, query = '', }) {
|
|
|
61
68
|
const title = cleanText(row.title);
|
|
62
69
|
const detailText = cleanText(row.detailText);
|
|
63
70
|
const publishTime = cleanText(row.publishTime);
|
|
71
|
+
const authGateText = cleanText(`${title} ${detailText}`);
|
|
72
|
+
if (DETAIL_AUTH_CHALLENGE_PATTERNS.some((pattern) => pattern.test(authGateText))) {
|
|
73
|
+
throw taxonomyError('selector_drift', {
|
|
74
|
+
site,
|
|
75
|
+
command: 'detail',
|
|
76
|
+
detail: `detail page blocked by verification challenge: ${targetUrl}`,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
64
79
|
if (!title && !detailText) {
|
|
65
80
|
throw taxonomyError('empty_result', {
|
|
66
81
|
site,
|
|
@@ -69,4 +69,16 @@ describe('procurement detail runner', () => {
|
|
|
69
69
|
})).rejects.toThrow('[taxonomy=extraction_drift]');
|
|
70
70
|
expect(attempts).toBe(1);
|
|
71
71
|
});
|
|
72
|
+
it('rejects captcha/verification pages as selector_drift', async () => {
|
|
73
|
+
const page = createPage(async () => ({
|
|
74
|
+
title: '验证码',
|
|
75
|
+
detailText: '请在下图依次点击:槨畽黛',
|
|
76
|
+
publishTime: '',
|
|
77
|
+
}));
|
|
78
|
+
await expect(runProcurementDetail(page, {
|
|
79
|
+
url: 'https://www.jianyu360.cn/nologin/content/ABC.html',
|
|
80
|
+
site: 'jianyu',
|
|
81
|
+
query: '电梯',
|
|
82
|
+
})).rejects.toThrow('[taxonomy=selector_drift]');
|
|
83
|
+
});
|
|
72
84
|
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { cli, Strategy } from '@jackwener/opencli/registry';
|
|
2
|
+
|
|
3
|
+
cli({
|
|
4
|
+
site: 'nowcoder',
|
|
5
|
+
name: 'companies',
|
|
6
|
+
description: 'Hot companies for interview prep',
|
|
7
|
+
domain: 'www.nowcoder.com',
|
|
8
|
+
strategy: Strategy.PUBLIC,
|
|
9
|
+
browser: false,
|
|
10
|
+
args: [
|
|
11
|
+
{ name: 'job', type: 'str', default: '11002', help: 'Job ID (11002=Java, 11003=C++, 11200=Backend, 11203=QA, 11201=Frontend)' },
|
|
12
|
+
],
|
|
13
|
+
columns: ['rank', 'company', 'companyId'],
|
|
14
|
+
pipeline: [
|
|
15
|
+
{ fetch: { url: 'https://gw-c.nowcoder.com/api/sparta/company-question/hot-company-list?jobId=${{ args.job }}' } },
|
|
16
|
+
{ select: 'data.result' },
|
|
17
|
+
{ map: {
|
|
18
|
+
rank: '${{ index + 1 }}',
|
|
19
|
+
company: '${{ item.companyName }}',
|
|
20
|
+
companyId: '${{ item.companyId }}',
|
|
21
|
+
} },
|
|
22
|
+
],
|
|
23
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { cli, Strategy } from '@jackwener/opencli/registry';
|
|
2
|
+
|
|
3
|
+
cli({
|
|
4
|
+
site: 'nowcoder',
|
|
5
|
+
name: 'creators',
|
|
6
|
+
description: 'Top content creators leaderboard',
|
|
7
|
+
domain: 'www.nowcoder.com',
|
|
8
|
+
strategy: Strategy.PUBLIC,
|
|
9
|
+
browser: false,
|
|
10
|
+
args: [
|
|
11
|
+
{ name: 'limit', type: 'int', default: 10, help: 'Number of items' },
|
|
12
|
+
],
|
|
13
|
+
columns: ['rank', 'nickname', 'school', 'level', 'heat', 'tag'],
|
|
14
|
+
pipeline: [
|
|
15
|
+
{ fetch: { url: 'https://gw-c.nowcoder.com/api/sparta/content/creator/top-list' } },
|
|
16
|
+
{ select: 'data.result' },
|
|
17
|
+
{ map: {
|
|
18
|
+
rank: '${{ index + 1 }}',
|
|
19
|
+
nickname: `\${{ item.userBrief?.nickname || '' }}`,
|
|
20
|
+
school: `\${{ item.userBrief?.educationInfo || '' }}`,
|
|
21
|
+
level: `\${{ item.userBrief?.honorLevelName || '' }}`,
|
|
22
|
+
heat: '${{ item.hotValue }}',
|
|
23
|
+
tag: `\${{ item.tag || '' }}`,
|
|
24
|
+
} },
|
|
25
|
+
{ limit: '${{ args.limit }}' },
|
|
26
|
+
],
|
|
27
|
+
});
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { cli } from '@jackwener/opencli/registry';
|
|
2
|
+
|
|
3
|
+
cli({
|
|
4
|
+
site: 'nowcoder',
|
|
5
|
+
name: 'detail',
|
|
6
|
+
description: 'Post detail view (supports ID / UUID / URL)',
|
|
7
|
+
domain: 'www.nowcoder.com',
|
|
8
|
+
args: [
|
|
9
|
+
{ name: 'id', positional: true, required: true, help: 'Post ID, UUID, or URL' },
|
|
10
|
+
],
|
|
11
|
+
columns: ['title', 'author', 'school', 'content', 'likes', 'comments', 'views', 'time', 'location'],
|
|
12
|
+
pipeline: [
|
|
13
|
+
{ navigate: 'https://www.nowcoder.com' },
|
|
14
|
+
{ evaluate: `(async () => {
|
|
15
|
+
const raw = \${{ args.id | json }};
|
|
16
|
+
const base = 'https://gw-c.nowcoder.com';
|
|
17
|
+
const strip = (html) => (html || '').replace(/<[^>]+>/g, '').replace(/ /g, ' ').replace(/</g, '<').replace(/>/g, '>').replace(/&/g, '&').trim();
|
|
18
|
+
|
|
19
|
+
let id = raw;
|
|
20
|
+
const urlMatch = raw.match(/discuss\\/(\\d+)/);
|
|
21
|
+
if (urlMatch) id = urlMatch[1];
|
|
22
|
+
|
|
23
|
+
let data = null;
|
|
24
|
+
|
|
25
|
+
if (/[a-f]/.test(id) && id.length > 20) {
|
|
26
|
+
const r = await fetch(base + '/api/sparta/detail/moment-data/detail/' + id, {credentials: 'include'});
|
|
27
|
+
const d = await r.json();
|
|
28
|
+
if (d.success && d.data) data = d.data;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (!data && /^\\d+$/.test(id)) {
|
|
32
|
+
const r = await fetch(base + '/api/sparta/detail/content-data/detail/' + id, {credentials: 'include'});
|
|
33
|
+
const d = await r.json();
|
|
34
|
+
if (d.success && d.data) data = d.data;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (!data && /^\\d+$/.test(id)) {
|
|
38
|
+
const r = await fetch(base + '/api/sparta/detail/moment-data/detail/' + id, {credentials: 'include'});
|
|
39
|
+
const d = await r.json();
|
|
40
|
+
if (d.success && d.data) data = d.data;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (!data) throw new Error('Post not found: ' + id);
|
|
44
|
+
|
|
45
|
+
const user = data.userBrief || {};
|
|
46
|
+
const freq = data.frequencyData || {};
|
|
47
|
+
return [{
|
|
48
|
+
title: data.title || '(untitled)',
|
|
49
|
+
author: user.nickname || '',
|
|
50
|
+
school: user.educationInfo || '',
|
|
51
|
+
content: strip(data.content || '').substring(0, 500),
|
|
52
|
+
likes: freq.likeCnt || 0,
|
|
53
|
+
comments: freq.commentCnt || freq.totalCommentCnt || 0,
|
|
54
|
+
views: freq.viewCnt || 0,
|
|
55
|
+
time: data.createdAt ? new Date(data.createdAt).toISOString().slice(0, 19) : '',
|
|
56
|
+
location: data.ip4Location || '',
|
|
57
|
+
}];
|
|
58
|
+
})()
|
|
59
|
+
` },
|
|
60
|
+
],
|
|
61
|
+
});
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { cli } from '@jackwener/opencli/registry';
|
|
2
|
+
|
|
3
|
+
cli({
|
|
4
|
+
site: 'nowcoder',
|
|
5
|
+
name: 'experience',
|
|
6
|
+
description: 'Interview experience posts',
|
|
7
|
+
domain: 'www.nowcoder.com',
|
|
8
|
+
args: [
|
|
9
|
+
{ name: 'page', type: 'int', default: 1, help: 'Page number' },
|
|
10
|
+
{ name: 'limit', type: 'int', default: 15, help: 'Number of items' },
|
|
11
|
+
],
|
|
12
|
+
columns: ['rank', 'title', 'author', 'school', 'likes', 'comments', 'views', 'id'],
|
|
13
|
+
pipeline: [
|
|
14
|
+
{ navigate: 'https://www.nowcoder.com' },
|
|
15
|
+
{ evaluate: `(async () => {
|
|
16
|
+
const page = \${{ args.page }};
|
|
17
|
+
const limit = \${{ args.limit }};
|
|
18
|
+
const r = await fetch('https://gw-c.nowcoder.com/api/sparta/home/tab/content?tabId=818&categoryType=1&pageNo=' + page + '&pageSize=' + limit, {credentials: 'include'});
|
|
19
|
+
const d = await r.json();
|
|
20
|
+
if (!d.success) throw new Error(d.msg || 'API failed');
|
|
21
|
+
return (d.data?.records || []).map((item, i) => ({
|
|
22
|
+
rank: i + 1,
|
|
23
|
+
title: item.contentData?.title || '',
|
|
24
|
+
author: item.userBrief?.nickname || '',
|
|
25
|
+
school: item.userBrief?.educationInfo || '',
|
|
26
|
+
likes: item.frequencyData?.likeCnt || 0,
|
|
27
|
+
comments: item.frequencyData?.commentCnt || 0,
|
|
28
|
+
views: item.frequencyData?.viewCnt || 0,
|
|
29
|
+
id: item.contentData?.uuid || item.contentData?.id || item.contentId || '',
|
|
30
|
+
}));
|
|
31
|
+
})()
|
|
32
|
+
` },
|
|
33
|
+
{ filter: 'item.title' },
|
|
34
|
+
{ limit: '${{ args.limit }}' },
|
|
35
|
+
],
|
|
36
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { cli, Strategy } from '@jackwener/opencli/registry';
|
|
2
|
+
|
|
3
|
+
cli({
|
|
4
|
+
site: 'nowcoder',
|
|
5
|
+
name: 'hot',
|
|
6
|
+
description: 'Hot search ranking',
|
|
7
|
+
domain: 'www.nowcoder.com',
|
|
8
|
+
strategy: Strategy.PUBLIC,
|
|
9
|
+
browser: false,
|
|
10
|
+
args: [
|
|
11
|
+
{ name: 'limit', type: 'int', default: 10, help: 'Number of items' },
|
|
12
|
+
],
|
|
13
|
+
columns: ['rank', 'title', 'heat'],
|
|
14
|
+
pipeline: [
|
|
15
|
+
{ fetch: { url: 'https://gw-c.nowcoder.com/api/sparta/hot-search/hot-content' } },
|
|
16
|
+
{ select: 'data.hotQuery' },
|
|
17
|
+
{ map: {
|
|
18
|
+
rank: '${{ item.rank }}',
|
|
19
|
+
title: '${{ item.query }}',
|
|
20
|
+
heat: '${{ item.hotValue }}',
|
|
21
|
+
} },
|
|
22
|
+
{ limit: '${{ args.limit }}' },
|
|
23
|
+
],
|
|
24
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { cli, Strategy } from '@jackwener/opencli/registry';
|
|
2
|
+
|
|
3
|
+
cli({
|
|
4
|
+
site: 'nowcoder',
|
|
5
|
+
name: 'jobs',
|
|
6
|
+
description: 'Career category listing',
|
|
7
|
+
domain: 'www.nowcoder.com',
|
|
8
|
+
strategy: Strategy.PUBLIC,
|
|
9
|
+
browser: false,
|
|
10
|
+
args: [],
|
|
11
|
+
columns: ['id', 'career', 'learners'],
|
|
12
|
+
pipeline: [
|
|
13
|
+
{ fetch: { url: 'https://gw-c.nowcoder.com/api/sparta/company-question/careerJobLevel1List' } },
|
|
14
|
+
{ select: 'data.careerJobSelectors' },
|
|
15
|
+
{ map: {
|
|
16
|
+
id: '${{ item.id }}',
|
|
17
|
+
career: '${{ item.name }}',
|
|
18
|
+
learners: `\${{ item.practiceCount || '' }}`,
|
|
19
|
+
} },
|
|
20
|
+
],
|
|
21
|
+
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { cli } from '@jackwener/opencli/registry';
|
|
2
|
+
|
|
3
|
+
cli({
|
|
4
|
+
site: 'nowcoder',
|
|
5
|
+
name: 'notifications',
|
|
6
|
+
description: 'Unread message summary',
|
|
7
|
+
domain: 'www.nowcoder.com',
|
|
8
|
+
args: [],
|
|
9
|
+
columns: ['type', 'unread'],
|
|
10
|
+
pipeline: [
|
|
11
|
+
{ navigate: 'https://www.nowcoder.com' },
|
|
12
|
+
{ evaluate: `(async () => {
|
|
13
|
+
const r = await fetch('https://gw-c.nowcoder.com/api/sparta/message/pc/unread/detail', {credentials: 'include'});
|
|
14
|
+
const d = await r.json();
|
|
15
|
+
if (!d.success) throw new Error(d.msg || 'API failed');
|
|
16
|
+
const data = d.data;
|
|
17
|
+
return [
|
|
18
|
+
{type: 'system', unread: data.systemNotice?.unreadCount || 0},
|
|
19
|
+
{type: 'likes', unread: data.likeCollect?.unreadCount || 0},
|
|
20
|
+
{type: 'comments', unread: data.commentMessage?.unreadCount || 0},
|
|
21
|
+
{type: 'follows', unread: data.followMessage?.unreadCount || 0},
|
|
22
|
+
{type: 'messages', unread: data.privateMessage?.unreadCount || 0},
|
|
23
|
+
{type: 'job_apply', unread: data.nowPickJobApply?.unreadCount || 0},
|
|
24
|
+
{type: 'total', unread: data.total?.unreadCount || 0},
|
|
25
|
+
];
|
|
26
|
+
})()
|
|
27
|
+
` },
|
|
28
|
+
],
|
|
29
|
+
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { cli } from '@jackwener/opencli/registry';
|
|
2
|
+
|
|
3
|
+
cli({
|
|
4
|
+
site: 'nowcoder',
|
|
5
|
+
name: 'papers',
|
|
6
|
+
description: 'Interview question bank by company and job',
|
|
7
|
+
domain: 'www.nowcoder.com',
|
|
8
|
+
args: [
|
|
9
|
+
{ name: 'job', type: 'str', default: '11002', help: 'Job ID (11002=Java, 11003=C++, 11200=Backend, 11203=QA, 11201=Frontend)' },
|
|
10
|
+
{ name: 'company', type: 'str', default: '', help: 'Company ID (e.g. 139=Baidu, 138=Tencent, 239=Huawei)' },
|
|
11
|
+
{ name: 'limit', type: 'int', default: 10, help: 'Number of items' },
|
|
12
|
+
],
|
|
13
|
+
columns: ['rank', 'title', 'company', 'practitioners'],
|
|
14
|
+
pipeline: [
|
|
15
|
+
{ navigate: 'https://www.nowcoder.com' },
|
|
16
|
+
{ evaluate: `(async () => {
|
|
17
|
+
const jobId = parseInt(\${{ args.job | json }});
|
|
18
|
+
const companyId = \${{ args.company | json }};
|
|
19
|
+
const limit = \${{ args.limit }};
|
|
20
|
+
const body = {jobId, page: 1, pageSize: limit};
|
|
21
|
+
if (companyId) body.companyId = parseInt(companyId);
|
|
22
|
+
const r = await fetch('https://gw-c.nowcoder.com/api/sparta/company-question/get-paper-list', {
|
|
23
|
+
method: 'POST',
|
|
24
|
+
credentials: 'include',
|
|
25
|
+
headers: {'Content-Type': 'application/json'},
|
|
26
|
+
body: JSON.stringify(body)
|
|
27
|
+
});
|
|
28
|
+
const d = await r.json();
|
|
29
|
+
if (!d.success) throw new Error(d.msg || 'API failed');
|
|
30
|
+
return (d.data?.records || []).map((p, i) => ({
|
|
31
|
+
rank: i + 1,
|
|
32
|
+
title: p.paperName || '',
|
|
33
|
+
company: p.companyTag?.name || '',
|
|
34
|
+
practitioners: p.practiceCnt || 0,
|
|
35
|
+
}));
|
|
36
|
+
})()
|
|
37
|
+
` },
|
|
38
|
+
{ limit: '${{ args.limit }}' },
|
|
39
|
+
],
|
|
40
|
+
});
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { cli } from '@jackwener/opencli/registry';
|
|
2
|
+
|
|
3
|
+
cli({
|
|
4
|
+
site: 'nowcoder',
|
|
5
|
+
name: 'practice',
|
|
6
|
+
description: 'Categorized practice questions with progress',
|
|
7
|
+
domain: 'www.nowcoder.com',
|
|
8
|
+
args: [
|
|
9
|
+
{ name: 'job', type: 'str', default: '11226', help: 'Career ID (11226=Software, 11227=Hardware, 11229=Product, 11230=Finance)' },
|
|
10
|
+
{ name: 'limit', type: 'int', default: 20, help: 'Number of items' },
|
|
11
|
+
],
|
|
12
|
+
columns: ['category', 'subject', 'total', 'done', 'remaining'],
|
|
13
|
+
pipeline: [
|
|
14
|
+
{ navigate: 'https://www.nowcoder.com' },
|
|
15
|
+
{ evaluate: `(async () => {
|
|
16
|
+
const jobId = \${{ args.job | json }};
|
|
17
|
+
const limit = \${{ args.limit }};
|
|
18
|
+
const r = await fetch('https://gw-c.nowcoder.com/api/sparta/intelligent/getPCIntelligentList?jobId=' + jobId, {credentials: 'include'});
|
|
19
|
+
const d = await r.json();
|
|
20
|
+
if (!d.success) throw new Error(d.msg || 'API failed');
|
|
21
|
+
const all = [];
|
|
22
|
+
for (const tag of (d.data?.tags || [])) {
|
|
23
|
+
for (const item of (tag.items || [])) {
|
|
24
|
+
all.push({
|
|
25
|
+
category: tag.title || 'recommended',
|
|
26
|
+
subject: item.title,
|
|
27
|
+
total: item.tcount,
|
|
28
|
+
done: item.rcount,
|
|
29
|
+
remaining: item.leftCount,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return all.slice(0, limit);
|
|
34
|
+
})()
|
|
35
|
+
` },
|
|
36
|
+
],
|
|
37
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { cli, Strategy } from '@jackwener/opencli/registry';
|
|
2
|
+
|
|
3
|
+
cli({
|
|
4
|
+
site: 'nowcoder',
|
|
5
|
+
name: 'recommend',
|
|
6
|
+
description: 'Recommended feed',
|
|
7
|
+
domain: 'www.nowcoder.com',
|
|
8
|
+
strategy: Strategy.PUBLIC,
|
|
9
|
+
browser: false,
|
|
10
|
+
args: [
|
|
11
|
+
{ name: 'page', type: 'int', default: 1, help: 'Page number' },
|
|
12
|
+
{ name: 'limit', type: 'int', default: 15, help: 'Number of items' },
|
|
13
|
+
],
|
|
14
|
+
columns: ['rank', 'title', 'author', 'likes', 'comments', 'views', 'id'],
|
|
15
|
+
pipeline: [
|
|
16
|
+
{ fetch: { url: 'https://gw-c.nowcoder.com/api/sparta/home/recommend?page=${{ args.page }}&size=${{ args.limit }}' } },
|
|
17
|
+
{ select: 'data.records' },
|
|
18
|
+
{ map: {
|
|
19
|
+
rank: '${{ index + 1 }}',
|
|
20
|
+
title: `\${{ item.momentData?.title || item.longContentData?.title || item.contentData?.title || '' }}`,
|
|
21
|
+
author: `\${{ item.userBrief?.nickname || '' }}`,
|
|
22
|
+
likes: '${{ item.frequencyData?.likeCnt || 0 }}',
|
|
23
|
+
comments: '${{ item.frequencyData?.commentCnt || 0 }}',
|
|
24
|
+
views: '${{ item.frequencyData?.viewCnt || 0 }}',
|
|
25
|
+
id: `\${{ item.momentData?.uuid || item.longContentData?.uuid || item.contentData?.uuid || item.contentId || '' }}`,
|
|
26
|
+
} },
|
|
27
|
+
{ filter: 'item.title' },
|
|
28
|
+
{ limit: '${{ args.limit }}' },
|
|
29
|
+
],
|
|
30
|
+
});
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { cli } from '@jackwener/opencli/registry';
|
|
2
|
+
|
|
3
|
+
cli({
|
|
4
|
+
site: 'nowcoder',
|
|
5
|
+
name: 'referral',
|
|
6
|
+
description: 'Internal referral posts',
|
|
7
|
+
domain: 'www.nowcoder.com',
|
|
8
|
+
args: [
|
|
9
|
+
{ name: 'page', type: 'int', default: 1, help: 'Page number' },
|
|
10
|
+
{ name: 'limit', type: 'int', default: 15, help: 'Number of items' },
|
|
11
|
+
],
|
|
12
|
+
columns: ['rank', 'title', 'author', 'school', 'likes', 'comments', 'views', 'id'],
|
|
13
|
+
pipeline: [
|
|
14
|
+
{ navigate: 'https://www.nowcoder.com' },
|
|
15
|
+
{ evaluate: `(async () => {
|
|
16
|
+
const page = \${{ args.page }};
|
|
17
|
+
const limit = \${{ args.limit }};
|
|
18
|
+
const r = await fetch('https://gw-c.nowcoder.com/api/sparta/home/tab/content?tabId=861&categoryType=1&pageNo=' + page + '&pageSize=' + limit, {credentials: 'include'});
|
|
19
|
+
const d = await r.json();
|
|
20
|
+
if (!d.success) throw new Error(d.msg || 'API failed');
|
|
21
|
+
return (d.data?.records || []).map((item, i) => {
|
|
22
|
+
const content = item.contentData || item.momentData || {};
|
|
23
|
+
return {
|
|
24
|
+
rank: i + 1,
|
|
25
|
+
title: content.title || '',
|
|
26
|
+
author: item.userBrief?.nickname || '',
|
|
27
|
+
school: item.userBrief?.educationInfo || '',
|
|
28
|
+
likes: item.frequencyData?.likeCnt || 0,
|
|
29
|
+
comments: item.frequencyData?.commentCnt || 0,
|
|
30
|
+
views: item.frequencyData?.viewCnt || 0,
|
|
31
|
+
id: item.momentData?.uuid || item.contentData?.uuid || item.contentId || '',
|
|
32
|
+
};
|
|
33
|
+
});
|
|
34
|
+
})()
|
|
35
|
+
` },
|
|
36
|
+
{ filter: 'item.title' },
|
|
37
|
+
{ limit: '${{ args.limit }}' },
|
|
38
|
+
],
|
|
39
|
+
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { cli } from '@jackwener/opencli/registry';
|
|
2
|
+
|
|
3
|
+
cli({
|
|
4
|
+
site: 'nowcoder',
|
|
5
|
+
name: 'salary',
|
|
6
|
+
description: 'Salary disclosure posts',
|
|
7
|
+
domain: 'www.nowcoder.com',
|
|
8
|
+
args: [
|
|
9
|
+
{ name: 'page', type: 'int', default: 1, help: 'Page number' },
|
|
10
|
+
{ name: 'limit', type: 'int', default: 15, help: 'Number of items' },
|
|
11
|
+
],
|
|
12
|
+
columns: ['rank', 'title', 'author', 'school', 'likes', 'comments', 'views', 'id'],
|
|
13
|
+
pipeline: [
|
|
14
|
+
{ navigate: 'https://www.nowcoder.com' },
|
|
15
|
+
{ evaluate: `(async () => {
|
|
16
|
+
const page = \${{ args.page }};
|
|
17
|
+
const limit = \${{ args.limit }};
|
|
18
|
+
const r = await fetch('https://gw-c.nowcoder.com/api/sparta/home/tab/content?tabId=858&categoryType=1&pageNo=' + page + '&pageSize=' + limit, {credentials: 'include'});
|
|
19
|
+
const d = await r.json();
|
|
20
|
+
if (!d.success) throw new Error(d.msg || 'API failed');
|
|
21
|
+
return (d.data?.records || []).map((item, i) => {
|
|
22
|
+
const moment = item.momentData || {};
|
|
23
|
+
const content = item.contentData || {};
|
|
24
|
+
return {
|
|
25
|
+
rank: i + 1,
|
|
26
|
+
title: moment.title || content.title || '',
|
|
27
|
+
author: item.userBrief?.nickname || '',
|
|
28
|
+
school: item.userBrief?.educationInfo || '',
|
|
29
|
+
likes: item.frequencyData?.likeCnt || 0,
|
|
30
|
+
comments: item.frequencyData?.commentCnt || 0,
|
|
31
|
+
views: item.frequencyData?.viewCnt || 0,
|
|
32
|
+
id: moment.uuid || content.uuid || item.contentId || '',
|
|
33
|
+
};
|
|
34
|
+
});
|
|
35
|
+
})()
|
|
36
|
+
` },
|
|
37
|
+
{ filter: 'item.title' },
|
|
38
|
+
{ limit: '${{ args.limit }}' },
|
|
39
|
+
],
|
|
40
|
+
});
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { cli } from '@jackwener/opencli/registry';
|
|
2
|
+
|
|
3
|
+
cli({
|
|
4
|
+
site: 'nowcoder',
|
|
5
|
+
name: 'search',
|
|
6
|
+
description: 'Full-text search',
|
|
7
|
+
domain: 'www.nowcoder.com',
|
|
8
|
+
args: [
|
|
9
|
+
{ name: 'query', positional: true, required: true, help: 'Search keyword' },
|
|
10
|
+
{ name: 'type', type: 'str', default: 'all', help: 'Search type (all/post/question/user/job)' },
|
|
11
|
+
{ name: 'limit', type: 'int', default: 10, help: 'Number of results' },
|
|
12
|
+
],
|
|
13
|
+
columns: ['rank', 'title', 'author', 'school', 'content', 'id'],
|
|
14
|
+
pipeline: [
|
|
15
|
+
{ navigate: 'https://www.nowcoder.com' },
|
|
16
|
+
{ evaluate: `(async () => {
|
|
17
|
+
const query = \${{ args.query | json }};
|
|
18
|
+
const type = \${{ args.type | json }};
|
|
19
|
+
const limit = \${{ args.limit }};
|
|
20
|
+
const strip = (html) => (html || '').replace(/<[^>]+>/g, '').replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/ /g, ' ').trim();
|
|
21
|
+
const r = await fetch('https://gw-c.nowcoder.com/api/sparta/pc/search', {
|
|
22
|
+
method: 'POST',
|
|
23
|
+
credentials: 'include',
|
|
24
|
+
headers: {'Content-Type': 'application/json'},
|
|
25
|
+
body: JSON.stringify({query, type, page: 1, pageSize: limit})
|
|
26
|
+
});
|
|
27
|
+
const d = await r.json();
|
|
28
|
+
if (!d.success) throw new Error(d.msg || 'search failed');
|
|
29
|
+
return (d.data?.records || []).map((item, i) => {
|
|
30
|
+
const data = item.data || {};
|
|
31
|
+
const moment = data.momentData || {};
|
|
32
|
+
const contentData = data.contentData || {};
|
|
33
|
+
const user = data.userBrief || {};
|
|
34
|
+
const uuid = moment.uuid || contentData.uuid || '';
|
|
35
|
+
const id = data.contentId || '';
|
|
36
|
+
return {
|
|
37
|
+
rank: i + 1,
|
|
38
|
+
title: moment.title || contentData.title || user.nickname || '',
|
|
39
|
+
author: user.nickname || '',
|
|
40
|
+
school: user.educationInfo || '',
|
|
41
|
+
content: strip(moment.content || contentData.content || ''),
|
|
42
|
+
id: uuid || id,
|
|
43
|
+
};
|
|
44
|
+
}).filter(r => r.title);
|
|
45
|
+
})()
|
|
46
|
+
` },
|
|
47
|
+
{ limit: '${{ args.limit }}' },
|
|
48
|
+
],
|
|
49
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { cli } from '@jackwener/opencli/registry';
|
|
2
|
+
|
|
3
|
+
cli({
|
|
4
|
+
site: 'nowcoder',
|
|
5
|
+
name: 'suggest',
|
|
6
|
+
description: 'Search suggestions',
|
|
7
|
+
domain: 'www.nowcoder.com',
|
|
8
|
+
args: [
|
|
9
|
+
{ name: 'query', positional: true, required: true, help: 'Search keyword' },
|
|
10
|
+
],
|
|
11
|
+
columns: ['rank', 'suggestion', 'type'],
|
|
12
|
+
pipeline: [
|
|
13
|
+
{ navigate: 'https://www.nowcoder.com' },
|
|
14
|
+
{ evaluate: `(async () => {
|
|
15
|
+
const query = \${{ args.query | json }};
|
|
16
|
+
const r = await fetch('https://gw-c.nowcoder.com/api/sparta/search/suggest', {
|
|
17
|
+
method: 'POST',
|
|
18
|
+
credentials: 'include',
|
|
19
|
+
headers: {'Content-Type': 'application/json'},
|
|
20
|
+
body: JSON.stringify({query})
|
|
21
|
+
});
|
|
22
|
+
const d = await r.json();
|
|
23
|
+
if (!d.success) throw new Error(d.msg || 'suggest failed');
|
|
24
|
+
return (d.data?.records || []).map((item, i) => ({
|
|
25
|
+
rank: i + 1,
|
|
26
|
+
suggestion: item.name || '',
|
|
27
|
+
type: item.typeName || 'general',
|
|
28
|
+
}));
|
|
29
|
+
})()
|
|
30
|
+
` },
|
|
31
|
+
{ limit: '10' },
|
|
32
|
+
],
|
|
33
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { cli, Strategy } from '@jackwener/opencli/registry';
|
|
2
|
+
|
|
3
|
+
cli({
|
|
4
|
+
site: 'nowcoder',
|
|
5
|
+
name: 'topics',
|
|
6
|
+
description: 'Hot discussion topics',
|
|
7
|
+
domain: 'www.nowcoder.com',
|
|
8
|
+
strategy: Strategy.PUBLIC,
|
|
9
|
+
browser: false,
|
|
10
|
+
args: [
|
|
11
|
+
{ name: 'limit', type: 'int', default: 10, help: 'Number of items' },
|
|
12
|
+
],
|
|
13
|
+
columns: ['rank', 'topic', 'views', 'posts', 'heat', 'id'],
|
|
14
|
+
pipeline: [
|
|
15
|
+
{ fetch: { url: 'https://gw-c.nowcoder.com/api/sparta/subject/hot-subject' } },
|
|
16
|
+
{ select: 'data.result' },
|
|
17
|
+
{ map: {
|
|
18
|
+
rank: '${{ index + 1 }}',
|
|
19
|
+
topic: '${{ item.content }}',
|
|
20
|
+
views: '${{ item.viewCount }}',
|
|
21
|
+
posts: '${{ item.momentCount }}',
|
|
22
|
+
heat: '${{ item.hotValue }}',
|
|
23
|
+
id: '${{ item.uuid || item.id || "" }}',
|
|
24
|
+
} },
|
|
25
|
+
{ limit: '${{ args.limit }}' },
|
|
26
|
+
],
|
|
27
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { cli, Strategy } from '@jackwener/opencli/registry';
|
|
2
|
+
|
|
3
|
+
cli({
|
|
4
|
+
site: 'nowcoder',
|
|
5
|
+
name: 'trending',
|
|
6
|
+
description: 'Trending posts',
|
|
7
|
+
domain: 'www.nowcoder.com',
|
|
8
|
+
strategy: Strategy.PUBLIC,
|
|
9
|
+
browser: false,
|
|
10
|
+
args: [
|
|
11
|
+
{ name: 'limit', type: 'int', default: 10, help: 'Number of items' },
|
|
12
|
+
],
|
|
13
|
+
columns: ['rank', 'title', 'heat', 'id'],
|
|
14
|
+
pipeline: [
|
|
15
|
+
{ fetch: { url: 'https://gw-c.nowcoder.com/api/sparta/hot-search/top-hot-pc' } },
|
|
16
|
+
{ select: 'data.result' },
|
|
17
|
+
{ map: {
|
|
18
|
+
rank: '${{ index + 1 }}',
|
|
19
|
+
title: '${{ item.title }}',
|
|
20
|
+
heat: '${{ item.hotValueFromDolphin }}',
|
|
21
|
+
id: '${{ item.uuid || item.id || "" }}',
|
|
22
|
+
} },
|
|
23
|
+
{ limit: '${{ args.limit }}' },
|
|
24
|
+
],
|
|
25
|
+
});
|