Skip to content

Commit e043997

Browse files
committed
feat: download system, AI discovery docs, twitter media batch download
- Twitter download: batch image/video download with reqwest + yt-dlp fallback - README (EN/ZH/JA): add AI Discovery and Download sections with examples - Features: add download and AI discovery highlights
1 parent b0fee2e commit e043997

File tree

8 files changed

+320
-63
lines changed

8 files changed

+320
-63
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@
22
/docs
33
/opencli
44
test-results*.md
5+
twitter-downloads/

README.ja.md

Lines changed: 46 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@
3737
- **55サイト、333コマンド** —— Bilibili、Twitter、Reddit、知乎、小紅書、YouTube、Hacker News などをカバー
3838
- **ブラウザセッション再利用** —— Chrome 拡張機能でログイン済み状態を再利用、トークン管理不要
3939
- **宣言型 YAML Pipeline** —— YAML でデータ取得フローを記述、コードゼロで新しいアダプターを追加
40-
- **AI ネイティブディスカバリー** —— `explore` でサイト API を分析、`synthesize` でアダプターを自動生成、`cascade` で認証ストラテジーを探索
40+
- **AI ネイティブディスカバリー** —— `explore` でサイト API を分析、`generate` で1コマンドでアダプターを自動生成、`cascade` で認証ストラテジーを探索
41+
- **メディア&記事ダウンロード** —— 動画ダウンロード(yt-dlp)、記事を Markdown にエクスポート+画像のローカル保存
4142
- **外部 CLI パススルー** —— GitHub CLI、Docker、Kubernetes などのツールを統合
4243
- **複数出力フォーマット** —— table、JSON、YAML、CSV、Markdown
4344
- **単一バイナリ** —— 4MB の静的バイナリにコンパイル、ランタイム依存ゼロ
@@ -207,17 +208,55 @@ opencli-rs completion fish > ~/.config/fish/completions/opencli-rs.fish
207208

208209
## AI ディスカバリー機能
209210

211+
1コマンドで API を発見し、アダプターを自動生成、即座に利用可能:
212+
213+
```bash
214+
# ワンショット:探索 + 合成 + アダプター保存
215+
opencli-rs generate https://www.example.com --goal hot
216+
# ✅ アダプター生成: example hot
217+
# 保存先: ~/.opencli-rs/adapters/example/hot.yaml
218+
# 実行: opencli-rs example hot
219+
220+
# Web サイトの API を探索(エンドポイント、フレームワーク、Store)
221+
opencli-rs explore https://www.example.com --site mysite
222+
223+
# インタラクティブファジング(ボタンクリックで隠し API を発見)
224+
opencli-rs explore https://www.example.com --auto --click "コメント,字幕"
225+
226+
# 認証ストラテジー自動検出(PUBLIC → COOKIE → HEADER)
227+
opencli-rs cascade https://api.example.com/hot
228+
```
229+
230+
**ディスカバリー機能:**
231+
- `.json` サフィックスプローブ(Reddit 式 REST ディスカバリー)
232+
- `__INITIAL_STATE__` 抽出(Bilibili、小紅書などの SSR サイト)
233+
- Pinia/Vuex Store 発見とアクションマッピング
234+
- `--goal search` による検索エンドポイント自動発見
235+
- フレームワーク検出(Vue/React/Next.js/Nuxt)
236+
237+
## ダウンロード
238+
239+
対応サイトからメディアと記事をダウンロード:
240+
210241
```bash
211-
# Web サイトの API を探索
212-
opencli-rs explore https://example.com
242+
# Bilibili 動画ダウンロード(yt-dlp が必要)
243+
opencli-rs bilibili download BV1xxx --output ./videos --quality 1080p
244+
245+
# 知乎記事を Markdown でダウンロード(画像付き)
246+
opencli-rs zhihu download "https://zhuanlan.zhihu.com/p/xxx" --output ./articles
213247

214-
# 認証ストラテジーを自動探索
215-
opencli-rs cascade https://api.example.com/data
248+
# WeChat 公式アカウント記事を Markdown でダウンロード(画像付き)
249+
opencli-rs weixin download "https://mp.weixin.qq.com/s/xxx" --output ./articles
216250

217-
# ワンクリックでアダプターを生成
218-
opencli-rs generate https://example.com --goal "hot posts"
251+
# Twitter/X メディアダウンロード(画像 + 動画)
252+
opencli-rs twitter download nash_su --limit 10 --output ./twitter
219253
```
220254

