Skip to content

Commit f99a097

Browse files
feat(hupu): add Hupu adapter (jackwener#751)
* feat(hupu): add hupu cli adapter * fix(hupu): prevent detail from returning the wrong thread * refactor: deduplicate shared utilities in hupu adapter - Merge postHupuJson and postHupuReplyJson into single function with mode parameter - Move stripHtml and decodeHtmlEntities to utils.ts, remove duplicate definitions --------- Co-authored-by: jackwener <jakevingoo@gmail.com>
1 parent fe29188 commit f99a097

File tree

12 files changed

+929
-10
lines changed

12 files changed

+929
-10
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ git clone git@github.com:jackwener/opencli.git && cd opencli && npm install && n
126126
| **xiaohongshu** | `search` `note` `comments` `feed` `user` `download` `publish` `notifications` `creator-notes` `creator-notes-summary` `creator-note-detail` `creator-profile` `creator-stats` |
127127
| **bilibili** | `hot` `search` `history` `feed` `ranking` `download` `comments` `dynamic` `favorite` `following` `me` `subtitle` `user-videos` |
128128
| **tieba** | `hot` `posts` `search` `read` |
129+
| **hupu** | `hot` `search` `detail` `reply` `like` `unlike` |
129130
| **twitter** | `trending` `search` `timeline` `bookmarks` `post` `download` `profile` `article` `like` `likes` `notifications` `reply` `reply-dm` `thread` `follow` `unfollow` `followers` `following` `block` `unblock` `bookmark` `unbookmark` `delete` `hide-reply` `accept` |
130131
| **reddit** | `hot` `frontpage` `popular` `search` `subreddit` `user` `user-posts` `user-comments` `read` `save` `saved` `subscribe` `upvote` `upvoted` `comment` |
131132
| **amazon** | `bestsellers` `search` `product` `offer` `discussion` |

README.zh-CN.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ npx skills add jackwener/opencli --skill opencli-oneshot # 快速命令参
129129
| **twitter** | `trending` `bookmarks` `profile` `search` `timeline` `thread` `following` `followers` `notifications` `post` `reply` `delete` `like` `article` `follow` `unfollow` `bookmark` `unbookmark` `download` `accept` `reply-dm` `block` `unblock` `hide-reply` | 浏览器 |
130130
| **reddit** | `hot` `frontpage` `popular` `search` `subreddit` `read` `user` `user-posts` `user-comments` `upvote` `save` `comment` `subscribe` `saved` `upvoted` | 浏览器 |
131131
| **tieba** | `hot` `posts` `search` `read` | 浏览器 |
132+
| **hupu** | `hot` `search` `detail` `reply` `like` `unlike` | 浏览器 |
132133
| **cursor** | `status` `send` `read` `new` `dump` `composer` `model` `extract-code` `ask` `screenshot` `history` `export` | 桌面端 |
133134
| **bilibili** | `hot` `search` `me` `favorite` `history` `feed` `subtitle` `dynamic` `ranking` `following` `user-videos` `download` | 浏览器 |
134135
| **codex** | `status` `send` `read` `new` `dump` `extract-diff` `model` `ask` `screenshot` `history` `export` | 桌面端 |

docs/adapters/browser/hupu.md

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# Hupu (虎扑)
2+
3+
**Mode**: 🌐 Public / 🔐 Browser · **Domain**: `bbs.hupu.com`
4+
5+
## Commands
6+
7+
| Command | Description |
8+
|---------|-------------|
9+
| `opencli hupu hot` | Read Hupu hot threads |
10+
| `opencli hupu search <keyword>` | Search Hupu threads by keyword |
11+
| `opencli hupu detail <tid>` | Read one thread and optional hot replies |
12+
| `opencli hupu reply <tid> <text>` | Reply to a thread or quote one reply |
13+
| `opencli hupu like <tid> <pid>` | Like one reply |
14+
| `opencli hupu unlike <tid> <pid>` | Cancel like on one reply |
15+
16+
## Usage Examples
17+
18+
```bash
19+
# Hot threads
20+
opencli hupu hot --limit 5
21+
22+
# Search threads
23+
opencli hupu search 湖人 --limit 10
24+
25+
# Read one thread and include hot replies
26+
opencli hupu detail 638234927 --replies true
27+
28+
# Reply to the thread
29+
opencli hupu reply 638234927 "hello from opencli" --topic_id 502
30+
31+
# Quote one hot reply by pid
32+
opencli hupu reply 638234927 "replying to this comment" --topic_id 502 --quote_id 174908
33+
34+
# Like / unlike one reply
35+
opencli hupu like 638234927 174908 --fid 4860
36+
opencli hupu unlike 638234927 174908 --fid 4860
37+
38+
# JSON output
39+
opencli hupu detail 638234927 -f json
40+
```
41+
42+
## Notes
43+
44+
- `reply --topic_id` maps to Hupu's API `topicId`, for example `502` for Basketball News
45+
- `reply --quote_id` is the quoted reply `pid`
46+
- `like` / `unlike --fid` uses the forum ID from thread metadata
47+
- `detail --replies true` appends top hot replies to the content field
48+
49+
## Prerequisites
50+
51+
- Chrome running and able to open `bbs.hupu.com`
52+
- [Browser Bridge extension](/guide/browser-bridge) installed
53+
- For `reply`, `like`, and `unlike`, a valid Hupu login session in Chrome is required

docs/adapters/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ Run `opencli list` for the live registry.
99
| **[twitter](./browser/twitter)** | `trending` `bookmarks` `profile` `search` `timeline` `thread` `following` `followers` `notifications` `post` `reply` `delete` `like` `likes` `article` `follow` `unfollow` `bookmark` `unbookmark` `download` `accept` `reply-dm` `block` `unblock` `hide-reply` | 🔐 Browser |
1010
| **[reddit](./browser/reddit)** | `hot` `frontpage` `popular` `search` `subreddit` `read` `user` `user-posts` `user-comments` `upvote` `save` `comment` `subscribe` `saved` `upvoted` | 🔐 Browser |
1111
| **[tieba](./browser/tieba)** | `hot` `posts` `search` `read` | 🔐 Browser |
12+
| **[hupu](./browser/hupu)** | `hot` `search` `detail` `reply` `like` `unlike` | 🌐 / 🔐 |
1213
| **[bilibili](./browser/bilibili)** | `hot` `search` `me` `favorite` `history` `feed` `subtitle` `dynamic` `ranking` `following` `user-videos` `download` | 🔐 Browser |
1314
| **[zhihu](./browser/zhihu)** | `hot` `search` `question` `download` | 🔐 Browser |
1415
| **[xiaohongshu](./browser/xiaohongshu)** | `search` `notifications` `feed` `user` `note` `comments` `download` `publish` `creator-notes` `creator-note-detail` `creator-notes-summary` `creator-profile` `creator-stats` | 🔐 Browser |

package-lock.json

Lines changed: 0 additions & 10 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/clis/hupu/detail.ts

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
import { cli, Strategy } from '../../registry.js';
2+
import { getHupuThreadUrl, readHupuNextData, stripHtml } from './utils.js';
3+
4+
// JSON数据结构(对应Next.js的__NEXT_DATA__)
5+
interface NextData {
6+
props: Props;
7+
}
8+
9+
interface Props {
10+
pageProps: PageProps;
11+
}
12+
13+
interface PageProps {
14+
detail?: Detail;
15+
detail_error_info?: DetailError;
16+
}
17+
18+
interface DetailError {
19+
code: number;
20+
message: string;
21+
}
22+
23+
interface Detail {
24+
thread?: Thread;
25+
lights?: ReplyData[];
26+
}
27+
28+
interface Thread {
29+
tid: string;
30+
title: string;
31+
content: string;
32+
lights?: number;
33+
replies?: number;
34+
author?: Author;
35+
}
36+
37+
interface Author {
38+
puname?: string;
39+
}
40+
41+
interface ReplyData {
42+
pid: string;
43+
author?: Author;
44+
content: string;
45+
allLightCount?: number; // 修复:正确的字段名
46+
created_at_format?: string;
47+
}
48+
49+
cli({
50+
site: 'hupu',
51+
name: 'detail',
52+
description: '获取虎扑帖子详情 (使用Next.js JSON数据)',
53+
domain: 'bbs.hupu.com',
54+
strategy: Strategy.PUBLIC,
55+
browser: true,
56+
navigateBefore: false,
57+
args: [
58+
{
59+
name: 'tid',
60+
required: true,
61+
positional: true,
62+
help: '帖子ID(9位数字)'
63+
},
64+
{
65+
name: 'replies',
66+
type: 'boolean',
67+
default: false,
68+
help: '是否包含热门回复'
69+
}
70+
],
71+
columns: ['title', 'author', 'content', 'replies', 'lights', 'url'],
72+
func: async (page, kwargs) => {
73+
const { tid, replies: includeReplies = false } = kwargs;
74+
75+
const url = getHupuThreadUrl(tid).replace(/-1\.html$/, '.html');
76+
const data = await readHupuNextData<NextData>(page, url, 'Read Hupu thread detail', {
77+
expectedTid: String(tid),
78+
});
79+
80+
// 检查错误信息(只有当code不是200时才报错)
81+
const errorInfo = data.props.pageProps.detail_error_info;
82+
if (errorInfo && errorInfo.code !== 200) {
83+
throw new Error(`帖子访问失败: ${errorInfo.message} (code: ${errorInfo.code})`);
84+
}
85+
86+
// 获取帖子信息
87+
const thread = data.props.pageProps.detail?.thread;
88+
if (!thread) {
89+
throw new Error('帖子不存在或已被删除');
90+
}
91+
92+
const authorName = thread.author?.puname || '未知作者';
93+
const content = stripHtml(thread.content);
94+
const contentPreview = content.length > 300 ? content.substring(0, 300) + '...' : content;
95+
96+
// 构建结果
97+
const result: any = {
98+
title: thread.title,
99+
author: authorName,
100+
content: contentPreview,
101+
replies: thread.replies || 0,
102+
lights: thread.lights || 0,
103+
url: `https://bbs.hupu.com/${tid}.html`
104+
};
105+
106+
// 如果需要包含回复,添加回复信息到内容中
107+
if (includeReplies) {
108+
const replyList = data.props.pageProps.detail?.lights || [];
109+
const topReplies = replyList.slice(0, 3);
110+
111+
if (topReplies.length > 0) {
112+
let replyText = '\n\n【热门回复】\n';
113+
topReplies.forEach((reply, index) => {
114+
const userName = reply.author?.puname || '未知用户';
115+
const replyContent = stripHtml(reply.content).substring(0, 100);
116+
const replyLights = reply.allLightCount || 0; // 修复:使用正确的字段名
117+
const replyTime = reply.created_at_format || '未知时间';
118+
replyText += `${index + 1}. ${userName} (亮${replyLights} ${replyTime}):\n ${replyContent}\n\n`;
119+
});
120+
result.content = contentPreview + replyText;
121+
}
122+
}
123+
124+
return [result];
125+
},
126+
});

src/clis/hupu/hot.yaml

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
site: hupu
2+
name: hot
3+
description: 虎扑热门帖子
4+
domain: bbs.hupu.com
5+
6+
args:
7+
limit:
8+
type: int
9+
default: 20
10+
description: Number of hot posts
11+
12+
pipeline:
13+
- navigate: https://bbs.hupu.com/
14+
15+
- evaluate: |
16+
(async () => {
17+
// 从HTML中提取帖子信息(适配新的HTML结构)
18+
const html = document.documentElement.outerHTML;
19+
const posts = [];
20+
21+
// 匹配当前虎扑页面结构的正则表达式
22+
// 结构: <a href="/638249612.html"...><span class="t-title">标题</span></a>
23+
const regex = /<a[^>]*href="\/(\d{9})\.html"[^>]*><span[^>]*class="t-title"[^>]*>([^<]+)<\/span><\/a>/g;
24+
let match;
25+
26+
while ((match = regex.exec(html)) !== null && posts.length < ${{ args.limit }}) {
27+
posts.push({
28+
tid: match[1],
29+
title: match[2].trim()
30+
});
31+
}
32+
33+
return posts;
34+
})()
35+
36+
- map:
37+
rank: ${{ index + 1 }}
38+
title: ${{ item.title }}
39+
url: https://bbs.hupu.com/${{ item.tid }}.html
40+
41+
- limit: ${{ args.limit }}
42+
43+
columns: [rank, title, url]

0 commit comments

Comments
 (0)