/** * 云函数:douyinManager * 代理抖音/TikHub 相关接口,避免前端暴露 TikHub 地址和 token。 * * 部署后把函数 objectId 填入 src/app/services/cloud-functions.ts 的 douyin。 */ const TIKHUB_BASE_URL = 'https://api.tikhub.io'; const TIKHUB_TOKEN = 'gqsZHfMWgAiMwV+ITbmZy0qALADWBZVS7QnV7kKJe9CwzgWgJG+7bwK+GQ=='; const ROUTES = { searchVideos: { method: 'POST', path: '/api/v1/douyin/search/fetch_general_search_v2' }, challengeSearch: { method: 'POST', path: '/api/v1/douyin/search/fetch_challenge_search_v2' }, videoDetail: { method: 'GET', path: '/api/v1/douyin/app/v3/fetch_one_video_v3' }, userProfileWeb: { method: 'GET', path: '/api/v1/douyin/web/handler_user_profile_v2' }, userProfileApp: { method: 'GET', path: '/api/v1/douyin/app/v3/handler_user_profile' }, userPosts: { method: 'GET', path: '/api/v1/douyin/web/fetch_user_post_videos' }, comments: { method: 'GET', path: '/api/v1/douyin/app/v3/fetch_video_comments' }, replies: { method: 'POST', path: '/api/v1/douyin/comment/reply/list' }, }; async function handler(request, response) { try { const action = pickParam(request, 'action') || 'call'; if (action !== 'call') { return response.json({ code: 400, success: false, error: `未知 action: ${action}` }); } const routeName = String(pickParam(request, 'route') || '').trim(); const route = ROUTES[routeName]; if (!route) { return response.json({ code: 400, success: false, error: '不支持的抖音接口' }); } const params = pickParam(request, 'params') || {}; const body = pickParam(request, 'payload', 'data') || {}; const data = route.method === 'GET' ? await requestJson('GET', route.path, params) : await requestJson('POST', route.path, body); response.json(data); } catch (error) { console.error('douyinManager failed:', error && error.message ? error.message : error); response.json({ code: 500, success: false, error: error && error.message ? error.message : '抖音服务调用失败' }); } } async function requestJson(method, path, payload) { const url = new URL(`${TIKHUB_BASE_URL}${path}`); const init = { method, headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', 'Authorization': `Bearer ${TIKHUB_TOKEN}`, }, }; if (method === 'GET') { for (const [key, value] of Object.entries(payload || {})) { if (value !== undefined && value !== null && value !== '') { url.searchParams.set(key, String(value)); } } } else { init.body = JSON.stringify(stripEmpty(payload || {})); } const r = await fetch(url.toString(), init); const text = await r.text(); let data = null; try { data = text ? JSON.parse(text) : null; } catch {} if (!r.ok) { return { code: r.status, success: false, error: readError(data, text) || `HTTP ${r.status}` }; } return data || { code: 500, success: false, error: '服务返回异常' }; } function pickParam(request, ...names) { const sources = [request.params, request.body, request]; for (const src of sources) { if (!src || typeof src !== 'object') continue; for (const n of names) { const v = src[n]; if (v !== undefined && v !== null && v !== '') return v; } } return null; } function stripEmpty(value) { if (!value || typeof value !== 'object') return value; const out = Array.isArray(value) ? [] : {}; for (const [key, val] of Object.entries(value)) { if (val === undefined || val === null || val === '') continue; if (val && typeof val === 'object' && !Array.isArray(val)) { const nested = stripEmpty(val); if (Object.keys(nested).length) out[key] = nested; } else { out[key] = val; } } return out; } function readError(data, fallback) { return data?.error?.message || data?.error || data?.message || data?.msg || fallback || ''; }