rsshub
Version:
Make RSS Great Again!
330 lines (328 loc) âĸ 15.5 kB
JavaScript
import { t as config } from "./config-MQ-7ebJ_.mjs";
import { t as parseDate } from "./parse-date-r5WDWHno.mjs";
import { i as queryToInteger, n as queryToBoolean, t as fallback } from "./readable-social-CExV9fL0.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 = null;
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`;
} else 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";
} else return url;
};
const replaceBreak = (text) => text.replaceAll(/<br><br>|<br>/g, " ");
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)
};
const { readable, authorNameBold, showAuthorInTitle, showAuthorAsTitleOnly, showAuthorInDesc, showQuotedAuthorAvatarInDesc, showAuthorAvatarInDesc, showEmojiForRetweetAndReply, showSymbolForRetweetAndReply, showRetweetTextInTitle, addLinkForPics, showTimestampInDescription, showQuotedInTitle, mediaNumber, 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) => {
const originalItem = item;
item = item.retweeted_status || item;
item.full_text = 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) {
quoteData.full_text = quoteData.full_text || quoteData.text;
const author = quoteData.user;
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;'>`;
} else quote += `<br><br>`;
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 || userScreenName}/status/${item.conversation_id_str}`,
type: "repost"
}] } || item.is_quote_status && { 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"
}] }
};
});
};
let getAppClient = () => null;
if (config.twitter.consumer_key && config.twitter.consumer_secret) {
const consumer_keys = config.twitter.consumer_key.split(",");
const consumer_secrets = config.twitter.consumer_secret.split(",");
const T = {};
let count = 0;
let index = -1;
for (const [i, consumer_key] of consumer_keys.entries()) {
const consumer_secret = consumer_secrets[i];
if (consumer_key && consumer_secret) {
T[i] = new TwitterApi({
appKey: consumer_key,
appSecret: consumer_secret
}).readOnly;
count = i + 1;
}
}
getAppClient = () => {
index++;
return T[index % count].appLogin();
};
}
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")));
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);
};
var utils_default = {
ProcessFeed,
getAppClient,
parseRouteParams,
excludeRetweet,
keepOnlyMedia
};
//#endregion
export { utils_default as t };