├── .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 | 
20 | 
21 | 
22 | 
23 | 
24 | 
25 | 
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 | 
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 |
--------------------------------------------------------------------------------
/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 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
![]()
27 |
48 |
49 |
50 |
__MSG_trends__
51 |
52 |
53 |
54 |
55 |
56 | __MSG_running__ OldTwitter v.
57 | __MSG_created_by__ dimden.
58 |
59 |
60 |
66 |
73 |
74 |
75 |
76 |
77 |
78 |
__MSG_bookmarks__
79 |
__MSG_delete_all__
80 |
81 |
88 |
89 |
90 |
91 |
92 |
__MSG_who_to_follow__
93 |
__MSG_refresh__ · __MSG_view_all__
94 |
95 |
96 |
97 |
98 |
99 | __MSG_running__ OldTwitter v.
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 |
2 |
3 |
4 |
12 |
15 |
16 |
21 |
22 |
![]()
23 |
43 |
44 |
48 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/layouts/home/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | __MSG_home__ - __MSG_twitter__
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
![]()
27 |
48 |
49 |
55 |
56 |
57 | __MSG_running__ OldTwitter v.
58 | __MSG_created_by__ dimden.
59 |
60 |
61 |
67 |
74 |
75 |
76 |
77 |
78 |
87 |
126 |
127 |
128 |
131 |
138 |
__MSG_load_more__
139 |
140 |
141 |
150 |
151 |
__MSG_who_to_follow__
152 |
__MSG_refresh__ · __MSG_view_all__
153 |
154 |
155 |
156 |
157 |
158 | __MSG_running__ OldTwitter v.
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 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
![]()
27 |
48 |
49 |
50 |
__MSG_trends__
51 |
52 |
53 |
54 |
55 |
56 | __MSG_running__ OldTwitter v.
57 | __MSG_created_by__ dimden.
58 |
59 |
60 |
66 |
73 |
74 |
75 |
76 |
90 |
91 |
92 |
__MSG_who_to_follow__
93 |
__MSG_refresh__ · __MSG_view_all__
94 |
95 |
96 |
97 |
98 |
99 | __MSG_running__ OldTwitter v.
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 |
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 |
47 |
48 | __MSG_list_members__
49 |
50 | __MSG_list_subscribers__
51 |
52 |
53 |
54 | __MSG_running__ OldTwitter v.
55 | __MSG_created_by__ dimden.
56 |
57 |
58 |
64 |
71 |
72 |
73 |
74 |
97 |
98 |
99 |
__MSG_who_to_follow__
100 |
__MSG_refresh__ · __MSG_view_all__
101 |
102 |
103 |
104 |
105 |
106 | __MSG_running__ OldTwitter v.
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 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | __MSG_notifications__
27 |
28 | __MSG_mentions__
29 |
30 |
31 |
__MSG_trends__
32 |
33 |
34 |
35 |
36 |
37 | __MSG_running__ OldTwitter v.
38 | __MSG_created_by__ dimden.
39 |
40 |
41 |
47 |
54 |
55 |
56 |
57 |
58 |
59 |

60 |
61 |
68 |
__MSG_load_more__
69 |
70 |
71 |
72 |
__MSG_who_to_follow__
73 |
__MSG_refresh__ · __MSG_view_all__
74 |
75 |
76 |
77 |
78 |
79 | __MSG_running__ OldTwitter v.
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 |
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 |
__MSG_save_search__
50 |
51 |
__MSG_trends__
52 |
53 |
54 |
55 |
56 |
57 | __MSG_running__ OldTwitter v.
58 | __MSG_created_by__ dimden.
59 |
60 |
61 |
67 |
74 |
75 |
76 |
77 |
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 v.
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 |
48 |
49 |
50 |
__MSG_trends__
51 |
52 |
53 |
54 |
55 |
56 | __MSG_running__ OldTwitter v.
57 | __MSG_created_by__ dimden.
58 |
59 |
60 |
66 |
73 |
74 |
75 |
76 |
77 |
78 |
__MSG_loading__
79 |
80 |
81 |
82 | __MSG_youre_not_interested__
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
97 |
98 |
99 |
100 |
101 |
__MSG_who_to_follow__
102 |
__MSG_refresh__ · __MSG_view_all__
103 |
104 |
105 |
106 |
107 |
108 | __MSG_running__ OldTwitter v.
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 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
![]()
28 |
49 |
50 |
51 |
__MSG_trends__
52 |
53 |
54 |
55 |
56 |
57 | __MSG_running__ OldTwitter v.
58 | __MSG_created_by__ dimden.
59 |
60 |
61 |
67 |
74 |
75 |
76 |
77 |
78 |
85 |
86 |
87 |
88 |
89 |
90 |
__MSG_load_more__
91 |
92 |
93 |
94 |
__MSG_who_to_follow__
95 |
__MSG_refresh__ · __MSG_view_all__
96 |
97 |
98 |
99 |
100 |
101 | __MSG_running__ OldTwitter v.
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 |
23 |
![]()
24 |
45 |
46 |
47 |
__MSG_trends__
48 |
49 |
50 |
51 |
52 |
53 | __MSG_running__ OldTwitter v.
54 | __MSG_created_by__ dimden.
55 |
56 |
57 |
63 |
70 |
71 |
72 |
73 |
74 |
__MSG_unfollows__
75 |
76 |
77 |
78 |
__MSG_load_more__
79 |
80 |
81 |
82 |
83 |
__MSG_who_to_follow__
84 |
__MSG_refresh__ ··
__MSG_view_all__
85 |
86 |
87 |
88 |
89 |
90 | __MSG_running__ OldTwitter v.
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 });
--------------------------------------------------------------------------------