probe_douyin_media_from_search.js 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
  1. #!/usr/bin/env node
  2. const { DouyinApi } = require('../claude-code/claude-code-voc-intelligence/mcp/src/providers/douyin-api');
  3. function parseArgs(argv) {
  4. const args = {};
  5. for (let i = 0; i < argv.length; i += 1) {
  6. const token = argv[i];
  7. if (!token.startsWith('--')) continue;
  8. const eq = token.indexOf('=');
  9. if (eq > 0) {
  10. args[token.slice(2, eq)] = token.slice(eq + 1);
  11. continue;
  12. }
  13. const key = token.slice(2);
  14. const next = argv[i + 1];
  15. if (next && !next.startsWith('--')) {
  16. args[key] = next;
  17. i += 1;
  18. } else {
  19. args[key] = true;
  20. }
  21. }
  22. return args;
  23. }
  24. function decodeMaybeBase64Url(value) {
  25. const text = String(value || '').trim();
  26. if (!text) return '';
  27. if (/^https?:\/\//i.test(text)) return text;
  28. if (!/^[A-Za-z0-9+/=_-]+$/.test(text) || text.length < 24) return '';
  29. try {
  30. const decoded = Buffer.from(text.replace(/-/g, '+').replace(/_/g, '/'), 'base64').toString('utf8');
  31. return /^https?:\/\//i.test(decoded) ? decoded : '';
  32. } catch {
  33. return '';
  34. }
  35. }
  36. function collectUrls(node, path = [], out = []) {
  37. if (!node) return out;
  38. if (Array.isArray(node)) {
  39. node.forEach((item, index) => collectUrls(item, [...path, String(index)], out));
  40. return out;
  41. }
  42. if (typeof node !== 'object') {
  43. if (typeof node === 'string') {
  44. const url = decodeMaybeBase64Url(node);
  45. if (url) out.push({ url, path: path.join('.') });
  46. }
  47. return out;
  48. }
  49. Object.entries(node).forEach(([key, value]) => collectUrls(value, [...path, key], out));
  50. return out;
  51. }
  52. function isMediaUrl(url) {
  53. const lower = String(url || '').toLowerCase();
  54. return lower.includes('douyinvod.com')
  55. || lower.includes('/aweme/v1/play')
  56. || lower.includes('mime_type=video')
  57. || /\.(mp4|m4a|mp3|aac|wav|webm)(?:\?|$)/i.test(lower);
  58. }
  59. function findAweme(node, awemeId, depth = 0) {
  60. if (!node || depth > 8) return null;
  61. if (Array.isArray(node)) {
  62. for (const item of node) {
  63. const found = findAweme(item, awemeId, depth + 1);
  64. if (found) return found;
  65. }
  66. return null;
  67. }
  68. if (typeof node !== 'object') return null;
  69. const id = String(node.aweme_id || node.id || '');
  70. if (id === String(awemeId)) return node;
  71. for (const value of Object.values(node)) {
  72. const found = findAweme(value, awemeId, depth + 1);
  73. if (found) return found;
  74. }
  75. return null;
  76. }
  77. async function main() {
  78. const args = parseArgs(process.argv.slice(2));
  79. const token = process.env.VOC_TOKEN || process.env.VOC_DOUYIN_TOKEN || process.env.DOUYIN_TOKEN;
  80. if (!token) throw new Error('VOC_TOKEN is required in environment');
  81. if (!args.keyword) throw new Error('--keyword is required');
  82. const api = new DouyinApi({ token });
  83. const response = await api.searchVideos({
  84. keyword: args.keyword,
  85. cursor: Number(args.cursor || 0),
  86. sortType: args.sortType || '1',
  87. publishTime: args.publishTime || '180',
  88. filterDuration: args.filterDuration || '0',
  89. contentType: args.contentType || '1',
  90. });
  91. const target = args.awemeId ? findAweme(response.data, args.awemeId) : response.data;
  92. const urls = collectUrls(target || response.data)
  93. .filter(item => isMediaUrl(item.url))
  94. .filter((item, index, list) => list.findIndex(other => other.url === item.url) === index)
  95. .slice(0, 20);
  96. console.log(JSON.stringify({
  97. status: 'ok',
  98. foundTarget: Boolean(target),
  99. mediaUrlCount: urls.length,
  100. mediaCandidates: urls.map(item => ({
  101. path: item.path,
  102. urlPrefix: item.url.slice(0, 180),
  103. })),
  104. }, null, 2));
  105. }
  106. main().catch(error => {
  107. console.error(JSON.stringify({ status: 'error', message: error.message }, null, 2));
  108. process.exit(1);
  109. });