255+
**ダウンロード機能:**
256+
- yt-dlp による動画ダウンロード(ブラウザから Cookie を自動取得、システム認証不要)
257+
- YAML フロントマター付き Markdown エクスポート(タイトル、著者、日付、出典)
258+
- 画像の自動ダウンロードとローカル化(リモート URL をローカル `images/img_001.jpg` に置換)
259+
221260
## 外部 CLI 統合
222261

223262
統合済みの外部ツール(パススルー実行):

README.md

Lines changed: 48 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@ A **complete rewrite in pure Rust** based on [OpenCLI](https://github.com/jackwe
3737
- **55 sites, 333 commands** — Covers Bilibili, Twitter, Reddit, Zhihu, Xiaohongshu, YouTube, Hacker News, and more
3838
- **Browser session reuse** — Reuse logged-in sessions via Chrome extension, no need to manage tokens
3939
- **Declarative YAML Pipeline** — Describe data scraping workflows in YAML, add new adapters with zero code
40-
- **AI-native discovery**`explore` analyzes website APIs, `synthesize` auto-generates adapters, `cascade` probes authentication strategies
40+
- **AI-native discovery**`explore` analyzes website APIs, `generate` auto-creates adapters with one command, `cascade` probes authentication strategies
41+
- **Download media & articles** — Download videos (via yt-dlp), articles as Markdown with images localized
4142
- **External CLI passthrough** — Integrate GitHub CLI, Docker, Kubernetes, and other tools
4243
- **Multi-format output** — table, JSON, YAML, CSV, Markdown
4344
- **Single binary** — Compiles to a 4MB static binary with zero runtime dependencies
@@ -207,17 +208,57 @@ Run `opencli-rs --help` to see all available commands.
207208

208209
## AI Discovery Capabilities
209210

211+
One command to discover APIs, auto-generate adapters, and start using them immediately:
212+
213+
```bash
214+
# One-shot: explore + synthesize + save adapter
215+
opencli-rs generate https://www.example.com --goal hot
216+
# ✅ Generated adapter: example hot
217+
# Saved to: ~/.opencli-rs/adapters/example/hot.yaml
218+
# Run it now: opencli-rs example hot
219+
220+
# Explore website API surface (endpoints, framework, stores)
221+
opencli-rs explore https://www.example.com --site mysite
222+
223+
# With interactive fuzzing (click buttons to trigger hidden APIs)
224+
opencli-rs explore https://www.example.com --auto --click "Comments,CC"
225+
226+
# Auto-detect authentication strategy (PUBLIC → COOKIE → HEADER)
227+
opencli-rs cascade https://api.example.com/hot
228+
```
229+
230+
**Discovery features:**
231+
- `.json` suffix probing (Reddit-style REST discovery)
232+
- `__INITIAL_STATE__` extraction (SSR sites like Bilibili, Xiaohongshu)
233+
- Pinia/Vuex store discovery and action mapping
234+
- Auto search endpoint discovery with `--goal search`
235+
- Framework detection (Vue/React/Next.js/Nuxt)
236+
237+
## Download
238+
239+
Download media and articles from supported sites:
240+
210241
```bash
211-
# Explore website APIs
212-
opencli-rs explore https://example.com
242+
# Download Bilibili video (requires yt-dlp)
243+
opencli-rs bilibili download BV1xxx --output ./videos --quality 1080p
244+
245+
# Download Zhihu article as Markdown with images
246+
opencli-rs zhihu download "https://zhuanlan.zhihu.com/p/xxx" --output ./articles
213247

214-
# Auto-detect authentication strategies
215-
opencli-rs cascade https://api.example.com/data
248+
# Download WeChat article as Markdown with images
249+
opencli-rs weixin download "https://mp.weixin.qq.com/s/xxx" --output ./articles
216250

217-
# One-click adapter generation
218-
opencli-rs generate https://example.com --goal "hot posts"
251+
# Download Twitter/X media (images + videos)
252+
opencli-rs twitter download nash_su --limit 10 --output ./twitter
253+
opencli-rs twitter download --tweet-url "https://x.com/user/status/123" --output ./twitter
219254
```
220255

256+
**Download features:**
257+
- Videos via yt-dlp (cookies extracted from browser automatically, no Keychain prompt)
258+
- Articles as Markdown with YAML frontmatter (title, author, date, source)
259+
- Images downloaded and localized (remote URLs replaced with local `images/img_001.jpg`)
260+
- Output directory structure: `output/article_title/title.md` + `output/article_title/images/`
261+
221262
## External CLI Integration
222263

223264
Integrated external tools (passthrough execution):

README.zh.md

Lines changed: 48 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@
3838
- **55 个站点、333 个命令** —— 覆盖 Bilibili、Twitter、Reddit、知乎、小红书、YouTube、Hacker News 等
3939
- **浏览器会话复用** —— 通过 Chrome 扩展复用已登录状态,无需管理 token
4040
- **声明式 YAML Pipeline** —— 用 YAML 描述数据抓取流程,零代码新增适配器
41-
- **AI 原生发现** —— `explore` 分析网站 API、`synthesize` 自动生成适配器、`cascade` 探测认证策略
41+
- **AI 原生发现** —— `explore` 分析网站 API、`generate` 一键生成适配器、`cascade` 探测认证策略
42+
- **下载媒体和文章** —— 视频下载(yt-dlp)、文章导出为 Markdown 并本地化配图
4243
- **外部 CLI 透传** —— 集成 GitHub CLI、Docker、Kubernetes 等工具
4344
- **多格式输出** —— table、JSON、YAML、CSV、Markdown
4445
- **单一二进制** —— 编译为 4MB 静态二进制,零运行时依赖
@@ -208,17 +209,57 @@ opencli-rs completion fish > ~/.config/fish/completions/opencli-rs.fish
208209

209210
## AI 发现能力
210211

212+
一行命令发现 API、自动生成适配器、立即可用:
213+
214+
```bash
215+
# 一键:探索 + 合成 + 保存适配器
216+
opencli-rs generate https://www.example.com --goal hot
217+
# ✅ 已生成适配器: example hot
218+
# 保存到: ~/.opencli-rs/adapters/example/hot.yaml
219+
# 立即运行: opencli-rs example hot
220+
221+
# 探索网站 API(端点、框架、Store)
222+
opencli-rs explore https://www.example.com --site mysite
223+
224+
# 交互式模糊测试(点击按钮触发隐藏 API)
225+
opencli-rs explore https://www.example.com --auto --click "评论,字幕"
226+
227+
# 自动探测认证策略(PUBLIC → COOKIE → HEADER)
228+
opencli-rs cascade https://api.example.com/hot
229+
```
230+
231+
**发现能力:**
232+
- `.json` 后缀探测(Reddit 风格 REST 发现)
233+
- `__INITIAL_STATE__` 提取(Bilibili、小红书等 SSR 站点)
234+
- Pinia/Vuex Store 发现和 Action 映射
235+
- `--goal search` 自动发现搜索端点
236+
- 框架检测(Vue/React/Next.js/Nuxt)
237+
238+
## 下载
239+
240+
下载支持站点的媒体和文章:
241+
211242
```bash
212-
# 探索网站 API
213-
opencli-rs explore https://example.com
243+
# 下载 B 站视频(需要 yt-dlp)
244+
opencli-rs bilibili download BV1xxx --output ./videos --quality 1080p
245+
246+
# 下载知乎文章为 Markdown(含配图)
247+
opencli-rs zhihu download "https://zhuanlan.zhihu.com/p/xxx" --output ./articles
214248

215-
# 自动探测认证策略
216-
opencli-rs cascade https://api.example.com/data
249+
# 下载微信公众号文章为 Markdown(含配图)
250+
opencli-rs weixin download "https://mp.weixin.qq.com/s/xxx" --output ./articles
217251

218-
# 一键生成适配器
219-
opencli-rs generate https://example.com --goal "hot posts"
252+
# 下载 Twitter/X 媒体(图片 + 视频)
253+
opencli-rs twitter download nash_su --limit 10 --output ./twitter
254+
opencli-rs twitter download --tweet-url "https://x.com/user/status/123" --output ./twitter
220255
```
221256

257+
**下载特性:**
258+
- 视频通过 yt-dlp 下载(自动从浏览器提取 cookies,无需系统授权)
259+
- 文章导出为 Markdown + YAML 头信息(标题、作者、日期、来源)
260+
- 配图自动下载并本地化(远程 URL 替换为本地 `images/img_001.jpg`
261+
- 输出结构:`output/文章标题/标题.md` + `output/文章标题/images/`
262+
222263
## 外部 CLI 集成
223264

224265
已集成的外部工具(透传执行):

adapters/twitter/download.yaml

Lines changed: 28 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
site: twitter
22
name: download
3-
description: Download Twitter/X media (images and videos)
3+
description: 下载 Twitter/X 媒体(图片和视频)
44
domain: x.com
55
strategy: cookie
66
browser: true
@@ -25,36 +25,30 @@ args:
2525
columns: [index, type, status, size]
2626

2727
pipeline:
28+
# Step 1: Navigate to target page
2829
- navigate:
29-
url: https://x.com
30+
url: https://x.com/home
3031
settleMs: 3000
3132

3233
- evaluate: |
3334
(() => {
34-
const username = ${{ args.username | json }};
35-
const tweetUrl = ${{ args['tweet-url'] | json }};
36-
37-
if (!username && !tweetUrl) {
38-
throw new Error('Must provide a username or --tweet-url');
39-
}
40-
41-
if (tweetUrl) {
42-
return tweetUrl;
43-
} else {
44-
return 'https://x.com/' + username + '/media';
45-
}
35+
const username = args.username || '';
36+
const tweetUrl = args['tweet-url'] || '';
37+
if (!username && !tweetUrl) throw new Error('Must provide a username or --tweet-url');
38+
return tweetUrl || ('https://x.com/' + username + '/media');
4639
})()
4740
4841
- navigate:
49-
url: ${{ data }}
42+
url: "${{ data }}"
5043
settleMs: 5000
5144

45+
# Step 2: Scroll and extract media
5246
- evaluate: |
5347
(async () => {
54-
const tweetUrl = ${{ args['tweet-url'] | json }};
55-
const limit = ${{ args.limit }};
48+
const tweetUrl = args['tweet-url'] || '';
49+
const limit = args.limit || 10;
5650
57-
// Scroll to load more content
51+
// Scroll to load more
5852
if (!tweetUrl) {
5953
for (let i = 0; i < Math.ceil(limit / 5); i++) {
6054
window.scrollTo(0, document.body.scrollHeight);
@@ -68,45 +62,37 @@ pipeline:
6862
document.querySelectorAll('img[src*="pbs.twimg.com/media"]').forEach(img => {
6963
let src = img.src || '';
7064
src = src.replace(/&name=\w+$/, '&name=large');
71-
if (!src.includes('&name=')) {
72-
src = src + '&name=large';
73-
}
65+
if (!src.includes('&name=')) src += '&name=large';
7466
media.push({ type: 'image', url: src });
7567
});
7668
7769
// Find videos
7870
document.querySelectorAll('video').forEach(video => {
7971
const src = video.src || '';
80-
if (src) {
81-
media.push({ type: 'video', url: src, poster: video.poster || '' });
82-
}
72+
if (src) media.push({ type: 'video', url: src });
8373
});
8474
8575
// Find video tweets (for yt-dlp)
8676
document.querySelectorAll('[data-testid="videoPlayer"]').forEach(player => {
8777
const tweetLink = player.closest('article')?.querySelector('a[href*="/status/"]');
8878
const href = tweetLink?.getAttribute('href') || '';
89-
if (href) {
90-
media.push({ type: 'video-tweet', url: 'https://x.com' + href });
91-
}
79+
if (href) media.push({ type: 'video-tweet', url: 'https://x.com' + href });
9280
});
9381
94-
if (!media || media.length === 0) {
95-
return [{ index: 0, type: '-', status: 'failed', size: 'No media found' }];
96-
}
97-
9882
// Deduplicate
9983
const seen = new Set();
100-
const unique = media.filter(m => {
101-
if (seen.has(m.url)) return false;
102-
seen.add(m.url);
103-
return true;
104-
}).slice(0, limit);
84+
const unique = media.filter(m => { if (seen.has(m.url)) return false; seen.add(m.url); return true; }).slice(0, limit);
10585
106-
return unique.map((m, i) => ({
107-
index: i + 1,
108-
type: m.type,
109-
status: 'found',
110-
size: m.url,
111-
}));
86+
if (!unique.length) return [{ index: 0, type: '-', status: 'failed', size: 'No media found' }];
87+
88+
return {
89+
items: unique,
90+
output: args.output || './twitter-downloads',
91+
username: args.username || 'tweet',
92+
};
11293
})()
94+
95+
- download:
96+
type: twitter-media
97+
output: "${{ data.output }}"
98+
username: "${{ data.username }}"

0 commit comments

Comments
 (0)