├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── feature-request.md │ └── question.md └── workflows │ ├── pack.yml │ └── test.yml ├── .gitignore ├── LIBRARIES.md ├── LICENSE ├── README.md ├── _locales ├── ar │ └── messages.json ├── bg │ └── messages.json ├── ca │ └── messages.json ├── cs │ └── messages.json ├── de │ └── messages.json ├── el │ └── messages.json ├── en │ └── messages.json ├── es │ └── messages.json ├── fi │ └── messages.json ├── fr │ └── messages.json ├── gl │ └── messages.json ├── he │ └── messages.json ├── id │ └── messages.json ├── is │ └── messages.json ├── it │ └── messages.json ├── ja │ └── messages.json ├── ko │ └── messages.json ├── locales-support.html ├── lv │ └── messages.json ├── nb │ └── messages.json ├── ne │ └── messages.json ├── nl │ └── messages.json ├── pl │ └── messages.json ├── pt_BR │ └── messages.json ├── readme.md ├── ro │ └── messages.json ├── ru │ └── messages.json ├── sv │ └── messages.json ├── th │ └── messages.json ├── tl │ └── messages.json ├── tr │ └── messages.json ├── uk │ └── messages.json ├── vi │ └── messages.json ├── zh_CN │ └── messages.json └── zh_TW │ └── messages.json ├── fonts ├── JustBird.woff2 ├── edge-icons-Regular.woff └── rosetta.woff ├── images ├── default_profile_images │ ├── default_profile_0_400x400.png │ ├── default_profile_0_bigger.png │ ├── default_profile_0_normal.png │ ├── default_profile_1_400x400.png │ ├── default_profile_1_bigger.png │ ├── default_profile_1_normal.png │ ├── default_profile_2_400x400.png │ ├── default_profile_2_bigger.png │ ├── default_profile_2_normal.png │ ├── default_profile_3_400x400.png │ ├── default_profile_3_bigger.png │ ├── default_profile_3_normal.png │ ├── default_profile_400x400.png │ ├── default_profile_4_400x400.png │ ├── default_profile_4_bigger.png │ ├── default_profile_4_normal.png │ ├── default_profile_5_400x400.png │ ├── default_profile_5_bigger.png │ ├── default_profile_5_normal.png │ ├── default_profile_6_400x400.png │ ├── default_profile_6_bigger.png │ ├── default_profile_6_normal.png │ ├── default_profile_NaN_400x400.png │ ├── default_profile_NaN_bigger.png │ ├── default_profile_NaN_normal.png │ ├── default_profile_bigger.png │ └── default_profile_normal.png ├── group.jpg ├── loading.svg ├── logo128.png ├── logo128_notification.png ├── logo16.png ├── logo16_notification.png ├── logo192.png ├── logo32.png ├── logo32_new.png ├── logo32_new_notification.png ├── logo32_notification.png ├── logo48.png ├── logo48_notification.png └── logo512.png ├── layouts ├── bookmarks │ ├── index.html │ ├── script.js │ └── style.css ├── header │ ├── index.html │ ├── script.js │ └── style.css ├── home │ ├── index.html │ ├── script.js │ └── style.css ├── itl │ ├── index.html │ ├── script.js │ └── style.css ├── lists │ ├── index.html │ ├── script.js │ └── style.css ├── notifications │ ├── index.html │ ├── script.js │ └── style.css ├── profile │ ├── index.html │ ├── script.js │ └── style.css ├── search │ ├── index.html │ ├── script.js │ └── style.css ├── settings │ ├── index.html │ ├── script.js │ └── style.css ├── topics │ ├── index.html │ ├── script.js │ └── style.css ├── tweet │ ├── index.html │ ├── script.js │ └── style.css └── unfollows │ ├── index.html │ ├── script.js │ └── style.css ├── libraries ├── coloris.min.css ├── coloris.min.js ├── custom-elements.min.js ├── emojipicker.js ├── gif.js ├── gif.worker.js ├── parseCssColor.js ├── purify.min.js ├── tinytoast.js ├── twemoji.js ├── twitter-text.js ├── viewer.min.css └── viewer.min.js ├── manifest.json ├── pack.js ├── package.json ├── ruleset.json ├── sandbox.html ├── scripts ├── apis.js ├── background.js ├── background_v2.js ├── blockBeforeInject.js ├── config.js ├── helpers.js ├── iframeNavigation.js ├── injection.js ├── newtwitter.js ├── twchallenge.js ├── tweetviewer.js └── xIconRemove.js └── test.js /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. Make sure to search issues, maybe it's already reported. 12 | 13 | **Screenshots** 14 | If applicable, add screenshots or video to help explain your problem. Include screenshot of your browser console (F12 -> Console tab). 15 | 16 | **Browser** 17 | Include your browsers name and it's version. 18 | 19 | **Links** 20 | Link to profile/tweet or other page where bug happens. 21 | 22 | **OldTwitter version** 23 | Can be seen on bottom-right 24 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Request a feature you'd like to see 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | Please do not request multiple big features at once, break them down into multiple issues. 11 | Also keep in mind that OldTwitter is basically on life support now, so don't expect too much. -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Question 3 | about: Ask a question 4 | title: '' 5 | labels: question 6 | assignees: '' 7 | 8 | --- 9 | 10 | Why x does y? 11 | -------------------------------------------------------------------------------- /.github/workflows/pack.yml: -------------------------------------------------------------------------------- 1 | name: Pack Extension 2 | 3 | on: 4 | push: 5 | branches: [ "master" ] 6 | pull_request: 7 | branches: [ "master" ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - name: Checkout Source Tree 16 | uses: actions/checkout@v3 17 | 18 | - name: Setup Node.js environment 19 | uses: actions/setup-node@v3 20 | 21 | - name: Install ZIP 22 | uses: montudor/action-zip@v1 23 | 24 | - name: Setup NPM packages 25 | run: npm install 26 | 27 | - name: Pack extension 28 | run: npm run build-action 29 | 30 | - name: Unzip extension 31 | run: unzip -qq ../OldTwitterChrome.zip -d OldTwitterChrome; unzip -qq ../OldTwitterFirefox.zip -d OldTwitterFirefox 32 | working-directory: ${{ github.workspace }} 33 | 34 | - name: Upload for Firefox 35 | uses: actions/upload-artifact@v4 36 | with: 37 | name: OldTwitterFirefox 38 | path: ${{ github.workspace }}/OldTwitterFirefox 39 | - name: Upload for Chromium 40 | uses: actions/upload-artifact@v4 41 | with: 42 | name: OldTwitterChrome 43 | path: ${{ github.workspace }}/OldTwitterChrome 44 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test locales 2 | 3 | on: 4 | push: 5 | branches: [ "master" ] 6 | pull_request: 7 | branches: [ "master" ] 8 | 9 | jobs: 10 | test: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - name: Checkout Source Tree 16 | uses: actions/checkout@v3 17 | 18 | - name: Setup Node.js environment 19 | uses: actions/setup-node@v3 20 | 21 | - name: Test locales 22 | run: npm run test -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | _metadata/ 2 | node_modules/ 3 | package-lock.json 4 | yarn.lock -------------------------------------------------------------------------------- /LIBRARIES.md: -------------------------------------------------------------------------------- 1 | # Libraries used in the extension 2 | All libraries are built with Ubuntu 22.04.3 LTS, Node.js v20.9.0, Rollup v4.17.2 3 | 4 | ### DOMPurify 5 | File: `libraries/purify.min.js` 6 | Repo: https://github.com/cure53/DOMPurify/tree/3fe78d7501103832166613bb1452985dd4674008 7 | Direct URL: https://raw.githubusercontent.com/cure53/DOMPurify/3fe78d7501103832166613bb1452985dd4674008/dist/purify.min.js 8 | 9 | ### Coloris 10 | File: `libraries/coloris.min.js` 11 | Repo: https://github.com/mdbassit/Coloris/tree/ec2e67f35425a9765c42e1f50e24b177996556b3 12 | Direct URL: https://raw.githubusercontent.com/mdbassit/Coloris/ec2e67f35425a9765c42e1f50e24b177996556b3/dist/coloris.min.js 13 | 14 | ### Custom Elements Polyfill 15 | File: `libraries/custom-elements.min.js` 16 | Repo: https://github.com/webcomponents/polyfills/tree/651e207e1865e0ac959070faaf8d2f3bd7710d34/packages/custom-elements 17 | Direct URL: *No official build provided, need to build it yourself* 18 | Build notes: I only managed to build it using `gulp` 19 | 20 | ### Emoji picker 21 | File: `libraries/emojipicker.js` 22 | Repo: https://github.com/nolanlawson/emoji-picker-element/tree/1cd4b9da68a54fabebf301950c0f07457f79a99f 23 | Direct URL: *No official build provided, need to build it yourself* 24 | Build notes: Building using `npm run build` produces 2 files: `picker.js` and `database.js`. To bundle them into 1 file use Rollup: `rollup picker.js --file emojipicker.js --format iife -n EmojiPicker` 25 | 26 | ### GIF.js 27 | Files: `libraries/gif.js`, `libraries/gif.worker.js` 28 | Repo: https://github.com/jnordberg/gif.js/tree/92d27a02841339e202c75150dcf6fe5f4fa42ec5 29 | Direct URLs: https://raw.githubusercontent.com/jnordberg/gif.js/92d27a02841339e202c75150dcf6fe5f4fa42ec5/dist/gif.js, https://raw.githubusercontent.com/jnordberg/gif.js/92d27a02841339e202c75150dcf6fe5f4fa42ec5/dist/gif.worker.js 30 | 31 | ### parse-css-color 32 | File: `libraries/parseCssColor.js` 33 | Repo: https://github.com/noeldelgado/parse-css-color/tree/3b1825a4c65eed06dcbcfa9976d9053466b9f5f5 34 | Direct URL: *No official build provided, need to build it yourself* 35 | Build notes: Build using Rollup: `rollup -c rollup.config.js --bundleConfigAsCjs`, use `dist/index.umd.js` file 36 | 37 | ### Tiny.toast 38 | File: `libraries/tinytoast.js` 39 | Repo: https://github.com/catdad/tiny.toast/tree/4ec659d3444cd33cc1b0a8b6acb82a5d333e512a 40 | Direct URL: https://www.kirilv.com/tiny.cdn/lib/toast/1.0.0/toast.min.js 41 | 42 | ### Twemoji 43 | File: `libraries/twemoji.js` 44 | Repo: https://github.com/twitter/twemoji/tree/d94f4cf793e6d5ca592aa00f58a88f6a4229ad43 45 | Direct URL: *No official build provided, need to build it yourself* 46 | Build notes: Build using `npm run build` 47 | 48 | ### twitter-text-js 49 | File: `libraries/twitter-text.js` 50 | Repo: https://github.com/twitter/twitter-text/tree/30e2430d90cff3b46393ea54caf511441983c260/js 51 | Direct URL: *No official build provided, need to build it yourself* 52 | Build notes: Build using Rollup: `rollup -c rollup.config.js --bundleConfigAsCjs` 53 | 54 | ### Viewer.js 55 | File: `libraries/viewer.min.js` 56 | Repo: https://github.com/fengyuanchen/viewerjs/tree/cf6fb29a6bef0577cecad18a25770403c89a579d 57 | Direct URL: https://raw.githubusercontent.com/fengyuanchen/viewerjs/cf6fb29a6bef0577cecad18a25770403c89a579d/dist/viewer.min.js -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | All Rights Reserved -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OldTwitter (2025) 2 | Browser extension to return old Twitter layout from 2015 (and option to use 2018 design). 3 | This extension doesn't add any CSS on top of original Twitter. It's fully original client that replaces Twitter, making it much faster than alternatives. 4 | 5 | ## Installation 6 | 7 | **Chrome, Edge, Opera, Brave & Chromium browsers:** [Chrome Web Store](https://chrome.google.com/webstore/detail/old-twitter-layout-2022/jgejdcdoeeabklepnkdbglgccjpdgpmf) 8 | **Firefox:** ~~[Addons For Firefox](https://addons.mozilla.org/en-US/firefox/addon/old-twitter-layout-2022/)~~ was removed, [install manually from Github](#Manual-installation) 9 | 10 | ## Donate 11 | 12 | If you like this extension please consider donating: 13 | 14 | - https://dimden.dev/donate 15 | - https://patreon.com/dimdendev 16 | 17 | ## Screenshots 18 | 19 | ![Profile](https://lune.dimden.dev/ab9304b8c5.png) 20 | ![Profile 2](https://lune.dimden.dev/a198d81e47.png) 21 | ![Profile dark mode](https://lune.dimden.dev/8e7afd71fe.png) 22 | ![Tweet](https://lune.dimden.dev/9acc5de7ad.png) 23 | ![Notifications](https://lune.dimden.dev/73938743da.png) 24 | ![Search](https://lune.dimden.dev/575b9d30f1.png) 25 | ![Modern home](https://lune.dimden.dev/e1cf7d3fa61.png) 26 | 27 | ## Features 28 | 29 | - Almost all of Twitter functionality is implemented 30 | - Both reverse chronological and algorithmical timelines support. And exclusive: Reverse chronological timeline with friends likes and follows (basically mix of both chrono and algo timelines) 31 | - Custom profile link colors supported 32 | - You can change custom profile link color and it'll appear for other extension users (priority: oldtwitter color db -> twitter color db -> default color) 33 | - Removes all analytics and tracking from Twitter 34 | - Track your unfollowers for free 35 | - Search, sort and filter your followers 36 | - Removes all ads 37 | - Easy download of videos and GIFs 38 | - Translate tweets without having to open them, also ability to set specific users/languages to autotranslate 39 | - Shows why tweets were added to algorthimical timeline 40 | - Dark mode support 41 | - Ability to enable/disable Twemoji, disable stars (favorites) back to likes (hearts), change font, default link color and any other color in extension 42 | - Lot of hotkeys 43 | - Mobile support with Kiwi Browser or Firefox 44 | - Custom CSS support 45 | 46 | ## Manual installation 47 | 48 | For beta version: 49 | 50 | - You need Github account, please register if you haven't first! 51 | - Go to [Actions page](https://github.com/dimdenGD/OldTwitter/actions/workflows/pack.yml) 52 | - Click on latest "Pack Extension" workflow run (first from top) 53 | - Download `OldTwitterChrome` if you're on Chromium based browsers and `OldTwitterFirefox` if you're on Firefox 54 | 55 | For stable version: 56 | 57 | - Go to [Releases page](https://github.com/dimdenGD/OldTwitter/releases/) 58 | - Download `OldTwitterChrome.zip` if you're on Chromium based browsers and `OldTwitterFirefox.zip` if you're on Firefox 59 | 60 | #### Chromium 61 | 62 | - Unpack file anywhere 63 | - Go to `chrome://extensions` 64 | - Turn on Developer mode 65 | - Press "Load unpacked" and select folder with extension 66 | ![Install Chrome](https://lune.dimden.dev/ef1ac2f9ef.png) 67 | 68 | #### Firefox 69 | 70 | - Go to `about:debugging#/runtime/this-firefox` 71 | - Press "Load Temporary Add-on" and select zip file you downloaded 72 | 73 | **Installing this way on Firefox will remove it after closing browser. You need to use [Firefox Developer Edition](https://www.mozilla.org/en-US/firefox/developer/) instead for permament installation (see below).** 74 | 75 | #### Firefox Developer Edition 76 | 77 | - Go to `about:config` 78 | - Set `xpinstall.signatures.required` to `false` 79 | - Go to `about:addons` 80 | - Press "Install Add-on From File" and select zip file you downloaded 81 | 82 | *This reportedly works with [Firefox Extended Support Release](https://www.mozilla.org/en-US/firefox/enterprise/) and [Nightly](https://www.mozilla.org/en-US/firefox/channel/desktop/) as well.* 83 | 84 | ## FAQ 85 | 86 | #### Can you use this extension on Android? 87 | 88 | Yes, you can use Kiwi Browser to install it from [Chrome Web Store](https://chrome.google.com/webstore/detail/old-twitter-layout-2022/jgejdcdoeeabklepnkdbglgccjpdgpmf) or Firefox Beta/Nightly to install it from [Addons For Firefox](https://addons.mozilla.org/en-US/firefox/addon/old-twitter-layout-2022/) ([follow these steps for Firefox](https://www.androidpolice.com/install-add-on-extension-mozilla-firefox-android/)). Once installed you can press on "Add to Home screen" button in Kiwi Browser to have it as standalone app. 89 | 90 | #### Is this extension safe? 91 | 92 | [The source code is available at GitHub](https://github.com/dimdenGD/OldTwitter) so you can check everything yourself. It never sends any of your personal info anywhere. 93 | 94 | #### [insert thing] doesn't look like it was in 2015 Twitter! 95 | 96 | Extension won't be pixel perfect copy of old Twitter. I just took general look and feel of it. 97 | 98 | #### I installed extension and my timeline feels kinda unusual 99 | 100 | You had algorithmical timeline enabled. By default OldTwitter turns on reverse chronological timeline, you can switch it back to algorithmical timeline in settings or using the switch on the right side. 101 | 102 | #### How do I visit original Twitter client after installing extension? 103 | 104 | You need to add `?newtwitter=true` to end of your current URL. There's also a "Open this page in new Twitter" button on all pages on bottom right. 105 | 106 | #### Where are the extension settings? 107 | 108 | Click on your profile picture on top-right and then Settings button. 109 | 110 | #### I don't like 2015 design, can I have something more modern 111 | 112 | There's a setting to use design from 2018. 113 | 114 | ## Hotkeys 115 | 116 | You can disable all hotkeys in settings. 117 | 118 | General hotkeys. 119 | `F` - focus search bar. 120 | `ALT+F` - unfocus search bar (only when search bar is focused). 121 | `N` - open new tweet modal. 122 | `ALT+N` - close new tweet modal. 123 | `ESC` - close any modal. 124 | `M` - open/close user menu (use TAB to navigate). 125 | `CTRL+ENTER` - send tweet (when typing reply/quote/new tweet). 126 | 127 | Navigation hotkeys. 128 | `CTRL+ALT+O` - switch between old and new Twitter. 129 | `G+H` - Home 130 | `G+N` - Notifications 131 | `G+M` - Mentions 132 | `G+P` - Profile 133 | `G+L` - Likes 134 | `G+I` - Lists 135 | `G+M` - Messages 136 | `G+S` - Settings 137 | `G+B` - Bookmarks 138 | 139 | Active tweet hotkeys. On bottom-right of tweet element there's blue dot showing tweet is active. 140 | `S` - move to next tweet. 141 | `W` - move to previous tweet. 142 | `L` - (un)favorite/like tweet. 143 | `B` - (un)bookmark tweet. 144 | `T` - (un)retweet tweet. 145 | `R` - open reply box. 146 | `Q` - open quote box. 147 | `C` - copy tweet image. 148 | `D` - download first tweet media. 149 | `SPACE` - open full image / pause or resume video. 150 | `ENTER` - open tweet in new tab. 151 | 152 | These hotkeys work only when reply/quote box is opened. 153 | `ALT+R` - close reply box. 154 | `ALT+Q` - close quote box. 155 | 156 | These will work when reply/quote/new tweet modal is focused. 157 | `ALT+M` - upload media. 158 | `ALT+F` - remove first uploaded media. 159 | 160 | ## Translations 161 | 162 | [Help to translate this extension to your language.](https://github.com/dimdenGD/OldTwitter/tree/master/_locales#readme) 163 | 164 | English - [dimden](https://dimden.dev/) 165 | Russian - [dimden](https://dimden.dev/) 166 | Ukrainian - [dimden](https://dimden.dev/) 167 | French - [Aurore C.](https://asuure.com/), [zdimension](https://twitter.com/zdimension_), [Pikatchoum](https://twitter.com/applitom45), [adriend](https://twitter.com/_adriend_), [celestial04_](https://twitter.com/celestial04_) 168 | Portuguese (Brazil) - [kigidere](https://twitter.com/kigidere), [guko](https://twitter.com/gukodev), [prophamoon](https://twitter.com/prophamoony) 169 | Spanish - [rogerpb98](https://twitter.com/anbulansia), [gaelcoral](https://twitter.com/gaelcoral), [hue](https://twitter.com/huey1116), Beelzenef, [elderreka](https://twitter.com/elderreka) 170 | Greek - [VasilisTheChu](https://pikachu.systems/) 171 | Romanian - [Skyrina](https://skyrina.dev/), [AlexSem](https://twitter.com/AlexSem5399) 172 | Tagalog - [Eurasian](https://twitter.com/NotPROxV), [@conc1erge](https://twitter.com/conc1erge), [@cheesee_its](https://twitter.com/cheesee_its) 173 | Latvian - [sophie](https://sad.ovh/) 174 | Hebrew - "ugh", qqqq, [kriterin](https://twitter.com/kriterin) 175 | Nepali - [DimeDead](https://dimedead.neocities.org/) 176 | Dutch - Puka1611 177 | Japanese - [Chazuru](https://twitter.com/AIWMD), [Nyankodasu](https://twitter.com/Nyankodasu1234), [kamokakesu](https://twitter.com/kamokakesu) 178 | Korean - [Nyankodasu](https://twitter.com/Nyankodasu1234), HY, [Sch](https://me.shtelo.org) 179 | Turkish - [KayrabCebll](https://steamcommunity.com/id/KayrabCebll), [YordemEren](https://twitter.com/YordemEren), [dursunator](https://x.com/dursunator) 180 | Italian - [krek](https://twitter.com/CactusInc420), [Francesco](https://twitter.com/FrancescoRosi27) 181 | Arabic - [Yours Truly,](https://twitter.com/schrotheneko) 182 | Thai - [0.21%BloodAlcohol](https://github.com/Silberweich) 183 | Polish - lele, [nomi](https://twitter.com/youmaynomi) 184 | Vietnamese - [btmxh](https://github.com/btmxh) 185 | Traditional Chinese - [Oliver Tzeng(曾嘉禾)](https://github.com/olivertzeng), [cirx1e](https://github.com/cirx1e) 186 | Simplified Chinese - [am1006](https://github.com/am1006), [CarimoWuling](https://twitter.com/carimowuling) 187 | Czech - Menal 188 | German - [basti564](https://twitter.com/basti564) 189 | Catalan - [elmees21](https://twitter.com/elmees21), [Luna](https://twitter.com/chica_botella) 190 | Swedish - [actuallyaridan](https://twitter.com/actuallyaridan) 191 | Bulgarian - [Scarlett7447](https://twitter.com/Scarlett7447) 192 | Norwegian - [twistquest](https://twitter.com/twistquest) 193 | Indonesian - [lorizade](https://twitter.com/lorizade), [Feerse_](https://twitter.com/Feerse_), [DaGamerFiles](https://twitter.com/DaGamerFiles), [KuchingNeko](https://twitter.com/KuchingNeko) 194 | -------------------------------------------------------------------------------- /_locales/readme.md: -------------------------------------------------------------------------------- 1 | ## Localization / Translation 2 | 3 | Hello! I would be very thankful if you help me to translate this extension. 4 | 5 | ### Getting Started 6 | 7 | There's couple of folders here, each corresponding to language code. 8 | 9 | If you want to translate, copy ``en`` folder (English) and paste with renaming it to your language's 2-letter country code. 10 | 11 | [Here's list of codes](https://www.loc.gov/standards/iso639-2/php/code_list.php). Seek for second row (ISO 639-1). If it's empty there, then sorry, you can't translate to this language. 12 | 13 | Then open newly pasted folder and open ``messages.json`` in it. You'll now see ``{ "message": "some text" }`` parts in it. Then you can start translating by editing ``some text`` part! 14 | 15 | ### Translation Guidelines 16 | 17 | **Things to keep in mind:** 18 | - Some lines have ``description``, ``example`` or ``placeholder`` fields, they are comments for translators or placeholders. You don't have to translate them. 19 | - Pay attention to texts case and symbols in it! If English version has something in lowercase, use lowercase in translation too. 20 | - Keep ``$SOMETHING_HERE$`` parts unchanged. 21 | - Place ``\`` before ``"`` inside your text. 22 | - Try to keep everything as short as English version, if possible (unless it's big messages). 23 | - Some translations are ``replacer``s used in code to batch replace certain strings. Read the description before editing it! 24 | 25 | ### Translation Support Tools 26 | 27 | We provide a helpful tool in the `./_locales` directory to assist with translations: 28 | 29 | #### Locale Diff Tool (./locales-support.html) 30 | This tool helps you: 31 | - Compare your translation with the English base version side by side 32 | - Identify missing translations 33 | - Convert text to proper JSON format for messages.json 34 | - Support dark/light mode for comfortable editing 35 | - Visualize differences between translations 36 | 37 | ### Contributing Your Translation 38 | 39 | When you're finished you can send me your ``messages.json file``: 40 | - create a pull request on GitHub (preferred) 41 | - create [GitHub issue](https://github.com/dimdenGD/OldTwitter/issues) with your file attached 42 | 43 | You can also include your Twitter profile to get credited. 44 | 45 | Thank you! -------------------------------------------------------------------------------- /fonts/JustBird.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dimdenGD/OldTwitter/82c6820eac91e91a358d5fbaa2723d5677854531/fonts/JustBird.woff2 -------------------------------------------------------------------------------- /fonts/edge-icons-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dimdenGD/OldTwitter/82c6820eac91e91a358d5fbaa2723d5677854531/fonts/edge-icons-Regular.woff -------------------------------------------------------------------------------- /fonts/rosetta.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dimdenGD/OldTwitter/82c6820eac91e91a358d5fbaa2723d5677854531/fonts/rosetta.woff -------------------------------------------------------------------------------- /images/default_profile_images/default_profile_0_400x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dimdenGD/OldTwitter/82c6820eac91e91a358d5fbaa2723d5677854531/images/default_profile_images/default_profile_0_400x400.png -------------------------------------------------------------------------------- /images/default_profile_images/default_profile_0_bigger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dimdenGD/OldTwitter/82c6820eac91e91a358d5fbaa2723d5677854531/images/default_profile_images/default_profile_0_bigger.png -------------------------------------------------------------------------------- /images/default_profile_images/default_profile_0_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dimdenGD/OldTwitter/82c6820eac91e91a358d5fbaa2723d5677854531/images/default_profile_images/default_profile_0_normal.png -------------------------------------------------------------------------------- /images/default_profile_images/default_profile_1_400x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dimdenGD/OldTwitter/82c6820eac91e91a358d5fbaa2723d5677854531/images/default_profile_images/default_profile_1_400x400.png -------------------------------------------------------------------------------- /images/default_profile_images/default_profile_1_bigger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dimdenGD/OldTwitter/82c6820eac91e91a358d5fbaa2723d5677854531/images/default_profile_images/default_profile_1_bigger.png -------------------------------------------------------------------------------- /images/default_profile_images/default_profile_1_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dimdenGD/OldTwitter/82c6820eac91e91a358d5fbaa2723d5677854531/images/default_profile_images/default_profile_1_normal.png -------------------------------------------------------------------------------- /images/default_profile_images/default_profile_2_400x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dimdenGD/OldTwitter/82c6820eac91e91a358d5fbaa2723d5677854531/images/default_profile_images/default_profile_2_400x400.png -------------------------------------------------------------------------------- /images/default_profile_images/default_profile_2_bigger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dimdenGD/OldTwitter/82c6820eac91e91a358d5fbaa2723d5677854531/images/default_profile_images/default_profile_2_bigger.png -------------------------------------------------------------------------------- /images/default_profile_images/default_profile_2_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dimdenGD/OldTwitter/82c6820eac91e91a358d5fbaa2723d5677854531/images/default_profile_images/default_profile_2_normal.png -------------------------------------------------------------------------------- /images/default_profile_images/default_profile_3_400x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dimdenGD/OldTwitter/82c6820eac91e91a358d5fbaa2723d5677854531/images/default_profile_images/default_profile_3_400x400.png -------------------------------------------------------------------------------- /images/default_profile_images/default_profile_3_bigger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dimdenGD/OldTwitter/82c6820eac91e91a358d5fbaa2723d5677854531/images/default_profile_images/default_profile_3_bigger.png -------------------------------------------------------------------------------- /images/default_profile_images/default_profile_3_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dimdenGD/OldTwitter/82c6820eac91e91a358d5fbaa2723d5677854531/images/default_profile_images/default_profile_3_normal.png -------------------------------------------------------------------------------- /images/default_profile_images/default_profile_400x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dimdenGD/OldTwitter/82c6820eac91e91a358d5fbaa2723d5677854531/images/default_profile_images/default_profile_400x400.png -------------------------------------------------------------------------------- /images/default_profile_images/default_profile_4_400x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dimdenGD/OldTwitter/82c6820eac91e91a358d5fbaa2723d5677854531/images/default_profile_images/default_profile_4_400x400.png -------------------------------------------------------------------------------- /images/default_profile_images/default_profile_4_bigger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dimdenGD/OldTwitter/82c6820eac91e91a358d5fbaa2723d5677854531/images/default_profile_images/default_profile_4_bigger.png -------------------------------------------------------------------------------- /images/default_profile_images/default_profile_4_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dimdenGD/OldTwitter/82c6820eac91e91a358d5fbaa2723d5677854531/images/default_profile_images/default_profile_4_normal.png -------------------------------------------------------------------------------- /images/default_profile_images/default_profile_5_400x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dimdenGD/OldTwitter/82c6820eac91e91a358d5fbaa2723d5677854531/images/default_profile_images/default_profile_5_400x400.png -------------------------------------------------------------------------------- /images/default_profile_images/default_profile_5_bigger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dimdenGD/OldTwitter/82c6820eac91e91a358d5fbaa2723d5677854531/images/default_profile_images/default_profile_5_bigger.png -------------------------------------------------------------------------------- /images/default_profile_images/default_profile_5_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dimdenGD/OldTwitter/82c6820eac91e91a358d5fbaa2723d5677854531/images/default_profile_images/default_profile_5_normal.png -------------------------------------------------------------------------------- /images/default_profile_images/default_profile_6_400x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dimdenGD/OldTwitter/82c6820eac91e91a358d5fbaa2723d5677854531/images/default_profile_images/default_profile_6_400x400.png -------------------------------------------------------------------------------- /images/default_profile_images/default_profile_6_bigger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dimdenGD/OldTwitter/82c6820eac91e91a358d5fbaa2723d5677854531/images/default_profile_images/default_profile_6_bigger.png -------------------------------------------------------------------------------- /images/default_profile_images/default_profile_6_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dimdenGD/OldTwitter/82c6820eac91e91a358d5fbaa2723d5677854531/images/default_profile_images/default_profile_6_normal.png -------------------------------------------------------------------------------- /images/default_profile_images/default_profile_NaN_400x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dimdenGD/OldTwitter/82c6820eac91e91a358d5fbaa2723d5677854531/images/default_profile_images/default_profile_NaN_400x400.png -------------------------------------------------------------------------------- /images/default_profile_images/default_profile_NaN_bigger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dimdenGD/OldTwitter/82c6820eac91e91a358d5fbaa2723d5677854531/images/default_profile_images/default_profile_NaN_bigger.png -------------------------------------------------------------------------------- /images/default_profile_images/default_profile_NaN_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dimdenGD/OldTwitter/82c6820eac91e91a358d5fbaa2723d5677854531/images/default_profile_images/default_profile_NaN_normal.png -------------------------------------------------------------------------------- /images/default_profile_images/default_profile_bigger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dimdenGD/OldTwitter/82c6820eac91e91a358d5fbaa2723d5677854531/images/default_profile_images/default_profile_bigger.png -------------------------------------------------------------------------------- /images/default_profile_images/default_profile_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dimdenGD/OldTwitter/82c6820eac91e91a358d5fbaa2723d5677854531/images/default_profile_images/default_profile_normal.png -------------------------------------------------------------------------------- /images/group.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dimdenGD/OldTwitter/82c6820eac91e91a358d5fbaa2723d5677854531/images/group.jpg -------------------------------------------------------------------------------- /images/loading.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /images/logo128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dimdenGD/OldTwitter/82c6820eac91e91a358d5fbaa2723d5677854531/images/logo128.png -------------------------------------------------------------------------------- /images/logo128_notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dimdenGD/OldTwitter/82c6820eac91e91a358d5fbaa2723d5677854531/images/logo128_notification.png -------------------------------------------------------------------------------- /images/logo16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dimdenGD/OldTwitter/82c6820eac91e91a358d5fbaa2723d5677854531/images/logo16.png -------------------------------------------------------------------------------- /images/logo16_notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dimdenGD/OldTwitter/82c6820eac91e91a358d5fbaa2723d5677854531/images/logo16_notification.png -------------------------------------------------------------------------------- /images/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dimdenGD/OldTwitter/82c6820eac91e91a358d5fbaa2723d5677854531/images/logo192.png -------------------------------------------------------------------------------- /images/logo32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dimdenGD/OldTwitter/82c6820eac91e91a358d5fbaa2723d5677854531/images/logo32.png -------------------------------------------------------------------------------- /images/logo32_new.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dimdenGD/OldTwitter/82c6820eac91e91a358d5fbaa2723d5677854531/images/logo32_new.png -------------------------------------------------------------------------------- /images/logo32_new_notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dimdenGD/OldTwitter/82c6820eac91e91a358d5fbaa2723d5677854531/images/logo32_new_notification.png -------------------------------------------------------------------------------- /images/logo32_notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dimdenGD/OldTwitter/82c6820eac91e91a358d5fbaa2723d5677854531/images/logo32_notification.png -------------------------------------------------------------------------------- /images/logo48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dimdenGD/OldTwitter/82c6820eac91e91a358d5fbaa2723d5677854531/images/logo48.png -------------------------------------------------------------------------------- /images/logo48_notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dimdenGD/OldTwitter/82c6820eac91e91a358d5fbaa2723d5677854531/images/logo48_notification.png -------------------------------------------------------------------------------- /images/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dimdenGD/OldTwitter/82c6820eac91e91a358d5fbaa2723d5677854531/images/logo512.png -------------------------------------------------------------------------------- /layouts/bookmarks/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | __MSG_bookmarks__ - __MSG_twitter__ 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 |
17 | 18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | 27 |
28 | 29 | 30 |

__MSG_loading__

31 |

@loading

32 |
33 |
34 |
35 |

__MSG_tweets__

36 |

0

37 |
38 |
39 |

__MSG_following__

40 |

0

41 |
42 |
43 |

__MSG_followers__

44 |

0

45 |
46 |
47 |
48 |
49 | 54 |
55 | 56 | __MSG_running__ OldTwitter v0.0.0.
57 | __MSG_created_by__ dimden. 58 |
59 |
60 | 66 | 73 |
74 |
75 |
76 |
77 |
78 |

__MSG_bookmarks__

79 | __MSG_delete_all__ 80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |

__MSG_who_to_follow__


93 | __MSG_refresh__ · __MSG_view_all__ 94 |

95 |
96 |
97 |
98 | 99 | __MSG_running__ OldTwitter v0.0.0.
100 | __MSG_created_by__ dimden. 101 |
102 |
103 | 109 | 116 |
117 |
118 |
119 |
120 |
121 |
122 | 123 | -------------------------------------------------------------------------------- /layouts/bookmarks/script.js: -------------------------------------------------------------------------------- 1 | let user = {}; 2 | let bookmarkCursor = null; 3 | let end = false; 4 | let linkColors = {}; 5 | 6 | function updateUserData() { 7 | API.account.verifyCredentials().then(async u => { 8 | user = u; 9 | userDataFunction(u); 10 | renderUserData(); 11 | }).catch(e => { 12 | if (e === "Not logged in") { 13 | window.location.href = "/i/flow/login?newtwitter=true"; 14 | } 15 | console.error(e); 16 | }); 17 | } 18 | // Render 19 | function renderUserData() { 20 | document.getElementById('user-name').innerText = user.name; 21 | document.getElementById('user-name').classList.toggle('user-verified', user.verified); 22 | document.getElementById('user-name').classList.toggle('user-protected', user.protected); 23 | 24 | document.getElementById('user-handle').innerText = `@${user.screen_name}`; 25 | document.getElementById('user-tweets').innerText = formatLargeNumber(user.statuses_count).replace(/\s/g, ','); 26 | if(user.statuses_count >= 100000 && vars.showExactValues) { 27 | let style = document.createElement('style'); 28 | style.innerText = ` 29 | .user-stat-div > h1 { font-size: 18px !important } 30 | .user-stat-div > h2 { font-size: 13px !important } 31 | `; 32 | document.head.appendChild(style); 33 | } 34 | document.getElementById('user-following').innerText = formatLargeNumber(user.friends_count).replace(/\s/g, ','); 35 | document.getElementById('user-followers').innerText = formatLargeNumber(user.followers_count).replace(/\s/g, ','); 36 | document.getElementById('user-banner').src = user.profile_banner_url ? user.profile_banner_url : 'https://abs.twimg.com/images/themes/theme1/bg.png'; 37 | document.getElementById('user-avatar').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}`.replace('_normal.', '_400x400.'); 38 | document.getElementById('wtf-viewall').href = `/i/connect_people?newtwitter=true&user_id=${user.id_str}`; 39 | document.getElementById('user-avatar-link').href = `/${user.screen_name}`; 40 | document.getElementById('user-info').href = `/${user.screen_name}`; 41 | 42 | if(vars.enableTwemoji) twemoji.parse(document.getElementById('user-name')); 43 | 44 | if(document.getElementById('user-stats').clientWidth > 300) { 45 | let style = document.createElement('style'); 46 | style.innerHTML = html`.user-stat-div > h2 { font-size: 10px !important }`; 47 | document.head.appendChild(style); 48 | } 49 | } 50 | 51 | async function renderBookmarks(cursor) { 52 | let bookmarks; 53 | let bookmarksContainer = document.getElementById('timeline'); 54 | try { 55 | bookmarks = await API.bookmarks.get(cursor); 56 | } catch(e) { 57 | console.error(e); 58 | bookmarksContainer.innerHTML = html`
${e}
`; 59 | document.getElementById('loading-box').hidden = true; 60 | return; 61 | } 62 | 63 | if (bookmarks.cursor) { 64 | bookmarkCursor = bookmarks.cursor; 65 | } else { 66 | end = true; 67 | } 68 | bookmarks = bookmarks.list; 69 | if(bookmarks.length === 0 && !cursor) { 70 | bookmarksContainer.innerHTML = html`
${LOC.empty.message}
`; 71 | document.getElementById('delete-all').hidden = true; 72 | document.getElementById('loading-box').hidden = true; 73 | return; 74 | } 75 | if(bookmarks.length === 0 && cursor) { 76 | end = true; 77 | document.getElementById('loading-box').hidden = true; 78 | return; 79 | } 80 | for (let i = 0; i < bookmarks.length; i++) { 81 | let b = bookmarks[i]; 82 | await appendTweet(b, bookmarksContainer, { 83 | bigFont: b.full_text && b.full_text.length < 75 84 | }); 85 | } 86 | document.getElementById('loading-box').hidden = true; 87 | 88 | } 89 | let loadingNewTweets = false; 90 | 91 | setTimeout(async () => { 92 | if(!vars) { 93 | await loadVars(); 94 | } 95 | 96 | // weird bug 97 | try { 98 | document.getElementById('wtf-refresh').addEventListener('click', async () => { 99 | renderDiscovery(false); 100 | }); 101 | } catch(e) { 102 | setTimeout(() => location.reload(), 2500); 103 | console.error(e); 104 | return; 105 | } 106 | document.addEventListener('scroll', async () => { 107 | // loading new tweets 108 | if ((window.innerHeight + window.scrollY) >= document.body.offsetHeight - 500 && !end) { 109 | if (loadingNewTweets) return; 110 | loadingNewTweets = true; 111 | await renderBookmarks(bookmarkCursor); 112 | setTimeout(() => { 113 | loadingNewTweets = false; 114 | }, 250); 115 | } 116 | }, { passive: true }); 117 | document.getElementById('delete-all').addEventListener('click', async () => { 118 | let modal = createModal(html` 119 |

${LOC.delete_bookmarks.message}

120 | 121 | `); 122 | modal.getElementsByClassName('nice-button')[0].addEventListener('click', () => { 123 | API.bookmarks.deleteAll().then(() => { 124 | document.getElementById('timeline').innerHTML = html`
${LOC.empty.message}
`; 125 | document.getElementById('delete-all').hidden = true; 126 | modal.remove(); 127 | }); 128 | }); 129 | }); 130 | 131 | 132 | // Run 133 | updateUserData(); 134 | renderDiscovery(); 135 | renderTrends(); 136 | renderBookmarks(); 137 | setInterval(updateUserData, 60000 * 3); 138 | setInterval(() => renderDiscovery(false), 60000 * 15); 139 | setInterval(renderTrends, 60000 * 5); 140 | }, 50); -------------------------------------------------------------------------------- /layouts/header/index.html: -------------------------------------------------------------------------------- 1 | 51 | -------------------------------------------------------------------------------- /layouts/home/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | __MSG_home__ - __MSG_twitter__ 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 |
17 | 18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | 49 | 55 |
56 | 57 | __MSG_running__ OldTwitter v0.0.0.
58 | __MSG_created_by__ dimden. 59 |
60 |
61 | 67 | 74 |
75 |
76 |
77 |
78 | 87 |
88 |
89 | 108 | 109 | 110 | 111 | 112 |
113 | 114 |
115 | 124 |
125 |
126 | 127 | 128 |
129 | 130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 | 139 |
140 |
141 | 150 |
151 |

__MSG_who_to_follow__


152 | __MSG_refresh__ · __MSG_view_all__ 153 |

154 |
155 |
156 |
157 | 158 | __MSG_running__ OldTwitter v0.0.0.
159 | __MSG_created_by__ dimden. 160 |
161 |
162 | 168 | 175 |
176 |
177 |
178 |
179 |
180 |
181 | 182 | -------------------------------------------------------------------------------- /layouts/itl/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | __MSG_tweets__ - __MSG_twitter__ 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 |
17 | 18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | 27 |
28 | 29 | 30 |

__MSG_loading__

31 |

@loading

32 |
33 |
34 |
35 |

__MSG_tweets__

36 |

0

37 |
38 |
39 |

__MSG_following__

40 |

0

41 |
42 |
43 |

__MSG_followers__

44 |

0

45 |
46 |
47 |
48 |
49 | 54 |
55 | 56 | __MSG_running__ OldTwitter v0.0.0.
57 | __MSG_created_by__ dimden. 58 |
59 |
60 | 66 | 73 |
74 |
75 |
76 |
77 |
78 |

__MSG_tweets__

79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |

__MSG_who_to_follow__


93 | __MSG_refresh__ · __MSG_view_all__ 94 |

95 |
96 |
97 |
98 | 99 | __MSG_running__ OldTwitter v0.0.0.
100 | __MSG_created_by__ dimden. 101 |
102 |
103 | 109 | 116 |
117 |
118 |
119 |
120 |
121 |
122 | 123 | -------------------------------------------------------------------------------- /layouts/itl/script.js: -------------------------------------------------------------------------------- 1 | let user = {}; 2 | let tlCursor = null; 3 | let end = false; 4 | let linkColors = {}; 5 | let subpage = new URLSearchParams(location.search).get('page'); 6 | let nid = new URLSearchParams(location.search).get('nid'); 7 | 8 | function updateUserData() { 9 | API.account.verifyCredentials().then(async u => { 10 | user = u; 11 | userDataFunction(u); 12 | renderUserData(); 13 | }).catch(e => { 14 | if (e === "Not logged in") { 15 | window.location.href = "/i/flow/login?newtwitter=true"; 16 | } 17 | console.error(e); 18 | }); 19 | } 20 | // Render 21 | function renderUserData() { 22 | document.getElementById('user-name').innerText = user.name; 23 | document.getElementById('user-name').classList.toggle('user-verified', user.verified); 24 | document.getElementById('user-name').classList.toggle('user-protected', user.protected); 25 | 26 | document.getElementById('user-handle').innerText = `@${user.screen_name}`; 27 | document.getElementById('user-tweets').innerText = formatLargeNumber(user.statuses_count).replace(/\s/g, ','); 28 | if(user.statuses_count >= 100000 && vars.showExactValues) { 29 | let style = document.createElement('style'); 30 | style.innerText = ` 31 | .user-stat-div > h1 { font-size: 18px !important } 32 | .user-stat-div > h2 { font-size: 13px !important } 33 | `; 34 | document.head.appendChild(style); 35 | } 36 | document.getElementById('user-following').innerText = formatLargeNumber(user.friends_count).replace(/\s/g, ','); 37 | document.getElementById('user-followers').innerText = formatLargeNumber(user.followers_count).replace(/\s/g, ','); 38 | document.getElementById('user-banner').src = user.profile_banner_url ? user.profile_banner_url : 'https://abs.twimg.com/images/themes/theme1/bg.png'; 39 | document.getElementById('user-avatar').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}`.replace("_normal", "_400x400"); 40 | document.getElementById('wtf-viewall').href = `/i/connect_people?newtwitter=true&user_id=${user.id_str}`; 41 | document.getElementById('user-avatar-link').href = `/${user.screen_name}`; 42 | document.getElementById('user-info').href = `/${user.screen_name}`; 43 | 44 | document.getElementById('loading-box').hidden = true; 45 | if(vars.enableTwemoji) twemoji.parse(document.getElementById('user-name')); 46 | 47 | if(document.getElementById('user-stats').clientWidth > 300) { 48 | let style = document.createElement('style'); 49 | style.innerHTML = html`.user-stat-div > h2 { font-size: 10px !important }`; 50 | document.head.appendChild(style); 51 | } 52 | } 53 | async function renderDeviceNotificationTimeline(cursor) { 54 | let tl = await API.notifications.getDeviceFollowTweets(cursor); 55 | let container = document.getElementById('timeline'); 56 | if (tl.cursor) { 57 | tlCursor = tl.cursor; 58 | } else { 59 | end = true; 60 | } 61 | tl = tl.list; 62 | if(tl.length === 0 && cursor) { 63 | end = true; 64 | return; 65 | } 66 | for (let i = 0; i < tl.length; i++) { 67 | let t = tl[i]; 68 | if (t.retweeted_status) { 69 | await appendTweet(t.retweeted_status, container, { 70 | bigFont: false, 71 | top: { 72 | text: html`${t.user.name} ${LOC.retweeted.message}`, 73 | icon: "\uf006", 74 | color: "#77b255", 75 | class: 'retweet-label' 76 | } 77 | }); 78 | } else { 79 | await appendTweet(t, container, { 80 | bigFont: t.full_text.length < 75 81 | }); 82 | } 83 | } 84 | } 85 | async function renderLikesTimeline() { 86 | document.getElementById("itl-header").innerText = vars.heartsNotStars? LOC.likes.message : LOC.favorites.message; 87 | document.getElementsByTagName('title')[0].innerText = vars.heartsNotStars? LOC.likes.message : LOC.favorites.message + ` - ` + LOC.twitter.message; 88 | let tl = await API.notifications.view(nid); 89 | let tweetContainer = document.getElementById('timeline'); 90 | let userContainer = document.getElementById('user-list'); 91 | for(let i in tl) { 92 | let d = tl[i]; 93 | if(d.type === 'tweet') { 94 | if (d.data.retweeted_status) { 95 | await appendTweet(d.data.retweeted_status, tweetContainer, { 96 | bigFont: false, 97 | top: { 98 | text: html`${d.data.user.name} ${LOC.retweeted.message}`, 99 | icon: "\uf006", 100 | color: "#77b255", 101 | class: 'retweet-label' 102 | } 103 | }); 104 | } else { 105 | await appendTweet(d.data, tweetContainer, { 106 | bigFont: d.data.full_text.length < 75 107 | }); 108 | } 109 | } else if(d.type === 'user') { 110 | await appendUser(d.data, userContainer); 111 | } 112 | } 113 | } 114 | async function renderListsTimeline() { 115 | document.getElementById("itl-header").innerText = LOC.lists.message; 116 | document.getElementsByTagName('title')[0].innerText = LOC.lists.message + ` - ` + LOC.twitter.message; 117 | let lists = await API.notifications.view(nid); 118 | let container = document.getElementById('timeline'); 119 | let userContainer = document.getElementById('user-list'); 120 | userContainer.hidden = true; 121 | container.classList.add('box'); 122 | container.style.padding = '10px'; 123 | 124 | for(let i in lists) { 125 | let l = lists[i]; 126 | if(!l || l.type !== 'list') continue; 127 | l = l.data; 128 | let listElement = document.createElement('div'); 129 | listElement.classList.add('list-item'); 130 | listElement.innerHTML = html` 131 |
132 | 133 | ${l.name} 134 |
135 | ${escapeHTML(l.name)}
136 | ${l.description ? escapeHTML(l.description).slice(0, 52) : LOC.no_description.message} 137 |
138 |
139 |
140 | `; 141 | container.appendChild(listElement); 142 | } 143 | } 144 | 145 | let loadingNewTweets = false; 146 | 147 | setTimeout(async () => { 148 | if(!vars) { 149 | await loadVars(); 150 | } 151 | 152 | if(!subpage || !['likes', 'device_follow', 'lists'].includes(subpage) || (subpage === 'likes' && !nid)) { 153 | location.href = '/home'; 154 | } 155 | 156 | // weird bug 157 | try { 158 | document.getElementById('wtf-refresh').addEventListener('click', async () => { 159 | renderDiscovery(false); 160 | }); 161 | } catch(e) { 162 | setTimeout(() => location.reload(), 2500); 163 | console.error(e); 164 | return; 165 | } 166 | document.addEventListener('scroll', async () => { 167 | // loading new tweets 168 | if (subpage === 'device_follow' && (window.innerHeight + window.scrollY) >= document.body.offsetHeight - 500 && !end) { 169 | if (loadingNewTweets) return; 170 | loadingNewTweets = true; 171 | await renderDeviceNotificationTimeline(tlCursor); 172 | setTimeout(() => { 173 | loadingNewTweets = false; 174 | }, 250); 175 | } 176 | }, { passive: true }); 177 | 178 | // Run 179 | updateUserData(); 180 | renderDiscovery(); 181 | renderTrends(); 182 | if(subpage === 'device_follow') renderDeviceNotificationTimeline(); 183 | else if(subpage === 'likes') renderLikesTimeline(); 184 | else if(subpage === 'lists') renderListsTimeline(nid); 185 | setInterval(updateUserData, 60000 * 3); 186 | setInterval(() => renderDiscovery(false), 60000 * 15); 187 | setInterval(renderTrends, 60000 * 5); 188 | }, 50); -------------------------------------------------------------------------------- /layouts/lists/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | __MSG_list__ - __MSG_twitter__ 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 |
17 | 18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | 29 | 30 |

__MSG_loading__

31 |
32 |
33 | 43 |
44 |
45 |
46 | __MSG_tweets__ 47 |
48 | __MSG_list_members__ 49 |
50 | __MSG_list_subscribers__ 51 |
52 |
53 | 54 | __MSG_running__ OldTwitter v0.0.0.
55 | __MSG_created_by__ dimden. 56 |
57 |
58 | 64 | 71 |
72 |
73 |
74 |
75 |
76 | 87 | 91 | 95 |
96 |
97 |
98 |
99 |

__MSG_who_to_follow__


100 | __MSG_refresh__ · __MSG_view_all__ 101 |

102 |
103 |
104 |
105 | 106 | __MSG_running__ OldTwitter v0.0.0.
107 | __MSG_created_by__ dimden. 108 |
109 |
110 | 116 | 123 |
124 |
125 |
126 |
127 |
128 |
129 | 130 | -------------------------------------------------------------------------------- /layouts/notifications/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | __MSG_notifications__ - __MSG_twitter__ 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 |
17 | 18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | __MSG_notifications__ 27 |
28 | __MSG_mentions__ 29 |
30 | 35 |
36 | 37 | __MSG_running__ OldTwitter v0.0.0.
38 | __MSG_created_by__ dimden. 39 |
40 |
41 | 47 | 54 |
55 |
56 |
57 |
58 |
59 | 60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 | 69 |
70 |
71 |
72 |

__MSG_who_to_follow__


73 | __MSG_refresh__ · __MSG_view_all__ 74 |

75 |
76 |
77 |
78 | 79 | __MSG_running__ OldTwitter v0.0.0.
80 | __MSG_created_by__ dimden. 81 |
82 |
83 | 89 | 96 |
97 |
98 |
99 |
100 |
101 |
102 | 103 | -------------------------------------------------------------------------------- /layouts/notifications/script.js: -------------------------------------------------------------------------------- 1 | let user = {}; 2 | let subpage; 3 | let loadingMore = false; 4 | 5 | // Util 6 | 7 | function updateSubpage() { 8 | if(location.pathname.includes('notifications/mentions')) { 9 | subpage = 'mentions'; 10 | document.getElementById('ns-m').classList.add('notification-switch-active'); 11 | document.getElementById('ns-n').classList.remove('notification-switch-active'); 12 | } else { 13 | subpage = 'notifications'; 14 | document.getElementById('ns-n').classList.add('notification-switch-active'); 15 | document.getElementById('ns-m').classList.remove('notification-switch-active'); 16 | }; 17 | } 18 | 19 | function updateUserData() { 20 | API.account.verifyCredentials().then(u => { 21 | user = u; 22 | userDataFunction(u); 23 | renderUserData(); 24 | }).catch(e => { 25 | if (e === "Not logged in") { 26 | window.location.href = "/i/flow/login?newtwitter=true"; 27 | } 28 | console.error(e); 29 | }); 30 | } 31 | // Render 32 | function renderUserData() { 33 | document.getElementById('wtf-viewall').href = `/i/connect_people?newtwitter=true&user_id=${user.id_str}`; 34 | } 35 | 36 | let cursorTop = undefined; 37 | let cursorBottom = undefined; 38 | 39 | async function updateNotifications(options = { mode: 'rewrite', quiet: false }) { 40 | if(options.mode === 'rewrite' && !options.quiet) { 41 | document.getElementById('notifs-loading').hidden = false; 42 | document.getElementById('notifications-more').hidden = true; 43 | } 44 | let data; 45 | try { 46 | data = await API.notifications.get(options.mode === 'append' ? cursorBottom : options.mode === 'prepend' ? cursorTop : undefined, subpage === 'mentions'); 47 | } catch(e) { 48 | await sleep(2500); 49 | try { 50 | data = await API.notifications.get(options.mode === 'append' ? cursorBottom : options.mode === 'prepend' ? cursorTop : undefined, subpage === 'mentions'); 51 | } catch(e) { 52 | document.getElementById('notifs-loading').hidden = true; 53 | document.getElementById('notifications-more').hidden = false; 54 | document.getElementById('notifications-more').innerText = LOC.load_more.message; 55 | loadingMore = false; 56 | console.error(e); 57 | return; 58 | } 59 | } 60 | if(options.mode === 'append' || options.mode === 'rewrite') { 61 | cursorBottom = data.cursorBottom; 62 | } 63 | if(options.mode === 'prepend' || options.mode === 'rewrite') { 64 | if(data.cursorTop !== cursorTop) { 65 | setTimeout(() => { 66 | API.notifications.markAsRead(cursorTop); 67 | if(windowFocused) { 68 | chrome.storage.local.remove(['unreadCount'], () => {}); 69 | if (data.unreadNotifications > 0) { 70 | document.getElementById('site-icon').href = chrome.runtime.getURL(`images/logo32${vars.useNewIcon ? '_new' : ''}_notification.png`); 71 | let newTitle = document.title; 72 | if(document.title.startsWith('(')) { 73 | newTitle = document.title.split(') ')[1]; 74 | } 75 | newTitle = `(${data.unreadNotifications}) ${newTitle}`; 76 | if(document.title !== newTitle) { 77 | document.title = newTitle; 78 | } 79 | } 80 | notificationBus.postMessage({type: 'markAsRead', cursor: cursorTop}); 81 | } 82 | }, 500); 83 | } 84 | 85 | cursorTop = data.cursorTop; 86 | } 87 | 88 | let notificationsContainer = document.getElementById('notifications-div'); 89 | 90 | if(options.mode === 'append' || options.mode === 'rewrite') { 91 | if(options.mode === 'rewrite') { 92 | notificationsContainer.innerHTML = ''; 93 | } 94 | 95 | let notifs = data.list; 96 | for(let n of notifs) { 97 | if(n.type === 'notification') { 98 | let nd = renderNotification(n, { unread: n.unread }); 99 | notificationsContainer.appendChild(nd); 100 | } else if(n.type === 'tweet') { 101 | let t = await appendTweet(n, notificationsContainer, { noInsert: true, ignoreSeen: true }); 102 | if(t) { 103 | if(n.unread) { 104 | t.classList.add('notification-unread'); 105 | } 106 | notificationsContainer.appendChild(t); 107 | if(vars.enableTwemoji) { 108 | twemoji.parse(t); 109 | } 110 | } 111 | } 112 | } 113 | } else if(options.mode === 'prepend') { 114 | let divs = []; 115 | 116 | let notifs = data.list; 117 | for(let n of notifs) { 118 | if(n.type === 'notification') { 119 | let notificationsWithSameId = document.querySelectorAll(`div[data-notification-id="${n.id}"]`); 120 | notificationsWithSameId.forEach(nd => nd.remove()); 121 | let nd = renderNotification(n, { unread: true }); 122 | divs.push(nd); 123 | } else if(n.type === 'tweet') { 124 | let t = await appendTweet(n, notificationsContainer, { noInsert: true }); 125 | t.classList.add('notification-unread'); 126 | divs.push(t); 127 | } 128 | } 129 | 130 | notificationsContainer.prepend(...divs); 131 | if(vars.enableTwemoji) { 132 | for(let nd of divs) { 133 | twemoji.parse(nd); 134 | } 135 | } 136 | } 137 | 138 | document.getElementById('notifs-loading').hidden = true; 139 | document.getElementById('notifications-more').hidden = false; 140 | document.getElementById('notifications-more').innerText = LOC.load_more.message; 141 | loadingMore = false; 142 | document.getElementById('loading-box').hidden = true; 143 | } 144 | 145 | let windowFocused = document.hidden; 146 | 147 | setTimeout(async () => { 148 | if(typeof vars === 'undefined') { 149 | await loadVars(); 150 | } 151 | 152 | // weird bug 153 | if(!document.getElementById('wtf-refresh')) { 154 | return setTimeout(() => location.reload(), 2500); 155 | } 156 | try { 157 | document.getElementById('wtf-refresh').addEventListener('click', async () => { 158 | renderDiscovery(false); 159 | }); 160 | } catch(e) { 161 | setTimeout(() => location.reload(), 2500); 162 | console.error(e); 163 | return; 164 | } 165 | document.getElementById('notifs-loading').children[0].src = chrome.runtime.getURL(`images/loading.svg`); 166 | 167 | windowFocused = document.hidden; 168 | onVisibilityChange(vis => { 169 | windowFocused = vis; 170 | if(vis) { 171 | notificationBus.postMessage({type: 'markAsRead', cursor: undefined}); 172 | document.getElementById('site-icon').href = chrome.runtime.getURL(`images/logo32${vars.useNewIcon ? '_new' : ''}.png`); 173 | } 174 | }); 175 | 176 | // buttons 177 | document.getElementById('notifications-more').addEventListener('click', async () => { 178 | if(!cursorBottom) return; 179 | if(loadingMore) return; 180 | 181 | loadingMore = true; 182 | document.getElementById('notifications-more').innerText = LOC.loading.message; 183 | updateNotifications({ 184 | mode: 'append', 185 | quiet: false 186 | }); 187 | }); 188 | document.getElementById('ns-m').addEventListener('click', async () => { 189 | cursorTop = undefined; 190 | cursorBottom = undefined; 191 | history.pushState({}, null, '/notifications/mentions'); 192 | document.getElementById('notifs-loading').hidden = false; 193 | document.getElementById('notifications-more').hidden = true; 194 | document.getElementById('notifications-div').innerHTML = html``; 195 | updateSubpage(); 196 | updateNotifications(); 197 | }); 198 | document.getElementById('ns-n').addEventListener('click', async () => { 199 | cursorTop = undefined; 200 | cursorBottom = undefined; 201 | history.pushState({}, null, '/notifications'); 202 | document.getElementById('notifs-loading').hidden = false; 203 | document.getElementById('notifications-more').hidden = true; 204 | document.getElementById('notifications-div').innerHTML = html``; 205 | updateSubpage(); 206 | updateNotifications(); 207 | }); 208 | window.addEventListener("popstate", async () => { 209 | updateSubpage(); 210 | updateNotifications(); 211 | }); 212 | 213 | let search = new URLSearchParams(location.search); 214 | if(search.get('nonavbar') === '1') { 215 | document.getElementById('navbar').hidden = true; 216 | document.getElementById('navbar-line').hidden = true; 217 | document.getElementById('notification-switches').style.top = '5px'; 218 | document.getElementById('notifications-div').style.marginTop = '16px'; 219 | 220 | let root = document.querySelector(":root"); 221 | let bg = root.style.getPropertyValue('--background-color'); 222 | root.style.setProperty('--darker-background-color', bg); 223 | } 224 | 225 | // Update dates every minute 226 | setInterval(() => { 227 | let tweetDates = Array.from(document.getElementsByClassName('tweet-time')); 228 | let tweetQuoteDates = Array.from(document.getElementsByClassName('tweet-time-quote')); 229 | let all = [...tweetDates, ...tweetQuoteDates]; 230 | all.forEach(date => { 231 | date.innerText = timeElapsed(+date.dataset.timestamp); 232 | }); 233 | }, 60000); 234 | 235 | 236 | // Run 237 | updateSubpage(); 238 | updateUserData(); 239 | renderDiscovery(); 240 | renderTrends(); 241 | 242 | await updateNotifications({ mode: 'rewrite', quiet: false }); 243 | await updateNotifications({ mode: 'prepend', quiet: true }); 244 | 245 | setInterval(updateUserData, 60000 * 3); 246 | setInterval(() => renderDiscovery(false), 60000 * 5); 247 | setInterval(renderTrends, 60000 * 5); 248 | setInterval(() => { 249 | updateNotifications({ 250 | mode: 'prepend', 251 | quiet: true 252 | }); 253 | }, 20000); 254 | 255 | document.getElementById('loading-box').hidden = true; 256 | }, 50); -------------------------------------------------------------------------------- /layouts/search/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | __MSG_search__ - __MSG_twitter__ 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 |
17 | 18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | __MSG_popular__ 27 |
28 | __MSG_live__ 29 |
30 | __MSG_people__ 31 |
32 | __MSG_photos__ 33 |
34 | __MSG_videos__ 35 |
36 |
37 | __MSG_all_people__ 38 |
39 | __MSG_people_yk__ 40 |
41 |
42 | __MSG_everywhere__ 43 |
44 | __MSG_near_you__ 45 |
46 |
47 | __MSG_advanced_search__ 48 |
49 | 50 | 55 |
56 | 57 | __MSG_running__ OldTwitter v0.0.0.
58 | __MSG_created_by__ dimden. 59 |
60 |
61 | 67 | 74 |
75 |
76 |
77 |
78 | 79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
__MSG_save_search__
89 |
90 |

__MSG_who_to_follow__


91 | __MSG_refresh__ · __MSG_view_all__ 92 |

93 |
94 |
95 |
96 | 97 | __MSG_running__ OldTwitter v0.0.0.
98 | __MSG_created_by__ dimden. 99 |
100 |
101 | 107 | 114 |
115 |
116 |
117 |
118 |
119 |
120 | 121 | -------------------------------------------------------------------------------- /layouts/topics/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | __MSG_topic__ - __MSG_twitter__ 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 20 |
21 |
22 |
23 |
24 |
25 |
26 | 27 |
28 | 29 | 30 |

__MSG_loading__

31 |

@loading

32 |
33 |
34 |
35 |

__MSG_tweets__

36 |

0

37 |
38 |
39 |

__MSG_following__

40 |

0

41 |
42 |
43 |

__MSG_followers__

44 |

0

45 |
46 |
47 |
48 |
49 | 54 |
55 | 56 | __MSG_running__ OldTwitter v0.0.0.
57 | __MSG_created_by__ dimden. 58 |
59 |
60 | 66 | 73 |
74 |
75 |
76 |
77 |
78 |

__MSG_loading__

79 | 80 |
81 | 85 | 89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |

__MSG_who_to_follow__


102 | __MSG_refresh__ · __MSG_view_all__ 103 |

104 |
105 |
106 |
107 | 108 | __MSG_running__ OldTwitter v0.0.0.
109 | __MSG_created_by__ dimden. 110 |
111 |
112 | 118 | 125 |
126 |
127 |
128 |
129 |
130 |
131 | 132 | -------------------------------------------------------------------------------- /layouts/topics/script.js: -------------------------------------------------------------------------------- 1 | let user = {}; 2 | let cursor = null; 3 | let end = false; 4 | let linkColors = {}; 5 | // /i/topics/1397001890898989057 6 | let topicId = location.pathname.split('/')[3]; 7 | 8 | function updateUserData() { 9 | API.account.verifyCredentials().then(async u => { 10 | user = u; 11 | userDataFunction(u); 12 | renderUserData(); 13 | }).catch(e => { 14 | if (e === "Not logged in") { 15 | window.location.href = "/i/flow/login?newtwitter=true"; 16 | } 17 | console.error(e); 18 | }); 19 | } 20 | // Render 21 | function renderUserData() { 22 | document.getElementById('user-name').innerText = user.name; 23 | document.getElementById('user-name').classList.toggle('user-verified', user.verified); 24 | document.getElementById('user-name').classList.toggle('user-protected', user.protected); 25 | 26 | document.getElementById('user-handle').innerText = `@${user.screen_name}`; 27 | document.getElementById('user-tweets').innerText = formatLargeNumber(user.statuses_count).replace(/\s/g, ','); 28 | if(user.statuses_count >= 100000 && vars.showExactValues) { 29 | let style = document.createElement('style'); 30 | style.innerText = ` 31 | .user-stat-div > h1 { font-size: 18px !important } 32 | .user-stat-div > h2 { font-size: 13px !important } 33 | `; 34 | document.head.appendChild(style); 35 | } 36 | document.getElementById('user-following').innerText = formatLargeNumber(user.friends_count).replace(/\s/g, ','); 37 | document.getElementById('user-followers').innerText = formatLargeNumber(user.followers_count).replace(/\s/g, ','); 38 | document.getElementById('user-banner').src = user.profile_banner_url ? user.profile_banner_url : 'https://abs.twimg.com/images/themes/theme1/bg.png'; 39 | document.getElementById('user-avatar').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}`.replace("_normal", "_400x400"); 40 | document.getElementById('wtf-viewall').href = `/i/connect_people?newtwitter=true&user_id=${user.id_str}`; 41 | document.getElementById('user-avatar-link').href = `/${user.screen_name}`; 42 | document.getElementById('user-info').href = `/${user.screen_name}`; 43 | 44 | if(vars.enableTwemoji) twemoji.parse(document.getElementById('user-name')); 45 | 46 | if(document.getElementById('user-stats').clientWidth > 300) { 47 | let style = document.createElement('style'); 48 | style.innerHTML = html`.user-stat-div > h2 { font-size: 10px !important }`; 49 | document.head.appendChild(style); 50 | } 51 | } 52 | async function renderTopic(cursorRn) { 53 | let [topic] = await Promise.all([ 54 | API.topic.landingPage(topicId, cursorRn) 55 | ]); 56 | 57 | document.getElementsByTagName('title')[0].innerText = `"${topic.header.topic.name}" ` + LOC.topic.message + ` - ` + LOC.twitter.message; 58 | document.getElementById("topic-name").innerText = topic.header.topic.name; 59 | document.getElementById("topic-description").innerText = topic.header.topic.description; 60 | if(topic.header.topic.not_interested) { 61 | document.getElementById("topic-not-interested").hidden = false; 62 | document.getElementById("topic-interested").hidden = true; 63 | } else { 64 | document.getElementById("topic-not-interested").hidden = true; 65 | document.getElementById("topic-interested").hidden = false; 66 | } 67 | if(topic.header.topic.following) { 68 | document.getElementById('topic-not-interested-btn').hidden = true; 69 | document.getElementById('topic-follow-control').innerText = LOC.following.message; 70 | document.getElementById('topic-follow-control').classList.add("topic-following"); 71 | document.getElementById('topic-follow-control').classList.remove("topic-follow"); 72 | } else { 73 | document.getElementById('topic-not-interested-btn').hidden = false; 74 | document.getElementById('topic-follow-control').innerText = LOC.follow.message; 75 | document.getElementById('topic-follow-control').classList.add("topic-follow"); 76 | document.getElementById('topic-follow-control').classList.remove("topic-following"); 77 | } 78 | 79 | let entries = topic.body.timeline.instructions.find(i => i.type === "TimelineAddEntries").entries; 80 | cursor = entries.find(e => e.entryId.startsWith("cursor-bottom")).content.value; 81 | let tweets = entries.filter(e => e.entryId.startsWith('tweet-')).map(e => { 82 | let result = e.content.itemContent.tweet_results.result; 83 | if(!result || !result.legacy) return; 84 | result.legacy.id_str = result.rest_id; 85 | result.legacy.user = result.core.user_results.result; 86 | result.legacy.user.legacy.id_str = result.legacy.user.rest_id; 87 | result.legacy.user = result.legacy.user.legacy; 88 | return result.legacy; 89 | }).filter(i => !!i); 90 | 91 | let tl = document.getElementById("timeline"); 92 | for(let i in tweets) { 93 | await appendTweet(tweets[i], tl, { 94 | bigFont: tweets[i].full_text.length < 75 95 | }); 96 | } 97 | } 98 | 99 | let loadingNewTweets = false; 100 | 101 | setTimeout(async () => { 102 | if(!vars) { 103 | await loadVars(); 104 | } 105 | document.addEventListener('scroll', async () => { 106 | // loading new tweets 107 | if ((window.innerHeight + window.scrollY) >= document.body.offsetHeight - 500 && !end) { 108 | if (loadingNewTweets) return; 109 | loadingNewTweets = true; 110 | await renderTopic(cursor); 111 | setTimeout(() => { 112 | loadingNewTweets = false; 113 | }, 250); 114 | } 115 | }, { passive: true }); 116 | 117 | // weird bug 118 | if(!document.getElementById('wtf-refresh')) { 119 | return setTimeout(() => location.reload(), 2500); 120 | } 121 | try { 122 | document.getElementById('wtf-refresh').addEventListener('click', async () => { 123 | renderDiscovery(false); 124 | }); 125 | } catch(e) { 126 | setTimeout(() => location.reload(), 2500); 127 | console.error(e); 128 | return; 129 | } 130 | document.getElementById('topic-not-interested-btn').addEventListener('click', async () => { 131 | try { 132 | await API.topic.notInterested(topicId); 133 | } catch(e) { 134 | console.error(e); 135 | alert(e); 136 | return; 137 | } 138 | document.getElementById("topic-not-interested").hidden = false; 139 | document.getElementById("topic-interested").hidden = true; 140 | }); 141 | document.getElementById('topic-not-interested-cancel').addEventListener('click', async () => { 142 | try { 143 | await API.topic.undoNotInterested(topicId); 144 | } catch(e) { 145 | console.error(e); 146 | alert(e); 147 | return; 148 | } 149 | document.getElementById("topic-not-interested").hidden = true; 150 | document.getElementById("topic-interested").hidden = false; 151 | }); 152 | document.getElementById('topic-follow-control').addEventListener('click', async e => { 153 | if(e.target.className.includes('topic-following')) { 154 | try { 155 | await API.topic.unfollow(topicId); 156 | } catch(e) { 157 | console.error(e); 158 | alert(e); 159 | return; 160 | } 161 | document.getElementById('topic-follow-control').classList.remove('topic-following'); 162 | document.getElementById('topic-follow-control').classList.add('topic-follow'); 163 | document.getElementById('topic-follow-control').innerText = LOC.follow.message; 164 | document.getElementById('topic-not-interested-btn').hidden = false; 165 | } else { 166 | try { 167 | await API.topic.follow(topicId); 168 | } catch(e) { 169 | console.error(e); 170 | alert(e); 171 | return; 172 | } 173 | document.getElementById('topic-follow-control').classList.remove('topic-follow'); 174 | document.getElementById('topic-follow-control').classList.add('topic-following'); 175 | document.getElementById('topic-follow-control').innerText = LOC.following.message; 176 | document.getElementById('topic-not-interested-btn').hidden = true; 177 | } 178 | }); 179 | 180 | // Run 181 | updateUserData(); 182 | renderDiscovery(); 183 | renderTrends(); 184 | renderTopic(); 185 | document.getElementById('loading-box').hidden = true; 186 | setInterval(updateUserData, 60000 * 3); 187 | setInterval(() => renderDiscovery(false), 60000 * 15); 188 | setInterval(renderTrends, 60000 * 5); 189 | }, 50); -------------------------------------------------------------------------------- /layouts/tweet/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | __MSG_tweet__ - __MSG_twitter__ 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 |
17 | 18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | 50 | 55 |
56 | 57 | __MSG_running__ OldTwitter v0.0.0.
58 | __MSG_created_by__ dimden. 59 |
60 |
61 | 67 | 74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 | 82 |
83 |
84 |
85 | 86 | 87 | 88 | 89 | 90 | 91 |
92 |
93 |
94 |

__MSG_who_to_follow__


95 | __MSG_refresh__ · __MSG_view_all__ 96 |

97 |
98 |
99 |
100 | 101 | __MSG_running__ OldTwitter v0.0.0.
102 | __MSG_created_by__ dimden. 103 |
104 |
105 | 111 | 118 |
119 |
120 |
121 |
122 |
123 |
124 | 125 | -------------------------------------------------------------------------------- /layouts/unfollows/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | __MSG_unfollowers__ - __MSG_twitter__ 8 | 9 | 10 | 11 | 12 | 13 | 18 |
19 |
20 |
21 |
22 | 46 | 51 |
52 | 53 | __MSG_running__ OldTwitter v0.0.0.
54 | __MSG_created_by__ dimden. 55 |
56 |
57 | 63 | 70 |
71 |
72 |
73 |
74 |

__MSG_unfollows__

75 | 76 |
77 |
78 | 79 |
80 |
81 |
82 |
83 |

__MSG_who_to_follow__


84 | __MSG_refresh__ ·· __MSG_view_all__ 85 |

86 |
87 |
88 |
89 | 90 | __MSG_running__ OldTwitter v0.0.0.
91 | __MSG_created_by__ dimden. 92 |
93 |
94 | 100 | 107 |
108 |
109 |
110 |
111 | 112 | -------------------------------------------------------------------------------- /layouts/unfollows/script.js: -------------------------------------------------------------------------------- 1 | let user = {}; 2 | let bookmarkCursor = null; 3 | let end = false; 4 | let linkColors = {}; 5 | let unfollowersPage = location.pathname.includes('/followers'); 6 | let lastPage = 0, loading = true; 7 | 8 | function updateUserData() { 9 | API.account.verifyCredentials().then(async u => { 10 | user = u; 11 | userDataFunction(u); 12 | renderUserData(); 13 | renderUnfollows(); 14 | }).catch(e => { 15 | if (e === "Not logged in") { 16 | window.location.href = "/i/flow/login?newtwitter=true"; 17 | } 18 | console.error(e); 19 | }); 20 | } 21 | // Render 22 | function renderUserData() { 23 | document.getElementById('user-name').innerText = user.name; 24 | document.getElementById('user-name').classList.toggle('user-verified', user.verified); 25 | document.getElementById('user-name').classList.toggle('user-protected', user.protected); 26 | 27 | document.getElementById('user-handle').innerText = `@${user.screen_name}`; 28 | document.getElementById('user-tweets').innerText = formatLargeNumber(user.statuses_count).replace(/\s/g, ','); 29 | if(user.statuses_count >= 100000 && vars.showExactValues) { 30 | let style = document.createElement('style'); 31 | style.innerText = ` 32 | .user-stat-div > h1 { font-size: 18px !important } 33 | .user-stat-div > h2 { font-size: 13px !important } 34 | `; 35 | document.head.appendChild(style); 36 | } 37 | document.getElementById('user-following').innerText = formatLargeNumber(user.friends_count).replace(/\s/g, ','); 38 | document.getElementById('user-followers').innerText = formatLargeNumber(user.followers_count).replace(/\s/g, ','); 39 | document.getElementById('user-tweets-div').href = `/${user.screen_name}`; 40 | document.getElementById('user-following-div').href = `/${user.screen_name}/following`; 41 | document.getElementById('user-followers-div').href = `/${user.screen_name}/followers`; 42 | document.getElementById('user-banner').src = user.profile_banner_url ? user.profile_banner_url : 'https://abs.twimg.com/images/themes/theme1/bg.png'; 43 | document.getElementById('user-avatar').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}`.replace("_normal", "_400x400"); 44 | document.getElementById('wtf-viewall').href = `/i/connect_people?newtwitter=true&user_id=${user.id_str}`; 45 | document.getElementById('user-avatar-link').href = `/${user.screen_name}`; 46 | document.getElementById('user-info').href = `/${user.screen_name}`; 47 | 48 | if(user.followers_count > 50000 || user.friends_count > 50000) { 49 | document.getElementById('timeline').innerHTML = html`${LOC.not_possible_to_see_unfollowers.message}`; 50 | } 51 | 52 | if(vars.enableTwemoji) twemoji.parse(document.getElementById('user-name')); 53 | 54 | document.getElementById('loading-box').hidden = true; 55 | 56 | if(document.getElementById('user-stats').clientWidth > 300) { 57 | let style = document.createElement('style'); 58 | style.innerHTML = html`.user-stat-div > h2 { font-size: 10px !important }`; 59 | document.head.appendChild(style); 60 | } 61 | } 62 | function renderUnfollows(page = 0) { 63 | chrome.storage.local.get(['unfollows'], async d => { 64 | loading = true; 65 | if(user.followers_count && (user.followers_count > 50000 || user.friends_count > 50000)) { 66 | return; 67 | } 68 | 69 | let res = d.unfollows; 70 | if(!res) res = {}; 71 | if(!res[user.id_str]) res[user.id_str] = { 72 | followers: [], 73 | following: [], 74 | unfollowers: [], 75 | unfollowings: [], 76 | lastUpdate: 0 77 | }; 78 | if(res[user.id_str].lastUpdate > Date.now() - 60000 * 15) { 79 | document.getElementById('update-btn').disabled = true; 80 | document.getElementById('update-btn').title = LOC.recent_unfollow_update.message; 81 | } else { 82 | document.getElementById('update-btn').disabled = false; 83 | document.getElementById('update-btn').title = ''; 84 | } 85 | 86 | let raw = res[user.id_str][unfollowersPage ? 'unfollowers' : 'unfollowings'].sort((a, b) => b[1] - a[1]); 87 | let unfollows = raw.slice(page * 100, page * 100 + 100); 88 | let timeline = document.getElementById('timeline'); 89 | if(page === 0) timeline.innerHTML = ''; 90 | 91 | if(unfollows.length === 0) { 92 | return timeline.innerHTML = html`${unfollowersPage ? LOC.no_unfollowers.message : LOC.no_unfollowings.message}`; 93 | } 94 | 95 | let userData; 96 | try { 97 | userData = await API.user.lookup(unfollows.map(u => u[0])); 98 | } catch(e) { 99 | console.error(e); 100 | if(String(e).includes('No user matches for specified terms.')) { 101 | return timeline.innerHTML = html`${LOC.deleted_accounts.message}`; 102 | } else { 103 | return timeline.innerHTML = html`${escapeHTML(String(e))}`; 104 | } 105 | } 106 | document.getElementById('load-more').hidden = raw.length < page * 100 + 100; 107 | document.getElementById('load-more').innerText = LOC.load_more.message; 108 | lastPage = page; 109 | 110 | for(let i = 0; i < unfollows.length; i++) { 111 | let user = userData.find(u => u.id_str === unfollows[i][0]); 112 | if(!user) continue; 113 | if(unfollowersPage) { 114 | if(user.followed_by) continue; 115 | } else { 116 | if(user.following) continue; 117 | } 118 | if(unfollowersPage && user.id_str === '1708130407663759360') continue; // dimden 119 | 120 | appendUser(user, timeline, new Date(unfollows[i][1]).toLocaleString()); 121 | } 122 | loading = false; 123 | }); 124 | } 125 | 126 | setTimeout(async () => { 127 | if(!vars) { 128 | await loadVars(); 129 | } 130 | // weird bug 131 | try { 132 | document.getElementById('wtf-refresh').addEventListener('click', async () => { 133 | renderDiscovery(false); 134 | }); 135 | } catch(e) { 136 | setTimeout(() => location.reload(), 2500); 137 | console.error(e); 138 | return; 139 | } 140 | 141 | document.getElementById('utitle').innerText = unfollowersPage ? LOC.unfollowers.message : LOC.unfollowings.message; 142 | document.getElementsByTagName('title')[0].innerText = unfollowersPage ? LOC.unfollowers.message : LOC.unfollowings.message; 143 | document.getElementById('update-btn').addEventListener('click', async () => { 144 | chrome.storage.local.get(['unfollows'], async d => { 145 | let res = d.unfollows; 146 | if(!res) res = {}; 147 | if(!res[user.id_str]) res[user.id_str] = { 148 | followers: [], 149 | following: [], 150 | unfollowers: [], 151 | unfollowings: [], 152 | lastUpdate: 0 153 | }; 154 | await updateUnfollows(res); 155 | lastPage = 0; 156 | renderUnfollows(); 157 | }); 158 | }); 159 | document.getElementById('load-more').addEventListener('click', () => { 160 | if(loading) return; 161 | document.getElementById('load-more').innerText = LOC.loading.message; 162 | renderUnfollows(lastPage + 1) 163 | }); 164 | 165 | // Run 166 | updateUserData(); 167 | renderDiscovery(); 168 | renderTrends(); 169 | document.getElementById('loading-box').hidden = true; 170 | setInterval(updateUserData, 60000 * 3); 171 | setInterval(() => renderDiscovery(false), 60000 * 15); 172 | setInterval(renderTrends, 60000 * 5); 173 | }, 50); -------------------------------------------------------------------------------- /libraries/coloris.min.css: -------------------------------------------------------------------------------- 1 | .clr-picker{display:none;flex-wrap:wrap;position:absolute;width:200px;z-index:1000;border-radius:10px;background-color:#fff;justify-content:flex-end;direction:ltr;box-shadow:0 0 5px rgba(0,0,0,.05),0 5px 20px rgba(0,0,0,.1);-moz-user-select:none;-webkit-user-select:none;user-select:none}.clr-picker.clr-open,.clr-picker[data-inline=true]{display:flex}.clr-picker[data-inline=true]{position:relative}.clr-gradient{position:relative;width:100%;height:100px;margin-bottom:15px;border-radius:3px 3px 0 0;background-image:linear-gradient(rgba(0,0,0,0),#000),linear-gradient(90deg,#fff,currentColor);cursor:pointer}.clr-marker{position:absolute;width:12px;height:12px;margin:-6px 0 0 -6px;border:1px solid #fff;border-radius:50%;background-color:currentColor;cursor:pointer}.clr-picker input[type=range]::-webkit-slider-runnable-track{width:100%;height:16px}.clr-picker input[type=range]::-webkit-slider-thumb{width:16px;height:16px;-webkit-appearance:none}.clr-picker input[type=range]::-moz-range-track{width:100%;height:16px;border:0}.clr-picker input[type=range]::-moz-range-thumb{width:16px;height:16px;border:0}.clr-hue{background-image:linear-gradient(to right,red 0,#ff0 16.66%,#0f0 33.33%,#0ff 50%,#00f 66.66%,#f0f 83.33%,red 100%)}.clr-alpha,.clr-hue{position:relative;width:calc(100% - 40px);height:8px;margin:5px 20px;border-radius:4px}.clr-alpha span{display:block;height:100%;width:100%;border-radius:inherit;background-image:linear-gradient(90deg,rgba(0,0,0,0),currentColor)}.clr-alpha input,.clr-hue input{position:absolute;width:calc(100% + 32px);height:16px;left:-16px;top:-4px;margin:0;background-color:transparent;opacity:0;cursor:pointer;appearance:none;-webkit-appearance:none}.clr-alpha div,.clr-hue div{position:absolute;width:16px;height:16px;left:0;top:50%;margin-left:-8px;transform:translateY(-50%);border:2px solid #fff;border-radius:50%;background-color:currentColor;box-shadow:0 0 1px #888;pointer-events:none}.clr-alpha div:before{content:'';position:absolute;height:100%;width:100%;left:0;top:0;border-radius:50%;background-color:currentColor}.clr-format{display:none;order:1;width:calc(100% - 40px);margin:0 20px 20px}.clr-segmented{display:flex;position:relative;width:100%;margin:0;padding:0;border:1px solid #ddd;border-radius:15px;box-sizing:border-box;color:#999;font-size:12px}.clr-segmented input,.clr-segmented legend{position:absolute;width:100%;height:100%;margin:0;padding:0;border:0;left:0;top:0;opacity:0;pointer-events:none}.clr-segmented label{flex-grow:1;margin:0;padding:4px 0;font-size:inherit;font-weight:400;line-height:initial;text-align:center;cursor:pointer}.clr-segmented label:first-of-type{border-radius:10px 0 0 10px}.clr-segmented label:last-of-type{border-radius:0 10px 10px 0}.clr-segmented input:checked+label{color:#fff;background-color:#666}.clr-swatches{order:2;width:calc(100% - 32px);margin:0 16px}.clr-swatches div{display:flex;flex-wrap:wrap;padding-bottom:12px;justify-content:center}.clr-swatches button{position:relative;width:20px;height:20px;margin:0 4px 6px 4px;padding:0;border:0;border-radius:50%;color:inherit;text-indent:-1000px;white-space:nowrap;overflow:hidden;cursor:pointer}.clr-swatches button:after{content:'';display:block;position:absolute;width:100%;height:100%;left:0;top:0;border-radius:inherit;background-color:currentColor;box-shadow:inset 0 0 0 1px rgba(0,0,0,.1)}input.clr-color{order:1;width:calc(100% - 80px);height:32px;margin:15px 20px 20px auto;padding:0 10px;border:1px solid #ddd;border-radius:16px;color:#444;background-color:#fff;font-family:sans-serif;font-size:14px;text-align:center;box-shadow:none}input.clr-color:focus{outline:0;border:1px solid #1e90ff}.clr-clear,.clr-close{display:none;order:2;height:24px;margin:0 20px 20px;padding:0 20px;border:0;border-radius:12px;color:#fff;background-color:#666;font-family:inherit;font-size:12px;font-weight:400;cursor:pointer}.clr-close{display:block;margin:0 20px 20px auto}.clr-preview{position:relative;width:32px;height:32px;margin:15px 0 20px 20px;border-radius:50%;overflow:hidden}.clr-preview:after,.clr-preview:before{content:'';position:absolute;height:100%;width:100%;left:0;top:0;border:1px solid #fff;border-radius:50%}.clr-preview:after{border:0;background-color:currentColor;box-shadow:inset 0 0 0 1px rgba(0,0,0,.1)}.clr-preview button{position:absolute;width:100%;height:100%;z-index:1;margin:0;padding:0;border:0;border-radius:50%;outline-offset:-2px;background-color:transparent;text-indent:-9999px;cursor:pointer;overflow:hidden}.clr-alpha div,.clr-color,.clr-hue div,.clr-marker{box-sizing:border-box}.clr-field{display:inline-block;position:relative;color:transparent}.clr-field input{margin:0;direction:ltr}.clr-field.clr-rtl input{text-align:right}.clr-field button{position:absolute;width:30px;height:100%;right:0;top:50%;transform:translateY(-50%);margin:0;padding:0;border:0;color:inherit;text-indent:-1000px;white-space:nowrap;overflow:hidden;pointer-events:none}.clr-field.clr-rtl button{right:auto;left:0}.clr-field button:after{content:'';display:block;position:absolute;width:100%;height:100%;left:0;top:0;border-radius:inherit;background-color:currentColor;box-shadow:inset 0 0 1px rgba(0,0,0,.5)}.clr-alpha,.clr-alpha div,.clr-field button,.clr-preview:before,.clr-swatches button{background-image:repeating-linear-gradient(45deg,#aaa 25%,transparent 25%,transparent 75%,#aaa 75%,#aaa),repeating-linear-gradient(45deg,#aaa 25%,#fff 25%,#fff 75%,#aaa 75%,#aaa);background-position:0 0,4px 4px;background-size:8px 8px}.clr-marker:focus{outline:0}.clr-keyboard-nav .clr-alpha input:focus+div,.clr-keyboard-nav .clr-hue input:focus+div,.clr-keyboard-nav .clr-marker:focus,.clr-keyboard-nav .clr-segmented input:focus+label{outline:0;box-shadow:0 0 0 2px #1e90ff,0 0 2px 2px #fff}.clr-picker[data-alpha=false] .clr-alpha{display:none}.clr-picker[data-minimal=true]{padding-top:16px}.clr-picker[data-minimal=true] .clr-alpha,.clr-picker[data-minimal=true] .clr-color,.clr-picker[data-minimal=true] .clr-gradient,.clr-picker[data-minimal=true] .clr-hue,.clr-picker[data-minimal=true] .clr-preview{display:none}.clr-dark{background-color:#444}.clr-dark .clr-segmented{border-color:#777}.clr-dark .clr-swatches button:after{box-shadow:inset 0 0 0 1px rgba(255,255,255,.3)}.clr-dark input.clr-color{color:#fff;border-color:#777;background-color:#555}.clr-dark input.clr-color:focus{border-color:#1e90ff}.clr-dark .clr-preview:after{box-shadow:inset 0 0 0 1px rgba(255,255,255,.5)}.clr-dark .clr-alpha,.clr-dark .clr-alpha div,.clr-dark .clr-preview:before,.clr-dark .clr-swatches button{background-image:repeating-linear-gradient(45deg,#666 25%,transparent 25%,transparent 75%,#888 75%,#888),repeating-linear-gradient(45deg,#888 25%,#444 25%,#444 75%,#888 75%,#888)}.clr-picker.clr-polaroid{border-radius:6px;box-shadow:0 0 5px rgba(0,0,0,.1),0 5px 30px rgba(0,0,0,.2)}.clr-picker.clr-polaroid:before{content:'';display:block;position:absolute;width:16px;height:10px;left:20px;top:-10px;border:solid transparent;border-width:0 8px 10px 8px;border-bottom-color:currentColor;box-sizing:border-box;color:#fff;filter:drop-shadow(0 -4px 3px rgba(0,0,0,.1));pointer-events:none}.clr-picker.clr-polaroid.clr-dark:before{color:#444}.clr-picker.clr-polaroid.clr-left:before{left:auto;right:20px}.clr-picker.clr-polaroid.clr-top:before{top:auto;bottom:-10px;transform:rotateZ(180deg)}.clr-polaroid .clr-gradient{width:calc(100% - 20px);height:120px;margin:10px;border-radius:3px}.clr-polaroid .clr-alpha,.clr-polaroid .clr-hue{width:calc(100% - 30px);height:10px;margin:6px 15px;border-radius:5px}.clr-polaroid .clr-alpha div,.clr-polaroid .clr-hue div{box-shadow:0 0 5px rgba(0,0,0,.2)}.clr-polaroid .clr-format{width:calc(100% - 20px);margin:0 10px 15px}.clr-polaroid .clr-swatches{width:calc(100% - 12px);margin:0 6px}.clr-polaroid .clr-swatches div{padding-bottom:10px}.clr-polaroid .clr-swatches button{width:22px;height:22px}.clr-polaroid input.clr-color{width:calc(100% - 60px);margin:10px 10px 15px auto}.clr-polaroid .clr-clear{margin:0 10px 15px 10px}.clr-polaroid .clr-close{margin:0 10px 15px auto}.clr-polaroid .clr-preview{margin:10px 0 15px 10px}.clr-picker.clr-large{width:275px}.clr-large .clr-gradient{height:150px}.clr-large .clr-swatches button{width:22px;height:22px}.clr-picker.clr-pill{width:380px;padding-left:180px;box-sizing:border-box}.clr-pill .clr-gradient{position:absolute;width:180px;height:100%;left:0;top:0;margin-bottom:0;border-radius:3px 0 0 3px}.clr-pill .clr-hue{margin-top:20px} -------------------------------------------------------------------------------- /libraries/parseCssColor.js: -------------------------------------------------------------------------------- 1 | !function(e,r){"object"==typeof exports&&"undefined"!=typeof module?module.exports=r():"function"==typeof define&&define.amd?define(r):(e="undefined"!=typeof globalThis?globalThis:e||self).parseCssColor=r()}(this,(function(){"use strict";var e={aliceblue:[240,248,255],antiquewhite:[250,235,215],aqua:[0,255,255],aquamarine:[127,255,212],azure:[240,255,255],beige:[245,245,220],bisque:[255,228,196],black:[0,0,0],blanchedalmond:[255,235,205],blue:[0,0,255],blueviolet:[138,43,226],brown:[165,42,42],burlywood:[222,184,135],cadetblue:[95,158,160],chartreuse:[127,255,0],chocolate:[210,105,30],coral:[255,127,80],cornflowerblue:[100,149,237],cornsilk:[255,248,220],crimson:[220,20,60],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgoldenrod:[184,134,11],darkgray:[169,169,169],darkgreen:[0,100,0],darkgrey:[169,169,169],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkseagreen:[143,188,143],darkslateblue:[72,61,139],darkslategray:[47,79,79],darkslategrey:[47,79,79],darkturquoise:[0,206,209],darkviolet:[148,0,211],deeppink:[255,20,147],deepskyblue:[0,191,255],dimgray:[105,105,105],dimgrey:[105,105,105],dodgerblue:[30,144,255],firebrick:[178,34,34],floralwhite:[255,250,240],forestgreen:[34,139,34],fuchsia:[255,0,255],gainsboro:[220,220,220],ghostwhite:[248,248,255],gold:[255,215,0],goldenrod:[218,165,32],gray:[128,128,128],green:[0,128,0],greenyellow:[173,255,47],grey:[128,128,128],honeydew:[240,255,240],hotpink:[255,105,180],indianred:[205,92,92],indigo:[75,0,130],ivory:[255,255,240],khaki:[240,230,140],lavender:[230,230,250],lavenderblush:[255,240,245],lawngreen:[124,252,0],lemonchiffon:[255,250,205],lightblue:[173,216,230],lightcoral:[240,128,128],lightcyan:[224,255,255],lightgoldenrodyellow:[250,250,210],lightgray:[211,211,211],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightsalmon:[255,160,122],lightseagreen:[32,178,170],lightskyblue:[135,206,250],lightslategray:[119,136,153],lightslategrey:[119,136,153],lightsteelblue:[176,196,222],lightyellow:[255,255,224],lime:[0,255,0],limegreen:[50,205,50],linen:[250,240,230],magenta:[255,0,255],maroon:[128,0,0],mediumaquamarine:[102,205,170],mediumblue:[0,0,205],mediumorchid:[186,85,211],mediumpurple:[147,112,219],mediumseagreen:[60,179,113],mediumslateblue:[123,104,238],mediumspringgreen:[0,250,154],mediumturquoise:[72,209,204],mediumvioletred:[199,21,133],midnightblue:[25,25,112],mintcream:[245,255,250],mistyrose:[255,228,225],moccasin:[255,228,181],navajowhite:[255,222,173],navy:[0,0,128],oldlace:[253,245,230],olive:[128,128,0],olivedrab:[107,142,35],orange:[255,165,0],orangered:[255,69,0],orchid:[218,112,214],palegoldenrod:[238,232,170],palegreen:[152,251,152],paleturquoise:[175,238,238],palevioletred:[219,112,147],papayawhip:[255,239,213],peachpuff:[255,218,185],peru:[205,133,63],pink:[255,192,203],plum:[221,160,221],powderblue:[176,224,230],purple:[128,0,128],rebeccapurple:[102,51,153],red:[255,0,0],rosybrown:[188,143,143],royalblue:[65,105,225],saddlebrown:[139,69,19],salmon:[250,128,114],sandybrown:[244,164,96],seagreen:[46,139,87],seashell:[255,245,238],sienna:[160,82,45],silver:[192,192,192],skyblue:[135,206,235],slateblue:[106,90,205],slategray:[112,128,144],slategrey:[112,128,144],snow:[255,250,250],springgreen:[0,255,127],steelblue:[70,130,180],tan:[210,180,140],teal:[0,128,128],thistle:[216,191,216],tomato:[255,99,71],turquoise:[64,224,208],violet:[238,130,238],wheat:[245,222,179],white:[255,255,255],whitesmoke:[245,245,245],yellow:[255,255,0],yellowgreen:[154,205,50]},r=new RegExp(/^#([a-f0-9]{3,4}|[a-f0-9]{4}(?:[a-f0-9]{2}){1,2})\b$/,"i"),n="-?\\d*(?:\\.\\d+)",a="("+n+"?)",l="("+n+"?%)",t=("^\n hsla?\\(\n \\s*(-?\\d*(?:\\.\\d+)?(?:deg|rad|turn)?)\\s*,\n \\s*"+l+"\\s*,\n \\s*"+l+"\\s*\n (?:,\\s*"+"(-?\\d*(?:\\.\\d+)?%?)\\s*)?\n \\)\n $\n").replace(/\n|\s/g,""),s=new RegExp(t),i=("^\n hsla?\\(\n \\s*(-?\\d*(?:\\.\\d+)?(?:deg|rad|turn)?)\\s*\n \\s+"+l+"\n \\s+"+l+"\n \\s*(?:\\s*\\/\\s*"+"(-?\\d*(?:\\.\\d+)?%?)\\s*)?\n \\)\n $\n").replace(/\n|\s/g,""),o=new RegExp(i),d=("^\n rgba?\\(\n \\s*"+a+"\\s*,\n \\s*"+a+"\\s*,\n \\s*"+a+"\\s*\n (?:,\\s*"+"(-?\\d*(?:\\.\\d+)?%?)\\s*)?\n \\)\n $\n").replace(/\n|\s/g,""),u=new RegExp(d),g=("^\n rgba?\\(\n \\s*"+l+"\\s*,\n \\s*"+l+"\\s*,\n \\s*"+l+"\\s*\n (?:,\\s*"+"(-?\\d*(?:\\.\\d+)?%?)\\s*)?\n \\)\n $\n").replace(/\n|\s/g,""),p=new RegExp(g),h=("^\n rgba?\\(\n \\s*"+a+"\n \\s+"+a+"\n \\s+"+a+"\n \\s*(?:\\s*\\/\\s*"+"(-?\\d*(?:\\.\\d+)?%?)\\s*)?\n \\)\n$\n").replace(/\n|\s/g,""),c=new RegExp(h),m=("^\n rgba?\\(\n \\s*"+l+"\n \\s+"+l+"\n \\s+"+l+"\n \\s*(?:\\s*\\/\\s*"+"(-?\\d*(?:\\.\\d+)?%?)\\s*)?\n \\)\n$\n").replace(/\n|\s/g,""),f=new RegExp(m),b=new RegExp(/^transparent$/,"i"),y=new RegExp("[^#a-f\\d]","gi"),w=new RegExp("^#?[a-f\\d]{3}[a-f\\d]?$|^#?[a-f\\d]{6}([a-f\\d]{2})?$","i"),k=function(e,r,n){return Math.min(Math.max(r,e),n)},v=function(e){var r=e;return"number"!=typeof r&&(r=r.endsWith("%")?255*parseFloat(r)/100:parseFloat(r)),k(Math.round(r),0,255)},x=function(e){return k(parseFloat(e),0,100)};function E(e){var r=e;return"number"!=typeof r&&(r=r.endsWith("%")?parseFloat(r)/100:parseFloat(r)),k(r,0,1)}function R(e){var r=function(e,r){if(void 0===r&&(r={}),"string"!=typeof e||y.test(e)||!w.test(e))throw new TypeError("Expected a valid hex string");var n=1;8===(e=e.replace(/^#/,"")).length&&(n=Number.parseInt(e.slice(6,8),16)/255,e=e.slice(0,6)),4===e.length&&(n=Number.parseInt(e.slice(3,4).repeat(2),16)/255,e=e.slice(0,3)),3===e.length&&(e=e[0]+e[0]+e[1]+e[1]+e[2]+e[2]);var a=Number.parseInt(e,16),l=a>>16,t=a>>8&255,s=255&a,i="number"==typeof r.alpha?r.alpha:n;return"array"===r.format?[l,t,s,i]:"css"===r.format?"rgb("+l+" "+t+" "+s+(1===i?"":" / "+Number((100*i).toFixed(2))+"%")+")":{red:l,green:t,blue:s,alpha:i}}(e,{format:"array"});return $([null,r[0],r[1],r[2],r[3]])}function $(e){var r=e[1],n=e[2],a=e[3],l=e[4];return void 0===l&&(l=1),{type:"rgb",values:[r,n,a].map(v),alpha:E(null===l?1:l)}} 2 | /** 3 | * parse-css-color 4 | * @version v0.2.1 5 | * @link http://github.com/noeldelgado/parse-css-color/ 6 | * @license MIT 7 | */return function(n){if("string"!=typeof n)return null;var a=r.exec(n);if(a)return R(a[0]);var l=o.exec(n)||s.exec(n);if(l)return function(e){var r=e[1],n=e[2],a=e[3],l=e[4];void 0===l&&(l=1);var t=r;return{type:"hsl",values:[t=t.endsWith("turn")?360*parseFloat(t)/1:t.endsWith("rad")?Math.round(180*parseFloat(t)/Math.PI):parseFloat(t),x(n),x(a)],alpha:E(null===l?1:l)}}(l);var t=c.exec(n)||f.exec(n)||u.exec(n)||p.exec(n);if(t)return $(t);if(b.exec(n))return $([null,0,0,0,0]);var i=e[n.toLowerCase()];return i?$([null,i[0],i[1],i[2],1]):null}})); 8 | -------------------------------------------------------------------------------- /libraries/tinytoast.js: -------------------------------------------------------------------------------- 1 | /*! tiny.toast 2014-11-04 */ 2 | !function(e,t){function o(e){return e?"object"==typeof Node?e instanceof Node:e&&"object"==typeof e&&"number"==typeof e.nodeType&&"string"==typeof e.nodeName:!1}function a(e){return e===t.body?!1:t.body.contains(e)}function n(e,t){for(var o=0,a=e.length;a>o;o++)t(e[o],o,e)}function i(e){function t(){a(e)&&e.parentElement.removeChild(e)}h?(e.addEventListener("webkitAnimationEnd",t,!1),e.addEventListener("animationend",t,!1),e.className+=" t-exit"):t(e)}function r(e){e=[].slice.call(e);var t;return n(e,function(e){t||"object"!=typeof e||(t=e)}),t=t||{},t.msg=t.msg||t.message,t.group=t.action?!1:void 0!==t.group?!!t.group:!!y.group,"string"==typeof e[0]&&(t.msg=e[0]),"number"==typeof e[1]&&(t.timeout=e[1]),t}function c(e,a){if(e instanceof Array)return a=a||t.createElement("span"),n(e,function(e){a.appendChild(c(e,a))}),a;if(e.dom&&o(e.dom))return e.dom;if(e.name&&e.onclick){var i=t.createElement("span"),r=t.createTextNode(e.name);return i.onclick=e.onclick,i.appendChild(r),i.className="t-action",i}return t.createTextNode("")}function m(e){var o={},a=function(a){var n=this;n.count=0,n.dom=t.createElement("div"),n.textNode=t.createTextNode(""),n.autoRemove=void 0,n.remove=function(){clearTimeout(n.autoRemove),delete o[a],i(n.dom)},n.dom.appendChild(n.textNode),n.dom.removeToast=n.remove,n.dom.className=e+" t-toast",g.appendChild(n.dom),!!y.group&&(o[a]=n)};return function(){var e=r(arguments),t=e.msg||"",n=e.dismissible,i="function"==typeof e.onclick?e.onclick:!1,m=n===!1?-1:e.timeout,d=e.group&&o[t]?o[t]:new a(t),s=d.dom,u=d&&++d.count>1?t+" (x"+d.count+")":t;return d.textNode.nodeValue=u,e.action&&"object"==typeof e.action&&s.appendChild(c(e.action)),-1!==m&&(clearTimeout(d.autoRemove),d.autoRemove=setTimeout(function(){d.remove()},+m||+y.timeout||5e3)),s.className+=n!==!1||i?" t-click":"",s.onclick=function(){i&&i({message:t,count:d.count}),n!==!1&&d.remove()},d.remove}}function d(){n(g.children,function(e){e.removeToast?e.removeToast():i(e)})}var s=t.head||t.getElementsByTagName("head")[0],u=t.createElement("style");u.type="text/css";var l=".t-wrap{position:fixed;bottom:0;text-align:center;font-family:sans-serif;width:100%}@media (min-width:0){.t-wrap{width:auto;display:inline-block;left:50%;-webkit-transform:translate(-50%,0);-ms-transform:translate(-50%,0);transform:translate(-50%,0);transform:translate3d(-50%,0,0);transform-style:preserve-3d}}.t-toast{width:16em;margin:.6em auto;padding:.5em .3em;border-radius:2em;box-shadow:0 4px 0 -1px rgba(0,0,0,.2);color:#eee;cursor:default;overflow-y:hidden;will-change:opacity,height,margin;-webkit-animation:t-enter 500ms ease-out;animation:t-enter 500ms ease-out;transform-style:preserve-3d}.t-toast.t-gray{background:#777;background:rgba(119,119,119,.9)}.t-toast.t-red{background:#D85955;background:rgba(216,89,85,.9)}.t-toast.t-blue{background:#4374AD;background:rgba(67,116,173,.9)}.t-toast.t-green{background:#75AD44;background:rgba(117,173,68,.9)}.t-toast.t-orange{background:#D89B55;background:rgba(216,133,73,.9)}.t-toast.t-white{background:#FAFAFA;background:rgba(255,255,255,.9);color:#777}.t-action,.t-click{cursor:pointer}.t-action{font-weight:700;text-decoration:underline;margin-left:.5em;display:inline-block}.t-toast.t-exit{-webkit-animation:t-exit 500ms ease-in;animation:t-exit 500ms ease-in}@-webkit-keyframes t-enter{from{opacity:0;max-height:0}to{opacity:1;max-height:2em}}@keyframes t-enter{from{opacity:0;max-height:0}to{opacity:1;max-height:2em}}@-webkit-keyframes t-exit{from{opacity:1;max-height:2em}to{opacity:0;max-height:0}}@keyframes t-exit{from{opacity:1;max-height:2em}to{opacity:0;max-height:0}}@media screen and (max-width:17em){.t-toast{width:90%}}";u.styleSheet?u.styleSheet.cssText=l:u.appendChild(t.createTextNode(l)),s.appendChild(u);var g=t.createElement("div");g.className="t-wrap";var f=function(){t.body.appendChild(g)},p=function(){"complete"===t.readyState&&f()};t.addEventListener?t.addEventListener("readystatechange",p,!1):t.attachEvent("onreadystatechange",p);var h=function(e){return"animation"in e||"-webkit-animation"in e}(g.style),y=e.toastr=e.toast={timeout:4e3,group:!0,clear:d,log:m("t-gray"),alert:m("t-white"),error:m("t-red"),info:m("t-blue"),success:m("t-green"),warning:m("t-orange")}}(window,document); -------------------------------------------------------------------------------- /libraries/viewer.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Viewer.js v1.10.5 3 | * https://fengyuanchen.github.io/viewerjs 4 | * 5 | * Copyright 2015-present Chen Fengyuan 6 | * Released under the MIT license 7 | * 8 | * Date: 2022-04-05T08:21:00.150Z 9 | */.viewer-close:before,.viewer-flip-horizontal:before,.viewer-flip-vertical:before,.viewer-fullscreen-exit:before,.viewer-fullscreen:before,.viewer-next:before,.viewer-one-to-one:before,.viewer-play:before,.viewer-prev:before,.viewer-reset:before,.viewer-rotate-left:before,.viewer-rotate-right:before,.viewer-zoom-in:before,.viewer-zoom-out:before{background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAARgAAAAUCAYAAABWOyJDAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNui8sowAAAQPSURBVHic7Zs/iFxVFMa/0U2UaJGksUgnIVhYxVhpjDbZCBmLdAYECxsRFBTUamcXUiSNncgKQbSxsxH8gzAP3FU2jY0kKKJNiiiIghFlccnP4p3nPCdv3p9778vsLOcHB2bfveeb7955c3jvvNkBIMdxnD64a94GHMfZu3iBcRynN7zAOI7TG15gHCeeNUkr8zaxG2lbYDYsdgMbktBsP03jdQwljSXdtBhLOmtjowC9Mg9L+knSlcD8TNKpSA9lBpK2JF2VdDSR5n5J64m0qli399hNFMUlpshQii5jbXTbHGviB0nLNeNDSd9VO4A2UdB2fp+x0eCnaXxWXGA2X0au/3HgN9P4LFCjIANOJdrLr0zzZ+BEpNYDwKbpnQMeAw4m8HjQtM6Z9qa917zPQwFr3M5KgA6J5rTJCdFZJj9/lyvGhsDvwFNVuV2MhhjrK6b9bFiE+j1r87eBl4HDwCF7/U/k+ofAX5b/EXBv5JoLMuILzf3Ap6Z3EzgdqHMCuF7hcQf4HDgeoHnccncqdK/TvSDWffFXI/exICY/xZyqc6XLWF1UFZna4gJ7q8BsRvgd2/xXpo6P+D9dfT7PpECtA3cnWPM0GXGFZh/wgWltA+cDNC7X+AP4GzjZQe+k5dRxuYPeiuXU7e1qwLpDz7dFjXKRaSwuMLvAlG8zZlG+YmiK1HoFqT7wP2z+4Q45TfEGcMt01xLoNZEBTwRqD4BLpnMLeC1A41UmVxsXgXeBayV/Wx20rpTyrpnWRft7p6O/FdqzGrDukPNtkaMoMo3FBdBSQMOnYBCReyf05s126fU9ytfX98+mY54Kxnp7S9K3kj6U9KYdG0h6UdLbkh7poFXMfUnSOyVvL0h6VtIXHbS6nOP+s/Zm9mvyXW1uuC9ohZ72E9uDmXWLJOB1GxsH+DxPftsB8B6wlGDN02TAkxG6+4D3TWsbeC5CS8CDFce+AW500LhhOW2020TRjK3b21HEmgti9m0RonxbdMZeVzV+/4tF3cBpP7E9mKHNL5q8h5g0eYsCMQz0epq8gQrwMXAgcs0FGXGFRcB9wCemF9PkbYqM/Bas7fxLwNeJPdTdpo4itQti8lPMqTpXuozVRVXPpbHI3KkNTB1NfkL81j2mvhDp91HgV9MKuRIqrykj3WPq4rHyL+axj8/qGPmTqi6F9YDlHOvJU6oYcTsh/TYSzWmTE6JT19CtLTJt32D6CmHe0eQn1O8z5AXgT4sx4Vcu0/EQecMydB8z0hUWkTd2t4CrwNEePqMBcAR4mrBbwyXLPWJa8zrXmmLEhNBmfpkuY2102xxrih+pb+ieAb6vGhuA97UcJ5KR8gZ77K+99xxeYBzH6Q3/Z0fHcXrDC4zjOL3hBcZxnN74F+zlvXFWXF9PAAAAAElFTkSuQmCC");background-repeat:no-repeat;background-size:280px;color:transparent;display:block;font-size:0;height:20px;line-height:0;width:20px}.viewer-zoom-in:before{background-position:0 0;content:"Zoom In"}.viewer-zoom-out:before{background-position:-20px 0;content:"Zoom Out"}.viewer-one-to-one:before{background-position:-40px 0;content:"One to One"}.viewer-reset:before{background-position:-60px 0;content:"Reset"}.viewer-prev:before{background-position:-80px 0;content:"Previous"}.viewer-play:before{background-position:-100px 0;content:"Play"}.viewer-next:before{background-position:-120px 0;content:"Next"}.viewer-rotate-left:before{background-position:-140px 0;content:"Rotate Left"}.viewer-rotate-right:before{background-position:-160px 0;content:"Rotate Right"}.viewer-flip-horizontal:before{background-position:-180px 0;content:"Flip Horizontal"}.viewer-flip-vertical:before{background-position:-200px 0;content:"Flip Vertical"}.viewer-fullscreen:before{background-position:-220px 0;content:"Enter Full Screen"}.viewer-fullscreen-exit:before{background-position:-240px 0;content:"Exit Full Screen"}.viewer-close:before{background-position:-260px 0;content:"Close"}.viewer-container{-webkit-tap-highlight-color:transparent;-webkit-touch-callout:none;bottom:0;direction:ltr;font-size:0;left:0;line-height:0;overflow:hidden;position:absolute;right:0;top:0;-ms-touch-action:none;touch-action:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.viewer-container ::-moz-selection,.viewer-container::-moz-selection{background-color:transparent}.viewer-container ::selection,.viewer-container::selection{background-color:transparent}.viewer-container:focus{outline:0}.viewer-container img{display:block;height:auto;max-height:none!important;max-width:none!important;min-height:0!important;min-width:0!important;width:100%}.viewer-canvas{bottom:0;left:0;overflow:hidden;position:absolute;right:0;top:0}.viewer-canvas>img{height:auto;margin:15px auto;max-width:90%!important;width:auto}.viewer-footer{bottom:0;left:0;overflow:hidden;position:absolute;right:0;text-align:center}.viewer-navbar{background-color:rgba(0,0,0,.5);overflow:hidden}.viewer-list{box-sizing:content-box;height:50px;margin:0;overflow:hidden;padding:1px 0}.viewer-list>li{color:transparent;cursor:pointer;float:left;font-size:0;height:50px;line-height:0;opacity:.5;overflow:hidden;transition:opacity .15s;width:30px}.viewer-list>li:focus,.viewer-list>li:hover{opacity:.75}.viewer-list>li:focus{outline:0}.viewer-list>li+li{margin-left:1px}.viewer-list>.viewer-loading{position:relative}.viewer-list>.viewer-loading:after{border-width:2px;height:20px;margin-left:-10px;margin-top:-10px;width:20px}.viewer-list>.viewer-active,.viewer-list>.viewer-active:focus,.viewer-list>.viewer-active:hover{opacity:1}.viewer-player{background-color:var(--default-text-color);bottom:0;cursor:none;display:none;right:0;z-index:1}.viewer-player,.viewer-player>img{left:0;position:absolute;top:0}.viewer-toolbar>ul{display:inline-block;margin:0 auto 5px;overflow:hidden;padding:6px 3px}.viewer-toolbar>ul>li{background-color:rgba(0,0,0,.5);border-radius:50%;cursor:pointer;float:left;height:24px;overflow:hidden;transition:background-color .15s;width:24px}.viewer-toolbar>ul>li:focus,.viewer-toolbar>ul>li:hover{background-color:rgba(0,0,0,.8)}.viewer-toolbar>ul>li:focus{box-shadow:0 0 3px #fff;outline:0;position:relative;z-index:1}.viewer-toolbar>ul>li:before{margin:2px}.viewer-toolbar>ul>li+li{margin-left:1px}.viewer-toolbar>ul>.viewer-small{height:18px;margin-bottom:3px;margin-top:3px;width:18px}.viewer-toolbar>ul>.viewer-small:before{margin:-1px}.viewer-toolbar>ul>.viewer-large{height:30px;margin-bottom:-3px;margin-top:-3px;width:30px}.viewer-toolbar>ul>.viewer-large:before{margin:5px}.viewer-tooltip{background-color:rgba(0,0,0,.8);border-radius:10px;color:#fff;display:none;font-size:12px;height:20px;left:50%;line-height:20px;margin-left:-25px;margin-top:-10px;position:absolute;text-align:center;top:50%;width:50px}.viewer-title{color:#ccc;display:inline-block;font-size:12px;line-height:1.2;margin:0 5% 5px;max-width:90%;opacity:.8;overflow:hidden;text-overflow:ellipsis;transition:opacity .15s;white-space:nowrap}.viewer-title:hover{opacity:1}.viewer-button{-webkit-app-region:no-drag;background-color:rgba(0,0,0,.5);border-radius:50%;cursor:pointer;height:80px;overflow:hidden;position:absolute;right:-40px;top:-40px;transition:background-color .15s;width:80px}.viewer-button:focus,.viewer-button:hover{background-color:rgba(0,0,0,.8)}.viewer-button:focus{box-shadow:0 0 3px #fff;outline:0}.viewer-button:before{bottom:15px;left:15px;position:absolute}.viewer-fixed{position:fixed}.viewer-open{overflow:hidden}.viewer-show{display:block}.viewer-hide{display:none}.viewer-backdrop{background-color:rgba(0,0,0,.5)}.viewer-invisible{visibility:hidden}.viewer-move{cursor:move;cursor:-webkit-grab;cursor:grab}.viewer-fade{opacity:0}.viewer-in{opacity:1}.viewer-transition{transition:all .3s}@-webkit-keyframes viewer-spinner{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}@keyframes viewer-spinner{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}.viewer-loading:after{-webkit-animation:viewer-spinner 1s linear infinite;animation:viewer-spinner 1s linear infinite;border:4px solid hsla(0,0%,100%,.1);border-left-color:hsla(0,0%,100%,.5);border-radius:50%;content:"";display:inline-block;height:40px;left:50%;margin-left:-20px;margin-top:-20px;position:absolute;top:50%;width:40px;z-index:1}@media (max-width:767px){.viewer-hide-xs-down{display:none}}@media (max-width:991px){.viewer-hide-sm-down{display:none}}@media (max-width:1199px){.viewer-hide-md-down{display:none}} -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Old Twitter Layout (2025)", 3 | "description": "__MSG_ext_description__", 4 | "version": "1.8.9.12", 5 | "manifest_version": 3, 6 | "homepage_url": "https://github.com/dimdenGD/OldTwitter", 7 | "background": { 8 | "service_worker": "scripts/background.js" 9 | }, 10 | "default_locale": "en", 11 | "permissions": [ 12 | "storage", 13 | "declarativeNetRequest", 14 | "contextMenus", 15 | "scripting", 16 | "unlimitedStorage" 17 | ], 18 | "host_permissions": [ 19 | "*://*.twitter.com/*", 20 | "*://twitter.com/*", 21 | "*://twimg.com/*", 22 | "*://*.twimg.com/*", 23 | "*://x.com/*", 24 | "*://*.x.com/*" 25 | ], 26 | "sandbox": { 27 | "pages": ["sandbox.html"] 28 | }, 29 | 30 | "declarative_net_request": { 31 | "rule_resources" : [{ 32 | "id": "ruleset", 33 | "enabled": true, 34 | "path": "ruleset.json" 35 | }] 36 | }, 37 | "web_accessible_resources": [ 38 | { 39 | "resources": [ 40 | "layouts/*", 41 | "images/*", 42 | "fonts/*", 43 | "libraries/*", 44 | "_locales/*", 45 | "sandbox.html" 46 | ], 47 | "matches": [ 48 | "*://*.twitter.com/*", 49 | "*://*.x.com/*" 50 | ] 51 | } 52 | ], 53 | "icons": { 54 | "16": "/images/logo16.png", 55 | "32": "/images/logo32.png", 56 | "48": "/images/logo48.png", 57 | "128": "/images/logo128.png" 58 | }, 59 | "action": { 60 | "default_icon": { 61 | "16": "/images/logo16.png", 62 | "32": "/images/logo32.png", 63 | "48": "/images/logo48.png", 64 | "128": "/images/logo128.png" 65 | }, 66 | "default_title": "Open settings" 67 | }, 68 | "content_scripts": [ 69 | { 70 | "matches": ["https://twitter.com/*?*newtwitter=true*", "https://x.com/*?*newtwitter=true*"], 71 | "js": ["scripts/xIconRemove.js"], 72 | "all_frames": true, 73 | "run_at": "document_start" 74 | }, 75 | { 76 | "matches": ["https://twitter.com/*?*newtwitter=true*", "https://x.com/*?*newtwitter=true*"], 77 | "js": ["scripts/newtwitter.js"], 78 | "all_frames": true, 79 | "run_at": "document_end" 80 | }, 81 | { 82 | "matches": ["https://twitter.com/*", "https://x.com/*"], 83 | "exclude_matches": [ 84 | "https://twitter.com/*?*newtwitter=true*", "https://twitter.com/settings/download_your_data", 85 | "https://twitter.com/i/flow/login*", "https://twitter.com/i/tweetdeck", "https://twitter.com/i/communitynotes", 86 | "https://twitter.com/i/broadcasts/*", "https://twitter.com/search-advanced", "https://twitter.com/x/migrate", 87 | 88 | "https://x.com/*?*newtwitter=true*", "https://x.com/settings/download_your_data", "https://x.com/i/flow/login*", 89 | "https://x.com/i/tweetdeck", "https://x.com/i/communitynotes", "https://x.com/i/broadcasts/*", 90 | "https://x.com/search-advanced", "https://x.com/x/migrate", "https://x.com/i/grok" 91 | ], 92 | "js": ["scripts/blockBeforeInject.js", "scripts/config.js", "scripts/helpers.js", "scripts/apis.js", "scripts/twchallenge.js", "scripts/injection.js"], 93 | "css": ["libraries/viewer.min.css"], 94 | "all_frames": true, 95 | "run_at": "document_start" 96 | } 97 | ] 98 | } -------------------------------------------------------------------------------- /pack.js: -------------------------------------------------------------------------------- 1 | // This script generates Firefox version of the extension and packs Chrome and Firefox versions to zip files. 2 | 3 | const fsp = require('fs').promises; 4 | const fs = require('fs'); 5 | const path = require('path'); 6 | const AdmZip = require('adm-zip'); 7 | const args = process.argv.slice(2); 8 | 9 | async function copyDir(src, dest) { 10 | const entries = await fsp.readdir(src, { withFileTypes: true }); 11 | await fsp.mkdir(dest); 12 | for (let entry of entries) { 13 | if(entry.name === '.git' || entry.name === '.github' || entry.name === '_metadata' || entry.name === 'node_modules') continue; 14 | const srcPath = path.join(src, entry.name); 15 | const destPath = path.join(dest, entry.name); 16 | if (entry.isDirectory()) { 17 | await copyDir(srcPath, destPath); 18 | } else { 19 | await fsp.copyFile(srcPath, destPath); 20 | } 21 | } 22 | } 23 | 24 | if(fs.existsSync('../OldTwitterTempChrome')) { 25 | fs.rmSync('../OldTwitterTempChrome', { recursive: true }); 26 | } 27 | if(fs.existsSync('../OldTwitterFirefox')) { 28 | fs.rmSync('../OldTwitterFirefox', { recursive: true }); 29 | } 30 | 31 | console.log("Copying..."); 32 | copyDir('./', '../OldTwitterFirefox').then(async () => { 33 | await copyDir('./', '../OldTwitterTempChrome'); 34 | console.log("Copied!"); 35 | console.log("Patching..."); 36 | let manifest = JSON.parse(fs.readFileSync('../OldTwitterFirefox/manifest.json', 'utf8')); 37 | manifest.manifest_version = 2; 38 | manifest.background.scripts = ['scripts/background.js']; 39 | manifest.web_accessible_resources = manifest.web_accessible_resources[0].resources; 40 | manifest.permissions = manifest.permissions.filter(p => p !== 'declarativeNetRequest' && p !== 'contextMenus'); 41 | manifest.browser_specific_settings = { 42 | "gecko": { 43 | "strict_min_version": "78.0" 44 | }, 45 | "gecko_android": { 46 | "strict_min_version": "78.0" 47 | } 48 | } 49 | if(args[0] === '-a') { 50 | manifest.browser_specific_settings.gecko.id = "oldtwitter@dimden.dev"; 51 | } else { 52 | setTimeout(() => console.warn("Warning: Extension ID is not set."), 1500); 53 | } 54 | manifest.permissions = [ 55 | ...manifest.permissions, 56 | ...manifest.host_permissions, 57 | "https://dimden.dev/*", 58 | "https://raw.githubusercontent.com/*", 59 | "webRequest", 60 | "webRequestBlocking" 61 | ]; 62 | delete manifest.sandbox; 63 | delete manifest.host_permissions; 64 | delete manifest.declarative_net_request; 65 | delete manifest.background.service_worker; 66 | delete manifest.action; 67 | let config = fs.readFileSync('../OldTwitterFirefox/scripts/config.js', 'utf8'); 68 | config = config.replace(/chrome\.storage\.sync\./g, "chrome.storage.local."); 69 | let helpers = fs.readFileSync('../OldTwitterFirefox/scripts/helpers.js', 'utf8'); 70 | helpers = helpers.replace(/chrome\.storage\.sync\./g, "chrome.storage.local."); 71 | let tweetviewer = fs.readFileSync('../OldTwitterFirefox/scripts/tweetviewer.js', 'utf8'); 72 | tweetviewer = tweetviewer.replace(/chrome\.storage\.sync\./g, "chrome.storage.local."); 73 | let content = fs.readFileSync('../OldTwitterFirefox/scripts/injection.js', 'utf8'); 74 | content = content.replace(/chrome\.storage\.sync\./g, "chrome.storage.local."); 75 | 76 | let apis = fs.readFileSync('../OldTwitterFirefox/scripts/apis.js', 'utf8'); 77 | apis = apis.replace(/chrome\.storage\.sync\./g, "chrome.storage.local."); 78 | if(apis.includes("&& true") || apis.includes("&& false") || apis.includes("|| true") || apis.includes("|| false") || apis.includes("&&true") || apis.includes("&&false") || apis.includes("||true") || apis.includes("||false")) { 79 | if(args[0] === '-a') { 80 | let line = apis.split("\n").findIndex(l => l.includes("&& true") || l.includes("&& false") || l.includes("|| true") || l.includes("|| false") || l.includes("&&true") || l.includes("&&false") || l.includes("||true") || l.includes("||false")); 81 | console.warn("::warning file=scripts/api.js,line=" + (line+1) + "::Probably temporary boolean left in code."); 82 | } else { 83 | for(let i = 0; i < 5; i++) { 84 | console.warn("\x1b[33m", "Warning: probably temporary boolean left in code.", '\x1b[0m'); 85 | } 86 | } 87 | } 88 | if(args[0] !== '-a') { 89 | try { 90 | require('node-fetch')('https://twitter.com/manifest.json').then(res => res.text()).then(json => { 91 | if(json.includes("content_security_policy")) { 92 | for(let i = 0; i < 5; i++) { 93 | console.warn("\x1b[33m", "Warning: Twitter returned CSP in manifest.json!!!!", '\x1b[0m'); 94 | } 95 | } 96 | }); 97 | } catch(e) {} 98 | } 99 | 100 | let background = fs.readFileSync('../OldTwitterFirefox/scripts/background_v2.js', 'utf8'); 101 | 102 | fs.writeFileSync('../OldTwitterFirefox/manifest.json', JSON.stringify(manifest, null, 2)); 103 | fs.writeFileSync('../OldTwitterFirefox/scripts/injection.js', content); 104 | fs.writeFileSync('../OldTwitterFirefox/scripts/helpers.js', helpers); 105 | fs.writeFileSync('../OldTwitterFirefox/scripts/tweetviewer.js', tweetviewer); 106 | fs.writeFileSync('../OldTwitterFirefox/scripts/config.js', config); 107 | fs.writeFileSync('../OldTwitterFirefox/scripts/background.js', background); 108 | fs.writeFileSync('../OldTwitterFirefox/scripts/apis.js', apis); 109 | fs.unlinkSync('../OldTwitterFirefox/ruleset.json'); 110 | fs.unlinkSync('../OldTwitterFirefox/pack.js'); 111 | fs.unlinkSync('../OldTwitterTempChrome/pack.js'); 112 | fs.unlinkSync('../OldTwitterTempChrome/scripts/background_v2.js'); 113 | fs.unlinkSync('../OldTwitterFirefox/scripts/background_v2.js'); 114 | fs.unlinkSync('../OldTwitterFirefox/.gitignore'); 115 | fs.unlinkSync('../OldTwitterTempChrome/.gitignore'); 116 | fs.unlinkSync('../OldTwitterFirefox/test.js'); 117 | fs.unlinkSync('../OldTwitterTempChrome/test.js'); 118 | fs.unlinkSync('../OldTwitterFirefox/package.json'); 119 | fs.unlinkSync('../OldTwitterTempChrome/package.json'); 120 | fs.unlinkSync('../OldTwitterFirefox/_locales/locales-support.html'); 121 | fs.unlinkSync('../OldTwitterTempChrome/_locales/locales-support.html'); 122 | 123 | if (fs.existsSync('../OldTwitterFirefox/package-lock.json')) // Delete NPM package-lock (if exists) 124 | fs.unlinkSync('../OldTwitterFirefox/package-lock.json'); 125 | if (fs.existsSync('../OldTwitterTempChrome/package-lock.json')) 126 | fs.unlinkSync('../OldTwitterTempChrome/package-lock.json'); 127 | 128 | if (fs.existsSync('../OldTwitterFirefox/yarn.lock')) // Delete yarn package-lock (if exists) 129 | fs.unlinkSync('../OldTwitterFirefox/yarn.lock'); 130 | if (fs.existsSync('../OldTwitterTempChrome/yarn.lock')) 131 | fs.unlinkSync('../OldTwitterTempChrome/yarn.lock'); 132 | 133 | let layouts = fs.readdirSync('../OldTwitterFirefox/layouts'); 134 | for (let layout of layouts) { 135 | let script = fs.readFileSync(`../OldTwitterFirefox/layouts/${layout}/script.js`, 'utf8'); 136 | script = script.replace(/chrome\.storage\.sync\./g, "chrome.storage.local."); 137 | script = script.replace("https://chrome.google.com/webstore/detail/old-twitter-layout-2022/jgejdcdoeeabklepnkdbglgccjpdgpmf", "https://addons.mozilla.org/en-US/firefox/addon/old-twitter-layout-2022/"); 138 | fs.writeFileSync(`../OldTwitterFirefox/layouts/${layout}/script.js`, script); 139 | 140 | let style = fs.readFileSync(`../OldTwitterFirefox/layouts/${layout}/style.css`, 'utf8'); 141 | style = style.replaceAll("chrome-extension://", "moz-extension://"); 142 | fs.writeFileSync(`../OldTwitterFirefox/layouts/${layout}/style.css`, style); 143 | 144 | let html = fs.readFileSync(`../OldTwitterFirefox/layouts/${layout}/index.html`, 'utf8'); 145 | html = html.replaceAll("chrome-extension://", "moz-extension://"); 146 | fs.writeFileSync(`../OldTwitterFirefox/layouts/${layout}/index.html`, html); 147 | } 148 | 149 | console.log("Patched!"); 150 | if (fs.existsSync('../OldTwitterFirefox.zip')) { 151 | console.log("Deleting old zip..."); 152 | fs.unlinkSync('../OldTwitterFirefox.zip'); 153 | console.log("Deleted old zip!"); 154 | } 155 | console.log("Zipping Firefox version..."); 156 | try { 157 | const zip = new AdmZip(); 158 | const outputDir = "../OldTwitterFirefox.zip"; 159 | zip.addLocalFolder("../OldTwitterFirefox"); 160 | zip.writeZip(outputDir); 161 | } catch (e) { 162 | console.log(`Something went wrong ${e}`); 163 | } 164 | console.log(`Zipped Firefox version into ${path.resolve('../OldTwitterFirefox.zip')}!`); 165 | console.log("Zipping Chrome version..."); 166 | try { 167 | const zip = new AdmZip(); 168 | const outputDir = "../OldTwitterChrome.zip"; 169 | zip.addLocalFolder("../OldTwitterTempChrome"); 170 | zip.writeZip(outputDir); 171 | } catch (e) { 172 | console.log(`Something went wrong ${e}`); 173 | } 174 | console.log(`Zipped Chrome version into ${path.resolve('../OldTwitterChrome.zip')}!`); 175 | console.log("Deleting temporary folders..."); 176 | fs.rmSync('../OldTwitterTempChrome', { recursive: true }); 177 | fs.rmSync('../OldTwitterFirefox', { recursive: true }); 178 | console.log("Deleted!"); 179 | }); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "oldtwitter", 3 | "version": "0.0.0", 4 | "description": "Extension to return old Twitter layout from 2015.", 5 | "main": "pack.js", 6 | "scripts": { 7 | "test": "node test.js", 8 | "build": "node pack.js", 9 | "build-action": "node pack.js -a" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/dimdenGD/OldTwitter.git" 14 | }, 15 | "keywords": [ 16 | "twitter", 17 | "twitter-client", 18 | "chrome-extension", 19 | "firefox-addons", 20 | "chrome", 21 | "firefox-extension", 22 | "chromium-extension" 23 | ], 24 | "author": "dimden", 25 | "license": "UNLICENSED", 26 | "bugs": { 27 | "url": "https://github.com/dimdenGD/OldTwitter/issues" 28 | }, 29 | "homepage": "https://github.com/dimdenGD/OldTwitter#readme", 30 | "dependencies": { 31 | "adm-zip": "^0.5.10", 32 | "node-fetch": "^2.6.7" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /ruleset.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": 1, 4 | "priority": 1, 5 | "action": { 6 | "type": "modifyHeaders", 7 | "responseHeaders": [ 8 | { 9 | "header": "content-security-policy", 10 | "operation": "remove" 11 | }, 12 | { 13 | "header": "x-frame-options", 14 | "operation": "remove" 15 | } 16 | ] 17 | }, 18 | "condition": { 19 | "urlFilter": "*://twitter.com/*", 20 | "resourceTypes": ["main_frame", "sub_frame", "stylesheet", "script", "image", "font", "xmlhttprequest", "other"] 21 | } 22 | }, 23 | { 24 | "id": 2, 25 | "priority": 1, 26 | "action": { "type": "block" }, 27 | "condition": { 28 | "urlFilter": "*://twitter.com/sw.js", 29 | "resourceTypes": ["main_frame", "sub_frame", "script", "image", "stylesheet", "object", "xmlhttprequest", "other"] 30 | } 31 | }, 32 | { 33 | "id": 3, 34 | "priority": 1, 35 | "action": { "type": "redirect", "redirect": { "extensionPath": "/images/logo32_new.png" } }, 36 | "condition": { 37 | "urlFilter": "*://abs.twimg.com/favicons/twitter.3.ico", 38 | "resourceTypes": ["main_frame", "sub_frame", "stylesheet", "script", "image", "font", "xmlhttprequest", "other"] 39 | } 40 | }, 41 | { 42 | "id": 4, 43 | "priority": 1, 44 | "action": { "type": "redirect", "redirect": { "extensionPath": "/images/logo32_new_notification.png" } }, 45 | "condition": { 46 | "urlFilter": "*://abs.twimg.com/favicons/twitter-pip.3.ico", 47 | "resourceTypes": ["main_frame", "sub_frame", "stylesheet", "script", "image", "font", "xmlhttprequest", "other"] 48 | } 49 | }, 50 | { 51 | "id": 5, 52 | "priority": 1, 53 | "action": { "type": "redirect", "redirect": { "extensionPath": "/images/logo192.png" } }, 54 | "condition": { 55 | "urlFilter": "*://abs.twimg.com/responsive-web/client-web/icon-default.*.png", 56 | "resourceTypes": ["main_frame", "sub_frame", "stylesheet", "script", "image", "font", "xmlhttprequest", "other"] 57 | } 58 | }, 59 | { 60 | "id": 6, 61 | "priority": 1, 62 | "action": { "type": "redirect", "redirect": { "extensionPath": "/images/logo192.png" } }, 63 | "condition": { 64 | "urlFilter": "*://abs.twimg.com/responsive-web/client-web/icon-default-maskable.*.png", 65 | "resourceTypes": ["main_frame", "sub_frame", "stylesheet", "script", "image", "font", "xmlhttprequest", "other"] 66 | } 67 | }, 68 | { 69 | "id": 7, 70 | "priority": 1, 71 | "action": { "type": "redirect", "redirect": { "extensionPath": "/images/logo512.png" } }, 72 | "condition": { 73 | "urlFilter": "*://abs.twimg.com/responsive-web/client-web/icon-default-large.*.png", 74 | "resourceTypes": ["main_frame", "sub_frame", "stylesheet", "script", "image", "font", "xmlhttprequest", "other"] 75 | } 76 | }, 77 | { 78 | "id": 8, 79 | "priority": 1, 80 | "action": { "type": "redirect", "redirect": { "extensionPath": "/images/logo512.png" } }, 81 | "condition": { 82 | "urlFilter": "*://abs.twimg.com/responsive-web/client-web/icon-default-maskable-large.*.png", 83 | "resourceTypes": ["main_frame", "sub_frame", "stylesheet", "script", "image", "font", "xmlhttprequest", "other"] 84 | } 85 | }, 86 | { 87 | "id": 9, 88 | "priority": 1, 89 | "action": { 90 | "type": "modifyHeaders", 91 | "responseHeaders": [ 92 | { 93 | "header": "access-control-allow-origin", 94 | "operation": "set", 95 | "value": "https://twitter.com" 96 | } 97 | ] 98 | }, 99 | "condition": { 100 | "urlFilter": "*://*.twimg.com/*", 101 | "initiatorDomains": ["twitter.com"], 102 | "excludedInitiatorDomains": ["tweetdeck.twitter.com", "platform.twitter.com"], 103 | "resourceTypes": ["main_frame", "sub_frame", "stylesheet", "script", "image", "font", "xmlhttprequest", "other"] 104 | } 105 | }, 106 | { 107 | "id": 10, 108 | "priority": 1, 109 | "action": { 110 | "type": "modifyHeaders", 111 | "responseHeaders": [ 112 | { 113 | "header": "access-control-allow-origin", 114 | "operation": "set", 115 | "value": "https://tweetdeck.twitter.com" 116 | } 117 | ] 118 | }, 119 | "condition": { 120 | "urlFilter": "*://*.twimg.com/*", 121 | "initiatorDomains": ["tweetdeck.twitter.com"], 122 | "resourceTypes": ["main_frame", "sub_frame", "stylesheet", "script", "image", "font", "xmlhttprequest", "other"] 123 | } 124 | }, 125 | { 126 | "id": 11, 127 | "priority": 1, 128 | "action": { "type": "redirect", "redirect": { "extensionPath": "/images/logo512.png" } }, 129 | "condition": { 130 | "urlFilter": "*://abs.twimg.com/responsive-web/client-web/icon-ios.*.png", 131 | "resourceTypes": ["main_frame", "sub_frame", "stylesheet", "script", "image", "font", "xmlhttprequest", "other"] 132 | } 133 | }, 134 | 135 | 136 | { 137 | "id": 12, 138 | "priority": 1, 139 | "action": { 140 | "type": "modifyHeaders", 141 | "responseHeaders": [ 142 | { 143 | "header": "content-security-policy", 144 | "operation": "remove" 145 | }, 146 | { 147 | "header": "x-frame-options", 148 | "operation": "remove" 149 | } 150 | ] 151 | }, 152 | "condition": { 153 | "urlFilter": "*://x.com/*", 154 | "resourceTypes": ["main_frame", "sub_frame", "stylesheet", "script", "image", "font", "xmlhttprequest", "other"] 155 | } 156 | }, 157 | { 158 | "id": 13, 159 | "priority": 1, 160 | "action": { "type": "block" }, 161 | "condition": { 162 | "urlFilter": "*://x.com/sw.js", 163 | "resourceTypes": ["main_frame", "sub_frame", "script", "image", "stylesheet", "object", "xmlhttprequest", "other"] 164 | } 165 | }, 166 | { 167 | "id": 14, 168 | "priority": 1, 169 | "action": { 170 | "type": "modifyHeaders", 171 | "responseHeaders": [ 172 | { 173 | "header": "access-control-allow-origin", 174 | "operation": "set", 175 | "value": "https://x.com" 176 | } 177 | ] 178 | }, 179 | "condition": { 180 | "urlFilter": "*://*.twimg.com/*", 181 | "initiatorDomains": ["x.com"], 182 | "excludedInitiatorDomains": ["tweetdeck.x.com", "platform.x.com"], 183 | "resourceTypes": ["main_frame", "sub_frame", "stylesheet", "script", "image", "font", "xmlhttprequest", "other"] 184 | } 185 | }, 186 | { 187 | "id": 15, 188 | "priority": 1, 189 | "action": { 190 | "type": "modifyHeaders", 191 | "responseHeaders": [ 192 | { 193 | "header": "access-control-allow-origin", 194 | "operation": "set", 195 | "value": "https://tweetdeck.x.com" 196 | } 197 | ] 198 | }, 199 | "condition": { 200 | "urlFilter": "*://*.twimg.com/*", 201 | "initiatorDomains": ["tweetdeck.x.com"], 202 | "resourceTypes": ["main_frame", "sub_frame", "stylesheet", "script", "image", "font", "xmlhttprequest", "other"] 203 | } 204 | } 205 | ] -------------------------------------------------------------------------------- /sandbox.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Sandbox 7 | 8 | 9 | 10 |
11 | 105 | 106 | 107 | -------------------------------------------------------------------------------- /scripts/background.js: -------------------------------------------------------------------------------- 1 | chrome.contextMenus.create({ 2 | id: 'open_settings', 3 | title: 'Open settings', 4 | contexts: ['action'] 5 | }); 6 | 7 | chrome.runtime.onInstalled.addListener(() => { 8 | chrome.runtime.setUninstallURL('https://dimden.dev/ot/uninstall.html'); 9 | }); 10 | 11 | chrome.contextMenus.onClicked.addListener(info => { 12 | if (info.menuItemId === 'open_settings') { 13 | chrome.tabs.create({ 14 | url: 'https://twitter.com/old/settings' 15 | }); 16 | } 17 | }); 18 | chrome.action.onClicked.addListener(() => { 19 | chrome.tabs.create({ 20 | url: 'https://twitter.com/old/settings' 21 | }); 22 | }); 23 | 24 | chrome.runtime.onMessage.addListener(async (request, sender, sendResponse) => { 25 | if(request.action === "inject") { 26 | console.log(request, sender.tab.id); 27 | chrome.scripting.executeScript({ 28 | target: { 29 | tabId: sender.tab.id, 30 | allFrames : true 31 | }, 32 | injectImmediately: true, 33 | files: request.files 34 | }).then(res => { 35 | console.log('injected', res); 36 | }).catch(e => { 37 | console.log('error injecting', e); 38 | }); 39 | } 40 | }); -------------------------------------------------------------------------------- /scripts/background_v2.js: -------------------------------------------------------------------------------- 1 | chrome.runtime.onInstalled.addListener(() => { 2 | chrome.runtime.setUninstallURL('https://dimden.dev/ot/uninstall.html'); 3 | }); 4 | 5 | const redirectUrls = [ 6 | ['abs.twimg.com/favicons/twitter.3.ico', 'images/logo32_new.png'], 7 | ['abs.twimg.com/favicons/twitter-pip.3.ico', 'images/logo32_new_notification.png'], 8 | ['abs.twimg.com/responsive-web/client-web/icon-default.', 'images/logo512.png'], 9 | ['abs.twimg.com/responsive-web/client-web/icon-default-maskable.', 'images/logo192.png'], 10 | ['abs.twimg.com/responsive-web/client-web/icon-default-large.', 'images/logo512.png'], 11 | ['abs.twimg.com/responsive-web/client-web/icon-default-maskable-large.', 'images/logo512.png'], 12 | ['abs.twimg.com/responsive-web/client-web/icon-ios.', 'images/logo32_new_notification.png'], 13 | ] 14 | 15 | chrome.webRequest.onBeforeRequest.addListener( 16 | function(details) { 17 | for(let i = 0; i < redirectUrls.length; i++) { 18 | if(details.url.includes(redirectUrls[i][0])) { 19 | return { 20 | redirectUrl: chrome.runtime.getURL(redirectUrls[i][1]) 21 | }; 22 | } 23 | } 24 | if(details.url.includes("/sw.js")) { 25 | return { cancel: true }; 26 | } 27 | return { 28 | cancel: 29 | ( // excludes 30 | details.originUrl && 31 | !details.originUrl.includes("newtwitter=true") && 32 | !details.originUrl.includes("/i/flow/login") && 33 | !details.originUrl.includes("/settings/download_your_data") && 34 | !details.originUrl.includes("/i/broadcasts") && 35 | !details.originUrl.includes("/i/communitynotes") && 36 | !details.originUrl.includes("tweetdeck.twitter.com") && 37 | !details.url.includes("ondemand.s.") 38 | ) && 39 | ( // includes 40 | details.url.includes("abs.twimg.com/responsive-web/client-web") 41 | ) 42 | }; 43 | }, { 44 | urls: ["*://*.twitter.com/*", "*://*.x.com/*", "*://*.twimg.com/*"] 45 | }, 46 | ["blocking"] 47 | ); 48 | chrome.webRequest.onBeforeSendHeaders.addListener( 49 | function(details) { 50 | if(!details.requestHeaders.find(h => h.name.toLowerCase() === 'origin')) details.requestHeaders.push({ 51 | name: "Origin", 52 | value: "https://x.com" 53 | }); 54 | return { 55 | requestHeaders: details.requestHeaders 56 | }; 57 | }, { 58 | urls: ["*://*.twimg.com/*", "*://twimg.com/*"] 59 | }, 60 | ["blocking", "requestHeaders"] 61 | ); 62 | chrome.webRequest.onBeforeSendHeaders.addListener( //this isnt particularly elegant solution 63 | function(details) { 64 | for (let i = 0; i < details.requestHeaders.length; i++) { 65 | if (details.requestHeaders[i].name.toLowerCase() === 'user-agent') { 66 | if (details.requestHeaders[i].value.toLowerCase().includes('firefox')) { 67 | let latest = 128; //if this ever breaks set this to latest firefox version 68 | let rvRegex = /rv:(\d+\.\d+)/; //gecko version (whats important here) 69 | let versionRegex = /Firefox\/(\d+(?:\.\d+)+)/; //browser version 70 | let rvMatch = details.requestHeaders[i].value.match(rvRegex); 71 | let versionMatch = details.requestHeaders[i].value.match(versionRegex); 72 | if (rvMatch && versionMatch) { 73 | let rv = parseFloat(rvMatch[1]); 74 | let version = parseFloat(versionMatch[1]); 75 | if (rv < latest || version < latest) { //rv 110 is cutoff point between client-web-legacy and client-web, so we should just spoof browser and rv to latest 76 | details.requestHeaders[i].value = details.requestHeaders[i].value.replace(rvRegex, `rv:${latest}.0`).replace(versionRegex, `Firefox/${latest}.0`); 77 | } 78 | } 79 | } 80 | break; 81 | } 82 | } 83 | return { 84 | requestHeaders: details.requestHeaders 85 | }; 86 | }, { 87 | urls: ["*://x.com/", "*://twitter.com/"] 88 | }, 89 | ["blocking", "requestHeaders"] 90 | ); 91 | chrome.runtime.onMessage.addListener(async (request, sender, sendResponse) => { 92 | if(request.action === "inject") { 93 | console.log(request, sender.tab.id); 94 | chrome.scripting.executeScript({ 95 | target: { 96 | tabId: sender.tab.id, 97 | allFrames : true 98 | }, 99 | injectImmediately: true, 100 | files: request.files 101 | }).then(res => { 102 | console.log('injected', res); 103 | }).catch(e => { 104 | console.log('error injecting', e); 105 | }); 106 | } 107 | }); -------------------------------------------------------------------------------- /scripts/blockBeforeInject.js: -------------------------------------------------------------------------------- 1 | // block all twitters scripts 2 | function blockScriptElements(element) { 3 | if (element.tagName === 'SCRIPT') { 4 | element.type = 'javascript/blocked'; 5 | const beforeScriptExecuteListener = function (event) { 6 | if(element.getAttribute('type') === 'javascript/blocked') { 7 | event.preventDefault(); 8 | } 9 | element.removeEventListener('beforescriptexecute', beforeScriptExecuteListener); 10 | } 11 | element.addEventListener('beforescriptexecute', beforeScriptExecuteListener); 12 | element.remove(); 13 | } 14 | } 15 | 16 | const blockingObserver = new MutationObserver((mutations) => { 17 | mutations.forEach((mutation) => { 18 | if (mutation.type === 'childList' && mutation.addedNodes.length > 0) { 19 | mutation.addedNodes.forEach((node) => { 20 | if (node.nodeType === Node.ELEMENT_NODE) { 21 | blockScriptElements(node); 22 | node.querySelectorAll('script').forEach(blockScriptElements); 23 | if(node.tagName === 'SVG') { 24 | node.remove(); 25 | } 26 | if(node.id === 'placeholder') { 27 | node.remove(); 28 | } 29 | node.querySelectorAll('svg').forEach(i => i.remove()); 30 | if(document.getElementById('placeholder')) document.getElementById('placeholder').remove(); 31 | } 32 | }); 33 | } 34 | }); 35 | }); 36 | 37 | // Start observing the page for changes 38 | blockingObserver.observe(document.documentElement, { childList: true, subtree: true }); -------------------------------------------------------------------------------- /scripts/iframeNavigation.js: -------------------------------------------------------------------------------- 1 | // i don't understand how this file works anymore please send help 2 | 3 | function getFrameDepth(winToID) { 4 | if (winToID === window.top) { 5 | return 0; 6 | } 7 | else if (winToID.parent === window.top) { 8 | return 1; 9 | } 10 | 11 | return 1 + getFrameDepth(winToID.parent); 12 | } 13 | 14 | if(!window.top.windows && window.top === window) { 15 | window.top.windows = []; 16 | window.top._title = document.title; 17 | setTimeout(() => { 18 | window.top._title = document.title; 19 | }, 1000); 20 | setInterval(() => { 21 | let iframe = document.getElementsByClassName('iframe-navigation')[0]; 22 | if(window.top.windows.slice(1).some(w => w.location.pathname === '/home') || (iframe && iframe.src === "/home")) { 23 | document.body.style.overflowY = window.previousOverflow && window.previousOverflow !== 'hidden' ? window.previousOverflow : 'auto'; 24 | window.top.windows = [window]; 25 | iframe.remove(); 26 | window.focus(); 27 | 28 | window.top.document.title = window.top._title; 29 | } 30 | if(!iframe) { 31 | window.top._title = document.title; 32 | } 33 | }, 250); 34 | } 35 | if(!window.top.windows.includes(window)) window.top.windows.push(window); 36 | if(!window._realPath) window._realPath = location.pathname; 37 | if(window.top !== window && location.protocol == 'https:') { 38 | setTimeout(() => { 39 | window.top.document.title = document.title; 40 | }, 1000); 41 | } 42 | 43 | window.addEventListener('unload', () => { 44 | window.top.windows = window.top.windows.filter(w => w !== window); 45 | }); 46 | 47 | let lastNavigation = 0; 48 | function useIframeNavigation(e) { 49 | if(e.defaultPrevented) return; 50 | if(typeof vars === 'undefined') return; 51 | if(!vars.enableIframeNavigation) return; 52 | // because of dumb mozilla's policies need to turn off this feature 53 | // (they don't allow changing security headers so cant modify x-frame-options) 54 | if(navigator.userAgent.toLowerCase().includes('firefox')) return; 55 | 56 | let a = e.target.closest('a'); 57 | if(!a || !a.href || a.href.startsWith('#') || a.href.startsWith('javascript:') || a.href.startsWith('blob:')) return; 58 | if(a.href.startsWith('http') && !a.href.startsWith(location.origin)) return; 59 | 60 | let depth = getFrameDepth(window); 61 | if(depth > 3) return; 62 | 63 | let parsedURL = new URL(a.href); 64 | let windowExists = window.top.windows.findIndex(w => w._realPath === parsedURL.pathname); 65 | 66 | if(windowExists !== -1) { 67 | let we = window.top.windows[windowExists]; 68 | let iframe = we.document.getElementsByClassName('iframe-navigation')[0]; 69 | window.top.windows = window.top.windows.slice(0, windowExists + 1); 70 | we.document.body.style.overflowY = we.previousOverflow && we.previousOverflow !== 'hidden' ? we.previousOverflow : 'auto'; 71 | we.focus(); 72 | 73 | window.top.document.title = we.document.title; 74 | if(window.top.windows.length === 1) window.top.document.title = window.top._title; 75 | 76 | if(location.href !== a.href) window.top.history.pushState(null, null, a.href); 77 | if(iframe) iframe.remove(); 78 | return; 79 | }; 80 | 81 | if(window.top._realPath === '/' || window.top._realPath === '/home') { 82 | e.preventDefault(); 83 | e.stopImmediatePropagation(); 84 | 85 | window.previousOverflow = document.body.style.overflowY; 86 | document.body.style.overflowY = 'hidden'; 87 | 88 | if(location.href !== a.href) window.top.history.pushState(null, null, a.href); 89 | 90 | let iframe = document.createElement('iframe'); 91 | iframe.classList.add('iframe-navigation'); 92 | iframe.src = a.href; 93 | iframe.style = ` 94 | position: fixed; 95 | top: 0; 96 | left: 0; 97 | width: 100vw; 98 | height: 100vh; 99 | border: none; 100 | z-index: 99999; 101 | background-color: var(--background-color); 102 | `; 103 | document.body.appendChild(iframe); 104 | iframe.focus(); 105 | } 106 | 107 | window.addEventListener('popstate', () => { 108 | let windowExists = window.top.windows.findIndex(w => w._realPath === location.pathname); 109 | 110 | if(windowExists !== -1) { 111 | let we = window.top.windows[windowExists]; 112 | let iframe = we.document.getElementsByClassName('iframe-navigation')[0]; 113 | window.top.windows = window.top.windows.slice(0, windowExists + 1); 114 | we.document.body.style.overflowY = we.previousOverflow && we.previousOverflow !== 'hidden' ? we.previousOverflow : 'auto'; 115 | we.focus(); 116 | 117 | window.top.document.title = we.document.title; 118 | if(window.top.windows.length === 1) window.top.document.title = window.top._title; 119 | 120 | if(iframe) iframe.remove(); 121 | } else { 122 | if(location.pathname === '/notifications' || location.pathname.includes('/status/')) { 123 | let we = window.top.windows[Math.max(window.top.windows.length - 2, 0)]; 124 | let iframe = we.document.getElementsByClassName('iframe-navigation')[0]; 125 | window.top.windows = window.top.windows.slice(0, window.top.windows.length - 1); 126 | if(window.top.windows.length === 0) window.top.windows = [window]; 127 | we.document.body.style.overflowY = we.previousOverflow && we.previousOverflow !== 'hidden' ? we.previousOverflow : 'auto'; 128 | we.focus(); 129 | 130 | window.top.document.title = we.document.title; 131 | if(window.top.windows.length === 1) window.top.document.title = window.top._title; 132 | 133 | if(iframe) iframe.remove(); 134 | } else { 135 | if(Date.now() - lastNavigation < 20) return; 136 | lastNavigation = Date.now(); 137 | useIframeNavigation({ 138 | target: { closest: () => ({ href: location.href }) }, 139 | preventDefault: () => {}, 140 | stopImmediatePropagation: () => {} 141 | }); 142 | } 143 | } 144 | }); 145 | } 146 | 147 | document.addEventListener('click', useIframeNavigation); -------------------------------------------------------------------------------- /scripts/newtwitter.js: -------------------------------------------------------------------------------- 1 | let r = document.createElement('a'); 2 | let hrefUrl = new URL(location.href); 3 | let searchParams = new URLSearchParams(hrefUrl.search); 4 | searchParams.delete('newtwitter') 5 | hrefUrl.search = searchParams.toString(); 6 | r.href = hrefUrl.toString(); 7 | setInterval(() => { 8 | let hrefUrl = new URL(location.href); 9 | let searchParams = new URLSearchParams(hrefUrl.search); 10 | searchParams.delete('newtwitter') 11 | hrefUrl.search = searchParams.toString(); 12 | r.href = hrefUrl.toString(); 13 | 14 | let realPath = location.pathname.split('?')[0].split('#')[0]; 15 | if (realPath.endsWith("/")) { 16 | realPath = realPath.slice(0, -1); 17 | } 18 | if( 19 | /^\/[A-z-0-9-_]{1,15}\/status\/\d{5,32}\/analytics$/.test(realPath) || 20 | (realPath.startsWith('/i/') && realPath !== "/i/bookmarks" && !realPath.startsWith('/i/lists/')) || 21 | realPath === '/explore' || 22 | realPath === '/login' || 23 | realPath === '/register' || 24 | realPath === '/logout' || 25 | realPath === '/messages' || 26 | realPath.endsWith('/tos') || 27 | realPath.endsWith('/privacy') || 28 | realPath.startsWith('/account/') || 29 | realPath.endsWith('/lists') || 30 | realPath.endsWith('/topics') || 31 | realPath.startsWith('/settings/') 32 | ) { 33 | r.hidden = true; 34 | } else { 35 | r.hidden = false; 36 | } 37 | 38 | if(!location.search.includes('newtwitter=true')) { 39 | let url = new URL(location.href); 40 | url.searchParams.set('newtwitter', 'true'); 41 | history.replaceState(null, null, url.href); 42 | } 43 | }, 500); 44 | r.textContent = 'Open this page in OldTwitter'; 45 | r.style.cssText = 'position: fixed; top: 0; right: 10px; padding: 0.5em; background: #fff; color: #000; font-family: Arial, sans-serif;border-radius:3px;'; 46 | document.body.appendChild(r); 47 | 48 | setTimeout(() => { 49 | let realPath = location.pathname.split('?')[0].split('#')[0]; 50 | if (realPath.endsWith("/")) { 51 | realPath = realPath.slice(0, -1); 52 | } 53 | if(realPath === '/i/flow/login') { 54 | let i = setInterval(() => { 55 | let head = document.getElementById('modal-header'); 56 | if(head) { 57 | clearInterval(i); 58 | let span = document.createElement('span'); 59 | span.innerHTML = html`OldTwitter relies on internal APIs that only work when you're logged in.
Please log in on this page to see old Twitter layout.`; 60 | span.style.cssText = `display: block;margin: 0.5em 0px;color: #fbfeff;font-family: TwitterChirp;background: rgb(0 161 255 / 10%);padding: 8px;border-radius: 5px;`; 61 | head.after(span); 62 | } 63 | }, 500); 64 | } 65 | }, 1000); 66 | 67 | (() => { 68 | let keysHeld = {}; 69 | function processHotkeys() { 70 | if (keysHeld['Alt'] && keysHeld['Control'] && keysHeld['KeyO']) { 71 | let url = new URL(location.href); 72 | url.searchParams.delete('newtwitter'); 73 | location.replace(url.href); 74 | } 75 | } 76 | window.addEventListener('keydown', (ev) => { 77 | let key = ev.code; 78 | if(key === 'AltLeft' || key === 'AltRight') key = 'Alt'; 79 | if(key === 'ControlLeft' || key === 'ControlRight') key = 'Control'; 80 | if(key === 'ShiftLeft' || key === 'ShiftRight') key = 'Shift'; 81 | keysHeld[key] = true; 82 | 83 | processHotkeys(); 84 | }); 85 | 86 | window.addEventListener('keyup', (ev) => { 87 | let key = ev.code; 88 | if(key === 'AltLeft' || key === 'AltRight') key = 'Alt'; 89 | if(key === 'ControlLeft' || key === 'ControlRight') key = 'Control'; 90 | if(key === 'ShiftLeft' || key === 'ShiftRight') key = 'Shift'; 91 | keysHeld[key] = true; 92 | processHotkeys(); 93 | keysHeld[key] = false; 94 | }); 95 | })(); 96 | 97 | function modifyLink(a) { 98 | if(a.href && !a.href.includes('newtwitter=true')) { 99 | let url = new URL(a.href); 100 | url.searchParams.set('newtwitter', 'true'); 101 | a.href = url.href; 102 | } 103 | } 104 | 105 | const linkObserver = new MutationObserver((mutations) => { 106 | mutations.forEach((mutation) => { 107 | if (mutation.type === 'childList' && mutation.addedNodes.length > 0) { 108 | mutation.addedNodes.forEach((node) => { 109 | if (node.nodeType === Node.ELEMENT_NODE) { 110 | if(node.tagName === 'A') { 111 | modifyLink(node); 112 | } 113 | node.querySelectorAll('a').forEach(modifyLink); 114 | } 115 | }); 116 | } 117 | }); 118 | }); 119 | 120 | // Start observing the page for changes 121 | linkObserver.observe(document.documentElement, { childList: true, subtree: true }); -------------------------------------------------------------------------------- /scripts/twchallenge.js: -------------------------------------------------------------------------------- 1 | let solverIframe; 2 | let solveId = 0; 3 | let solveCallbacks = {}; 4 | let solveQueue = [] 5 | let solverReady = false; 6 | let solverErrored = false; 7 | let sentData = false; 8 | 9 | let sandboxUrl = fetch(chrome.runtime.getURL(`sandbox.html`)) 10 | .then(resp => resp.blob()) 11 | .then(blob => URL.createObjectURL(blob)) 12 | .catch(console.error); 13 | 14 | function createSolverFrame() { 15 | if (solverIframe) solverIframe.remove(); 16 | solverIframe = document.createElement('iframe'); 17 | //display:none causes animations to not play which breaks the challenge solver, so have to hide it in different way 18 | solverIframe.style.position = 'absolute'; 19 | solverIframe.width = '0px'; 20 | solverIframe.height = '0px'; 21 | solverIframe.style.border = 'none'; 22 | solverIframe.style.opacity = 0; 23 | solverIframe.style.pointerEvents = 'none'; 24 | solverIframe.tabIndex = -1; 25 | sandboxUrl.then(url => solverIframe.src = url); 26 | let injectedBody = document.getElementById('injected-body'); 27 | if(injectedBody) { 28 | injectedBody.appendChild(solverIframe); 29 | } else { 30 | let int = setInterval(() => { 31 | let injectedBody = document.getElementById('injected-body'); 32 | if(injectedBody) { 33 | injectedBody.appendChild(solverIframe); 34 | clearInterval(int); 35 | } 36 | }, 10); 37 | } 38 | } 39 | createSolverFrame(); 40 | 41 | function solveChallenge(path, method) { 42 | return new Promise((resolve, reject) => { 43 | if(solverErrored) { 44 | reject('Solver errored during initialization'); 45 | return; 46 | } 47 | let id = solveId++; 48 | solveCallbacks[id] = { resolve, reject, time: Date.now() }; 49 | if(!solverReady || !solverIframe || !solverIframe.contentWindow) { 50 | solveQueue.push({ id, path, method }) 51 | } else { 52 | try { 53 | solverIframe.contentWindow.postMessage({ action: 'solve', id, path, method }, '*'); 54 | } catch(e) { 55 | console.error(`Error sending challenge to solver:`, e); 56 | reject(e); 57 | } 58 | // setTimeout(() => { 59 | // if(solveCallbacks[id]) { 60 | // solveCallbacks[id].reject('Solver timed out'); 61 | // delete solveCallbacks[id]; 62 | // } 63 | // }, 1750); 64 | } 65 | }); 66 | } 67 | 68 | setInterval(() => { 69 | if(!document.getElementById('loading-box').hidden && sentData && solveQueue.length) { 70 | console.log("Something's wrong with the challenge solver, reloading", solveQueue); 71 | createSolverFrame(); 72 | initChallenge(); 73 | } 74 | }, 2000); 75 | 76 | window.addEventListener('message', e => { 77 | if(e.source !== solverIframe.contentWindow) return; 78 | let data = e.data; 79 | if(data.action === 'solved' && typeof data.id === 'number') { 80 | let { id, result } = data; 81 | if(solveCallbacks[id]) { 82 | solveCallbacks[id].resolve(result); 83 | delete solveCallbacks[id]; 84 | } 85 | } else if(data.action === 'error' && typeof data.id === 'number') { 86 | let { id, error } = data; 87 | if(solveCallbacks[id]) { 88 | solveCallbacks[id].reject(error); 89 | delete solveCallbacks[id]; 90 | } 91 | } else if(data.action === 'initError') { 92 | solverErrored = true; 93 | for(let id in solveCallbacks) { 94 | solveCallbacks[id].reject('Solver errored during initialization'); 95 | delete solveCallbacks[id]; 96 | } 97 | alert(`There was an error in initializing security header generator:\n${data.error}\nUser Agent: ${navigator.userAgent}\nOldTwitter doesn't allow unsigned requests anymore for your account security.`); 98 | console.error('Error initializing solver:'); 99 | console.error(data.error); 100 | } else if(data.action === 'ready') { 101 | solverReady = true; 102 | for (let task of solveQueue) { 103 | solverIframe.contentWindow.postMessage({ action: 'solve', id: task.id, path: task.path, method: task.method }, '*') 104 | } 105 | } 106 | }); 107 | 108 | window._fetch = window.fetch; 109 | fetch = async function(url, options) { 110 | if(!url.startsWith('/i/api') && !url.startsWith('https://api.twitter.com') && !url.startsWith('https://api.x.com')) return _fetch(url, options); 111 | if(!options) options = {}; 112 | if(!options.headers) options.headers = {}; 113 | if(!options.headers['x-twitter-auth-type']) { 114 | options.headers['x-twitter-auth-type'] = 'OAuth2Session'; 115 | } 116 | if(!options.headers['x-twitter-active-user']) { 117 | options.headers['x-twitter-active-user'] = 'yes'; 118 | } 119 | if(!options.headers['X-Client-UUID']) { 120 | options.headers['X-Client-UUID'] = OLDTWITTER_CONFIG.deviceId; 121 | } 122 | if(!url.startsWith('http:') && !url.startsWith('https:')) { 123 | let host = location.hostname; 124 | if(!['x.com', 'twitter.com'].includes(host)) host = 'x.com'; 125 | if(!url.startsWith('/')) url = '/' + url; 126 | url = `https://${host}${url}`; 127 | } 128 | let parsedUrl = new URL(url); 129 | // try { 130 | let solved = await solveChallenge(parsedUrl.pathname, options.method ? options.method.toUpperCase() : 'GET'); 131 | options.headers['x-client-transaction-id'] = solved; 132 | // } catch (e) { 133 | // console.error(`Error solving challenge for ${url}:`); 134 | // console.error(e); 135 | // } 136 | if(options.method && options.method.toUpperCase() === 'POST' && typeof options.body === 'string') { 137 | options.headers['Content-Length'] = options.body.length; 138 | } 139 | 140 | return _fetch(url, options); 141 | } 142 | 143 | async function initChallenge() { 144 | try { 145 | let homepageData; 146 | let sleep = ms => new Promise(resolve => setTimeout(resolve, ms)); 147 | let host = location.hostname; 148 | if(!['x.com', 'twitter.com'].includes(host)) host = 'x.com'; 149 | try { 150 | homepageData = await _fetch(`https://${host}/`).then(res => res.text()); 151 | } catch(e) { 152 | await sleep(500); 153 | try { 154 | homepageData = await _fetch(`https://${host}/`).then(res => res.text()); 155 | } catch(e) { 156 | throw new Error('Failed to fetch homepage: ' + e); 157 | } 158 | } 159 | let dom = new DOMParser().parseFromString(homepageData, 'text/html'); 160 | let verificationKey = dom.querySelector('meta[name="twitter-site-verification"]').content; 161 | let anims = Array.from(dom.querySelectorAll('svg[id^="loading-x"]')).map(svg => svg.outerHTML); 162 | 163 | let challengeCode = homepageData.match(/"ondemand.s":"(\w+)"/)[1]; 164 | 165 | OLDTWITTER_CONFIG.verificationKey = verificationKey; 166 | 167 | function sendInit() { 168 | sentData = true; 169 | if(!solverIframe || !solverIframe.contentWindow) return setTimeout(sendInit, 50); 170 | solverIframe.contentWindow.postMessage({ 171 | action: 'init', 172 | challengeCode, 173 | anims, 174 | verificationCode: OLDTWITTER_CONFIG.verificationKey 175 | }, '*'); 176 | } 177 | setTimeout(sendInit, 50); 178 | return true; 179 | } catch (e) { 180 | console.error(`Error during challenge init:`); 181 | console.error(e); 182 | if(location.hostname === 'twitter.com') { 183 | alert(`There was an error in initializing security header generator: ${e}\nUser Agent: ${navigator.userAgent}\nOldTwitter doesn't allow unsigned requests anymore for your account security. Currently the main reason for this happening is social network tracker protection blocking the script. Try disabling such settings in your browser and extensions that do that and refresh the page. This also might be because you're either not logged in or using twitter.com instead of x.com.`); 184 | } else { 185 | alert(`There was an error in initializing security header generator: ${e}\nUser Agent: ${navigator.userAgent}\nOldTwitter doesn't allow unsigned requests anymore for your account security. Currently the main reason for this happening is social network tracker protection blocking the script. Try disabling such settings in your browser and extensions that do that and refresh the page. This can also happen if you're not logged in.`); 186 | } 187 | return false; 188 | } 189 | }; 190 | 191 | initChallenge(); 192 | -------------------------------------------------------------------------------- /scripts/xIconRemove.js: -------------------------------------------------------------------------------- 1 | setInterval(() => { 2 | let xIcon = document.querySelector('a[href^="https://twitter.com/home"] > div > svg, a[href^="https://x.com/home"] > div > svg'); 3 | if(xIcon) { 4 | let parent = xIcon.parentElement; 5 | let img = document.createElement('img'); 6 | img.src = chrome.runtime.getURL('images/logo32_new.png'); 7 | img.style.cssText = 'width: 2em;height: 2em;image-rendering: -webkit-optimize-contrast;'; 8 | parent.appendChild(img); 9 | xIcon.remove(); 10 | }; 11 | 12 | let title = document.querySelector('title'); 13 | if(title) { 14 | if(title.innerText.endsWith(' / X')) { 15 | title.innerText = title.innerText.replace(' / X', ' / Twitter'); 16 | } 17 | } 18 | }, 200); 19 | 20 | function removeAndReplaceX(element) { 21 | if(element) { 22 | let parent = element.parentElement; 23 | let img = document.createElement('img'); 24 | img.src = chrome.runtime.getURL('images/logo32_new.png'); 25 | img.style.cssText = 'width: 2em;height: 2em;image-rendering: -webkit-optimize-contrast;display: block;top: 50%;position: absolute;left: 50%;transform: translate(-50%, -50%);'; 26 | parent.appendChild(img); 27 | element.remove(); 28 | xObserver.disconnect(); 29 | 30 | setTimeout(() => { 31 | img.remove(); 32 | }, 500); 33 | }; 34 | } 35 | 36 | const xObserver = new MutationObserver((mutations) => { 37 | mutations.forEach((mutation) => { 38 | if (mutation.type === 'childList' && mutation.addedNodes.length > 0) { 39 | mutation.addedNodes.forEach((node) => { 40 | if (node.nodeType === Node.ELEMENT_NODE) { 41 | if(node.tagName === 'SVG') { 42 | removeAndReplaceX(node); 43 | } 44 | node.querySelectorAll('svg').forEach(removeAndReplaceX); 45 | } 46 | }); 47 | } 48 | }); 49 | }); 50 | 51 | // Start observing the page for changes 52 | xObserver.observe(document.documentElement, { childList: true, subtree: true }); --------------------------------------------------------------------------------