| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216 |
- #!/usr/bin/env node
- /**
- * TikHub Ultimate Probe
- * 一口气验证三类接口(15+ 端点):
- * A. Douyin Billboard(热榜系列,POST)
- * B. TikTok Ads Insights(创意中心,GET)
- * C. TikTok Shop Web / App API(GET)
- * 把每个端点的 status + 关键字段 dump 到 data/probe-ultimate-<name>.json
- */
- const fs = require('fs');
- const path = require('path');
- const os = require('os');
- const https = require('https');
- const TIKHUB_TOKEN = JSON.parse(
- fs.readFileSync(
- path.join(os.homedir(), '.openclaw', 'skills', 'xiaohongshu-search-notes', 'api-config.json'),
- 'utf-8'
- )
- ).endpoint.headers.Authorization.replace(/^Bearer\s+/, '');
- const OUT_DIR = path.join('data', 'probe-ultimate');
- if (!fs.existsSync(OUT_DIR)) fs.mkdirSync(OUT_DIR, { recursive: true });
- function request(method, apiPath, { params = {}, body = null } = {}) {
- const qs = Object.entries(params)
- .filter(([, v]) => v !== undefined && v !== null && v !== '')
- .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`)
- .join('&');
- const fullPath = qs ? `${apiPath}?${qs}` : apiPath;
- const bodyStr = body ? JSON.stringify(body) : null;
- return new Promise((resolve) => {
- const headers = { Authorization: `Bearer ${TIKHUB_TOKEN}`, Accept: 'application/json' };
- if (bodyStr) {
- headers['Content-Type'] = 'application/json';
- headers['Content-Length'] = Buffer.byteLength(bodyStr);
- }
- const opts = { hostname: 'api.tikhub.io', path: fullPath, method, headers };
- const req = https.request(opts, (res) => {
- const chunks = [];
- res.on('data', (c) => chunks.push(c));
- res.on('end', () => {
- const raw = Buffer.concat(chunks).toString('utf-8');
- try { resolve({ status: res.statusCode, json: JSON.parse(raw), rawLen: raw.length }); }
- catch (e) { resolve({ status: res.statusCode, json: { _raw: raw.slice(0, 300) }, rawLen: raw.length }); }
- });
- });
- req.on('error', (e) => resolve({ status: 0, json: { _error: e.message } }));
- req.setTimeout(30000, () => { req.destroy(); resolve({ status: 0, json: { _error: 'timeout' } }); });
- if (bodyStr) req.write(bodyStr);
- req.end();
- });
- }
- const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
- // ============================================================
- // 探针列表
- // ============================================================
- const PROBES = [
- // ---- A. Douyin Billboard(POST)----
- { group: 'A-douyin-billboard', name: 'fetch_hot_total_video_list', method: 'POST',
- path: '/api/v1/douyin/billboard/fetch_hot_total_video_list',
- body: { date_window: 24, keyword: '益生菌', page: 1, page_size: 10 } },
- { group: 'A-douyin-billboard', name: 'fetch_hot_total_topic_list', method: 'POST',
- path: '/api/v1/douyin/billboard/fetch_hot_total_topic_list',
- body: { date_window: 24, keyword: '益生菌', page: 1, page_size: 10 } },
- { group: 'A-douyin-billboard', name: 'fetch_hot_total_search_list', method: 'POST',
- path: '/api/v1/douyin/billboard/fetch_hot_total_search_list',
- body: { date_window: 24, keyword: '益生菌', page: 1, page_size: 10 } },
- { group: 'A-douyin-billboard', name: 'fetch_hot_total_hot_word_list', method: 'POST',
- path: '/api/v1/douyin/billboard/fetch_hot_total_hot_word_list',
- body: { date_window: 24, page: 1, page_size: 10 } },
- { group: 'A-douyin-billboard', name: 'fetch_hot_total_high_fan_list', method: 'POST',
- path: '/api/v1/douyin/billboard/fetch_hot_total_high_fan_list',
- body: { date_window: 24, page: 1, page_size: 10 } },
- { group: 'A-douyin-billboard', name: 'fetch_hot_total_high_like_list', method: 'POST',
- path: '/api/v1/douyin/billboard/fetch_hot_total_high_like_list',
- body: { date_window: 24, page: 1, page_size: 10 } },
- { group: 'A-douyin-billboard', name: 'fetch_hot_total_high_play_list', method: 'POST',
- path: '/api/v1/douyin/billboard/fetch_hot_total_high_play_list',
- body: { date_window: 24, page: 1, page_size: 10 } },
- { group: 'A-douyin-billboard', name: 'fetch_hot_total_high_topic_list', method: 'POST',
- path: '/api/v1/douyin/billboard/fetch_hot_total_high_topic_list',
- body: { date_window: 24, page: 1, page_size: 10 } },
- { group: 'A-douyin-billboard', name: 'fetch_hot_total_high_search_list', method: 'POST',
- path: '/api/v1/douyin/billboard/fetch_hot_total_high_search_list',
- body: { date_window: 24, page: 1, page_size: 10 } },
- { group: 'A-douyin-billboard', name: 'fetch_hot_account_list', method: 'POST',
- path: '/api/v1/douyin/billboard/fetch_hot_account_list',
- body: { date_window: 24, page: 1, page_size: 10, tags: '1' } },
- { group: 'A-douyin-billboard', name: 'fetch_hot_calendar_list', method: 'POST',
- path: '/api/v1/douyin/billboard/fetch_hot_calendar_list',
- body: { start_date: 20260101, end_date: 20260417, city_code: '', category_code: '' } },
- // Douyin Web hot search(不需要 body)
- { group: 'A-douyin-billboard', name: 'web_fetch_hot_search_result', method: 'GET',
- path: '/api/v1/douyin/web/fetch_hot_search_result' },
- // ---- B. TikTok Ads Creative Center(GET)----
- { group: 'B-tiktok-ads', name: 'get_keyword_list', method: 'GET',
- path: '/api/v1/tiktok/ads/get_keyword_list',
- params: { period: 30, country_code: 'US', page: 1, limit: 10 } },
- { group: 'B-tiktok-ads', name: 'get_keyword_details', method: 'GET',
- path: '/api/v1/tiktok/ads/get_keyword_details',
- params: { keyword: 'milk thistle', period: 30, country_code: 'US' } },
- { group: 'B-tiktok-ads', name: 'get_keyword_insights', method: 'GET',
- path: '/api/v1/tiktok/ads/get_keyword_insights',
- params: { keyword: 'milk thistle', period: 30, country_code: 'US' } },
- { group: 'B-tiktok-ads', name: 'get_related_keywords', method: 'GET',
- path: '/api/v1/tiktok/ads/get_related_keywords',
- params: { keyword: 'milk thistle', period: 30, country_code: 'US' } },
- { group: 'B-tiktok-ads', name: 'get_top_products', method: 'GET',
- path: '/api/v1/tiktok/ads/get_top_products',
- params: { period: 30, country_code: 'US', page: 1, limit: 10 } },
- { group: 'B-tiktok-ads', name: 'get_popular_hashtags', method: 'GET',
- path: '/api/v1/tiktok/ads/get_popular_hashtags',
- params: { period: 30, country_code: 'US', page: 1, limit: 10 } },
- { group: 'B-tiktok-ads', name: 'get_popular_sound', method: 'GET',
- path: '/api/v1/tiktok/ads/get_popular_sound',
- params: { period: 30, country_code: 'US', page: 1, limit: 10 } },
- { group: 'B-tiktok-ads', name: 'get_top_ads_spotlight', method: 'GET',
- path: '/api/v1/tiktok/ads/get_top_ads_spotlight',
- params: { period: 30, country_code: 'US', page: 1, limit: 10 } },
- { group: 'B-tiktok-ads', name: 'get_creator_list', method: 'GET',
- path: '/api/v1/tiktok/ads/get_creator_list',
- params: { country_code: 'US', page: 1, limit: 10 } },
- { group: 'B-tiktok-ads', name: 'search_creators', method: 'GET',
- path: '/api/v1/tiktok/ads/search_creators',
- params: { keyword: 'probiotic', country_code: 'US', page: 1, limit: 10 } },
- { group: 'B-tiktok-ads', name: 'get_trend_popular_videos', method: 'GET',
- path: '/api/v1/tiktok/ads/get_trend_popular_videos',
- params: { period: 30, country_code: 'US', page: 1, limit: 10 } },
- { group: 'B-tiktok-ads', name: 'creator_search_insights', method: 'GET',
- path: '/api/v1/tiktok/app/v3/creator_search_insights',
- params: { keyword: 'milk thistle' } },
- { group: 'B-tiktok-ads', name: 'creator_search_insights_trend', method: 'GET',
- path: '/api/v1/tiktok/app/v3/creator_search_insights_trend',
- params: { keyword: 'milk thistle' } },
- // ---- C. TikTok Shop Web(GET)----
- { group: 'C-tiktok-shop', name: 'shop_search_products_v1', method: 'GET',
- path: '/api/v1/tiktok/shop/fetch_search_products_v1',
- params: { keyword: 'milk thistle', country_code: 'US', page: 1, size: 10 } },
- { group: 'C-tiktok-shop', name: 'shop_search_products_v3', method: 'GET',
- path: '/api/v1/tiktok/shop/fetch_search_products_v3',
- params: { keyword: 'milk thistle', country_code: 'US', page: 1, size: 10 } },
- { group: 'C-tiktok-shop', name: 'shop_search_keyword_suggest_v1', method: 'GET',
- path: '/api/v1/tiktok/shop/fetch_search_keyword_suggest_v1',
- params: { keyword: 'milk thistle', country_code: 'US' } },
- { group: 'C-tiktok-shop', name: 'shop_get_hot_selling_products', method: 'GET',
- path: '/api/v1/tiktok/shop/fetch_hot_selling_products',
- params: { country_code: 'US', page: 1, size: 10 } },
- { group: 'C-tiktok-shop', name: 'shop_get_product_category_list', method: 'GET',
- path: '/api/v1/tiktok/shop/fetch_product_category_list',
- params: { country_code: 'US' } },
- { group: 'C-tiktok-shop', name: 'shop_get_product_reviews_v2', method: 'GET',
- path: '/api/v1/tiktok/shop/fetch_product_reviews_v2',
- params: { product_id: '1730213111571944226', country_code: 'US', page: 1, size: 10 } }, // 占位 ID,用来测路径
- ];
- function summarizeJson(j, maxLen = 300) {
- if (!j) return { _empty: true };
- const topKeys = Object.keys(j).slice(0, 8);
- const dataPreview = (() => {
- if (j.data) {
- if (Array.isArray(j.data)) return { _arrayLen: j.data.length, _first: j.data[0] ? Object.keys(j.data[0]).slice(0, 6) : null };
- if (typeof j.data === 'object') return { _keys: Object.keys(j.data).slice(0, 8) };
- }
- return null;
- })();
- return { topKeys, message: j.message || j.msg, code: j.code, error: j.error, dataPreview };
- }
- async function main() {
- console.log('==== TikHub Ultimate Probe ====');
- const results = [];
- let okCount = 0, failCount = 0;
- for (const p of PROBES) {
- process.stdout.write(`[${p.group}] ${p.method} ${p.name} `);
- const resp = await request(p.method, p.path, { params: p.params, body: p.body });
- const ok = resp.status === 200 && !(resp.json && (resp.json.code && resp.json.code !== 0 && resp.json.code !== 200));
- if (ok) okCount++; else failCount++;
- const summary = summarizeJson(resp.json);
- const rec = {
- group: p.group, name: p.name, method: p.method, path: p.path,
- params: p.params || null, body: p.body || null,
- status: resp.status, ok, rawLen: resp.rawLen,
- summary,
- sampleJson: resp.status === 200 ? resp.json : { errorJson: resp.json },
- };
- fs.writeFileSync(path.join(OUT_DIR, `${p.name}.json`), JSON.stringify(rec, null, 2), 'utf-8');
- console.log(`→ ${resp.status}${ok ? ' ✓' : ' ✗'} ${summary.message ? '| ' + String(summary.message).slice(0, 60) : ''}`);
- results.push(rec);
- await sleep(600);
- }
- const overview = results.map((r) => ({
- group: r.group, name: r.name, method: r.method, path: r.path,
- status: r.status, ok: r.ok,
- msg: (r.summary?.message || '').slice(0, 80),
- dataPreview: r.summary?.dataPreview,
- }));
- fs.writeFileSync(path.join(OUT_DIR, '_overview.json'), JSON.stringify(overview, null, 2), 'utf-8');
- console.log('\n==== 汇总 ====');
- console.log(`OK: ${okCount} / Fail: ${failCount} / Total: ${PROBES.length}`);
- console.log('\nByGroup:');
- ['A-douyin-billboard', 'B-tiktok-ads', 'C-tiktok-shop'].forEach((g) => {
- const gs = overview.filter((x) => x.group === g);
- const gOk = gs.filter((x) => x.ok).length;
- console.log(` ${g}: ${gOk}/${gs.length} OK`);
- gs.forEach((x) => console.log(` [${x.ok ? '✓' : '✗'}] ${x.name} ${x.status} ${x.msg || ''}`));
- });
- console.log(`\nDetails → ${OUT_DIR}/_overview.json`);
- }
- main().catch((e) => { console.error(e); process.exit(1); });
|