| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119 |
- #!/usr/bin/env node
- const { DouyinApi } = require('../claude-code/claude-code-voc-intelligence/mcp/src/providers/douyin-api');
- function parseArgs(argv) {
- const args = {};
- for (let i = 0; i < argv.length; i += 1) {
- const token = argv[i];
- if (!token.startsWith('--')) continue;
- const eq = token.indexOf('=');
- if (eq > 0) {
- args[token.slice(2, eq)] = token.slice(eq + 1);
- continue;
- }
- const key = token.slice(2);
- const next = argv[i + 1];
- if (next && !next.startsWith('--')) {
- args[key] = next;
- i += 1;
- } else {
- args[key] = true;
- }
- }
- return args;
- }
- function decodeMaybeBase64Url(value) {
- const text = String(value || '').trim();
- if (!text) return '';
- if (/^https?:\/\//i.test(text)) return text;
- if (!/^[A-Za-z0-9+/=_-]+$/.test(text) || text.length < 24) return '';
- try {
- const decoded = Buffer.from(text.replace(/-/g, '+').replace(/_/g, '/'), 'base64').toString('utf8');
- return /^https?:\/\//i.test(decoded) ? decoded : '';
- } catch {
- return '';
- }
- }
- function collectUrls(node, path = [], out = []) {
- if (!node) return out;
- if (Array.isArray(node)) {
- node.forEach((item, index) => collectUrls(item, [...path, String(index)], out));
- return out;
- }
- if (typeof node !== 'object') {
- if (typeof node === 'string') {
- const url = decodeMaybeBase64Url(node);
- if (url) out.push({ url, path: path.join('.') });
- }
- return out;
- }
- Object.entries(node).forEach(([key, value]) => collectUrls(value, [...path, key], out));
- return out;
- }
- function isMediaUrl(url) {
- const lower = String(url || '').toLowerCase();
- return lower.includes('douyinvod.com')
- || lower.includes('/aweme/v1/play')
- || lower.includes('mime_type=video')
- || /\.(mp4|m4a|mp3|aac|wav|webm)(?:\?|$)/i.test(lower);
- }
- function findAweme(node, awemeId, depth = 0) {
- if (!node || depth > 8) return null;
- if (Array.isArray(node)) {
- for (const item of node) {
- const found = findAweme(item, awemeId, depth + 1);
- if (found) return found;
- }
- return null;
- }
- if (typeof node !== 'object') return null;
- const id = String(node.aweme_id || node.id || '');
- if (id === String(awemeId)) return node;
- for (const value of Object.values(node)) {
- const found = findAweme(value, awemeId, depth + 1);
- if (found) return found;
- }
- return null;
- }
- async function main() {
- const args = parseArgs(process.argv.slice(2));
- const token = process.env.VOC_TOKEN || process.env.VOC_DOUYIN_TOKEN || process.env.DOUYIN_TOKEN;
- if (!token) throw new Error('VOC_TOKEN is required in environment');
- if (!args.keyword) throw new Error('--keyword is required');
- const api = new DouyinApi({ token });
- const response = await api.searchVideos({
- keyword: args.keyword,
- cursor: Number(args.cursor || 0),
- sortType: args.sortType || '1',
- publishTime: args.publishTime || '180',
- filterDuration: args.filterDuration || '0',
- contentType: args.contentType || '1',
- });
- const target = args.awemeId ? findAweme(response.data, args.awemeId) : response.data;
- const urls = collectUrls(target || response.data)
- .filter(item => isMediaUrl(item.url))
- .filter((item, index, list) => list.findIndex(other => other.url === item.url) === index)
- .slice(0, 20);
- console.log(JSON.stringify({
- status: 'ok',
- foundTarget: Boolean(target),
- mediaUrlCount: urls.length,
- mediaCandidates: urls.map(item => ({
- path: item.path,
- urlPrefix: item.url.slice(0, 180),
- })),
- }, null, 2));
- }
- main().catch(error => {
- console.error(JSON.stringify({ status: 'error', message: error.message }, null, 2));
- process.exit(1);
- });
|