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(`

${LOC.content_warnings.message}





`); 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 = ` ${escapeHTML(userData.name)}
${escapeHTML(userData.name)} @${userData.screen_name}
`; 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 += `
${cws}
`; } 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 = `
${u.screen_name}
${escapeHTML(u.name)}
@${u.screen_name} ${u.followed_by ? `${LOC.follows_you.message}` : ''} ${label ? `
${escapeHTML(label)}` : ''}
`; 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*/` ${t.user.name}
${escapeHTML(t.user.name)} @${t.user.screen_name} ${timeElapsed(new Date(t.created_at).getTime())} ${location.pathname.split("?")[0].split("#")[0] === '/i/bookmarks' ? `×` : ''} ${options.mainTweet && t.user.id_str !== user.id_str ? `` : ''} ${!options.mainTweet && !isEnglish ? `${`${t.user.name} ${t.user.screen_name} 1 Sept`.length < 40 ? LOC.view_translation.message : ''}` : ''}
${mentionedUserText !== `` && !options.threadContinuation && !options.noTop && !location.pathname.includes('/status/') && !vars.useOldStyleReply ? /*html*/`
${LOC.replying_to_user.message.replace('$SCREEN_NAME$', mentionedUserText.trim().replaceAll(`> <`, `>${LOC.replying_to_comma.message}<`).replace(`>${LOC.replying_to_comma.message}<`, `>${LOC.replying_to_and.message}<`))}
`: ''}
${vars.useOldStyleReply ? /*html*/mentionedUserText: ''}${full_text ? await renderTweetBodyHTML(full_text, t.entities, t.display_text_range) : ''} ${!isEnglish && options.mainTweet ? /*html*/`
${LOC.view_translation.message} ` : ``} ${t.extended_entities && t.extended_entities.media ? /*html*/`
${t.extended_entities.media.length === 1 && t.extended_entities.media[0].type === 'video' ? /*html*/`
` : ''} ${renderMedia(t)}
${t.extended_entities && t.extended_entities.media && t.extended_entities.media.some(m => m.type === 'animated_gif') ? /*html*/`
GIF
` : ''} ${videos ? /*html*/`
${videos[0].ext && videos[0].ext.mediaStats && videos[0].ext.mediaStats.r && videos[0].ext.mediaStats.r.ok ? `${Number(videos[0].ext.mediaStats.r.ok.viewCount).toLocaleString().replace(/\s/g, ',')} ${LOC.views.message} • ` : ''}${LOC.reload.message} • ${videos[0].video_info.variants.filter(v => v.bitrate).map(v => `${v.url.match(/\/(\d+)x/)[1] + 'p'} `).join(" / ")}
` : ``} ` : ``} ${t.card ? `
` : ''} ${t.quoted_status ? /*html*/` ${escapeHTML(t.quoted_status.user.name)}
${escapeHTML(t.quoted_status.user.name)} @${t.quoted_status.user.screen_name}
${timeElapsed(new Date(t.quoted_status.created_at).getTime())} ${quoteMentionedUserText !== `` && !vars.useOldStyleReply ? /*html*/` ${LOC.replying_to_user.message.replace('$SCREEN_NAME$', quoteMentionedUserText.trim().replaceAll(` `,LOC.replying_to_comma.message).replace(LOC.replying_to_comma.message,LOC.replying_to_and.message))} ` : ''} ${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.quoted_status.extended_entities.media.map(m => `<${m.type === 'photo' ? 'img' : 'video'} ${m.ext_alt_text ? `alt="${escapeHTML(m.ext_alt_text)}" title="${escapeHTML(m.ext_alt_text)}"` : ''} crossorigin="anonymous" width="${quoteSizeFunctions[t.quoted_status.extended_entities.media.length](m.original_info.width, m.original_info.height)[0]}" height="${quoteSizeFunctions[t.quoted_status.extended_entities.media.length](m.original_info.width, m.original_info.height)[1]}" loading="lazy" ${m.type === 'video' ? 'controls' : ''} ${m.type === 'animated_gif' ? 'loop muted onclick="if(this.paused) this.play(); else this.pause()"' : ''}${m.type === 'animated_gif' && !vars.disableGifAutoplay ? ' autoplay' : ''} src="${m.type === 'photo' ? m.media_url_https : m.video_info.variants.find(v => v.content_type === 'video/mp4').url}" class="tweet-media-element tweet-media-element-quote ${mediaClasses[t.quoted_status.extended_entities.media.length]} ${!vars.displaySensitiveContent && t.quoted_status.possibly_sensitive ? 'tweet-media-element-censor' : ''}">${m.type === 'video' ? '' : ''}`).join('\n')}
` : ''}
` : ``} ${t.limited_actions === 'limit_trusted_friends_tweet' && (options.mainTweet || !location.pathname.includes('/status/')) ? /*html*/`
${LOC.circle_limited_tweet.message} ${LOC.learn_more.message}
`.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.tombstone}
` : ''} ${((t.withheld_in_countries && (t.withheld_in_countries.includes("XX") || t.withheld_in_countries.includes("XY"))) || t.withheld_scope) ? `
This Tweet has been withheld in response to a report from the copyright holder. Learn more.
` : ''} ${t.conversation_control ? `
${t.limited_actions_text ? t.limited_actions_text : LOC.limited_tweet.message}${t.conversation_control.policy && (t.user.id_str === user.id_str || (t.conversation_control.policy.toLowerCase() === 'community' && (t.user.followed_by || (full_text && full_text.includes(`@${user.screen_name}`)))) || (t.conversation_control.policy.toLowerCase() === 'by_invitation' && full_text && full_text.includes(`@${user.screen_name}`))) ? ' ' + LOC.you_can_reply.message : ''}.
` : ''} ${options.mainTweet ? /*html*/` ` : ''}
${new Date(t.created_at).toLocaleTimeString(undefined, { hour: 'numeric', minute: 'numeric' }).toLowerCase()} - ${new Date(t.created_at).toLocaleDateString(undefined, { day: 'numeric', month: 'short', year: 'numeric' })}  ・ ${t.source ? t.source.split('>')[1].split('<')[0] : 'Unknown'}
${options.mainTweet ? '' : Number(t.reply_count).toLocaleString().replace(/\s/g, ',')} ${options.mainTweet ? '' : Number(t.retweet_count).toLocaleString().replace(/\s/g, ',')} ${options.mainTweet ? '' : Number(t.favorite_count).toLocaleString().replace(/\s/g, ',')} ${vars.seeTweetViews && t.ext && t.ext.views && t.ext.views.r && t.ext.views.r.ok && t.ext.views.r.ok.count ? /*html*/`${Number(t.ext.views.r.ok.count).toLocaleString().replace(/\s/g, ',')}` : ''} ${t.bookmark_count && vars.showBookmarkCount && options.mainTweet ? /*html*/`${Number(t.bookmark_count).toLocaleString().replace(/\s/g, ',')}` : ''} ${options.selfThreadButton && t.self_thread && t.self_thread.id_str && !options.threadContinuation && !location.pathname.includes('/status/') ? /*html*/`${LOC.show_this_thread.message}` : ``} ${!options.noTop && !options.selfThreadButton && t.in_reply_to_status_id_str && !(options.threadContinuation || (options.selfThreadContinuation && t.self_thread && t.self_thread.id_str)) && !location.pathname.includes('/status/') ? `${LOC.show_this_thread.message}` : ``}
${options.selfThreadContinuation && t.self_thread && t.self_thread.id_str && !location.pathname.includes('/status/') ? /*html*/`
${LOC.show_this_thread.message}
` : /*html*/` ${location.pathname.includes('/status/') ? `

` : ''}
`}
`; // 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*/`
${escapeHTML(t.birdwatch.title)}
${text}
`; 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}:`+ `
`+ `${await renderTweetBodyHTML(translated.text, translated.entities)}`; 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); }