/ clis / twitter / shared.js
shared.js
 1  const QUERY_ID_PATTERN = /^[A-Za-z0-9_-]+$/;
 2  export function sanitizeQueryId(resolved, fallbackId) {
 3      return typeof resolved === 'string' && QUERY_ID_PATTERN.test(resolved) ? resolved : fallbackId;
 4  }
 5  export async function resolveTwitterQueryId(page, operationName, fallbackId) {
 6      const resolved = await page.evaluate(`async () => {
 7      const operationName = ${JSON.stringify(operationName)};
 8      const controller = new AbortController();
 9      const timeout = setTimeout(() => controller.abort(), 5000);
10      try {
11        const ghResp = await fetch('https://raw.githubusercontent.com/fa0311/twitter-openapi/refs/heads/main/src/config/placeholder.json', { signal: controller.signal });
12        clearTimeout(timeout);
13        if (ghResp.ok) {
14          const data = await ghResp.json();
15          const entry = data?.[operationName];
16          if (entry && entry.queryId) return entry.queryId;
17        }
18      } catch {
19        clearTimeout(timeout);
20      }
21      try {
22        const scripts = performance.getEntriesByType('resource')
23          .filter(r => r.name.includes('client-web') && r.name.endsWith('.js'))
24          .map(r => r.name);
25        for (const scriptUrl of scripts.slice(0, 15)) {
26          try {
27            const text = await (await fetch(scriptUrl)).text();
28            const re = new RegExp('queryId:"([A-Za-z0-9_-]+)"[^}]{0,200}operationName:"' + operationName + '"');
29            const match = text.match(re);
30            if (match) return match[1];
31          } catch {}
32        }
33      } catch {}
34      return null;
35    }`);
36      return sanitizeQueryId(resolved, fallbackId);
37  }
38  /**
39   * Extract media flags and URLs from a tweet's `legacy` object.
40   *
41   * Prefers `extended_entities.media` (superset with full video_info) and falls
42   * back to `entities.media` when the extended form is missing. For videos and
43   * animated GIFs, returns the mp4 variant URL; for photos, returns
44   * `media_url_https`.
45   */
46  export function extractMedia(legacy) {
47      const media = legacy?.extended_entities?.media || legacy?.entities?.media;
48      if (!Array.isArray(media) || media.length === 0) {
49          return { has_media: false, media_urls: [] };
50      }
51      const urls = [];
52      for (const m of media) {
53          if (!m) continue;
54          if (m.type === 'video' || m.type === 'animated_gif') {
55              const variants = m.video_info?.variants || [];
56              const mp4 = variants.find((v) => v?.content_type === 'video/mp4');
57              const url = mp4?.url || m.media_url_https;
58              if (url) urls.push(url);
59          } else {
60              if (m.media_url_https) urls.push(m.media_url_https);
61          }
62      }
63      return { has_media: urls.length > 0, media_urls: urls };
64  }
65  export const __test__ = {
66      sanitizeQueryId,
67      extractMedia,
68  };