@jackwener/opencli 1.8.0 → 1.8.1
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 +8 -49
- package/README.zh-CN.md +8 -52
- package/cli-manifest.json +1796 -191
- package/clis/_atlassian/shared.js +577 -0
- package/clis/_atlassian/shared.test.js +170 -0
- package/clis/bilibili/comment.js +125 -0
- package/clis/bilibili/comment.test.js +153 -0
- package/clis/bilibili/comments.js +116 -21
- package/clis/bilibili/comments.test.js +77 -18
- package/clis/bilibili/subtitle.js +76 -31
- package/clis/bilibili/subtitle.test.js +156 -9
- package/clis/bilibili/utils.js +63 -5
- package/clis/bilibili/utils.test.js +45 -1
- package/clis/chess/analyze.js +35 -0
- package/clis/chess/analyze.test.js +79 -0
- package/clis/chess/game.js +114 -0
- package/clis/chess/game.test.js +178 -0
- package/clis/chess/games.js +67 -0
- package/clis/chess/games.test.js +164 -0
- package/clis/chess/stats.js +32 -0
- package/clis/chess/stats.test.js +79 -0
- package/clis/chess/utils.js +170 -0
- package/clis/chess/utils.test.js +230 -0
- package/clis/confluence/commands.test.js +195 -0
- package/clis/confluence/create.js +39 -0
- package/clis/confluence/page.js +23 -0
- package/clis/confluence/search.js +34 -0
- package/clis/confluence/shared.js +173 -0
- package/clis/confluence/update.js +38 -0
- package/clis/douyin/hashtag.js +84 -23
- package/clis/douyin/hashtag.test.js +113 -0
- package/clis/geogebra/add-circle.js +46 -0
- package/clis/geogebra/add-line.js +35 -0
- package/clis/geogebra/add-point.js +27 -0
- package/clis/geogebra/add-polygon.js +25 -0
- package/clis/geogebra/eval.js +35 -0
- package/clis/geogebra/geogebra.test.js +175 -0
- package/clis/geogebra/hexagon.js +62 -0
- package/clis/geogebra/info.js +72 -0
- package/clis/geogebra/list.js +35 -0
- package/clis/geogebra/triangle.js +60 -0
- package/clis/geogebra/utils.js +271 -0
- package/clis/jira/attachments.js +28 -0
- package/clis/jira/commands.test.js +287 -0
- package/clis/jira/comments.js +28 -0
- package/clis/jira/issue.js +28 -0
- package/clis/jira/links.js +28 -0
- package/clis/jira/search.js +47 -0
- package/clis/jira/shared.js +256 -0
- package/clis/linkedin/job-detail.js +167 -0
- package/clis/linkedin/job-detail.test.js +38 -0
- package/clis/linkedin/jobs-preferences.js +113 -0
- package/clis/linkedin/jobs-preferences.test.js +43 -0
- package/clis/linkedin/post-analytics.js +74 -0
- package/clis/linkedin/post-analytics.test.js +40 -0
- package/clis/linkedin/posts-core.js +241 -0
- package/clis/linkedin/posts.js +22 -0
- package/clis/linkedin/posts.test.js +40 -0
- package/clis/linkedin/profile-analytics.js +104 -0
- package/clis/linkedin/profile-analytics.test.js +67 -0
- package/clis/linkedin/profile-experience.js +671 -0
- package/clis/linkedin/profile-experience.test.js +152 -0
- package/clis/linkedin/profile-projects.js +311 -0
- package/clis/linkedin/profile-projects.test.js +111 -0
- package/clis/linkedin/profile-read.js +148 -0
- package/clis/linkedin/profile-read.test.js +77 -0
- package/clis/linkedin/services-read.js +213 -0
- package/clis/linkedin/services-read.test.js +105 -0
- package/clis/linkedin/shared.js +124 -0
- package/clis/linkedin/timeline.js +14 -7
- package/clis/notebooklm/add-source.js +269 -0
- package/clis/notebooklm/add-source.test.js +97 -0
- package/clis/notebooklm/create.js +76 -0
- package/clis/notebooklm/create.test.js +58 -0
- package/clis/notebooklm/generate-audio.js +91 -0
- package/clis/notebooklm/generate-audio.test.js +63 -0
- package/clis/notebooklm/generate-slides.js +106 -0
- package/clis/notebooklm/generate-slides.test.js +75 -0
- package/clis/notebooklm/open.test.js +10 -10
- package/clis/notebooklm/rpc.js +20 -6
- package/clis/notebooklm/rpc.test.js +27 -1
- package/clis/notebooklm/utils.js +100 -24
- package/clis/notebooklm/utils.test.js +60 -1
- package/clis/notebooklm/write-note.js +103 -0
- package/clis/notebooklm/write-note.test.js +70 -0
- package/clis/pixiv/detail.js +41 -34
- package/clis/pixiv/detail.test.js +93 -0
- package/clis/pixiv/user.js +36 -31
- package/clis/pixiv/user.test.js +100 -0
- package/clis/pixiv/utils.js +56 -7
- package/clis/suno/generate.js +5 -0
- package/clis/suno/generate.test.js +9 -0
- package/clis/suno/status.js +3 -2
- package/clis/suno/utils.js +33 -24
- package/clis/suno/utils.test.js +106 -0
- package/clis/twitter/followers.js +6 -2
- package/clis/twitter/followers.test.js +19 -1
- package/clis/twitter/following.js +14 -5
- package/clis/twitter/following.test.js +29 -0
- package/clis/twitter/likes.js +12 -4
- package/clis/twitter/likes.test.js +26 -1
- package/clis/twitter/list-add.js +1 -1
- package/clis/twitter/list-remove.js +1 -1
- package/clis/twitter/notifications.js +4 -4
- package/clis/twitter/post.js +62 -4
- package/clis/twitter/post.test.js +35 -3
- package/clis/twitter/profile.js +81 -28
- package/clis/twitter/profile.test.js +113 -2
- package/clis/twitter/quote.js +9 -4
- package/clis/twitter/reply.js +13 -10
- package/clis/twitter/reply.test.js +41 -0
- package/clis/twitter/search.js +1 -1
- package/clis/twitter/search.test.js +35 -0
- package/clis/twitter/shared.js +11 -0
- package/clis/twitter/shared.test.js +37 -1
- package/clis/twitter/utils.js +53 -16
- package/clis/upwork/detail.js +132 -0
- package/clis/upwork/feed.js +109 -0
- package/clis/upwork/search.js +115 -0
- package/clis/upwork/upwork.test.js +566 -0
- package/clis/upwork/utils.js +323 -0
- package/clis/weread/book-search.js +438 -0
- package/clis/weread/book-search.test.js +242 -0
- package/clis/weread/search-regression.test.js +80 -0
- package/clis/weread/search.js +17 -2
- package/clis/xiaohongshu/creator-note-detail.js +165 -28
- package/clis/xiaohongshu/creator-note-detail.test.js +186 -37
- package/clis/xiaohongshu/creator-notes.js +251 -2
- package/clis/xiaohongshu/creator-notes.test.js +79 -2
- package/clis/xiaohongshu/download.js +97 -39
- package/clis/xiaohongshu/download.test.js +201 -0
- package/clis/zhihu/answer-comments.js +2 -21
- package/clis/zhihu/answer-detail.js +2 -31
- package/clis/zhihu/collection.js +2 -14
- package/clis/zhihu/collection.test.js +4 -3
- package/clis/zhihu/question.js +1 -9
- package/clis/zhihu/question.test.js +2 -2
- package/clis/zhihu/search.js +1 -12
- package/clis/zhihu/search.test.js +2 -2
- package/clis/zhihu/text.js +29 -0
- package/clis/zhihu/text.test.js +24 -0
- package/dist/src/browser/network-cache.js +13 -1
- package/dist/src/browser/network-cache.test.js +17 -0
- package/dist/src/download/index.js +13 -1
- package/dist/src/download/index.test.js +23 -1
- package/dist/src/download/media-download.test.js +3 -1
- package/dist/src/download/progress.js +2 -2
- package/dist/src/download/progress.test.js +12 -1
- package/dist/src/output.js +11 -1
- package/dist/src/output.test.js +6 -0
- package/dist/src/registry.js +1 -0
- package/dist/src/registry.test.js +11 -0
- package/package.json +1 -1
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Upwork job search.
|
|
3
|
+
*
|
|
4
|
+
* Drives the public `/nx/search/jobs/?q=` page through a real browser
|
|
5
|
+
* session. The full search payload is embedded in `window.__NUXT__.state.
|
|
6
|
+
* jobsSearch.{jobs, paging}` after Nuxt SSR — we read straight from that
|
|
7
|
+
* global rather than DOM-scraping cards, because Upwork's card classes
|
|
8
|
+
* change frequently while the state shape has been stable.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { cli, Strategy } from '@jackwener/opencli/registry';
|
|
12
|
+
import {
|
|
13
|
+
CommandExecutionError,
|
|
14
|
+
EmptyResultError,
|
|
15
|
+
AuthRequiredError,
|
|
16
|
+
} from '@jackwener/opencli/errors';
|
|
17
|
+
import {
|
|
18
|
+
buildSearchUrl,
|
|
19
|
+
isPlainObject,
|
|
20
|
+
jobsToListRows,
|
|
21
|
+
LIST_COLUMNS,
|
|
22
|
+
requireBoundedInt,
|
|
23
|
+
requirePositiveInt,
|
|
24
|
+
requireQuery,
|
|
25
|
+
requireSort,
|
|
26
|
+
unwrapBrowserResult,
|
|
27
|
+
} from './utils.js';
|
|
28
|
+
|
|
29
|
+
cli({
|
|
30
|
+
site: 'upwork',
|
|
31
|
+
name: 'search',
|
|
32
|
+
access: 'read',
|
|
33
|
+
description: 'Upwork keyword job search (logged-in browser session, US site)',
|
|
34
|
+
domain: 'www.upwork.com',
|
|
35
|
+
strategy: Strategy.COOKIE,
|
|
36
|
+
browser: true,
|
|
37
|
+
navigateBefore: false,
|
|
38
|
+
args: [
|
|
39
|
+
{ name: 'query', positional: true, required: true, help: 'Job keyword (skill / title / company)' },
|
|
40
|
+
{ name: 'location', type: 'string', default: '', help: 'Country/city filter (e.g. "United States", "Remote")' },
|
|
41
|
+
{ name: 'category', type: 'string', default: '', help: 'Category uid filter (advanced; from job detail `category` slug)' },
|
|
42
|
+
{ name: 'sort', type: 'string', default: 'recency', help: 'Sort: recency | relevance | client_total_charge | client_total_reviews' },
|
|
43
|
+
{ name: 'page', type: 'int', default: 1, help: 'Page number (1-based)' },
|
|
44
|
+
{ name: 'per_page', type: 'int', default: 10, help: 'Rows per page (10-50, capped at one page)' },
|
|
45
|
+
],
|
|
46
|
+
columns: LIST_COLUMNS,
|
|
47
|
+
func: async (page, kwargs) => {
|
|
48
|
+
const query = requireQuery(kwargs.query);
|
|
49
|
+
const location = String(kwargs.location ?? '').trim();
|
|
50
|
+
const category = String(kwargs.category ?? '').trim();
|
|
51
|
+
const sort = requireSort(kwargs.sort);
|
|
52
|
+
const pageNum = requirePositiveInt(kwargs.page, 1, 'page');
|
|
53
|
+
const perPage = requireBoundedInt(kwargs.per_page, 10, 10, 50, 'per_page');
|
|
54
|
+
|
|
55
|
+
const url = buildSearchUrl({ query, location, category, sort, page: pageNum, perPage });
|
|
56
|
+
await page.goto(url);
|
|
57
|
+
await page.wait(4);
|
|
58
|
+
|
|
59
|
+
let payload;
|
|
60
|
+
try {
|
|
61
|
+
payload = unwrapBrowserResult(await page.evaluate(`(async () => {
|
|
62
|
+
const haveState = () => !!(window.__NUXT__ && window.__NUXT__.state && window.__NUXT__.state.jobsSearch);
|
|
63
|
+
let ready = haveState();
|
|
64
|
+
for (let i = 0; i < 30; i++) {
|
|
65
|
+
if (ready) break;
|
|
66
|
+
await new Promise(r => setTimeout(r, 500));
|
|
67
|
+
ready = haveState();
|
|
68
|
+
}
|
|
69
|
+
const onLogin = /\\/(ab\\/account-security\\/login|nx\\/login)/.test(location.pathname);
|
|
70
|
+
const challenge = (document.title || '').toLowerCase().includes('just a moment') || !!document.querySelector('[id^="cf-"]');
|
|
71
|
+
const state = window.__NUXT__ && window.__NUXT__.state && window.__NUXT__.state.jobsSearch;
|
|
72
|
+
return {
|
|
73
|
+
ready,
|
|
74
|
+
onLogin,
|
|
75
|
+
challenge,
|
|
76
|
+
jobsPresent: !!(state && Object.prototype.hasOwnProperty.call(state, 'jobs')),
|
|
77
|
+
jobs: state ? state.jobs : undefined,
|
|
78
|
+
paging: state && state.paging ? state.paging : null,
|
|
79
|
+
status: state ? state.status : null,
|
|
80
|
+
};
|
|
81
|
+
})()`));
|
|
82
|
+
}
|
|
83
|
+
catch (e) {
|
|
84
|
+
throw new CommandExecutionError(`Failed to read Upwork search state: ${e?.message ?? e}`, 'The Nuxt state global was not reachable; try again after opening Upwork in the connected browser.');
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (payload?.onLogin) {
|
|
88
|
+
throw new AuthRequiredError('upwork.com', 'Upwork redirected to login. Open https://www.upwork.com in the connected browser and sign in, then retry.');
|
|
89
|
+
}
|
|
90
|
+
if (payload?.challenge) {
|
|
91
|
+
throw new CommandExecutionError('Upwork served a Cloudflare challenge page', 'Open https://www.upwork.com in the connected browser and clear the challenge, then retry.');
|
|
92
|
+
}
|
|
93
|
+
if (!payload?.ready) {
|
|
94
|
+
throw new CommandExecutionError('Upwork search state (window.__NUXT__.state.jobsSearch) was not present within 15s', 'The page may not have finished hydrating, or the SSR state shape may have changed.');
|
|
95
|
+
}
|
|
96
|
+
if (!isPlainObject(payload)) {
|
|
97
|
+
throw new CommandExecutionError('Upwork search returned an unexpected Browser Bridge payload shape');
|
|
98
|
+
}
|
|
99
|
+
if (!payload.jobsPresent || !Array.isArray(payload.jobs)) {
|
|
100
|
+
throw new CommandExecutionError('Upwork search state had an unexpected jobs shape; expected window.__NUXT__.state.jobsSearch.jobs to be an array.');
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const jobs = payload.jobs;
|
|
104
|
+
if (jobs.length === 0) {
|
|
105
|
+
throw new EmptyResultError('upwork search', `No Upwork jobs matched "${query}"${location ? ` in ${location}` : ''}`);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const offset = (pageNum - 1) * perPage;
|
|
109
|
+
const rows = jobsToListRows(jobs, { offset, limit: perPage });
|
|
110
|
+
if (rows.length === 0) {
|
|
111
|
+
throw new CommandExecutionError('Upwork search results did not include any job with a valid ciphertext id; cannot produce round-trippable detail rows.');
|
|
112
|
+
}
|
|
113
|
+
return rows;
|
|
114
|
+
},
|
|
115
|
+
});
|