├── .gitignore ├── img ├── icon.48.png └── icon.96.png ├── README.md ├── Makefile ├── src ├── options.html └── redirect.js ├── manifest.json └── .github └── workflows └── dev.yml /.gitignore: -------------------------------------------------------------------------------- 1 | .web-extension-id 2 | web-ext-artifacts 3 | -------------------------------------------------------------------------------- /img/icon.48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/serverwentdown/nofeed/main/img/icon.48.png -------------------------------------------------------------------------------- /img/icon.96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/serverwentdown/nofeed/main/img/icon.96.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # WIP: Nofeed 3 | 4 | Feeds are great. But they are overwhelming, infinite and scream for your attention. It is slightly better to take an active role in consuming content. 5 | 6 | Nofeed forces you to curate your own content: 7 | 8 | - Subscribe to only the channels you love on YouTube 9 | - Use lists to limit the people you follow on Twitter 10 | 11 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | # To build an unlisted extension, get an API key from https://addons.mozilla.org/en-US/developers/addon/api/key/ 3 | JWT_ISSUER= 4 | JWT_SECRET= 5 | 6 | # Set these to run in an existing Firefox profile 7 | FIREFOX=nightly 8 | FIREFOX_PROFILE=default-nightly 9 | 10 | .PHONY: all 11 | all: nofeed.zip nofeed.xpi 12 | 13 | .PHONY: dev 14 | dev: 15 | web-ext run --firefox=${FIREFOX} --firefox-profile=${FIREFOX_PROFILE} 16 | 17 | nofeed.zip: img src manifest.json 18 | web-ext build 19 | nofeed.xpi: img src manifest.json 20 | web-ext sign --channel=unlisted --api-key=${JWT_ISSUER} --api-secret=${JWT_SECRET} 21 | -------------------------------------------------------------------------------- /src/options.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 | 7 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 2, 3 | "name": "Nofeed", 4 | "version": "3", 5 | "description": "Disable social feeds. Go feed yourself.", 6 | "icons": { 7 | "48": "img/icon.48.png", 8 | "96": "img/icon.96.png" 9 | }, 10 | "permissions": [ 11 | "storage" 12 | ], 13 | "content_scripts": [{ 14 | "matches": [ 15 | "https://www.youtube.com/*", 16 | "https://twitter.com/*" 17 | ], 18 | "js": [ 19 | "src/redirect.js" 20 | ] 21 | }], 22 | "browser_specific_settings": { 23 | "gecko": { 24 | "id": "addon@nofeed.makerforce.io", 25 | "strict_min_version": "70.0" 26 | } 27 | }, 28 | "options_ui": { 29 | "page": "src/options.html", 30 | "browser_style": true, 31 | "chrome_style": true 32 | } 33 | } 34 | 35 | -------------------------------------------------------------------------------- /.github/workflows/dev.yml: -------------------------------------------------------------------------------- 1 | name: Development builds 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | steps: 11 | 12 | - uses: actions/checkout@v2 13 | 14 | - name: Bump manifest version 15 | run: | 16 | sed -i "s/\"version\": \"\(.*\)\",/\"version\": \"\1.$(( ($(date +%s)/60/60) - ($(date -d '2020-01-01' +%s)/60/60) ))\",/g" manifest.json 17 | cat manifest.json 18 | 19 | - name: Build and sign Firefox Extension 20 | run: | 21 | npx web-ext sign --channel=unlisted --api-key=${{ secrets.JWT_ISSUER }} --api-secret=${{ secrets.JWT_SECRET }} 22 | mv web-ext-artifacts/nofeed*.xpi nofeed.xpi 23 | 24 | - name: Create prerelease 25 | id: create_release 26 | uses: actions/create-release@v1 27 | env: 28 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 29 | with: 30 | tag_name: prerelease 31 | release_name: Prerelease 32 | draft: false 33 | prerelease: true 34 | 35 | - name: Upload prerelease Firefox Extension 36 | uses: actions/upload-release-asset@v1 37 | env: 38 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 39 | with: 40 | upload_url: ${{ steps.create_release.outputs.upload_url }} 41 | asset_path: ./nofeed.xpi 42 | asset_name: nofeed.xpi 43 | asset_content_type: application/x-xpinstall 44 | -------------------------------------------------------------------------------- /src/redirect.js: -------------------------------------------------------------------------------- 1 | console.log("hello, world!") 2 | 3 | const sites = { 4 | "youtube": { 5 | _hosts: ["www.youtube.com"], 6 | _bad_urls: ["https://www.youtube.com/"], 7 | _default: "subscriptions", 8 | subscriptions: async function () { 9 | return "/feed/subscriptions"; 10 | }, 11 | _clean: async function () { 12 | const sidebar_mini = document.querySelector('ytd-mini-guide-renderer > div'); 13 | if (sidebar_mini) { 14 | sidebar_mini.childNodes.forEach(item => { 15 | const link = item.querySelector("a"); 16 | if (link && link.href && link.href.endsWith("youtube.com/")) { 17 | sidebar_mini.removeChild(item); 18 | } 19 | }); 20 | } 21 | 22 | const sidebar = document.querySelector('ytd-guide-section-renderer > div'); 23 | if (sidebar) { 24 | sidebar.childNodes.forEach(item => { 25 | const link = item.querySelector("a"); 26 | if (link && link.href && link.href.endsWith("youtube.com/")) { 27 | sidebar.removeChild(item); 28 | } 29 | }); 30 | } 31 | 32 | const logos = document.querySelectorAll('ytd-topbar-logo-renderer a'); 33 | logos.forEach(item => { 34 | item.style.pointerEvents = "none"; 35 | }); 36 | 37 | const home = document.querySelector('[page-subtype=home]'); 38 | if (home) { 39 | home.style.display = "none"; 40 | } 41 | }, 42 | }, 43 | "twitter": { 44 | _hosts: ["twitter.com"], 45 | _bad_urls: ["https://twitter.com/home"], 46 | _default: "lists", 47 | lists: async function () { 48 | const sidebar = document.querySelector("[role=navigation]"); 49 | if (!sidebar) { 50 | return null; 51 | } 52 | 53 | let url = null; 54 | sidebar.childNodes.forEach(item => { 55 | if (item.href && item.href.endsWith("/lists")) { 56 | url = item.href; 57 | } 58 | }); 59 | return url; 60 | }, 61 | _clean: async function () { 62 | const sidebar = document.querySelector("[role=banner] [role=navigation]"); 63 | if (sidebar) { 64 | sidebar.childNodes.forEach(item => { 65 | if (item.href && item.href.endsWith("/home")) { 66 | sidebar.removeChild(item); 67 | } 68 | }); 69 | } 70 | 71 | const heading = document.querySelector("[role=banner] [role=heading]"); 72 | if (heading) { 73 | const logo = heading.querySelector('[href="/home"]'); 74 | if (logo) { 75 | logo.style.pointerEvents = "none"; 76 | } 77 | } 78 | 79 | if (window.location.href.endsWith("/home")) { 80 | const timeline = document.querySelector('main [role=region]'); 81 | if (timeline) { 82 | timeline.style.display = "none"; 83 | } 84 | } 85 | }, 86 | }, 87 | }; 88 | 89 | // Configuration 90 | 91 | async function get_config_one(key) { 92 | return await browser.storage.sync.get(key)[key]; 93 | } 94 | 95 | // DOM 96 | 97 | let dom_content_loaded = false; 98 | async function wait_dom_content_loaded() { 99 | if (dom_content_loaded) { 100 | return; 101 | } 102 | return new Promise(resolve => { 103 | window.addEventListener("DOMContentLoaded", () => { 104 | resolve(); 105 | }); 106 | }); 107 | } 108 | 109 | // Main 110 | 111 | async function main() { 112 | const current_host = window.location.host; 113 | 114 | let site_name = null; 115 | for (let k in sites) { 116 | if (sites[k]._hosts.indexOf(current_host) >= 0) { 117 | site_name = k; 118 | } 119 | } 120 | let site = sites[site_name]; 121 | 122 | if (!site) { 123 | console.debug(`site ${current_host} not found`); 124 | return; 125 | } 126 | 127 | // Obtain user configuration 128 | let mode = await get_config_one(site_name + "-mode"); 129 | if (!mode) { 130 | mode = site._default; 131 | console.debug(`mode not set. using default mode ${mode}`); 132 | } 133 | const mode_options = await get_config_one(site_name + "-mode-" + mode + "-options"); 134 | console.debug(`mode ${mode} options ${mode_options}`); 135 | 136 | if (mode === "disabled") { 137 | return; 138 | } 139 | 140 | // Run cleanup scripts 141 | await site._clean(); 142 | setInterval(async () => { 143 | await site._clean(); 144 | }, 1000); 145 | 146 | 147 | const current_url = window.location.href; 148 | if (site._bad_urls.indexOf(current_url) < 0) { 149 | console.debug(`url is not bad`); 150 | return; 151 | } 152 | 153 | // Run the chosen mode 154 | if (!site[mode]) { 155 | return; 156 | } 157 | const url = await site[mode](mode_options); 158 | console.debug(`new url ${url}`); 159 | if (!url) { 160 | return; 161 | } 162 | window.location.href = url; 163 | } 164 | 165 | main().then(done => { 166 | // Success! 167 | }, err => { 168 | console.error(err); 169 | }); 170 | --------------------------------------------------------------------------------