const linkRegex = /(\s|^)(http(s)?:\/\/.)?(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,60}\.(삼성|닷컴|닷넷|香格里拉|餐厅|食品|飞利浦|電訊盈科|集团|通販|购物|谷歌|诺基亚|联通|网络|网站|网店|网址|组织机构|移动|珠宝|点看|游戏|淡马锡|机构|書籍|时尚|新闻|政府|政务|招聘|手表|手机|我爱你|慈善|微博|广东|工行|家電|娱乐|天主教|大拿|大众汽车|在线|嘉里大酒店|嘉里|商标|商店|商城|公益|公司|八卦|健康|信息|佛山|企业|亚马逊|中文网|中信|世界|ポイント|ファッション|セール|ストア|コム|グーグル|クラウド|アマゾン|みんな|คอม|संगठन|नेट|कॉम|همراه|موقع|موبايلي|كوم|كاثوليك|عرب|شبكة|بيتك|بازار|العليان|ارامكو|اتصالات|ابوظبي|קום|сайт|рус|орг|онлайн|москва|ком|католик|дети|zuerich|zone|zippo|zip|zero|zara|zappos|yun|youtube|you|yokohama|yoga|yodobashi|yandex|yamaxun|yahoo|yachts|xyz|xxx|xperia|xin|xihuan|xfinity|xerox|xbox|wtf|wtc|wow|world|works|work|woodside|wolterskluwer|wme|winners|wine|windows|win|williamhill|wiki|wien|whoswho|weir|weibo|wedding|wed|website|weber|webcam|weatherchannel|weather|watches|watch|warman|wanggou|wang|walter|walmart|wales|vuelos|voyage|voto|voting|vote|volvo|volkswagen|vodka|vlaanderen|vivo|viva|vistaprint|vista|vision|visa|virgin|vip|vin|villas|viking|vig|video|viajes|vet|versicherung|vermögensberatung|vermögensberater|verisign|ventures|vegas|vanguard|vana|vacations|ups|uol|uno|university|unicom|uconnect|ubs|ubank|tvs|tushu|tunes|tui|tube|trv|trust|travelersinsurance|travelers|travelchannel|travel|training|trading|trade|toys|toyota|town|tours|total|toshiba|toray|top|tools|tokyo|today|tmall|tkmaxx|tjx|tjmaxx|tirol|tires|tips|tiffany|tienda|tickets|tiaa|theatre|theater|thd|teva|tennis|temasek|telefonica|telecity|tel|technology|tech|team|tdk|tci|taxi|tax|tattoo|tatar|tatamotors|target|taobao|talk|taipei|tab|systems|symantec|sydney|swiss|swiftcover|swatch|suzuki|surgery|surf|support|supply|supplies|sucks|style|study|studio|stream|store|storage|stockholm|stcgroup|stc|statoil|statefarm|statebank|starhub|star|staples|stada|srt|srl|spreadbetting|spot|sport|spiegel|space|soy|sony|song|solutions|solar|sohu|software|softbank|social|soccer|sncf|smile|smart|sling|skype|sky|skin|ski|site|singles|sina|silk|shriram|showtime|show|shouji|shopping|shop|shoes|shiksha|shia|shell|shaw|sharp|shangrila|sfr|sexy|sex|sew|seven|ses|services|sener|select|seek|security|secure|seat|search|scot|scor|scjohnson|science|schwarz|schule|school|scholarships|schmidt|schaeffler|scb|sca|sbs|sbi|saxo|save|sas|sarl|sapo|sap|sanofi|sandvikcoromant|sandvik|samsung|samsclub|salon|sale|sakura|safety|safe|saarland|ryukyu|rwe|run|ruhr|rugby|rsvp|room|rogers|rodeo|rocks|rocher|rmit|rip|rio|ril|rightathome|ricoh|richardli|rich|rexroth|reviews|review|restaurant|rest|republican|report|repair|rentals|rent|ren|reliance|reit|reisen|reise|rehab|redumbrella|redstone|red|recipes|realty|realtor|realestate|read|raid|radio|racing|qvc|quest|quebec|qpon|pwc|pub|prudential|pru|protection|property|properties|promo|progressive|prof|productions|prod|pro|prime|press|praxi|pramerica|post|porn|politie|poker|pohl|pnc|plus|plumbing|playstation|play|place|pizza|pioneer|pink|ping|pin|pid|pictures|pictet|pics|piaget|physio|photos|photography|photo|phone|philips|phd|pharmacy|pfizer|pet|pccw|pay|passagens|party|parts|partners|pars|paris|panerai|panasonic|pamperedchef|page|ovh|ott|otsuka|osaka|origins|orientexpress|organic|org|orange|oracle|open|ooo|onyourside|online|onl|ong|one|omega|ollo|oldnavy|olayangroup|olayan|okinawa|office|off|observer|obi|nyc|ntt|nrw|nra|nowtv|nowruz|now|norton|northwesternmutual|nokia|nissay|nissan|ninja|nikon|nike|nico|nhk|ngo|nfl|nexus|nextdirect|next|news|newholland|new|neustar|network|netflix|netbank|net|nec|nba|navy|natura|nationwide|name|nagoya|nadex|nab|mutuelle|mutual|museum|mtr|mtpc|mtn|msd|movistar|movie|mov|motorcycles|moto|moscow|mortgage|mormon|mopar|montblanc|monster|money|monash|mom|moi|moe|moda|mobily|mobile|mobi|mma|mls|mlb|mitsubishi|mit|mint|mini|mil|microsoft|miami|metlife|merckmsd|meo|menu|men|memorial|meme|melbourne|meet|media|med|mckinsey|mcdonalds|mcd|mba|mattel|maserati|marshalls|marriott|markets|marketing|market|map|mango|management|man|makeup|maison|maif|madrid|macys|luxury|luxe|lupin|lundbeck|ltda|ltd|lplfinancial|lpl|love|lotto|lotte|london|lol|loft|locus|locker|loans|loan|llp|llc|lixil|living|live|lipsy|link|linde|lincoln|limo|limited|lilly|like|lighting|lifestyle|lifeinsurance|life|lidl|liaison|lgbt|lexus|lego|legal|lefrak|leclerc|lease|lds|lawyer|law|latrobe|latino|lat|lasalle|lanxess|landrover|land|lancome|lancia|lancaster|lamer|lamborghini|ladbrokes|lacaixa|kyoto|kuokgroup|kred|krd|kpn|kpmg|kosher|komatsu|koeln|kiwi|kitchen|kindle|kinder|kim|kia|kfh|kerryproperties|kerrylogistics|kerryhotels|kddi|kaufen|juniper|juegos|jprs|jpmorgan|joy|jot|joburg|jobs|jnj|jmp|jll|jlc|jio|jewelry|jetzt|jeep|jcp|jcb|java|jaguar|iwc|iveco|itv|itau|istanbul|ist|ismaili|iselect|irish|ipiranga|investments|intuit|international|intel|int|insure|insurance|institute|ink|ing|info|infiniti|industries|inc|immobilien|immo|imdb|imamat|ikano|iinet|ifm|ieee|icu|ice|icbc|ibm|hyundai|hyatt|hughes|htc|hsbc|how|house|hotmail|hotels|hoteles|hot|hosting|host|hospital|horse|honeywell|honda|homesense|homes|homegoods|homedepot|holiday|holdings|hockey|hkt|hiv|hitachi|hisamitsu|hiphop|hgtv|hermes|here|helsinki|help|healthcare|health|hdfcbank|hdfc|hbo|haus|hangout|hamburg|hair|guru|guitars|guide|guge|gucci|guardian|group|grocery|gripe|green|gratis|graphics|grainger|gov|got|gop|google|goog|goodyear|goodhands|goo|golf|goldpoint|gold|godaddy|gmx|gmo|gmbh|gmail|globo|global|gle|glass|glade|giving|gives|gifts|gift|ggee|george|genting|gent|gea|gdn|gbiz|gay|garden|gap|games|game|gallup|gallo|gallery|gal|fyi|futbol|furniture|fund|fun|fujixerox|fujitsu|ftr|frontier|frontdoor|frogans|frl|fresenius|free|fox|foundation|forum|forsale|forex|ford|football|foodnetwork|food|foo|fly|flsmidth|flowers|florist|flir|flights|flickr|fitness|fit|fishing|fish|firmdale|firestone|fire|financial|finance|final|film|fido|fidelity|fiat|ferrero|ferrari|feedback|fedex|fast|fashion|farmers|farm|fans|fan|family|faith|fairwinds|fail|fage|extraspace|express|exposed|expert|exchange|everbank|events|eus|eurovision|etisalat|esurance|estate|esq|erni|ericsson|equipment|epson|epost|enterprises|engineering|engineer|energy|emerck|email|education|edu|edeka|eco|eat|earth|dvr|dvag|durban|dupont|duns|dunlop|duck|dubai|dtv|drive|download|dot|doosan|domains|doha|dog|dodge|doctor|docs|dnp|diy|dish|discover|discount|directory|direct|digital|diet|diamonds|dhl|dev|design|desi|dentist|dental|democrat|delta|deloitte|dell|delivery|degree|deals|dealer|deal|dds|dclk|day|datsun|dating|date|data|dance|dad|dabur|cyou|cymru|cuisinella|csc|cruises|cruise|crs|crown|cricket|creditunion|creditcard|credit|cpa|courses|coupons|coupon|country|corsica|coop|cool|cookingchannel|cooking|contractors|contact|consulting|construction|condos|comsec|computer|compare|company|community|commbank|comcast|com|cologne|college|coffee|codes|coach|clubmed|club|cloud|clothing|clinique|clinic|click|cleaning|claims|cityeats|city|citic|citi|citadel|cisco|circle|cipriani|church|chrysler|chrome|christmas|chloe|chintai|cheap|chat|chase|charity|channel|chanel|cfd|cfa|cern|ceo|center|ceb|cbs|cbre|cbn|cba|catholic|catering|cat|casino|cash|caseih|case|casa|cartier|cars|careers|career|care|cards|caravan|car|capitalone|capital|capetown|canon|cancerresearch|camp|camera|cam|calvinklein|call|cal|cafe|cab|bzh|buzz|buy|business|builders|build|bugatti|budapest|brussels|brother|broker|broadway|bridgestone|bradesco|box|boutique|bot|boston|bostik|bosch|boots|booking|book|boo|bond|bom|bofa|boehringer|boats|bnpparibas|bnl|bmw|bms|blue|bloomberg|blog|blockbuster|blanco|blackfriday|black|biz|bio|bingo|bing|bike|bid|bible|bharti|bet|bestbuy|best|berlin|bentley|beer|beauty|beats|bcn|bcg|bbva|bbt|bbc|bayern|bauhaus|basketball|baseball|bargains|barefoot|barclays|barclaycard|barcelona|bar|bank|band|bananarepublic|banamex|baidu|baby|azure|axa|aws|avianca|autos|auto|author|auspost|audio|audible|audi|auction|attorney|athleta|associates|asia|asda|arte|art|arpa|army|archi|aramco|arab|aquarelle|apple|app|apartments|aol|anz|anquan|android|analytics|amsterdam|amica|amfam|amex|americanfamily|americanexpress|amazon|alstom|alsace|ally|allstate|allfinanz|alipay|alibaba|alfaromeo|akdn|airtel|airforce|airbus|aigo|aig|agency|agakhan|africa|afl|afamilycompany|aetna|aero|aeg|adult|ads|adac|actor|active|aco|accountants|accountant|accenture|academy|abudhabi|abogado|able|abc|abbvie|abbott|abb|abarth|aarp|aaa|onion)\b([-a-zA-Z0-9@:%_\+.~#?&/=]*)/gi;
const hashtagRegex = /(#|#)([a-z0-9_\u00c0-\u00d6\u00d8-\u00f6\u00f8-\u00ff\u0100-\u024f\u0253-\u0254\u0256-\u0257\u0300-\u036f\u1e00-\u1eff\u0400-\u04ff\u0500-\u0527\u2de0-\u2dff\ua640-\ua69f\u0591-\u05bf\u05c1-\u05c2\u05c4-\u05c5\u05d0-\u05ea\u05f0-\u05f4\ufb12-\ufb28\ufb2a-\ufb36\ufb38-\ufb3c\ufb40-\ufb41\ufb43-\ufb44\ufb46-\ufb4f\u0610-\u061a\u0620-\u065f\u066e-\u06d3\u06d5-\u06dc\u06de-\u06e8\u06ea-\u06ef\u06fa-\u06fc\u0750-\u077f\u08a2-\u08ac\u08e4-\u08fe\ufb50-\ufbb1\ufbd3-\ufd3d\ufd50-\ufd8f\ufd92-\ufdc7\ufdf0-\ufdfb\ufe70-\ufe74\ufe76-\ufefc\u200c-\u200c\u0e01-\u0e3a\u0e40-\u0e4e\u1100-\u11ff\u3130-\u3185\ua960-\ua97f\uac00-\ud7af\ud7b0-\ud7ff\uffa1-\uffdc\u30a1-\u30fa\u30fc-\u30fe\uff66-\uff9f\uff10-\uff19\uff21-\uff3a\uff41-\uff5a\u3041-\u3096\u3099-\u309e\u3400-\u4dbf\u4e00-\u9fff\u20000-\u2a6df\u2a700-\u2b73f\u2b740-\u2b81f\u2f800-\u2fa1f]*[a-z_\u00c0-\u00d6\u00d8-\u00f6\u00f8-\u00ff\u0100-\u024f\u0253-\u0254\u0256-\u0257\u0300-\u036f\u1e00-\u1eff\u0400-\u04ff\u0500-\u0527\u2de0-\u2dff\ua640-\ua69f\u0591-\u05bf\u05c1-\u05c2\u05c4-\u05c5\u05d0-\u05ea\u05f0-\u05f4\ufb12-\ufb28\ufb2a-\ufb36\ufb38-\ufb3c\ufb40-\ufb41\ufb43-\ufb44\ufb46-\ufb4f\u0610-\u061a\u0620-\u065f\u066e-\u06d3\u06d5-\u06dc\u06de-\u06e8\u06ea-\u06ef\u06fa-\u06fc\u0750-\u077f\u08a2-\u08ac\u08e4-\u08fe\ufb50-\ufbb1\ufbd3-\ufd3d\ufd50-\ufd8f\ufd92-\ufdc7\ufdf0-\ufdfb\ufe70-\ufe74\ufe76-\ufefc\u200c-\u200c\u0e01-\u0e3a\u0e40-\u0e4e\u1100-\u11ff\u3130-\u3185\ua960-\ua97f\uac00-\ud7af\ud7b0-\ud7ff\uffa1-\uffdc\u30a1-\u30fa\u30fc-\u30fe\uff66-\uff9f\uff10-\uff19\uff21-\uff3a\uff41-\uff5a\u3041-\u3096\u3099-\u309e\u3400-\u4dbf\u4e00-\u9fff\u20000-\u2a6df\u2a700-\u2b73f\u2b740-\u2b81f\u2f800-\u2fa1f][a-z0-9_\u00c0-\u00d6\u00d8-\u00f6\u00f8-\u00ff\u0100-\u024f\u0253-\u0254\u0256-\u0257\u0300-\u036f\u1e00-\u1eff\u0400-\u04ff\u0500-\u0527\u2de0-\u2dff\ua640-\ua69f\u0591-\u05bf\u05c1-\u05c2\u05c4-\u05c5\u05d0-\u05ea\u05f0-\u05f4\ufb12-\ufb28\ufb2a-\ufb36\ufb38-\ufb3c\ufb40-\ufb41\ufb43-\ufb44\ufb46-\ufb4f\u0610-\u061a\u0620-\u065f\u066e-\u06d3\u06d5-\u06dc\u06de-\u06e8\u06ea-\u06ef\u06fa-\u06fc\u0750-\u077f\u08a2-\u08ac\u08e4-\u08fe\ufb50-\ufbb1\ufbd3-\ufd3d\ufd50-\ufd8f\ufd92-\ufdc7\ufdf0-\ufdfb\ufe70-\ufe74\ufe76-\ufefc\u200c-\u200c\u0e01-\u0e3a\u0e40-\u0e4e\u1100-\u11ff\u3130-\u3185\ua960-\ua97f\uac00-\ud7af\ud7b0-\ud7ff\uffa1-\uffdc\u30a1-\u30fa\u30fc-\u30fe\uff66-\uff9f\uff10-\uff19\uff21-\uff3a\uff41-\uff5a\u3041-\u3096\u3099-\u309e\u3400-\u4dbf\u4e00-\u9fff\u20000-\u2a6df\u2a700-\u2b73f\u2b740-\u2b81f\u2f800-\u2fa1f]*)/gi;
const rtlLanguages = ['ar', 'arc', 'dv', 'fa', 'ha', 'he', 'khw', 'ks', 'ku', 'ps', 'ur', 'yi'];
function arrayBufferToBase64(buffer) {
let binary = '';
let bytes = new Uint8Array(buffer);
let len = bytes.byteLength;
for (let i = 0; i < len; i++) {
binary += String.fromCharCode(bytes[i]);
}
return window.btoa(binary);
}
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
function createModal(html, className, onclose, canclose) {
let modal = document.createElement('div');
modal.classList.add('modal');
let modal_content = document.createElement('div');
modal_content.classList.add('modal-content');
if(className) modal_content.classList.add(className);
modal_content.innerHTML = html;
modal.appendChild(modal_content);
let close = document.createElement('span');
close.classList.add('modal-close');
close.title = "ESC";
close.innerHTML = '×';
document.body.style.overflowY = 'hidden';
function removeModal() {
modal.remove();
let event = new Event('findActiveTweet');
document.dispatchEvent(event);
document.removeEventListener('keydown', escapeEvent);
if(onclose) onclose();
let modals = document.getElementsByClassName('modal');
if(modals.length === 0) {
document.body.style.overflowY = 'auto';
}
}
modal.removeModal = removeModal;
function escapeEvent(e) {
if(document.querySelector('.viewer-in')) return;
if(e.key === 'Escape' || (e.altKey && e.keyCode === 78)) {
removeModal();
}
}
close.addEventListener('click', removeModal);
modal.addEventListener('click', e => {
if(e.target === modal) {
if(!canclose || canclose()) removeModal();
}
});
document.addEventListener('keydown', escapeEvent);
modal_content.appendChild(close);
document.body.appendChild(modal);
return modal;
}
async function handleFiles(files, mediaArray, mediaContainer) {
let images = [];
let videos = [];
let gifs = [];
for (let i = 0; i < files.length; i++) {
let file = files[i];
if (file.type.includes('gif')) {
// max 15 mb
if (file.size > 15000000) {
return alert(LOC.gifs_max.message);
}
gifs.push(file);
} else if (file.type.includes('video')) {
// max 500 mb
if (file.size > 500000000) {
return alert(LOC.videos_max.message);
}
videos.push(file);
} else if (file.type.includes('image')) {
// max 5 mb
if (file.size > 5000000) {
// convert png to jpeg
await new Promise(resolve => {
let canvas = document.createElement('canvas');
let ctx = canvas.getContext('2d');
let img = new Image();
img.onload = function () {
canvas.width = img.width;
canvas.height = img.height;
ctx.drawImage(img, 0, 0);
let dataURL = canvas.toDataURL('image/jpeg', 0.9);
let blobBin = atob(dataURL.split(',')[1]);
let array = [];
for (let i = 0; i < blobBin.length; i++) {
array.push(blobBin.charCodeAt(i));
}
file = new Blob([new Uint8Array(array)], { type: 'image/jpeg' });
resolve();
};
img.src = URL.createObjectURL(file);
});
if(file.size > 5000000) {
return alert(LOC.images_max.message);
}
}
images.push(file);
}
}
// either up to 4 images or 1 video or 1 gif
if (images.length > 0) {
if (images.length > 4) {
images = images.slice(0, 4);
}
if (videos.length > 0 || gifs.length > 0) {
return alert(LOC.max_count.message);
}
}
if (videos.length > 0) {
if (images.length > 0 || gifs.length > 0 || videos.length > 1) {
return alert(LOC.max_count.message);
}
}
if (gifs.length > 0) {
if (images.length > 0 || videos.length > 0 || gifs.length > 1) {
return alert(LOC.max_count.message);
}
}
// get base64 data
let media = [...images, ...videos, ...gifs];
let base64Data = [];
for (let i = 0; i < media.length; i++) {
let file = media[i];
let reader = new FileReader();
reader.readAsArrayBuffer(file);
reader.onload = () => {
base64Data.push(reader.result);
if (base64Data.length === media.length) {
while (mediaArray.length >= 4) {
mediaArray.pop();
mediaContainer.lastChild.remove();
}
base64Data.forEach(data => {
let div = document.createElement('div');
let img = document.createElement('img');
div.title = file.name;
div.id = `new-tweet-media-img-${Date.now()}${Math.random()}`.replace('.', '-');
div.className = "new-tweet-media-img-div";
img.className = "new-tweet-media-img";
let progress = document.createElement('span');
progress.hidden = true;
progress.className = "new-tweet-media-img-progress";
let remove = document.createElement('span');
remove.className = "new-tweet-media-img-remove";
let alt;
if (!file.type.includes('video')) {
alt = document.createElement('span');
alt.className = "new-tweet-media-img-alt";
alt.innerText = "ALT";
alt.addEventListener('click', () => {
mediaObject.alt = prompt(LOC.alt_text.message, mediaObject.alt || '');
});
}
let cw = document.createElement('span');
cw.className = "new-tweet-media-img-cw";
cw.innerText = "CW";
cw.addEventListener('click', () => {
createModal(`
`);
let graphic_violence = document.getElementById('cw-modal-graphic_violence');
let adult_content = document.getElementById('cw-modal-adult_content');
let sensitive_content = document.getElementById('cw-modal-other');
[graphic_violence, adult_content, sensitive_content].forEach(checkbox => {
checkbox.addEventListener('change', () => {
if (checkbox.checked) {
mediaObject.cw.push(checkbox.id.slice(9));
} else {
let index = mediaObject.cw.indexOf(checkbox.id.slice(9));
if (index > -1) {
mediaObject.cw.splice(index, 1);
}
}
});
});
});
let dataBase64 = arrayBufferToBase64(data);
let mediaObject = {
div, img,
id: img.id,
data: data,
dataBase64: dataBase64,
type: file.type,
cw: [],
category: file.type.includes('gif') ? 'tweet_gif' : file.type.includes('video') ? 'tweet_video' : 'tweet_image'
};
mediaArray.push(mediaObject);
img.src = file.type.includes('video') ? 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAWUSURBVHhe7Z1pqG5THMbPNV1jul1TJEOZuqYMRZEpoRARvlw+uIjwASlRFIkMHwzJ8AVfZMhYOGRKESlDkciQyJhknj3PXu9b3nP2sPba9x3Wfp5f/dpr77p1zl7Ped+11l77f5fMz8/PGV3WGByNKA6AOA6AOA6AOA6AOA6AOA6AOA6AOA6AOA6AOA6AOA6AOA6AOA6AOA6AOA6AOA6AOA6AOA6AOA6AOA6AOA6AOA6AOG3eC1gGl4ammXF+h9+HZj0xAdgC3gwPhw5AHjAAL8Kz4Re8UEVTANaCT8HDijOTGy9B9t1fxVkJTWOAneAhoWky5ADIPqykKQCbQA8U84V9xz6spKlzlwyOJl9q+9B/3eI4AOI0zQIOhs+H5iJeh3fBP4qzcjaDF8DNizPTls/gDfCH4qycDeBZcLfibDEcxL8QmotJDQA7fVf4QXFWz8nwvtA0LTkJPhCatewM34LrFGej1AYg9SvgF/hNaDby8eBo2vPp4NjEl5B90hqPAcRxAMRxAMRxAMRxAMRxAMRJDcCaA2NYe3A07Ym9d236Y4TUAGwET4VlCw//Z124MjRNAmfADUOzEnb8iZB90pouS8H/QC5A1C0FMwDcUWTS4YLbz6FZCgOwFaz6Yx7LUrDJh7EsBZue0KcA/Av/Dk0TS18CwIcm/KjbEV4Nf4Qmgr4E4ErIbdAfwUvhXvB+WLkb1gS6BICzAG5Y+KTG2EfGXVn42PRDeAo8AnLjSs5wplV2b4dy3z/7IokuATgHbtfg9vBuOA04JngOHgjPhJ/D3Lgdlt3XhV4Ek0gNAL9jH4RNg66f4J2hOTX4lgx/hj3gdbBuTj1r3At/C81KuA5zD0wa96QGgB0fO+L+c3CcNt/Bi+G+8BGYw4wh9t616Y8R+jIIbMN78AR4NHyTF5RRDADhoInvPO4Pz4NfQUlUAzCE36+3wN0h34D+FUqhHoAhX8Pz4X7wSZg8rcoNB2CUt+Ex8Hj4Li/0HQdgMRxNPwY5W+D8+lvYW1IDsD6Mfc6/zeCYG3zRgq9lcf3gDsj1hEnDRZ4YNoXsk9Z02Q/wDuRKVd3CysbwQrh1cTY+WL7m2dAcG/vAa+ChcFKvzXN2ciPkGKUK7spaBfmJVYbEhpBJBICwZA7HB1dBPnnMAW8IWY3w6SJf1twb3soLueMApMFnHJfBqFJss4wDkE4vyuc4AGlwqzafLLJ4ZtY4AO0Y7sF/A57OC7nTZRYwSyViJjEL4MDvWjjJaaBLxEQyzgBsCS+Hp8FJl8p1iZgpwpU1LmLxxnJL2TTqJLtEzBTg9/yx8DV4PayttJk7DsAo3BfwOHwYruCFvuMABDhYvQm+Co+CMvdFPQB8e/lcyH0A3Bq2HpRCNQD8vY+Er0BuBZOtZKoYgF3gQ/AJuCcvKJMaAI6UaQyzUiJmOeTyLRewjoOxP/80cYmY1QDn7yy1wvk8t3hx5SwXXCImkrKVQC7XchWMu3iqdsvkwFhLxHQZA/Dfcpl02xonVR9o4d65HSCXn5+GOXc+4X6/sns7lNvtkvuxSwBmiSsgV+/4QIQFIvi0juvo3MJlauhLAPhJ9CjkfP4SmPR9qEhfAmAScQDE6RKAWSoR02dcIkYYl4gRxyVixHGJGDNeHABxHABxHABxHABxHABxUgOgUCJmFuAiTwzyJWL6ikvEmM6MbUeQ6QEOgDhNAeB/umDyprYPmwLAKpkydXN7CPuuttJpUwDehy+HpskQDuDZh5U0zQIIN1zeBg+C0yiSYNrDsrbPQL7wyh1FlcQEYAgrYjkAecAARNUwbBMA00M8DRTHARDHARDHARDHARDHARDHARDHARDHARDHARDHARDHARDHARDHARDHARDHARDHARDHARDHARDHARDHARDHARDHAZBmbu4/x6swK3hIFr4AAAAASUVORK5CYII=' : `data:${file.type};base64,${dataBase64}`;
remove.addEventListener('click', () => {
div.remove();
for (let i = mediaArray.length - 1; i >= 0; i--) {
let m = mediaArray[i];
if (m.id === img.id) mediaArray.splice(i, 1);
}
});
div.append(img, progress, remove);
if (!file.type.includes('video')) {
img.addEventListener('click', () => {
if(!img.src.endsWith('?name=orig') && !img.src.startsWith('data:')) {
img.src += '?name=orig';
}
new Viewer(mediaContainer, {
transition: false
});
});
div.append(alt);
} else {
cw.style.marginLeft = '-53px';
}
div.append(cw);
mediaContainer.append(div);
});
}
}
}
}
let isURL = (str) => {
try {
new URL(str);
return true;
} catch (_) {
return false;
}
}
function handleDrop(event, mediaArray, mediaContainer) {
let text = event.dataTransfer.getData("Text").trim();
if(text.length <= 1) {
event.stopPropagation();
event.preventDefault();
let files = event.dataTransfer.files;
handleFiles(files, mediaArray, mediaContainer);
}
}
function getMedia(mediaArray, mediaContainer) {
let input = document.createElement('input');
input.type = 'file';
input.multiple = true;
input.accept = 'image/png,image/jpeg,image/gif,video/mp4,video/mov';
input.addEventListener('change', () => {
handleFiles(input.files, mediaArray, mediaContainer);
});
input.click();
};
function getDMMedia(mediaArray, mediaContainer, modalElement) {
let input = document.createElement('input');
input.type = 'file';
input.accept = 'image/*';
input.addEventListener('change', async () => {
let files = input.files;
let images = [];
let gifs = [];
for (let i = 0; i < files.length; i++) {
let file = files[i];
if (file.type.includes('gif')) {
// max 15 mb
if (file.size > 15000000) {
return alert(LOC.gifs_max.message);
}
gifs.push(file);
} else if (file.type.includes('image')) {
// max 5 mb
if (file.size > 5000000) {
return alert(LOC.images_max.message);
}
images.push(file);
}
}
// get base64 data
let media = [...images, ...gifs];
let base64Data = [];
for (let i = 0; i < media.length; i++) {
let file = media[i];
let reader = new FileReader();
reader.readAsArrayBuffer(file);
reader.onload = () => {
base64Data.push(reader.result);
if (base64Data.length === media.length) {
mediaContainer.innerHTML = '';
while (mediaArray.length > 0) {
mediaArray.pop();
}
base64Data.forEach(data => {
let div = document.createElement('div');
let img = document.createElement('img');
div.title = file.name;
div.id = `new-tweet-media-img-${Date.now()}${Math.random()}`.replace('.', '-');
div.className = "new-tweet-media-img-div";
img.className = "new-tweet-media-img";
let progress = document.createElement('span');
progress.hidden = true;
progress.className = "new-tweet-media-img-progress";
let remove = document.createElement('span');
remove.className = "new-tweet-media-img-remove";
let dataBase64 = arrayBufferToBase64(data);
let mediaObject = {
div, img,
id: img.id,
data: data,
dataBase64: dataBase64,
type: file.type,
category: file.type.includes('gif') ? 'tweet_gif' : file.type.includes('video') ? 'tweet_video' : 'tweet_image'
};
mediaArray.push(mediaObject);
img.src = file.type.includes('video') ? 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAWUSURBVHhe7Z1pqG5THMbPNV1jul1TJEOZuqYMRZEpoRARvlw+uIjwASlRFIkMHwzJ8AVfZMhYOGRKESlDkciQyJhknj3PXu9b3nP2sPba9x3Wfp5f/dpr77p1zl7Ped+11l77f5fMz8/PGV3WGByNKA6AOA6AOA6AOA6AOA6AOA6AOA6AOA6AOA6AOA6AOA6AOA6AOA6AOA6AOA6AOA6AOA6AOA6AOA6AOA6AOA6AOA6AOG3eC1gGl4ammXF+h9+HZj0xAdgC3gwPhw5AHjAAL8Kz4Re8UEVTANaCT8HDijOTGy9B9t1fxVkJTWOAneAhoWky5ADIPqykKQCbQA8U84V9xz6spKlzlwyOJl9q+9B/3eI4AOI0zQIOhs+H5iJeh3fBP4qzcjaDF8DNizPTls/gDfCH4qycDeBZcLfibDEcxL8QmotJDQA7fVf4QXFWz8nwvtA0LTkJPhCatewM34LrFGej1AYg9SvgF/hNaDby8eBo2vPp4NjEl5B90hqPAcRxAMRxAMRxAMRxAMRxAMRJDcCaA2NYe3A07Ym9d236Y4TUAGwET4VlCw//Z124MjRNAmfADUOzEnb8iZB90pouS8H/QC5A1C0FMwDcUWTS4YLbz6FZCgOwFaz6Yx7LUrDJh7EsBZue0KcA/Av/Dk0TS18CwIcm/KjbEV4Nf4Qmgr4E4ErIbdAfwUvhXvB+WLkb1gS6BICzAG5Y+KTG2EfGXVn42PRDeAo8AnLjSs5wplV2b4dy3z/7IokuATgHbtfg9vBuOA04JngOHgjPhJ/D3Lgdlt3XhV4Ek0gNAL9jH4RNg66f4J2hOTX4lgx/hj3gdbBuTj1r3At/C81KuA5zD0wa96QGgB0fO+L+c3CcNt/Bi+G+8BGYw4wh9t616Y8R+jIIbMN78AR4NHyTF5RRDADhoInvPO4Pz4NfQUlUAzCE36+3wN0h34D+FUqhHoAhX8Pz4X7wSZg8rcoNB2CUt+Ex8Hj4Li/0HQdgMRxNPwY5W+D8+lvYW1IDsD6Mfc6/zeCYG3zRgq9lcf3gDsj1hEnDRZ4YNoXsk9Z02Q/wDuRKVd3CysbwQrh1cTY+WL7m2dAcG/vAa+ChcFKvzXN2ciPkGKUK7spaBfmJVYbEhpBJBICwZA7HB1dBPnnMAW8IWY3w6SJf1twb3soLueMApMFnHJfBqFJss4wDkE4vyuc4AGlwqzafLLJ4ZtY4AO0Y7sF/A57OC7nTZRYwSyViJjEL4MDvWjjJaaBLxEQyzgBsCS+Hp8FJl8p1iZgpwpU1LmLxxnJL2TTqJLtEzBTg9/yx8DV4PayttJk7DsAo3BfwOHwYruCFvuMABDhYvQm+Co+CMvdFPQB8e/lcyH0A3Bq2HpRCNQD8vY+Er0BuBZOtZKoYgF3gQ/AJuCcvKJMaAI6UaQyzUiJmOeTyLRewjoOxP/80cYmY1QDn7yy1wvk8t3hx5SwXXCImkrKVQC7XchWMu3iqdsvkwFhLxHQZA/Dfcpl02xonVR9o4d65HSCXn5+GOXc+4X6/sns7lNvtkvuxSwBmiSsgV+/4QIQFIvi0juvo3MJlauhLAPhJ9CjkfP4SmPR9qEhfAmAScQDE6RKAWSoR02dcIkYYl4gRxyVixHGJGDNeHABxHABxHABxHABxHABxUgOgUCJmFuAiTwzyJWL6ikvEmM6MbUeQ6QEOgDhNAeB/umDyprYPmwLAKpkydXN7CPuuttJpUwDehy+HpskQDuDZh5U0zQIIN1zeBg+C0yiSYNrDsrbPQL7wyh1FlcQEYAgrYjkAecAARNUwbBMA00M8DRTHARDHARDHARDHARDHARDHARDHARDHARDHARDHARDHARDHARDHARDHARDHARDHARDHARDHARDHARDHARDHARDHAZBmbu4/x6swK3hIFr4AAAAASUVORK5CYII=' : `data:${file.type};base64,${dataBase64}`;
remove.addEventListener('click', () => {
div.remove();
for (let i = mediaArray.length - 1; i >= 0; i--) {
let m = mediaArray[i];
if (m.id === img.id) mediaArray.splice(i, 1);
}
});
div.append(img, progress, remove);
if (!file.type.includes('video')) {
img.addEventListener('click', () => {
if(!img.src.endsWith('?name=orig') && !img.src.startsWith('data:')) {
img.src += '?name=orig';
}
new Viewer(mediaContainer, {
transition: false
});
});
}
mediaContainer.append(div);
setTimeout(() => modalElement.scrollTop = modalElement.scrollHeight, 50);
});
}
}
}
});
input.click();
};
function timeElapsed(targetTimestamp) {
let currentDate = new Date();
let currentTimeInms = currentDate.getTime();
let targetDate = new Date(targetTimestamp);
let targetTimeInms = targetDate.getTime();
let elapsed = Math.floor((currentTimeInms - targetTimeInms) / 1000);
const MonthNames = [
LOC.january.message,
LOC.february.message,
LOC.march.message,
LOC.april.message,
LOC.may.message,
LOC.june.message,
LOC.july.message,
LOC.august.message,
LOC.september.message,
LOC.october.message,
LOC.november.message,
LOC.december.message
];
if (elapsed < 1) {
return LOC.s.message.replace('$NUMBER$', 0);
}
if (elapsed < 60) { //< 60 sec
return LOC.s.message.replace('$NUMBER$', elapsed);
}
if (elapsed < 3600) { //< 60 minutes
return LOC.m.message.replace('$NUMBER$', Math.floor(elapsed / (60)));
}
if (elapsed < 86400) { //< 24 hours
return LOC.h.message.replace('$NUMBER$', Math.floor(elapsed / (3600)));
}
if (elapsed < 604800) { //<7 days
return LOC.d.message.replace('$NUMBER$', Math.floor(elapsed / (86400)));
}
if (elapsed < 2628000) { //<1 month
return MonthNames[targetDate.getMonth()].replace('$NUMBER$', targetDate.getDate());
}
return `${MonthNames[targetDate.getMonth()].replace('$NUMBER$', targetDate.getDate())}, ${targetDate.getFullYear()}`; //more than a monh
}
function openInNewTab(href) {
Object.assign(document.createElement('a'), {
target: '_blank',
rel: 'noopener noreferrer',
href: href,
}).click();
}
function onVisibilityChange(callback) {
var visible = true;
if (!callback) {
throw new Error('no callback given');
}
function focused() {
if (!visible) {
callback(visible = true);
}
}
function unfocused() {
if (visible) {
callback(visible = false);
}
}
// Standards:
if ('hidden' in document) {
visible = !document.hidden;
document.addEventListener('visibilitychange',
function () { (document.hidden ? unfocused : focused)() });
}
if ('mozHidden' in document) {
visible = !document.mozHidden;
document.addEventListener('mozvisibilitychange',
function () { (document.mozHidden ? unfocused : focused)() });
}
if ('webkitHidden' in document) {
visible = !document.webkitHidden;
document.addEventListener('webkitvisibilitychange',
function () { (document.webkitHidden ? unfocused : focused)() });
}
if ('msHidden' in document) {
visible = !document.msHidden;
document.addEventListener('msvisibilitychange',
function () { (document.msHidden ? unfocused : focused)() });
}
// IE 9 and lower:
if ('onfocusin' in document) {
document.onfocusin = focused;
document.onfocusout = unfocused;
}
// All others:
window.onpageshow = window.onfocus = focused;
window.onpagehide = window.onblur = unfocused;
};
function escapeHTML(unsafe) {
return unsafe
.replace(//g, ">")
.replace(/"/g, """)
.replace(/'/g, "’");
}
async function renderTweetBodyHTML(full_text, entities, display_text_range, is_quote_tweet=false) {
let result = "",
last_pos = 0,
index_map = {}; // {start_position: [end_position, replacer_func]}
hashflags = [];
if (vars.enableHashflags) {
hashflags = await API.discover.getHashflagsV2();
}
full_text_array = Array.from(full_text);
if (is_quote_tweet) { // for quoted tweet we need only hashflags and readable urls
if (entities.hashtags) {
entities.hashtags.forEach(hashtag => {
let hashflag = hashflags.find(h => h.hashtag.toLowerCase() === hashtag.text.toLowerCase());
index_map[hashtag.indices[0]] = [hashtag.indices[1], text =>
`#${escapeHTML(hashtag.text)}`+
`${hashflag ? `
` : ''}`
];
});
}
if (entities.urls) {
entities.urls.forEach(url => {
index_map[url.indices[0]] = [url.indices[1], text => `${escapeHTML(url.display_url)}`];
});
}
} else {
if (entities.hashtags) {
entities.hashtags.forEach(hashtag => {
let hashflag = hashflags.find(h => h.hashtag.toLowerCase() === hashtag.text.toLowerCase());
index_map[hashtag.indices[0]] = [hashtag.indices[1], text => ``+
`#${escapeHTML(hashtag.text)}`+
`${hashflag ? `
` : ''}`+
``];
});
}
if (entities.symbols) {
entities.symbols.forEach(symbol => {
index_map[symbol.indices[0]] = [symbol.indices[1], text => ``+
`$${escapeHTML(symbol.text)}`+
``];
});
}
if (entities.urls) {
entities.urls.forEach(url => {
index_map[url.indices[0]] = [url.indices[1], text =>
``+
`${escapeHTML(url.display_url)}`];
});
}
if (entities.user_mentions) {
entities.user_mentions.forEach(user => {
index_map[user.indices[0]] = [user.indices[1], text => `${escapeHTML(text)}`];
});
}
}
let display_start = display_text_range !== undefined ? display_text_range[0] : 0;
let display_end = display_text_range !== undefined ? display_text_range[1] : full_text_array.length;
for (let [current_pos, _] of full_text_array.entries()) {
if (current_pos < display_start) { // do not render first part of message
last_pos = current_pos + 1; // to start copy from next symbol
continue;
}
if (current_pos == display_end || // reached the end of visible part
current_pos == full_text_array.length - 1) { // reached the end of tweet itself
if (display_end == full_text_array.length) current_pos++; // dirty hack to include last element of slice
result += escapeHTML(full_text_array.slice(last_pos, current_pos).join(''));
break;
}
if (current_pos > display_end) {
break; // do not render last part of message
}
if (current_pos in index_map) {
let [end, func] = index_map[current_pos];
if (current_pos > last_pos) {
result += escapeHTML(full_text_array.slice(last_pos, current_pos).join('')); // store chunk of untouched text
}
result += func(full_text_array.slice(current_pos, end).join('')); // run replacer func on corresponding range
last_pos = end;
}
}
return result
}
function arrayInsert(arr, index, value) {
return [...arr.slice(0, index), value, ...arr.slice(index)];
}
function generatePoll(tweet, tweetElement, user) {
let pollElement = tweetElement.getElementsByClassName('tweet-card')[0];
pollElement.innerHTML = '';
let poll = tweet.card.binding_values;
let choices = Object.keys(poll).filter(key => key.endsWith('label')).map((key, i) => ({
label: poll[key].string_value,
count: poll[key.replace('label', 'count')] ? +poll[key.replace('label', 'count')].string_value : 0,
id: i+1
}));
let voteCount = choices.reduce((acc, cur) => acc + cur.count, 0);
if(poll.selected_choice || user.id_str === tweet.user.id_str || (poll.counts_are_final && poll.counts_are_final.boolean_value)) {
for(let i in choices) {
let choice = choices[i];
if(user.id_str !== tweet.user.id_str && poll.selected_choice && choice.id === +poll.selected_choice.string_value) {
choice.selected = true;
}
choice.percentage = Math.round(choice.count / voteCount * 100);
let choiceElement = document.createElement('div');
choiceElement.classList.add('choice');
choiceElement.innerHTML = `
${escapeHTML(choice.label)}
${choice.selected ? `` : ''}
${isFinite(choice.percentage) ? `${choice.count} (${choice.percentage}%)
` : '0
'}
`;
pollElement.append(choiceElement);
}
} else {
for(let i in choices) {
let choice = choices[i];
let choiceElement = document.createElement('div');
choiceElement.classList.add('choice', 'choice-unselected');
choiceElement.innerHTML = `
${escapeHTML(choice.label)}
`;
choiceElement.addEventListener('click', async () => {
let newCard = await API.tweet.vote(poll.api.string_value, tweet.id_str, tweet.card.url, tweet.card.name, choice.id);
tweet.card = newCard.card;
generateCard(tweet, tweetElement, user);
});
pollElement.append(choiceElement);
}
}
if(tweet.card.url.startsWith('card://')) {
let footer = document.createElement('span');
footer.classList.add('poll-footer');
let endsAtMessage;
if(LOC.ends_at.message.includes("$DATE$")) {
endsAtMessage = LOC.ends_at.message.replace('$DATE$', new Date(poll.end_datetime_utc.string_value).toLocaleString());
} else {
endsAtMessage = `${LOC.ends_at.message} ${new Date(poll.end_datetime_utc.string_value).toLocaleString()}`;
}
footer.innerHTML = `${voteCount} ${voteCount === 1 ? LOC.vote.message : LOC.votes.message}${(!poll.counts_are_final || !poll.counts_are_final.boolean_value) && poll.end_datetime_utc ? ` ・ ${endsAtMessage}` : ''}`;
pollElement.append(footer);
}
}
function generateCard(tweet, tweetElement, user) {
if(!tweet.card) return;
if(tweet.card.name === 'promo_image_convo' || tweet.card.name === 'promo_video_convo') {
let vals = tweet.card.binding_values;
let a = document.createElement('a');
a.href = vals.thank_you_url ? vals.thank_you_url.string_value : "#";
a.target = '_blank';
a.title = vals.thank_you_text.string_value;
let img = document.createElement('img');
let imgValue = vals.promo_image;
if(!imgValue) {
imgValue = vals.cover_promo_image_original;
}
if(!imgValue) {
imgValue = vals.cover_promo_image_large;
}
if(!imgValue) {
return;
}
img.src = imgValue.image_value.url;
img.width = sizeFunctions[1](imgValue.image_value.width, imgValue.image_value.height)[0];
img.height = sizeFunctions[1](imgValue.image_value.width, imgValue.image_value.height)[1];
img.className = 'tweet-media-element';
let ctas = [];
if(vals.cta_one) {
ctas.push([vals.cta_one, vals.cta_one_tweet]);
}
if(vals.cta_two) {
ctas.push([vals.cta_two, vals.cta_two_tweet]);
}
if(vals.cta_three) {
ctas.push([vals.cta_three, vals.cta_three_tweet]);
}
if(vals.cta_four) {
ctas.push([vals.cta_four, vals.cta_four_tweet]);
}
let buttonGroup = document.createElement('div');
buttonGroup.classList.add('tweet-button-group');
for(let b of ctas) {
let button = document.createElement('button');
button.className = `nice-button tweet-app-button`;
button.innerText = `${LOC.tweet_verb.message} ${b[0].string_value}`;
button.addEventListener('click', async () => {
let modal = createModal(`
${LOC.do_you_want_to_tweet.message.replace("$TWEET_TEXT$", b[1].string_value)}
`);
modal.getElementsByClassName('nice-button')[0].addEventListener('click', async () => {
modal.removeModal();
try {
await API.tweet.postV2({
"text": b[1].string_value,
"card_uri": tweet.card.url,
});
} catch(e) {
console.error(e);
alert(String(e));
}
});
});
buttonGroup.append(button);
}
a.append(img);
tweetElement.getElementsByClassName('tweet-card')[0].append(a);
tweetElement.getElementsByClassName('tweet-card')[0].append(buttonGroup);
} else if(tweet.card.name === "player") {
let iframe = document.createElement('iframe');
iframe.src = tweet.card.binding_values.player_url.string_value.replace("autoplay=true", "autoplay=false");
iframe.classList.add('tweet-player');
iframe.width = 450;
iframe.height = 250;
tweetElement.getElementsByClassName('tweet-card')[0].innerHTML = '';
tweetElement.getElementsByClassName('tweet-card')[0].append(iframe);
} else if(tweet.card.name === "unified_card") {
let uc = JSON.parse(tweet.card.binding_values.unified_card.string_value);
for(let cn of uc.components) {
let co = uc.component_objects[cn];
if(co.type === "media") {
let media = uc.media_entities[co.data.id];
let video = document.createElement('video');
video.className = 'tweet-media-element tweet-media-element-one';
let [w, h] = sizeFunctions[1](media.original_info.width, media.original_info.height);
video.width = w;
video.height = h;
video.crossOrigin = 'anonymous';
video.loading = 'lazy';
video.controls = true;
if(!media.video_info) {
console.log(`bug found in ${tweet.id_str}, please report this message to https://github.com/dimdenGD/OldTwitter/issues`, tweet);
continue;
};
let variants = media.video_info.variants.sort((a, b) => {
if(!b.bitrate) return -1;
return b.bitrate-a.bitrate;
});
if(typeof(vars.savePreferredQuality) !== 'boolean') {
chrome.storage.sync.set({
savePreferredQuality: true
}, () => {});
vars.savePreferredQuality = true;
}
if(localStorage.preferredQuality && vars.savePreferredQuality) {
let closestQuality = variants.filter(v => v.bitrate).reduce((prev, curr) => {
return (Math.abs(parseInt(curr.url.match(/\/(\d+)x/)[1]) - parseInt(localStorage.preferredQuality)) < Math.abs(parseInt(prev.url.match(/\/(\d+)x/)[1]) - parseInt(localStorage.preferredQuality)) ? curr : prev);
});
let preferredQualityVariantIndex = variants.findIndex(v => v.url === closestQuality.url);
if(preferredQualityVariantIndex !== -1) {
let preferredQualityVariant = variants[preferredQualityVariantIndex];
variants.splice(preferredQualityVariantIndex, 1);
variants.unshift(preferredQualityVariant);
}
}
for(let v in variants) {
let source = document.createElement('source');
source.src = variants[v].url;
source.type = variants[v].content_type;
video.append(source);
}
tweetElement.getElementsByClassName('tweet-card')[0].append(video, document.createElement('br'));
} else if(co.type === "app_store_details") {
let app = uc.app_store_data[uc.destination_objects[co.data.destination].data.app_id][0];
let appElement = document.createElement('div');
appElement.classList.add('tweet-app-info');
appElement.innerHTML = `
${escapeHTML(app.title.content)}
${escapeHTML(app.category.content)}
`;
tweetElement.getElementsByClassName('tweet-card')[0].append(appElement);
} else if(co.type === "button_group") {
let buttonGroup = document.createElement('div');
buttonGroup.classList.add('tweet-button-group');
for(let b of co.data.buttons) {
let app = uc.app_store_data[uc.destination_objects[b.destination].data.app_id][0];
let button = document.createElement('a');
button.href = `http://play.google.com/store/apps/details?id=${app.id}`;
button.target = '_blank';
button.className = `nice-button tweet-app-button tweet-app-button-${b.style}`
button.innerText = b.action[0].toUpperCase() + b.action.slice(1);
buttonGroup.append(button);
}
tweetElement.getElementsByClassName('tweet-card')[0].append(buttonGroup);
}
}
} else if(tweet.card.name === "summary" || tweet.card.name === "summary_large_image") {
let vals = tweet.card.binding_values;
let a = document.createElement('a');
let url = vals.card_url.string_value;
if(tweet.entities && tweet.entities.urls) {
let urlEntity = tweet.entities.urls.find(u => u.url === url);
if(urlEntity) {
url = urlEntity.expanded_url;
}
}
a.target = '_blank';
a.href = url;
a.className = 'tweet-card-link box';
a.innerHTML = `
${vals.thumbnail_image ? `` : ''}
`;
tweetElement.getElementsByClassName('tweet-card')[0].append(a);
} else if(tweet.card.url.startsWith('card://')) {
generatePoll(tweet, tweetElement, user);
}
}
function createEmojiPicker(container, input, style = {}) {
let picker = new EmojiPicker({
i18n: {
"categories": {
"custom": LOC.custom.message,
"smileys-emotion": LOC.smileys_emotion.message,
"people-body": LOC.people_body.message,
"animals-nature": LOC.animals_nature.message,
"food-drink": LOC.food_drink.message,
"travel-places": LOC.travel_places.message,
"activities": LOC.activities.message,
"objects": LOC.objects.message,
"symbols": LOC.symbols.message,
"flags": LOC.flags.message
},
"categoriesLabel": LOC.categories.message,
"emojiUnsupportedMessage": LOC.unsupported_emoji.message,
"favoritesLabel": LOC.favorites.message,
"loadingMessage": LOC.loading.message,
"networkErrorMessage": LOC.cant_load_emoji.message,
"regionLabel": LOC.emoji_picker.message,
"searchDescription": LOC.emoji_search_description.message,
"searchLabel": LOC.search.message,
"searchResultsLabel": LOC.search_results.message,
"skinToneDescription": "When expanded, press up or down to select and enter to choose.",
"skinToneLabel": LOC.skin_tone_label.message.replace("$SKIN_TONE$", "{skinTone}"),
"skinTones": [
"Default",
"Light",
"Medium-Light",
"Medium",
"Medium-Dark",
"Dark"
],
"skinTonesLabel": LOC.skin_tones_label.message
}
});
for(let i in style) {
picker.style[i] = style[i];
}
picker.className = isDarkModeEnabled ? 'dark' : 'light';
picker.addEventListener('emoji-click', e => {
let pos = input.selectionStart;
let text = input.value;
input.value = text.slice(0, pos) + e.detail.unicode + text.slice(pos);
input.selectionStart = pos + e.detail.unicode.length;
});
container.append(picker);
let observer;
if(vars.enableTwemoji) {
const style = document.createElement('style');
style.textContent = `.twemoji {
width: var(--emoji-size);
height: var(--emoji-size);
pointer-events: none;
}`;
picker.shadowRoot.appendChild(style);
observer = new MutationObserver(() => {
for (const emoji of picker.shadowRoot.querySelectorAll('.emoji')) {
// Avoid infinite loops of MutationObserver
if (!emoji.querySelector('.twemoji')) {
// Do not use default 'emoji' class name because it conflicts with emoji-picker-element's
twemoji.parse(emoji, { className: 'twemoji' })
}
}
})
observer.observe(picker.shadowRoot, {
subtree: true,
childList: true
});
}
setTimeout(() => {
function oc (e) {
if (picker.contains(e.target)) return;
if(observer) {
observer.disconnect();
}
picker.remove();
document.removeEventListener('click', oc);
picker.database.close();
}
document.addEventListener('click', oc);
}, 100);
return picker;
}
const RED = 0.2126;
const GREEN = 0.7152;
const BLUE = 0.0722;
const GAMMA = 2.4;
function luminance(r, g, b) {
const a = [r, g, b].map(v => {
v /= 255;
return v <= 0.03928
? v / 12.92
: Math.pow((v + 0.055) / 1.055, GAMMA);
});
return a[0] * RED + a[1] * GREEN + a[2] * BLUE;
}
function contrast(rgb1, rgb2) {
const lum1 = luminance(...rgb1);
const lum2 = luminance(...rgb2);
const brightest = Math.max(lum1, lum2);
const darkest = Math.min(lum1, lum2);
return (brightest + 0.05) / (darkest + 0.05);
}
function hex2rgb(hex) {
if(!hex.startsWith('#')) hex = `#${hex}`;
const r = parseInt(hex.slice(1, 3), 16)
const g = parseInt(hex.slice(3, 5), 16)
const b = parseInt(hex.slice(5, 7), 16)
return [r, g, b];
}
function rgb2hex(r, g, b) {
return `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`;
}
function rgbToHsl(r, g, b) {
r /= 255; g /= 255; b /= 255;
let max = Math.max(r, g, b);
let min = Math.min(r, g, b);
let d = max - min;
let h;
if (d === 0) h = 0;
else if (max === r) h = ((((g - b) / d) % 6)+6)%6;
else if (max === g) h = (b - r) / d + 2;
else if (max === b) h = (r - g) / d + 4;
let l = (min + max) / 2;
let s = d === 0 ? 0 : d / (1 - Math.abs(2 * l - 1));
return [h * 60, s, l];
}
function hslToRgb(h, s, l) {
let c = (1 - Math.abs(2 * l - 1)) * s;
let hp = h / 60.0;
let x = c * (1 - Math.abs((hp % 2) - 1));
let rgb1;
if (isNaN(h)) rgb1 = [0, 0, 0];
else if (hp <= 1) rgb1 = [c, x, 0];
else if (hp <= 2) rgb1 = [x, c, 0];
else if (hp <= 3) rgb1 = [0, c, x];
else if (hp <= 4) rgb1 = [0, x, c];
else if (hp <= 5) rgb1 = [x, 0, c];
else if (hp <= 6) rgb1 = [c, 0, x];
let m = l - c * 0.5;
return [
Math.round(255 * (rgb1[0] + m)),
Math.round(255 * (rgb1[1] + m)),
Math.round(255 * (rgb1[2] + m))
];
}
function getBackgroundColor() {
let root = document.documentElement;
let bg_color = getComputedStyle(root).getPropertyValue('--background-color');
if(bg_color === 'white') {
bg_color = '#ffffff';
} else if(bg_color === 'black') {
bg_color = '#000000';
} else if(bg_color.startsWith('rgb(')) {
let rgb = bg_color.slice(4, -1).split(',').map(v => parseInt(v));
bg_color = rgb2hex(...rgb);
}
if(!bg_color) bg_color = '#ffffff';
return bg_color;
}
function makeSeeableColor(color, bg_color = getBackgroundColor()) {
let bg_rgb = hex2rgb(bg_color);
let rgb = hex2rgb(color);
let c = contrast(bg_rgb, rgb);
let hsl = rgbToHsl(...rgb);
let bg_hsl = rgbToHsl(...bg_rgb);
if(c < 4.5) {
if(bg_hsl[2] > 0.7) {
if(hsl[2] > 0.7) {
hsl[2] = 0.4;
if(hsl[1] >= 0.1) hsl[1] -= 0.1;
}
}
if(bg_hsl[2] < 0.4) {
if(c < 2.9) {
if(hsl[2] <= 0.6) {
hsl[2] = 0.6;
if(hsl[1] >= 0.1) hsl[1] -= 0.1;
}
}
}
}
return rgb2hex(...hslToRgb(...hsl));
}
const getLinkColors = ids => {
if(typeof ids === "string") ids = ids.split(",");
ids = [...new Set(ids)];
return new Promise(async (resolve, reject) => {
chrome.storage.local.get(["linkColors"], async data => {
let linkColors = data.linkColors || {};
let toFetch = [];
let fetched = [];
for(let id of ids) {
if(typeof linkColors[id] === "undefined") {
toFetch.push(id);
} else {
if(linkColors[id]) fetched.push({id, color: linkColors[id]});
}
}
if(toFetch.length === 0) {
return resolve(fetched);
}
try {
let res = await fetch("https://dimden.dev/services/twitter_link_colors/v2/get_multiple/"+toFetch.join(","));
let json = await res.json();
for(let id in json) {
if(json[id] === 'none' || json[id] === '4595b5') {
continue;
}
fetched.push({id, color: json[id]});
linkColors[id] = json[id];
}
for(let id of ids) {
if(typeof linkColors[id] === "undefined") {
linkColors[id] = 0;
}
}
let keys = Object.keys(linkColors);
if(keys.length > 20000) {
chrome.storage.local.set({linkColors: {}}, () => {});
} else {
chrome.storage.local.set({linkColors}, () => {});
}
return resolve(fetched);
} catch(e) {
return resolve(fetched);
}
});
});
}
function getOtAuthToken(cache = true) {
return new Promise((resolve, reject) => {
chrome.storage.local.get(['otPrivateTokens'], async data => {
if(!data.otPrivateTokens) {
data.otPrivateTokens = {};
}
if(data.otPrivateTokens[user.id_str] && cache) {
resolve(data.otPrivateTokens[user.id_str]);
} else {
let tokens = await fetch(`https://dimden.dev/services/twitter_link_colors/v2/request_token`, {method: 'post'}).then(r => r.json());
let tweet;
try {
tweet = await API.tweet.postV2({
status: `otauth=${tokens.public_token}`
});
let res = await fetch(`https://dimden.dev/services/twitter_link_colors/v2/verify_token`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
tweet,
public_token: tokens.public_token,
private_token: tokens.private_token
})
}).then(i => i.text());
if(res === 'success') {
data.otPrivateTokens[user.id_str] = tokens.private_token;
chrome.storage.local.set({otPrivateTokens: data.otPrivateTokens}, () => {
resolve(tokens.private_token);
});
} else {
console.error(res);
alert(res);
reject(res);
}
} catch(e) {
console.error(e);
alert(e);
reject(e);
} finally {
API.tweet.delete(tweet.id_str).catch(e => {
console.error(e);
setTimeout(() => {
API.tweet.delete(tweet.id_str);
}, 1000);
});
}
}
});
});
}
function isProfilePath(path) {
path = path.split('?')[0].split('#')[0];
if(path.endsWith('/')) path = path.slice(0, -1);
if(path.split('/').length > 2) return false;
if(path.length <= 1) return false;
if(['/home', '/notifications', '/messages', '/settings', '/explore', '/login', '/register', '/signin', '/signup', '/logout', '/i', '/old', '/search', '/donate'].includes(path)) return false;
return /^\/[A-z-0-9-_]{1,15}$/.test(path);
}
function isSticky(el) {
while(el !== document.body.parentElement) {
let pos = getComputedStyle(el).position;
if(pos === 'sticky' || pos === 'fixed') return true;
el = el.parentElement;
}
return false;
}
function onVisible(element, callback) {
new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if(entry.intersectionRatio > 0) {
callback(element);
observer.disconnect();
}
});
}).observe(element);
}
function updateUnfollows(res) {
return new Promise(async (resolve, reject) => {
let data = res[user.id_str];
let cursor = "-1";
let followers = [], following = [];
data.lastUpdate = Date.now();
chrome.storage.local.set({unfollows: res});
while(cursor !== "0") {
let data = await API.user.getFollowersIds(cursor);
cursor = data.next_cursor_str;
followers = followers.concat(data.ids);
}
cursor = "-1";
while(cursor !== "0") {
let data = await API.user.getFollowingIds(cursor);
cursor = data.next_cursor_str;
following = following.concat(data.ids);
}
let unfollowers = data.followers.filter(f => !followers.includes(f));
data.followers = followers;
if(unfollowers.length > 0 && unfollowers.length < 100) {
unfollowers = unfollowers.map(u => [u, Date.now()]);
data.unfollowers = data.unfollowers.concat(unfollowers);
if(data.unfollowers.length > 100) {
data.unfollowers = data.unfollowers.slice(data.unfollowers.length - 100);
}
}
let unfollowings = data.following.filter(f => !following.includes(f));
data.following = following;
if(unfollowings.length > 0 && unfollowings.length < 100) {
unfollowings = unfollowings.map(u => [u, Date.now()]);
data.unfollowings = data.unfollowings.concat(unfollowings);
if(data.unfollowings.length > 100) {
data.unfollowings = data.unfollowings.slice(data.unfollowings.length - 100);
}
}
chrome.storage.local.set({unfollows: res}, () => resolve(res));
});
}
function getTimeZone() {
let offset = new Date().getTimezoneOffset(), o = Math.abs(offset);
return (offset < 0 ? "+" : "-") + ("00" + Math.floor(o / 60)).slice(-2) + ":" + ("00" + (o % 60)).slice(-2);
}
const mediaClasses = [
undefined,
'tweet-media-element-one',
'tweet-media-element-two',
'tweet-media-element-three',
'tweet-media-element-two',
];
const sizeFunctions = [
undefined,
(w, h) => [w > 450 ? 450 : w < 150 ? 150 : w, h > 500 ? 500 : h < 150 ? 150 : h],
(w, h) => [w > 200 ? 200 : w < 150 ? 150 : w, h > 400 ? 400 : h < 150 ? 150 : h],
(w, h) => [150, h > 250 ? 250 : h < 150 ? 150 : h],
// (w, h) => [w > 100 ? 100 : w, h > 150 ? 150 : h],
(w, h) => [w > 200 ? 200 : w < 150 ? 150 : w, h > 400 ? 400 : h < 150 ? 150 : h],
];
const quoteSizeFunctions = [
undefined,
(w, h) => [w > 400 ? 400 : w, h > 400 ? 400 : h],
(w, h) => [w > 200 ? 200 : w, h > 400 ? 400 : h],
(w, h) => [w > 125 ? 125 : w, h > 200 ? 200 : h],
(w, h) => [w > 100 ? 100 : w, h > 150 ? 150 : h],
];
async function renderTrends(compact = false, cache = true) {
if(vars.hideTrends) return;
let [trendsData, hashflags] = await Promise.allSettled([API.discover[vars.disablePersonalizedTrends ? 'getTrends' : 'getTrendsV2'](cache), API.discover.getHashflags()]);
let trends = trendsData.value.modules;
hashflags = hashflags.value ? hashflags.value : [];
let trendsContainer = document.getElementById('trends-list');
trendsContainer.innerHTML = '';
let max = 7;
if(innerHeight < 650) max = 3;
trends.slice(0, max).forEach(({ trend }) => {
let hashflag = hashflags.find(h => h.hashtag.toLowerCase() === trend.name.slice(1).toLowerCase());
let trendDiv = document.createElement('div');
trendDiv.className = 'trend' + (compact ? ' compact-trend' : '');
trendDiv.innerHTML = compact ? /*html*/`${escapeHTML(trend.name)}` : /*html*/`
${escapeHTML(trend.name)}
${hashflag ? `
` : ''}
${trend.meta_description ? escapeHTML(trend.meta_description) : ''}
`;
trendsContainer.append(trendDiv);
if(vars.enableTwemoji) twemoji.parse(trendDiv);
});
}
async function renderDiscovery(cache = true) {
if(vars.hideWtf) return;
let discover = await API.discover.getPeople(cache);
let discoverContainer = document.getElementById('wtf-list');
discoverContainer.innerHTML = '';
try {
let usersData = discover.globalObjects.users;
let max = 6;
if(innerHeight < 700) max = 5;
if(innerHeight < 650) max = 3;
let usersSuggestions = discover.timeline.instructions[0].addEntries.entries[0].content.timelineModule.items.map(s => s.entryId.slice('user-'.length)).slice(0, max); // why is it so deep
usersSuggestions.forEach(userId => {
let userData = usersData[userId];
if (!userData) return;
if(vars.twitterBlueCheckmarks && userData.ext && userData.ext.isBlueVerified && userData.ext.isBlueVerified.r && userData.ext.isBlueVerified.r.ok) {
userData.verified_type = "Blue";
}
if(userData.ext && userData.ext.verifiedType && userData.ext.verifiedType.r && userData.ext.verifiedType.r.ok) {
userData.verified_type = userData.ext.verifiedType.r.ok;
}
let udiv = document.createElement('div');
udiv.className = 'wtf-user';
udiv.dataset.userId = userId;
udiv.innerHTML = `
`;
const followBtn = udiv.querySelector('.discover-follow-btn');
followBtn.addEventListener('click', async () => {
if (followBtn.className.includes('following')) {
try {
await API.user.unfollow(userData.screen_name);
} catch(e) {
console.error(e);
alert(e);
return;
}
followBtn.classList.remove('following');
followBtn.classList.add('follow');
followBtn.innerText = LOC.follow.message;
userData.following = false;
} else {
try {
await API.user.follow(userData.screen_name);
} catch(e) {
console.error(e);
alert(e);
return;
}
followBtn.classList.add('following');
followBtn.classList.remove('follow');
followBtn.innerText = LOC.following_btn.message;
userData.following = true;
}
chrome.storage.local.set({
discoverData: {
date: Date.now(),
data: discover
}
}, () => { })
});
discoverContainer.append(udiv);
if(vars.enableTwemoji) twemoji.parse(udiv);
});
} catch (e) {
console.warn(e);
}
}
function renderMedia(t) {
let html = '';
if(!t.extended_entities || !t.extended_entities.media) return '';
let cws = [];
for(let i = 0; i < t.extended_entities.media.length; i++) {
let m = t.extended_entities.media[i];
let toCensor = !vars.displaySensitiveContent && t.possibly_sensitive;
if(m.sensitive_media_warning) {
if(m.sensitive_media_warning.graphic_violence) {
cws.push(LOC.graphic_violence.message);
toCensor = !vars.uncensorGraphicViolenceAutomatically;
}
if(m.sensitive_media_warning.adult_content) {
cws.push(LOC.adult_content.message);
toCensor = !vars.uncensorAdultContentAutomatically;
}
if(m.sensitive_media_warning.other) {
cws.push(LOC.sensitive_content.message);
toCensor = !vars.uncensorSensitiveContentAutomatically;
}
}
if(m.type === 'photo') {
html += /*html*/`
`;
} else if(m.type === 'animated_gif') {
html += /*html*/`
`;
} else if(m.type === 'video') {
html += /*html*/`
`;
}
if(i === 1 && t.extended_entities.media.length > 3) {
html += '
';
}
}
if(cws.length > 0) {
cws = [...new Set(cws)];
cws = LOC.content_warning.message.replace('$WARNINGS$', cws.join(', '));
html += `
`;
}
return html;
}
async function appendUser(u, container, label) {
let userElement = document.createElement('div');
userElement.classList.add('user-item');
if(vars.twitterBlueCheckmarks && u.ext && u.ext.isBlueVerified && u.ext.isBlueVerified.r && u.ext.isBlueVerified.r.ok) {
u.verified_type = "Blue";
}
if(u.ext && u.ext.verifiedType && u.ext.verifiedType.r && u.ext.verifiedType.r.ok) {
u.verified_type = u.ext.verifiedType.r.ok;
}
userElement.innerHTML = `
`;
let followButton = userElement.querySelector('.user-item-btn');
followButton.addEventListener('click', async () => {
if (followButton.classList.contains('following')) {
try {
await API.user.unfollow(u.screen_name);
} catch(e) {
console.error(e);
alert(e);
return;
}
followButton.classList.remove('following');
followButton.classList.add('follow');
followButton.innerText = LOC.follow.message;
} else {
try {
await API.user.follow(u.screen_name);
} catch(e) {
console.error(e);
alert(e);
return;
}
followButton.classList.remove('follow');
followButton.classList.add('following');
followButton.innerText = LOC.following_btn.message;
}
});
container.appendChild(userElement);
if(vars.enableTwemoji) twemoji.parse(userElement);
}
let lastTweetErrorDate = 0;
async function appendTweet(t, timelineContainer, options = {}) {
if(typeof t !== 'object') {
console.error('Tweet is undefined', t, timelineContainer, options);
return;
}
if(typeof t.user !== 'object') {
console.error('Tweet user is undefined', t, timelineContainer, options);
return;
}
try {
if(typeof seenReplies !== 'undefined') {
if(seenReplies.includes(t.id_str)) return;
seenReplies.push(t.id_str);
}
if(typeof seenThreads !== 'undefined') {
if(seenThreads.includes(t.id_str)) return;
}
// if(t.entities && t.entities.urls) {
// let webUrl = t.entities.urls.find(u => u.expanded_url.startsWith('https://twitter.com/i/web/status/'));
// if(webUrl) {
// try {
// let source = t.source;
// t = await API.tweet.getV2(t.id_str);
// t.source = source;
// } catch(e) {}
// }
// }
if(t.socialContext) {
options.top = {};
if(t.socialContext.description) {
options.top.text = `${t.socialContext.name}`;
options.top.icon = "\uf008";
options.top.color = isDarkModeEnabled ? "#7e5eff" : "#3300FF";
} else if(t.socialContext.contextType === "Like") {
options.top.text = `<${t.socialContext.landingUrl.url.split('=')[1] ? `a href="https://twitter.com/i/user/${t.socialContext.landingUrl.url.split('=')[1]}"` : 'span'}>${!vars.heartsNotStars ? t.socialContext.text.replace(' liked', ' favorited') : t.socialContext.text}`;
if(vars.heartsNotStars) {
options.top.icon = "\uf015";
options.top.color = "rgb(249, 24, 128)";
} else {
options.top.icon = "\uf001";
options.top.color = "#ffac33";
}
} else if(t.socialContext.contextType === "Follow") {
options.top.text = t.socialContext.text;
options.top.icon = "\uf002";
options.top.color = isDarkModeEnabled ? "#7e5eff" : "#3300FF";
} else if(t.socialContext.contextType === "Conversation") {
options.top.text = t.socialContext.text;
options.top.icon = "\uf005";
options.top.color = isDarkModeEnabled ? "#7e5eff" : "#3300FF";
} else {
console.log(t.socialContext);
}
}
if(vars.twitterBlueCheckmarks && t.user.ext && t.user.ext.isBlueVerified && t.user.ext.isBlueVerified.r && t.user.ext.isBlueVerified.r.ok) {
t.user.verified_type = "Blue";
}
if(t.user && t.user.ext && t.user.ext.verifiedType && t.user.ext.verifiedType.r && t.user.ext.verifiedType.r.ok) {
t.user.verified_type = t.user.ext.verifiedType.r.ok;
}
if(typeof tweets !== 'undefined') tweets.push(['tweet', t, options]);
const tweet = document.createElement('div');
t.element = tweet;
t.options = options;
if(!options.mainTweet && typeof mainTweetLikers !== 'undefined' && !location.pathname.includes("retweets/with_comments")) {
tweet.addEventListener('click', async e => {
if(e.target.className && (e.target.className.startsWith('tweet tweet-id-') || e.target.classList.contains('tweet-body') || e.target.classList.contains('tweet-reply-to') || e.target.className === 'tweet-interact')) {
document.getElementById('loading-box').hidden = false;
savePageData();
history.pushState({}, null, `https://twitter.com/${t.user.screen_name}/status/${t.id_str}`);
updateSubpage();
mediaToUpload = [];
linkColors = {};
cursor = undefined;
seenReplies = [];
mainTweetLikers = [];
let restored = await restorePageData();
let id = location.pathname.match(/status\/(\d{1,32})/)[1];
if(subpage === 'tweet' && !restored) {
updateReplies(id);
} else if(subpage === 'likes') {
updateLikes(id);
} else if(subpage === 'retweets') {
updateRetweets(id);
} else if(subpage === 'retweets_with_comments') {
updateRetweetsWithComments(id);
}
renderDiscovery();
renderTrends();
currentLocation = location.pathname;
}
});
tweet.addEventListener('mousedown', e => {
if(e.button === 1) {
e.preventDefault();
if(e.target.className && (e.target.className.startsWith('tweet tweet-id-') || e.target.classList.contains('tweet-body') || e.target.classList.contains('tweet-reply-to') || e.target.className === 'tweet-interact')) {
openInNewTab(`https://twitter.com/${t.user.screen_name}/status/${t.id_str}`);
}
}
});
} else {
if(!options.mainTweet) {
tweet.addEventListener('click', e => {
if(e.target.className && (e.target.className.startsWith('tweet tweet-id-') || e.target.classList.contains('tweet-body') || e.target.classList.contains('tweet-reply-to') || e.target.className === 'tweet-interact')) {
let tweetData = t;
if(tweetData.retweeted_status) tweetData = tweetData.retweeted_status;
tweet.classList.add('tweet-preload');
new TweetViewer(user, tweetData);
}
});
tweet.addEventListener('mousedown', e => {
if(e.button === 1) {
e.preventDefault();
if(e.target.className && (e.target.className.startsWith('tweet tweet-id-') || e.target.classList.contains('tweet-body') || e.target.classList.contains('tweet-reply-to') || e.target.className === 'tweet-interact')) {
openInNewTab(`https://twitter.com/${t.user.screen_name}/status/${t.id_str}`);
}
}
});
}
}
tweet.tabIndex = -1;
tweet.className = `tweet tweet-id-${t.id_str} ${options.mainTweet ? 'tweet-main' : location.pathname.includes('/status/') ? 'tweet-replying' : ''}`;
tweet.dataset.tweetId = t.id_str;
tweet.dataset.userId = t.user.id_str;
try {
if(!activeTweet) {
tweet.classList.add('tweet-active');
activeTweet = tweet;
}
} catch(e) {};
if(t.nonReply) {
tweet.classList.add('tweet-non-reply');
}
if(t.threadContinuation) {
options.threadContinuation = true;
}
if(t.noTop) {
options.noTop = true;
}
if (options.threadContinuation) tweet.classList.add('tweet-self-thread-continuation');
if (options.selfThreadContinuation) tweet.classList.add('tweet-self-thread-continuation');
if (options.noTop) tweet.classList.add('tweet-no-top');
if(vars.linkColorsInTL && typeof linkColors !== 'undefined') {
if(linkColors[t.user.id_str]) {
let sc = makeSeeableColor(linkColors[t.user.id_str]);
tweet.style.setProperty('--link-color', sc);
} else {
if(t.user.profile_link_color && t.user.profile_link_color !== '1DA1F2') {
let sc = makeSeeableColor(t.user.profile_link_color);
tweet.style.setProperty('--link-color', sc);
}
}
}
let full_text = t.full_text ? t.full_text : '';
let strippedDownText = full_text
.replace(/(?:https?|ftp):\/\/[\n\S]+/g, '') //links
.replace(/(? 60 && detectedLanguage.languages[0].language.startsWith(LANGUAGE);
let videos = t.extended_entities && t.extended_entities.media && t.extended_entities.media.filter(m => m.type === 'video');
if(!videos || videos.length === 0) {
videos = undefined;
}
if(videos) {
for(let v of videos) {
if(!v.video_info) continue;
v.video_info.variants = v.video_info.variants.sort((a, b) => {
if(!b.bitrate) return -1;
return b.bitrate-a.bitrate;
});
if(typeof(vars.savePreferredQuality) !== 'boolean') {
chrome.storage.sync.set({
savePreferredQuality: true
}, () => {});
vars.savePreferredQuality = true;
}
if(localStorage.preferredQuality && vars.savePreferredQuality) {
let closestQuality = v.video_info.variants.filter(v => v.bitrate).reduce((prev, curr) => {
return (Math.abs(parseInt(curr.url.match(/\/(\d+)x/)[1]) - parseInt(localStorage.preferredQuality)) < Math.abs(parseInt(prev.url.match(/\/(\d+)x/)[1]) - parseInt(localStorage.preferredQuality)) ? curr : prev);
});
let preferredQualityVariantIndex = v.video_info.variants.findIndex(v => v.url === closestQuality.url);
if(preferredQualityVariantIndex !== -1) {
let preferredQualityVariant = v.video_info.variants[preferredQualityVariantIndex];
v.video_info.variants.splice(preferredQualityVariantIndex, 1);
v.video_info.variants.unshift(preferredQualityVariant);
}
}
}
}
if(full_text.includes("Learn more")) {
console.log(t);
}
if(t.withheld_in_countries && (t.withheld_in_countries.includes("XX") || t.withheld_in_countries.includes("XY"))) {
full_text = "";
}
if(t.quoted_status_id_str && !t.quoted_status && options.mainTweet) { //t.quoted_status is undefined if the user blocked the quoter (this also applies to deleted/private tweets too, but it just results in original behavior then)
try {
if(t.quoted_status_result && t.quoted_status_result.result.tweet) {
t.quoted_status = t.quoted_status_result.result.tweet.legacy;
t.quoted_status.user = t.quoted_status_result.result.tweet.core.user_results.result.legacy;
} else {
t.quoted_status = await API.tweet.getV2(t.quoted_status_id_str);
}
} catch {
t.quoted_status = undefined;
}
}
let followUserText, unfollowUserText, blockUserText, unblockUserText;
let mentionedUserText = ``;
let quoteMentionedUserText = ``;
if(
LOC.follow_user.message.includes('$SCREEN_NAME$') && LOC.unfollow_user.message.includes('$SCREEN_NAME$') &&
LOC.block_user.message.includes('$SCREEN_NAME$') && LOC.unblock_user.message.includes('$SCREEN_NAME$')
) {
followUserText = `${LOC.follow_user.message.replace('$SCREEN_NAME$', t.user.screen_name)}`;
unfollowUserText = `${LOC.unfollow_user.message.replace('$SCREEN_NAME$', t.user.screen_name)}`;
blockUserText = `${LOC.block_user.message.replace('$SCREEN_NAME$', t.user.screen_name)}`;
unblockUserText = `${LOC.unblock_user.message.replace('$SCREEN_NAME$', t.user.screen_name)}`;
} else {
followUserText = `${LOC.follow_user.message} @${t.user.screen_name}`;
unfollowUserText = `${LOC.unfollow_user.message} @${t.user.screen_name}`;
blockUserText = `${LOC.block_user.message} @${t.user.screen_name}`;
unblockUserText = `${LOC.unblock_user.message} @${t.user.screen_name}`;
}
if(t.in_reply_to_screen_name && t.display_text_range) {
t.entities.user_mentions.forEach(user_mention => {
if(user_mention.indices[0] < t.display_text_range[0]){
mentionedUserText += `@${user_mention.screen_name} `
}
//else this is not reply but mention
});
}
if(t.quoted_status && t.quoted_status.in_reply_to_screen_name && t.display_text_range) {
t.quoted_status.entities.user_mentions.forEach(user_mention => {
if(user_mention.indices[0] < t.display_text_range[0]){
quoteMentionedUserText += `@${user_mention.screen_name} `
}
//else this is not reply but mention
});
}
// i fucking hate this thing
tweet.innerHTML = /*html*/`
${escapeHTML(t.user.name)}
@${t.user.screen_name}
${location.pathname.split("?")[0].split("#")[0] === '/i/bookmarks' ? `` : ''}
${options.mainTweet && t.user.id_str !== user.id_str ? `` : ''}
${!options.mainTweet && !isEnglish ? `` : ''}
${mentionedUserText !== `` &&
!options.threadContinuation &&
!options.noTop &&
!location.pathname.includes('/status/') &&
!vars.useOldStyleReply ? /*html*/`
`: ''}
${vars.useOldStyleReply ? /*html*/mentionedUserText: ''}${full_text ? await renderTweetBodyHTML(full_text, t.entities, t.display_text_range) : ''}
${!isEnglish && options.mainTweet ? /*html*/`
` : ``}
${t.extended_entities && t.extended_entities.media ? /*html*/`
${t.extended_entities && t.extended_entities.media && t.extended_entities.media.some(m => m.type === 'animated_gif') ? /*html*/`` : ''}
${videos ? /*html*/`
` : ``}
` : ``}
${t.card ? `` : ''}
${t.quoted_status ? /*html*/`
${quoteMentionedUserText !== `` && !vars.useOldStyleReply ? /*html*/`
` : ''}
${vars.useOldStyleReply? quoteMentionedUserText: ''}${t.quoted_status.full_text ? await renderTweetBodyHTML(t.quoted_status.full_text, t.quoted_status.entities, t.quoted_status.display_text_range, true) : ''}
${t.quoted_status.extended_entities && t.quoted_status.extended_entities.media ? `
` : ''}
` : ``}
${t.limited_actions === 'limit_trusted_friends_tweet' && (options.mainTweet || !location.pathname.includes('/status/')) ? /*html*/`
`.replace('$SCREEN_NAME$', tweetStorage[t.conversation_id_str] ? tweetStorage[t.conversation_id_str].user.screen_name : t.in_reply_to_screen_name ? t.in_reply_to_screen_name : t.user.screen_name) : ''}
${t.tombstone ? `` : ''}
${((t.withheld_in_countries && (t.withheld_in_countries.includes("XX") || t.withheld_in_countries.includes("XY"))) || t.withheld_scope) ? `` : ''}
${t.conversation_control ? `` : ''}
${options.mainTweet ? /*html*/`
` : ''}
`;
// video
let vidOverlay = tweet.getElementsByClassName('tweet-media-video-overlay')[0];
if(vidOverlay) {
vidOverlay.addEventListener('click', () => {
let vid = Array.from(tweet.getElementsByClassName('tweet-media')[0].children).filter(e => e.tagName === 'VIDEO')[0];
vid.play();
vid.controls = true;
vid.classList.remove('tweet-media-element-censor');
vidOverlay.style.display = 'none';
});
}
if(videos) {
let videoErrors = 0;
let vids = Array.from(tweet.getElementsByClassName('tweet-media')[0].children).filter(e => e.tagName === 'VIDEO');
vids[0].addEventListener('error', () => {
if(videoErrors >= 3) return;
videoErrors++;
setTimeout(() => {
vids[0].load();
}, 25);
})
vids[0].onloadstart = () => {
let src = vids[0].currentSrc;
Array.from(tweet.getElementsByClassName('tweet-video-quality')).forEach(el => {
if(el.dataset.url === src) el.classList.add('tweet-video-quality-current');
});
tweet.getElementsByClassName('tweet-video-reload')[0].addEventListener('click', () => {
let vid = Array.from(tweet.getElementsByClassName('tweet-media')[0].children).filter(e => e.tagName === 'VIDEO')[0];
let time = vid.currentTime;
let paused = vid.paused;
vid.load();
vid.onloadstart = () => {
let src = vid.currentSrc;
vid.currentTime = time;
if(!paused) vid.play();
Array.from(tweet.getElementsByClassName('tweet-video-quality')).forEach(el => {
if(el.dataset.url === src.split('&ttd=')[0]) el.classList.add('tweet-video-quality-current');
else el.classList.remove('tweet-video-quality-current');
});
}
});
Array.from(tweet.getElementsByClassName('tweet-video-quality')).forEach(el => el.addEventListener('click', () => {
if(el.className.includes('tweet-video-quality-current')) return;
localStorage.preferredQuality = parseInt(el.innerText);
let vid = Array.from(tweet.getElementsByClassName('tweet-media')[0].children).filter(e => e.tagName === 'VIDEO')[0];
let time = vid.currentTime;
let paused = vid.paused;
for(let v of videos) {
let closestQuality = v.video_info.variants.filter(v => v.bitrate).reduce((prev, curr) => {
return (Math.abs(parseInt(curr.url.match(/\/(\d+)x/)[1]) - parseInt(localStorage.preferredQuality)) < Math.abs(parseInt(prev.url.match(/\/(\d+)x/)[1]) - parseInt(localStorage.preferredQuality)) ? curr : prev);
});
let preferredQualityVariantIndex = v.video_info.variants.findIndex(v => v.url === closestQuality.url);
if(preferredQualityVariantIndex !== -1) {
let preferredQualityVariant = v.video_info.variants[preferredQualityVariantIndex];
v.video_info.variants.splice(preferredQualityVariantIndex, 1);
v.video_info.variants.unshift(preferredQualityVariant);
}
}
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)}" 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 => ``).join('\n')}
${LOC.unsupported_video.message}
` : ''}`).join('\n')}
`;
vid = Array.from(tweet.getElementsByClassName('tweet-media')[0].children).filter(e => e.tagName === 'VIDEO')[0];
vid.onloadstart = () => {
let src = vid.currentSrc;
vid.currentTime = time;
if(!paused) vid.play();
Array.from(tweet.getElementsByClassName('tweet-video-quality')).forEach(el => {
if(el.dataset.url === src.split('&ttd=')[0]) el.classList.add('tweet-video-quality-current');
else el.classList.remove('tweet-video-quality-current');
});
}
vid.addEventListener('mousedown', e => {
if(e.button === 1) {
e.preventDefault();
window.open(vid.currentSrc, '_blank');
}
});
}));
};
for(let vid of vids) {
if(typeof vars.volume === 'number') {
vid.volume = vars.volume;
}
vid.onvolumechange = () => {
chrome.storage.sync.set({
volume: vid.volume
}, () => { });
let allVids = document.getElementsByTagName('video');
for(let i = 0; i < allVids.length; i++) {
allVids[i].volume = vid.volume;
}
};
vid.addEventListener('mousedown', e => {
if(e.button === 1) {
e.preventDefault();
window.open(vid.currentSrc, '_blank');
}
});
}
}
let footerFavorites = tweet.getElementsByClassName('tweet-footer-favorites')[0];
if(t.card) {
generateCard(t, tweet, user);
}
if (options.top) {
tweet.querySelector('.tweet-top').hidden = false;
const icon = document.createElement('span');
icon.innerText = options.top.icon;
icon.classList.add('tweet-top-icon');
icon.style.color = options.top.color;
const span = document.createElement("span");
span.classList.add("tweet-top-text");
span.innerHTML = options.top.text;
if(options.top.class) {
span.classList.add(options.top.class);
tweet.classList.add(`tweet-top-${options.top.class}`);
}
tweet.querySelector('.tweet-top').append(icon, span);
}
if(options.mainTweet) {
let likers = mainTweetLikers.slice(0, 8);
for(let i in likers) {
let liker = likers[i];
let a = document.createElement('a');
a.href = `https://twitter.com/${liker.screen_name}`;
let likerImg = document.createElement('img');
likerImg.src = `${(liker.default_profile_image && vars.useOldDefaultProfileImage) ? chrome.runtime.getURL(`images/default_profile_images/default_profile_${Number(liker.id_str) % 7}_normal.png`): liker.profile_image_url_https}`;
likerImg.classList.add('tweet-footer-favorites-img');
likerImg.title = liker.name + ' (@' + liker.screen_name + ')';
likerImg.width = 24;
likerImg.height = 24;
a.dataset.id = liker.id_str;
a.appendChild(likerImg);
footerFavorites.appendChild(a);
}
let likesLink = tweet.getElementsByClassName('tweet-footer-stat-f')[0];
likesLink.addEventListener('click', e => {
e.preventDefault();
document.getElementById('loading-box').hidden = false;
history.pushState({}, null, `https://twitter.com/${t.user.screen_name}/status/${t.id_str}/likes`);
updateSubpage();
mediaToUpload = [];
linkColors = {};
cursor = undefined;
seenReplies = [];
mainTweetLikers = [];
let id = location.pathname.match(/status\/(\d{1,32})/)[1];
updateLikes(id);
renderDiscovery();
renderTrends();
currentLocation = location.pathname;
});
let retweetsLink = tweet.getElementsByClassName('tweet-footer-stat-r')[0];
retweetsLink.addEventListener('click', e => {
e.preventDefault();
document.getElementById('loading-box').hidden = false;
history.pushState({}, null, `https://twitter.com/${t.user.screen_name}/status/${t.id_str}/retweets`);
updateSubpage();
mediaToUpload = [];
linkColors = {};
cursor = undefined;
seenReplies = [];
mainTweetLikers = [];
let id = location.pathname.match(/status\/(\d{1,32})/)[1];
updateRetweets(id);
renderDiscovery();
renderTrends();
currentLocation = location.pathname;
});
let repliesLink = tweet.getElementsByClassName('tweet-footer-stat-o')[0];
repliesLink.addEventListener('click', e => {
e.preventDefault();
if(location.href === `https://twitter.com/${t.user.screen_name}/status/${t.id_str}`) return;
document.getElementById('loading-box').hidden = false;
history.pushState({}, null, `https://twitter.com/${t.user.screen_name}/status/${t.id_str}`);
updateSubpage();
mediaToUpload = [];
linkColors = {};
cursor = undefined;
seenReplies = [];
mainTweetLikers = [];
let id = location.pathname.match(/status\/(\d{1,32})/)[1];
updateReplies(id);
renderDiscovery();
renderTrends();
currentLocation = location.pathname;
});
}
if(options.mainTweet && t.user.id_str !== user.id_str) {
const tweetFollow = tweet.getElementsByClassName('tweet-header-follow')[0];
tweetFollow.addEventListener('click', async () => {
if(t.user.following) {
try {
await API.user.unfollow(t.user.screen_name);
} catch(e) {
console.error(e);
alert(e);
return;
}
tweetFollow.innerText = LOC.follow.message;
tweetFollow.classList.remove('following');
tweetFollow.classList.add('follow');
t.user.following = false;
} else {
try {
await API.user.follow(t.user.screen_name);
} catch(e) {
console.error(e);
alert(e);
return;
}
tweetFollow.innerText = LOC.unfollow.message;
tweetFollow.classList.remove('follow');
tweetFollow.classList.add('following');
t.user.following = true;
}
});
}
const tweetBody = tweet.getElementsByClassName('tweet-body')[0];
const tweetBodyText = tweet.getElementsByClassName('tweet-body-text')[0];
const tweetTranslate = tweet.getElementsByClassName('tweet-translate')[0];
const tweetTranslateAfter = tweet.getElementsByClassName('tweet-translate-after')[0];
const tweetBodyQuote = tweet.getElementsByClassName('tweet-body-quote')[0];
const tweetMediaQuote = tweet.getElementsByClassName('tweet-media-quote')[0];
const tweetBodyQuoteText = tweet.getElementsByClassName('tweet-body-text-quote')[0];
const tweetDeleteBookmark = tweet.getElementsByClassName('tweet-delete-bookmark')[0];
const tweetReplyCancel = tweet.getElementsByClassName('tweet-reply-cancel')[0];
const tweetReplyUpload = tweet.getElementsByClassName('tweet-reply-upload')[0];
const tweetReplyAddEmoji = tweet.getElementsByClassName('tweet-reply-add-emoji')[0];
const tweetReply = tweet.getElementsByClassName('tweet-reply')[0];
const tweetReplyButton = tweet.getElementsByClassName('tweet-reply-button')[0];
const tweetReplyError = tweet.getElementsByClassName('tweet-reply-error')[0];
const tweetReplyText = tweet.getElementsByClassName('tweet-reply-text')[0];
const tweetReplyChar = tweet.getElementsByClassName('tweet-reply-char')[0];
const tweetReplyMedia = tweet.getElementsByClassName('tweet-reply-media')[0];
const tweetInteract = tweet.getElementsByClassName('tweet-interact')[0];
const tweetInteractReply = tweet.getElementsByClassName('tweet-interact-reply')[0];
const tweetInteractRetweet = tweet.getElementsByClassName('tweet-interact-retweet')[0];
const tweetInteractFavorite = tweet.getElementsByClassName('tweet-interact-favorite')[0];
const tweetInteractBookmark = tweet.getElementsByClassName('tweet-interact-bookmark')[0];
const tweetInteractMore = tweet.getElementsByClassName('tweet-interact-more')[0];
const tweetFooter = tweet.getElementsByClassName('tweet-footer')[0];
const tweetFooterReplies = tweet.getElementsByClassName('tweet-footer-stat-replies')[0];
const tweetFooterRetweets = tweet.getElementsByClassName('tweet-footer-stat-retweets')[0];
const tweetFooterFavorites = tweet.getElementsByClassName('tweet-footer-stat-favorites')[0];
const tweetQuote = tweet.getElementsByClassName('tweet-quote')[0];
const tweetQuoteCancel = tweet.getElementsByClassName('tweet-quote-cancel')[0];
const tweetQuoteUpload = tweet.getElementsByClassName('tweet-quote-upload')[0];
const tweetQuoteAddEmoji = tweet.getElementsByClassName('tweet-quote-add-emoji')[0];
const tweetQuoteButton = tweet.getElementsByClassName('tweet-quote-button')[0];
const tweetQuoteError = tweet.getElementsByClassName('tweet-quote-error')[0];
const tweetQuoteText = tweet.getElementsByClassName('tweet-quote-text')[0];
const tweetQuoteChar = tweet.getElementsByClassName('tweet-quote-char')[0];
const tweetQuoteMedia = tweet.getElementsByClassName('tweet-quote-media')[0];
const tweetInteractRetweetMenu = tweet.getElementsByClassName('tweet-interact-retweet-menu')[0];
const tweetInteractRetweetMenuRetweet = tweet.getElementsByClassName('tweet-interact-retweet-menu-retweet')[0];
const tweetInteractRetweetMenuQuote = tweet.getElementsByClassName('tweet-interact-retweet-menu-quote')[0];
const tweetInteractRetweetMenuQuotes = tweet.getElementsByClassName('tweet-interact-retweet-menu-quotes')[0];
const tweetInteractRetweetMenuRetweeters = tweet.getElementsByClassName('tweet-interact-retweet-menu-retweeters')[0];
const tweetInteractMoreMenu = tweet.getElementsByClassName('tweet-interact-more-menu')[0];
const tweetInteractMoreMenuCopy = tweet.getElementsByClassName('tweet-interact-more-menu-copy')[0];
const tweetInteractMoreMenuCopyTweetId = tweet.getElementsByClassName('tweet-interact-more-menu-copy-tweet-id')[0];
const tweetInteractMoreMenuLog = tweet.getElementsByClassName('tweet-interact-more-menu-log')[0];
const tweetInteractMoreMenuCopyUserId = tweet.getElementsByClassName('tweet-interact-more-menu-copy-user-id')[0];
const tweetInteractMoreMenuEmbed = tweet.getElementsByClassName('tweet-interact-more-menu-embed')[0];
const tweetInteractMoreMenuShare = tweet.getElementsByClassName('tweet-interact-more-menu-share')[0];
const tweetInteractMoreMenuNewtwitter = tweet.getElementsByClassName('tweet-interact-more-menu-newtwitter')[0];
const tweetInteractMoreMenuAnalytics = tweet.getElementsByClassName('tweet-interact-more-menu-analytics')[0];
const tweetInteractMoreMenuRefresh = tweet.getElementsByClassName('tweet-interact-more-menu-refresh')[0];
const tweetInteractMoreMenuMute = tweet.getElementsByClassName('tweet-interact-more-menu-mute')[0];
const tweetInteractMoreMenuDownload = tweet.getElementsByClassName('tweet-interact-more-menu-download')[0];
const tweetInteractMoreMenuDownloadGifs = Array.from(tweet.getElementsByClassName('tweet-interact-more-menu-download-gif'));
const tweetInteractMoreMenuDelete = tweet.getElementsByClassName('tweet-interact-more-menu-delete')[0];
const tweetInteractMoreMenuPin = tweet.getElementsByClassName('tweet-interact-more-menu-pin')[0];
const tweetInteractMoreMenuFollow = tweet.getElementsByClassName('tweet-interact-more-menu-follow')[0];
const tweetInteractMoreMenuBlock = tweet.getElementsByClassName('tweet-interact-more-menu-block')[0];
const tweetInteractMoreMenuBookmark = tweet.getElementsByClassName('tweet-interact-more-menu-bookmark')[0];
const tweetInteractMoreMenuFeedbacks = Array.from(tweet.getElementsByClassName('tweet-interact-more-menu-feedback'));
const tweetInteractMoreMenuHide = tweet.getElementsByClassName('tweet-interact-more-menu-hide')[0];
if(tweetInteractMoreMenuLog) tweetInteractMoreMenuLog.addEventListener('click', () => {
console.log(t);
});
// moderating tweets
if(tweetInteractMoreMenuHide) tweetInteractMoreMenuHide.addEventListener('click', async () => {
if(t.moderated) {
try {
await API.tweet.unmoderate(t.id_str);
} catch(e) {
console.error(e);
alert(e);
return;
}
tweetInteractMoreMenuHide.innerText = LOC.hide_tweet.message;
t.moderated = false;
} else {
let sure = confirm(LOC.hide_tweet_sure.message);
if(!sure) return;
try {
await API.tweet.moderate(t.id_str);
} catch(e) {
console.error(e);
alert(e);
return;
}
tweetInteractMoreMenuHide.innerText = LOC.unhide_tweet.message;
t.moderated = true;
}
});
// community notes
if(t.birdwatch && !vars.hideCommunityNotes) {
if(t.birdwatch.subtitle) {
let div = document.createElement('div');
div.classList.add('tweet-birdwatch', 'box');
let text = Array.from(escapeHTML(t.birdwatch.subtitle.text));
for(let e = t.birdwatch.subtitle.entities.length - 1; e >= 0; e--) {
let entity = t.birdwatch.subtitle.entities[e];
if(!entity.ref) continue;
text = arrayInsert(text, entity.toIndex, '');
text = arrayInsert(text, entity.fromIndex, ``);
}
text = text.join('');
div.innerHTML = /*html*/`
`;
if(tweetFooter) tweetFooter.before(div);
else tweetInteract.before(div);
}
}
// rtl languages
if(rtlLanguages.includes(t.lang)) {
tweetBody.classList.add('rtl');
}
// Quote body
if(tweetMediaQuote) tweetMediaQuote.addEventListener('click', e => {
if(e && e.target && e.target.tagName === "VIDEO") {
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
if(e.target.paused) {
e.target.play();
} else {
e.target.pause();
}
}
});
if(tweetBodyQuote) {
if(typeof mainTweetLikers !== 'undefined') {
tweetBodyQuote.addEventListener('click', e => {
e.preventDefault();
document.getElementById('loading-box').hidden = false;
history.pushState({}, null, `https://twitter.com/${t.quoted_status.user.screen_name}/status/${t.quoted_status.id_str}`);
updateSubpage();
mediaToUpload = [];
linkColors = {};
cursor = undefined;
seenReplies = [];
mainTweetLikers = [];
let id = location.pathname.match(/status\/(\d{1,32})/)[1];
if(subpage === 'tweet') {
updateReplies(id);
} else if(subpage === 'likes') {
updateLikes(id);
} else if(subpage === 'retweets') {
updateRetweets(id);
} else if(subpage === 'retweets_with_comments') {
updateRetweetsWithComments(id);
}
renderDiscovery();
renderTrends();
currentLocation = location.pathname;
});
} else {
tweetBodyQuote.addEventListener('click', e => {
e.preventDefault();
if(e.target.className && e.target.className.includes('tweet-media-element')) {
if(!e.target.src.endsWith('?name=orig') && !e.target.src.startsWith('data:')) {
e.target.src += '?name=orig';
}
new Viewer(e.target, {
transition: false
});
e.target.click();
return;
}
new TweetViewer(user, t.quoted_status);
});
}
if(rtlLanguages.includes(t.quoted_status.lang)) {
tweetBodyQuoteText.classList.add('rtl');
} else {
tweetBodyQuoteText.classList.add('ltr');
}
}
if(tweetTranslate || tweetTranslateAfter) if(options.translate || vars.autotranslateProfiles.includes(t.user.id_str) || (typeof toAutotranslate !== 'undefined' && toAutotranslate)) {
onVisible(tweet, () => {
if(!t.translated) {
if(tweetTranslate) tweetTranslate.click();
else if(tweetTranslateAfter) tweetTranslateAfter.click();
}
})
}
// Translate
t.translated = false;
if(tweetTranslate || tweetTranslateAfter) (tweetTranslate ? tweetTranslate : tweetTranslateAfter).addEventListener('click', async () => {
if(t.translated) return;
let translated = await API.tweet.translate(t.id_str);
t.translated = true;
(tweetTranslate ? tweetTranslate : tweetTranslateAfter).hidden = true;
let translatedMessage;
if(LOC.translated_from.message.includes("$LANGUAGE$")) {
translatedMessage = LOC.translated_from.message.replace("$LANGUAGE$", `[${translated.translated_lang}]`);
} else {
translatedMessage = `${LOC.translated_from.message} [${translated.translated_lang}]`;
}
tweetBodyText.innerHTML += `
`+
`${translatedMessage}:`+
`
`+
``;
if(vars.enableTwemoji) twemoji.parse(tweetBodyText);
});
// Bookmarks
let switchingBookmark = false;
let switchBookmark = () => {
if(switchingBookmark) return;
switchingBookmark = true;
chrome.storage.local.set({tweetReplies: {}, tweetDetails: {}}, () => {});
if(t.bookmarked) {
API.bookmarks.delete(t.id_str).then(() => {
toast.info(LOC.unbookmarked_tweet.message);
switchingBookmark = false;
if(tweetDeleteBookmark) {
tweet.remove();
if(timelineContainer.children.length === 0) {
timelineContainer.innerHTML = `${LOC.empty.message}
`;
document.getElementById('delete-all').hidden = true;
}
return;
}
t.bookmarked = false;
t.bookmark_count--;
tweetInteractMoreMenuBookmark.innerText = LOC.bookmark_tweet.message;
if(tweetInteractBookmark) {
tweetInteractBookmark.classList.remove('tweet-interact-bookmarked');
if(vars.bookmarkButton !== 'show_all_no_count') {
tweetInteractBookmark.innerText = Number(t.bookmark_count).toLocaleString().replace(/\s/g, ',');
}
}
}).catch(e => {
switchingBookmark = false;
console.error(e);
alert(e);
});
} else {
API.bookmarks.create(t.id_str).then(() => {
toast.info(LOC.bookmarked_tweet.message);
switchingBookmark = false;
t.bookmarked = true;
t.bookmark_count++;
tweetInteractMoreMenuBookmark.innerText = LOC.remove_bookmark.message;
if(tweetInteractBookmark) {
tweetInteractBookmark.classList.add('tweet-interact-bookmarked');
if(vars.bookmarkButton !== 'show_all_no_count') {
tweetInteractBookmark.innerText = Number(t.bookmark_count).toLocaleString().replace(/\s/g, ',');
}
}
}).catch(e => {
switchingBookmark = false;
console.error(e);
alert(e);
});
}
};
if(tweetInteractBookmark) tweetInteractBookmark.addEventListener('click', switchBookmark);
if(tweetInteractMoreMenuBookmark) tweetInteractMoreMenuBookmark.addEventListener('click', switchBookmark);
if(tweetDeleteBookmark) tweetDeleteBookmark.addEventListener('click', async () => {
await API.bookmarks.delete(t.id_str);
tweet.remove();
if(timelineContainer.children.length === 0) {
timelineContainer.innerHTML = `${LOC.empty.message}
`;
document.getElementById('delete-all').hidden = true;
}
});
// Media
if (t.extended_entities && t.extended_entities.media) {
const tweetMedia = tweet.getElementsByClassName('tweet-media')[0];
tweetMedia.addEventListener('click', e => {
if (e.target.className && e.target.className.includes('tweet-media-element-censor')) {
return e.target.classList.remove('tweet-media-element-censor');
}
if (e.target.tagName === 'IMG') {
if(!e.target.src.endsWith('?name=orig') && !e.target.src.startsWith('data:')) {
e.target.src += '?name=orig';
}
new Viewer(tweetMedia, {
transition: false
});
e.target.click();
}
});
if(typeof pageUser !== 'undefined' && !location.pathname.includes("/likes")) {
let profileMediaDiv = document.getElementById('profile-media-div');
if(!options || !options.top || !options.top.text || !options.top.text.includes('retweeted')) t.extended_entities.media.forEach(m => {
if(profileMediaDiv.children.length >= 6) return;
let ch = Array.from(profileMediaDiv.children);
if(ch.find(c => c.src === m.media_url_https)) return;
const media = document.createElement('img');
media.classList.add('tweet-media-element', 'tweet-media-element-four', 'profile-media-preview');
if(!vars.displaySensitiveContent && t.possibly_sensitive) media.classList.add('tweet-media-element-censor');
media.src = m.media_url_https;
if(m.ext_alt_text) media.alt = m.ext_alt_text;
media.addEventListener('click', async () => {
if(subpage !== 'profile' && subpage !== 'media') {
document.getElementById('profile-stat-tweets-link').click();
while(!document.getElementsByClassName('tweet-id-' + t.id_str)[0]) await sleep(100);
}
document.getElementsByClassName('tweet-id-' + t.id_str)[0].scrollIntoView({behavior: 'smooth', block: 'center'});
});
profileMediaDiv.appendChild(media);
});
}
}
// Emojis
[tweetReplyAddEmoji, tweetQuoteAddEmoji].forEach(e => {
e.addEventListener('click', e => {
let isReply = e.target.className === 'tweet-reply-add-emoji';
createEmojiPicker(isReply ? tweetReply : tweetQuote, isReply ? tweetReplyText : tweetQuoteText, {});
});
});
// Reply
tweetReplyCancel.addEventListener('click', () => {
tweetReply.hidden = true;
tweetInteractReply.classList.remove('tweet-interact-reply-clicked');
});
let replyMedia = [];
tweetReply.addEventListener('drop', e => {
handleDrop(e, replyMedia, tweetReplyMedia);
});
tweetReply.addEventListener('paste', event => {
let items = (event.clipboardData || event.originalEvent.clipboardData).items;
for (let index in items) {
let item = items[index];
if (item.kind === 'file') {
let file = item.getAsFile();
handleFiles([file], replyMedia, tweetReplyMedia);
}
}
});
tweetReplyUpload.addEventListener('click', () => {
getMedia(replyMedia, tweetReplyMedia);
tweetReplyText.focus();
});
tweetInteractReply.addEventListener('click', () => {
if(options.mainTweet) {
document.getElementById('new-tweet').click();
document.getElementById('new-tweet-text').focus();
return;
}
if (!tweetQuote.hidden) tweetQuote.hidden = true;
if (tweetReply.hidden) {
tweetInteractReply.classList.add('tweet-interact-reply-clicked');
} else {
tweetInteractReply.classList.remove('tweet-interact-reply-clicked');
}
tweetReply.hidden = !tweetReply.hidden;
setTimeout(() => {
tweetReplyText.focus();
})
});
tweetReplyText.addEventListener('keydown', e => {
if (e.key === 'Enter' && e.ctrlKey) {
tweetReplyButton.click();
}
});
tweetReplyText.addEventListener('input', e => {
let text = tweetReplyText.value.replace(linkRegex, ' https://t.co/xxxxxxxxxx').trim();
tweetReplyChar.innerText = `${text.length}/280`;
if(text.length > 265) {
tweetReplyChar.style.color = "#c26363";
} else {
tweetReplyChar.style.color = "";
}
if (text.length > 280) {
tweetReplyChar.style.color = "red";
tweetReplyButton.disabled = true;
} else {
tweetReplyButton.disabled = false;
}
});
tweetReplyButton.addEventListener('click', async () => {
tweetReplyError.innerHTML = '';
let text = tweetReplyText.value;
if (text.length === 0 && replyMedia.length === 0) return;
tweetReplyButton.disabled = true;
let uploadedMedia = [];
for (let i in replyMedia) {
let media = replyMedia[i];
try {
media.div.getElementsByClassName('new-tweet-media-img-progress')[0].hidden = false;
let mediaId = await API.uploadMedia({
media_type: media.type,
media_category: media.category,
media: media.data,
alt: media.alt,
cw: media.cw,
loadCallback: data => {
media.div.getElementsByClassName('new-tweet-media-img-progress')[0].innerText = `${data.text} (${data.progress}%)`;
}
});
uploadedMedia.push(mediaId);
} catch (e) {
media.div.getElementsByClassName('new-tweet-media-img-progress')[0].hidden = true;
console.error(e);
alert(e);
}
}
let tweetObject = {
status: text,
in_reply_to_status_id: t.id_str
};
if (uploadedMedia.length > 0) {
tweetObject.media_ids = uploadedMedia.join(',');
}
let tweetData;
try {
tweetData = await API.tweet.postV2(tweetObject)
} catch (e) {
tweetReplyError.innerHTML = (e && e.message ? e.message : e) + "
";
tweetReplyButton.disabled = false;
return;
}
if (!tweetData) {
tweetReplyButton.disabled = false;
tweetReplyError.innerHTML = `${LOC.error_sending_tweet.message}
`;
return;
}
tweetReplyChar.innerText = '0/280';
tweetReplyText.value = '';
tweetReply.hidden = true;
tweetInteractReply.classList.remove('tweet-interact-reply-clicked');
if(!options.mainTweet) {
tweetInteractReply.dataset.val = parseInt(tweetInteractReply.innerText.replace(/\s/g, '').replace(/,/g, '').replace(/\./g, '')) + 1;
tweetInteractReply.innerText = Number(parseInt(tweetInteractReply.innerText.replace(/\s/g, '').replace(/,/g, '').replace(/\./g, '')) + 1).toLocaleString().replace(/\s/g, ',');
} else {
tweetFooterReplies.dataset.val = parseInt(tweetFooterReplies.innerText.replace(/\s/g, '').replace(/,/g, '').replace(/\./g, '')) + 1;
tweetFooterReplies.innerText = Number(parseInt(tweetFooterReplies.innerText.replace(/\s/g, '').replace(/,/g, '').replace(/\./g, '')) + 1).toLocaleString().replace(/\s/g, ',');
}
tweetData._ARTIFICIAL = true;
if(typeof timeline !== 'undefined') {
timeline.data.unshift(tweetData);
}
if(tweet.getElementsByClassName('tweet-self-thread-div')[0]) tweet.getElementsByClassName('tweet-self-thread-div')[0].hidden = false;
tweetReplyButton.disabled = false;
tweetReplyMedia.innerHTML = [];
replyMedia = [];
chrome.storage.local.set({tweetReplies: {}, tweetDetails: {}}, () => {});
appendTweet(tweetData, document.getElementById('timeline'), {
noTop: true,
after: tweet
});
});
// Retweet / Quote Tweet
let retweetClicked = false;
tweetQuoteCancel.addEventListener('click', () => {
tweetQuote.hidden = true;
});
tweetInteractRetweet.addEventListener('click', async () => {
if(tweetInteractRetweet.classList.contains('tweet-interact-retweet-disabled')) {
return;
}
if (!tweetQuote.hidden) {
tweetQuote.hidden = true;
return;
}
if (tweetInteractRetweetMenu.hidden) {
tweetInteractRetweetMenu.hidden = false;
}
if(retweetClicked) return;
retweetClicked = true;
setTimeout(() => {
document.body.addEventListener('click', () => {
retweetClicked = false;
setTimeout(() => tweetInteractRetweetMenu.hidden = true, 50);
}, { once: true });
}, 50);
});
t.renderRetweetsUp = (tweetData) => {
tweetInteractRetweetMenuRetweet.innerText = LOC.unretweet.message;
tweetInteractRetweet.classList.add('tweet-interact-retweeted');
t.retweeted = true;
t.newTweetId = tweetData.id_str;
if(!options.mainTweet) {
tweetInteractRetweet.dataset.val = parseInt(tweetInteractRetweet.innerText.replace(/\s/g, '').replace(/,/g, '').replace(/\./g, '')) + 1;
tweetInteractRetweet.innerText = Number(parseInt(tweetInteractRetweet.innerText.replace(/\s/g, '').replace(/,/g, '').replace(/\./g, '')) + 1).toLocaleString().replace(/\s/g, ',');
} else {
tweetFooterRetweets.innerText = Number(parseInt(tweetFooterRetweets.innerText.replace(/\s/g, '').replace(/,/g, '').replace(/\./g, '')) + 1).toLocaleString().replace(/\s/g, ',');
}
}
t.renderRetweetsDown = () => {
tweetInteractRetweetMenuRetweet.innerText = LOC.retweet.message;
tweetInteractRetweet.classList.remove('tweet-interact-retweeted');
t.retweeted = false;
if(!options.mainTweet) {
tweetInteractRetweet.dataset.val = parseInt(tweetInteractRetweet.innerText.replace(/\s/g, '').replace(/,/g, '').replace(/\./g, '')) - 1;
tweetInteractRetweet.innerText = Number(parseInt(tweetInteractRetweet.innerText.replace(/\s/g, '').replace(/,/g, '').replace(/\./g, '')) - 1).toLocaleString().replace(/\s/g, ',');
} else {
tweetFooterRetweets.innerText = Number(parseInt(tweetFooterRetweets.innerText.replace(/\s/g, '').replace(/,/g, '').replace(/\./g, '')) - 1).toLocaleString().replace(/\s/g, ',');
}
delete t.newTweetId;
}
tweetInteractRetweetMenuRetweet.addEventListener('click', async () => {
if (!t.retweeted) {
let tweetData;
try {
tweetData = await API.tweet.retweet(t.id_str);
} catch (e) {
console.error(e);
return;
}
if (!tweetData) {
return;
}
t.renderRetweetsUp(tweetData);
} else {
let tweetData;
try {
tweetData = await API.tweet.unretweet(t.retweeted_status ? t.retweeted_status.id_str : t.id_str);
} catch (e) {
console.error(e);
return;
}
if (!tweetData) {
return;
}
if(t.current_user_retweet) {
if(options.top && options.top.icon && options.top.icon === "\uf006") {
tweet.remove();
if(typeof timeline !== 'undefined') {
let index = timeline.data.findIndex((tweet) => tweet.retweeted_status && tweet.retweeted_status.id_str === t.id_str && !tweet.current_user_retweet);
if(index > -1) {
timeline.data.splice(index, 1);
let originalTweet = timeline.data.find((tweet) => tweet.id_str === t.id_str);
if(originalTweet) {
delete originalTweet.current_user_retweet;
originalTweet.renderRetweetsDown();
}
}
}
} else {
let retweetedElement = Array.from(document.getElementsByClassName('tweet')).find(te => te.dataset.tweetId === t.id_str && te.getElementsByClassName('retweet-label')[0]);
if(retweetedElement) {
retweetedElement.remove();
}
if(typeof timeline !== 'undefined') {
let index = timeline.data.findIndex((tweet) => tweet.retweeted_status && tweet.retweeted_status.id_str === t.id_str && !tweet.current_user_retweet);
if(index > -1) {
timeline.data.splice(index, 1);
}
}
}
}
t.renderRetweetsDown();
}
chrome.storage.local.set({tweetReplies: {}, tweetDetails: {}}, () => {});
});
if(options.mainTweet) {
tweetInteractRetweetMenuQuotes.addEventListener('click', async () => {
document.getElementById('loading-box').hidden = false;
history.pushState({}, null, `https://twitter.com/${t.user.screen_name}/status/${t.id_str}/retweets/with_comments`);
updateSubpage();
mediaToUpload = [];
linkColors = {};
cursor = undefined;
seenReplies = [];
mainTweetLikers = [];
let id = location.pathname.match(/status\/(\d{1,32})/)[1];
if(subpage === 'tweet') {
updateReplies(id);
} else if(subpage === 'likes') {
updateLikes(id);
} else if(subpage === 'retweets') {
updateRetweets(id);
} else if(subpage === 'retweets_with_comments') {
updateRetweetsWithComments(id);
}
renderDiscovery();
renderTrends();
currentLocation = location.pathname;
});
tweetInteractRetweetMenuRetweeters.addEventListener('click', async () => {
document.getElementById('loading-box').hidden = false;
history.pushState({}, null, `https://twitter.com/${t.user.screen_name}/status/${t.id_str}/retweets`);
updateSubpage();
mediaToUpload = [];
linkColors = {};
cursor = undefined;
seenReplies = [];
mainTweetLikers = [];
let id = location.pathname.match(/status\/(\d{1,32})/)[1];
if(subpage === 'tweet') {
updateReplies(id);
} else if(subpage === 'likes') {
updateLikes(id);
} else if(subpage === 'retweets') {
updateRetweets(id);
} else if(subpage === 'retweets_with_comments') {
updateRetweetsWithComments(id);
}
renderDiscovery();
renderTrends();
currentLocation = location.pathname;
});
}
tweetInteractRetweetMenuQuote.addEventListener('click', async () => {
if (!tweetReply.hidden) {
tweetInteractReply.classList.remove('tweet-interact-reply-clicked');
tweetReply.hidden = true;
}
tweetQuote.hidden = false;
setTimeout(() => {
tweetQuoteText.focus();
})
});
let quoteMedia = [];
tweetQuote.addEventListener('drop', e => {
handleDrop(e, quoteMedia, tweetQuoteMedia);
});
tweetQuote.addEventListener('paste', event => {
let items = (event.clipboardData || event.originalEvent.clipboardData).items;
for (let index in items) {
let item = items[index];
if (item.kind === 'file') {
let file = item.getAsFile();
handleFiles([file], quoteMedia, tweetQuoteMedia);
}
}
});
tweetQuoteUpload.addEventListener('click', () => {
getMedia(quoteMedia, tweetQuoteMedia);
});
tweetQuoteText.addEventListener('keydown', e => {
if (e.key === 'Enter' && e.ctrlKey) {
tweetQuoteButton.click();
}
});
tweetQuoteText.addEventListener('input', e => {
let text = tweetQuoteText.value.replace(linkRegex, ' https://t.co/xxxxxxxxxx').trim();
tweetQuoteChar.innerText = `${text.length}/280`;
if(text.length > 265) {
tweetQuoteChar.style.color = "#c26363";
} else {
tweetQuoteChar.style.color = "";
}
if (text.length > 280) {
tweetQuoteChar.style.color = "red";
tweetQuoteButton.disabled = true;
} else {
tweetQuoteButton.disabled = false;
}
});
tweetQuoteButton.addEventListener('click', async () => {
let text = tweetQuoteText.value;
tweetQuoteError.innerHTML = '';
if (text.length === 0 && quoteMedia.length === 0) return;
tweetQuoteButton.disabled = true;
let uploadedMedia = [];
for (let i in quoteMedia) {
let media = quoteMedia[i];
try {
media.div.getElementsByClassName('new-tweet-media-img-progress')[0].hidden = false;
let mediaId = await API.uploadMedia({
media_type: media.type,
media_category: media.category,
media: media.data,
alt: media.alt,
cw: media.cw,
loadCallback: data => {
media.div.getElementsByClassName('new-tweet-media-img-progress')[0].innerText = `${data.text} (${data.progress}%)`;
}
});
uploadedMedia.push(mediaId);
} catch (e) {
media.div.getElementsByClassName('new-tweet-media-img-progress')[0].hidden = true;
console.error(e);
alert(e);
}
}
let tweetObject = {
status: text,
attachment_url: `https://twitter.com/${t.user.screen_name}/status/${t.id_str}`
};
if (uploadedMedia.length > 0) {
tweetObject.media_ids = uploadedMedia.join(',');
}
let tweetData;
try {
tweetData = await API.tweet.postV2(tweetObject)
} catch (e) {
tweetQuoteError.innerHTML = (e && e.message ? e.message : e) + "
";
tweetQuoteButton.disabled = false;
return;
}
if (!tweetData) {
tweetQuoteError.innerHTML = `${LOC.error_sending_tweet}
`;
tweetQuoteButton.disabled = false;
return;
}
tweetQuoteText.value = '';
tweetQuoteChar.innerText = '0/280';
tweetQuote.hidden = true;
tweetData._ARTIFICIAL = true;
quoteMedia = [];
tweetQuoteButton.disabled = false;
tweetQuoteMedia.innerHTML = '';
chrome.storage.local.set({tweetReplies: {}, tweetDetails: {}}, () => {});
if(typeof timeline !== 'undefined') timeline.data.unshift(tweetData);
else appendTweet(tweetData, timelineContainer, { prepend: true });
});
// Favorite
t.renderFavoritesDown = () => {
t.favorited = false;
t.favorite_count--;
if(!options.mainTweet) {
tweetInteractFavorite.dataset.val = parseInt(tweetInteractFavorite.innerText.replace(/\s/g, '').replace(/,/g, '').replace(/\./g, '')) - 1;
tweetInteractFavorite.innerText = Number(parseInt(tweetInteractFavorite.innerText.replace(/\s/g, '').replace(/,/g, '').replace(/\./g, '')) - 1).toLocaleString().replace(/\s/g, ',');;
} else {
if(mainTweetLikers.find(liker => liker.id_str === user.id_str)) {
mainTweetLikers.splice(mainTweetLikers.findIndex(liker => liker.id_str === user.id_str), 1);
let likerImg = footerFavorites.querySelector(`a[data-id="${user.id_str}"]`);
if(likerImg) likerImg.remove()
}
tweetFooterFavorites.innerText = Number(parseInt(tweetFooterFavorites.innerText.replace(/\s/g, '').replace(/,/g, '').replace(/\./g, '')) - 1).toLocaleString().replace(/\s/g, ',');
}
tweetInteractFavorite.classList.remove('tweet-interact-favorited');
}
t.renderFavoritesUp = () => {
t.favorited = true;
t.favorite_count++;
if(!options.mainTweet) {
tweetInteractFavorite.dataset.val = parseInt(tweetInteractFavorite.innerText.replace(/\s/g, '').replace(/,/g, '').replace(/\./g, '')) + 1;
tweetInteractFavorite.innerText = Number(parseInt(tweetInteractFavorite.innerText.replace(/\s/g, '').replace(/,/g, '').replace(/\./g, '')) + 1).toLocaleString().replace(/\s/g, ',');;
} else {
if(footerFavorites.children.length < 8 && !mainTweetLikers.find(liker => liker.id_str === user.id_str)) {
let a = document.createElement('a');
a.href = `https://twitter.com/${user.screen_name}`;
let likerImg = document.createElement('img');
likerImg.src = `${(user.default_profile_image && vars.useOldDefaultProfileImage) ? chrome.runtime.getURL(`images/default_profile_images/default_profile_${Number(user.id_str) % 7}_normal.png`): user.profile_image_url_https}`;
likerImg.classList.add('tweet-footer-favorites-img');
likerImg.title = user.name + ' (@' + user.screen_name + ')';
likerImg.width = 24;
likerImg.height = 24;
a.dataset.id = user.id_str;
a.appendChild(likerImg);
footerFavorites.appendChild(a);
mainTweetLikers.push(user);
}
tweetFooterFavorites.innerText = Number(parseInt(tweetFooterFavorites.innerText.replace(/\s/g, '').replace(/,/g, '').replace(/\./g, '')) + 1).toLocaleString().replace(/\s/g, ',');
}
tweetInteractFavorite.classList.add('tweet-interact-favorited');
}
tweetInteractFavorite.addEventListener('click', () => {
if (t.favorited) {
API.tweet.unfavorite(t.id_str).catch(e => {
console.error(e);
alert(e);
t.renderFavoritesUp();
});
t.renderFavoritesDown();
} else {
API.tweet.favorite(t.id_str).catch(e => {
console.error(e);
alert(e);
t.renderFavoritesDown();
});
t.renderFavoritesUp();
}
chrome.storage.local.set({tweetReplies: {}, tweetDetails: {}}, () => {});
});
// More
let moreClicked = false;
tweetInteractMore.addEventListener('click', () => {
if (tweetInteractMoreMenu.hidden) {
tweetInteractMoreMenu.hidden = false;
}
if(moreClicked) return;
moreClicked = true;
setTimeout(() => {
document.body.addEventListener('click', () => {
moreClicked = false;
setTimeout(() => tweetInteractMoreMenu.hidden = true, 50);
}, { once: true });
}, 50);
});
if(tweetInteractMoreMenuFollow) tweetInteractMoreMenuFollow.addEventListener('click', async () => {
if (t.user.following) {
try {
await API.user.unfollow(t.user.screen_name);
} catch(e) {
console.error(e);
alert(e);
return;
}
t.user.following = false;
tweetInteractMoreMenuFollow.innerText = followUserText;
let event = new CustomEvent('tweetAction', { detail: {
action: 'unfollow',
tweet: t
} });
document.dispatchEvent(event);
} else {
try {
await API.user.follow(t.user.screen_name);
} catch(e) {
console.error(e);
alert(e);
return;
}
t.user.following = true;
tweetInteractMoreMenuFollow.innerText = unfollowUserText;
let event = new CustomEvent('tweetAction', { detail: {
action: 'follow',
tweet: t
} });
document.dispatchEvent(event);
}
chrome.storage.local.set({tweetReplies: {}, tweetDetails: {}}, () => {});
});
if(tweetInteractMoreMenuBlock) tweetInteractMoreMenuBlock.addEventListener('click', async () => {
if (t.user.blocking) {
await API.user.unblock(t.user.id_str);
t.user.blocking = false;
if(LOC.block_user.message.includes("$SCREEN_NAME$")) {
tweetInteractMoreMenuBlock.innerText = LOC.block_user.message.replace("$SCREEN_NAME$", t.user.screen_name);
} else {
tweetInteractMoreMenuBlock.innerText = `${LOC.block_user.message} @${t.user.screen_name}`;
}
tweetInteractMoreMenuFollow.hidden = false;
let event = new CustomEvent('tweetAction', { detail: {
action: 'unblock',
tweet: t
} });
document.dispatchEvent(event);
} else {
let blockMessage;
if(LOC.block_sure.message.includes("$SCREEN_NAME$")) {
blockMessage = LOC.block_sure.message.replace("$SCREEN_NAME$", t.user.screen_name);
} else {
blockMessage = `${LOC.block_sure.message} @${t.user.screen_name}?`;
}
let c = confirm(blockMessage);
if (!c) return;
await API.user.block(t.user.id_str);
t.user.blocking = true;
if(LOC.unblock_user.message.includes("$SCREEN_NAME$")) {
tweetInteractMoreMenuBlock.innerText = LOC.unblock_user.message.replace("$SCREEN_NAME$", t.user.screen_name);
} else {
tweetInteractMoreMenuBlock.innerText = `${LOC.unblock_user.message} @${t.user.screen_name}`;
}
tweetInteractMoreMenuFollow.hidden = true;
t.user.following = false;
tweetInteractMoreMenuFollow.innerText = followUserText;
let event = new CustomEvent('tweetAction', { detail: {
action: 'block',
tweet: t
} });
document.dispatchEvent(event);
}
chrome.storage.local.set({tweetReplies: {}, tweetDetails: {}}, () => {});
});
tweetInteractMoreMenuCopy.addEventListener('click', () => {
navigator.clipboard.writeText(`https://${vars.copyLinksAs}/${t.user.screen_name}/status/${t.id_str}`);
});
if(tweetInteractMoreMenuCopyTweetId) tweetInteractMoreMenuCopyTweetId.addEventListener('click', () => {
navigator.clipboard.writeText(t.id_str);
});
if(tweetInteractMoreMenuCopyUserId) tweetInteractMoreMenuCopyUserId.addEventListener('click', () => {
navigator.clipboard.writeText(t.user.id_str);
});
if(tweetInteractMoreMenuShare) tweetInteractMoreMenuShare.addEventListener('click', () => {
navigator.share({ url: `https://twitter.com/${t.user.screen_name}/status/${t.id_str}` });
});
tweetInteractMoreMenuNewtwitter.addEventListener('click', () => {
openInNewTab(`https://twitter.com/${t.user.screen_name}/status/${t.id_str}?newtwitter=true`);
});
tweetInteractMoreMenuEmbed.addEventListener('click', () => {
openInNewTab(`https://publish.twitter.com/?query=https://twitter.com/${t.user.screen_name}/status/${t.id_str}&widget=Tweet`);
});
if (t.user.id_str === user.id_str) {
tweetInteractMoreMenuAnalytics.addEventListener('click', () => {
openInNewTab(`https://twitter.com/${t.user.screen_name}/status/${t.id_str}/analytics?newtwitter=true`);
});
tweetInteractMoreMenuDelete.addEventListener('click', async () => {
let sure = confirm(LOC.delete_sure.message);
if (!sure) return;
try {
await API.tweet.delete(t.id_str);
} catch (e) {
alert(e);
console.error(e);
return;
}
chrome.storage.local.set({tweetReplies: {}, tweetDetails: {}}, () => {});
Array.from(document.getElementsByClassName(`tweet-id-${t.id_str}`)).forEach(tweet => {
tweet.remove();
});
if(options.mainTweet) {
let tweets = Array.from(document.getElementsByClassName('tweet'));
if(tweets.length === 0) {
location.href = 'https://twitter.com/home';
} else {
location.href = tweets[0].getElementsByClassName('tweet-time')[0].href;
}
}
if(typeof timeline !== 'undefined') {
timeline.data = timeline.data.filter(tweet => tweet.id_str !== t.id_str);
}
if(options.after && !options.disableAfterReplyCounter) {
if(options.after.getElementsByClassName('tweet-self-thread-div')[0]) options.after.getElementsByClassName('tweet-self-thread-div')[0].hidden = true;
if(!options.after.classList.contains('tweet-main')) options.after.getElementsByClassName('tweet-interact-reply')[0].innerText = (+options.after.getElementsByClassName('tweet-interact-reply')[0].innerText - 1).toString();
else options.after.getElementsByClassName('tweet-footer-stat-replies')[0].innerText = (+options.after.getElementsByClassName('tweet-footer-stat-replies')[0].innerText - 1).toString();
}
});
if(tweetInteractMoreMenuPin) tweetInteractMoreMenuPin.addEventListener('click', async () => {
if(pinnedTweet && pinnedTweet.id_str === t.id_str) {
await API.tweet.unpin(t.id_str);
pinnedTweet = null;
tweet.remove();
let tweetTime = new Date(t.created_at).getTime();
let beforeTweet = Array.from(document.getElementsByClassName('tweet')).find(i => {
let timestamp = +i.getElementsByClassName('tweet-time')[0].dataset.timestamp;
return timestamp < tweetTime;
});
if(beforeTweet) {
appendTweet(t, timelineContainer, { after: beforeTweet, disableAfterReplyCounter: true });
}
return;
} else {
await API.tweet.pin(t.id_str);
pinnedTweet = t;
let pinnedTweetElement = Array.from(document.getElementsByClassName('tweet')).find(i => {
let topText = i.getElementsByClassName('tweet-top-text')[0];
return (topText && topText.className.includes('pinned'));
});
if(pinnedTweetElement) {
pinnedTweetElement.remove();
}
tweet.remove();
appendTweet(t, timelineContainer, {
prepend: true,
top: {
text: LOC.pinned_tweet.message,
icon: "\uf003",
color: "var(--link-color)",
class: "pinned"
}
});
return;
}
});
}
tweetInteractMoreMenuRefresh.addEventListener('click', async () => {
let tweetData;
try {
tweetData = await API.tweet.getV2(t.id_str);
} catch (e) {
console.error(e);
return;
}
if (!tweetData) {
return;
}
if(typeof timeline !== 'undefined') {
let tweetIndex = timeline.data.findIndex(tweet => tweet.id_str === t.id_str);
if (tweetIndex !== -1) {
timeline.data[tweetIndex] = tweetData;
}
}
if (tweetInteractFavorite.className.includes('tweet-interact-favorited') && !tweetData.favorited) {
tweetInteractFavorite.classList.remove('tweet-interact-favorited');
}
if (tweetInteractRetweet.className.includes('tweet-interact-retweeted') && !tweetData.retweeted) {
tweetInteractRetweet.classList.remove('tweet-interact-retweeted');
}
if (!tweetInteractFavorite.className.includes('tweet-interact-favorited') && tweetData.favorited) {
tweetInteractFavorite.classList.add('tweet-interact-favorited');
}
if (!tweetInteractRetweet.className.includes('tweet-interact-retweeted') && tweetData.retweeted) {
tweetInteractRetweet.classList.add('tweet-interact-retweeted');
}
if(!options.mainTweet) {
tweetInteractFavorite.innerText = tweetData.favorite_count;
tweetInteractRetweet.innerText = tweetData.retweet_count;
tweetInteractReply.innerText = tweetData.reply_count;
}
chrome.storage.local.set({tweetReplies: {}, tweetDetails: {}}, () => {});
});
tweetInteractMoreMenuMute.addEventListener('click', async () => {
if(t.conversation_muted) {
await API.tweet.unmute(t.id_str);
toast.info(LOC.unmuted_convo.message);
t.conversation_muted = false;
tweetInteractMoreMenuMute.innerText = LOC.mute_convo.message;
} else {
await API.tweet.mute(t.id_str);
toast.info(LOC.muted_convo.message);
t.conversation_muted = true;
tweetInteractMoreMenuMute.innerText = LOC.unmute_convo.message;
}
chrome.storage.local.set({tweetReplies: {}, tweetDetails: {}}, () => {});
});
let downloading = false;
if (t.extended_entities && t.extended_entities.media.length > 0) {
tweetInteractMoreMenuDownload.addEventListener('click', () => {
if (downloading) return;
downloading = true;
t.extended_entities.media.forEach((item, index) => {
let url = item.type === 'photo' ? item.media_url_https : item.video_info.variants[0].url;
url = new URL(url);
if (item.type === 'photo') {
url.searchParams.set("name", "orig"); // force original resolution
}
fetch(url).then(res => res.blob()).then(blob => {
downloading = false;
let a = document.createElement('a');
a.href = URL.createObjectURL(blob);
let ts = new Date(t.created_at).toISOString().split("T")[0];
let extension = url.pathname.split('.').pop();
let _index = t.extended_entities.media.length > 1 ? "_"+(index+1) : "";
let filename = `${t.user.screen_name}_${ts}_${t.id_str}${_index}.${extension}`;
a.download = filename;
a.click();
a.remove();
}).catch(e => {
downloading = false;
console.error(e);
});
});
});
}
if (t.extended_entities && t.extended_entities.media.some(m => m.type === 'animated_gif')) {
tweetInteractMoreMenuDownloadGifs.forEach(dgb => dgb.addEventListener('click', e => {
if (downloading) return;
downloading = true;
let n = parseInt(e.target.dataset.gifno)-1;
let videos = Array.from(tweet.getElementsByClassName('tweet-media-gif'));
let video = videos[n];
let canvas = document.createElement('canvas');
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
let ctx = canvas.getContext('2d');
if (video.duration > 10 && !confirm(LOC.long_vid.message)) {
return downloading = false;
}
let mde = tweet.getElementsByClassName('tweet-media-data')[0];
mde.innerText = LOC.initialization.message + '...';
let gif = new GIF({
workers: 4,
quality: 15,
debug: true
});
video.currentTime = 0;
video.loop = false;
let isFirst = true;
let interval = setInterval(async () => {
if(isFirst) {
video.currentTime = 0;
isFirst = false;
await sleep(5);
}
mde.innerText = `${LOC.initialization.message}... (${Math.round(video.currentTime/video.duration*100|0)}%)`;
if (video.currentTime+0.1 >= video.duration) {
clearInterval(interval);
gif.on('working', (frame, frames) => {
mde.innerText = `${LOC.converting.message}... (${frame}/${frames})`;
});
gif.on('finished', blob => {
mde.innerText = '';
let a = document.createElement('a');
a.href = URL.createObjectURL(blob);
a.download = `${t.id_str}.gif`;
document.body.append(a);
a.click();
a.remove();
downloading = false;
video.loop = true;
video.play();
});
gif.render();
return;
}
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
let imgData = ctx.getImageData(0, 0, canvas.width, canvas.height);
gif.addFrame(imgData, { delay: 100 });
}, 100);
}));
}
if(tweetInteractMoreMenuFeedbacks) tweetInteractMoreMenuFeedbacks.forEach(feedbackButton => {
let feedback = t.feedback[feedbackButton.dataset.index];
if (!feedback) return;
feedbackButton.addEventListener('click', () => {
chrome.storage.local.remove(["algoTimeline"], () => {});
if(feedback.richBehavior && feedback.richBehavior.markNotInterestedTopic) {
fetch(`https://twitter.com/i/api/graphql/OiKldXdrDrSjh36WO9_3Xw/TopicNotInterested`, {
method: 'post',
headers: {
'content-type': 'application/json',
'authorization': OLDTWITTER_CONFIG.public_token,
"x-twitter-active-user": 'yes',
"x-csrf-token": OLDTWITTER_CONFIG.csrf,
"x-twitter-auth-type": 'OAuth2Session',
},
body: JSON.stringify({"variables":{"topicId": feedback.richBehavior.markNotInterestedTopic.topicId,"undo":false},"queryId":"OiKldXdrDrSjh36WO9_3Xw"}),
credentials: 'include'
}).then(i => i.json()).then(() => {});
}
fetch(`https://twitter.com/i/api${feedback.feedbackUrl}`, {
method: 'post',
headers: {
'content-type': 'application/x-www-form-urlencoded',
'authorization': OLDTWITTER_CONFIG.public_token,
"x-twitter-active-user": 'yes',
"x-csrf-token": OLDTWITTER_CONFIG.csrf,
"x-twitter-auth-type": 'OAuth2Session',
},
body: `feedback_type=${feedback.feedbackType}&feedback_metadata=${t.feedbackMetadata}&undo=false`,
credentials: 'include'
}).then(i => i.json()).then(i => {
alert(feedback.confirmation ? feedback.confirmation : LOC.feedback_thanks.message);
tweet.remove();
});
});
});
if(options.after) {
options.after.after(tweet);
} else if (options.before) {
options.before.before(tweet);
} else if (options.prepend) {
timelineContainer.prepend(tweet);
} else {
timelineContainer.append(tweet);
}
if(vars.enableTwemoji) twemoji.parse(tweet);
return tweet;
} catch(e) {
console.error(e);
if(Date.now() - lastTweetErrorDate > 1000) {
lastTweetErrorDate = Date.now();
createModal(`
${LOC.something_went_wrong.message}
${LOC.tweet_error.message}
${LOC.error_instructions.message.replace('$AT1$', "").replace(/\$AT2\$/g, '').replace("$AT3$", "")}
${escapeHTML(e.stack ? e.stack : String(e))} at ${t.id_str} (OldTwitter v${chrome.runtime.getManifest().version})
`);
}
return null;
}
}
function replaceAll(str, find, replace) {
return str.split(find).join(replace);
}