rsshub
Version:
Make RSS Great Again!
558 lines (557 loc) âĸ 26.7 kB
JavaScript
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 += ": ";
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 += " ";
}
description += showEmojiForRetweetAndReply ? "đ" : showSymbolForRetweetAndReply ? "RT" : "";
if (!showAuthorInDesc) {
description += " ";
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 += ": ";
}
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 };