UNPKG

rsshub

Version:
558 lines (557 loc) â€ĸ 26.7 kB
import { t as config } from "./config-CCmw1BNE.mjs"; import { t as parseDate } from "./parse-date-Cr0wQjV_.mjs"; import { t as cache_default } from "./cache-BkqOokyU.mjs"; import { t as InvalidParameterError } from "./invalid-parameter-CGxhjomn.mjs"; import { t as ConfigNotFoundError } from "./config-not-found-fQ5mxvDU.mjs"; import { i as queryToInteger, n as queryToBoolean, t as fallback } from "./readable-social-DP5kGwF5.mjs"; import { TwitterApi } from "twitter-api-v2"; //#region lib/routes/twitter/utils.ts const getQueryParams = (url) => Object.fromEntries(new URL(url).searchParams.entries()); const getOriginalImg = (url) => { let m; if (m = url.match(/^(https?:\/\/\w+\.twimg\.com\/media\/[^/:]+)\.(jpg|jpeg|gif|png|bmp|webp)(:\w+)?$/i)) { let format = m[2]; if (m[2] === "jpeg") format = "jpg"; return `${m[1]}?format=${format}&name=orig`; } if (m = url.match(/^(https?:\/\/\w+\.twimg\.com\/[^?]+)(\?.+)$/i)) { const pars = getQueryParams(url); if (!pars.format || !pars.name) return url; if (pars.name === "orig") return url; return m[1] + "?format=" + pars.format + "&name=orig"; } return url; }; const replaceBreak = (text) => text.replaceAll(/<br><br>|<br>/g, " "); const quoteSeparator = "<hr style='border:0;border-top:1px solid #80808030;margin:12px 0;'>"; const formatText = (item) => { let text = item.full_text; const id_str = item.id_str || item.conversation_id_str; const urls = item.entities.urls || []; for (const url of urls) text = text.replaceAll(url.url, () => url.expanded_url?.endsWith(id_str) ? "" : url.expanded_url); const media = item.extended_entities?.media || []; for (const m of media) text = text.replaceAll(m.url, ""); return text.trim().replaceAll("\n", "<br>"); }; const ProcessFeed = (ctx, { data = [] }, params = {}) => { const routeParams = new URLSearchParams(ctx.req.param("routeParams")); params = { readable: fallback(params.readable, queryToBoolean(routeParams.get("readable")), false), authorNameBold: fallback(params.authorNameBold, queryToBoolean(routeParams.get("authorNameBold")), false), showAuthorInTitle: fallback(params.showAuthorInTitle, queryToBoolean(routeParams.get("showAuthorInTitle")), false), showAuthorAsTitleOnly: fallback(params.showAuthorAsTitleOnly, queryToBoolean(routeParams.get("showAuthorAsTitleOnly")), false), showAuthorInDesc: fallback(params.showAuthorInDesc, queryToBoolean(routeParams.get("showAuthorInDesc")), false), showQuotedAuthorAvatarInDesc: fallback(params.showQuotedAuthorAvatarInDesc, queryToBoolean(routeParams.get("showQuotedAuthorAvatarInDesc")), false), showAuthorAvatarInDesc: fallback(params.showAuthorAvatarInDesc, queryToBoolean(routeParams.get("showAuthorAvatarInDesc")), false), showEmojiForRetweetAndReply: fallback(params.showEmojiForRetweetAndReply, queryToBoolean(routeParams.get("showEmojiForRetweetAndReply")), false), showSymbolForRetweetAndReply: fallback(params.showSymbolForRetweetAndReply, queryToBoolean(routeParams.get("showSymbolForRetweetAndReply")), true), showRetweetTextInTitle: fallback(params.showRetweetTextInTitle, queryToBoolean(routeParams.get("showRetweetTextInTitle")), true), addLinkForPics: fallback(params.addLinkForPics, queryToBoolean(routeParams.get("addLinkForPics")), false), showTimestampInDescription: fallback(params.showTimestampInDescription, queryToBoolean(routeParams.get("showTimestampInDescription")), false), showQuotedInTitle: fallback(params.showQuotedInTitle, queryToBoolean(routeParams.get("showQuotedInTitle")), false), widthOfPics: fallback(params.widthOfPics, queryToInteger(routeParams.get("widthOfPics")), -1), heightOfPics: fallback(params.heightOfPics, queryToInteger(routeParams.get("heightOfPics")), -1), sizeOfAuthorAvatar: fallback(params.sizeOfAuthorAvatar, queryToInteger(routeParams.get("sizeOfAuthorAvatar")), 48), sizeOfQuotedAuthorAvatar: fallback(params.sizeOfQuotedAuthorAvatar, queryToInteger(routeParams.get("sizeOfQuotedAuthorAvatar")), 24), mediaNumber: fallback(params.mediaNumber, queryToInteger(routeParams.get("mediaNumber")), false), showEmojiForSubscriberOnly: fallback(params.showEmojiForSubscriberOnly, queryToBoolean(routeParams.get("showEmojiForSubscriberOnly")), false), showSymbolForSubscriberOnly: fallback(params.showSymbolForSubscriberOnly, queryToBoolean(routeParams.get("showSymbolForSubscriberOnly")), true), showFullPrefixForSubscriberOnly: fallback(params.showFullPrefixForSubscriberOnly, queryToBoolean(routeParams.get("showFullPrefixForSubscriberOnly")), false) }; const { readable, authorNameBold, showAuthorInTitle, showAuthorAsTitleOnly, showAuthorInDesc, showQuotedAuthorAvatarInDesc, showAuthorAvatarInDesc, showEmojiForRetweetAndReply, showSymbolForRetweetAndReply, showRetweetTextInTitle, addLinkForPics, showTimestampInDescription, showQuotedInTitle, mediaNumber, showEmojiForSubscriberOnly, showSymbolForSubscriberOnly, showFullPrefixForSubscriberOnly, widthOfPics, heightOfPics, sizeOfAuthorAvatar, sizeOfQuotedAuthorAvatar } = params; const formatVideo = (media, extraAttrs = "") => { let content = ""; let bestVideo = null; for (const item of media.video_info.variants) if (!bestVideo || (item.bitrate || 0) > (bestVideo.bitrate || -Infinity)) bestVideo = item; if (bestVideo && bestVideo.url) { const gifAutoPlayAttr = media.type === "animated_gif" ? "autoplay loop muted webkit-playsinline playsinline" : ""; if (!readable) content += "<br>"; content += `<video width="${media.sizes.large.w}" height="${media.sizes.large.h}" src='${bestVideo.url}' ${gifAutoPlayAttr} controls='controls' poster='${getOriginalImg(media.media_url_https)}' ${extraAttrs}></video>`; } return content; }; const formatMedia = (item) => { let img = ""; if (item.extended_entities) { const mediaCount = item.extended_entities.media.length; let index = 1; for (const media of item.extended_entities.media) { let content = ""; let style = ""; let originalImg; switch (media.type) { case "animated_gif": case "video": content = formatVideo(media); break; default: originalImg = getOriginalImg(media.media_url_https); if (!readable) content += "<br>"; if (addLinkForPics) content += `<a href='${originalImg}' target='_blank' rel='noopener noreferrer'>`; content += "<img "; if (widthOfPics >= 0) { content += ` width="${widthOfPics}"`; style += `width: ${widthOfPics}px;`; } if (heightOfPics > 0) { content += `height="${heightOfPics}" `; style += `height: ${heightOfPics}px;`; } if (widthOfPics <= 0 && heightOfPics <= 0) content += `width="${media.sizes.large.w}" height="${media.sizes.large.h}" `; content += ` style="${style}" ${readable ? "hspace=\"4\" vspace=\"8\"" : ""} src="${originalImg}">`; if (addLinkForPics) content += "</a>"; break; } img += content; if (mediaNumber) { img += `<p style="text-align:center">${index}/${mediaCount}</p>`; index++; } } } if (readable && img) img = `<br clear='both' /><div style='clear: both'></div>` + img; return img; }; const generatePicsPrefix = (item) => { let picsPrefix = ""; if (item.extended_entities) for (const media of item.extended_entities.media) { let content; let originalImg; switch (media.type) { case "video": content = formatVideo(media, "width=\"0\" height=\"0\""); break; default: originalImg = getOriginalImg(media.media_url_https); content = `<img width='0' height='0' hidden='true' src='${originalImg}'>`; break; } picsPrefix += content; } return picsPrefix; }; return data.map((item) => { if (item.full_text?.startsWith("[Subscribers Only]")) { const text = item.full_text.replace("[Subscribers Only] ", ""); item.full_text = `${showFullPrefixForSubscriberOnly ? "🔒 [Subscribers Only] " : showEmojiForSubscriberOnly ? "🔒 " : showSymbolForSubscriberOnly ? "[Subscribers Only] " : ""}${text}`; } const originalItem = item; item = item.retweeted_status || item; item.full_text ||= item.text; item.full_text = formatText(item); const img = formatMedia(item); let picsPrefix = generatePicsPrefix(item); let quote = ""; let quoteInTitle = ""; if (item.is_quote_status) { const quoteData = item.quoted_status; if (quoteData?.user) { quoteData.full_text ||= quoteData.text; const author = quoteData.user; if (!readable) quote += quoteSeparator; quote += "<div class=\"rsshub-quote\">"; if (readable) { quote += `<br clear='both' /><div style='clear: both'></div>`; quote += `<blockquote style='background: #80808010;border-top:1px solid #80808030;border-bottom:1px solid #80808030;margin:0;padding:5px 20px;'>`; } if (readable) quote += `<a href='https://x.com/${author.screen_name}' target='_blank' rel='noopener noreferrer'>`; if (showQuotedAuthorAvatarInDesc) quote += `<img width='${sizeOfQuotedAuthorAvatar}' height='${sizeOfQuotedAuthorAvatar}' src='${author.profile_image_url_https}' ${readable ? "hspace=\"8\" vspace=\"8\" align=\"left\"" : ""}>`; if (authorNameBold) quote += "<strong>"; quote += author.name; if (authorNameBold) quote += "</strong>"; if (readable) quote += "</a>"; quote += ":&ensp;"; quote += formatText(quoteData); if (!readable) quote += "<br>"; quote += formatMedia(quoteData); picsPrefix += generatePicsPrefix(quoteData); quoteInTitle += showEmojiForRetweetAndReply ? " đŸ’Ŧ " : showSymbolForRetweetAndReply ? " RT " : ""; quoteInTitle += `${author.name}: ${formatText(quoteData)}`; if (readable) quote += `<br><small>Link: <a href='https://x.com/${author.screen_name}/status/${quoteData.id_str || quoteData.conversation_id_str}' target='_blank' rel='noopener noreferrer'>https://x.com/${author.screen_name}/status/${quoteData.id_str || quoteData.conversation_id_str}</a></small>`; if (showTimestampInDescription) { quote += "<br><small>" + parseDate(quoteData.created_at); quote += "</small>"; if (readable) quote += `<br clear='both' /><div style='clear: both'></div>`; } if (readable) quote += "</blockquote>"; quote += "</div>"; } } let title = ""; if (showAuthorInTitle) title += originalItem.user?.name + ": "; const isRetweet = originalItem !== item; const isQuote = item.is_quote_status; if (!isRetweet && (!isQuote || showRetweetTextInTitle)) { if (item.in_reply_to_screen_name) title += showEmojiForRetweetAndReply ? "â†Šī¸ " : showSymbolForRetweetAndReply ? "Re " : ""; title += replaceBreak(originalItem.full_text); } if (isRetweet) { title += showEmojiForRetweetAndReply ? "🔁 " : showSymbolForRetweetAndReply ? "RT " : ""; title += item.user.name + ": "; if (item.in_reply_to_screen_name) title += showEmojiForRetweetAndReply ? " â†Šī¸ " : showSymbolForRetweetAndReply ? " Re " : ""; title += replaceBreak(item.full_text); } if (showQuotedInTitle) title += quoteInTitle; if (showAuthorAsTitleOnly) title = originalItem.user?.name; let description = ""; if (showAuthorInDesc && showAuthorAvatarInDesc) description += picsPrefix; if (isRetweet) { if (showAuthorInDesc) { if (readable) { description += "<small>"; description += `<a href='https://x.com/${originalItem.user?.screen_name}' target='_blank' rel='noopener noreferrer'>`; } if (authorNameBold) description += "<strong>"; description += originalItem.user?.name; if (authorNameBold) description += "</strong>"; if (readable) description += "</a>"; description += "&ensp;"; } description += showEmojiForRetweetAndReply ? "🔁" : showSymbolForRetweetAndReply ? "RT" : ""; if (!showAuthorInDesc) { description += "&ensp;"; if (readable) description += `<a href='https://x.com/${item.user?.screen_name}' target='_blank' rel='noopener noreferrer'>`; if (authorNameBold) description += "<strong>"; description += item.user?.name; if (authorNameBold) description += "</strong>"; if (readable) description += "</a>"; } if (readable) description += "</small>"; description += "<br>"; } if (showAuthorInDesc) { if (readable) description += `<a href='https://x.com/${item.user?.screen_name}' target='_blank' rel='noopener noreferrer'>`; if (showAuthorAvatarInDesc) description += `<img width='${sizeOfAuthorAvatar}' height='${sizeOfAuthorAvatar}' src='${item.user.profile_image_url_https}' ${readable ? "hspace=\"8\" vspace=\"8\" align=\"left\"" : ""}>`; if (authorNameBold) description += "<strong>"; description += item.user?.name; if (authorNameBold) description += "</strong>"; if (readable) description += "</a>"; description += ":&ensp;"; } if (item.in_reply_to_screen_name) description += showEmojiForRetweetAndReply ? "â†Šī¸ " : showSymbolForRetweetAndReply ? "Re " : ""; description += item.full_text; const category = description.match(/(\s)?(#[^\s;<]+)/g)?.map((e) => e?.match(/#([^\s<]+)/)?.[1]); description += img; description += quote; if (readable) description += `<br clear='both' /><div style='clear: both'></div>`; if (showTimestampInDescription) { if (readable) description += "<hr>"; description += `<small>${parseDate(item.created_at)}</small>`; } const link = originalItem.user?.screen_name && (originalItem.id_str || originalItem.conversation_id_str) ? `https://x.com/${originalItem.user?.screen_name}/status/${originalItem.id_str || originalItem.conversation_id_str}` : `https://x.com/${item.user?.screen_name}/status/${item.id_str || item.conversation_id_str}`; return { title, author: [{ name: originalItem.user?.name, url: `https://x.com/${originalItem.user?.screen_name}`, avatar: originalItem.user?.profile_image_url_https }], description, pubDate: parseDate(item.created_at), link, guid: link.replace("x.com", "twitter.com"), category, _extra: isRetweet && { links: [{ url: `https://x.com/${item.user?.screen_name}/status/${item.conversation_id_str}`, type: "repost" }] } || item.is_quote_status && item.quoted_status?.user && { links: [{ url: `https://x.com/${item.quoted_status?.user?.screen_name}/status/${item.quoted_status?.id_str || item.quoted_status?.conversation_id_str}`, type: "quote" }] } || item.in_reply_to_screen_name && item.in_reply_to_status_id_str && { links: [{ url: `https://x.com/${item.in_reply_to_screen_name}/status/${item.in_reply_to_status_id_str}`, type: "reply" }] } }; }); }; const parseRouteParams = (routeParams) => { let count, include_replies, include_rts, only_media; let force_web_api = false; switch (routeParams) { case "exclude_rts_replies": case "exclude_replies_rts": include_replies = false; include_rts = false; break; case "include_replies": include_replies = true; include_rts = true; break; case "exclude_rts": include_replies = false; include_rts = false; break; default: { const parsed = new URLSearchParams(routeParams); count = fallback(void 0, queryToInteger(parsed.get("count")), void 0); include_replies = fallback(void 0, queryToBoolean(parsed.get("includeReplies")), false); include_rts = fallback(void 0, queryToBoolean(parsed.get("includeRts")), true); force_web_api = fallback(void 0, queryToBoolean(parsed.get("forceWebApi")), false); only_media = fallback(void 0, queryToBoolean(parsed.get("onlyMedia")), false); } } return { count, include_replies, include_rts, force_web_api, only_media }; }; const excludeRetweet = function(tweets) { const excluded = []; for (const t of tweets) { if (t.retweeted_status) continue; excluded.push(t); } return excluded; }; const keepOnlyMedia = function(tweets) { return tweets.filter((t) => t.extended_entities && t.extended_entities.media); }; const getTwitterUserCacheKey = (id, operationName, params) => `twitter:${id}:${operationName}:${JSON.stringify(params)}`; var utils_default = { ProcessFeed, parseRouteParams, excludeRetweet, keepOnlyMedia }; //#endregion //#region lib/routes/twitter/api/developer-api/api.ts const appClients = []; let index = -1; const init = () => { if (appClients.length) return; if (!config.twitter.consumerKey || !config.twitter.consumerSecret) return; const consumerKeys = config.twitter.consumerKey.split(","); const consumerSecrets = config.twitter.consumerSecret.split(","); const accessTokens = config.twitter.accessToken?.split(",") || []; const accessSecrets = config.twitter.accessSecret?.split(",") || []; for (const [index, consumerKey] of consumerKeys.entries()) { const consumerSecret = consumerSecrets[index]; const accessToken = accessTokens[index]; const accessSecret = accessSecrets[index]; if (!consumerKey || !consumerSecret) continue; if (accessToken && accessSecret) appClients.push({ client: new TwitterApi({ appKey: consumerKey, appSecret: consumerSecret, accessToken, accessSecret }).readOnly, isUserAuth: true }); else appClients.push({ client: new TwitterApi({ appKey: consumerKey, appSecret: consumerSecret }).readOnly, isUserAuth: false }); } }; const getAppClient = async () => { init(); if (!appClients.length) throw new ConfigNotFoundError("Twitter API is not configured"); index += 1; const currentWrapper = appClients[index % appClients.length]; return currentWrapper.isUserAuth ? currentWrapper.client : await currentWrapper.client.appLogin(); }; const mapUserToLegacy = (user) => user ? { id_str: user.id, name: user.name, screen_name: user.username, description: user.description, profile_image_url: user.profile_image_url, profile_image_url_https: user.profile_image_url, url: user.url, verified: user.verified } : null; const mapUrlsToLegacy = (urls = []) => urls.map((url) => ({ url: url.url, expanded_url: url.expanded_url ?? url.unwound_url ?? url.url, display_url: url.display_url ?? url.url })); const mapHashtagsToLegacy = (hashtags = []) => hashtags.map((hashtag) => ({ text: hashtag.tag })); const mapMentionsToLegacy = (mentions = []) => mentions.map((mention) => ({ id_str: mention.id, screen_name: mention.username, name: mention.username })); const mapMediaToLegacy = (media) => { const url = media.url ?? media.preview_image_url; const mapped = { id_str: media.media_key, type: media.type, media_url_https: url, media_url: url, url, sizes: { large: { w: media.width ?? 0, h: media.height ?? 0, resize: "fit" } } }; if (media.variants?.length) mapped.video_info = { variants: media.variants.map((variant) => ({ bitrate: variant.bit_rate, content_type: variant.content_type, url: variant.url })) }; return mapped; }; const mapTweetToLegacy = (tweet, includes, cacheMap) => { if (cacheMap.has(tweet.id)) return cacheMap.get(tweet.id); const users = new Map((includes?.users ?? []).map((user) => [user.id, user])); const tweets = new Map((includes?.tweets ?? []).map((item) => [item.id, item])); const media = new Map((includes?.media ?? []).map((item) => [item.media_key, item])); const user = users.get(tweet.author_id); const legacyUser = mapUserToLegacy(user); const legacy = { id_str: tweet.id, conversation_id_str: tweet.conversation_id, full_text: tweet.text, text: tweet.text, created_at: tweet.created_at, entities: { urls: mapUrlsToLegacy(tweet.entities?.urls), hashtags: mapHashtagsToLegacy(tweet.entities?.hashtags), user_mentions: mapMentionsToLegacy(tweet.entities?.mentions), symbols: [] }, extended_entities: { media: (tweet.attachments?.media_keys ?? []).map((key) => media.get(key)).filter(Boolean).map((item) => mapMediaToLegacy(item)) }, user: legacyUser, user_id_str: tweet.author_id, in_reply_to_user_id_str: tweet.in_reply_to_user_id }; cacheMap.set(tweet.id, legacy); const referencedTweets = tweet.referenced_tweets ?? []; for (const reference of referencedTweets) { const referenced = tweets.get(reference.id); if (!referenced) continue; const mappedReferenced = mapTweetToLegacy(referenced, includes, cacheMap); switch (reference.type) { case "retweeted": legacy.retweeted_status = mappedReferenced; break; case "quoted": legacy.quoted_status = mappedReferenced; break; case "replied_to": legacy.in_reply_to_status_id_str = reference.id; legacy.in_reply_to_user_id_str = referenced.author_id; legacy.in_reply_to_screen_name = users.get(referenced.author_id)?.username; break; default: } } if (!legacy.extended_entities.media?.length) delete legacy.extended_entities; return legacy; }; const mapTweetResponseToLegacy = (response) => { const cacheMap = /* @__PURE__ */ new Map(); return (response?.data ?? []).map((tweet) => mapTweetToLegacy(tweet, response.includes, cacheMap)); }; const getUserData = (id) => cache_default.tryGet(`twitter-userdata-${id}`, async () => { const client = await getAppClient(); const params = { "user.fields": "profile_image_url,description,verified,url" }; const response = id.startsWith("+") ? await client.v2.user(id.slice(1), params) : await client.v2.userByUsername(id, params); return mapUserToLegacy(response?.data) ?? ""; }); const cacheTryGet = async (_id, params, operationName, func) => { const id = (await getUserData(_id))?.id_str; if (id === void 0) { cache_default.set(`twitter-userdata-${_id}`, "", config.cache.contentExpire); throw new InvalidParameterError("User not found"); } return cache_default.tryGet(getTwitterUserCacheKey(id, operationName, params), () => func(id, params), config.cache.routeExpire, false); }; const getUserTimeline = async (id, params, options = {}) => { const response = await (await getAppClient()).v2.get(`users/${id}/tweets`, { max_results: params?.count ?? 20, expansions: "author_id,attachments.media_keys,referenced_tweets.id,referenced_tweets.id.author_id", "tweet.fields": "created_at,entities,conversation_id,referenced_tweets,author_id,in_reply_to_user_id", "user.fields": "username,name,profile_image_url,description", "media.fields": "preview_image_url,url,type,width,height,variants", ...options }); return mapTweetResponseToLegacy(response); }; const getUserTweets = (id, params) => cacheTryGet(id, params, "getUserTweets", (id, params = {}) => getUserTimeline(id, params, { exclude: "replies" })); const getUserTweetsAndReplies = (id, params) => cacheTryGet(id, params, "getUserTweetsAndReplies", (id, params = {}) => getUserTimeline(id, params)); const getUserMedia = (id, params) => cacheTryGet(id, params, "getUserMedia", async (id, params = {}) => { return (await getUserTimeline(id, params)).filter((tweet) => tweet.extended_entities?.media); }); const getUserLikes = (id, params) => cacheTryGet(id, params, "getUserLikes", async (id, params = {}) => { const response = await (await getAppClient()).v2.get(`users/${id}/liked_tweets`, { max_results: params.count ?? 20, expansions: "author_id,attachments.media_keys,referenced_tweets.id,referenced_tweets.id.author_id", "tweet.fields": "created_at,entities,conversation_id,referenced_tweets,author_id,in_reply_to_user_id", "user.fields": "username,name,profile_image_url,description", "media.fields": "preview_image_url,url,type,width,height,variants" }); return mapTweetResponseToLegacy(response); }); const getUserTweet = (id, params) => cacheTryGet(id, params, "getUserTweet", async (_id, params = {}) => { const client = await getAppClient(); const tweetId = params.focalTweetId; if (!tweetId) throw new InvalidParameterError("Tweet ID is required"); const response = await client.v2.get(`tweets/${tweetId}`, { expansions: "author_id,attachments.media_keys,referenced_tweets.id,referenced_tweets.id.author_id", "tweet.fields": "created_at,entities,conversation_id,referenced_tweets,author_id,in_reply_to_user_id", "user.fields": "username,name,profile_image_url,description", "media.fields": "preview_image_url,url,type,width,height,variants" }); return mapTweetResponseToLegacy({ data: response?.data ? [response.data] : [], includes: response?.includes }); }); const getSearch = (keywords, params) => cache_default.tryGet(`twitter:search:${keywords}:${JSON.stringify(params)}`, async () => { const response = await (await getAppClient()).v2.get("tweets/search/recent", { query: keywords, max_results: params?.count ?? 20, expansions: "author_id,attachments.media_keys,referenced_tweets.id,referenced_tweets.id.author_id", "tweet.fields": "created_at,entities,conversation_id,referenced_tweets,author_id,in_reply_to_user_id", "user.fields": "username,name,profile_image_url,description", "media.fields": "preview_image_url,url,type,width,height,variants" }); return mapTweetResponseToLegacy(response); }, config.cache.routeExpire, false); const getList = (id, params) => cache_default.tryGet(`twitter:list:${id}:${JSON.stringify(params)}`, async () => { const response = await (await getAppClient()).v2.get(`lists/${id}/tweets`, { max_results: params?.count ?? 20, expansions: "author_id,attachments.media_keys,referenced_tweets.id,referenced_tweets.id.author_id", "tweet.fields": "created_at,entities,conversation_id,referenced_tweets,author_id,in_reply_to_user_id", "user.fields": "username,name,profile_image_url,description", "media.fields": "preview_image_url,url,type,width,height,variants" }); return mapTweetResponseToLegacy(response); }, config.cache.routeExpire, false); const getHomeTimeline = (_id, params) => cache_default.tryGet(`twitter:home:${JSON.stringify(params)}`, async () => { if (!_id) throw new InvalidParameterError("User ID is required for the v2 home timeline"); const response = await (await getAppClient()).v2.get(`users/${_id}/timelines/reverse_chronological`, { max_results: params?.count ?? 20, expansions: "author_id,attachments.media_keys,referenced_tweets.id,referenced_tweets.id.author_id", "tweet.fields": "created_at,entities,conversation_id,referenced_tweets,author_id,in_reply_to_user_id", "user.fields": "username,name,profile_image_url,description", "media.fields": "preview_image_url,url,type,width,height,variants" }); return mapTweetResponseToLegacy(response); }, config.cache.routeExpire, false); const getHomeLatestTimeline = (id, params) => getHomeTimeline(id, params); const getUser = (id) => getUserData(id); var api_default = { getUser, getUserTweets, getUserTweetsAndReplies, getUserMedia, getUserLikes, getUserTweet, getSearch, getList, getHomeTimeline, getHomeLatestTimeline, init }; //#endregion export { utils_default as i, getAppClient as n, getTwitterUserCacheKey as r, api_default as t };