@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.
Files changed (181) hide show
  1. package/README.md +76 -51
  2. package/README.zh-CN.md +78 -62
  3. package/cli-manifest.json +4558 -2979
  4. package/clis/antigravity/serve.js +71 -25
  5. package/clis/baidu-scholar/search.js +87 -0
  6. package/clis/baidu-scholar/search.test.js +23 -0
  7. package/clis/bilibili/video.js +61 -0
  8. package/clis/bilibili/video.test.js +81 -0
  9. package/clis/deepseek/ask.js +94 -0
  10. package/clis/deepseek/ask.test.js +73 -0
  11. package/clis/deepseek/history.js +25 -0
  12. package/clis/deepseek/new.js +20 -0
  13. package/clis/deepseek/read.js +22 -0
  14. package/clis/deepseek/status.js +24 -0
  15. package/clis/deepseek/utils.js +291 -0
  16. package/clis/deepseek/utils.test.js +37 -0
  17. package/clis/eastmoney/_secid.js +78 -0
  18. package/clis/eastmoney/announcement.js +52 -0
  19. package/clis/eastmoney/convertible.js +73 -0
  20. package/clis/eastmoney/etf.js +65 -0
  21. package/clis/eastmoney/holders.js +78 -0
  22. package/clis/eastmoney/index-board.js +96 -0
  23. package/clis/eastmoney/kline.js +87 -0
  24. package/clis/eastmoney/kuaixun.js +54 -0
  25. package/clis/eastmoney/longhu.js +67 -0
  26. package/clis/eastmoney/money-flow.js +78 -0
  27. package/clis/eastmoney/northbound.js +57 -0
  28. package/clis/eastmoney/quote.js +107 -0
  29. package/clis/eastmoney/rank.js +94 -0
  30. package/clis/eastmoney/sectors.js +76 -0
  31. package/clis/google-scholar/search.js +58 -0
  32. package/clis/google-scholar/search.test.js +23 -0
  33. package/clis/gov-law/commands.test.js +39 -0
  34. package/clis/gov-law/recent.js +22 -0
  35. package/clis/gov-law/search.js +41 -0
  36. package/clis/gov-law/shared.js +51 -0
  37. package/clis/gov-policy/commands.test.js +27 -0
  38. package/clis/gov-policy/recent.js +47 -0
  39. package/clis/gov-policy/search.js +48 -0
  40. package/clis/jianyu/search.js +139 -3
  41. package/clis/jianyu/search.test.js +25 -0
  42. package/clis/jianyu/shared/procurement-detail.js +15 -0
  43. package/clis/jianyu/shared/procurement-detail.test.js +12 -0
  44. package/clis/nowcoder/companies.js +23 -0
  45. package/clis/nowcoder/creators.js +27 -0
  46. package/clis/nowcoder/detail.js +61 -0
  47. package/clis/nowcoder/experience.js +36 -0
  48. package/clis/nowcoder/hot.js +24 -0
  49. package/clis/nowcoder/jobs.js +21 -0
  50. package/clis/nowcoder/notifications.js +29 -0
  51. package/clis/nowcoder/papers.js +40 -0
  52. package/clis/nowcoder/practice.js +37 -0
  53. package/clis/nowcoder/recommend.js +30 -0
  54. package/clis/nowcoder/referral.js +39 -0
  55. package/clis/nowcoder/salary.js +40 -0
  56. package/clis/nowcoder/search.js +49 -0
  57. package/clis/nowcoder/suggest.js +33 -0
  58. package/clis/nowcoder/topics.js +27 -0
  59. package/clis/nowcoder/trending.js +25 -0
  60. package/clis/twitter/list-add.js +337 -0
  61. package/clis/twitter/list-add.test.js +15 -0
  62. package/clis/twitter/list-remove.js +297 -0
  63. package/clis/twitter/list-remove.test.js +14 -0
  64. package/clis/twitter/list-tweets.js +185 -0
  65. package/clis/twitter/list-tweets.test.js +108 -0
  66. package/clis/twitter/lists.js +134 -47
  67. package/clis/twitter/lists.test.js +105 -38
  68. package/clis/twitter/shared.js +7 -2
  69. package/clis/twitter/tweets.js +218 -0
  70. package/clis/twitter/tweets.test.js +125 -0
  71. package/clis/wanfang/search.js +66 -0
  72. package/clis/wanfang/search.test.js +23 -0
  73. package/clis/web/read.js +1 -1
  74. package/clis/weixin/download.js +3 -2
  75. package/clis/xiaohongshu/publish.js +149 -28
  76. package/clis/xiaohongshu/publish.test.js +319 -6
  77. package/clis/xiaoyuzhou/download.js +8 -4
  78. package/clis/xiaoyuzhou/download.test.js +23 -13
  79. package/clis/xiaoyuzhou/episode.js +9 -4
  80. package/clis/xiaoyuzhou/podcast-episodes.js +15 -11
  81. package/clis/xiaoyuzhou/podcast.js +9 -4
  82. package/clis/xiaoyuzhou/utils.js +0 -40
  83. package/clis/xiaoyuzhou/utils.test.js +15 -75
  84. package/clis/youtube/channel.js +35 -0
  85. package/clis/zsxq/dynamics.js +1 -1
  86. package/clis/zsxq/utils.js +6 -3
  87. package/clis/zsxq/utils.test.js +31 -0
  88. package/dist/src/browser/base-page.d.ts +14 -4
  89. package/dist/src/browser/base-page.js +35 -25
  90. package/dist/src/browser/bridge.d.ts +1 -0
  91. package/dist/src/browser/bridge.js +1 -1
  92. package/dist/src/browser/cdp.d.ts +1 -0
  93. package/dist/src/browser/cdp.js +13 -4
  94. package/dist/src/browser/compound.d.ts +59 -0
  95. package/dist/src/browser/compound.js +112 -0
  96. package/dist/src/browser/compound.test.js +175 -0
  97. package/dist/src/browser/daemon-client.d.ts +6 -4
  98. package/dist/src/browser/daemon-client.js +6 -1
  99. package/dist/src/browser/daemon-client.test.js +40 -1
  100. package/dist/src/browser/dom-snapshot.d.ts +7 -0
  101. package/dist/src/browser/dom-snapshot.js +83 -5
  102. package/dist/src/browser/dom-snapshot.test.js +65 -0
  103. package/dist/src/browser/extract.d.ts +69 -0
  104. package/dist/src/browser/extract.js +132 -0
  105. package/dist/src/browser/extract.test.js +129 -0
  106. package/dist/src/browser/find.d.ts +76 -0
  107. package/dist/src/browser/find.js +179 -0
  108. package/dist/src/browser/find.test.js +120 -0
  109. package/dist/src/browser/html-tree.d.ts +75 -0
  110. package/dist/src/browser/html-tree.js +112 -0
  111. package/dist/src/browser/html-tree.test.d.ts +1 -0
  112. package/dist/src/browser/html-tree.test.js +181 -0
  113. package/dist/src/browser/network-cache.d.ts +48 -0
  114. package/dist/src/browser/network-cache.js +66 -0
  115. package/dist/src/browser/network-cache.test.d.ts +1 -0
  116. package/dist/src/browser/network-cache.test.js +58 -0
  117. package/dist/src/browser/network-key.d.ts +22 -0
  118. package/dist/src/browser/network-key.js +66 -0
  119. package/dist/src/browser/network-key.test.d.ts +1 -0
  120. package/dist/src/browser/network-key.test.js +49 -0
  121. package/dist/src/browser/page.d.ts +14 -4
  122. package/dist/src/browser/page.js +48 -7
  123. package/dist/src/browser/page.test.js +97 -0
  124. package/dist/src/browser/shape-filter.d.ts +52 -0
  125. package/dist/src/browser/shape-filter.js +101 -0
  126. package/dist/src/browser/shape-filter.test.d.ts +1 -0
  127. package/dist/src/browser/shape-filter.test.js +101 -0
  128. package/dist/src/browser/shape.d.ts +23 -0
  129. package/dist/src/browser/shape.js +95 -0
  130. package/dist/src/browser/shape.test.d.ts +1 -0
  131. package/dist/src/browser/shape.test.js +82 -0
  132. package/dist/src/browser/target-errors.d.ts +14 -1
  133. package/dist/src/browser/target-errors.js +13 -0
  134. package/dist/src/browser/target-errors.test.js +39 -6
  135. package/dist/src/browser/target-resolver.d.ts +57 -10
  136. package/dist/src/browser/target-resolver.js +195 -75
  137. package/dist/src/browser/target-resolver.test.js +80 -5
  138. package/dist/src/cli.js +849 -267
  139. package/dist/src/cli.test.js +961 -90
  140. package/dist/src/commanderAdapter.d.ts +0 -1
  141. package/dist/src/commanderAdapter.js +2 -16
  142. package/dist/src/commanderAdapter.test.js +1 -1
  143. package/dist/src/completion-shared.js +2 -5
  144. package/dist/src/daemon.js +8 -0
  145. package/dist/src/download/article-download.d.ts +1 -0
  146. package/dist/src/download/article-download.js +3 -0
  147. package/dist/src/download/article-download.test.d.ts +1 -0
  148. package/dist/src/download/article-download.test.js +39 -0
  149. package/dist/src/execution.js +7 -2
  150. package/dist/src/execution.test.js +54 -0
  151. package/dist/src/main.js +16 -0
  152. package/dist/src/plugin.d.ts +1 -8
  153. package/dist/src/plugin.js +1 -27
  154. package/dist/src/plugin.test.js +1 -59
  155. package/dist/src/registry.d.ts +1 -0
  156. package/dist/src/registry.js +3 -2
  157. package/dist/src/registry.test.js +22 -0
  158. package/dist/src/types.d.ts +32 -8
  159. package/package.json +1 -1
  160. package/clis/twitter/lists-parser.js +0 -77
  161. package/clis/twitter/lists.d.ts +0 -5
  162. package/dist/src/cascade.d.ts +0 -46
  163. package/dist/src/cascade.js +0 -135
  164. package/dist/src/explore.d.ts +0 -99
  165. package/dist/src/explore.js +0 -402
  166. package/dist/src/generate-verified.d.ts +0 -105
  167. package/dist/src/generate-verified.js +0 -696
  168. package/dist/src/generate-verified.test.js +0 -925
  169. package/dist/src/generate.d.ts +0 -46
  170. package/dist/src/generate.js +0 -117
  171. package/dist/src/record.d.ts +0 -96
  172. package/dist/src/record.js +0 -657
  173. package/dist/src/record.test.js +0 -293
  174. package/dist/src/skill-generate.d.ts +0 -30
  175. package/dist/src/skill-generate.js +0 -75
  176. package/dist/src/skill-generate.test.js +0 -173
  177. package/dist/src/synthesize.d.ts +0 -97
  178. package/dist/src/synthesize.js +0 -208
  179. /package/dist/src/{generate-verified.test.d.ts → browser/compound.test.d.ts} +0 -0
  180. /package/dist/src/{record.test.d.ts → browser/extract.test.d.ts} +0 -0
  181. /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(/&nbsp;/g, ' ').replace(/&lt;/g, '<').replace(/&gt;/g, '>').replace(/&amp;/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(/&amp;/g, '&').replace(/&lt;/g, '<').replace(/&gt;/g, '>').replace(/&nbsp;/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
+ });