import { config } from '../config.ts'; export interface ContentSample { noteId: string; title: string; content: string; publishTime: string; likeCount: number; commentCount: number; collectCount: number; shareCount: number; type: 'image' | 'video'; } export interface UserProfile { userId: string; nickname: string; avatar: string; fansCount: number; notesCount: number; likeAndCollect: number; description: string; location: string; tags: string[]; } /** * TikHub API 内容采样验证服务 * 文档: https://docs.tikhub.io/4579297m0 * 用于近10条内容采样、风格判断、活跃度校验 */ export async function searchNotes(keyword: string, platform: string = 'xiaohongshu', page: number = 1): Promise { if (!config.tikHub.apiKey) { console.warn('[TikHub] No API key configured, returning empty results'); return []; } try { const endpoint = getPlatformSearchEndpoint(platform); const response = await fetch(`${config.tikHub.baseUrl}${endpoint}`, { method: 'GET', headers: { Authorization: `Bearer ${config.tikHub.apiKey}`, 'Content-Type': 'application/json', }, // @ts-ignore - URL search params ...buildSearchParams(keyword, page, platform), }); if (!response.ok) { const errorText = await response.text(); console.error(`[TikHub] Search error: ${response.status} - ${errorText}`); return []; } const data = await response.json(); return normalizeContentSamples(data, platform); } catch (error) { console.error('[TikHub] Search failed:', error); return []; } } /** * 获取用户近期内容列表(用于内容风格判断) */ export async function getUserNotes(userId: string, platform: string = 'xiaohongshu', count: number = 10): Promise { if (!config.tikHub.apiKey) { return []; } try { const endpoint = getPlatformUserNotesEndpoint(platform); const queryParams = new URLSearchParams(); queryParams.set('user_id', userId); queryParams.set('count', String(count)); const response = await fetch(`${config.tikHub.baseUrl}${endpoint}?${queryParams.toString()}`, { method: 'GET', headers: { Authorization: `Bearer ${config.tikHub.apiKey}`, 'Content-Type': 'application/json', }, }); if (!response.ok) { return []; } const data = await response.json(); return normalizeContentSamples(data, platform); } catch (error) { console.error('[TikHub] User notes fetch failed:', error); return []; } } /** * 获取用户个人资料 */ export async function getUserProfile(userId: string, platform: string = 'xiaohongshu'): Promise { if (!config.tikHub.apiKey) { return null; } try { const endpoint = getPlatformUserProfileEndpoint(platform); const queryParams = new URLSearchParams(); queryParams.set('user_id', userId); const response = await fetch(`${config.tikHub.baseUrl}${endpoint}?${queryParams.toString()}`, { method: 'GET', headers: { Authorization: `Bearer ${config.tikHub.apiKey}`, 'Content-Type': 'application/json', }, }); if (!response.ok) { return null; } const data = await response.json(); return normalizeUserProfile(data, platform); } catch (error) { console.error('[TikHub] User profile fetch failed:', error); return null; } } function getPlatformSearchEndpoint(platform: string): string { switch (platform) { case 'xiaohongshu': return '/api/v1/xiaohongshu/web/search_notes'; case 'douyin': return '/api/v1/douyin/web/search_videos'; case 'bilibili': return '/api/v1/bilibili/web/search'; default: return '/api/v1/xiaohongshu/web/search_notes'; } } function getPlatformUserNotesEndpoint(platform: string): string { switch (platform) { case 'xiaohongshu': return '/api/v1/xiaohongshu/web/get_user_notes'; case 'douyin': return '/api/v1/douyin/web/get_user_posts'; case 'bilibili': return '/api/v1/bilibili/web/get_user_videos'; default: return '/api/v1/xiaohongshu/web/get_user_notes'; } } function getPlatformUserProfileEndpoint(platform: string): string { switch (platform) { case 'xiaohongshu': return '/api/v1/xiaohongshu/web/get_user_info'; case 'douyin': return '/api/v1/douyin/web/get_user_info'; case 'bilibili': return '/api/v1/bilibili/web/get_user_info'; default: return '/api/v1/xiaohongshu/web/get_user_info'; } } function buildSearchParams(keyword: string, page: number, platform: string): { body?: string } { // TikHub uses query params in URL, build accordingly const params = new URLSearchParams(); params.set('keyword', keyword); params.set('page', String(page)); if (platform === 'xiaohongshu') { params.set('sort', 'general'); params.set('note_type', '0'); } // Append to the URL instead of body return {}; } function normalizeContentSamples(data: Record, platform: string): ContentSample[] { const items = (data.data as Record[] | undefined) || []; if (!Array.isArray(items)) return []; return items.map((item: Record) => { if (platform === 'xiaohongshu') { return { noteId: String(item.note_id || item.id || ''), title: String(item.title || item.display_title || ''), content: String(item.desc || item.content || ''), publishTime: String(item.time || item.create_time || ''), likeCount: Number(item.liked_count || item.like_count || 0), commentCount: Number(item.comment_count || 0), collectCount: Number(item.collected_count || item.collect_count || 0), shareCount: Number(item.share_count || 0), type: (item.type === 'video' || item.note_type === 'video') ? 'video' : 'image', }; } // Generic fallback for douyin/bilibili return { noteId: String(item.id || item.aweme_id || item.bvid || ''), title: String(item.title || item.desc || ''), content: String(item.content || item.desc || ''), publishTime: String(item.create_time || item.pubdate || ''), likeCount: Number(item.like_count || item.digg_count || item.like || 0), commentCount: Number(item.comment_count || item.reply || 0), collectCount: Number(item.collect_count || item.favorite || 0), shareCount: Number(item.share_count || 0), type: 'video', }; }); } function normalizeUserProfile(data: Record, _platform: string): UserProfile | null { const user = (data.data as Record) || data; if (!user) return null; return { userId: String(user.user_id || user.uid || ''), nickname: String(user.nickname || user.name || ''), avatar: String(user.avatar || user.avatar_url || ''), fansCount: Number(user.fans_count || user.follower_count || 0), notesCount: Number(user.notes_count || user.aweme_count || 0), likeAndCollect: Number(user.liked_and_collected || user.total_favorited || 0), description: String(user.description || user.desc || user.signature || ''), location: String(user.location || user.ip_location || ''), tags: Array.isArray(user.tags) ? user.tags as string[] : [], }; }