search.js
1 import { cli, Strategy } from '@jackwener/opencli/registry'; 2 function normalize(value) { 3 return typeof value === 'string' ? value.replace(/\s+/g, ' ').trim() : ''; 4 } 5 function stripHtml(value) { 6 return value.replace(/<[^>]+>/g, ''); 7 } 8 async function searchSinaBlog(keyword, limit) { 9 const url = new URL('https://search.sina.com.cn/api/search'); 10 url.searchParams.set('q', keyword); 11 url.searchParams.set('tp', 'mix'); 12 url.searchParams.set('sort', '0'); 13 url.searchParams.set('page', '1'); 14 url.searchParams.set('size', String(Math.max(limit, 10))); 15 url.searchParams.set('from', 'search_result'); 16 const resp = await fetch(url, { 17 headers: { 18 'User-Agent': 'Mozilla/5.0', 19 Accept: 'application/json', 20 }, 21 }); 22 if (!resp.ok) 23 throw new Error(`Sina blog search failed: HTTP ${resp.status}`); 24 const data = await resp.json(); 25 const list = Array.isArray(data?.data?.list) ? data.data.list : []; 26 return list 27 .filter((item) => normalize(item?.url).includes('blog.sina.com.cn/s/blog_')) 28 .slice(0, limit) 29 .map((item, index) => ({ 30 rank: index + 1, 31 title: normalize(stripHtml(item?.title || '')), 32 author: normalize(item?.media_show || item?.author), 33 date: normalize(item?.time || item?.dataTime), 34 description: normalize(item?.intro || item?.searchSummary).slice(0, 150), 35 url: normalize(item?.url), 36 })); 37 } 38 cli({ 39 site: 'sinablog', 40 name: 'search', 41 description: '搜索新浪博客文章(通过新浪搜索)', 42 domain: 'blog.sina.com.cn', 43 strategy: Strategy.PUBLIC, 44 browser: false, 45 args: [ 46 { name: 'keyword', required: true, positional: true, help: '搜索关键词' }, 47 { name: 'limit', type: 'int', default: 20, help: '返回的文章数量' }, 48 ], 49 columns: ['rank', 'title', 'author', 'date', 'description', 'url'], 50 func: async (_page, args) => searchSinaBlog(args.keyword, Math.max(1, Math.min(Number(args.limit) || 20, 50))), 51 });