Skip to content

Commit e640462

Browse files
tiaot33claudejackwener
authored
feat(linux-do): refactor adapters with unified feed, tags, user commands (#434)
* feat(linux-do): refactor adapters with unified feed, tags, user commands - Replace hot/latest/category with unified `feed` command (tag/category/view routing) - Add `tags`, `user-topics`, `user-posts` commands - Add static data files for categories and tags lookup - Fix error handling: use CliError subclasses instead of raw Error - Fix Discourse API field mapping in search (tags, created) - Add strategy: cookie to all YAML adapters - Update docs and README command listings - Update E2E tests for new command signatures Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * review: resolve linux-do feed from live metadata * fix: restore linux-do CI * fix: harden linux-do compatibility * refactor: stabilize linux-do command migration --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: jackwener <jakevingoo@gmail.com>
1 parent ed706f6 commit e640462

25 files changed

Lines changed: 1215 additions & 187 deletions

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ Run `opencli list` for the live registry.
164164
| **jike** | `feed` `search` `create` `like` `comment` `repost` `notifications` `post` `topic` `user` | Browser |
165165
| **jimeng** | `generate` `history` | Browser |
166166
| **yollomi** | `generate` `video` `edit` `upload` `models` `remove-bg` `upscale` `face-swap` `restore` `try-on` `background` `object-remover` | Browser |
167-
| **linux-do** | `hot` `latest` `search` `categories` `category` `topic` | Public |
167+
| **linux-do** | `feed` `categories` `tags` `search` `topic` `user-topics` `user-posts` | Browser |
168168
| **stackoverflow** | `hot` `search` `bounties` `unanswered` | Public |
169169
| **steam** | `top-sellers` | Public |
170170
| **weread** | `shelf` `search` `book` `highlights` `notes` `notebooks` `ranking` | Browser |

README.zh-CN.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ npm install -g @jackwener/opencli@latest
166166
| **jike** | `feed` `search` `create` `like` `comment` `repost` `notifications` `post` `topic` `user` | 浏览器 |
167167
| **jimeng** | `generate` `history` | 浏览器 |
168168
| **yollomi** | `generate` `video` `edit` `upload` `models` `remove-bg` `upscale` `face-swap` `restore` `try-on` `background` `object-remover` | 浏览器 |
169-
| **linux-do** | `hot` `latest` `search` `categories` `category` `topic` | 公开 |
169+
| **linux-do** | `feed` `categories` `tags` `search` `topic` `user-topics` `user-posts` | 浏览器 |
170170
| **stackoverflow** | `hot` `search` `bounties` `unanswered` | 公开 |
171171
| **steam** | `top-sellers` | 公开 |
172172
| **weread** | `shelf` `search` `book` `highlights` `notes` `notebooks` `ranking` | 浏览器 |

docs/adapters/browser/linux-do.md

Lines changed: 181 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,37 +6,198 @@
66

77
| Command | Description |
88
|---------|-------------|
9-
| `opencli linux-do hot` | 热门话题 |
10-
| `opencli linux-do latest` | 最新话题 |
11-
| `opencli linux-do categories` | 板块列表 |
12-
| `opencli linux-do category` | 板块话题 |
13-
| `opencli linux-do search` | 搜索话题 |
14-
| `opencli linux-do topic` | 话题详情 |
9+
| `opencli linux-do feed` | Browse topics (site-wide, by tag, or by category) |
10+
| `opencli linux-do categories` | List all categories |
11+
| `opencli linux-do tags` | List popular tags |
12+
| `opencli linux-do search <query>` | Search topics |
13+
| `opencli linux-do topic <id>` | View topic posts |
14+
| `opencli linux-do user-topics <username>` | Topics created by a user |
15+
| `opencli linux-do user-posts <username>` | Replies posted by a user |
1516

16-
## Usage Examples
17+
## feed
18+
19+
Browse topic listings. Defaults to latest topics when called with no arguments.
20+
21+
- Supports filtering by `--tag`, `--category`, or both
22+
- `--tag` accepts tag name, slug, or ID
23+
- `--category` accepts category name, slug, ID, or `Parent / Child` path for sub-categories
24+
- Use `--view` to switch between latest / hot / top
25+
26+
### Basic
1727

1828
```bash
19-
# Hot topics this week
20-
opencli linux-do hot --limit 20
29+
# Latest topics (default)
30+
opencli linux-do feed
31+
32+
# Hot topics
33+
opencli linux-do feed --view hot
34+
35+
# Top topics — default period is weekly
36+
opencli linux-do feed --view top
37+
opencli linux-do feed --view top --period daily
38+
opencli linux-do feed --view top --period monthly
2139

22-
# Hot topics by period
23-
opencli linux-do hot --period daily
24-
opencli linux-do hot --period monthly
40+
# Sort by views descending
41+
opencli linux-do feed --order views
42+
43+
# Sort by created time ascending
44+
opencli linux-do feed --order created --ascending
45+
46+
# Limit results
47+
opencli linux-do feed --limit 10
48+
49+
# JSON output
50+
opencli linux-do feed -f json
51+
```
2552

26-
# Latest topics
27-
opencli linux-do latest --limit 10
53+
### Filter by tag
54+
55+
```bash
56+
# By tag name, slug, or ID — all equivalent
57+
opencli linux-do feed --tag "ChatGPT"
58+
opencli linux-do feed --tag chatgpt
59+
opencli linux-do feed --tag 3
60+
61+
# Tag + hot view
62+
opencli linux-do feed --tag "ChatGPT" --view hot
63+
64+
# Tag + top view with period
65+
opencli linux-do feed --tag "OpenAI" --view top --period monthly
66+
```
67+
68+
### Filter by category
69+
70+
Supports both top-level and sub-categories. Sub-categories auto-resolve their parent path.
71+
72+
```bash
73+
# Top-level category — name, slug, or ID
74+
opencli linux-do feed --category "开发调优"
75+
opencli linux-do feed --category develop
76+
opencli linux-do feed --category 4
2877

29-
# List all categories
78+
# Sub-category
79+
opencli linux-do feed --category "开发调优 / Lv1"
80+
opencli linux-do feed --category "网盘资源"
81+
82+
# Category + hot / top view
83+
opencli linux-do feed --category "开发调优" --view hot
84+
opencli linux-do feed --category "开发调优" --view top --period weekly
85+
```
86+
87+
### Category + tag
88+
89+
Combine `--category` and `--tag` to narrow results within a category.
90+
91+
```bash
92+
opencli linux-do feed --category "开发调优" --tag "ChatGPT"
93+
opencli linux-do feed --category "网盘资源" --tag "OpenAI"
94+
opencli linux-do feed --category 94 --tag 4 --view top --period monthly
95+
```
96+
97+
### Parameters
98+
99+
| Parameter | Description | Default |
100+
|-----------|-------------|---------|
101+
| `--view V` | `latest`, `hot`, `top` | `latest` |
102+
| `--tag VALUE` | Tag name, slug, or ID ||
103+
| `--category VALUE` | Category name, slug, or ID ||
104+
| `--limit N` | Number of results | `20` |
105+
| `--order O` | `default`, `created`, `activity`, `views`, `posts`, `category`, `likes`, `op_likes`, `posters` | `default` |
106+
| `--ascending` | Sort ascending instead of descending | off |
107+
| `--period P` | `all`, `daily`, `weekly`, `monthly`, `quarterly`, `yearly` (only with `--view top`) | `weekly` |
108+
109+
Output columns: `title`, `replies`, `created`, `likes`, `views`, `url`
110+
111+
## categories
112+
113+
List forum categories with optional sub-category expansion.
114+
115+
```bash
30116
opencli linux-do categories
117+
opencli linux-do categories --subcategories
118+
opencli linux-do categories --limit 50
119+
```
120+
121+
When `--subcategories` is enabled, sub-categories are rendered as `Parent / Child` so the `name` value can be copied directly into `opencli linux-do feed --category ...`.
122+
123+
Output columns: `name`, `slug`, `id`, `topics`, `description`
124+
125+
## tags
31126

32-
# Search topics
127+
List tags sorted by usage count.
128+
129+
```bash
130+
opencli linux-do tags
131+
opencli linux-do tags --limit 50
132+
```
133+
134+
Output columns: `rank`, `name`, `count`, `url`
135+
136+
## search
137+
138+
Search topics by keyword.
139+
140+
```bash
33141
opencli linux-do search "NixOS"
142+
opencli linux-do search "Docker" --limit 10
143+
opencli linux-do search "Claude" -f json
144+
```
34145

35-
# View topic details
36-
opencli linux-do topic 12345
146+
Output columns: `rank`, `title`, `views`, `likes`, `replies`, `url`
37147

38-
# JSON output
39-
opencli linux-do hot -f json
148+
## topic
149+
150+
View posts within a topic (first page).
151+
152+
```bash
153+
opencli linux-do topic 1234
154+
opencli linux-do topic 1234 --limit 50
155+
opencli linux-do topic 1234 --main_only -f json | jq -r '.[0].content'
156+
```
157+
158+
Notes:
159+
- `--main_only` returns only the main post row and keeps the body untruncated
160+
161+
Output columns: `author`, `content`, `likes`, `created_at`
162+
163+
## user-topics
164+
165+
List topics created by a user.
166+
167+
```bash
168+
opencli linux-do user-topics neo
169+
opencli linux-do user-topics neo --limit 10
170+
```
171+
172+
Output columns: `rank`, `title`, `replies`, `created_at`, `likes`, `views`, `url`
173+
174+
## user-posts
175+
176+
List replies posted by a user.
177+
178+
```bash
179+
opencli linux-do user-posts neo
180+
opencli linux-do user-posts neo --limit 10
181+
```
182+
183+
Output columns: `index`, `topic_user`, `topic`, `reply`, `time`, `url`
184+
185+
## Compatibility
186+
187+
The legacy commands below are still available as compatibility wrappers while `feed` becomes the canonical entrypoint:
188+
189+
```bash
190+
opencli linux-do latest
191+
opencli linux-do hot --period weekly
192+
opencli linux-do category develop 4
193+
```
194+
195+
Preferred modern forms:
196+
197+
```bash
198+
opencli linux-do feed --view latest
199+
opencli linux-do feed --view top --period weekly
200+
opencli linux-do feed --category 4
40201
```
41202

42203
## Prerequisites

docs/adapters/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ Run `opencli list` for the live registry.
2525
| **[jike](/adapters/browser/jike)** | `feed` `search` `post` `topic` `user` `create` `comment` `like` `repost` `notifications` | 🔐 Browser |
2626
| **[jimeng](/adapters/browser/jimeng)** | `generate` `history` | 🔐 Browser |
2727
| **[yollomi](/adapters/browser/yollomi)** | `generate` `video` `edit` `upload` `models` `remove-bg` `upscale` `face-swap` `restore` `try-on` `background` `object-remover` | 🔐 Browser |
28-
| **[linux-do](/adapters/browser/linux-do)** | `hot` `latest` `categories` `category` `search` `topic` | 🔐 Browser |
28+
| **[linux-do](/adapters/browser/linux-do)** | `feed` `categories` `tags` `search` `topic` `user-topics` `user-posts` | 🔐 Browser |
2929
| **[chaoxing](/adapters/browser/chaoxing)** | `assignments` `exams` | 🔐 Browser |
3030
| **[grok](/adapters/browser/grok)** | `ask` | 🔐 Browser |
3131
| **[doubao](/adapters/browser/doubao)** | `status` `new` `send` `read` `ask` | 🔐 Browser |

src/build-manifest.test.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,4 +143,27 @@ describe('manifest helper rules', () => {
143143
modulePath: 'xueqiu/fund-holdings.js',
144144
});
145145
});
146+
147+
it('captures deprecated metadata for TS adapters', () => {
148+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'opencli-manifest-'));
149+
tempDirs.push(dir);
150+
const file = path.join(dir, 'legacy.ts');
151+
fs.writeFileSync(file, `
152+
import { cli } from '../../registry.js';
153+
cli({
154+
site: 'demo',
155+
name: 'legacy',
156+
description: 'legacy command',
157+
deprecated: 'legacy is deprecated',
158+
replacedBy: 'opencli demo new',
159+
});
160+
`);
161+
162+
expect(scanTs(file, 'demo')).toMatchObject({
163+
site: 'demo',
164+
name: 'legacy',
165+
deprecated: 'legacy is deprecated',
166+
replacedBy: 'opencli demo new',
167+
});
168+
});
146169
});

