├── icons ├── icon128.png ├── icon16.png ├── icon48.png ├── icon64.png ├── icon96.png └── icon.svg ├── screenshots ├── tweets.png ├── retweets.png ├── chrome_options.png ├── firefox_options.png └── suggested_tweets.png ├── options.js ├── manifest.json ├── LICENSE ├── options.html ├── README.md └── manage-twitter-engagement.user.js /icons/icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insin/manage-twitter-engagement/HEAD/icons/icon128.png -------------------------------------------------------------------------------- /icons/icon16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insin/manage-twitter-engagement/HEAD/icons/icon16.png -------------------------------------------------------------------------------- /icons/icon48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insin/manage-twitter-engagement/HEAD/icons/icon48.png -------------------------------------------------------------------------------- /icons/icon64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insin/manage-twitter-engagement/HEAD/icons/icon64.png -------------------------------------------------------------------------------- /icons/icon96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insin/manage-twitter-engagement/HEAD/icons/icon96.png -------------------------------------------------------------------------------- /screenshots/tweets.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insin/manage-twitter-engagement/HEAD/screenshots/tweets.png -------------------------------------------------------------------------------- /screenshots/retweets.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insin/manage-twitter-engagement/HEAD/screenshots/retweets.png -------------------------------------------------------------------------------- /screenshots/chrome_options.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insin/manage-twitter-engagement/HEAD/screenshots/chrome_options.png -------------------------------------------------------------------------------- /screenshots/firefox_options.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insin/manage-twitter-engagement/HEAD/screenshots/firefox_options.png -------------------------------------------------------------------------------- /screenshots/suggested_tweets.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insin/manage-twitter-engagement/HEAD/screenshots/suggested_tweets.png -------------------------------------------------------------------------------- /options.js: -------------------------------------------------------------------------------- 1 | chrome.storage.local.get((config) => { 2 | let form = document.querySelector('form') 3 | 4 | for (let configItem in config) { 5 | form.elements[configItem].checked = config[configItem] || false 6 | } 7 | 8 | form.addEventListener('change', (e) => { 9 | config[e.target.name] = e.target.checked 10 | chrome.storage.local.set(config) 11 | }) 12 | }) 13 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 2, 3 | "name": "Manage Twitter Engagement", 4 | "description": "Manage \"engagement\" on Twitter by moving retweets and algorithmic tweets to their own lists, or hiding them completely", 5 | "homepage_url": "https://github.com/insin/manage-twitter-engagement/", 6 | "version": "2.3", 7 | "icons": { 8 | "16": "icons/icon16.png", 9 | "48": "icons/icon48.png", 10 | "64": "icons/icon64.png", 11 | "96": "icons/icon96.png", 12 | "128": "icons/icon128.png" 13 | }, 14 | "content_scripts": [ 15 | { 16 | "matches": [ 17 | "https://twitter.com/*" 18 | ], 19 | "js": ["./manage-twitter-engagement.user.js"] 20 | } 21 | ], 22 | "options_ui": { 23 | "page": "options.html" 24 | }, 25 | "permissions": ["storage"] 26 | } 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018, Jonny Buchanan 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so, 8 | subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | -------------------------------------------------------------------------------- /options.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 26 | 27 | 28 |
29 |
30 | 34 |
35 |
36 |
37 | 41 |
42 |
43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Manage Twitter Engagement 2 | 3 | ![](icons/icon128.png) 4 | 5 | **Manage "engagement" on Twitter by moving retweets and algorithmic tweets to their own lists, or hiding them completely** 6 | 7 | Retweets and algorithmic timeline tweets on Twitter bring tension and emotion to a scene that already has too much of both. 8 | 9 | Manage Twitter Engagement moves them to their own lists on the homepage to reduce the default amount of "engagement" on Twitter, allowing you to opt-in to extra tweets when you have the headspace for them, instead of mixing them all together in your feed. 10 | 11 | It also provides options to completely opt out of seeing retweets and algorithmic tweets. 12 | 13 | * [Install Chrome Extension](https://chrome.google.com/webstore/detail/manage-twitter-engagement/epgepgoafebogggijjemimfjbkidblia) 14 | * [Install Firefox Add-on](https://addons.mozilla.org/en-US/firefox/addon/manage-twitter-engagement/) 15 | * [Install as a user script](https://github.com/insin/manage-twitter-engagement/raw/master/manage-twitter-engagement.user.js) (requires a [user script manager](https://greasyfork.org/en#home-step-1)) 16 | 17 | ## Screenshots 18 | 19 | Use the new section headings added by the extension to only show specific types of tweets: 20 | 21 | ![Screenshot of tweets in their own list on the Twitter homepage](screenshots/tweets.png) 22 | 23 | ![Screenshot of retweets in their own list on the Twitter homepage](screenshots/retweets.png) 24 | 25 | ![Screenshot of algorithmic tweets in their own list on the Twitter homepage](screenshots/suggested_tweets.png) 26 | 27 | Chrome extension options dialog: 28 | 29 | ![Screenshot of the options UI in Chrome](screenshots/chrome_options.png) 30 | 31 | Firefox extension page options: 32 | 33 | ![Screenshot of the options UI in Firefox](screenshots/firefox_options.png) 34 | 35 | ## Options 36 | 37 | ### Hide "Retweets" section 38 | 39 | Enable this option to completely hide retweets. 40 | 41 | ### Hide "Suggested tweets" section 42 | 43 | Enable this option to completely hide algorithmic tweets. 44 | 45 | > Hiding both sections will remove this extension's section headings from the Twitter homepage 46 | 47 | ## Attribution 48 | 49 | Icon adapted from "Twitter free icon" by [Icomoon](https://icomoon.io/) from [www.flaticon.com](https://www.flaticon.com/), [CC 3.0 BY](https://creativecommons.org/licenses/by/3.0/) 50 | -------------------------------------------------------------------------------- /icons/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | image/svg+xmlX 47 | -------------------------------------------------------------------------------- /manage-twitter-engagement.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name Manage Twitter Engagement 3 | // @description Manage "engagement" on Twitter by moving retweets and algorithmic tweets to their own lists 4 | // @namespace https://github.com/insin/manage-twitter-engagement/ 5 | // @match https://twitter.com/* 6 | // @version 8 7 | // ==/UserScript== 8 | 9 | // Identify retweets by by their retweet id in element data 10 | const RETWEET_SELECTOR = 'div[data-retweet-id]' 11 | 12 | // Identify retweetlikes by the heart icon in their context header 13 | const RETWEETLIKE_SELECTOR = '.tweet-context .Icon--heartBadge' 14 | 15 | // State 16 | let config 17 | let displayedTweetType 18 | let streamObserver 19 | let $tabs 20 | 21 | let getAllTweets = () => document.querySelectorAll('#stream-items-id > .stream-item') 22 | 23 | function activateTab($tab) { 24 | $tab.classList.add('is-active') 25 | $tab.classList.remove('u-textUserColor') 26 | $tab.querySelector('span').classList.remove('u-hiddenVisually') 27 | $tab.querySelector('a').classList.add('u-hiddenVisually') 28 | 29 | // Deactivate the previously-active tab 30 | let $otherTab = $tab.parentNode.firstElementChild 31 | do { 32 | if ($otherTab !== $tab && $otherTab.classList.contains('is-active')) { 33 | $otherTab.classList.remove('is-active') 34 | $otherTab.classList.add('u-textUserColor') 35 | $otherTab.querySelector('span').classList.add('u-hiddenVisually') 36 | $otherTab.querySelector('a').classList.remove('u-hiddenVisually') 37 | break 38 | } 39 | } while ($otherTab = $otherTab.nextElementSibling) 40 | } 41 | 42 | function toggleDisplayedTweets(tweets) { 43 | for (let tweet of tweets) { 44 | if (tweet.querySelector(RETWEETLIKE_SELECTOR)) { 45 | tweet.style.display = displayedTweetType === 'suggested' ? '' : 'none' 46 | } 47 | else if (tweet.querySelector(RETWEET_SELECTOR)) { 48 | tweet.style.display = displayedTweetType === 'retweets' ? '' : 'none' 49 | } 50 | else { 51 | tweet.style.display = displayedTweetType === 'tweets' ? '' : 'none' 52 | } 53 | } 54 | } 55 | 56 | function injectUI() { 57 | // Don't do anything if we don't need a UI 58 | if (config.hideRetweets && config.hideSuggestedTweets) { 59 | return 60 | } 61 | 62 | // It seems like Twitter caches some homepage content on navigation and restores it later? 63 | // Without this, navigating from Home → Notifications → Home results in a non-functional dupe. 64 | if (document.querySelector('#mte_tabs')) { 65 | document.querySelector('#mte_tabs').remove() 66 | } 67 | 68 | $tabs = document.createElement('div') 69 | $tabs.id = 'mte_tabs' 70 | $tabs.innerHTML = `
71 |
72 | 89 |
90 |
` 91 | 92 | for (let $tab of $tabs.querySelectorAll('li')) { 93 | if ($tab.dataset.type === 'retweets' && config.hideRetweets || 94 | $tab.dataset.type === 'suggested' && config.hideSuggestedTweets) { 95 | $tab.style.display = 'none' 96 | continue 97 | } 98 | 99 | $tab.querySelector('a').addEventListener('click', (e) => { 100 | e.preventDefault() 101 | activateTab($tab) 102 | displayedTweetType = $tab.dataset.type 103 | toggleDisplayedTweets(getAllTweets()) 104 | }) 105 | } 106 | 107 | let $streamContainer = document.querySelector('div.stream-container') 108 | if ($streamContainer) { 109 | $streamContainer.insertAdjacentElement('beforebegin', $tabs) 110 | } 111 | } 112 | 113 | function startManagingEngagement() { 114 | // Always start on the tweets list 115 | displayedTweetType = 'tweets' 116 | 117 | // Deal with the initial batch of tweets 118 | toggleDisplayedTweets(getAllTweets()) 119 | 120 | // Watch the stream for the appearance of new tweets 121 | streamObserver = new MutationObserver(mutations => 122 | mutations.forEach(mutation => toggleDisplayedTweets(mutation.addedNodes)) 123 | ) 124 | streamObserver.observe(document.getElementById('stream-items-id'), { 125 | childList: true 126 | }) 127 | 128 | // Show controls 129 | injectUI() 130 | } 131 | 132 | function stopManagingEngagement() { 133 | // There won't be any UI to clean up if everything is hidden 134 | if ($tabs) { 135 | $tabs.remove() 136 | $tabs = null 137 | } 138 | streamObserver.disconnect() 139 | streamObserver = null 140 | } 141 | 142 | /** 143 | * When you navigate between sections, the Twitter website loads HTML for the 144 | * next page and injects it. 145 | * 146 | * Check for page changes by observing 's class attribute, which is 147 | * updated with loading state classes, then checking the URL to see if we're on 148 | * the homepage. 149 | */ 150 | new MutationObserver(() => { 151 | // Ignore loading states 152 | if (/(swift-loading|pushing-state)/.test(document.body.className)) return 153 | 154 | if (window.location.pathname === '/') { 155 | if (streamObserver == null) { 156 | streamObserver = {disconnect(){}} 157 | chrome.storage.local.get((storedConfig) => { 158 | // TODO Provide a way to configure the user script version 159 | // For now, manually replace with config = {hideRetweets: true, hideSuggestedTweets: true} 160 | config = storedConfig 161 | startManagingEngagement() 162 | }) 163 | } 164 | } 165 | else if (streamObserver) { 166 | stopManagingEngagement() 167 | } 168 | }).observe(document.body, {attributes: true, attributeFilter: ['class']}) 169 | --------------------------------------------------------------------------------