#!/usr/bin/env node /** 深入探测两条路径的完整 response 结构,锁定抖音搜索可用接口 */ 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 VOC_TOKEN = JSON.parse(fs.readFileSync( path.join(os.homedir(), '.openclaw', 'voc-credentials.json'), 'utf-8' )).vocToken; function req(hostname, pathUrl, method, body, token) { return new Promise((resolve) => { const headers = { Authorization: `Bearer ${token}`, Accept: 'application/json' }; const opts = { hostname, path: pathUrl, method, headers }; if (body) { headers['Content-Type'] = 'application/json'; headers['Content-Length'] = Buffer.byteLength(body); } const r = https.request(opts, (res) => { const chunks = []; res.on('data', (c) => chunks.push(c)); res.on('end', () => resolve({ status: res.statusCode, body: Buffer.concat(chunks).toString('utf-8') })); }); r.on('error', (e) => resolve({ status: 0, body: e.message })); r.setTimeout(30000, () => { r.destroy(); resolve({ status: 0, body: 'timeout' }); }); if (body) r.write(body); r.end(); }); } function summarize(obj, depth = 0, prefix = '') { if (depth > 4) return '...'; if (obj == null) return String(obj); if (Array.isArray(obj)) { if (obj.length === 0) return '[]'; return `Array(${obj.length}) [0]=${summarize(obj[0], depth + 1, prefix + ' ')}`; } if (typeof obj === 'object') { const keys = Object.keys(obj); if (keys.length === 0) return '{}'; const parts = keys.slice(0, 6).map((k) => { const v = obj[k]; if (typeof v === 'string') return `${k}:"${v.slice(0, 30)}"`; if (typeof v === 'number') return `${k}:${v}`; if (typeof v === 'boolean') return `${k}:${v}`; if (Array.isArray(v)) return `${k}:Arr(${v.length})`; if (typeof v === 'object' && v != null) return `${k}:{${Object.keys(v).slice(0, 3).join(',')}}`; return `${k}:${typeof v}`; }); return `{${parts.join(', ')}${keys.length > 6 ? ', ...' : ''}}`; } return typeof obj === 'string' ? `"${obj.slice(0, 40)}"` : String(obj); } (async () => { const kw = '江中猴菇'; console.log(`🔎 keyword = "${kw}"\n`); // —— 1) TikHub direct on fmode /thapi/v1/ proxy —— console.log('━━━ 1) TikHub proxy /thapi/v1 (GET, TikHub token) ━━━'); const r1 = await req('server.fmode.cn', `/thapi/v1/douyin/app/v3/fetch_general_search_result?keyword=${encodeURIComponent(kw)}&offset=0&count=20&sort_type=0&publish_time=0`, 'GET', null, TIKHUB_TOKEN); console.log(`status = ${r1.status}`); if (r1.status === 200) { try { const j = JSON.parse(r1.body); console.log('outer:', summarize(j)); const inner = j.data ?? j; console.log('data:', summarize(inner)); if (inner?.data) console.log('data.data:', summarize(inner.data)); if (inner?.data?.[0]) { const v = inner.data[0]; console.log('sample[0] keys:', Object.keys(v).slice(0, 20).join(', ')); if (v.aweme_info) { console.log('aweme_info:', summarize(v.aweme_info)); } } } catch (e) { console.log('PARSE ERR', e.message, r1.body.slice(0, 300)); } } else { console.log(r1.body.slice(0, 400)); } console.log(); // —— 2) voc-social gateway (POST, voc token) —— console.log('━━━ 2) voc-social gateway (POST, vocToken) ━━━'); const searchBody = JSON.stringify({ keyword: kw, cursor: 0, sort_type: '0', publish_time: '0', filter_duration: '0', content_type: '0', search_id: '', backtrace: '', }); const r2 = await req('server.fmode.cn', '/api/voc-social/douyin/search/fetch_general_search_v2', 'POST', searchBody, VOC_TOKEN); console.log(`status = ${r2.status}`); if (r2.status === 200) { try { const j = JSON.parse(r2.body); console.log('outer:', summarize(j)); if (j?.data) console.log('data:', summarize(j.data)); if (j?.data?.data) console.log('data.data:', summarize(j.data.data)); } catch (e) { console.log('PARSE ERR', e.message, r2.body.slice(0, 300)); } } else { console.log(r2.body.slice(0, 400)); } console.log(); // —— 3) Try official api.tikhub.io directly —— console.log('━━━ 3) api.tikhub.io direct (GET, TikHub token) ━━━'); const r3 = await req('api.tikhub.io', `/api/v1/douyin/app/v3/fetch_general_search_result?keyword=${encodeURIComponent(kw)}&offset=0&count=20&sort_type=0&publish_time=0`, 'GET', null, TIKHUB_TOKEN); console.log(`status = ${r3.status}`); if (r3.status === 200) { try { const j = JSON.parse(r3.body); console.log('outer:', summarize(j)); const inner = j.data ?? j; if (inner?.data) console.log('data.data:', summarize(inner.data)); if (Array.isArray(inner?.data) && inner.data[0]) { const v = inner.data[0]; console.log('first aweme keys:', Object.keys(v).slice(0, 15).join(', ')); if (v.aweme_info || v.aweme_mix_info) { const info = v.aweme_info || v.aweme_mix_info; console.log('aweme_info keys:', Object.keys(info).slice(0, 20).join(', ')); console.log('aweme_id:', info.aweme_id, '| author:', info.author?.nickname, '| stats:', summarize(info.statistics || {})); } } } catch (e) { console.log('PARSE ERR', e.message, r3.body.slice(0, 300)); } } else { console.log(r3.body.slice(0, 400)); } })();