const URL = require('url');
const config = require('@/config').value;
const { TwitterApi } = require('twitter-api-v2');
const { fallback, queryToBoolean, queryToInteger } = require('@/utils/readable-social');
const { parseDate } = require('@/utils/parse-date');
const getQueryParams = (url) => URL.parse(url, true).query;
const getOriginalImg = (url) => {
// https://greasyfork.org/zh-CN/scripts/2312-resize-image-on-open-image-in-new-tab/code#n150
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.replace(/
|
/g, ' ');
const formatText = (item) => {
let text = item.full_text;
const urls = item.entities.urls || [];
urls.forEach((url) => {
text = text.replaceAll(url.url, url.expanded_url);
});
const media = item.extended_entities?.media || [];
media.forEach((m) => {
text = text.replaceAll(m.url, '');
});
return text.trim().replace(/\n/g, '
');
};
const ProcessFeed = (ctx, { data = [] }, params = {}) => {
// undefined and strings like "exclude_rts_replies" is also safely parsed, so no if branch is needed
const routeParams = new URLSearchParams(ctx.params.routeParams);
const mergedParams = {
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),
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),
};
params = mergedParams;
const {
readable,
authorNameBold,
showAuthorInTitle,
showAuthorInDesc,
showQuotedAuthorAvatarInDesc,
showAuthorAvatarInDesc,
showEmojiForRetweetAndReply,
showSymbolForRetweetAndReply,
showRetweetTextInTitle,
addLinkForPics,
showTimestampInDescription,
showQuotedInTitle,
widthOfPics,
heightOfPics,
sizeOfAuthorAvatar,
sizeOfQuotedAuthorAvatar,
} = params;
const formatVideo = (media, extraAttrs = '') => {
let content = '';
const video = media.video_info.variants.reduce((video, item) => {
if ((item.bitrate || 0) > (video.bitrate || -Infinity)) {
video = item;
}
return video;
}, {});
if (video.url) {
const gifAutoPlayAttr = media.type === 'animated_gif' ? `autoplay loop muted webkit-playsinline playsinline` : '';
if (!readable) {
content += '
';
}
content += ``;
}
return content;
};
const formatMedia = (item) => {
let img = '';
item.extended_entities &&
item.extended_entities.media.forEach((item) => {
// https://developer.twitter.com/en/docs/tweets/data-dictionary/overview/extended-entities-object
let content = '';
let style = '';
let originalImg;
switch (item.type) {
case 'animated_gif':
case 'video':
content = formatVideo(item);
break;
case 'photo':
default:
originalImg = getOriginalImg(item.media_url_https);
if (!readable) {
content += `
`;
}
if (addLinkForPics) {
content += ``;
}
content += `= 0) {
content += ` width="${widthOfPics}"`;
style += `width: ${widthOfPics}px;`;
}
if (heightOfPics > 0) {
content += `height="${heightOfPics}" `;
style += `height: ${heightOfPics}px;`;
}
content += ` style="${style}" ` + `${readable ? 'hspace="4" vspace="8"' : ''} src="${originalImg}">`;
if (addLinkForPics) {
content += ``;
}
break;
}
img += content;
});
if (readable && img) {
img = `
`; } else { quote += ``; } quote += '
`; } if (readable) { quote += ``; } if (showQuotedAuthorAvatarInDesc) { quote += ``; } if (authorNameBold) { quote += ``; } quote += author.name; if (authorNameBold) { quote += ``; } if (readable) { quote += ``; } quote += `: `; quote += formatText(quoteData); if (!readable) { quote += '
'; } quote += formatMedia(quoteData); picsPrefix += generatePicsPrefix(quoteData); quoteInTitle += showEmojiForRetweetAndReply ? ' đŦ ' : showSymbolForRetweetAndReply ? ' RT ' : ''; quoteInTitle += `${author.name}: ${formatText(quoteData)}`; if (readable) { quote += `
Link: https://twitter.com/${author.screen_name}/status/${quoteData.id_str}`; } if (showTimestampInDescription) { quote += '
' + parseDate(quoteData.created_at); quote += ``; if (readable) { quote += `
`; } } if (readable) { quote += `