From 02793e990ef4bdfdde9d7a748960b8a9ed6ea988 Mon Sep 17 00:00:00 2001 From: jackwener Date: Fri, 20 Mar 2026 17:22:42 +0800 Subject: [PATCH] feat: add sinafinance 7x24 news adapter (#131) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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> --- README.md | 1 + README.zh-CN.md | 1 + SKILL.md | 3 ++ src/clis/antigravity/serve.ts | 30 ++++++++++++-- src/clis/sinafinance/news.ts | 76 +++++++++++++++++++++++++++++++++++ 5 files changed, 107 insertions(+), 4 deletions(-) create mode 100644 src/clis/sinafinance/news.ts diff --git a/README.md b/README.md index 6eb5e96..5e73237 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/README.zh-CN.md b/README.zh-CN.md index e14d0dc..c9a4dc6 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -146,6 +146,7 @@ npm install -g @jackwener/opencli@latest | **smzdm** | `search` | 浏览器 | | **weibo** | `hot` | 浏览器 | | **yahoo-finance** | `quote` | 浏览器 | +| **sinafinance** | `news` | 🌐 公开 | ### 桌面应用适配器 diff --git a/SKILL.md b/SKILL.md index ddfef1b..754f554 100644 --- a/SKILL.md +++ b/SKILL.md @@ -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" # 路透社搜索 diff --git a/src/clis/antigravity/serve.ts b/src/clis/antigravity/serve.ts index 7241fed..52ec3cf 100644 --- a/src/clis/antigravity/serve.ts +++ b/src/clis/antigravity/serve.ts @@ -97,7 +97,7 @@ async function getConversationText(page: IPage): Promise { return String(text ?? ''); } -async function sendMessage(page: IPage, message: string): Promise { +async function sendMessage(page: IPage, message: string, bridge?: CDPBridge): Promise { await page.evaluate(` (async () => { const container = document.getElementById('antigravity.agentSidePanelInputBox'); @@ -110,7 +110,28 @@ async function sendMessage(page: IPage, message: string): Promise { })() `); 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 { // 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 { // 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; diff --git a/src/clis/sinafinance/news.ts b/src/clis/sinafinance/news.ts new file mode 100644 index 0000000..7f81b8c --- /dev/null +++ b/src/clis/sinafinance/news.ts @@ -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, + })); + }, +});