@jackwener/opencli 1.7.2 → 1.7.4
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 +18 -15
- package/README.zh-CN.md +31 -15
- package/cli-manifest.json +1265 -101
- package/clis/barchart/flow.js +1 -1
- package/clis/barchart/greeks.js +2 -2
- package/clis/barchart/options.js +2 -2
- package/clis/barchart/quote.js +1 -1
- package/clis/bilibili/favorite.js +18 -13
- package/clis/bilibili/feed.js +202 -48
- package/clis/binance/depth.js +3 -4
- package/clis/boss/utils.js +2 -2
- package/clis/chatgpt/image.js +97 -0
- package/clis/chatgpt/utils.js +297 -0
- package/clis/{chatgpt → chatgpt-app}/ask.js +1 -1
- package/clis/{chatgpt → chatgpt-app}/ax.js +6 -3
- package/clis/{chatgpt → chatgpt-app}/model.js +1 -1
- package/clis/{chatgpt → chatgpt-app}/new.js +1 -1
- package/clis/{chatgpt → chatgpt-app}/read.js +1 -1
- package/clis/{chatgpt → chatgpt-app}/send.js +1 -1
- package/clis/{chatgpt → chatgpt-app}/status.js +1 -1
- package/clis/discord-app/delete.js +114 -0
- package/clis/douban/search.js +1 -0
- package/clis/douban/search.test.js +11 -0
- package/clis/douban/subject.js +20 -93
- package/clis/douban/subject.test.js +11 -0
- package/clis/douban/utils.js +279 -10
- package/clis/douban/utils.test.js +296 -1
- package/clis/doubao/utils.js +319 -130
- package/clis/doubao/utils.test.js +241 -2
- package/clis/eastmoney/hot-rank.js +50 -0
- package/clis/eastmoney/hot-rank.test.js +59 -0
- package/clis/grok/image.test.ts +107 -0
- package/clis/grok/image.ts +356 -0
- package/clis/ke/chengjiao.js +77 -0
- package/clis/ke/ershoufang.js +100 -0
- package/clis/ke/utils.js +104 -0
- package/clis/ke/xiaoqu.js +77 -0
- package/clis/ke/zufang.js +94 -0
- package/clis/maimai/search-talents.js +172 -0
- package/clis/mubu/doc.js +40 -0
- package/clis/mubu/docs.js +43 -0
- package/clis/mubu/notes.js +244 -0
- package/clis/mubu/recent.js +27 -0
- package/clis/mubu/search.js +62 -0
- package/clis/mubu/utils.js +304 -0
- package/clis/reuters/search.js +1 -1
- package/clis/tdx/hot-rank.js +47 -0
- package/clis/tdx/hot-rank.test.js +59 -0
- package/clis/ths/hot-rank.js +49 -0
- package/clis/ths/hot-rank.test.js +64 -0
- package/clis/twitter/bookmarks.js +2 -1
- package/clis/uiverse/_shared.js +368 -0
- package/clis/uiverse/_shared.test.js +55 -0
- package/clis/uiverse/code.js +47 -0
- package/clis/uiverse/preview.js +71 -0
- package/clis/xiaohongshu/comments.js +20 -8
- package/clis/xiaohongshu/comments.test.js +69 -12
- package/clis/xiaohongshu/creator-note-detail.js +2 -0
- package/clis/xiaohongshu/creator-note-detail.test.js +32 -0
- package/clis/xiaohongshu/creator-notes-summary.js +4 -0
- package/clis/xiaohongshu/creator-notes-summary.test.js +39 -1
- package/clis/xiaohongshu/creator-notes.js +1 -0
- package/clis/xiaohongshu/creator-profile.js +1 -0
- package/clis/xiaohongshu/creator-stats.js +1 -0
- package/clis/xiaohongshu/download.js +18 -7
- package/clis/xiaohongshu/download.test.js +42 -0
- package/clis/xiaohongshu/navigation.test.js +34 -0
- package/clis/xiaohongshu/note-helpers.js +46 -12
- package/clis/xiaohongshu/note.js +17 -10
- package/clis/xiaohongshu/note.test.js +66 -11
- package/clis/xiaohongshu/publish.js +1 -0
- package/clis/xiaohongshu/search.js +1 -0
- package/clis/xiaohongshu/user.js +1 -0
- package/clis/xiaoyuzhou/auth.js +303 -0
- package/clis/xiaoyuzhou/auth.test.js +124 -0
- package/clis/xiaoyuzhou/download.js +49 -0
- package/clis/xiaoyuzhou/download.test.js +125 -0
- package/clis/xiaoyuzhou/transcript.js +76 -0
- package/clis/xiaoyuzhou/transcript.test.js +195 -0
- package/clis/yahoo-finance/quote.js +1 -1
- package/clis/youtube/feed.js +120 -0
- package/clis/youtube/history.js +118 -0
- package/clis/youtube/like.js +62 -0
- package/clis/youtube/playlist.js +97 -0
- package/clis/youtube/subscribe.js +71 -0
- package/clis/youtube/subscriptions.js +57 -0
- package/clis/youtube/unlike.js +62 -0
- package/clis/youtube/unsubscribe.js +71 -0
- package/clis/youtube/utils.js +122 -0
- package/clis/youtube/utils.test.js +32 -1
- package/clis/youtube/watch-later.js +76 -0
- package/dist/src/browser/base-page.d.ts +9 -0
- package/dist/src/browser/base-page.js +44 -5
- package/dist/src/browser/bridge.d.ts +2 -0
- package/dist/src/browser/bridge.js +51 -14
- package/dist/src/browser/cdp.js +11 -2
- package/dist/src/browser/daemon-client.d.ts +2 -0
- package/dist/src/browser/dom-snapshot.js +13 -1
- package/dist/src/browser/page.d.ts +4 -1
- package/dist/src/browser/page.js +48 -8
- package/dist/src/browser/page.test.js +61 -1
- package/dist/src/browser/target-errors.d.ts +23 -0
- package/dist/src/browser/target-errors.js +29 -0
- package/dist/src/browser/target-errors.test.d.ts +1 -0
- package/dist/src/browser/target-errors.test.js +61 -0
- package/dist/src/browser/target-resolver.d.ts +57 -0
- package/dist/src/browser/target-resolver.js +298 -0
- package/dist/src/browser/target-resolver.test.d.ts +1 -0
- package/dist/src/browser/target-resolver.test.js +43 -0
- package/dist/src/browser.test.js +38 -1
- package/dist/src/cli.js +45 -35
- package/dist/src/commands/daemon.d.ts +4 -2
- package/dist/src/commands/daemon.js +22 -2
- package/dist/src/commands/daemon.test.js +65 -2
- package/dist/src/daemon.js +7 -0
- package/dist/src/doctor.d.ts +2 -0
- package/dist/src/doctor.js +82 -10
- package/dist/src/doctor.test.js +28 -12
- package/dist/src/electron-apps.js +1 -1
- package/dist/src/errors.d.ts +1 -0
- package/dist/src/errors.js +13 -0
- package/dist/src/execution.js +36 -9
- package/dist/src/execution.test.js +23 -0
- package/dist/src/external-clis.yaml +2 -2
- package/dist/src/logger.d.ts +2 -2
- package/dist/src/logger.js +3 -8
- package/dist/src/output.js +1 -5
- package/dist/src/output.test.js +0 -21
- package/dist/src/pipeline/steps/transform.js +1 -1
- package/dist/src/pipeline/template.d.ts +1 -0
- package/dist/src/pipeline/template.js +11 -3
- package/dist/src/pipeline/template.test.js +3 -0
- package/dist/src/pipeline/transform.test.js +14 -0
- package/dist/src/plugin.d.ts +7 -1
- package/dist/src/plugin.js +23 -1
- package/dist/src/plugin.test.js +15 -1
- package/dist/src/registry.js +3 -4
- package/dist/src/types.d.ts +3 -1
- package/dist/src/update-check.d.ts +14 -0
- package/dist/src/update-check.js +48 -3
- package/dist/src/update-check.test.d.ts +1 -0
- package/dist/src/update-check.test.js +31 -0
- package/package.json +1 -1
- package/scripts/fetch-adapters.js +35 -8
package/clis/barchart/flow.js
CHANGED
|
@@ -26,7 +26,7 @@ cli({
|
|
|
26
26
|
const data = await page.evaluate(`
|
|
27
27
|
(async () => {
|
|
28
28
|
const limit = ${limit};
|
|
29
|
-
const typeFilter =
|
|
29
|
+
const typeFilter = ${JSON.stringify(optionType)}.toLowerCase();
|
|
30
30
|
|
|
31
31
|
// Wait for CSRF token to appear (Angular may inject it after initial render)
|
|
32
32
|
let csrf = '';
|
package/clis/barchart/greeks.js
CHANGED
|
@@ -27,8 +27,8 @@ cli({
|
|
|
27
27
|
await page.wait(4);
|
|
28
28
|
const data = await page.evaluate(`
|
|
29
29
|
(async () => {
|
|
30
|
-
const sym =
|
|
31
|
-
const expDate =
|
|
30
|
+
const sym = ${JSON.stringify(symbol)};
|
|
31
|
+
const expDate = ${JSON.stringify(expiration)};
|
|
32
32
|
const limit = ${limit};
|
|
33
33
|
const csrf = document.querySelector('meta[name="csrf-token"]')?.content || '';
|
|
34
34
|
const headers = { 'X-CSRF-TOKEN': csrf };
|
package/clis/barchart/options.js
CHANGED
|
@@ -26,8 +26,8 @@ cli({
|
|
|
26
26
|
await page.wait(4);
|
|
27
27
|
const data = await page.evaluate(`
|
|
28
28
|
(async () => {
|
|
29
|
-
const sym =
|
|
30
|
-
const type =
|
|
29
|
+
const sym = ${JSON.stringify(symbol)};
|
|
30
|
+
const type = ${JSON.stringify(optType)};
|
|
31
31
|
const limit = ${limit};
|
|
32
32
|
const csrf = document.querySelector('meta[name="csrf-token"]')?.content || '';
|
|
33
33
|
const headers = { 'X-CSRF-TOKEN': csrf };
|
package/clis/barchart/quote.js
CHANGED
|
@@ -24,7 +24,7 @@ cli({
|
|
|
24
24
|
await page.wait(4);
|
|
25
25
|
const data = await page.evaluate(`
|
|
26
26
|
(async () => {
|
|
27
|
-
const sym =
|
|
27
|
+
const sym = ${JSON.stringify(symbol)};
|
|
28
28
|
const csrf = document.querySelector('meta[name="csrf-token"]')?.content || '';
|
|
29
29
|
|
|
30
30
|
// Strategy 1: internal proxy API with CSRF token
|
|
@@ -3,27 +3,32 @@ import { apiGet, payloadData, getSelfUid } from './utils.js';
|
|
|
3
3
|
cli({
|
|
4
4
|
site: 'bilibili',
|
|
5
5
|
name: 'favorite',
|
|
6
|
-
description: '
|
|
6
|
+
description: '我的收藏夹',
|
|
7
7
|
domain: 'www.bilibili.com',
|
|
8
8
|
strategy: Strategy.COOKIE,
|
|
9
9
|
args: [
|
|
10
|
+
{ name: 'fid', type: 'int', required: false, help: 'Favorite folder ID (defaults to first folder)' },
|
|
10
11
|
{ name: 'limit', type: 'int', default: 20, help: 'Number of results' },
|
|
11
12
|
{ name: 'page', type: 'int', default: 1, help: 'Page number' },
|
|
12
13
|
],
|
|
13
14
|
columns: ['rank', 'title', 'author', 'plays', 'url'],
|
|
14
15
|
func: async (page, kwargs) => {
|
|
15
|
-
const { limit = 20, page: pageNum = 1 } = kwargs;
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
16
|
+
const { fid: favoriteId, limit = 20, page: pageNum = 1 } = kwargs;
|
|
17
|
+
let fid;
|
|
18
|
+
if (favoriteId) {
|
|
19
|
+
fid = Number(favoriteId);
|
|
20
|
+
} else {
|
|
21
|
+
// Fall back to the default (first) favorite folder
|
|
22
|
+
const uid = await getSelfUid(page);
|
|
23
|
+
const foldersPayload = await apiGet(page, '/x/v3/fav/folder/created/list-all', {
|
|
24
|
+
params: { up_mid: uid },
|
|
25
|
+
signed: true,
|
|
26
|
+
});
|
|
27
|
+
const folders = payloadData(foldersPayload)?.list ?? [];
|
|
28
|
+
if (!folders.length)
|
|
29
|
+
return [];
|
|
30
|
+
fid = folders[0].id;
|
|
31
|
+
}
|
|
27
32
|
// Fetch favorite items
|
|
28
33
|
const payload = await apiGet(page, '/x/v3/fav/resource/list', {
|
|
29
34
|
params: { media_id: fid, pn: pageNum, ps: Math.min(Number(limit), 40) },
|
package/clis/bilibili/feed.js
CHANGED
|
@@ -1,64 +1,218 @@
|
|
|
1
1
|
import { cli, Strategy } from '@jackwener/opencli/registry';
|
|
2
|
-
import { apiGet, payloadData, stripHtml } from './utils.js';
|
|
2
|
+
import { apiGet, payloadData, resolveUid, stripHtml } from './utils.js';
|
|
3
|
+
|
|
4
|
+
/** Map bilibili dynamic type to readable short name */
|
|
5
|
+
const TYPE_MAP = {
|
|
6
|
+
DYNAMIC_TYPE_AV: 'video',
|
|
7
|
+
DYNAMIC_TYPE_DRAW: 'draw',
|
|
8
|
+
DYNAMIC_TYPE_ARTICLE: 'article',
|
|
9
|
+
DYNAMIC_TYPE_FORWARD: 'forward',
|
|
10
|
+
DYNAMIC_TYPE_WORD: 'text',
|
|
11
|
+
DYNAMIC_TYPE_LIVE_RCMD: 'live',
|
|
12
|
+
DYNAMIC_TYPE_PGC: 'bangumi',
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
function parseItem(item) {
|
|
16
|
+
const modules = item.modules ?? {};
|
|
17
|
+
const authorModule = modules.module_author ?? {};
|
|
18
|
+
const dynamicModule = modules.module_dynamic ?? {};
|
|
19
|
+
const major = dynamicModule.major ?? {};
|
|
20
|
+
const stat = modules.module_stat ?? {};
|
|
21
|
+
|
|
22
|
+
let title = '';
|
|
23
|
+
let url = item.id_str ? `https://t.bilibili.com/${item.id_str}` : '';
|
|
24
|
+
const itemType = TYPE_MAP[item.type] ?? item.type ?? '';
|
|
25
|
+
|
|
26
|
+
// video
|
|
27
|
+
if (major.archive) {
|
|
28
|
+
title = major.archive.title ?? '';
|
|
29
|
+
url = major.archive.jump_url ? `https:${major.archive.jump_url}` : url;
|
|
30
|
+
}
|
|
31
|
+
// article
|
|
32
|
+
if (!title && major.article) {
|
|
33
|
+
title = major.article.title ?? '';
|
|
34
|
+
url = major.article.jump_url ? `https:${major.article.jump_url}` : url;
|
|
35
|
+
}
|
|
36
|
+
// text content in desc
|
|
37
|
+
if (!title && dynamicModule.desc?.text) {
|
|
38
|
+
title = stripHtml(dynamicModule.desc.text).slice(0, 60);
|
|
39
|
+
}
|
|
40
|
+
// draw (图文) — use opus or draw items count as hint
|
|
41
|
+
if (!title && major.draw) {
|
|
42
|
+
const imgCount = major.draw.items?.length ?? 0;
|
|
43
|
+
title = imgCount > 0 ? `[图片x${imgCount}]` : '[图文动态]';
|
|
44
|
+
}
|
|
45
|
+
// VIP only content
|
|
46
|
+
if (!title && item.basic?.is_only_fans) {
|
|
47
|
+
title = '[充电专属]';
|
|
48
|
+
}
|
|
49
|
+
// forward
|
|
50
|
+
if (!title && item.type === 'DYNAMIC_TYPE_FORWARD') {
|
|
51
|
+
title = '[转发动态]';
|
|
52
|
+
}
|
|
53
|
+
// final fallback
|
|
54
|
+
if (!title) {
|
|
55
|
+
title = `[${itemType || '动态'}]`;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const time = authorModule.pub_time ?? '';
|
|
59
|
+
const likes = stat.like?.count ?? 0;
|
|
60
|
+
const comments = stat.comment?.count ?? 0;
|
|
61
|
+
|
|
62
|
+
return { title, url, itemType, author: authorModule.name ?? '', time, likes, comments };
|
|
63
|
+
}
|
|
64
|
+
|
|
3
65
|
cli({
|
|
4
66
|
site: 'bilibili',
|
|
5
67
|
name: 'feed',
|
|
6
|
-
description: '
|
|
68
|
+
description: '动态时间线(不传 uid 查关注时间线,传 uid 查指定用户动态)',
|
|
7
69
|
domain: 'www.bilibili.com',
|
|
8
70
|
strategy: Strategy.COOKIE,
|
|
9
71
|
args: [
|
|
10
|
-
{ name: '
|
|
11
|
-
{ name: '
|
|
72
|
+
{ name: 'uid', positional: true, required: false, help: '用户 UID 或用户名(不传则显示关注时间线)' },
|
|
73
|
+
{ name: 'limit', type: 'int', default: 20, help: 'Max results to return' },
|
|
74
|
+
{ name: 'type', default: 'all', help: 'Filter: all, video, article, draw, text' },
|
|
75
|
+
{ name: 'pages', type: 'int', default: 1, help: 'Number of pages to fetch (each ~20 items)' },
|
|
12
76
|
],
|
|
13
|
-
columns: ['rank', 'author', 'title', 'type', 'url'],
|
|
77
|
+
columns: ['rank', 'time', 'author', 'title', 'type', 'likes', 'url'],
|
|
14
78
|
func: async (page, kwargs) => {
|
|
15
|
-
const
|
|
16
|
-
const
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
page: 1,
|
|
23
|
-
...(updateBaseline ? { update_baseline: updateBaseline } : {}),
|
|
24
|
-
},
|
|
25
|
-
});
|
|
26
|
-
const items = payloadData(payload)?.items ?? [];
|
|
79
|
+
const maxResults = Number(kwargs.limit) || 20;
|
|
80
|
+
const maxPages = Number(kwargs.pages) || 1;
|
|
81
|
+
const filterType = kwargs.type === 'all' ? '' : (kwargs.type ?? '');
|
|
82
|
+
|
|
83
|
+
const isUserFeed = !!kwargs.uid;
|
|
84
|
+
const uid = isUserFeed ? await resolveUid(page, String(kwargs.uid)) : null;
|
|
85
|
+
|
|
27
86
|
const rows = [];
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
87
|
+
let offset = '';
|
|
88
|
+
|
|
89
|
+
for (let p = 0; p < maxPages; p++) {
|
|
90
|
+
if (rows.length >= maxResults) break;
|
|
91
|
+
|
|
92
|
+
let payload;
|
|
93
|
+
if (isUserFeed) {
|
|
94
|
+
const params = { host_mid: uid, timezone_offset: -480 };
|
|
95
|
+
if (offset) params.offset = offset;
|
|
96
|
+
payload = await apiGet(page, '/x/polymer/web-dynamic/v1/feed/space', { params });
|
|
97
|
+
} else {
|
|
98
|
+
const params = {
|
|
99
|
+
timezone_offset: -480,
|
|
100
|
+
type: filterType || 'all',
|
|
101
|
+
page: p + 1,
|
|
102
|
+
};
|
|
103
|
+
if (offset) params.offset = offset;
|
|
104
|
+
payload = await apiGet(page, '/x/polymer/web-dynamic/v1/feed/all', { params });
|
|
46
105
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
106
|
+
|
|
107
|
+
const data = payloadData(payload) ?? {};
|
|
108
|
+
const items = data.items ?? [];
|
|
109
|
+
if (items.length === 0) break;
|
|
110
|
+
|
|
111
|
+
for (const item of items) {
|
|
112
|
+
if (rows.length >= maxResults) break;
|
|
113
|
+
const parsed = parseItem(item);
|
|
114
|
+
if (filterType && parsed.itemType !== filterType) continue;
|
|
115
|
+
rows.push({
|
|
116
|
+
rank: rows.length + 1,
|
|
117
|
+
time: parsed.time,
|
|
118
|
+
author: parsed.author,
|
|
119
|
+
title: parsed.title,
|
|
120
|
+
type: parsed.itemType,
|
|
121
|
+
likes: parsed.likes,
|
|
122
|
+
url: parsed.url,
|
|
123
|
+
});
|
|
51
124
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
125
|
+
|
|
126
|
+
offset = data.offset ?? items[items.length - 1]?.id_str ?? '';
|
|
127
|
+
if (!offset || !data.has_more) break;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return rows;
|
|
131
|
+
},
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
cli({
|
|
135
|
+
site: 'bilibili',
|
|
136
|
+
name: 'feed-detail',
|
|
137
|
+
description: '查看 Bilibili 动态详情(支持充电专属内容)',
|
|
138
|
+
domain: 'www.bilibili.com',
|
|
139
|
+
strategy: Strategy.COOKIE,
|
|
140
|
+
args: [
|
|
141
|
+
{ name: 'id', positional: true, required: true, help: '动态 ID(从 feed 命令的 url 中获取)' },
|
|
142
|
+
],
|
|
143
|
+
columns: ['field', 'value'],
|
|
144
|
+
func: async (page, kwargs) => {
|
|
145
|
+
const id = String(kwargs.id);
|
|
146
|
+
const payload = await apiGet(page, '/x/polymer/web-dynamic/v1/detail', {
|
|
147
|
+
params: { id, timezone_offset: -480 },
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
const rows = [];
|
|
151
|
+
const data = payloadData(payload);
|
|
152
|
+
const item = data?.item;
|
|
153
|
+
if (!item) {
|
|
154
|
+
rows.push({ field: 'error', value: '动态不存在或无权查看'});
|
|
155
|
+
return rows;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const modules = item.modules ?? {};
|
|
159
|
+
const author = modules.module_author ?? {};
|
|
160
|
+
const dynamicModule = modules.module_dynamic ?? {};
|
|
161
|
+
const major = dynamicModule.major ?? {};
|
|
162
|
+
const stat = modules.module_stat ?? {};
|
|
163
|
+
|
|
164
|
+
rows.push({ field: 'id', value: item.id_str ?? id });
|
|
165
|
+
rows.push({ field: 'author', value: author.name ?? '' });
|
|
166
|
+
rows.push({ field: 'time', value: author.pub_time ?? '' });
|
|
167
|
+
rows.push({ field: 'type', value: TYPE_MAP[item.type] ?? item.type ?? '' });
|
|
168
|
+
|
|
169
|
+
// text content
|
|
170
|
+
if (dynamicModule.desc?.text) {
|
|
171
|
+
rows.push({ field: 'text', value: stripHtml(dynamicModule.desc.text) });
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// video
|
|
175
|
+
if (major.archive) {
|
|
176
|
+
rows.push({ field: 'video_title', value: major.archive.title ?? '' });
|
|
177
|
+
rows.push({ field: 'video_desc', value: major.archive.desc ?? '' });
|
|
178
|
+
rows.push({ field: 'video_url', value: major.archive.jump_url ? `https:${major.archive.jump_url}` : '' });
|
|
179
|
+
rows.push({ field: 'play', value: String(major.archive.stat?.play ?? '') });
|
|
180
|
+
rows.push({ field: 'danmaku', value: String(major.archive.stat?.danmaku ?? '') });
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// article
|
|
184
|
+
if (major.article) {
|
|
185
|
+
rows.push({ field: 'article_title', value: major.article.title ?? '' });
|
|
186
|
+
rows.push({ field: 'article_url', value: major.article.jump_url ? `https:${major.article.jump_url}` : '' });
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// draw (images)
|
|
190
|
+
if (major.draw?.items?.length) {
|
|
191
|
+
rows.push({ field: 'images', value: major.draw.items.map((img) => img.src).join('\n') });
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// opus (rich text, some dynamics use this)
|
|
195
|
+
if (major.opus?.summary?.text) {
|
|
196
|
+
rows.push({ field: 'opus_text', value: stripHtml(major.opus.summary.text) });
|
|
197
|
+
}
|
|
198
|
+
if (major.opus?.title) {
|
|
199
|
+
rows.push({ field: 'opus_title', value: major.opus.title });
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// forward - show original dynamic info
|
|
203
|
+
if (item.orig) {
|
|
204
|
+
const origAuthor = item.orig.modules?.module_author?.name ?? '';
|
|
205
|
+
const origDesc = item.orig.modules?.module_dynamic?.desc?.text ?? '';
|
|
206
|
+
rows.push({ field: 'forward_from', value: origAuthor });
|
|
207
|
+
if (origDesc) rows.push({ field: 'forward_text', value: stripHtml(origDesc).slice(0, 200) });
|
|
61
208
|
}
|
|
209
|
+
|
|
210
|
+
// stats
|
|
211
|
+
rows.push({ field: 'likes', value: String(stat.like?.count ?? 0) });
|
|
212
|
+
rows.push({ field: 'comments', value: String(stat.comment?.count ?? 0) });
|
|
213
|
+
rows.push({ field: 'forwards', value: String(stat.forward?.count ?? 0) });
|
|
214
|
+
rows.push({ field: 'url', value: `https://t.bilibili.com/${item.id_str ?? id}` });
|
|
215
|
+
|
|
62
216
|
return rows;
|
|
63
217
|
},
|
|
64
218
|
});
|
package/clis/binance/depth.js
CHANGED
|
@@ -3,7 +3,7 @@ import { cli, Strategy } from '@jackwener/opencli/registry';
|
|
|
3
3
|
cli({
|
|
4
4
|
site: 'binance',
|
|
5
5
|
name: 'depth',
|
|
6
|
-
description: 'Order book bid prices for a trading pair',
|
|
6
|
+
description: 'Order book bid and ask prices for a trading pair',
|
|
7
7
|
domain: 'data-api.binance.vision',
|
|
8
8
|
strategy: Strategy.PUBLIC,
|
|
9
9
|
browser: false,
|
|
@@ -11,11 +11,10 @@ cli({
|
|
|
11
11
|
{ name: 'symbol', type: 'str', required: true, positional: true, help: 'Trading pair symbol (e.g. BTCUSDT, ETHUSDT)' },
|
|
12
12
|
{ name: 'limit', type: 'int', default: 10, help: 'Number of price levels (5, 10, 20, 50, 100)' },
|
|
13
13
|
],
|
|
14
|
-
columns: ['rank', 'bid_price', 'bid_qty'],
|
|
14
|
+
columns: ['rank', 'bid_price', 'bid_qty', 'ask_price', 'ask_qty'],
|
|
15
15
|
pipeline: [
|
|
16
16
|
{ fetch: { url: 'https://data-api.binance.vision/api/v3/depth?symbol=${{ args.symbol }}&limit=${{ args.limit }}' } },
|
|
17
|
-
{ select: 'bids' },
|
|
18
|
-
{ map: { rank: '${{ index + 1 }}', bid_price: '${{ item.0 }}', bid_qty: '${{ item.1 }}' } },
|
|
17
|
+
{ map: { select: 'bids', rank: '${{ index + 1 }}', bid_price: '${{ item[0] }}', bid_qty: '${{ item[1] }}', ask_price: '${{ root.asks[index]?.[0] ?? "" }}', ask_qty: '${{ root.asks[index]?.[1] ?? "" }}' } },
|
|
19
18
|
{ limit: '${{ args.limit }}' },
|
|
20
19
|
],
|
|
21
20
|
});
|
package/clis/boss/utils.js
CHANGED
|
@@ -214,10 +214,10 @@ export async function typeAndSendMessage(page, text) {
|
|
|
214
214
|
return true;
|
|
215
215
|
}
|
|
216
216
|
/**
|
|
217
|
-
* Verbose log helper — prints when OPENCLI_VERBOSE
|
|
217
|
+
* Verbose log helper — prints when OPENCLI_VERBOSE is set.
|
|
218
218
|
*/
|
|
219
219
|
export function verbose(msg) {
|
|
220
|
-
if (process.env.OPENCLI_VERBOSE
|
|
220
|
+
if (process.env.OPENCLI_VERBOSE) {
|
|
221
221
|
console.error(`[opencli:boss] ${msg}`);
|
|
222
222
|
}
|
|
223
223
|
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import * as os from 'node:os';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import { cli, Strategy } from '@jackwener/opencli/registry';
|
|
4
|
+
import { saveBase64ToFile } from '@jackwener/opencli/utils';
|
|
5
|
+
import { getChatGPTVisibleImageUrls, sendChatGPTMessage, waitForChatGPTImages, getChatGPTImageAssets } from './utils.js';
|
|
6
|
+
|
|
7
|
+
const CHATGPT_DOMAIN = 'chatgpt.com';
|
|
8
|
+
|
|
9
|
+
function extFromMime(mime) {
|
|
10
|
+
if (mime.includes('png')) return '.png';
|
|
11
|
+
if (mime.includes('webp')) return '.webp';
|
|
12
|
+
if (mime.includes('gif')) return '.gif';
|
|
13
|
+
return '.jpg';
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function normalizeBooleanFlag(value) {
|
|
17
|
+
if (typeof value === 'boolean') return value;
|
|
18
|
+
const normalized = String(value ?? '').trim().toLowerCase();
|
|
19
|
+
return normalized === 'true' || normalized === '1' || normalized === 'yes' || normalized === 'on';
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function displayPath(filePath) {
|
|
23
|
+
const home = os.homedir();
|
|
24
|
+
return filePath.startsWith(home) ? `~${filePath.slice(home.length)}` : filePath;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async function currentChatGPTLink(page) {
|
|
28
|
+
const url = await page.evaluate('window.location.href').catch(() => '');
|
|
29
|
+
return typeof url === 'string' && url ? url : 'https://chatgpt.com';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const imageCommand = cli({
|
|
33
|
+
site: 'chatgpt',
|
|
34
|
+
name: 'image',
|
|
35
|
+
description: 'Generate images with ChatGPT web and save them locally',
|
|
36
|
+
domain: CHATGPT_DOMAIN,
|
|
37
|
+
strategy: Strategy.COOKIE,
|
|
38
|
+
browser: true,
|
|
39
|
+
navigateBefore: false,
|
|
40
|
+
defaultFormat: 'plain',
|
|
41
|
+
timeoutSeconds: 240,
|
|
42
|
+
args: [
|
|
43
|
+
{ name: 'prompt', positional: true, required: true, help: 'Image prompt to send to ChatGPT' },
|
|
44
|
+
{ name: 'op', default: path.join(os.homedir(), 'Pictures', 'chatgpt'), help: 'Output directory' },
|
|
45
|
+
{ name: 'sd', type: 'boolean', default: false, help: 'Skip download shorthand; only show ChatGPT link' },
|
|
46
|
+
],
|
|
47
|
+
columns: ['status', 'file', 'link'],
|
|
48
|
+
func: async (page, kwargs) => {
|
|
49
|
+
const prompt = kwargs.prompt;
|
|
50
|
+
const outputDir = kwargs.op || path.join(os.homedir(), 'Pictures', 'chatgpt');
|
|
51
|
+
const skipDownloadRaw = kwargs.sd;
|
|
52
|
+
const skipDownload = skipDownloadRaw === '' || skipDownloadRaw === true || normalizeBooleanFlag(skipDownloadRaw);
|
|
53
|
+
const timeout = 120;
|
|
54
|
+
|
|
55
|
+
// Navigate to chatgpt.com/new with full reload to clear React sidebar state
|
|
56
|
+
await page.goto(`https://${CHATGPT_DOMAIN}/new`, { settleMs: 2000 });
|
|
57
|
+
|
|
58
|
+
const beforeUrls = await getChatGPTVisibleImageUrls(page);
|
|
59
|
+
|
|
60
|
+
// Send the image generation prompt - must be explicit
|
|
61
|
+
const sent = await sendChatGPTMessage(page, `Generate an image of: ${prompt}`);
|
|
62
|
+
if (!sent) {
|
|
63
|
+
return [{ status: '⚠️ send-failed', file: '📁 -', link: `🔗 ${await currentChatGPTLink(page)}` }];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Wait for response and images
|
|
67
|
+
const urls = await waitForChatGPTImages(page, beforeUrls, timeout);
|
|
68
|
+
const link = await currentChatGPTLink(page);
|
|
69
|
+
|
|
70
|
+
if (!urls.length) {
|
|
71
|
+
return [{ status: '⚠️ no-images', file: '📁 -', link: `🔗 ${link}` }];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (skipDownload) {
|
|
75
|
+
return [{ status: '🎨 generated', file: '📁 -', link: `🔗 ${link}` }];
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Export and save images
|
|
79
|
+
const assets = await getChatGPTImageAssets(page, urls);
|
|
80
|
+
if (!assets.length) {
|
|
81
|
+
return [{ status: '⚠️ export-failed', file: '📁 -', link: `🔗 ${link}` }];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const stamp = Date.now();
|
|
85
|
+
const results = [];
|
|
86
|
+
for (let index = 0; index < assets.length; index += 1) {
|
|
87
|
+
const asset = assets[index];
|
|
88
|
+
const base64 = asset.dataUrl.replace(/^data:[^;]+;base64,/, '');
|
|
89
|
+
const suffix = assets.length > 1 ? `_${index + 1}` : '';
|
|
90
|
+
const ext = extFromMime(asset.mimeType);
|
|
91
|
+
const filePath = path.join(outputDir, `chatgpt_${stamp}${suffix}${ext}`);
|
|
92
|
+
await saveBase64ToFile(base64, filePath);
|
|
93
|
+
results.push({ status: '✅ saved', file: `📁 ${displayPath(filePath)}`, link: `🔗 ${link}` });
|
|
94
|
+
}
|
|
95
|
+
return results;
|
|
96
|
+
},
|
|
97
|
+
});
|