import { LegacyTweetRaw, TimelineMediaExtendedRaw } from './timeline-v1'; import { Photo, Video } from './tweets'; import { isFieldDefined, NonNullableField } from './type-util'; const reHashtag = /\B(\#\S+\b)/g; const reCashtag = /\B(\$\S+\b)/g; const reTwitterUrl = /https:(\/\/t\.co\/([A-Za-z0-9]|[A-Za-z]){10})/g; const reUsername = /\B(\@\S{1,15}\b)/g; export function parseMediaGroups(media: TimelineMediaExtendedRaw[]): { sensitiveContent?: boolean; photos: Photo[]; videos: Video[]; } { const photos: Photo[] = []; const videos: Video[] = []; let sensitiveContent: boolean | undefined = undefined; for (const m of media .filter(isFieldDefined('id_str')) .filter(isFieldDefined('media_url_https'))) { if (m.type === 'photo') { photos.push({ id: m.id_str, url: m.media_url_https, alt_text: m.ext_alt_text, }); } else if (m.type === 'video') { videos.push(parseVideo(m)); } const sensitive = m.ext_sensitive_media_warning; if (sensitive != null) { sensitiveContent = sensitive.adult_content || sensitive.graphic_violence || sensitive.other; } } return { sensitiveContent, photos, videos }; } function parseVideo( m: NonNullableField, ): Video { const video: Video = { id: m.id_str, preview: m.media_url_https, }; let maxBitrate = 0; const variants = m.video_info?.variants ?? []; for (const variant of variants) { const bitrate = variant.bitrate; if (bitrate != null && bitrate > maxBitrate && variant.url != null) { let variantUrl = variant.url; const stringStart = 0; const tagSuffixIdx = variantUrl.indexOf('?tag=10'); if (tagSuffixIdx !== -1) { variantUrl = variantUrl.substring(stringStart, tagSuffixIdx + 1); } video.url = variantUrl; maxBitrate = bitrate; } } return video; } export function reconstructTweetHtml( tweet: LegacyTweetRaw, photos: Photo[], videos: Video[], ): string { const media: string[] = []; // HTML parsing with regex :) let html = tweet.full_text ?? ''; html = html.replace(reHashtag, linkHashtagHtml); html = html.replace(reCashtag, linkCashtagHtml); html = html.replace(reUsername, linkUsernameHtml); html = html.replace(reTwitterUrl, unwrapTcoUrlHtml(tweet, media)); for (const { url } of photos) { if (media.indexOf(url) !== -1) { continue; } html += `
`; } for (const { preview: url } of videos) { if (media.indexOf(url) !== -1) { continue; } html += `
`; } html = html.replace(/\n/g, '
'); return html; } function linkHashtagHtml(hashtag: string) { return `${hashtag}`; } function linkCashtagHtml(cashtag: string) { return `${cashtag}`; } function linkUsernameHtml(username: string) { return `${username}`; } function unwrapTcoUrlHtml(tweet: LegacyTweetRaw, foundedMedia: string[]) { return function (tco: string) { for (const entity of tweet.entities?.urls ?? []) { if (tco === entity.url && entity.expanded_url != null) { return `${tco}`; } } for (const entity of tweet.extended_entities?.media ?? []) { if (tco === entity.url && entity.media_url_https != null) { foundedMedia.push(entity.media_url_https); return `
`; } } return tco; }; }