Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ git clone git@github.com:jackwener/opencli.git && cd opencli && npm install && n
| **xiaohongshu** | `search` `note` `comments` `feed` `user` `download` `publish` `notifications` `creator-notes` `creator-notes-summary` `creator-note-detail` `creator-profile` `creator-stats` |
| **bilibili** | `hot` `search` `history` `feed` `ranking` `download` `comments` `dynamic` `favorite` `following` `me` `subtitle` `user-videos` |
| **tieba** | `hot` `posts` `search` `read` |
| **hupu** | `hot` `search` `detail` `reply` `like` `unlike` |
| **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` |
| **reddit** | `hot` `frontpage` `popular` `search` `subreddit` `user` `user-posts` `user-comments` `read` `save` `saved` `subscribe` `upvote` `upvoted` `comment` |
| **amazon** | `bestsellers` `search` `product` `offer` `discussion` |
Expand Down
1 change: 1 addition & 0 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ npx skills add jackwener/opencli --skill opencli-oneshot # 快速命令参
| **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` | 浏览器 |
| **reddit** | `hot` `frontpage` `popular` `search` `subreddit` `read` `user` `user-posts` `user-comments` `upvote` `save` `comment` `subscribe` `saved` `upvoted` | 浏览器 |
| **tieba** | `hot` `posts` `search` `read` | 浏览器 |
| **hupu** | `hot` `search` `detail` `reply` `like` `unlike` | 浏览器 |
| **cursor** | `status` `send` `read` `new` `dump` `composer` `model` `extract-code` `ask` `screenshot` `history` `export` | 桌面端 |
| **bilibili** | `hot` `search` `me` `favorite` `history` `feed` `subtitle` `dynamic` `ranking` `following` `user-videos` `download` | 浏览器 |
| **codex** | `status` `send` `read` `new` `dump` `extract-diff` `model` `ask` `screenshot` `history` `export` | 桌面端 |
Expand Down
53 changes: 53 additions & 0 deletions docs/adapters/browser/hupu.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Hupu (虎扑)

**Mode**: 🌐 Public / 🔐 Browser · **Domain**: `bbs.hupu.com`

## Commands

| Command | Description |
|---------|-------------|
| `opencli hupu hot` | Read Hupu hot threads |
| `opencli hupu search <keyword>` | Search Hupu threads by keyword |
| `opencli hupu detail <tid>` | Read one thread and optional hot replies |
| `opencli hupu reply <tid> <text>` | Reply to a thread or quote one reply |
| `opencli hupu like <tid> <pid>` | Like one reply |
| `opencli hupu unlike <tid> <pid>` | Cancel like on one reply |

## Usage Examples

```bash
# Hot threads
opencli hupu hot --limit 5

# Search threads
opencli hupu search 湖人 --limit 10

# Read one thread and include hot replies
opencli hupu detail 638234927 --replies true

# Reply to the thread
opencli hupu reply 638234927 "hello from opencli" --topic_id 502

# Quote one hot reply by pid
opencli hupu reply 638234927 "replying to this comment" --topic_id 502 --quote_id 174908

# Like / unlike one reply
opencli hupu like 638234927 174908 --fid 4860
opencli hupu unlike 638234927 174908 --fid 4860

# JSON output
opencli hupu detail 638234927 -f json
```

## Notes

- `reply --topic_id` maps to Hupu's API `topicId`, for example `502` for Basketball News
- `reply --quote_id` is the quoted reply `pid`
- `like` / `unlike --fid` uses the forum ID from thread metadata
- `detail --replies true` appends top hot replies to the content field

## Prerequisites

