@jackwener/opencli 0.9.6 → 1.0.0
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/.github/ISSUE_TEMPLATE/bug_report.yml +83 -0
- package/.github/ISSUE_TEMPLATE/config.yml +8 -0
- package/.github/ISSUE_TEMPLATE/feature_request.yml +42 -0
- package/.github/ISSUE_TEMPLATE/new_site_adapter.yml +57 -0
- package/.github/dependabot.yml +27 -0
- package/.github/pull_request_template.md +24 -0
- package/.github/workflows/ci.yml +14 -8
- package/.github/workflows/e2e-headed.yml +6 -2
- package/.github/workflows/pkg-pr-new.yml +2 -2
- package/.github/workflows/release-please.yml +25 -0
- package/.github/workflows/release.yml +2 -2
- package/.github/workflows/security.yml +36 -0
- package/CDP.md +1 -1
- package/CDP.zh-CN.md +1 -1
- package/CLI-ELECTRON.md +89 -36
- package/CLI-EXPLORER.md +4 -4
- package/CONTRIBUTING.md +167 -0
- package/README.md +113 -89
- package/README.zh-CN.md +114 -91
- package/SKILL.md +10 -8
- package/TESTING.md +7 -7
- package/dist/browser/daemon-client.d.ts +37 -0
- package/dist/browser/daemon-client.js +82 -0
- package/dist/browser/discover.d.ts +11 -34
- package/dist/browser/discover.js +15 -190
- package/dist/browser/errors.d.ts +6 -20
- package/dist/browser/errors.js +24 -63
- package/dist/browser/index.d.ts +2 -11
- package/dist/browser/index.js +5 -11
- package/dist/browser/mcp.d.ts +9 -18
- package/dist/browser/mcp.js +70 -284
- package/dist/browser/page.d.ts +28 -6
- package/dist/browser/page.js +210 -85
- package/dist/browser.test.js +4 -202
- package/dist/build-manifest.d.ts +26 -0
- package/dist/build-manifest.js +132 -60
- package/dist/build-manifest.test.d.ts +1 -0
- package/dist/build-manifest.test.js +26 -0
- package/dist/cli-manifest.json +1582 -29
- package/dist/clis/bilibili/download.d.ts +10 -0
- package/dist/clis/bilibili/download.js +135 -0
- package/dist/clis/chatwise/ask.d.ts +1 -0
- package/dist/clis/chatwise/ask.js +76 -0
- package/dist/clis/chatwise/export.d.ts +1 -0
- package/dist/clis/chatwise/export.js +46 -0
- package/dist/clis/chatwise/history.d.ts +1 -0
- package/dist/clis/chatwise/history.js +43 -0
- package/dist/clis/chatwise/model.d.ts +1 -0
- package/dist/clis/chatwise/model.js +81 -0
- package/dist/clis/chatwise/new.d.ts +1 -0
- package/dist/clis/chatwise/new.js +18 -0
- package/dist/clis/chatwise/read.d.ts +1 -0
- package/dist/clis/chatwise/read.js +39 -0
- package/dist/clis/chatwise/screenshot.d.ts +1 -0
- package/dist/clis/chatwise/screenshot.js +27 -0
- package/dist/clis/chatwise/send.d.ts +1 -0
- package/dist/clis/chatwise/send.js +45 -0
- package/dist/clis/chatwise/status.d.ts +1 -0
- package/dist/clis/chatwise/status.js +22 -0
- package/dist/clis/discord-app/channels.d.ts +1 -0
- package/dist/clis/discord-app/channels.js +45 -0
- package/dist/clis/discord-app/members.d.ts +1 -0
- package/dist/clis/discord-app/members.js +38 -0
- package/dist/clis/discord-app/read.d.ts +1 -0
- package/dist/clis/discord-app/read.js +45 -0
- package/dist/clis/discord-app/search.d.ts +1 -0
- package/dist/clis/discord-app/search.js +56 -0
- package/dist/clis/discord-app/send.d.ts +1 -0
- package/dist/clis/discord-app/send.js +27 -0
- package/dist/clis/discord-app/servers.d.ts +1 -0
- package/dist/clis/discord-app/servers.js +36 -0
- package/dist/clis/discord-app/status.d.ts +1 -0
- package/dist/clis/discord-app/status.js +16 -0
- package/dist/clis/feishu/new.d.ts +1 -0
- package/dist/clis/feishu/new.js +27 -0
- package/dist/clis/feishu/read.d.ts +1 -0
- package/dist/clis/feishu/read.js +40 -0
- package/dist/clis/feishu/search.d.ts +1 -0
- package/dist/clis/feishu/search.js +30 -0
- package/dist/clis/feishu/send.d.ts +1 -0
- package/dist/clis/feishu/send.js +39 -0
- package/dist/clis/feishu/status.d.ts +1 -0
- package/dist/clis/feishu/status.js +28 -0
- package/dist/clis/grok/ask.d.ts +1 -0
- package/dist/clis/grok/ask.js +82 -0
- package/dist/clis/grok/debug.d.ts +1 -0
- package/dist/clis/grok/debug.js +45 -0
- package/dist/clis/jimeng/generate.yaml +84 -0
- package/dist/clis/jimeng/history.yaml +47 -0
- package/dist/clis/linux-do/categories.yaml +41 -0
- package/dist/clis/linux-do/category.yaml +49 -0
- package/dist/clis/linux-do/hot.yaml +50 -0
- package/dist/clis/linux-do/latest.yaml +40 -0
- package/dist/clis/linux-do/search.yaml +45 -0
- package/dist/clis/linux-do/topic.yaml +38 -0
- package/dist/clis/neteasemusic/like.d.ts +1 -0
- package/dist/clis/neteasemusic/like.js +25 -0
- package/dist/clis/neteasemusic/lyrics.d.ts +1 -0
- package/dist/clis/neteasemusic/lyrics.js +47 -0
- package/dist/clis/neteasemusic/next.d.ts +1 -0
- package/dist/clis/neteasemusic/next.js +26 -0
- package/dist/clis/neteasemusic/play.d.ts +1 -0
- package/dist/clis/neteasemusic/play.js +26 -0
- package/dist/clis/neteasemusic/playing.d.ts +1 -0
- package/dist/clis/neteasemusic/playing.js +59 -0
- package/dist/clis/neteasemusic/playlist.d.ts +1 -0
- package/dist/clis/neteasemusic/playlist.js +46 -0
- package/dist/clis/neteasemusic/prev.d.ts +1 -0
- package/dist/clis/neteasemusic/prev.js +25 -0
- package/dist/clis/neteasemusic/search.d.ts +1 -0
- package/dist/clis/neteasemusic/search.js +52 -0
- package/dist/clis/neteasemusic/status.d.ts +1 -0
- package/dist/clis/neteasemusic/status.js +16 -0
- package/dist/clis/neteasemusic/volume.d.ts +1 -0
- package/dist/clis/neteasemusic/volume.js +54 -0
- package/dist/clis/notion/export.d.ts +1 -0
- package/dist/clis/notion/export.js +31 -0
- package/dist/clis/notion/favorites.d.ts +1 -0
- package/dist/clis/notion/favorites.js +84 -0
- package/dist/clis/notion/new.d.ts +1 -0
- package/dist/clis/notion/new.js +34 -0
- package/dist/clis/notion/read.d.ts +1 -0
- package/dist/clis/notion/read.js +30 -0
- package/dist/clis/notion/search.d.ts +1 -0
- package/dist/clis/notion/search.js +46 -0
- package/dist/clis/notion/sidebar.d.ts +1 -0
- package/dist/clis/notion/sidebar.js +41 -0
- package/dist/clis/notion/status.d.ts +1 -0
- package/dist/clis/notion/status.js +16 -0
- package/dist/clis/notion/write.d.ts +1 -0
- package/dist/clis/notion/write.js +40 -0
- package/dist/clis/twitter/download.d.ts +8 -0
- package/dist/clis/twitter/download.js +204 -0
- package/dist/clis/wechat/chats.d.ts +1 -0
- package/dist/clis/wechat/chats.js +28 -0
- package/dist/clis/wechat/contacts.d.ts +1 -0
- package/dist/clis/wechat/contacts.js +28 -0
- package/dist/clis/wechat/read.d.ts +1 -0
- package/dist/clis/wechat/read.js +58 -0
- package/dist/clis/wechat/search.d.ts +1 -0
- package/dist/clis/wechat/search.js +31 -0
- package/dist/clis/wechat/send.d.ts +1 -0
- package/dist/clis/wechat/send.js +42 -0
- package/dist/clis/wechat/status.d.ts +1 -0
- package/dist/clis/wechat/status.js +29 -0
- package/dist/clis/xiaohongshu/creator-note-detail.d.ts +10 -0
- package/dist/clis/xiaohongshu/creator-note-detail.js +88 -0
- package/dist/clis/xiaohongshu/creator-notes.d.ts +11 -0
- package/dist/clis/xiaohongshu/creator-notes.js +109 -0
- package/dist/clis/xiaohongshu/creator-profile.d.ts +10 -0
- package/dist/clis/xiaohongshu/creator-profile.js +54 -0
- package/dist/clis/xiaohongshu/creator-stats.d.ts +10 -0
- package/dist/clis/xiaohongshu/creator-stats.js +74 -0
- package/dist/clis/xiaohongshu/download.d.ts +7 -0
- package/dist/clis/xiaohongshu/download.js +155 -0
- package/dist/clis/xiaohongshu/search.js +1 -1
- package/dist/clis/xiaohongshu/user-helpers.d.ts +15 -0
- package/dist/clis/xiaohongshu/user-helpers.js +67 -0
- package/dist/clis/xiaohongshu/user-helpers.test.d.ts +1 -0
- package/dist/clis/xiaohongshu/user-helpers.test.js +81 -0
- package/dist/clis/xiaohongshu/user.js +46 -29
- package/dist/clis/zhihu/download.d.ts +11 -0
- package/dist/clis/zhihu/download.js +186 -0
- package/dist/clis/zhihu/download.test.d.ts +1 -0
- package/dist/clis/zhihu/download.test.js +10 -0
- package/dist/daemon.d.ts +13 -0
- package/dist/daemon.js +187 -0
- package/dist/doctor.d.ts +27 -61
- package/dist/doctor.js +70 -601
- package/dist/doctor.test.js +30 -170
- package/dist/download/index.d.ts +79 -0
- package/dist/download/index.js +325 -0
- package/dist/download/progress.d.ts +36 -0
- package/dist/download/progress.js +111 -0
- package/dist/engine.test.js +15 -0
- package/dist/main.js +22 -28
- package/dist/pipeline/executor.test.js +1 -0
- package/dist/pipeline/registry.js +2 -0
- package/dist/pipeline/steps/browser.js +2 -2
- package/dist/pipeline/steps/download.d.ts +34 -0
- package/dist/pipeline/steps/download.js +251 -0
- package/dist/pipeline/steps/intercept.js +1 -2
- package/dist/pipeline/template.js +28 -0
- package/dist/setup.d.ts +6 -0
- package/dist/setup.js +46 -160
- package/dist/types.d.ts +6 -0
- package/extension/icons/icon-128.png +0 -0
- package/extension/icons/icon-16.png +0 -0
- package/extension/icons/icon-32.png +0 -0
- package/extension/icons/icon-48.png +0 -0
- package/extension/manifest.json +31 -0
- package/extension/package.json +16 -0
- package/extension/src/background.ts +293 -0
- package/extension/src/cdp.ts +125 -0
- package/extension/src/protocol.ts +57 -0
- package/extension/store-assets/screenshot-1280x800.png +0 -0
- package/extension/tsconfig.json +15 -0
- package/extension/vite.config.ts +18 -0
- package/package.json +8 -7
- package/scripts/test-site.mjs +70 -0
- package/src/browser/daemon-client.ts +113 -0
- package/src/browser/discover.ts +18 -216
- package/src/browser/errors.ts +30 -100
- package/src/browser/index.ts +6 -12
- package/src/browser/mcp.ts +78 -278
- package/src/browser/page.ts +222 -88
- package/src/browser.test.ts +3 -210
- package/src/build-manifest.test.ts +28 -0
- package/src/build-manifest.ts +147 -57
- package/src/clis/bilibili/download.ts +161 -0
- package/src/clis/chatgpt/README.md +1 -1
- package/src/clis/chatgpt/README.zh-CN.md +1 -1
- package/src/clis/chatwise/README.md +38 -0
- package/src/clis/chatwise/README.zh-CN.md +38 -0
- package/src/clis/chatwise/ask.ts +87 -0
- package/src/clis/chatwise/export.ts +51 -0
- package/src/clis/chatwise/history.ts +47 -0
- package/src/clis/chatwise/model.ts +87 -0
- package/src/clis/chatwise/new.ts +21 -0
- package/src/clis/chatwise/read.ts +42 -0
- package/src/clis/chatwise/screenshot.ts +33 -0
- package/src/clis/chatwise/send.ts +50 -0
- package/src/clis/chatwise/status.ts +25 -0
- package/src/clis/discord-app/README.md +28 -0
- package/src/clis/discord-app/README.zh-CN.md +28 -0
- package/src/clis/discord-app/channels.ts +48 -0
- package/src/clis/discord-app/members.ts +41 -0
- package/src/clis/discord-app/read.ts +49 -0
- package/src/clis/discord-app/search.ts +64 -0
- package/src/clis/discord-app/send.ts +32 -0
- package/src/clis/discord-app/servers.ts +39 -0
- package/src/clis/discord-app/status.ts +18 -0
- package/src/clis/feishu/README.md +20 -0
- package/src/clis/feishu/README.zh-CN.md +20 -0
- package/src/clis/feishu/new.ts +32 -0
- package/src/clis/feishu/read.ts +48 -0
- package/src/clis/feishu/search.ts +35 -0
- package/src/clis/feishu/send.ts +46 -0
- package/src/clis/feishu/status.ts +34 -0
- package/src/clis/grok/ask.ts +90 -0
- package/src/clis/grok/debug.ts +49 -0
- package/src/clis/jimeng/generate.yaml +84 -0
- package/src/clis/jimeng/history.yaml +47 -0
- package/src/clis/linux-do/categories.yaml +41 -0
- package/src/clis/linux-do/category.yaml +49 -0
- package/src/clis/linux-do/hot.yaml +50 -0
- package/src/clis/linux-do/latest.yaml +40 -0
- package/src/clis/linux-do/search.yaml +45 -0
- package/src/clis/linux-do/topic.yaml +38 -0
- package/src/clis/neteasemusic/README.md +31 -0
- package/src/clis/neteasemusic/README.zh-CN.md +31 -0
- package/src/clis/neteasemusic/like.ts +28 -0
- package/src/clis/neteasemusic/lyrics.ts +53 -0
- package/src/clis/neteasemusic/next.ts +30 -0
- package/src/clis/neteasemusic/play.ts +30 -0
- package/src/clis/neteasemusic/playing.ts +62 -0
- package/src/clis/neteasemusic/playlist.ts +51 -0
- package/src/clis/neteasemusic/prev.ts +29 -0
- package/src/clis/neteasemusic/search.ts +58 -0
- package/src/clis/neteasemusic/status.ts +18 -0
- package/src/clis/neteasemusic/volume.ts +61 -0
- package/src/clis/notion/README.md +29 -0
- package/src/clis/notion/README.zh-CN.md +29 -0
- package/src/clis/notion/export.ts +36 -0
- package/src/clis/notion/favorites.ts +87 -0
- package/src/clis/notion/new.ts +39 -0
- package/src/clis/notion/read.ts +33 -0
- package/src/clis/notion/search.ts +54 -0
- package/src/clis/notion/sidebar.ts +44 -0
- package/src/clis/notion/status.ts +18 -0
- package/src/clis/notion/write.ts +45 -0
- package/src/clis/twitter/download.ts +227 -0
- package/src/clis/wechat/README.md +28 -0
- package/src/clis/wechat/README.zh-CN.md +28 -0
- package/src/clis/wechat/chats.ts +33 -0
- package/src/clis/wechat/contacts.ts +33 -0
- package/src/clis/wechat/read.ts +72 -0
- package/src/clis/wechat/search.ts +36 -0
- package/src/clis/wechat/send.ts +49 -0
- package/src/clis/wechat/status.ts +35 -0
- package/src/clis/xiaohongshu/creator-note-detail.ts +95 -0
- package/src/clis/xiaohongshu/creator-notes.ts +116 -0
- package/src/clis/xiaohongshu/creator-profile.ts +60 -0
- package/src/clis/xiaohongshu/creator-stats.ts +81 -0
- package/src/clis/xiaohongshu/download.ts +173 -0
- package/src/clis/xiaohongshu/search.ts +1 -1
- package/src/clis/xiaohongshu/user-helpers.test.ts +106 -0
- package/src/clis/xiaohongshu/user-helpers.ts +85 -0
- package/src/clis/xiaohongshu/user.ts +52 -32
- package/src/clis/zhihu/download.test.ts +12 -0
- package/src/clis/zhihu/download.ts +223 -0
- package/src/daemon.ts +217 -0
- package/src/doctor.test.ts +32 -193
- package/src/doctor.ts +74 -668
- package/src/download/index.ts +395 -0
- package/src/download/progress.ts +125 -0
- package/src/engine.test.ts +17 -0
- package/src/main.ts +18 -26
- package/src/pipeline/executor.test.ts +1 -0
- package/src/pipeline/registry.ts +2 -0
- package/src/pipeline/steps/browser.ts +2 -2
- package/src/pipeline/steps/download.ts +310 -0
- package/src/pipeline/steps/intercept.ts +1 -2
- package/src/pipeline/template.ts +26 -0
- package/src/setup.ts +47 -183
- package/src/types.ts +1 -0
- package/tests/e2e/browser-auth.test.ts +25 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { cli, Strategy } from '../../registry.js';
|
|
2
|
+
export const statusCommand = cli({
|
|
3
|
+
site: 'neteasemusic',
|
|
4
|
+
name: 'status',
|
|
5
|
+
description: 'Check CDP connection to NeteaseMusic Desktop',
|
|
6
|
+
domain: 'localhost',
|
|
7
|
+
strategy: Strategy.UI,
|
|
8
|
+
browser: true,
|
|
9
|
+
args: [],
|
|
10
|
+
columns: ['Status', 'Url', 'Title'],
|
|
11
|
+
func: async (page) => {
|
|
12
|
+
const url = await page.evaluate('window.location.href');
|
|
13
|
+
const title = await page.evaluate('document.title');
|
|
14
|
+
return [{ Status: 'Connected', Url: url, Title: title }];
|
|
15
|
+
},
|
|
16
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const volumeCommand: import("../../registry.js").CliCommand;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { cli, Strategy } from '../../registry.js';
|
|
2
|
+
export const volumeCommand = cli({
|
|
3
|
+
site: 'neteasemusic',
|
|
4
|
+
name: 'volume',
|
|
5
|
+
description: 'Get or set the volume level (0-100)',
|
|
6
|
+
domain: 'localhost',
|
|
7
|
+
strategy: Strategy.UI,
|
|
8
|
+
browser: true,
|
|
9
|
+
args: [
|
|
10
|
+
{ name: 'level', required: false, positional: true, help: 'Volume level 0-100 (omit to read current)' },
|
|
11
|
+
],
|
|
12
|
+
columns: ['Status', 'Volume'],
|
|
13
|
+
func: async (page, kwargs) => {
|
|
14
|
+
const level = kwargs.level;
|
|
15
|
+
if (!level) {
|
|
16
|
+
// Read current volume
|
|
17
|
+
const vol = await page.evaluate(`
|
|
18
|
+
(function() {
|
|
19
|
+
const bar = document.querySelector('.m-playbar .vol .barbg .rng, [class*="volume"] [class*="progress"], [class*="volume"] [class*="played"]');
|
|
20
|
+
if (bar) {
|
|
21
|
+
const style = bar.getAttribute('style') || '';
|
|
22
|
+
const match = style.match(/width:\\s*(\\d+\\.?\\d*)%/);
|
|
23
|
+
if (match) return match[1];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const vol = document.querySelector('.m-playbar .j-vol, [class*="volume-value"]');
|
|
27
|
+
if (vol) return vol.textContent.trim();
|
|
28
|
+
|
|
29
|
+
return 'Unknown';
|
|
30
|
+
})()
|
|
31
|
+
`);
|
|
32
|
+
return [{ Status: 'Current', Volume: vol + '%' }];
|
|
33
|
+
}
|
|
34
|
+
// Set volume by clicking on the volume bar at the right position
|
|
35
|
+
const targetVol = Math.max(0, Math.min(100, parseInt(level, 10)));
|
|
36
|
+
await page.evaluate(`
|
|
37
|
+
(function(target) {
|
|
38
|
+
const bar = document.querySelector('.m-playbar .vol .barbg, [class*="volume-bar"], [class*="volume"] [class*="track"]');
|
|
39
|
+
if (!bar) return;
|
|
40
|
+
|
|
41
|
+
const rect = bar.getBoundingClientRect();
|
|
42
|
+
const x = rect.left + (rect.width * target / 100);
|
|
43
|
+
const y = rect.top + rect.height / 2;
|
|
44
|
+
|
|
45
|
+
bar.dispatchEvent(new MouseEvent('click', {
|
|
46
|
+
clientX: x,
|
|
47
|
+
clientY: y,
|
|
48
|
+
bubbles: true,
|
|
49
|
+
}));
|
|
50
|
+
})(${targetVol})
|
|
51
|
+
`);
|
|
52
|
+
return [{ Status: 'Set', Volume: targetVol + '%' }];
|
|
53
|
+
},
|
|
54
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const exportCommand: import("../../registry.js").CliCommand;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import { cli, Strategy } from '../../registry.js';
|
|
3
|
+
export const exportCommand = cli({
|
|
4
|
+
site: 'notion',
|
|
5
|
+
name: 'export',
|
|
6
|
+
description: 'Export the current Notion page as Markdown',
|
|
7
|
+
domain: 'localhost',
|
|
8
|
+
strategy: Strategy.UI,
|
|
9
|
+
browser: true,
|
|
10
|
+
args: [
|
|
11
|
+
{ name: 'output', required: false, positional: true, help: 'Output file (default: /tmp/notion-export.md)' },
|
|
12
|
+
],
|
|
13
|
+
columns: ['Status', 'File'],
|
|
14
|
+
func: async (page, kwargs) => {
|
|
15
|
+
const outputPath = kwargs.output || '/tmp/notion-export.md';
|
|
16
|
+
const result = await page.evaluate(`
|
|
17
|
+
(function() {
|
|
18
|
+
const titleEl = document.querySelector('[data-block-id] [placeholder="Untitled"], h1.notion-title, [class*="title"]');
|
|
19
|
+
const title = titleEl ? (titleEl.textContent || '').trim() : document.title;
|
|
20
|
+
|
|
21
|
+
const frame = document.querySelector('.notion-page-content, [class*="page-content"], main');
|
|
22
|
+
const content = frame ? (frame.innerText || '').trim() : document.body.innerText;
|
|
23
|
+
|
|
24
|
+
return { title, content };
|
|
25
|
+
})()
|
|
26
|
+
`);
|
|
27
|
+
const md = `# ${result.title}\n\n${result.content}`;
|
|
28
|
+
fs.writeFileSync(outputPath, md);
|
|
29
|
+
return [{ Status: 'Success', File: outputPath }];
|
|
30
|
+
},
|
|
31
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const favoritesCommand: import("../../registry.js").CliCommand;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { cli, Strategy } from '../../registry.js';
|
|
2
|
+
export const favoritesCommand = cli({
|
|
3
|
+
site: 'notion',
|
|
4
|
+
name: 'favorites',
|
|
5
|
+
description: 'List pages from the Notion Favorites section in the sidebar',
|
|
6
|
+
domain: 'localhost',
|
|
7
|
+
strategy: Strategy.UI,
|
|
8
|
+
browser: true,
|
|
9
|
+
args: [],
|
|
10
|
+
columns: ['Index', 'Title', 'Icon'],
|
|
11
|
+
func: async (page) => {
|
|
12
|
+
const items = await page.evaluate(`
|
|
13
|
+
(function() {
|
|
14
|
+
const results = [];
|
|
15
|
+
|
|
16
|
+
// Strategy 1: Use Notion's own class 'notion-outliner-bookmarks-header-container'
|
|
17
|
+
const headerContainer = document.querySelector('.notion-outliner-bookmarks-header-container');
|
|
18
|
+
if (headerContainer) {
|
|
19
|
+
// Walk up to the section parent that wraps header + items
|
|
20
|
+
let section = headerContainer.parentElement;
|
|
21
|
+
if (section && section.children.length === 1) section = section.parentElement;
|
|
22
|
+
|
|
23
|
+
if (section) {
|
|
24
|
+
const treeItems = section.querySelectorAll('[role="treeitem"]');
|
|
25
|
+
treeItems.forEach((item) => {
|
|
26
|
+
// Title text is in a div.notranslate sibling of the icon area
|
|
27
|
+
const titleEl = item.querySelector('div.notranslate:not(.notion-record-icon)');
|
|
28
|
+
const title = titleEl
|
|
29
|
+
? titleEl.textContent.trim()
|
|
30
|
+
: (item.textContent || '').trim().substring(0, 80);
|
|
31
|
+
|
|
32
|
+
// Icon/emoji is in the notion-record-icon element
|
|
33
|
+
const iconEl = item.querySelector('.notion-record-icon');
|
|
34
|
+
const icon = iconEl ? iconEl.textContent.trim().substring(0, 4) : '';
|
|
35
|
+
|
|
36
|
+
if (title && title.length > 0) {
|
|
37
|
+
results.push({ Index: results.length + 1, Title: title, Icon: icon || '📄' });
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Strategy 2: Fallback — find "Favorites" text node and walk DOM
|
|
44
|
+
if (results.length === 0) {
|
|
45
|
+
const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, null);
|
|
46
|
+
let node;
|
|
47
|
+
let favEl = null;
|
|
48
|
+
while (node = walker.nextNode()) {
|
|
49
|
+
const text = node.textContent.trim();
|
|
50
|
+
if (text === 'Favorites' || text === '收藏' || text === '收藏夹') {
|
|
51
|
+
favEl = node.parentElement;
|
|
52
|
+
break;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (favEl) {
|
|
57
|
+
let section = favEl;
|
|
58
|
+
for (let i = 0; i < 6; i++) {
|
|
59
|
+
const p = section.parentElement;
|
|
60
|
+
if (!p || p === document.body) break;
|
|
61
|
+
const treeItems = p.querySelectorAll(':scope > [role="treeitem"]');
|
|
62
|
+
if (treeItems.length > 0) { section = p; break; }
|
|
63
|
+
section = p;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const treeItems = section.querySelectorAll('[role="treeitem"]');
|
|
67
|
+
treeItems.forEach((item) => {
|
|
68
|
+
const text = (item.textContent || '').trim().substring(0, 120);
|
|
69
|
+
if (text && text.length > 1 && !text.match(/^(Favorites|收藏夹?)$/)) {
|
|
70
|
+
results.push({ Index: results.length + 1, Title: text, Icon: '📄' });
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return results;
|
|
77
|
+
})()
|
|
78
|
+
`);
|
|
79
|
+
if (items.length === 0) {
|
|
80
|
+
return [{ Index: 0, Title: 'No favorites found. Make sure sidebar is visible and you have favorites.', Icon: '⚠️' }];
|
|
81
|
+
}
|
|
82
|
+
return items;
|
|
83
|
+
},
|
|
84
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const newCommand: import("../../registry.js").CliCommand;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { cli, Strategy } from '../../registry.js';
|
|
2
|
+
export const newCommand = cli({
|
|
3
|
+
site: 'notion',
|
|
4
|
+
name: 'new',
|
|
5
|
+
description: 'Create a new page in Notion',
|
|
6
|
+
domain: 'localhost',
|
|
7
|
+
strategy: Strategy.UI,
|
|
8
|
+
browser: true,
|
|
9
|
+
args: [
|
|
10
|
+
{ name: 'title', required: false, positional: true, help: 'Page title (optional)' },
|
|
11
|
+
],
|
|
12
|
+
columns: ['Status'],
|
|
13
|
+
func: async (page, kwargs) => {
|
|
14
|
+
const title = kwargs.title;
|
|
15
|
+
// Cmd+N creates a new page in Notion
|
|
16
|
+
const isMac = process.platform === 'darwin';
|
|
17
|
+
await page.pressKey(isMac ? 'Meta+N' : 'Control+N');
|
|
18
|
+
await page.wait(1);
|
|
19
|
+
// If title is provided, type it into the title field
|
|
20
|
+
if (title) {
|
|
21
|
+
await page.evaluate(`
|
|
22
|
+
(function(t) {
|
|
23
|
+
const titleEl = document.querySelector('[placeholder="Untitled"], [data-content-editable-leaf] [placeholder]');
|
|
24
|
+
if (titleEl) {
|
|
25
|
+
titleEl.focus();
|
|
26
|
+
document.execCommand('insertText', false, t);
|
|
27
|
+
}
|
|
28
|
+
})(${JSON.stringify(title)})
|
|
29
|
+
`);
|
|
30
|
+
await page.wait(0.5);
|
|
31
|
+
}
|
|
32
|
+
return [{ Status: title ? `Created page: ${title}` : 'New blank page created' }];
|
|
33
|
+
},
|
|
34
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const readCommand: import("../../registry.js").CliCommand;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { cli, Strategy } from '../../registry.js';
|
|
2
|
+
export const readCommand = cli({
|
|
3
|
+
site: 'notion',
|
|
4
|
+
name: 'read',
|
|
5
|
+
description: 'Read the content of the currently open Notion page',
|
|
6
|
+
domain: 'localhost',
|
|
7
|
+
strategy: Strategy.UI,
|
|
8
|
+
browser: true,
|
|
9
|
+
args: [],
|
|
10
|
+
columns: ['Title', 'Content'],
|
|
11
|
+
func: async (page) => {
|
|
12
|
+
const result = await page.evaluate(`
|
|
13
|
+
(function() {
|
|
14
|
+
// Get the page title
|
|
15
|
+
const titleEl = document.querySelector('[data-block-id] [placeholder="Untitled"], .notion-page-block .notranslate, h1.notion-title, [class*="title"]');
|
|
16
|
+
const title = titleEl ? (titleEl.textContent || '').trim() : document.title;
|
|
17
|
+
|
|
18
|
+
// Get the page content — Notion renders blocks in a frame
|
|
19
|
+
const frame = document.querySelector('.notion-page-content, [class*="page-content"], .layout-content, main');
|
|
20
|
+
const content = frame ? (frame.innerText || frame.textContent || '').trim() : '';
|
|
21
|
+
|
|
22
|
+
return { title, content };
|
|
23
|
+
})()
|
|
24
|
+
`);
|
|
25
|
+
return [{
|
|
26
|
+
Title: result.title || 'Untitled',
|
|
27
|
+
Content: result.content || '(empty page)',
|
|
28
|
+
}];
|
|
29
|
+
},
|
|
30
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const searchCommand: import("../../registry.js").CliCommand;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { cli, Strategy } from '../../registry.js';
|
|
2
|
+
export const searchCommand = cli({
|
|
3
|
+
site: 'notion',
|
|
4
|
+
name: 'search',
|
|
5
|
+
description: 'Search pages and databases in Notion via Quick Find (Cmd+P)',
|
|
6
|
+
domain: 'localhost',
|
|
7
|
+
strategy: Strategy.UI,
|
|
8
|
+
browser: true,
|
|
9
|
+
args: [{ name: 'query', required: true, positional: true, help: 'Search query' }],
|
|
10
|
+
columns: ['Index', 'Title'],
|
|
11
|
+
func: async (page, kwargs) => {
|
|
12
|
+
const query = kwargs.query;
|
|
13
|
+
// Open Quick Find
|
|
14
|
+
const isMac = process.platform === 'darwin';
|
|
15
|
+
await page.pressKey(isMac ? 'Meta+P' : 'Control+P');
|
|
16
|
+
await page.wait(0.5);
|
|
17
|
+
// Type the search query
|
|
18
|
+
await page.evaluate(`
|
|
19
|
+
(function(q) {
|
|
20
|
+
const input = document.querySelector('input[placeholder*="Search"], input[type="text"]');
|
|
21
|
+
if (input) {
|
|
22
|
+
const setter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set;
|
|
23
|
+
setter.call(input, q);
|
|
24
|
+
input.dispatchEvent(new Event('input', { bubbles: true }));
|
|
25
|
+
}
|
|
26
|
+
})(${JSON.stringify(query)})
|
|
27
|
+
`);
|
|
28
|
+
await page.wait(1.5);
|
|
29
|
+
// Scrape results
|
|
30
|
+
const results = await page.evaluate(`
|
|
31
|
+
(function() {
|
|
32
|
+
const items = document.querySelectorAll('[role="option"], [class*="searchResult"], [class*="quick-find"] [role="button"]');
|
|
33
|
+
return Array.from(items).slice(0, 20).map((item, i) => ({
|
|
34
|
+
Index: i + 1,
|
|
35
|
+
Title: (item.textContent || '').trim().substring(0, 120),
|
|
36
|
+
}));
|
|
37
|
+
})()
|
|
38
|
+
`);
|
|
39
|
+
// Close Quick Find
|
|
40
|
+
await page.pressKey('Escape');
|
|
41
|
+
if (results.length === 0) {
|
|
42
|
+
return [{ Index: 0, Title: `No results for "${query}"` }];
|
|
43
|
+
}
|
|
44
|
+
return results;
|
|
45
|
+
},
|
|
46
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const sidebarCommand: import("../../registry.js").CliCommand;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { cli, Strategy } from '../../registry.js';
|
|
2
|
+
export const sidebarCommand = cli({
|
|
3
|
+
site: 'notion',
|
|
4
|
+
name: 'sidebar',
|
|
5
|
+
description: 'List pages and databases from the Notion sidebar',
|
|
6
|
+
domain: 'localhost',
|
|
7
|
+
strategy: Strategy.UI,
|
|
8
|
+
browser: true,
|
|
9
|
+
args: [],
|
|
10
|
+
columns: ['Index', 'Title'],
|
|
11
|
+
func: async (page) => {
|
|
12
|
+
const items = await page.evaluate(`
|
|
13
|
+
(function() {
|
|
14
|
+
const results = [];
|
|
15
|
+
// Notion sidebar items
|
|
16
|
+
const selectors = [
|
|
17
|
+
'[class*="sidebar"] [role="treeitem"]',
|
|
18
|
+
'[class*="sidebar"] a',
|
|
19
|
+
'.notion-sidebar [role="button"]',
|
|
20
|
+
'nav [role="treeitem"]',
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
for (const sel of selectors) {
|
|
24
|
+
const nodes = document.querySelectorAll(sel);
|
|
25
|
+
if (nodes.length > 0) {
|
|
26
|
+
nodes.forEach((n, i) => {
|
|
27
|
+
const text = (n.textContent || '').trim().substring(0, 100);
|
|
28
|
+
if (text && text.length > 1) results.push({ Index: i + 1, Title: text });
|
|
29
|
+
});
|
|
30
|
+
break;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return results;
|
|
34
|
+
})()
|
|
35
|
+
`);
|
|
36
|
+
if (items.length === 0) {
|
|
37
|
+
return [{ Index: 0, Title: 'No sidebar items found. Toggle the sidebar first.' }];
|
|
38
|
+
}
|
|
39
|
+
return items;
|
|
40
|
+
},
|
|
41
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const statusCommand: import("../../registry.js").CliCommand;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { cli, Strategy } from '../../registry.js';
|
|
2
|
+
export const statusCommand = cli({
|
|
3
|
+
site: 'notion',
|
|
4
|
+
name: 'status',
|
|
5
|
+
description: 'Check active CDP connection to Notion Desktop',
|
|
6
|
+
domain: 'localhost',
|
|
7
|
+
strategy: Strategy.UI,
|
|
8
|
+
browser: true,
|
|
9
|
+
args: [],
|
|
10
|
+
columns: ['Status', 'Url', 'Title'],
|
|
11
|
+
func: async (page) => {
|
|
12
|
+
const url = await page.evaluate('window.location.href');
|
|
13
|
+
const title = await page.evaluate('document.title');
|
|
14
|
+
return [{ Status: 'Connected', Url: url, Title: title }];
|
|
15
|
+
},
|
|
16
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const writeCommand: import("../../registry.js").CliCommand;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { cli, Strategy } from '../../registry.js';
|
|
2
|
+
export const writeCommand = cli({
|
|
3
|
+
site: 'notion',
|
|
4
|
+
name: 'write',
|
|
5
|
+
description: 'Append text content to the currently open Notion page',
|
|
6
|
+
domain: 'localhost',
|
|
7
|
+
strategy: Strategy.UI,
|
|
8
|
+
browser: true,
|
|
9
|
+
args: [{ name: 'text', required: true, positional: true, help: 'Text to append to the page' }],
|
|
10
|
+
columns: ['Status'],
|
|
11
|
+
func: async (page, kwargs) => {
|
|
12
|
+
const text = kwargs.text;
|
|
13
|
+
// Focus the page body and move to the end
|
|
14
|
+
await page.evaluate(`
|
|
15
|
+
(function(text) {
|
|
16
|
+
// Find the editable area in Notion
|
|
17
|
+
const editables = document.querySelectorAll('.notion-page-content [contenteditable="true"], [class*="page-content"] [contenteditable="true"]');
|
|
18
|
+
let target = editables.length > 0 ? editables[editables.length - 1] : null;
|
|
19
|
+
|
|
20
|
+
if (!target) {
|
|
21
|
+
// Fallback: just find any contenteditable
|
|
22
|
+
const all = document.querySelectorAll('[contenteditable="true"]');
|
|
23
|
+
target = all.length > 0 ? all[all.length - 1] : null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (!target) throw new Error('Could not find editable area in Notion page');
|
|
27
|
+
|
|
28
|
+
target.focus();
|
|
29
|
+
// Move to end
|
|
30
|
+
const sel = window.getSelection();
|
|
31
|
+
sel.selectAllChildren(target);
|
|
32
|
+
sel.collapseToEnd();
|
|
33
|
+
|
|
34
|
+
document.execCommand('insertText', false, text);
|
|
35
|
+
})(${JSON.stringify(text)})
|
|
36
|
+
`);
|
|
37
|
+
await page.wait(0.5);
|
|
38
|
+
return [{ Status: 'Text appended successfully' }];
|
|
39
|
+
},
|
|
40
|
+
});
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Twitter/X download — download images and videos from tweets.
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* opencli twitter download --username elonmusk --limit 10 --output ./twitter
|
|
6
|
+
* opencli twitter download --tweet-url https://x.com/xxx/status/123 --output ./twitter
|
|
7
|
+
*/
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Twitter/X download — download images and videos from tweets.
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* opencli twitter download --username elonmusk --limit 10 --output ./twitter
|
|
6
|
+
* opencli twitter download --tweet-url https://x.com/xxx/status/123 --output ./twitter
|
|
7
|
+
*/
|
|
8
|
+
import * as fs from 'node:fs';
|
|
9
|
+
import * as path from 'node:path';
|
|
10
|
+
import { cli, Strategy } from '../../registry.js';
|
|
11
|
+
import { httpDownload, ytdlpDownload, checkYtdlp, getTempDir, exportCookiesToNetscape, } from '../../download/index.js';
|
|
12
|
+
import { DownloadProgressTracker, formatBytes } from '../../download/progress.js';
|
|
13
|
+
cli({
|
|
14
|
+
site: 'twitter',
|
|
15
|
+
name: 'download',
|
|
16
|
+
description: '下载 Twitter/X 媒体(图片和视频)',
|
|
17
|
+
domain: 'x.com',
|
|
18
|
+
strategy: Strategy.COOKIE,
|
|
19
|
+
args: [
|
|
20
|
+
{ name: 'username', help: 'Twitter username (downloads from media tab)' },
|
|
21
|
+
{ name: 'tweet-url', help: 'Single tweet URL to download' },
|
|
22
|
+
{ name: 'limit', type: 'int', default: 10, help: 'Number of tweets to scan' },
|
|
23
|
+
{ name: 'output', default: './twitter-downloads', help: 'Output directory' },
|
|
24
|
+
],
|
|
25
|
+
columns: ['index', 'type', 'status', 'size'],
|
|
26
|
+
func: async (page, kwargs) => {
|
|
27
|
+
const username = kwargs.username;
|
|
28
|
+
const tweetUrl = kwargs['tweet-url'];
|
|
29
|
+
const limit = kwargs.limit;
|
|
30
|
+
const output = kwargs.output;
|
|
31
|
+
if (!username && !tweetUrl) {
|
|
32
|
+
return [{
|
|
33
|
+
index: 0,
|
|
34
|
+
type: '-',
|
|
35
|
+
status: 'failed',
|
|
36
|
+
size: 'Must provide --username or --tweet-url',
|
|
37
|
+
}];
|
|
38
|
+
}
|
|
39
|
+
// Navigate to the appropriate page
|
|
40
|
+
if (tweetUrl) {
|
|
41
|
+
await page.goto(tweetUrl);
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
await page.goto(`https://x.com/${username}/media`);
|
|
45
|
+
}
|
|
46
|
+
await page.wait(3);
|
|
47
|
+
// Scroll to load more content
|
|
48
|
+
if (!tweetUrl) {
|
|
49
|
+
await page.autoScroll({ times: Math.ceil(limit / 5) });
|
|
50
|
+
}
|
|
51
|
+
// Extract media URLs
|
|
52
|
+
const data = await page.evaluate(`
|
|
53
|
+
(() => {
|
|
54
|
+
const media = [];
|
|
55
|
+
|
|
56
|
+
// Find images (high quality)
|
|
57
|
+
document.querySelectorAll('img[src*="pbs.twimg.com/media"]').forEach(img => {
|
|
58
|
+
let src = img.src || '';
|
|
59
|
+
// Get large version
|
|
60
|
+
src = src.replace(/&name=\\w+$/, '&name=large');
|
|
61
|
+
src = src.replace(/\\?format=/, '?format=');
|
|
62
|
+
if (!src.includes('&name=')) {
|
|
63
|
+
src = src + '&name=large';
|
|
64
|
+
}
|
|
65
|
+
media.push({ type: 'image', url: src });
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// Find videos
|
|
69
|
+
document.querySelectorAll('video').forEach(video => {
|
|
70
|
+
const src = video.src || '';
|
|
71
|
+
if (src) {
|
|
72
|
+
media.push({ type: 'video', url: src, poster: video.poster || '' });
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// Find video tweets (for yt-dlp)
|
|
77
|
+
document.querySelectorAll('[data-testid="videoPlayer"]').forEach(player => {
|
|
78
|
+
const tweetLink = player.closest('article')?.querySelector('a[href*="/status/"]');
|
|
79
|
+
const href = tweetLink?.getAttribute('href') || '';
|
|
80
|
+
if (href) {
|
|
81
|
+
const tweetUrl = 'https://x.com' + href;
|
|
82
|
+
media.push({ type: 'video-tweet', url: tweetUrl });
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
return media;
|
|
87
|
+
})()
|
|
88
|
+
`);
|
|
89
|
+
if (!data || data.length === 0) {
|
|
90
|
+
return [{
|
|
91
|
+
index: 0,
|
|
92
|
+
type: '-',
|
|
93
|
+
status: 'failed',
|
|
94
|
+
size: 'No media found',
|
|
95
|
+
}];
|
|
96
|
+
}
|
|
97
|
+
// Extract cookies
|
|
98
|
+
const cookieString = await page.evaluate(`(() => document.cookie)()`);
|
|
99
|
+
// Create output directory
|
|
100
|
+
const outputDir = tweetUrl
|
|
101
|
+
? path.join(output, 'tweets')
|
|
102
|
+
: path.join(output, username || 'media');
|
|
103
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
104
|
+
// Export cookies for yt-dlp
|
|
105
|
+
let cookiesFile;
|
|
106
|
+
if (typeof cookieString === 'string' && cookieString) {
|
|
107
|
+
const tempDir = getTempDir();
|
|
108
|
+
fs.mkdirSync(tempDir, { recursive: true });
|
|
109
|
+
cookiesFile = path.join(tempDir, `twitter_cookies_${Date.now()}.txt`);
|
|
110
|
+
const cookies = cookieString.split(';').map((c) => {
|
|
111
|
+
const [name, ...rest] = c.trim().split('=');
|
|
112
|
+
return {
|
|
113
|
+
name: name || '',
|
|
114
|
+
value: rest.join('=') || '',
|
|
115
|
+
domain: '.x.com',
|
|
116
|
+
path: '/',
|
|
117
|
+
secure: true,
|
|
118
|
+
httpOnly: false,
|
|
119
|
+
};
|
|
120
|
+
}).filter((c) => c.name);
|
|
121
|
+
exportCookiesToNetscape(cookies, cookiesFile);
|
|
122
|
+
}
|
|
123
|
+
// Deduplicate media
|
|
124
|
+
const seen = new Set();
|
|
125
|
+
const uniqueMedia = data.filter((m) => {
|
|
126
|
+
if (seen.has(m.url))
|
|
127
|
+
return false;
|
|
128
|
+
seen.add(m.url);
|
|
129
|
+
return true;
|
|
130
|
+
}).slice(0, limit);
|
|
131
|
+
const tracker = new DownloadProgressTracker(uniqueMedia.length, true);
|
|
132
|
+
const results = [];
|
|
133
|
+
for (let i = 0; i < uniqueMedia.length; i++) {
|
|
134
|
+
const media = uniqueMedia[i];
|
|
135
|
+
const ext = media.type === 'image' ? 'jpg' : 'mp4';
|
|
136
|
+
const filename = `${username || 'tweet'}_${i + 1}.${ext}`;
|
|
137
|
+
const destPath = path.join(outputDir, filename);
|
|
138
|
+
const progressBar = tracker.onFileStart(filename, i);
|
|
139
|
+
try {
|
|
140
|
+
let result;
|
|
141
|
+
if (media.type === 'video-tweet' && checkYtdlp()) {
|
|
142
|
+
// Use yt-dlp for video tweets
|
|
143
|
+
result = await ytdlpDownload(media.url, destPath, {
|
|
144
|
+
cookiesFile,
|
|
145
|
+
extraArgs: ['--merge-output-format', 'mp4'],
|
|
146
|
+
onProgress: (percent) => {
|
|
147
|
+
if (progressBar)
|
|
148
|
+
progressBar.update(percent, 100);
|
|
149
|
+
},
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
else if (media.type === 'image') {
|
|
153
|
+
// Direct HTTP download for images
|
|
154
|
+
result = await httpDownload(media.url, destPath, {
|
|
155
|
+
cookies: typeof cookieString === 'string' ? cookieString : '',
|
|
156
|
+
timeout: 30000,
|
|
157
|
+
onProgress: (received, total) => {
|
|
158
|
+
if (progressBar)
|
|
159
|
+
progressBar.update(received, total);
|
|
160
|
+
},
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
// Direct HTTP download for direct video URLs
|
|
165
|
+
result = await httpDownload(media.url, destPath, {
|
|
166
|
+
cookies: typeof cookieString === 'string' ? cookieString : '',
|
|
167
|
+
timeout: 60000,
|
|
168
|
+
onProgress: (received, total) => {
|
|
169
|
+
if (progressBar)
|
|
170
|
+
progressBar.update(received, total);
|
|
171
|
+
},
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
if (progressBar) {
|
|
175
|
+
progressBar.complete(result.success, result.success ? formatBytes(result.size) : undefined);
|
|
176
|
+
}
|
|
177
|
+
tracker.onFileComplete(result.success);
|
|
178
|
+
results.push({
|
|
179
|
+
index: i + 1,
|
|
180
|
+
type: media.type === 'video-tweet' ? 'video' : media.type,
|
|
181
|
+
status: result.success ? 'success' : 'failed',
|
|
182
|
+
size: result.success ? formatBytes(result.size) : (result.error || 'unknown error'),
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
catch (err) {
|
|
186
|
+
if (progressBar)
|
|
187
|
+
progressBar.fail(err.message);
|
|
188
|
+
tracker.onFileComplete(false);
|
|
189
|
+
results.push({
|
|
190
|
+
index: i + 1,
|
|
191
|
+
type: media.type,
|
|
192
|
+
status: 'failed',
|
|
193
|
+
size: err.message,
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
tracker.finish();
|
|
198
|
+
// Cleanup cookies file
|
|
199
|
+
if (cookiesFile && fs.existsSync(cookiesFile)) {
|
|
200
|
+
fs.unlinkSync(cookiesFile);
|
|
201
|
+
}
|
|
202
|
+
return results;
|
|
203
|
+
},
|
|
204
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const chatsCommand: import("../../registry.js").CliCommand;
|