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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,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` |
| **hupu** | `hot` `search` `detail` `mentions` `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
2 changes: 1 addition & 1 deletion README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,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` | 浏览器 |
| **hupu** | `hot` `search` `detail` `mentions` `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
194 changes: 194 additions & 0 deletions clis/hupu/mentions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
import { AuthRequiredError, CommandExecutionError } from '../../errors.js';
import { cli, Strategy } from '../../registry.js';
import { stripHtml } from './utils.js';

interface MentionItem {
id?: number;
msgType?: number;
puid?: number;
username?: string;
headerUrl?: string;
postContent?: string;
threadTitle?: string;
tid?: number;
pid?: number;
topicId?: number;
quoteContent?: string;
publishTime?: string;
updateTime?: number;
}

interface BrowserMentionResult {
ok?: boolean;
status?: number;
data?: {
items: MentionItem[];
hasNextPage: boolean;
pageStr?: string;
};
error?: string;
}

cli({
site: 'hupu',
name: 'mentions',
aliases: ['mention'],
description: '查看虎扑提到我的回复 (需要登录)',
domain: 'my.hupu.com',
strategy: Strategy.COOKIE,
navigateBefore: false,
args: [
{
name: 'limit',
type: 'int',
default: 20,
help: '最多返回多少条消息'
},
{
name: 'max_pages',
type: 'int',
default: 3,
help: '最多抓取多少页'
},
{
name: 'page_str',
help: '分页游标;不传时从第一页开始'
}
],
columns: ['time', 'username', 'thread_title', 'post_content', 'quote_content', 'reply_url'],
func: async (page, kwargs) => {
const plate = '1';
const limit = Math.max(1, Math.min(Number(kwargs.limit) || 20, 100));
const maxPages = Math.max(1, Math.min(Number(kwargs.max_pages) || 3, 10));
const inputPageStr = kwargs.page_str ? String(kwargs.page_str) : '';
const referer = `https://my.hupu.com/message?tabKey=${plate}`;

await page.goto(referer);

const result = await page.evaluate(`
(async () => {
const plate = ${JSON.stringify(plate)};
const limit = ${JSON.stringify(limit)};
const maxPages = ${JSON.stringify(maxPages)};
let pageStr = ${JSON.stringify(inputPageStr)};
const apiBase = 'https://my.hupu.com/pcmapi/pc/space/v1/getMentionedRemindList';
const items = [];
let hasNextPage = false;
let nextPageStr = pageStr || '';

const parseJson = async (response) => {
const text = await response.text();
try {
return text ? JSON.parse(text) : {};
} catch {
return {
message: text || 'invalid json response'
};
}
};

try {
for (let pageIndex = 0; pageIndex < maxPages && items.length < limit; pageIndex++) {
const url = new URL(apiBase);
url.searchParams.set('plate', plate);
if (pageStr) {
url.searchParams.set('pageStr', pageStr);
}

const response = await fetch(url.toString(), {
method: 'GET',
credentials: 'include'
});

const api = await parseJson(response);

if (response.status === 401 || response.status === 403) {
return {
ok: false,
status: response.status,
error: 'please log in to Hupu first'
};
}

if (!response.ok) {
return {
ok: false,
status: response.status,
error: api?.msg || api?.message || ('HTTP ' + response.status)
};
}

if ((api?.code ?? 0) > 1) {
return {
ok: false,
status: response.status,
error: api?.msg || api?.message || ('API error code=' + api?.code)
};
}

const data = api?.data || {};
const currentItems = Array.isArray(data.hisList) ? data.hisList : [];
items.push(...currentItems);

hasNextPage = Boolean(data.hasNextPage);
nextPageStr = typeof data.pageStr === 'string' ? data.pageStr : '';

if (!hasNextPage || !nextPageStr || nextPageStr === pageStr) {
break;
}

pageStr = nextPageStr;
}

return {
ok: true,
data: {
items: items.slice(0, limit),
hasNextPage,
pageStr: nextPageStr
}
};
} catch (error) {
return {
ok: false,
error: error instanceof Error ? error.message : String(error)
};
}
})()
`) as BrowserMentionResult;

if (!result || typeof result !== 'object') {
throw new CommandExecutionError('Read Hupu mentions failed: invalid browser response');
}

if (result.status === 401 || result.status === 403) {
throw new AuthRequiredError('my.hupu.com', 'Read Hupu mentions failed: please log in to Hupu first');
}

if (!result.ok) {
throw new CommandExecutionError(`Read Hupu mentions failed: ${result.error || 'unknown error'}`);
}

const items = result.data?.items || [];
return items.map((item) => {
const tid = item.tid ? String(item.tid) : '';
const pid = item.pid ? String(item.pid) : '';

return {
time: item.publishTime || '',
username: item.username || '',
thread_title: item.threadTitle || '',
post_content: stripHtml(item.postContent || ''),
quote_content: stripHtml(item.quoteContent || ''),
url: tid ? `https://bbs.hupu.com/${tid}.html` : '',
reply_url: tid && pid ? `https://bbs.hupu.com/${tid}.html?pid=${pid}` : '',
tid,
pid,
topic_id: item.topicId ? String(item.topicId) : '',
msg_type: item.msgType ?? '',
has_next_page: result.data?.hasNextPage ?? false,
next_page_str: result.data?.pageStr || ''
};
});
},
});
9 changes: 7 additions & 2 deletions docs/adapters/browser/hupu.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
| `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 mentions` | Read replies that mentioned you |
| `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 |
Expand All @@ -25,6 +26,9 @@ opencli hupu search 湖人 --limit 10
# Read one thread and include hot replies
opencli hupu detail 638234927 --replies true

# Read mentions that replied to you
opencli hupu mentions --limit 20

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

Expand All @@ -43,11 +47,12 @@ opencli hupu detail 638234927 -f json

- `reply --topic_id` maps to Hupu's API `topicId`, for example `502` for Basketball News
- `reply --quote_id` is the quoted reply `pid`
- `mentions` reads `my.hupu.com` notification APIs from the logged-in browser session
- `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`
- Chrome running and able to open `bbs.hupu.com` and `my.hupu.com`
- [Browser Bridge extension](/guide/browser-bridge) installed
- For `reply`, `like`, and `unlike`, a valid Hupu login session in Chrome is required
- For `mentions`, `reply`, `like`, and `unlike`, a valid Hupu login session in Chrome is required
2 changes: 1 addition & 1 deletion docs/adapters/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +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` | 🌐 / 🔐 |
| **[hupu](./browser/hupu)** | `hot` `search` `detail` `mentions` `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.

Loading