Extension to return old Twitter layout from 2015.
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})();