UNPKG

xdl-node

Version:

A library for retrieving audio streams and other data from X Spaces, built on Node.js and TypeScript.

307 lines (306 loc) 12.4 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.TwitterAPI = exports.LiveVideoStreamAPI = exports.FleetsAPI = exports.GraphQLAPI = exports.APIClient = exports.HTTPClient = void 0; // api.ts const axios_1 = __importDefault(require("axios")); const axios_retry_1 = __importDefault(require("axios-retry")); const cookies_1 = require("./cookies"); // Twitter unofficial API authorization header. const TWITTER_AUTHORIZATION = "Bearer AAAAAAAAAAAAAAAAAAAAANRILgAAAAAAnNwIzUejRCOuH5E6I8xnZz4puTs=1Zv7ttfk8LF81IUq16cHjhLTvJu4FA33AGWWjCpTnA"; // Default connection timeout in milliseconds. const TIMEOUT = 20000; /** * HTTPClient – класс для выполнения GET-запросов с помощью axios с поддержкой retry-логики. */ class HTTPClient { constructor() { this.axiosInstance = axios_1.default.create({ timeout: TIMEOUT }); // Добавляем retry‑логику, аналогичную Python‑версии: (0, axios_retry_1.default)(this.axiosInstance, { retries: 5, retryDelay: axios_retry_1.default.exponentialDelay, retryCondition: (error) => { return axios_retry_1.default.isNetworkOrIdempotentRequestError(error) || !!(error.response && [500, 502, 503, 504].includes(error.response.status)); } }); } /** * Выполняет HTTP GET-запрос по указанному URL. * @param url URL запроса. * @param params Параметры запроса. * @param headers Заголовки запроса. * @param cookies Cookies для запроса. * @returns Промис с AxiosResponse. */ async get(url, params = {}, headers = {}, cookies = {}) { try { // Формируем заголовок Cookie из объекта cookies const cookieHeader = Object.entries(cookies) .map(([k, v]) => `${k}=${v}`) .join('; '); const response = await this.axiosInstance.get(url, { params, headers: { ...headers, Cookie: cookieHeader } }); return response; } catch (error) { throw new Error(`HTTP GET request failed for ${url}: ${error.message}`); } } } exports.HTTPClient = HTTPClient; /** * APIClient – базовый класс для всех API. */ class APIClient { /** * @param client Экземпляр HTTPClient. * @param path Путь, добавляемый к базовому URL. * @param cookies Cookies для запросов. */ constructor(client, path, cookies) { (0, cookies_1.validateCookies)(cookies); this.client = client; // Собираем базовый URL: "https://x.com/i/api/<path>" this.baseUrl = this.joinUrl("https://x.com/i/api", path); this.cookies = cookies; this.headers = { "authorization": TWITTER_AUTHORIZATION, "x-csrf-token": cookies.ct0 }; } /** * Объединяет несколько частей URL. * @param paths Части URL. * @returns Собранный URL. */ joinUrl(...paths) { return paths.map(p => p.replace(/^\/+|\/+$/g, '')).join('/'); } /** * Выполняет GET-запрос к API по указанному пути. * @param path Путь запроса. * @param params Параметры запроса. * @returns Распарсенный JSON-ответ. */ async get(path, params = {}) { const url = this.joinUrl(this.baseUrl, path); try { const response = await this.client.get(url, params, this.headers, this.cookies); return response.data; } catch (err) { throw new Error(`API GET request failed for ${url}: ${err.message}`); } } } exports.APIClient = APIClient; /** * GraphQLAPI – клиент для Twitter GraphQL API. */ class GraphQLAPI extends APIClient { constructor(client, path, cookies) { super(client, path, cookies); } _dumpJson(obj) { return typeof obj === 'string' ? obj : JSON.stringify(obj); } /** * Отправляет GraphQL-запрос. * @param query_id Идентификатор запроса. * @param operation_name Имя операции. * @param variables Переменные запроса. * @param features Дополнительные флаги (опционально). * @returns Ответ API. */ async query(query_id, operation_name, variables, features = null) { const params = { variables: this._dumpJson(variables) }; if (features) { params.features = this._dumpJson(features); } const path = this.joinUrl(query_id, operation_name); return await super.get(path, params); } /** * Запрашивает детали Twitter Space по его ID. * @param space_id ID Space. * @returns Ответ API. */ async audioSpaceById(space_id) { const query_id = "xVEzTKg_mLTHubK5ayL0HA"; const operation_name = "AudioSpaceById"; const variables = { id: space_id, isMetatagsQuery: true, withReplays: true, withListeners: true }; const features = { spaces_2022_h2_clipping: true, spaces_2022_h2_spaces_communities: true, responsive_web_graphql_exclude_directive_enabled: true, verified_phone_label_enabled: false, creator_subscriptions_tweet_preview_api_enabled: true, responsive_web_graphql_skip_user_profile_image_extensions_enabled: false, tweetypie_unmention_optimization_enabled: true, responsive_web_edit_tweet_api_enabled: true, graphql_is_translatable_rweb_tweet_is_translatable_enabled: true, view_counts_everywhere_api_enabled: true, longform_notetweets_consumption_enabled: true, responsive_web_twitter_article_tweet_consumption_enabled: false, tweet_awards_web_tipping_enabled: false, freedom_of_speech_not_reach_fetch_enabled: true, standardized_nudges_misinfo: true, tweet_with_visibility_results_prefer_gql_limited_actions_policy_enabled: true, responsive_web_graphql_timeline_navigation_enabled: true, longform_notetweets_rich_text_read_enabled: true, longform_notetweets_inline_media_enabled: true, responsive_web_media_download_video_enabled: false, responsive_web_enhance_cards_enabled: false }; return await this.query(query_id, operation_name, variables, features); } /** * Запрашивает детали пользователя по его screen name. * @param screen_name Имя пользователя. * @returns Ответ API. */ async userByScreenName(screen_name) { const query_id = "oUZZZ8Oddwxs8Cd3iW3UEA"; const operation_name = "UserByScreenName"; const variables = { screen_name, withSafetyModeUserFields: true }; const features = { hidden_profile_likes_enabled: false, responsive_web_graphql_exclude_directive_enabled: true, verified_phone_label_enabled: false, subscriptions_verification_info_verified_since_enabled: true, highlights_tweets_tab_ui_enabled: true, creator_subscriptions_tweet_preview_api_enabled: true, responsive_web_graphql_skip_user_profile_image_extensions_enabled: false, responsive_web_graphql_timeline_navigation_enabled: true }; return await this.query(query_id, operation_name, variables, features); } /** * Backup endpoint для запроса деталей пользователя по screen name. * @param screen_name Имя пользователя. * @returns Ответ API. */ async profileSpotlightsQuery(screen_name) { const query_id = "ZQEuHPrIYlvh1NAyIQHP_w"; const operation_name = "ProfileSpotlightsQuery"; const variables = { screen_name }; return await this.query(query_id, operation_name, variables); } /** * Получает numeric user ID (rest_id) по screen name. * @param screen_name Имя пользователя. * @returns Numeric user ID. */ async userId(screen_name) { try { const data = await this.userByScreenName(screen_name); return data.data.user.result.rest_id; } catch (err) { console.warn("Trying with backup endpoint"); const data = await this.profileSpotlightsQuery(screen_name); return data.data.user_result_by_screen_name.result.rest_id; } } /** * Получает numeric user ID из URL профиля Twitter. * @param user_url URL профиля. * @returns Numeric user ID. */ async userIdFromUrl(user_url) { const regex = /^(?:https?:\/\/)?twitter\.com\/(?<screen_name>\w+)\/?$/; const match = user_url.trim().match(regex); if (match && match.groups && match.groups.screen_name) { return await this.userId(match.groups.screen_name); } throw new Error(`Invalid Twitter user URL: ${user_url}`); } } exports.GraphQLAPI = GraphQLAPI; /** * FleetsAPI – клиент для Twitter Fleets API. */ class FleetsAPI extends APIClient { constructor(client, path, cookies) { super(client, path, cookies); } /** * Выполняет GET-запрос к указанному endpoint Fleets API. * @param version Версия API. * @param endpoint Endpoint API. * @param params Параметры запроса. * @returns Ответ API. */ async fetch(version, endpoint, params) { const urlPath = this.joinUrl(version, endpoint); return await super.get(urlPath, params); } /** * Запрашивает информацию о аватаре для указанных user_ids. * @param user_ids Список user_ids. * @returns Ответ API. */ async avatarContent(...user_ids) { if (user_ids.length > 100) { throw new Error("Number of user IDs exceeded the limit of 100 per request"); } const version = "v1"; const endpoint = "avatar_content"; const params = { user_ids: user_ids.join(','), only_spaces: "true" }; return await this.fetch(version, endpoint, params); } } exports.FleetsAPI = FleetsAPI; /** * LiveVideoStreamAPI – клиент для Twitter Live Video Stream API. */ class LiveVideoStreamAPI extends APIClient { constructor(client, path, cookies) { super(client, path, cookies); } /** * Запрашивает детали плейлиста медиа по media_key. * @param media_key Ключ медиа. * @returns Ответ API. */ async status(media_key) { const urlPath = this.joinUrl("status", media_key); return await super.get(urlPath); } } exports.LiveVideoStreamAPI = LiveVideoStreamAPI; /** * TwitterAPI – коллекция всех Twitter API. */ class TwitterAPI { constructor() { this.client = new HTTPClient(); // Изначально API не инициализированы this.graphql_api = null; this.fleets_api = null; this.live_video_stream_api = null; } /** * Инициализирует все API с использованием cookies. * @param cookies Объект cookies. */ initApis(cookies) { this.graphql_api = new GraphQLAPI(this.client, "graphql", cookies); this.fleets_api = new FleetsAPI(this.client, "fleets", cookies); this.live_video_stream_api = new LiveVideoStreamAPI(this.client, "1.1/live_video_stream", cookies); } } exports.TwitterAPI = TwitterAPI;