agent-twitter-summary
Version:
A twitter client for agents
147 lines (122 loc) • 3.81 kB
text/typescript
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<TimelineMediaExtendedRaw, 'id_str' | 'media_url_https'>,
): 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 += `<br><img src="${url}"/>`;
}
for (const { preview: url } of videos) {
if (media.indexOf(url) !== -1) {
continue;
}
html += `<br><img src="${url}"/>`;
}
html = html.replace(/\n/g, '<br>');
return html;
}
function linkHashtagHtml(hashtag: string) {
return `<a href="https://twitter.com/hashtag/${hashtag.replace(
'#',
'',
)}">${hashtag}</a>`;
}
function linkCashtagHtml(cashtag: string) {
return `<a href="https://twitter.com/search?q=%24${cashtag.replace(
'$',
'',
)}">${cashtag}</a>`;
}
function linkUsernameHtml(username: string) {
return `<a href="https://twitter.com/${username.replace(
'@',
'',
)}">${username}</a>`;
}
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 `<a href="${entity.expanded_url}">${tco}</a>`;
}
}
for (const entity of tweet.extended_entities?.media ?? []) {
if (tco === entity.url && entity.media_url_https != null) {
foundedMedia.push(entity.media_url_https);
return `<br><a href="${tco}"><img src="${entity.media_url_https}"/></a>`;
}
}
return tco;
};
}