@jackwener/opencli 1.7.4 → 1.7.5
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 +71 -49
- package/README.zh-CN.md +73 -60
- package/cli-manifest.json +3261 -1758
- 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/deepseek/ask.js +74 -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 +208 -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/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/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/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 +1 -1
- package/dist/src/browser/bridge.d.ts +1 -0
- package/dist/src/browser/bridge.js +1 -1
- package/dist/src/browser/cdp.js +1 -1
- 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.js +7 -2
- 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/cli.js +227 -150
- package/dist/src/cli.test.js +167 -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.js +39 -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 +14 -5
- 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.d.ts +0 -1
- 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.d.ts +0 -1
- 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 → download/article-download.test.d.ts} +0 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { getRegistry } from '@jackwener/opencli/registry';
|
|
3
|
+
import './search.js';
|
|
4
|
+
import './recent.js';
|
|
5
|
+
|
|
6
|
+
describe('gov-policy commands', () => {
|
|
7
|
+
const search = getRegistry().get('gov-policy/search');
|
|
8
|
+
const recent = getRegistry().get('gov-policy/recent');
|
|
9
|
+
|
|
10
|
+
it('registers both commands as public browser commands', () => {
|
|
11
|
+
expect(search).toBeDefined();
|
|
12
|
+
expect(recent).toBeDefined();
|
|
13
|
+
expect(search.browser).toBe(true);
|
|
14
|
+
expect(recent.browser).toBe(true);
|
|
15
|
+
expect(search.strategy).toBe('public');
|
|
16
|
+
expect(recent.strategy).toBe('public');
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('rejects empty search queries before browser navigation', async () => {
|
|
20
|
+
const page = { goto: vi.fn() };
|
|
21
|
+
await expect(search.func(page, { query: ' ' })).rejects.toMatchObject({
|
|
22
|
+
name: 'ArgumentError',
|
|
23
|
+
code: 'ARGUMENT',
|
|
24
|
+
});
|
|
25
|
+
expect(page.goto).not.toHaveBeenCalled();
|
|
26
|
+
});
|
|
27
|
+
});
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { cli, Strategy } from '@jackwener/opencli/registry';
|
|
2
|
+
import { clampInt } from '../_shared/common.js';
|
|
3
|
+
|
|
4
|
+
cli({
|
|
5
|
+
site: 'gov-policy',
|
|
6
|
+
name: 'recent',
|
|
7
|
+
description: '国务院最新政策文件',
|
|
8
|
+
domain: 'www.gov.cn',
|
|
9
|
+
strategy: Strategy.PUBLIC,
|
|
10
|
+
browser: true,
|
|
11
|
+
args: [
|
|
12
|
+
{ name: 'limit', type: 'int', default: 10, help: '返回结果数量 (max 20)' },
|
|
13
|
+
],
|
|
14
|
+
columns: ['rank', 'title', 'date', 'source', 'url'],
|
|
15
|
+
navigateBefore: false,
|
|
16
|
+
func: async (page, kwargs) => {
|
|
17
|
+
const limit = clampInt(kwargs.limit, 10, 1, 20);
|
|
18
|
+
await page.goto('https://www.gov.cn/zhengce/zuixin/index.htm');
|
|
19
|
+
await page.wait(4);
|
|
20
|
+
const data = await page.evaluate(`
|
|
21
|
+
(async () => {
|
|
22
|
+
const normalize = v => (v || '').replace(/\\s+/g, ' ').trim();
|
|
23
|
+
for (let i = 0; i < 20; i++) {
|
|
24
|
+
if (document.querySelector('.news_box li, .list li, .list_item, .news-list li')) break;
|
|
25
|
+
await new Promise(r => setTimeout(r, 500));
|
|
26
|
+
}
|
|
27
|
+
const results = [];
|
|
28
|
+
for (const el of document.querySelectorAll('.news_box li, .list li, .list_item, .news-list li')) {
|
|
29
|
+
const titleEl = el.querySelector('a');
|
|
30
|
+
const title = normalize(titleEl?.textContent);
|
|
31
|
+
if (!title || title.length < 4) continue;
|
|
32
|
+
|
|
33
|
+
let url = titleEl?.getAttribute('href') || '';
|
|
34
|
+
if (url && !url.startsWith('http')) url = 'https://www.gov.cn' + url;
|
|
35
|
+
|
|
36
|
+
const date = (el.textContent || '').match(/(\\d{4}[-./]\\d{1,2}[-./]\\d{1,2})/)?.[1] || '';
|
|
37
|
+
const source = normalize(el.querySelector('.source, .from')?.textContent);
|
|
38
|
+
|
|
39
|
+
results.push({ rank: results.length + 1, title, date, source, url });
|
|
40
|
+
if (results.length >= ${limit}) break;
|
|
41
|
+
}
|
|
42
|
+
return results;
|
|
43
|
+
})()
|
|
44
|
+
`);
|
|
45
|
+
return Array.isArray(data) ? data : [];
|
|
46
|
+
},
|
|
47
|
+
});
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { cli, Strategy } from '@jackwener/opencli/registry';
|
|
2
|
+
import { clampInt, requireNonEmptyQuery } from '../_shared/common.js';
|
|
3
|
+
|
|
4
|
+
cli({
|
|
5
|
+
site: 'gov-policy',
|
|
6
|
+
name: 'search',
|
|
7
|
+
description: '中国政府网政策文件搜索',
|
|
8
|
+
domain: 'sousuo.www.gov.cn',
|
|
9
|
+
strategy: Strategy.PUBLIC,
|
|
10
|
+
browser: true,
|
|
11
|
+
args: [
|
|
12
|
+
{ name: 'query', positional: true, required: true, help: '搜索关键词' },
|
|
13
|
+
{ name: 'limit', type: 'int', default: 10, help: '返回结果数量 (max 20)' },
|
|
14
|
+
],
|
|
15
|
+
columns: ['rank', 'title', 'description', 'date', 'url'],
|
|
16
|
+
navigateBefore: false,
|
|
17
|
+
func: async (page, kwargs) => {
|
|
18
|
+
const limit = clampInt(kwargs.limit, 10, 1, 20);
|
|
19
|
+
const query = requireNonEmptyQuery(kwargs.query);
|
|
20
|
+
await page.goto(`https://sousuo.www.gov.cn/sousuo/search.shtml?code=17da70961a7&dataTypeId=107&searchWord=${encodeURIComponent(query)}`);
|
|
21
|
+
await page.wait(5);
|
|
22
|
+
const data = await page.evaluate(`
|
|
23
|
+
(async () => {
|
|
24
|
+
const normalize = v => (v || '').replace(/\\s+/g, ' ').trim();
|
|
25
|
+
for (let i = 0; i < 30; i++) {
|
|
26
|
+
if (document.querySelectorAll('.basic_result_content .item, .js_basic_result_content .item').length > 0) break;
|
|
27
|
+
await new Promise(r => setTimeout(r, 500));
|
|
28
|
+
}
|
|
29
|
+
const results = [];
|
|
30
|
+
for (const el of document.querySelectorAll('.basic_result_content .item, .js_basic_result_content .item')) {
|
|
31
|
+
const titleEl = el.querySelector('a.title, .title a, a.log-anchor');
|
|
32
|
+
const title = normalize(titleEl?.textContent).replace(/<[^>]+>/g, '');
|
|
33
|
+
if (!title || title.length < 4) continue;
|
|
34
|
+
|
|
35
|
+
let url = titleEl?.getAttribute('href') || '';
|
|
36
|
+
if (url && !url.startsWith('http')) url = 'https://www.gov.cn' + url;
|
|
37
|
+
|
|
38
|
+
const description = normalize(el.querySelector('.description')?.textContent).slice(0, 120);
|
|
39
|
+
const date = (el.textContent || '').match(/(\\d{4}[-./]\\d{1,2}[-./]\\d{1,2})/)?.[1] || '';
|
|
40
|
+
results.push({ rank: results.length + 1, title, description, date, url });
|
|
41
|
+
if (results.length >= ${limit}) break;
|
|
42
|
+
}
|
|
43
|
+
return results;
|
|
44
|
+
})()
|
|
45
|
+
`);
|
|
46
|
+
return Array.isArray(data) ? data : [];
|
|
47
|
+
},
|
|
48
|
+
});
|
|
@@ -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
|
+
});
|