news.js
1 /** 2 * Sina Finance 7x24 live news feed. 3 * 4 * Uses the public CJ API — no key or browser required. 5 * https://app.cj.sina.com.cn/api/news/pc 6 */ 7 import { cli, Strategy } from '@jackwener/opencli/registry'; 8 import { CliError } from '@jackwener/opencli/errors'; 9 // User-facing type (0-9) → Sina API tag ID 10 const TYPE_MAP = [ 11 0, // 0: 全部 12 10, // 1: A股 13 1, // 2: 宏观 14 3, // 3: 公司 15 4, // 4: 数据 16 5, // 5: 市场 17 102, // 6: 国际 18 6, // 7: 观点 19 6, // 8: 央行 20 8, // 9: 其它 21 ]; 22 function stripHtml(html) { 23 return html.replace(/<[^>]+>/g, '').trim(); 24 } 25 cli({ 26 site: 'sinafinance', 27 name: 'news', 28 description: '新浪财经 7x24 小时实时快讯', 29 domain: 'app.cj.sina.com.cn', 30 strategy: Strategy.PUBLIC, 31 browser: false, 32 args: [ 33 { name: 'limit', type: 'int', default: 20, help: 'Max results (max 50)' }, 34 { name: 'type', type: 'int', default: 0, help: 'News type: 0=全部 1=A股 2=宏观 3=公司 4=数据 5=市场 6=国际 7=观点 8=央行 9=其它' }, 35 ], 36 columns: ['id', 'time', 'content', 'views'], 37 func: async (_page, args) => { 38 const limit = Math.max(1, Math.min(Number(args.limit), 50)); 39 const apiTag = TYPE_MAP[args.type] ?? 0; 40 const params = new URLSearchParams({ 41 page: '1', 42 size: String(limit), 43 tag: String(apiTag), 44 }); 45 const res = await fetch(`https://app.cj.sina.com.cn/api/news/pc?${params}`); 46 if (!res.ok) { 47 throw new CliError('FETCH_ERROR', `Sina Finance API HTTP ${res.status}`, 'Check your network connection'); 48 } 49 const json = await res.json(); 50 const list = json?.result?.data?.feed?.list ?? []; 51 if (!list.length) { 52 throw new CliError('NOT_FOUND', 'No news found', 'Try a different type or increase limit'); 53 } 54 return list.map((item) => ({ 55 id: item.id ?? '', 56 time: item.create_time ?? '', 57 content: stripHtml(item.rich_text ?? ''), 58 views: item.view_num ?? 0, 59 })); 60 }, 61 });