- Chrome running and able to open `bbs.hupu.com`
- [Browser Bridge extension](/guide/browser-bridge) installed
- For `reply`, `like`, and `unlike`, a valid Hupu login session in Chrome is required
1 change: 1 addition & 0 deletions docs/adapters/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ Run `opencli list` for the live registry.
| **[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 |
| **[reddit](./browser/reddit)** | `hot` `frontpage` `popular` `search` `subreddit` `read` `user` `user-posts` `user-comments` `upvote` `save` `comment` `subscribe` `saved` `upvoted` | 🔐 Browser |
| **[tieba](./browser/tieba)** | `hot` `posts` `search` `read` | 🔐 Browser |
| **[hupu](./browser/hupu)** | `hot` `search` `detail` `reply` `like` `unlike` | 🌐 / 🔐 |
| **[bilibili](./browser/bilibili)** | `hot` `search` `me` `favorite` `history` `feed` `subtitle` `dynamic` `ranking` `following` `user-videos` `download` | 🔐 Browser |
| **[zhihu](./browser/zhihu)** | `hot` `search` `question` `download` | 🔐 Browser |
| **[xiaohongshu](./browser/xiaohongshu)** | `search` `notifications` `feed` `user` `note` `comments` `download` `publish` `creator-notes` `creator-note-detail` `creator-notes-summary` `creator-profile` `creator-stats` | 🔐 Browser |
Expand Down
10 changes: 0 additions & 10 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

126 changes: 126 additions & 0 deletions src/clis/hupu/detail.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import { cli, Strategy } from '../../registry.js';
import { getHupuThreadUrl, readHupuNextData, stripHtml } from './utils.js';

// JSON数据结构(对应Next.js的__NEXT_DATA__)
interface NextData {
props: Props;
}

interface Props {
pageProps: PageProps;
}

interface PageProps {
detail?: Detail;
detail_error_info?: DetailError;
}

interface DetailError {
code: number;
message: string;
}

interface Detail {
thread?: Thread;
lights?: ReplyData[];
}

interface Thread {
tid: string;
title: string;
content: string;
lights?: number;
replies?: number;
author?: Author;
}

interface Author {
puname?: string;
}

interface ReplyData {
pid: string;
author?: Author;
content: string;
allLightCount?: number; // 修复:正确的字段名
created_at_format?: string;
}

cli({
site: 'hupu',
name: 'detail',
description: '获取虎扑帖子详情 (使用Next.js JSON数据)',
domain: 'bbs.hupu.com',
strategy: Strategy.PUBLIC,
browser: true,
navigateBefore: false,
args: [
{
name: 'tid',
required: true,
positional: true,
help: '帖子ID(9位数字)'
},
{
name: 'replies',
type: 'boolean',
default: false,
help: '是否包含热门回复'
}
],
columns: ['title', 'author', 'content', 'replies', 'lights', 'url'],
func: async (page, kwargs) => {
const { tid, replies: includeReplies = false } = kwargs;

const url = getHupuThreadUrl(tid).replace(/-1\.html$/, '.html');
const data = await readHupuNextData<NextData>(page, url, 'Read Hupu thread detail', {
expectedTid: String(tid),
});

// 检查错误信息(只有当code不是200时才报错)
const errorInfo = data.props.pageProps.detail_error_info;
if (errorInfo && errorInfo.code !== 200) {
throw new Error(`帖子访问失败: ${errorInfo.message} (code: ${errorInfo.code})`);
}

// 获取帖子信息
const thread = data.props.pageProps.detail?.thread;
if (!thread) {
throw new Error('帖子不存在或已被删除');
}

const authorName = thread.author?.puname || '未知作者';
const content = stripHtml(thread.content);
const contentPreview = content.length > 300 ? content.substring(0, 300) + '...' : content;

// 构建结果
const result: any = {
title: thread.title,
author: authorName,
content: contentPreview,
replies: thread.replies || 0,
lights: thread.lights || 0,
url: `https://bbs.hupu.com/${tid}.html`
};

// 如果需要包含回复,添加回复信息到内容中
if (includeReplies) {
const replyList = data.props.pageProps.detail?.lights || [];
const topReplies = replyList.slice(0, 3);

if (topReplies.length > 0) {
let replyText = '\n\n【热门回复】\n';
topReplies.forEach((reply, index) => {
const userName = reply.author?.puname || '未知用户';
const replyContent = stripHtml(reply.content).substring(0, 100);
const replyLights = reply.allLightCount || 0; // 修复:使用正确的字段名
const replyTime = reply.created_at_format || '未知时间';
replyText += `${index + 1}. ${userName} (亮${replyLights} ${replyTime}):\n ${replyContent}\n\n`;
});
result.content = contentPreview + replyText;
}
}

return [result];
},
});
43 changes: 43 additions & 0 deletions src/clis/hupu/hot.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
site: hupu
name: hot
description: 虎扑热门帖子
domain: bbs.hupu.com

args:
limit:
type: int
default: 20
description: Number of hot posts

pipeline:
- navigate: https://bbs.hupu.com/

- evaluate: |
(async () => {
// 从HTML中提取帖子信息(适配新的HTML结构)
const html = document.documentElement.outerHTML;
const posts = [];

// 匹配当前虎扑页面结构的正则表达式
// 结构: <a href="/638249612.html"...><span class="t-title">标题</span></a>
const regex = /<a[^>]*href="\/(\d{9})\.html"[^>]*><span[^>]*class="t-title"[^>]*>([^<]+)<\/span><\/a>/g;
let match;

while ((match = regex.exec(html)) !== null && posts.length < ${{ args.limit }}) {
posts.push({
tid: match[1],
title: match[2].trim()
});
}

return posts;
})()

- map:
rank: ${{ index + 1 }}
title: ${{ item.title }}
url: https://bbs.hupu.com/${{ item.tid }}.html

- limit: ${{ args.limit }}

columns: [rank, title, url]
Loading