Extension to return old Twitter layout from 2015.
at master 22 kB view raw
1let pages = [ 2 { 3 name: "home", 4 paths: ["/", "/home"], 5 activeMenu: "home" 6 }, 7 { 8 name: "notifications", 9 paths: ["/notifications", "/notifications/mentions"], 10 activeMenu: "notifications" 11 }, 12 { 13 name: "settings", 14 paths: ["/old/settings"] 15 }, 16 { 17 name: "search", 18 paths: ["/search"] 19 }, 20 { 21 name: "bookmarks", 22 paths: ["/i/bookmarks"], 23 activeMenu: "pin-bookmarks" 24 }, 25 { 26 name: "lists", 27 paths: [/^\/i\/lists\/\d+(\/members|\/followers|)$/] 28 }, 29 { 30 name: "topics", 31 paths: [/^\/i\/topics\/\d+$/] 32 }, 33 { 34 name: "history", 35 paths: ["/old/history"] 36 }, 37 { 38 name: "itl", 39 paths: ["/i/timeline"] 40 }, 41 { 42 name: "tweet", 43 paths: [/^\/[A-z-0-9-_]{1,15}\/status\/\d{5,32}(|\/likes|\/retweets|\/retweets\/with_comments|)$/] 44 }, 45 { 46 name: "profile", 47 paths: [/^\/[A-z-0-9-_]{1,15}(\/with_replies|\/media|\/likes|\/following|\/followers|\/followers_you_follow|\/lists|)$/g], 48 exclude: ["/home", "/notifications", "/messages", "/settings", "/explore", "/login", "/register", "/logout", "/search"] 49 }, 50 { 51 name: "unfollows", 52 paths: ["/old/unfollows/followers", "/old/unfollows/following"] 53 } 54]; 55 56let realPath = location.pathname.split('?')[0].split('#')[0]; 57if (realPath.endsWith("/") && realPath !== "/") { 58 location.replace(realPath.slice(0, -1)); 59} 60if(location.hash.startsWith('#!/')) { 61 location.replace(location.hash.slice(2)); 62} 63 64if (realPath.startsWith("/i/user/")) { 65 let id = realPath.split("/i/user/")[1]; 66 if (id.endsWith("/")) id = id.slice(0, -1); 67 API.user.get(id, true).then(user => { 68 if (user.error) { 69 return; 70 } 71 location.replace("/" + user.screen_name); 72 }); 73} 74if (realPath === '/intent/user') { 75 let id = location.search.split('user_id=')[1]; 76 API.user.get(id, true).then(user => { 77 if (user.error) { 78 return; 79 } 80 location.replace("/" + user.screen_name); 81 }); 82} 83if(/^\/direct_messages\/create\/[A-z-0-9-_]{1,15}$/.test(realPath)) { 84 location.href = `https://twitter.com/${realPath.split("/direct_messages/create/")[1]}#dm`; 85} 86if(/^\/hashtag\/(.*?)/.test(realPath)) { 87 let hashtag = realPath.split("/hashtag/").slice(1).join("/hashtag/"); 88 location.replace(`https://twitter.com/search?q=%23${hashtag}`); 89} 90if(/^\/[A-z-0-9-_]{1,15}\/status\/\d{5,32}\/(photo|video)\/\d+$/.test(realPath)) { 91 let path = realPath.split("/photo/")[0]; 92 location.replace(path); 93} 94if(realPath === '/messages') { 95 location.replace('/home#dm'); 96} 97if(realPath === '/intent/tweet' || realPath === '/share') { 98 location.replace('/home#' + location.search); 99} 100if(realPath === '/intent/follow') { 101 let screen_name = location.search.split('screen_name=')[1].split('&')[0]; 102 location.replace(`/${screen_name}`); 103} 104if( 105 /^\/[A-z-0-9-_]{1,15}\/status\/\d{5,32}\/analytics$/.test(realPath) || 106 /^\/i\/events\/\d{5,32}$/.test(realPath) || 107 realPath.startsWith('/settings/') || 108 realPath.startsWith('/i/flow/') 109) { 110 location.replace(location.href.replace('twitter.com', 'mobile.twitter.com')); 111} 112const LANGUAGES = ["en", "ru", "uk", "fr", "pt_BR", "es", "el", "ro", "tl", "lv", "he", "ne", "nl", "ja", "tr", "it", "ar", "th", "ko", "pl", "vi", "zh_CN", "cs", "de", "ca"]; 113const TRANSLATORS = { 114 "ru": ["dimden", "https://dimden.dev/"], 115 "uk": ["dimden", "https://dimden.dev/"], 116 "fr": [ 117 ["Aurore C.", "https://asuure.com/"], 118 ["zdimension", "https://twitter.com/zdimension_"], 119 ["Pikatchoum", "https://twitter.com/applitom45"] 120 ], 121 "pt_BR": [ 122 ["dzshn", "https://dzshn.xyz/"], 123 ["kigi", "https://twitter.com/kigidere"], 124 ["umgustavo", "https://github.com/umgustavo"] 125 ], 126 "es": [ 127 ["Ruchi", "https://twitter.com/anbulansia"], 128 ["gaelcoral", "https://twitter.com/gaelcoral"], 129 ["hue", "https://twitter.com/huey1116"], 130 ["elderreka", "https://twitter.com/elderreka"] 131 ], 132 "el": ["VasilisTheChu", "https://pikachu.systems/"], 133 "ro": [ 134 ["Skyrina", "https://skyrina.dev/"], 135 ["AlexSem", "https://twitter.com/AlexSem5399"] 136 ], 137 "tl": ["Eurasian", "https://twitter.com/NotPROxV"], 138 "lv": ["yourfriend", "https://3.141.lv/"], 139 "he": [ 140 ["ugh"], 141 ["kriterin", "https://twitter.com/kriterin"] 142 ], 143 "ne": ["DimeDead", "https://dimedead.neocities.org/"], 144 "nl": ["Puka1611"], 145 "ja": [ 146 ["Chazuru", "https://twitter.com/AIWMD"], 147 ["Nyankodasu", "https://github.com/Nyankodasu"] 148 ], 149 "tr": [ 150 ["KayrabCebll", "https://steamcommunity.com/id/KayrabCebll"], 151 ["YordemEren", "https://twitter.com/YordemEren"] 152 ], 153 "it": ["krek", "https://twitter.com/CactusInc420"], 154 "ar": ["Yours Truly,", "https://twitter.com/schrotheneko"], 155 "th": ["0.21%BloodAlcohol", "https://github.com/Silberweich"], 156 "ko": [ 157 ["Nyankodasu", "https://github.com/Nyankodasu"], 158 ["한예림", "https://twitter.com/han_eirin"] 159 ], 160 "pl": [ 161 ["lele"], 162 ["nomi", "https://twitter.com/nomisigns"] 163 ], 164 "vi": ["btmxh", "https://github.com/btmxh"], 165 "zh_CN": ["am1006", "https://github.com/am1006"], 166 "cs": ["Menal"], 167 "de": ["basti564", "https://twitter.com/basti564"], 168 "ca": ["elmees21", "https://twitter.com/elmees21"] 169}; 170let LOC = {}; 171let LOC_EN = {}; 172let LANGUAGE = navigator.language.replace("-", "_"); 173if(!LANGUAGES.includes(LANGUAGE)) { 174 LANGUAGE = LANGUAGE.split("_")[0]; 175 if(!LANGUAGES.includes(LANGUAGE)) { 176 LANGUAGE = "en"; 177 } 178} 179function isDark() { 180 let date = new Date(); 181 let hours = date.getHours(); 182 return hours <= 9 || hours >= 19; 183} 184let customCSS, profileCSS = false; 185async function updateCustomCSS() { 186 let data = await new Promise(resolve => { 187 chrome.storage.sync.get(['customCSS'], data => { 188 resolve(data); 189 }); 190 }); 191 if(!data.customCSS) data.customCSS = ''; 192 if(profileCSS) return; 193 if(customCSS) customCSS.remove(); 194 customCSS = document.createElement('style'); 195 customCSS.id = 'oldtwitter-custom-css'; 196 customCSS.innerHTML = data.customCSS; 197 if(document.head) document.head.appendChild(customCSS); 198 else { 199 let int = setInterval(() => { 200 if(document.head) { 201 clearInterval(int); 202 document.head.appendChild(customCSS); 203 } 204 }, 100); 205 } 206} 207async function updateCustomCSSVariables() { 208 let root = document.querySelector(":root"); 209 let data = await new Promise(resolve => { 210 chrome.storage.sync.get(['customCSSVariables'], data => { 211 resolve(data); 212 }); 213 }); 214 root.style.setProperty('--font', vars.font); 215 root.style.setProperty('--tweet-font', vars.tweetFont); 216 if(data.customCSSVariables) { 217 let csv = parseVariables(data.customCSSVariables); 218 for(let i in csv) { 219 root.style.setProperty(i, csv[i]); 220 } 221 } 222} 223 224function getThemeVariables(enabled) { 225 let theme; 226 if(enabled) { 227 if(vars.pitchBlack) { 228 // Pitch black theme 229 theme = ` 230 --background-color: #000000; 231 --dark-background-color: #000000; 232 --darker-background-color: #000000; 233 --almost-black: #d4e3ed; 234 --border: #222222; 235 --darker-gray: #c9c9c9; 236 --lil-darker-gray: #8394a1; 237 --light-gray: #8394a1; 238 --default-text-color: white; 239 --new-tweet-over: rgb(0 0 0 / 92%); 240 --input-background: #090a0a; 241 --active-message: #0c0d0e; 242 --more-color: #a088ff; 243 --choice-bg: rgb(25 28 30); 244 --list-actions-bg: #19212b; 245 --menu-bg: rgb(16 19 22 / 98%); 246 `; 247 } else { 248 // Dark theme 249 theme = ` 250 --background-color: #1b2836; 251 --dark-background-color: #171f2a; 252 --darker-background-color: #141d26; 253 --almost-black: #d4e3ed; 254 --border: #2c3c52; 255 --darker-gray: #c9c9c9; 256 --lil-darker-gray: #8394a1; 257 --light-gray: #8394a1; 258 --default-text-color: white; 259 --new-tweet-over: rgba(27, 40, 54, 0.92); 260 --input-background: #15202a; 261 --active-message: #141d26; 262 --more-color: #a088ff; 263 --choice-bg: rgb(44 62 71); 264 --list-actions-bg: #19212b; 265 --menu-bg: rgba(34,46,60,0.98); 266 `; 267 } 268 } else { 269 // Light theme 270 theme = ` 271 --background-color: white; 272 --dark-background-color: #f5f8fa; 273 --darker-background-color: #f5f8fa; 274 --almost-black: #292f33; 275 --border: #e1e8ed; 276 --darker-gray: #66757f; 277 --lil-darker-gray: #6a7d8c; 278 --light-gray: #8899a6; 279 --default-text-color: black; 280 --new-tweet-over: rgba(255, 255, 255, 0.92); 281 --input-background: white; 282 --active-message: #eaf5fd; 283 --more-color: #30F; 284 --choice-bg: rgb(207, 217, 222); 285 --list-actions-bg: #efefef; 286 --menu-bg: rgba(255,255,255,0.98); 287 `; 288 } 289 290 return theme; 291} 292function parseVariables(vars) { 293 let obj = {}; 294 let styles = vars.split('\n').map(i => i.trim()).filter(i => i).map(i => i.split(':')).filter(i => i.length === 2); 295 styles.forEach(style => { 296 if(style[1].endsWith(";")) style[1] = style[1].slice(0, -1); 297 obj[style[0].trim()] = style[1].trim(); 298 }); 299 return obj; 300} 301 302async function switchDarkMode(enabled) { 303 let root = document.querySelector(":root"); 304 let theme = getThemeVariables(enabled); 305 let themeVars = parseVariables(theme); 306 for(let i in themeVars) { 307 root.style.setProperty(i, themeVars[i]); 308 } 309 await updateCustomCSSVariables(); 310 311 if(document.body) { 312 document.body.classList.toggle('body-dark', enabled); 313 document.body.classList.toggle('body-pitch-black', enabled && vars.pitchBlack); 314 document.body.classList.toggle('body-light', !enabled); 315 } else { 316 let int = setInterval(() => { 317 if(document.body) { 318 clearInterval(int); 319 document.body.classList.toggle('body-dark', enabled); 320 document.body.classList.toggle('body-pitch-black', enabled && vars.pitchBlack); 321 document.body.classList.toggle('body-light', !enabled); 322 } 323 }, 100); 324 } 325} 326 327let page = realPath === "" ? pages[0] : pages.find(p => (!p.exclude || !p.exclude.includes(realPath)) && (p.paths.includes(realPath) || p.paths.find(r => r instanceof RegExp && r.test(realPath)))); 328(async () => { 329 if (!page) return; 330 331 // block all twitters scripts 332 function blockScriptElements(element) { 333 if (element.tagName === 'SCRIPT') { 334 element.type = 'javascript/blocked'; 335 const beforeScriptExecuteListener = function (event) { 336 if(element.getAttribute('type') === 'javascript/blocked') { 337 event.preventDefault(); 338 } 339 element.removeEventListener('beforescriptexecute', beforeScriptExecuteListener); 340 } 341 element.addEventListener('beforescriptexecute', beforeScriptExecuteListener); 342 element.remove(); 343 } 344 } 345 346 const observer = new MutationObserver((mutations) => { 347 mutations.forEach((mutation) => { 348 if (mutation.type === 'childList' && mutation.addedNodes.length > 0) { 349 mutation.addedNodes.forEach((node) => { 350 if (node.nodeType === Node.ELEMENT_NODE) { 351 blockScriptElements(node); 352 node.querySelectorAll('script').forEach(blockScriptElements); 353 if(node.tagName === 'SVG') { 354 node.remove(); 355 } 356 if(node.id === 'placeholder') { 357 node.remove(); 358 } 359 node.querySelectorAll('svg').forEach(i => i.remove()); 360 if(document.getElementById('placeholder')) document.getElementById('placeholder').remove(); 361 } 362 }); 363 } 364 }); 365 }); 366 367 // Start observing the page for changes 368 observer.observe(document.documentElement, { childList: true, subtree: true }); 369 370 // wait for variables 371 if(!vars) { 372 await varsPromise; 373 } 374 if(vars.darkMode || (vars.timeMode && isDark())) { 375 isDarkModeEnabled = true; 376 switchDarkMode(true); 377 } 378 379 // disable twitters service worker 380 if ('serviceWorker' in navigator) { 381 navigator.serviceWorker.getRegistrations().then(registrations => { 382 for (const registration of registrations) { 383 registration.unregister() 384 } 385 }); 386 // clear cache of service worker 387 if(window.caches) window.caches.keys().then(keyList => { 388 return Promise.all( 389 keyList.map(key => { 390 return window.caches.delete(key); 391 }), 392 ); 393 }); 394 } 395 396 // invalidate manifest cache by blocking it 397 try { 398 fetch('/manifest.json').then(response => response.text()).catch(e => {}); 399 } catch (e) {} 400 401 // default variables 402 if(typeof(vars.linkColorsInTL) !== 'boolean') { 403 vars.linkColorsInTL = true; 404 chrome.storage.sync.set({ 405 linkColorsInTL: true 406 }, () => {}); 407 } 408 if(typeof(vars.enableTwemoji) !== 'boolean') { 409 vars.enableTwemoji = true; 410 chrome.storage.sync.set({ 411 enableTwemoji: true 412 }, () => {}); 413 } 414 if(typeof(vars.enableHashflags) !== 'boolean') { 415 vars.enableHashflags = false; 416 chrome.storage.sync.set({ 417 enableHashflags: false 418 }, () => {}); 419 } 420 if(typeof(vars.customCSSVariables) !== 'string') { 421 vars.customCSSVariables = ''; 422 chrome.storage.sync.set({ 423 customCSSVariables: '' 424 }, () => {}); 425 } 426 if(typeof(vars.copyLinksAs) !== 'string') { 427 vars.copyLinksAs = 'twitter.com'; 428 chrome.storage.sync.set({ 429 copyLinksAs: 'twitter.com' 430 }, () => {}); 431 } 432 if(typeof(vars.timelineType) !== 'string') { 433 let type; 434 if(typeof(vars.chronologicalTL) === 'boolean') { 435 type = vars.chronologicalTL ? 'chrono' : 'algo'; 436 } else { 437 type = 'chrono-social'; 438 } 439 vars.timelineType = type; 440 chrome.storage.sync.set({ 441 timelineType: type 442 }, () => {}); 443 } 444 if(vars.timelineType === 'algov2') { 445 vars.timelineType = 'algo'; 446 chrome.storage.sync.set({ 447 timelineType: 'algo' 448 }, () => {}); 449 } 450 if(typeof(vars.showTopicTweets) !== 'boolean') { 451 vars.showTopicTweets = true; 452 chrome.storage.sync.set({ 453 showTopicTweets: true 454 }, () => {}); 455 } 456 if(typeof(vars.savePreferredQuality) !== 'boolean') { 457 vars.savePreferredQuality = false; 458 chrome.storage.sync.set({ 459 savePreferredQuality: false 460 }, () => {}); 461 } 462 if(typeof(vars.openNotifsAsModal) !== 'boolean') { 463 vars.openNotifsAsModal = window.innerWidth < 650; 464 chrome.storage.sync.set({ 465 openNotifsAsModal: window.innerWidth < 650 466 }, () => {}); 467 } 468 if(typeof(vars.font) !== 'string') { 469 vars.font = 'Arial'; 470 chrome.storage.sync.set({ 471 font: 'Arial' 472 }, () => {}); 473 } 474 if(typeof(vars.tweetFont) !== 'string') { 475 vars.tweetFont = 'Arial'; 476 chrome.storage.sync.set({ 477 tweetFont: vars.font 478 }, () => {}); 479 } 480 if(typeof(vars.showOriginalImages) !== 'boolean') { 481 vars.showOriginalImages = false; 482 chrome.storage.sync.set({ 483 showOriginalImages: false 484 }, () => {}); 485 } 486 if(typeof(vars.useOldStyleReply) !== 'boolean') { 487 vars.useOldStyleReply = false; 488 chrome.storage.sync.set({ 489 useOldStyleReply: false 490 }, () => {}); 491 } 492 if(typeof(vars.enableAd) !== 'boolean') { 493 vars.enableAd = false; 494 chrome.storage.sync.set({ 495 enableAd: false 496 }, () => {}); 497 } 498 if(typeof(vars.useOldDefaultProfileImage) !== 'boolean') { 499 vars.useOldDefaultProfileImage = true; 500 chrome.storage.sync.set({ 501 useOldDefaultProfileImage: true 502 }, () => {}); 503 } 504 if(typeof(vars.pinProfileOnNavbar) !== 'boolean') { 505 vars.pinProfileOnNavbar = true; 506 chrome.storage.sync.set({ 507 pinProfileOnNavbar: true 508 }, () => {}); 509 } 510 if(typeof(vars.roundAvatars) !== 'boolean') { 511 vars.roundAvatars = false; 512 chrome.storage.sync.set({ 513 roundAvatars: false 514 }, () => {}); 515 } 516 517 if(typeof(vars.darkMode) !== 'boolean' && document.body) { 518 let bg = document.body.style.backgroundColor; 519 let isDark = bg === 'rgb(21, 32, 43)' || bg === 'rgb(0, 0, 0)'; 520 vars.darkMode = isDark; 521 chrome.storage.sync.set({ 522 darkMode: isDark 523 }, () => {}); 524 let pitchBlack = bg === 'rgb(0, 0, 0)'; 525 vars.pitchBlack = pitchBlack; 526 chrome.storage.sync.set({ 527 pitchBlack: pitchBlack 528 }, () => {}); 529 } 530 531 if(typeof(vars.autotranslateProfiles) !== 'object') { 532 vars.autotranslateProfiles = []; 533 chrome.storage.sync.set({ 534 autotranslateProfiles: [] 535 }, () => {}); 536 } 537 if(!vars.displaySensitiveContentMoved) { 538 API.account.getSettings().then(settings => { 539 vars.displaySensitiveContent = settings.display_sensitive_media; 540 chrome.storage.sync.set({ 541 displaySensitiveContentMoved: true, 542 displaySensitiveContent: settings.display_sensitive_media 543 }, () => {}); 544 }); 545 } 546 if(typeof(vars.language) !== 'string') { 547 chrome.storage.sync.set({ 548 language: LANGUAGE 549 }, () => {}); 550 } else { 551 LANGUAGE = LANGUAGES.includes(vars.language) ? vars.language : "en"; 552 } 553 554 let [LOC_DATA, LOC_EN_DATA, html, css, header_html, header_css] = await Promise.all([ 555 fetch(chrome.runtime.getURL(`_locales/${LANGUAGE}/messages.json`)).then(response => response.json()), 556 fetch(chrome.runtime.getURL(`_locales/en/messages.json`)).then(response => response.json()), 557 fetch(chrome.runtime.getURL(`layouts/${page.name}/index.html`)).then(response => response.text()), 558 fetch(chrome.runtime.getURL(`layouts/${page.name}/style.css`)).then(response => response.text()), 559 fetch(chrome.runtime.getURL(`layouts/header/index.html`)).then(response => response.text()), 560 fetch(chrome.runtime.getURL(`layouts/header/style.css`)).then(response => response.text()) 561 ]); 562 LOC = LOC_DATA; LOC_EN = LOC_EN_DATA; 563 LOC_EN.extension_id = {message: chrome.runtime.id}; 564 565 // internationalization 566 for(let i in LOC_EN) { 567 if(!LOC[i]) { 568 LOC[i] = LOC_EN[i]; 569 } 570 } 571 let msgs = html.match(/__MSG_(\w+)__/g); 572 if (msgs) { 573 for (let i = 0; i < msgs.length; i++) { 574 let m = msgs[i].slice(6, -2); 575 if(LOC[m]) html = replaceAll(html, msgs[i], LOC[m].message); 576 } 577 } 578 msgs = header_html.match(/__MSG_(\w+)__/g); 579 if (msgs) { 580 for (let i = 0; i < msgs.length; i++) { 581 let m = msgs[i].slice(6, -2); 582 if(LOC[m]) header_html = replaceAll(header_html, msgs[i], LOC[m].message); 583 } 584 } 585 586 let body = document.querySelector('body[style^="background"]'); 587 if(body) { 588 body.remove(); 589 } else { 590 let cleared = false; 591 let removeInt = setInterval(() => { 592 let body = document.querySelector('body[style^="background"]'); 593 if(body) { 594 clearInterval(removeInt); 595 cleared = true; 596 body.remove(); 597 }; 598 }, 25); 599 setTimeout(() => { 600 if(!cleared) clearInterval(removeInt); 601 }, 5000); 602 }; 603 604 document.documentElement.innerHTML = html; 605 if(vars.moveNavbarToBottom) { 606 document.body.classList.add('move-navbar-to-bottom'); 607 } 608 609 observer.disconnect(); 610 611 document.getElementsByTagName('header')[0].innerHTML = header_html; 612 if (page.activeMenu) { 613 let el = document.getElementById(page.activeMenu); 614 el.classList.add("menu-active"); 615 } 616 let version = document.getElementById('oldtwitter-version'); 617 if (version) { 618 version.innerText = chrome.runtime.getManifest().version; 619 } 620 621 let style = document.createElement("style"); 622 style.innerHTML = css; 623 document.head.appendChild(style); 624 let header_style = document.createElement("style"); 625 header_style.innerHTML = header_css; 626 document.head.appendChild(header_style); 627 628 let icon = document.createElement("link"); 629 icon.href = chrome.runtime.getURL(`images/logo32${vars.useNewIcon ? '_new' : ''}.png`); 630 icon.rel = "icon"; 631 icon.id = "site-icon"; 632 document.head.appendChild(icon); 633 634 chrome.runtime.sendMessage({ 635 action: "inject", 636 data: [ 637 "libraries/parseCssColor.js", 638 "libraries/twemoji.min.js", 639 "layouts/header/script.js", 640 `layouts/${page.name}/script.js`, 641 "scripts/tweetviewer.js", 642 "libraries/gif.js", 643 "libraries/viewer.min.js", 644 "libraries/custom-elements.min.js", 645 "libraries/emojipicker.js", 646 "libraries/tinytoast.js" 647 ] 648 }); 649})();