12-douyinManager.js 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107
  1. /**
  2. * 云函数:douyinManager
  3. * 代理抖音/TikHub 相关接口,避免前端暴露 TikHub 地址和 token。
  4. *
  5. * 部署后把函数 objectId 填入 src/app/services/cloud-functions.ts 的 douyin。
  6. */
  7. const TIKHUB_BASE_URL = 'https://api.tikhub.io';
  8. const TIKHUB_TOKEN = 'gqsZHfMWgAiMwV+ITbmZy0qALADWBZVS7QnV7kKJe9CwzgWgJG+7bwK+GQ==';
  9. const ROUTES = {
  10. searchVideos: { method: 'POST', path: '/api/v1/douyin/search/fetch_general_search_v2' },
  11. challengeSearch: { method: 'POST', path: '/api/v1/douyin/search/fetch_challenge_search_v2' },
  12. videoDetail: { method: 'GET', path: '/api/v1/douyin/app/v3/fetch_one_video_v3' },
  13. userProfileWeb: { method: 'GET', path: '/api/v1/douyin/web/handler_user_profile_v2' },
  14. userProfileApp: { method: 'GET', path: '/api/v1/douyin/app/v3/handler_user_profile' },
  15. userPosts: { method: 'GET', path: '/api/v1/douyin/web/fetch_user_post_videos' },
  16. comments: { method: 'GET', path: '/api/v1/douyin/app/v3/fetch_video_comments' },
  17. replies: { method: 'POST', path: '/api/v1/douyin/comment/reply/list' },
  18. };
  19. async function handler(request, response) {
  20. try {
  21. const action = pickParam(request, 'action') || 'call';
  22. if (action !== 'call') {
  23. return response.json({ code: 400, success: false, error: `未知 action: ${action}` });
  24. }
  25. const routeName = String(pickParam(request, 'route') || '').trim();
  26. const route = ROUTES[routeName];
  27. if (!route) {
  28. return response.json({ code: 400, success: false, error: '不支持的抖音接口' });
  29. }
  30. const params = pickParam(request, 'params') || {};
  31. const body = pickParam(request, 'payload', 'data') || {};
  32. const data = route.method === 'GET'
  33. ? await requestJson('GET', route.path, params)
  34. : await requestJson('POST', route.path, body);
  35. response.json(data);
  36. } catch (error) {
  37. console.error('douyinManager failed:', error && error.message ? error.message : error);
  38. response.json({ code: 500, success: false, error: error && error.message ? error.message : '抖音服务调用失败' });
  39. }
  40. }
  41. async function requestJson(method, path, payload) {
  42. const url = new URL(`${TIKHUB_BASE_URL}${path}`);
  43. const init = {
  44. method,
  45. headers: {
  46. 'Content-Type': 'application/json',
  47. 'Accept': 'application/json',
  48. 'Authorization': `Bearer ${TIKHUB_TOKEN}`,
  49. },
  50. };
  51. if (method === 'GET') {
  52. for (const [key, value] of Object.entries(payload || {})) {
  53. if (value !== undefined && value !== null && value !== '') {
  54. url.searchParams.set(key, String(value));
  55. }
  56. }
  57. } else {
  58. init.body = JSON.stringify(stripEmpty(payload || {}));
  59. }
  60. const r = await fetch(url.toString(), init);
  61. const text = await r.text();
  62. let data = null;
  63. try { data = text ? JSON.parse(text) : null; } catch {}
  64. if (!r.ok) {
  65. return { code: r.status, success: false, error: readError(data, text) || `HTTP ${r.status}` };
  66. }
  67. return data || { code: 500, success: false, error: '服务返回异常' };
  68. }
  69. function pickParam(request, ...names) {
  70. const sources = [request.params, request.body, request];
  71. for (const src of sources) {
  72. if (!src || typeof src !== 'object') continue;
  73. for (const n of names) {
  74. const v = src[n];
  75. if (v !== undefined && v !== null && v !== '') return v;
  76. }
  77. }
  78. return null;
  79. }
  80. function stripEmpty(value) {
  81. if (!value || typeof value !== 'object') return value;
  82. const out = Array.isArray(value) ? [] : {};
  83. for (const [key, val] of Object.entries(value)) {
  84. if (val === undefined || val === null || val === '') continue;
  85. if (val && typeof val === 'object' && !Array.isArray(val)) {
  86. const nested = stripEmpty(val);
  87. if (Object.keys(nested).length) out[key] = nested;
  88. } else {
  89. out[key] = val;
  90. }
  91. }
  92. return out;
  93. }
  94. function readError(data, fallback) {
  95. return data?.error?.message || data?.error || data?.message || data?.msg || fallback || '';
  96. }