src/build-manifest.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ export interface ManifestEntry {
3838
columns?: string[];
3939
pipeline?: Record<string, unknown>[];
4040
timeout?: number;
41+
deprecated?: boolean | string;
42+
replacedBy?: string;
4143
/** 'yaml' or 'ts' — determines how executeCommand loads the handler */
4244
type: 'yaml' | 'ts';
4345
/** Relative path from clis/ dir, e.g. 'bilibili/hot.yaml' or 'bilibili/search.js' */
@@ -199,6 +201,8 @@ function scanYaml(filePath: string, site: string): ManifestEntry | null {
199201
columns: cliDef.columns,
200202
pipeline: cliDef.pipeline,
201203
timeout: cliDef.timeout,
204+
deprecated: (cliDef as Record<string, unknown>).deprecated as boolean | string | undefined,
205+
replacedBy: (cliDef as Record<string, unknown>).replacedBy as string | undefined,
202206
type: 'yaml',
203207
navigateBefore: cliDef.navigateBefore,
204208
};
@@ -269,6 +273,17 @@ export function scanTs(filePath: string, site: string): ManifestEntry | null {
269273
if (navStringMatch) entry.navigateBefore = navStringMatch[1];
270274
}
271275

276+
const deprecatedBoolMatch = src.match(/deprecated\s*:\s*(true|false)/);
277+
if (deprecatedBoolMatch) {
278+
entry.deprecated = deprecatedBoolMatch[1] === 'true';
279+
} else {
280+
const deprecatedStringMatch = src.match(/deprecated\s*:\s*['"`]([^'"`]+)['"`]/);
281+
if (deprecatedStringMatch) entry.deprecated = deprecatedStringMatch[1];
282+
}
283+
284+
const replacedByMatch = src.match(/replacedBy\s*:\s*['"`]([^'"`]+)['"`]/);
285+
if (replacedByMatch) entry.replacedBy = replacedByMatch[1];
286+
272287
return entry;
273288
} catch (err) {
274289
// If parsing fails, log a warning (matching scanYaml behaviour) and skip the entry.

src/clis/linux-do/categories.yaml

Lines changed: 38 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,14 @@ site: linux-do
22
name: categories
33
description: linux.do 分类列表
44
domain: linux.do
5+
strategy: cookie
56
browser: true
67

78
args:
9+
subcategories:
10+
type: boolean
11+
default: false
12+
description: Include subcategories
813
limit:
914
type: int
1015
default: 20
@@ -20,13 +25,39 @@ pipeline:
2025
let data;
2126
try { data = await res.json(); } catch { throw new Error('响应不是有效 JSON - 请先登录 linux.do'); }
2227
const cats = data?.category_list?.categories || [];
23-
return cats.slice(0, ${{ args.limit }}).map(c => ({
24-
name: c.name,
25-
slug: c.slug,
26-
id: c.id,
27-
topics: c.topic_count,
28-
description: (c.description_text || '').slice(0, 80),
29-
}));
28+
const showSub = ${{ args.subcategories }};
29+
const results = [];
30+
const limit = ${{ args.limit }};
31+
for (const c of cats.slice(0, ${{ args.limit }})) {
32+
results.push({
33+
name: c.name,
34+
slug: c.slug,
35+
id: c.id,
36+
topics: c.topic_count,
37+
description: (c.description_text || '').slice(0, 80),
38+
});
39+
if (results.length >= limit) break;
40+
if (showSub && c.subcategory_ids && c.subcategory_ids.length > 0) {
41+
const subRes = await fetch('/categories.json?parent_category_id=' + c.id, { credentials: 'include' });
42+
if (subRes.ok) {
43+
let subData;
44+
try { subData = await subRes.json(); } catch { continue; }
45+
const subCats = subData?.category_list?.categories || [];
46+
for (const sc of subCats) {
47+
results.push({
48+
name: c.name + ' / ' + sc.name,
49+
slug: sc.slug,
50+
id: sc.id,
51+
topics: sc.topic_count,
52+
description: (sc.description_text || '').slice(0, 80),
53+
});
54+
if (results.length >= limit) break;
55+
}
56+
}
57+
}
58+
if (results.length >= limit) break;
59+
}
60+
return results;
3061
})()
3162
3263
- map:
@@ -36,6 +67,4 @@ pipeline:
3667
topics: ${{ item.topics }}
3768
description: ${{ item.description }}
3869

39-
- limit: ${{ args.limit }}
40-
4170
columns: [name, slug, id, topics, description]

0 commit comments

Comments
 (0)