probe-tikhub-ultimate.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  1. #!/usr/bin/env node
  2. /**
  3. * TikHub Ultimate Probe
  4. * 一口气验证三类接口(15+ 端点):
  5. * A. Douyin Billboard(热榜系列,POST)
  6. * B. TikTok Ads Insights(创意中心,GET)
  7. * C. TikTok Shop Web / App API(GET)
  8. * 把每个端点的 status + 关键字段 dump 到 data/probe-ultimate-<name>.json
  9. */
  10. const fs = require('fs');
  11. const path = require('path');
  12. const os = require('os');
  13. const https = require('https');
  14. const TIKHUB_TOKEN = JSON.parse(
  15. fs.readFileSync(
  16. path.join(os.homedir(), '.openclaw', 'skills', 'xiaohongshu-search-notes', 'api-config.json'),
  17. 'utf-8'
  18. )
  19. ).endpoint.headers.Authorization.replace(/^Bearer\s+/, '');
  20. const OUT_DIR = path.join('data', 'probe-ultimate');
  21. if (!fs.existsSync(OUT_DIR)) fs.mkdirSync(OUT_DIR, { recursive: true });
  22. function request(method, apiPath, { params = {}, body = null } = {}) {
  23. const qs = Object.entries(params)
  24. .filter(([, v]) => v !== undefined && v !== null && v !== '')
  25. .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`)
  26. .join('&');
  27. const fullPath = qs ? `${apiPath}?${qs}` : apiPath;
  28. const bodyStr = body ? JSON.stringify(body) : null;
  29. return new Promise((resolve) => {
  30. const headers = { Authorization: `Bearer ${TIKHUB_TOKEN}`, Accept: 'application/json' };
  31. if (bodyStr) {
  32. headers['Content-Type'] = 'application/json';
  33. headers['Content-Length'] = Buffer.byteLength(bodyStr);
  34. }
  35. const opts = { hostname: 'api.tikhub.io', path: fullPath, method, headers };
  36. const req = https.request(opts, (res) => {
  37. const chunks = [];
  38. res.on('data', (c) => chunks.push(c));
  39. res.on('end', () => {
  40. const raw = Buffer.concat(chunks).toString('utf-8');
  41. try { resolve({ status: res.statusCode, json: JSON.parse(raw), rawLen: raw.length }); }
  42. catch (e) { resolve({ status: res.statusCode, json: { _raw: raw.slice(0, 300) }, rawLen: raw.length }); }
  43. });
  44. });
  45. req.on('error', (e) => resolve({ status: 0, json: { _error: e.message } }));
  46. req.setTimeout(30000, () => { req.destroy(); resolve({ status: 0, json: { _error: 'timeout' } }); });
  47. if (bodyStr) req.write(bodyStr);
  48. req.end();
  49. });
  50. }
  51. const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
  52. // ============================================================
  53. // 探针列表
  54. // ============================================================
  55. const PROBES = [
  56. // ---- A. Douyin Billboard(POST)----
  57. { group: 'A-douyin-billboard', name: 'fetch_hot_total_video_list', method: 'POST',
  58. path: '/api/v1/douyin/billboard/fetch_hot_total_video_list',
  59. body: { date_window: 24, keyword: '益生菌', page: 1, page_size: 10 } },
  60. { group: 'A-douyin-billboard', name: 'fetch_hot_total_topic_list', method: 'POST',
  61. path: '/api/v1/douyin/billboard/fetch_hot_total_topic_list',
  62. body: { date_window: 24, keyword: '益生菌', page: 1, page_size: 10 } },
  63. { group: 'A-douyin-billboard', name: 'fetch_hot_total_search_list', method: 'POST',
  64. path: '/api/v1/douyin/billboard/fetch_hot_total_search_list',
  65. body: { date_window: 24, keyword: '益生菌', page: 1, page_size: 10 } },
  66. { group: 'A-douyin-billboard', name: 'fetch_hot_total_hot_word_list', method: 'POST',
  67. path: '/api/v1/douyin/billboard/fetch_hot_total_hot_word_list',
  68. body: { date_window: 24, page: 1, page_size: 10 } },
  69. { group: 'A-douyin-billboard', name: 'fetch_hot_total_high_fan_list', method: 'POST',
  70. path: '/api/v1/douyin/billboard/fetch_hot_total_high_fan_list',
  71. body: { date_window: 24, page: 1, page_size: 10 } },
  72. { group: 'A-douyin-billboard', name: 'fetch_hot_total_high_like_list', method: 'POST',
  73. path: '/api/v1/douyin/billboard/fetch_hot_total_high_like_list',
  74. body: { date_window: 24, page: 1, page_size: 10 } },
  75. { group: 'A-douyin-billboard', name: 'fetch_hot_total_high_play_list', method: 'POST',
  76. path: '/api/v1/douyin/billboard/fetch_hot_total_high_play_list',
  77. body: { date_window: 24, page: 1, page_size: 10 } },
  78. { group: 'A-douyin-billboard', name: 'fetch_hot_total_high_topic_list', method: 'POST',
  79. path: '/api/v1/douyin/billboard/fetch_hot_total_high_topic_list',
  80. body: { date_window: 24, page: 1, page_size: 10 } },
  81. { group: 'A-douyin-billboard', name: 'fetch_hot_total_high_search_list', method: 'POST',
  82. path: '/api/v1/douyin/billboard/fetch_hot_total_high_search_list',
  83. body: { date_window: 24, page: 1, page_size: 10 } },
  84. { group: 'A-douyin-billboard', name: 'fetch_hot_account_list', method: 'POST',
  85. path: '/api/v1/douyin/billboard/fetch_hot_account_list',
  86. body: { date_window: 24, page: 1, page_size: 10, tags: '1' } },
  87. { group: 'A-douyin-billboard', name: 'fetch_hot_calendar_list', method: 'POST',
  88. path: '/api/v1/douyin/billboard/fetch_hot_calendar_list',
  89. body: { start_date: 20260101, end_date: 20260417, city_code: '', category_code: '' } },
  90. // Douyin Web hot search(不需要 body)
  91. { group: 'A-douyin-billboard', name: 'web_fetch_hot_search_result', method: 'GET',
  92. path: '/api/v1/douyin/web/fetch_hot_search_result' },
  93. // ---- B. TikTok Ads Creative Center(GET)----
  94. { group: 'B-tiktok-ads', name: 'get_keyword_list', method: 'GET',
  95. path: '/api/v1/tiktok/ads/get_keyword_list',
  96. params: { period: 30, country_code: 'US', page: 1, limit: 10 } },
  97. { group: 'B-tiktok-ads', name: 'get_keyword_details', method: 'GET',
  98. path: '/api/v1/tiktok/ads/get_keyword_details',
  99. params: { keyword: 'milk thistle', period: 30, country_code: 'US' } },
  100. { group: 'B-tiktok-ads', name: 'get_keyword_insights', method: 'GET',
  101. path: '/api/v1/tiktok/ads/get_keyword_insights',
  102. params: { keyword: 'milk thistle', period: 30, country_code: 'US' } },
  103. { group: 'B-tiktok-ads', name: 'get_related_keywords', method: 'GET',
  104. path: '/api/v1/tiktok/ads/get_related_keywords',
  105. params: { keyword: 'milk thistle', period: 30, country_code: 'US' } },
  106. { group: 'B-tiktok-ads', name: 'get_top_products', method: 'GET',
  107. path: '/api/v1/tiktok/ads/get_top_products',
  108. params: { period: 30, country_code: 'US', page: 1, limit: 10 } },
  109. { group: 'B-tiktok-ads', name: 'get_popular_hashtags', method: 'GET',
  110. path: '/api/v1/tiktok/ads/get_popular_hashtags',
  111. params: { period: 30, country_code: 'US', page: 1, limit: 10 } },
  112. { group: 'B-tiktok-ads', name: 'get_popular_sound', method: 'GET',
  113. path: '/api/v1/tiktok/ads/get_popular_sound',
  114. params: { period: 30, country_code: 'US', page: 1, limit: 10 } },
  115. { group: 'B-tiktok-ads', name: 'get_top_ads_spotlight', method: 'GET',
  116. path: '/api/v1/tiktok/ads/get_top_ads_spotlight',
  117. params: { period: 30, country_code: 'US', page: 1, limit: 10 } },
  118. { group: 'B-tiktok-ads', name: 'get_creator_list', method: 'GET',
  119. path: '/api/v1/tiktok/ads/get_creator_list',
  120. params: { country_code: 'US', page: 1, limit: 10 } },
  121. { group: 'B-tiktok-ads', name: 'search_creators', method: 'GET',
  122. path: '/api/v1/tiktok/ads/search_creators',
  123. params: { keyword: 'probiotic', country_code: 'US', page: 1, limit: 10 } },
  124. { group: 'B-tiktok-ads', name: 'get_trend_popular_videos', method: 'GET',
  125. path: '/api/v1/tiktok/ads/get_trend_popular_videos',
  126. params: { period: 30, country_code: 'US', page: 1, limit: 10 } },
  127. { group: 'B-tiktok-ads', name: 'creator_search_insights', method: 'GET',
  128. path: '/api/v1/tiktok/app/v3/creator_search_insights',
  129. params: { keyword: 'milk thistle' } },
  130. { group: 'B-tiktok-ads', name: 'creator_search_insights_trend', method: 'GET',
  131. path: '/api/v1/tiktok/app/v3/creator_search_insights_trend',
  132. params: { keyword: 'milk thistle' } },
  133. // ---- C. TikTok Shop Web(GET)----
  134. { group: 'C-tiktok-shop', name: 'shop_search_products_v1', method: 'GET',
  135. path: '/api/v1/tiktok/shop/fetch_search_products_v1',
  136. params: { keyword: 'milk thistle', country_code: 'US', page: 1, size: 10 } },
  137. { group: 'C-tiktok-shop', name: 'shop_search_products_v3', method: 'GET',
  138. path: '/api/v1/tiktok/shop/fetch_search_products_v3',
  139. params: { keyword: 'milk thistle', country_code: 'US', page: 1, size: 10 } },
  140. { group: 'C-tiktok-shop', name: 'shop_search_keyword_suggest_v1', method: 'GET',
  141. path: '/api/v1/tiktok/shop/fetch_search_keyword_suggest_v1',
  142. params: { keyword: 'milk thistle', country_code: 'US' } },
  143. { group: 'C-tiktok-shop', name: 'shop_get_hot_selling_products', method: 'GET',
  144. path: '/api/v1/tiktok/shop/fetch_hot_selling_products',
  145. params: { country_code: 'US', page: 1, size: 10 } },
  146. { group: 'C-tiktok-shop', name: 'shop_get_product_category_list', method: 'GET',
  147. path: '/api/v1/tiktok/shop/fetch_product_category_list',
  148. params: { country_code: 'US' } },
  149. { group: 'C-tiktok-shop', name: 'shop_get_product_reviews_v2', method: 'GET',
  150. path: '/api/v1/tiktok/shop/fetch_product_reviews_v2',
  151. params: { product_id: '1730213111571944226', country_code: 'US', page: 1, size: 10 } }, // 占位 ID,用来测路径
  152. ];
  153. function summarizeJson(j, maxLen = 300) {
  154. if (!j) return { _empty: true };
  155. const topKeys = Object.keys(j).slice(0, 8);
  156. const dataPreview = (() => {
  157. if (j.data) {
  158. if (Array.isArray(j.data)) return { _arrayLen: j.data.length, _first: j.data[0] ? Object.keys(j.data[0]).slice(0, 6) : null };
  159. if (typeof j.data === 'object') return { _keys: Object.keys(j.data).slice(0, 8) };
  160. }
  161. return null;
  162. })();
  163. return { topKeys, message: j.message || j.msg, code: j.code, error: j.error, dataPreview };
  164. }
  165. async function main() {
  166. console.log('==== TikHub Ultimate Probe ====');
  167. const results = [];
  168. let okCount = 0, failCount = 0;
  169. for (const p of PROBES) {
  170. process.stdout.write(`[${p.group}] ${p.method} ${p.name} `);
  171. const resp = await request(p.method, p.path, { params: p.params, body: p.body });
  172. const ok = resp.status === 200 && !(resp.json && (resp.json.code && resp.json.code !== 0 && resp.json.code !== 200));
  173. if (ok) okCount++; else failCount++;
  174. const summary = summarizeJson(resp.json);
  175. const rec = {
  176. group: p.group, name: p.name, method: p.method, path: p.path,
  177. params: p.params || null, body: p.body || null,
  178. status: resp.status, ok, rawLen: resp.rawLen,
  179. summary,
  180. sampleJson: resp.status === 200 ? resp.json : { errorJson: resp.json },
  181. };
  182. fs.writeFileSync(path.join(OUT_DIR, `${p.name}.json`), JSON.stringify(rec, null, 2), 'utf-8');
  183. console.log(`→ ${resp.status}${ok ? ' ✓' : ' ✗'} ${summary.message ? '| ' + String(summary.message).slice(0, 60) : ''}`);
  184. results.push(rec);
  185. await sleep(600);
  186. }
  187. const overview = results.map((r) => ({
  188. group: r.group, name: r.name, method: r.method, path: r.path,
  189. status: r.status, ok: r.ok,
  190. msg: (r.summary?.message || '').slice(0, 80),
  191. dataPreview: r.summary?.dataPreview,
  192. }));
  193. fs.writeFileSync(path.join(OUT_DIR, '_overview.json'), JSON.stringify(overview, null, 2), 'utf-8');
  194. console.log('\n==== 汇总 ====');
  195. console.log(`OK: ${okCount} / Fail: ${failCount} / Total: ${PROBES.length}`);
  196. console.log('\nByGroup:');
  197. ['A-douyin-billboard', 'B-tiktok-ads', 'C-tiktok-shop'].forEach((g) => {
  198. const gs = overview.filter((x) => x.group === g);
  199. const gOk = gs.filter((x) => x.ok).length;
  200. console.log(` ${g}: ${gOk}/${gs.length} OK`);
  201. gs.forEach((x) => console.log(` [${x.ok ? '✓' : '✗'}] ${x.name} ${x.status} ${x.msg || ''}`));
  202. });
  203. console.log(`\nDetails → ${OUT_DIR}/_overview.json`);
  204. }
  205. main().catch((e) => { console.error(e); process.exit(1); });