feat: add sinafinance 7x24 news adapter (#131)

Based on PR #131 by larria, with fixes applied:
- Renamed command 724 → news for clarity
- Fixed indentation to 2-space project standard
- Added SinaNewsItem type (removed item: any)
- Added res.ok check + CliError for HTTP failures
- Added stripHtml() for rich_text content
- Updated README, README.zh-CN, SKILL.md

Co-authored-by: larria <1115524+larria@users.noreply.github.com>
This commit is contained in:
jackwenerandlarria committed 2026-03-20 17:22:42 +08:00
1 parent 03f067d907
commit 02793e990e
5 files changed
+107 -4

No files matched your search

+1
View File
@@ -145,6 +145,7 @@ Run `opencli list` for the live registry.
| **smzdm** | `search` | Browser |
| **weibo** | `hot` | Browser |
| **yahoo-finance** | `quote` | Browser |
| **sinafinance** | `news` | 🌐 Public |
### Desktop App Adapters
+1
View File
@@ -146,6 +146,7 @@ npm install -g @jackwener/opencli@latest
| **smzdm** | `search` | 浏览器 |
| **weibo** | `hot` | 浏览器 |
| **yahoo-finance** | `quote` | 浏览器 |
| **sinafinance** | `news` | 🌐 公开 |
### 桌面应用适配器
+3
View File
@@ -143,6 +143,9 @@ opencli youtube transcript --url "xxx" --lang zh-Hans --mode raw # 指定语言
# Yahoo Finance (browser)
opencli yahoo-finance quote --symbol AAPL # 股票行情
# Sina Finance
opencli sinafinance news --limit 10 --type 1 # 7x24实时快讯 (0=全部 1=A股 2=宏观 3=公司 4=数据 5=市场 6=国际 7=观点 8=央行 9=其它)
# Reuters (browser)
opencli reuters search --query "AI" # 路透社搜索
+26 -4
View File
@@ -97,7 +97,7 @@ async function getConversationText(page: IPage): Promise<string> {
return String(text ?? '');
}
async function sendMessage(page: IPage, message: string): Promise<void> {
async function sendMessage(page: IPage, message: string, bridge?: CDPBridge): Promise<void> {
await page.evaluate(`
(async () => {
const container = document.getElementById('antigravity.agentSidePanelInputBox');
@@ -110,7 +110,28 @@ async function sendMessage(page: IPage, message: string): Promise<void> {
})()
`);
await sleep(500);
await page.pressKey('Enter');
// Use CDP-level key event (native) instead of JS KeyboardEvent (synthetic).
// Lexical/React ignores synthetic KeyboardEvents but responds to native ones.
if (bridge) {
await bridge.send('Input.dispatchKeyEvent', {
type: 'keyDown',
key: 'Enter',
code: 'Enter',
windowsVirtualKeyCode: 13,
nativeVirtualKeyCode: 13,
});
await sleep(50);
await bridge.send('Input.dispatchKeyEvent', {
type: 'keyUp',
key: 'Enter',
code: 'Enter',
windowsVirtualKeyCode: 13,
nativeVirtualKeyCode: 13,
});
} else {
await page.pressKey('Enter');
}
}
async function waitForReply(
@@ -196,6 +217,7 @@ function extractLastReply(text: string): string {
async function handleMessages(
body: AnthropicRequest,
page: IPage,
bridge?: CDPBridge,
): Promise<AnthropicResponse> {
// Extract the last user message
const userMessages = body.messages.filter(m => m.role === 'user');
@@ -214,7 +236,7 @@ async function handleMessages(
// Send the message
console.error(`[serve] Sending: "${userText.slice(0, 80)}${userText.length > 80 ? '...' : ''}"`);
await sendMessage(page, userText);
await sendMessage(page, userText, bridge);
// Poll for reply
console.error('[serve] Waiting for reply...');
@@ -372,7 +394,7 @@ export async function startServe(opts: { port?: number } = {}): Promise<void> {
// Lazy connect on first request
const activePage = await ensureConnected();
const response = await handleMessages(body, activePage);
const response = await handleMessages(body, activePage, cdp ?? undefined);
jsonResponse(res, 200, response);
} finally {
requestInFlight = false;
+76
View File
@@ -0,0 +1,76 @@
/**
* Sina Finance 7x24 live news feed.
*
* Uses the public CJ API — no key or browser required.
* https://app.cj.sina.com.cn/api/news/pc
*/
import { cli, Strategy } from '../../registry.js';
import { CliError } from '../../errors.js';
// User-facing type (0-9) → Sina API tag ID
const TYPE_MAP = [
0, // 0: 全部
10, // 1: A股
1, // 2: 宏观
3, // 3: 公司
4, // 4: 数据
5, // 5: 市场
102, // 6: 国际
6, // 7: 观点
6, // 8: 央行
8, // 9: 其它
] as const;
interface SinaNewsItem {
id?: string;
create_time?: string;
rich_text?: string;
view_num?: number;
}
function stripHtml(html: string): string {
return html.replace(/<[^>]+>/g, '').trim();
}
cli({
site: 'sinafinance',
name: 'news',
description: '新浪财经 7x24 小时实时快讯',
domain: 'app.cj.sina.com.cn',
strategy: Strategy.PUBLIC,
browser: false,
args: [
{ name: 'limit', type: 'int', default: 20, help: 'Max results (max 50)' },
{ name: 'type', type: 'int', default: 0, help: 'News type: 0=全部 1=A股 2=宏观 3=公司 4=数据 5=市场 6=国际 7=观点 8=央行 9=其它' },
],
columns: ['id', 'time', 'content', 'views'],
func: async (_page, args) => {
const limit = Math.max(1, Math.min(Number(args.limit), 50));
const apiTag = TYPE_MAP[args.type as number] ?? 0;
const params = new URLSearchParams({
page: '1',
size: String(limit),
tag: String(apiTag),
});
const res = await fetch(`https://app.cj.sina.com.cn/api/news/pc?${params}`);
if (!res.ok) {
throw new CliError('FETCH_ERROR', `Sina Finance API HTTP ${res.status}`, 'Check your network connection');
}
const json = await res.json() as { result?: { data?: { feed?: { list?: SinaNewsItem[] } } } };
const list = json?.result?.data?.feed?.list ?? [];
if (!list.length) {
throw new CliError('NOT_FOUND', 'No news found', 'Try a different type or increase limit');
}
return list.map((item) => ({
id: item.id ?? '',
time: item.create_time ?? '',
content: stripHtml(item.rich_text ?? ''),
views: item.view_num ?? 0,
}));
},
});