Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 52 additions & 45 deletions layouts/header/script.js
Original file line number Diff line number Diff line change
Expand Up @@ -1043,10 +1043,11 @@ let userDataFunction = async user => {
messageElements.push(messageElement);
} else if (lastEntry.type == 'participants_leave') {
let leftUser = lastConvo.users[lastEntry.data.participants[0].user_id];
let messageText = LOC.user_left.message
.replace('$NAME$', escapeHTML(leftUser.name))
.replace('$A_START$', `<a href="/${leftUser.screen_name}">`)
.replace('$A_END$', '</a>');
let messageText = replaceTemplates(LOC.user_left.message, {
'$NAME$': escapeHTML(leftUser.name),
'$A_START$': `<a href="/${leftUser.screen_name}">`,
'$A_END$': '</a>'
});

let messageElement = document.createElement('div');
messageElement.classList.add('message-announcement');
Expand All @@ -1062,12 +1063,13 @@ let userDataFunction = async user => {
} else if (lastEntry.type == 'participants_join') {
let joinedUser = lastConvo.users[lastEntry.data.participants[0].user_id];
let userWhoAdded = lastConvo.users[lastEntry.data.sender_id];
let messageText = LOC.user_added.message
.replace('$USER_WHO_ADDED$', escapeHTML(userWhoAdded.name))
.replace('$USER_WHO_JOINED$', escapeHTML(joinedUser.name))
.replace('$A1$', `<a href="/${userWhoAdded.screen_name}">`)
.replace('$A2$', `<a href="/${joinedUser.screen_name}">`)
.replaceAll('$A_END$', '</a>');
let messageText = replaceTemplates(LOC.user_added.message, {
'$USER_WHO_ADDED$': escapeHTML(userWhoAdded.name),
'$USER_WHO_JOINED$': escapeHTML(joinedUser.name),
'$A1$': `<a href="/${userWhoAdded.screen_name}">`,
'$A2$': `<a href="/${joinedUser.screen_name}">`,
'$A_END$': '</a>'
});

let messageElement = document.createElement('div');
messageElement.classList.add('message-announcement');
Expand All @@ -1082,11 +1084,12 @@ let userDataFunction = async user => {
messageElements.push(messageElement);
} else if (lastEntry.type == 'conversation_name_update') {
let userWhoUpdated = lastConvo.users[lastEntry.data.by_user_id];
let messageText = LOC.user_changed_group_name.message
.replace('$NAME$', escapeHTML(userWhoUpdated.name))
.replace('$GROUP_NAME$', escapeHTML(lastEntry.data.conversation_name))
.replace('$A_START$', `<a href="/${userWhoUpdated.screen_name}">`)
.replace('$A_END$', '</a>');
let messageText = replaceTemplates(LOC.user_changed_group_name.message, {
'$NAME$': escapeHTML(userWhoUpdated.name),
'$GROUP_NAME$': escapeHTML(lastEntry.data.conversation_name),
'$A_START$': `<a href="/${userWhoUpdated.screen_name}">`,
'$A_END$': '</a>'
});

let messageElement = document.createElement('div');
messageElement.classList.add('message-announcement');
Expand All @@ -1101,11 +1104,11 @@ let userDataFunction = async user => {
messageElements.push(messageElement);
} else if (lastEntry.type == 'conversation_avatar_update') {
let userWhoUpdated = lastConvo.users[lastEntry.data.by_user_id];
let messageText = `<img src="${lastEntry.data.conversation_avatar_image_https}" class="message-announcement-icon">` +
LOC.user_changed_group_photo.message
.replace('$NAME$', escapeHTML(userWhoUpdated.name))
.replace('$A_START$', `<a href="/${userWhoUpdated.screen_name}">`)
.replace('$A_END$', '</a>');
let messageText = `<img src="${lastEntry.data.conversation_avatar_image_https}" class="message-announcement-icon">` + replaceTemplates(LOC.user_changed_group_photo.message, {
'$NAME$': escapeHTML(userWhoUpdated.name),
'$A_START$': `<a href="/${userWhoUpdated.screen_name}">`,
'$A_END$': '</a>'
});

let messageElement = document.createElement('div');
messageElement.classList.add('message-announcement');
Expand All @@ -1121,11 +1124,12 @@ let userDataFunction = async user => {
} else if (lastEntry.type == 'join_conversation') { //only when YOU get added to a conversation
let userWhoAdded = lastConvo.users[lastEntry.data.sender_id];
let otherUsers = (lastConvo.conversations[lastConvo.conversation_id].participants.length - 1).toLocaleString();
let messageText = LOC.user_added_you_msg.message
.replace('$NAME$', escapeHTML(userWhoAdded.name))
.replace('$NUMBER$', otherUsers)
.replace('$A_START$', `<a href="/${userWhoAdded.screen_name}">`)
.replace('$A_END$', `</a>`);
let messageText = replaceTemplates(LOC.user_added_you_msg.message, {
'$NAME$': escapeHTML(userWhoAdded.name),
'$NUMBER$': otherUsers,
'$A_START$': `<a href="/${userWhoAdded.screen_name}">`,
'$A_END$': '</a>'
});

let messageElement = document.createElement('div');
messageElement.classList.add('message-announcement');
Expand Down Expand Up @@ -1276,33 +1280,36 @@ let userDataFunction = async user => {
messageEntry.preview = LOC.accepted_conversation.message;
} else if (lastEvent.type == 'participants_leave') {
let leftUser = inbox.users[lastMessage.participants[0].user_id];
messageEntry.preview = LOC.user_left.message
.replace('$NAME$', escapeHTML(leftUser.name))
.replace('$A_START$', '')
.replace('$A_END$', '');
messageEntry.preview = replaceTemplates(LOC.user_left.message, {
'$NAME$': escapeHTML(leftUser.name),
'$A_START$': '',
'$A_END$': ''
});
} else if (lastEvent.type == 'participants_join') {
let joinedUser = inbox.users[lastMessage.participants[0].user_id];
let userWhoAdded = inbox.users[lastMessage.sender_id];
messageEntry.preview = LOC.user_added.message
.replace('$USER_WHO_ADDED$', escapeHTML(userWhoAdded.name))
.replace('$USER_WHO_JOINED$', escapeHTML(joinedUser.name))
.replace('$A1$', '')
.replace('$A2$', '')
.replaceAll('$A_END$', '');
messageEntry.preview = `${escapeHTML(userWhoAdded.name)} added ${escapeHTML(joinedUser.name)}`;
messageEntry.preview = replaceTemplates(LOC.user_added.message, {
'$USER_WHO_ADDED$': escapeHTML(userWhoAdded.name),
'$USER_WHO_JOINED$': escapeHTML(joinedUser.name),
'$A1$': '',
'$A2$': '',
'$A_END$': ''
});
} else if (lastEvent.type == 'conversation_name_update') {
let userWhoUpdated = inbox.users[lastMessage.by_user_id];
messageEntry.preview = LOC.user_changed_group_name.message
.replace('$NAME$', escapeHTML(userWhoUpdated.name))
.replace('$GROUP_NAME$', escapeHTML(lastMessage.conversation_name))
.replace('$A_START$', '')
.replace('$A_END$', '');
messageEntry.preview = replaceTemplates(LOC.user_changed_group_name.message, {
'$NAME$': escapeHTML(userWhoUpdated.name),
'$GROUP_NAME$': escapeHTML(lastMessage.conversation_name),
'$A_START$': '',
'$A_END$': ''
});
} else if (lastEvent.type == 'conversation_avatar_update') {
let userWhoUpdated = inbox.users[lastMessage.by_user_id];
messageEntry.preview = LOC.user_changed_group_photo.message
.replace('$NAME$', escapeHTML(userWhoUpdated.name))
.replace('$A_START$', '')
.replace('$A_END$', '');
messageEntry.preview = replaceTemplates(LOC.user_changed_group_photo.message, {
'$NAME$': escapeHTML(userWhoUpdated.name),
'$A_START$': '',
'$A_END$': ''
});
} else if (lastMessage.message_data) {
let lastMessageUser = lastMessage.message_data ? messageUsers.find(user => user.id_str === lastMessage.message_data.sender_id) : messageUsers[0];
if (lastMessage.message_data.text.startsWith('dmservice_reaction_')) {
Expand Down
55 changes: 36 additions & 19 deletions scripts/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -380,31 +380,48 @@ function onVisibilityChange(callback) {
window.onpageshow = window.onfocus = focused;
window.onpagehide = window.onblur = unfocused;
};
function escapeHTML(unsafe, strict = false) {
function escapeHTML(unsafe) {
if(typeof unsafe === 'undefined' || unsafe === null) {
return '';
}

if(strict) {
unsafe = unsafe
.replaceAll('"', '&quot;')
.replaceAll('\'', '&apos;')
.replaceAll('&', '&amp;')
.replaceAll('<', '&lt;')
.replaceAll('>', '&gt;');
}

return DOMPurify.sanitize(String(unsafe), { ADD_ATTR: ['target'] });
//twitter returns already-escaped text in some scenarios, which can cause it to get double-escaped, so we're unescaping that to re-escape it...
unsafe = unsafe
.replaceAll('&lt;', '<')
.replaceAll('&gt;', '>')
.replaceAll('&amp;', '&');

return unsafe = unsafe
.replaceAll('&', '&amp;')
.replaceAll('"', '&quot;')
.replaceAll('\'', '&apos;')
.replaceAll('<', '&lt;')
.replaceAll('>', '&gt;');
}

function html(strings, ...values) {
let str = '';
strings.forEach((string, i) => {
str += string + escapeHTML(values[i]);
let value;
if(typeof values[i] === 'undefined' || values[i] === null) {
value = '';
} else {
value = String(values[i]);
}

str += string + DOMPurify.sanitize(value, { ADD_ATTR: ['target'] });
});
return str;
}

function replaceTemplates(string, replacements) {
return string
.replace(/\$[A-Z_]+\$/g, (match) => {
if (match in replacements) return replacements[match];
return match;
});
}

async function renderTweetBodyHTML(t, is_quoted) {
let result = "",
last_pos = 0,
Expand Down Expand Up @@ -1444,7 +1461,7 @@ function renderMedia(t) {
let [w, h] = sizeFunctions[t.extended_entities.media.length](m.original_info.width, m.original_info.height);
_html += html`
<img
${m.ext_alt_text ? `alt="${escapeHTML(m.ext_alt_text, true)}" title="${escapeHTML(m.ext_alt_text, true)}"` : ''}
${m.ext_alt_text ? `alt="${escapeHTML(m.ext_alt_text)}" title="${escapeHTML(m.ext_alt_text)}"` : ''}
crossorigin="anonymous"
width="${w}"
height="${h}"
Expand All @@ -1454,7 +1471,7 @@ function renderMedia(t) {
>`;
console.log(html`
<img
${m.ext_alt_text ? `alt="${escapeHTML(m.ext_alt_text, true)}" title="${escapeHTML(m.ext_alt_text, true)}"` : ''}
${m.ext_alt_text ? `alt="${escapeHTML(m.ext_alt_text)}" title="${escapeHTML(m.ext_alt_text)}"` : ''}
crossorigin="anonymous"
width="${w}"
height="${h}"
Expand All @@ -1467,7 +1484,7 @@ function renderMedia(t) {
let rid = m.id_str + m.media_key;
_html += html`
<video
${m.ext_alt_text ? `alt="${escapeHTML(m.ext_alt_text, true)}" title="${escapeHTML(m.ext_alt_text, true)}"` : ''}
${m.ext_alt_text ? `alt="${escapeHTML(m.ext_alt_text)}" title="${escapeHTML(m.ext_alt_text)}"` : ''}
crossorigin="anonymous"
width="${w}"
height="${h}"
Expand All @@ -1490,7 +1507,7 @@ function renderMedia(t) {
let [w, h] = sizeFunctions[t.extended_entities.media.length](m.original_info.width, m.original_info.height);
_html += html`
<video
${m.ext_alt_text ? `alt="${escapeHTML(m.ext_alt_text, true)}" title="${escapeHTML(m.ext_alt_text, true)}"` : ''}
${m.ext_alt_text ? `alt="${escapeHTML(m.ext_alt_text)}" title="${escapeHTML(m.ext_alt_text)}"` : ''}
crossorigin="anonymous"
width="${w}"
height="${h}"
Expand Down Expand Up @@ -1934,7 +1951,7 @@ async function appendTweet(t, timelineContainer, options = {}) {
<img
onerror="this.src = '${vars.useOldDefaultProfileImage ? chrome.runtime.getURL(`images/default_profile_images/default_profile_bigger.png`) : 'https://abs.twimg.com/sticky/default_profile_images/default_profile_bigger.png'}'"
src="${`${(t.user.default_profile_image && vars.useOldDefaultProfileImage) ? chrome.runtime.getURL(`images/default_profile_images/default_profile_${Number(t.user.id_str) % 7}_normal.png`) : t.user.profile_image_url_https}`.replace("_normal.", "_bigger.")}"
alt="${t.user.name}"
alt="${escapeHTML(t.user.name)}"
class="tweet-avatar"
width="48"
height="48"
Expand Down Expand Up @@ -2008,7 +2025,7 @@ async function appendTweet(t, timelineContainer, options = {}) {
<span class="tweet-body-text tweet-body-text-quote tweet-body-text-long" style="color:var(--default-text-color)!important">${vars.useOldStyleReply? quoteMentionedUserText: ''}${t.quoted_status.full_text ? await renderTweetBodyHTML(t, true) : ''}</span>
${t.quoted_status.extended_entities && t.quoted_status.extended_entities.media ? html`
<div class="tweet-media-quote">
${t.quoted_status.extended_entities.media.map(m => `<${m.type === 'photo' ? 'img' : 'video'} ${m.ext_alt_text ? `alt="${escapeHTML(m.ext_alt_text, true)}" title="${escapeHTML(m.ext_alt_text, true)}"` : ''} crossorigin="anonymous" width="${quoteSizeFunctions[t.quoted_status.extended_entities.media.length](m.original_info.width, m.original_info.height)[0]}" height="${quoteSizeFunctions[t.quoted_status.extended_entities.media.length](m.original_info.width, m.original_info.height)[1]}" loading="lazy" ${m.type === 'video' ? 'disableRemotePlayback controls' : ''} ${m.type === 'animated_gif' ? 'disableRemotePlayback loop muted onclick="if(this.paused) this.play(); else this.pause()"' : ''}${m.type === 'animated_gif' && !vars.disableGifAutoplay ? ' autoplay' : ''} src="${m.type === 'photo' ? m.media_url_https + (vars.showOriginalImages && (m.media_url_https.endsWith('.jpg') || m.media_url_https.endsWith('.png')) ? '?name=orig' : window.navigator && navigator.connection && navigator.connection.type === 'cellular' && !vars.disableDataSaver ? '?name=small' : '') : m.video_info.variants.find(v => v.content_type === 'video/mp4').url}" class="tweet-media-element tweet-media-element-quote ${m.type === 'animated_gif' ? 'tweet-media-element-quote-gif' : ''} ${mediaClasses[t.quoted_status.extended_entities.media.length]} ${!vars.displaySensitiveContent && t.quoted_status.possibly_sensitive ? 'tweet-media-element-censor' : ''}">${m.type === 'photo' ? '' : '</video>'}`).join('\n')}
${t.quoted_status.extended_entities.media.map(m => `<${m.type === 'photo' ? 'img' : 'video'} ${m.ext_alt_text ? `alt="${escapeHTML(m.ext_alt_text)}" title="${escapeHTML(m.ext_alt_text)}"` : ''} crossorigin="anonymous" width="${quoteSizeFunctions[t.quoted_status.extended_entities.media.length](m.original_info.width, m.original_info.height)[0]}" height="${quoteSizeFunctions[t.quoted_status.extended_entities.media.length](m.original_info.width, m.original_info.height)[1]}" loading="lazy" ${m.type === 'video' ? 'disableRemotePlayback controls' : ''} ${m.type === 'animated_gif' ? 'disableRemotePlayback loop muted onclick="if(this.paused) this.play(); else this.pause()"' : ''}${m.type === 'animated_gif' && !vars.disableGifAutoplay ? ' autoplay' : ''} src="${m.type === 'photo' ? m.media_url_https + (vars.showOriginalImages && (m.media_url_https.endsWith('.jpg') || m.media_url_https.endsWith('.png')) ? '?name=orig' : window.navigator && navigator.connection && navigator.connection.type === 'cellular' && !vars.disableDataSaver ? '?name=small' : '') : m.video_info.variants.find(v => v.content_type === 'video/mp4').url}" class="tweet-media-element tweet-media-element-quote ${m.type === 'animated_gif' ? 'tweet-media-element-quote-gif' : ''} ${mediaClasses[t.quoted_status.extended_entities.media.length]} ${!vars.displaySensitiveContent && t.quoted_status.possibly_sensitive ? 'tweet-media-element-censor' : ''}">${m.type === 'photo' ? '' : '</video>'}`).join('\n')}
</div>
` : ''}
${!isQuoteMatchingLanguage ? html`
Expand Down Expand Up @@ -2224,7 +2241,7 @@ async function appendTweet(t, timelineContainer, options = {}) {
}
}
tweet.getElementsByClassName('tweet-media')[0].innerHTML = html`
${t.extended_entities.media.map(m => `<${m.type === 'photo' ? 'img' : 'video'} ${m.ext_alt_text ? `alt="${escapeHTML(m.ext_alt_text, true)}" title="${escapeHTML(m.ext_alt_text, true)}"` : ''} crossorigin="anonymous" width="${sizeFunctions[t.extended_entities.media.length](m.original_info.width, m.original_info.height)[0]}" height="${sizeFunctions[t.extended_entities.media.length](m.original_info.width, m.original_info.height)[1]}" loading="lazy" ${m.type === 'video' ? 'controls' : ''} ${m.type === 'animated_gif' ? 'loop muted onclick="if(this.paused) this.play(); else this.pause()"' : ''}${m.type === 'animated_gif' && !vars.disableGifAutoplay ? ' autoplay' : ''} ${m.type === 'photo' ? `src="${m.media_url_https}"` : ''} class="tweet-media-element ${mediaClasses[t.extended_entities.media.length]} ${!vars.displaySensitiveContent && t.possibly_sensitive ? 'tweet-media-element-censor' : ''}">${m.type === 'video' || m.type === 'animated_gif' ? `
${t.extended_entities.media.map(m => `<${m.type === 'photo' ? 'img' : 'video'} ${m.ext_alt_text ? `alt="${escapeHTML(m.ext_alt_text)}" title="${escapeHTML(m.ext_alt_text)}"` : ''} crossorigin="anonymous" width="${sizeFunctions[t.extended_entities.media.length](m.original_info.width, m.original_info.height)[0]}" height="${sizeFunctions[t.extended_entities.media.length](m.original_info.width, m.original_info.height)[1]}" loading="lazy" ${m.type === 'video' ? 'controls' : ''} ${m.type === 'animated_gif' ? 'loop muted onclick="if(this.paused) this.play(); else this.pause()"' : ''}${m.type === 'animated_gif' && !vars.disableGifAutoplay ? ' autoplay' : ''} ${m.type === 'photo' ? `src="${m.media_url_https}"` : ''} class="tweet-media-element ${mediaClasses[t.extended_entities.media.length]} ${!vars.displaySensitiveContent && t.possibly_sensitive ? 'tweet-media-element-censor' : ''}">${m.type === 'video' || m.type === 'animated_gif' ? `
${m.video_info.variants.map(v => `<source src="${v.url}" type="${v.content_type}">`).join('\n')}
${LOC.unsupported_video.message}
</video>` : ''}`).join('\n')}
Expand Down
Loading