├── .gitignore ├── assets ├── banner.jpg ├── toolbar.jpg └── context-menu.jpg ├── src ├── Chromium │ ├── introduction.js │ ├── img │ │ ├── icns │ │ │ ├── color │ │ │ │ ├── example.png │ │ │ │ ├── allow │ │ │ │ │ ├── 128.png │ │ │ │ │ ├── 16.png │ │ │ │ │ ├── 256.png │ │ │ │ │ ├── 32.png │ │ │ │ │ ├── 48.png │ │ │ │ │ ├── 512.png │ │ │ │ │ ├── 64.png │ │ │ │ │ └── 96.png │ │ │ │ └── disallow │ │ │ │ │ ├── 32.png │ │ │ │ │ └── 64.png │ │ │ └── mono │ │ │ │ ├── black │ │ │ │ ├── example.png │ │ │ │ ├── allow │ │ │ │ │ ├── 32.png │ │ │ │ │ └── 64.png │ │ │ │ └── disallow │ │ │ │ │ ├── 32.png │ │ │ │ │ └── 64.png │ │ │ │ └── white │ │ │ │ ├── example.png │ │ │ │ ├── allow │ │ │ │ ├── 32.png │ │ │ │ └── 64.png │ │ │ │ └── disallow │ │ │ │ ├── 32.png │ │ │ │ └── 64.png │ │ ├── toolbar-transparent.png │ │ ├── context-menu-transparent.png │ │ └── ft-logo.svg │ ├── colors.css │ ├── i18n │ │ ├── translate.js │ │ └── locales │ │ │ ├── en.json │ │ │ ├── fi.json │ │ │ ├── pl.json │ │ │ ├── lv.json │ │ │ ├── it.json │ │ │ ├── fr.json │ │ │ └── nl.json │ ├── content.css │ ├── manifest.json │ ├── popup.css │ ├── introduction.css │ ├── popup.js │ ├── options.js │ ├── options.css │ ├── background.js │ ├── popup.html │ ├── introduction.html │ └── content.js └── Gecko │ ├── introduction.js │ ├── img │ ├── icns │ │ ├── color │ │ │ ├── allow │ │ │ │ ├── 16.png │ │ │ │ ├── 32.png │ │ │ │ ├── 48.png │ │ │ │ ├── 64.png │ │ │ │ ├── 96.png │ │ │ │ ├── 128.png │ │ │ │ ├── 256.png │ │ │ │ └── 512.png │ │ │ ├── example.png │ │ │ └── disallow │ │ │ │ ├── 32.png │ │ │ │ └── 64.png │ │ └── mono │ │ │ ├── black │ │ │ ├── allow │ │ │ │ ├── 32.png │ │ │ │ └── 64.png │ │ │ ├── example.png │ │ │ └── disallow │ │ │ │ ├── 32.png │ │ │ │ └── 64.png │ │ │ └── white │ │ │ ├── allow │ │ │ ├── 32.png │ │ │ └── 64.png │ │ │ ├── example.png │ │ │ └── disallow │ │ │ ├── 32.png │ │ │ └── 64.png │ ├── toolbar-transparent.png │ ├── context-menu-transparent.png │ └── ft-logo.svg │ ├── colors.css │ ├── i18n │ ├── translate.js │ └── locales │ │ ├── en.json │ │ ├── fi.json │ │ ├── pl.json │ │ ├── lv.json │ │ ├── it.json │ │ ├── fr.json │ │ └── nl.json │ ├── content.css │ ├── manifest.json │ ├── popup.css │ ├── introduction.css │ ├── popup.js │ ├── options.js │ ├── options.css │ ├── background.js │ ├── popup.html │ ├── introduction.html │ └── content.js ├── release-notes.md ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── feature-request.yml │ └── bug-report.yml └── workflows │ └── release.yml ├── TRANSLATION-STANDARDS.md ├── LICENSE.md └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store -------------------------------------------------------------------------------- /assets/banner.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MStankiewiczOfficial/RedirectTube/HEAD/assets/banner.jpg -------------------------------------------------------------------------------- /assets/toolbar.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MStankiewiczOfficial/RedirectTube/HEAD/assets/toolbar.jpg -------------------------------------------------------------------------------- /assets/context-menu.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MStankiewiczOfficial/RedirectTube/HEAD/assets/context-menu.jpg -------------------------------------------------------------------------------- /src/Chromium/introduction.js: -------------------------------------------------------------------------------- 1 | document.getElementById("close").addEventListener("click", function () { 2 | window.close(); 3 | }); 4 | -------------------------------------------------------------------------------- /src/Gecko/introduction.js: -------------------------------------------------------------------------------- 1 | document.getElementById("close").addEventListener("click", function () { 2 | window.close(); 3 | }); 4 | -------------------------------------------------------------------------------- /src/Gecko/img/icns/color/allow/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MStankiewiczOfficial/RedirectTube/HEAD/src/Gecko/img/icns/color/allow/16.png -------------------------------------------------------------------------------- /src/Gecko/img/icns/color/allow/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MStankiewiczOfficial/RedirectTube/HEAD/src/Gecko/img/icns/color/allow/32.png -------------------------------------------------------------------------------- /src/Gecko/img/icns/color/allow/48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MStankiewiczOfficial/RedirectTube/HEAD/src/Gecko/img/icns/color/allow/48.png -------------------------------------------------------------------------------- /src/Gecko/img/icns/color/allow/64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MStankiewiczOfficial/RedirectTube/HEAD/src/Gecko/img/icns/color/allow/64.png -------------------------------------------------------------------------------- /src/Gecko/img/icns/color/allow/96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MStankiewiczOfficial/RedirectTube/HEAD/src/Gecko/img/icns/color/allow/96.png -------------------------------------------------------------------------------- /src/Gecko/img/icns/color/example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MStankiewiczOfficial/RedirectTube/HEAD/src/Gecko/img/icns/color/example.png -------------------------------------------------------------------------------- /src/Gecko/img/toolbar-transparent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MStankiewiczOfficial/RedirectTube/HEAD/src/Gecko/img/toolbar-transparent.png -------------------------------------------------------------------------------- /src/Chromium/img/icns/color/example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MStankiewiczOfficial/RedirectTube/HEAD/src/Chromium/img/icns/color/example.png -------------------------------------------------------------------------------- /src/Gecko/img/icns/color/allow/128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MStankiewiczOfficial/RedirectTube/HEAD/src/Gecko/img/icns/color/allow/128.png -------------------------------------------------------------------------------- /src/Gecko/img/icns/color/allow/256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MStankiewiczOfficial/RedirectTube/HEAD/src/Gecko/img/icns/color/allow/256.png -------------------------------------------------------------------------------- /src/Gecko/img/icns/color/allow/512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MStankiewiczOfficial/RedirectTube/HEAD/src/Gecko/img/icns/color/allow/512.png -------------------------------------------------------------------------------- /src/Chromium/img/icns/color/allow/128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MStankiewiczOfficial/RedirectTube/HEAD/src/Chromium/img/icns/color/allow/128.png -------------------------------------------------------------------------------- /src/Chromium/img/icns/color/allow/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MStankiewiczOfficial/RedirectTube/HEAD/src/Chromium/img/icns/color/allow/16.png -------------------------------------------------------------------------------- /src/Chromium/img/icns/color/allow/256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MStankiewiczOfficial/RedirectTube/HEAD/src/Chromium/img/icns/color/allow/256.png -------------------------------------------------------------------------------- /src/Chromium/img/icns/color/allow/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MStankiewiczOfficial/RedirectTube/HEAD/src/Chromium/img/icns/color/allow/32.png -------------------------------------------------------------------------------- /src/Chromium/img/icns/color/allow/48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MStankiewiczOfficial/RedirectTube/HEAD/src/Chromium/img/icns/color/allow/48.png -------------------------------------------------------------------------------- /src/Chromium/img/icns/color/allow/512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MStankiewiczOfficial/RedirectTube/HEAD/src/Chromium/img/icns/color/allow/512.png -------------------------------------------------------------------------------- /src/Chromium/img/icns/color/allow/64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MStankiewiczOfficial/RedirectTube/HEAD/src/Chromium/img/icns/color/allow/64.png -------------------------------------------------------------------------------- /src/Chromium/img/icns/color/allow/96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MStankiewiczOfficial/RedirectTube/HEAD/src/Chromium/img/icns/color/allow/96.png -------------------------------------------------------------------------------- /src/Chromium/img/toolbar-transparent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MStankiewiczOfficial/RedirectTube/HEAD/src/Chromium/img/toolbar-transparent.png -------------------------------------------------------------------------------- /src/Gecko/img/context-menu-transparent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MStankiewiczOfficial/RedirectTube/HEAD/src/Gecko/img/context-menu-transparent.png -------------------------------------------------------------------------------- /src/Gecko/img/icns/color/disallow/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MStankiewiczOfficial/RedirectTube/HEAD/src/Gecko/img/icns/color/disallow/32.png -------------------------------------------------------------------------------- /src/Gecko/img/icns/color/disallow/64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MStankiewiczOfficial/RedirectTube/HEAD/src/Gecko/img/icns/color/disallow/64.png -------------------------------------------------------------------------------- /src/Gecko/img/icns/mono/black/allow/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MStankiewiczOfficial/RedirectTube/HEAD/src/Gecko/img/icns/mono/black/allow/32.png -------------------------------------------------------------------------------- /src/Gecko/img/icns/mono/black/allow/64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MStankiewiczOfficial/RedirectTube/HEAD/src/Gecko/img/icns/mono/black/allow/64.png -------------------------------------------------------------------------------- /src/Gecko/img/icns/mono/black/example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MStankiewiczOfficial/RedirectTube/HEAD/src/Gecko/img/icns/mono/black/example.png -------------------------------------------------------------------------------- /src/Gecko/img/icns/mono/white/allow/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MStankiewiczOfficial/RedirectTube/HEAD/src/Gecko/img/icns/mono/white/allow/32.png -------------------------------------------------------------------------------- /src/Gecko/img/icns/mono/white/allow/64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MStankiewiczOfficial/RedirectTube/HEAD/src/Gecko/img/icns/mono/white/allow/64.png -------------------------------------------------------------------------------- /src/Gecko/img/icns/mono/white/example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MStankiewiczOfficial/RedirectTube/HEAD/src/Gecko/img/icns/mono/white/example.png -------------------------------------------------------------------------------- /src/Chromium/img/icns/color/disallow/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MStankiewiczOfficial/RedirectTube/HEAD/src/Chromium/img/icns/color/disallow/32.png -------------------------------------------------------------------------------- /src/Chromium/img/icns/color/disallow/64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MStankiewiczOfficial/RedirectTube/HEAD/src/Chromium/img/icns/color/disallow/64.png -------------------------------------------------------------------------------- /src/Chromium/img/icns/mono/black/example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MStankiewiczOfficial/RedirectTube/HEAD/src/Chromium/img/icns/mono/black/example.png -------------------------------------------------------------------------------- /src/Chromium/img/icns/mono/white/example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MStankiewiczOfficial/RedirectTube/HEAD/src/Chromium/img/icns/mono/white/example.png -------------------------------------------------------------------------------- /src/Chromium/img/context-menu-transparent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MStankiewiczOfficial/RedirectTube/HEAD/src/Chromium/img/context-menu-transparent.png -------------------------------------------------------------------------------- /src/Chromium/img/icns/mono/black/allow/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MStankiewiczOfficial/RedirectTube/HEAD/src/Chromium/img/icns/mono/black/allow/32.png -------------------------------------------------------------------------------- /src/Chromium/img/icns/mono/black/allow/64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MStankiewiczOfficial/RedirectTube/HEAD/src/Chromium/img/icns/mono/black/allow/64.png -------------------------------------------------------------------------------- /src/Chromium/img/icns/mono/white/allow/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MStankiewiczOfficial/RedirectTube/HEAD/src/Chromium/img/icns/mono/white/allow/32.png -------------------------------------------------------------------------------- /src/Chromium/img/icns/mono/white/allow/64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MStankiewiczOfficial/RedirectTube/HEAD/src/Chromium/img/icns/mono/white/allow/64.png -------------------------------------------------------------------------------- /src/Gecko/img/icns/mono/black/disallow/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MStankiewiczOfficial/RedirectTube/HEAD/src/Gecko/img/icns/mono/black/disallow/32.png -------------------------------------------------------------------------------- /src/Gecko/img/icns/mono/black/disallow/64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MStankiewiczOfficial/RedirectTube/HEAD/src/Gecko/img/icns/mono/black/disallow/64.png -------------------------------------------------------------------------------- /src/Gecko/img/icns/mono/white/disallow/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MStankiewiczOfficial/RedirectTube/HEAD/src/Gecko/img/icns/mono/white/disallow/32.png -------------------------------------------------------------------------------- /src/Gecko/img/icns/mono/white/disallow/64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MStankiewiczOfficial/RedirectTube/HEAD/src/Gecko/img/icns/mono/white/disallow/64.png -------------------------------------------------------------------------------- /src/Chromium/img/icns/mono/black/disallow/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MStankiewiczOfficial/RedirectTube/HEAD/src/Chromium/img/icns/mono/black/disallow/32.png -------------------------------------------------------------------------------- /src/Chromium/img/icns/mono/black/disallow/64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MStankiewiczOfficial/RedirectTube/HEAD/src/Chromium/img/icns/mono/black/disallow/64.png -------------------------------------------------------------------------------- /src/Chromium/img/icns/mono/white/disallow/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MStankiewiczOfficial/RedirectTube/HEAD/src/Chromium/img/icns/mono/white/disallow/32.png -------------------------------------------------------------------------------- /src/Chromium/img/icns/mono/white/disallow/64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MStankiewiczOfficial/RedirectTube/HEAD/src/Chromium/img/icns/mono/white/disallow/64.png -------------------------------------------------------------------------------- /release-notes.md: -------------------------------------------------------------------------------- 1 | # 1.11.1 (25122) 2 | 3 | ## Release Notes 4 | 5 | - A bug was fixed that caused options not to load and not to save. 6 | 7 | > [!WARNING] 8 | > For Firefox, the `-unsigned.xpi` artifact will most likely not install. Use the signed version (`-signed.xpi`) or download from [Firefox Add-ons](https://addons.mozilla.org/firefox/addon/redirecttube/). For Chromium-based browsers, download the `-chromium-unsigned.zip` and load it unpacked in Developer Mode. -------------------------------------------------------------------------------- /src/Chromium/colors.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --background: #f5f5f5; 3 | --color: #000; 4 | --color2: #cb1314; 5 | --color2-transparent: #cb131370; 6 | --url: #00308a; 7 | --url-hover: #0056b3; 8 | } 9 | 10 | .browser-light { 11 | display: block; 12 | } 13 | 14 | .browser-dark { 15 | display: none; 16 | } 17 | 18 | @media screen and (prefers-color-scheme: dark) { 19 | :root { 20 | --background: #181818; 21 | --color: #fff; 22 | --color2: #fff; 23 | --color2-transparent: #ffffff70; 24 | --url: #679ef1; 25 | --url-hover: #8cb4f8; 26 | } 27 | .browser-light { 28 | display: none; 29 | } 30 | 31 | .browser-dark { 32 | display: block; 33 | } 34 | } -------------------------------------------------------------------------------- /src/Gecko/colors.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --background: #f5f5f5; 3 | --color: #000; 4 | --color2: #cb1314; 5 | --color2-transparent: #cb131370; 6 | --url: #00308a; 7 | --url-hover: #0056b3; 8 | } 9 | 10 | .browser-light { 11 | display: block; 12 | } 13 | 14 | .browser-dark { 15 | display: none; 16 | } 17 | 18 | @media screen and (prefers-color-scheme: dark) { 19 | :root { 20 | --background: #181818; 21 | --color: #fff; 22 | --color2: #fff; 23 | --color2-transparent: #ffffff70; 24 | --url: #679ef1; 25 | --url-hover: #8cb4f8; 26 | } 27 | .browser-light { 28 | display: none; 29 | } 30 | 31 | .browser-dark { 32 | display: block; 33 | } 34 | } -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: mstankiewicz # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 12 | polar: # Replace with a single Polar username 13 | buy_me_a_coffee: # Replace with a single Buy Me a Coffee username 14 | thanks_dev: # Replace with a single thanks.dev username 15 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 16 | -------------------------------------------------------------------------------- /src/Gecko/i18n/translate.js: -------------------------------------------------------------------------------- 1 | (() => { 2 | const extensionApi = typeof chrome !== "undefined" ? chrome : browser; 3 | 4 | let langList = ["en", "pl", "nl", "fi", "fr", "it", "lv"] 5 | 6 | let lang = navigator.language.split("-")[0] 7 | if (!langList.includes(lang)) { 8 | lang = "en" 9 | } 10 | 11 | extensionApi.storage.local.set({ lang: lang }) 12 | 13 | document.documentElement.lang = lang 14 | 15 | let menuTitleRedirect = "" 16 | 17 | fetch(`i18n/locales/${lang}.json`) 18 | .then(response => response.json()) 19 | .then(data => { 20 | let elements = document.querySelectorAll("[data-i18n]") 21 | elements.forEach(element => { 22 | let key = element.getAttribute("data-i18n").split(".") 23 | let value = data 24 | key.forEach(k => { 25 | value = value[k] 26 | }) 27 | element.innerHTML = value 28 | }) 29 | menuTitleRedirect = data.ui.contextMenu.redirect 30 | }); 31 | })(); 32 | 33 | -------------------------------------------------------------------------------- /src/Chromium/i18n/translate.js: -------------------------------------------------------------------------------- 1 | (() => { 2 | const extensionApi = typeof chrome !== "undefined" ? chrome : browser; 3 | 4 | let langList = ["en", "pl", "nl", "fi", "fr", "it", "lv"] 5 | 6 | let lang = navigator.language.split("-")[0] 7 | if (!langList.includes(lang)) { 8 | lang = "en" 9 | } 10 | 11 | extensionApi.storage.local.set({ lang: lang }) 12 | 13 | document.documentElement.lang = lang 14 | 15 | let menuTitleRedirect = "" 16 | 17 | fetch(`i18n/locales/${lang}.json`) 18 | .then(response => response.json()) 19 | .then(data => { 20 | let elements = document.querySelectorAll("[data-i18n]") 21 | elements.forEach(element => { 22 | let key = element.getAttribute("data-i18n").split(".") 23 | let value = data 24 | key.forEach(k => { 25 | value = value[k] 26 | }) 27 | element.innerHTML = value 28 | }) 29 | menuTitleRedirect = data.ui.contextMenu.redirect 30 | }); 31 | })(); 32 | 33 | -------------------------------------------------------------------------------- /src/Chromium/content.css: -------------------------------------------------------------------------------- 1 | .redirecttube-redirection-div { 2 | position: absolute; 3 | z-index: 10000; 4 | height: 47px !important; 5 | align-items: center !important; 6 | padding: 0 10px; 7 | background-color: #171717cc !important; 8 | color: #fff !important; 9 | font: 400 16px/16px "YouTube Noto",Roboto,Arial,Helvetica,sans-serif !important; 10 | border: none; 11 | border-radius: 0 2px 2px 0; 12 | transition: all 0.3s; 13 | cursor: pointer; 14 | } 15 | 16 | .redirecttube-redirection-open-button { 17 | display: flex !important; 18 | align-items: center !important; 19 | justify-content: center !important; 20 | height: 100% !important; 21 | margin: 0 2px !important; 22 | color: #fff !important; 23 | text-decoration: none !important; 24 | } 25 | 26 | .redirecttube-redirection-open-button svg { 27 | height: 16px !important; 28 | fill: #fff !important; 29 | margin-left: 8px !important; 30 | margin-right: 2px !important; 31 | } 32 | 33 | .redirecttube-redirection-hide-button { 34 | display: flex !important; 35 | align-items: center !important; 36 | justify-content: center !important; 37 | height: 47px !important; 38 | width: 47px !important; 39 | margin: 0 2px !important; 40 | color: #fff !important; 41 | background-color: #171717cc !important; 42 | text-decoration: none !important; 43 | transform: translate(184px, -100%) !important; 44 | } -------------------------------------------------------------------------------- /src/Gecko/content.css: -------------------------------------------------------------------------------- 1 | .redirecttube-redirection-div { 2 | position: absolute; 3 | z-index: 10000; 4 | height: 47px !important; 5 | align-items: center !important; 6 | padding: 0 10px; 7 | background-color: #171717cc !important; 8 | color: #fff !important; 9 | font: 400 16px/16px "YouTube Noto",Roboto,Arial,Helvetica,sans-serif !important; 10 | border: none; 11 | border-radius: 0 2px 2px 0; 12 | transition: all 0.3s; 13 | cursor: pointer; 14 | } 15 | 16 | .redirecttube-redirection-open-button { 17 | display: flex !important; 18 | align-items: center !important; 19 | justify-content: center !important; 20 | height: 100% !important; 21 | margin: 0 2px !important; 22 | color: #fff !important; 23 | text-decoration: none !important; 24 | } 25 | 26 | .redirecttube-redirection-open-button svg { 27 | height: 16px !important; 28 | fill: #fff !important; 29 | margin-left: 8px !important; 30 | margin-right: 2px !important; 31 | } 32 | 33 | .redirecttube-redirection-hide-button { 34 | display: flex !important; 35 | align-items: center !important; 36 | justify-content: center !important; 37 | height: 47px !important; 38 | width: 47px !important; 39 | margin: 0 2px !important; 40 | color: #fff !important; 41 | background-color: #171717cc !important; 42 | text-decoration: none !important; 43 | transform: translate(184px, -100%) !important; 44 | } -------------------------------------------------------------------------------- /src/Chromium/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "RedirectTube", 3 | "description": "Open YouTube links in FreeTube", 4 | "author": "Michał Stankiewicz", 5 | "version": "1.11.1", 6 | "manifest_version": 3, 7 | "icons": { 8 | "16": "img/icns/color/allow/16.png", 9 | "32": "img/icns/color/allow/32.png", 10 | "48": "img/icns/color/allow/48.png", 11 | "64": "img/icns/color/allow/64.png", 12 | "96": "img/icns/color/allow/96.png", 13 | "128": "img/icns/color/allow/128.png", 14 | "256": "img/icns/color/allow/256.png", 15 | "512": "img/icns/color/allow/512.png" 16 | }, 17 | "background": { 18 | "service_worker": "background.js" 19 | }, 20 | "content_scripts": [ 21 | { 22 | "matches": [ 23 | "" 24 | ], 25 | "js": ["content.js"], 26 | "css": ["content.css"], 27 | "all_frames": true 28 | } 29 | ], 30 | "web_accessible_resources": [ 31 | { 32 | "resources": ["i18n/locales/*.json"], 33 | "matches": [""] 34 | } 35 | ], 36 | "permissions": [ 37 | "storage", 38 | "tabs", 39 | "activeTab", 40 | "contextMenus" 41 | ], 42 | "action": { 43 | "default_popup": "popup.html" 44 | }, 45 | "options_ui": { 46 | "page": "options.html" 47 | }, 48 | "host_permissions": [ 49 | "http://*/*", 50 | "https://*/*" 51 | ] 52 | } -------------------------------------------------------------------------------- /TRANSLATION-STANDARDS.md: -------------------------------------------------------------------------------- 1 | 2 | # Translation Standards for RedirectTube 3 | 4 | All translations for RedirectTube are managed via the Codeberg platform: https://translate.codeberg.org/projects/redirecttube 5 | 6 | ## General Requirements 7 | - Translations must be clear, contextually accurate, and user-friendly. 8 | - Prefer idiomatic translations over literal ones. 9 | - Do not translate brand names, product names, or application names (e.g., FreeTube, YouTube). 10 | - Maintain consistent terminology throughout the extension. 11 | - Do not translate technical strings that do not make sense in the target language. 12 | - If in doubt, consult the project author or the community. 13 | 14 | ## Publication Criteria 15 | - A translation must be at least 85% complete (measured by the number of translated words compared to the original). 16 | - Translations below 85% completion will not be included in the public release of the extension. 17 | - Each translation should be reviewed for linguistic and technical accuracy. 18 | 19 | 20 | ## Platform 21 | - All translations must be submitted and managed via the Codeberg platform: https://translate.codeberg.org/projects/redirecttube 22 | - Do not submit translations as files or pull requests; use the Codeberg interface for all contributions. 23 | 24 | ## Final Notes 25 | - Translations should reflect the spirit of the RedirectTube project and help users of the target language use the extension easily. 26 | - Suggestions and corrections can be submitted via the project's GitHub page or directly on the Codeberg translation platform. 27 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.yml: -------------------------------------------------------------------------------- 1 | name: Feature request 2 | description: Suggest an idea for RedirectTube! 3 | title: '[FR]: ' 4 | labels: 5 | - enhancement 6 | assignees: 7 | - MStankiewiczOfficial 8 | body: 9 | - type: markdown 10 | attributes: 11 | value: Accepted are answers in `English`, `Polish`. 12 | - type: textarea 13 | id: problem 14 | attributes: 15 | label: Is your feature request related to a problem? Please describe. 16 | description: A clear and concise description of what the problem is. 17 | placeholder: I'm always frustrated when [...] 18 | - type: textarea 19 | id: proposed-solution 20 | attributes: 21 | label: Describe the solution you'd like 22 | description: A clear and concise description of what you want to happen. 23 | validations: 24 | required: true 25 | - type: textarea 26 | id: alternatives 27 | attributes: 28 | label: Describe alternatives you've considered 29 | description: >- 30 | A clear and concise description of any alternative solutions or features 31 | you've considered. 32 | - type: textarea 33 | id: context 34 | attributes: 35 | label: Additional context 36 | description: Add any other context or screenshots about the feature request here. 37 | - type: checkboxes 38 | id: statement 39 | attributes: 40 | label: Statement 41 | options: 42 | - label: >- 43 | I verified that the suggestion has not already been requested on 44 | GitHub. 45 | required: true -------------------------------------------------------------------------------- /src/Gecko/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "RedirectTube", 3 | "description": "Open YouTube links in FreeTube", 4 | "author": "Michał Stankiewicz", 5 | "version": "1.11.1", 6 | "manifest_version": 3, 7 | "icons": { 8 | "16": "img/icns/color/allow/16.png", 9 | "32": "img/icns/color/allow/32.png", 10 | "48": "img/icns/color/allow/48.png", 11 | "64": "img/icns/color/allow/64.png", 12 | "96": "img/icns/color/allow/96.png", 13 | "128": "img/icns/color/allow/128.png", 14 | "256": "img/icns/color/allow/256.png", 15 | "512": "img/icns/color/allow/512.png" 16 | }, 17 | "background": { 18 | "scripts": [ 19 | "background.js" 20 | ] 21 | }, 22 | "content_scripts": [ 23 | { 24 | "matches": [ 25 | "" 26 | ], 27 | "js": ["content.js"], 28 | "css": ["content.css"], 29 | "all_frames": true 30 | } 31 | ], 32 | "web_accessible_resources": [ 33 | { 34 | "resources": ["i18n/locales/*.json"], 35 | "matches": [""] 36 | } 37 | ], 38 | "permissions": [ 39 | "storage", 40 | "tabs", 41 | "activeTab", 42 | "contextMenus" 43 | ], 44 | "action": { 45 | "default_popup": "popup.html" 46 | }, 47 | "options_ui": { 48 | "page": "options.html" 49 | }, 50 | "host_permissions": [ 51 | "http://*/*", 52 | "https://*/*" 53 | ], 54 | "browser_specific_settings": { 55 | "gecko": { 56 | "id": "redirecttube@stankiewiczm.eu", 57 | "strict_min_version": "109.0" 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /src/Gecko/popup.css: -------------------------------------------------------------------------------- 1 | @import url('colors.css'); 2 | 3 | body { 4 | background-color: var(--background); 5 | color: var(--color); 6 | font-family: sans-serif; 7 | width: 300px; 8 | padding: 0px; 9 | margin: 0; 10 | cursor: default; 11 | -webkit-user-select: none; 12 | user-select: none; 13 | } 14 | 15 | #title { 16 | width: 100%; 17 | padding: 0; 18 | text-align: center; 19 | background-color: #7979793f; 20 | } 21 | 22 | .logo { 23 | margin: 0; 24 | padding: 0.7em; 25 | } 26 | 27 | #content { 28 | padding: 0; 29 | } 30 | 31 | #content button { 32 | width: 100%; 33 | padding: 0.5em; 34 | margin: 0; 35 | border: #79797979 solid 1px; 36 | background-color: #79797979; 37 | color: var(--color); 38 | font-size: 1em; 39 | transition: all 0.15s; 40 | } 41 | 42 | #content button:hover { 43 | background-color: #797979b9; 44 | } 45 | 46 | #content button:disabled { 47 | background-color: #79797979; 48 | color: #44444479; 49 | cursor: not-allowed; 50 | } 51 | 52 | #content button.primary { 53 | color: #fff; 54 | background-color: #915050; 55 | border: #a23131 solid 1px; 56 | } 57 | 58 | #content button.primary:hover { 59 | background-color: #a23131; 60 | } 61 | 62 | #content button.primary:disabled { 63 | background-color: #79797979; 64 | color: #79797979; 65 | border: #79797979 solid 1px; 66 | cursor: not-allowed; 67 | } 68 | 69 | a { 70 | color: var(--url); 71 | transition: all 0.3s; 72 | } 73 | 74 | a:hover { 75 | color: var(--url-hover); 76 | } 77 | 78 | #errorText { 79 | color: rgb(201, 67, 67); 80 | font-size: 1em; 81 | text-align: center; 82 | padding: 0.5em; 83 | } 84 | 85 | footer { 86 | text-align: center; 87 | padding: 0; 88 | } 89 | 90 | footer p { 91 | font-size: 0.8em; 92 | margin: 0; 93 | padding: 0.5em; 94 | } -------------------------------------------------------------------------------- /src/Chromium/popup.css: -------------------------------------------------------------------------------- 1 | @import url('colors.css'); 2 | 3 | body { 4 | background-color: var(--background); 5 | color: var(--color); 6 | font-family: sans-serif; 7 | width: 300px; 8 | padding: 0px; 9 | margin: 0; 10 | cursor: default; 11 | -webkit-user-select: none; 12 | user-select: none; 13 | } 14 | 15 | #title { 16 | width: 100%; 17 | padding: 0; 18 | text-align: center; 19 | background-color: #7979793f; 20 | } 21 | 22 | .logo { 23 | margin: 0; 24 | padding: 0.7em; 25 | } 26 | 27 | #content { 28 | padding: 0; 29 | } 30 | 31 | #content button { 32 | width: 100%; 33 | padding: 0.5em; 34 | margin: 0; 35 | border: #79797979 solid 1px; 36 | background-color: #79797979; 37 | color: var(--color); 38 | font-size: 1em; 39 | transition: all 0.15s; 40 | } 41 | 42 | #content button:hover { 43 | background-color: #797979b9; 44 | } 45 | 46 | #content button:disabled { 47 | background-color: #79797979; 48 | color: #44444479; 49 | cursor: not-allowed; 50 | } 51 | 52 | #content button.primary { 53 | color: #fff; 54 | background-color: #915050; 55 | border: #a23131 solid 1px; 56 | } 57 | 58 | #content button.primary:hover { 59 | background-color: #a23131; 60 | } 61 | 62 | #content button.primary:disabled { 63 | background-color: #79797979; 64 | color: #79797979; 65 | border: #79797979 solid 1px; 66 | cursor: not-allowed; 67 | } 68 | 69 | a { 70 | color: var(--url); 71 | transition: all 0.3s; 72 | } 73 | 74 | a:hover { 75 | color: var(--url-hover); 76 | } 77 | 78 | #errorText { 79 | color: rgb(201, 67, 67); 80 | font-size: 1em; 81 | text-align: center; 82 | padding: 0.5em; 83 | } 84 | 85 | footer { 86 | text-align: center; 87 | padding: 0; 88 | } 89 | 90 | footer p { 91 | font-size: 0.8em; 92 | margin: 0; 93 | padding: 0.5em; 94 | } -------------------------------------------------------------------------------- /src/Gecko/introduction.css: -------------------------------------------------------------------------------- 1 | @import url('colors.css'); 2 | 3 | body { 4 | background-color: var(--background); 5 | color: var(--color); 6 | font-family: sans-serif; 7 | padding: 0; 8 | margin: 0; 9 | } 10 | 11 | #content { 12 | padding-bottom: 64px; 13 | } 14 | 15 | #title { 16 | width: 100%; 17 | padding: 0; 18 | text-align: center; 19 | background-color: #cb1314; 20 | } 21 | 22 | .logo { 23 | display: block; 24 | margin-left: auto; 25 | margin-right: auto; 26 | padding: 1.5em; 27 | } 28 | 29 | h1 { 30 | margin-top: 1em; 31 | margin-left: 32px; 32 | font-size: 1.7em; 33 | } 34 | 35 | a { 36 | color: var(--url); 37 | } 38 | 39 | a:hover { 40 | color: var(--url-hover); 41 | } 42 | 43 | .section { 44 | display: flex; 45 | flex-direction: column; 46 | align-items: center; 47 | margin: 0.75em 32px; 48 | } 49 | 50 | .socials { 51 | display: flex; 52 | position: fixed; 53 | align-items: flex-end; 54 | bottom: 16px; 55 | left: 50%; 56 | transform: translateX(-50%); 57 | } 58 | 59 | .social { 60 | display: flex; 61 | align-items: center; 62 | margin: 0 4px; 63 | padding: 6px 8px; 64 | border-radius: 8px; 65 | transition: all 0.3s; 66 | } 67 | 68 | .social-title { 69 | display: none; 70 | color: #ffffff; 71 | margin-right: 6px; 72 | } 73 | 74 | .social-logo { 75 | width: 24px; 76 | height: 24px; 77 | transition: all 0.3s; 78 | fill: #ffffffd0; 79 | } 80 | .social-logo:hover { 81 | fill: #ffffff; 82 | } 83 | 84 | button { 85 | padding: 0.5em 1em; 86 | margin: 2px; 87 | border: #79797979 solid 1px; 88 | border-radius: 4px; 89 | background-color: #79797979; 90 | color: var(--color); 91 | font-size: 0.9em; 92 | transition: all 0.3s; 93 | cursor: pointer; 94 | } 95 | 96 | button:hover { 97 | background-color: #797979b9; 98 | } 99 | 100 | button:disabled { 101 | background-color: #79797979; 102 | color: #79797979; 103 | cursor: not-allowed; 104 | } 105 | 106 | img { 107 | width: 100%; 108 | max-width: 700px; 109 | height: auto; 110 | } 111 | 112 | .images { 113 | display: flex; 114 | flex-wrap: wrap; 115 | justify-content: center; 116 | margin: 0.75em 32px; 117 | } 118 | 119 | .image { 120 | text-align: center; 121 | transition: all 0.3s; 122 | } -------------------------------------------------------------------------------- /src/Chromium/introduction.css: -------------------------------------------------------------------------------- 1 | @import url('colors.css'); 2 | 3 | body { 4 | background-color: var(--background); 5 | color: var(--color); 6 | font-family: sans-serif; 7 | padding: 0; 8 | margin: 0; 9 | } 10 | 11 | #content { 12 | padding-bottom: 64px; 13 | } 14 | 15 | #title { 16 | width: 100%; 17 | padding: 0; 18 | text-align: center; 19 | background-color: #cb1314; 20 | } 21 | 22 | .logo { 23 | display: block; 24 | margin-left: auto; 25 | margin-right: auto; 26 | padding: 1.5em; 27 | } 28 | 29 | h1 { 30 | margin-top: 1em; 31 | margin-left: 32px; 32 | font-size: 1.7em; 33 | } 34 | 35 | a { 36 | color: var(--url); 37 | } 38 | 39 | a:hover { 40 | color: var(--url-hover); 41 | } 42 | 43 | .section { 44 | display: flex; 45 | flex-direction: column; 46 | align-items: center; 47 | margin: 0.75em 32px; 48 | } 49 | 50 | .socials { 51 | display: flex; 52 | position: fixed; 53 | align-items: flex-end; 54 | bottom: 16px; 55 | left: 50%; 56 | transform: translateX(-50%); 57 | } 58 | 59 | .social { 60 | display: flex; 61 | align-items: center; 62 | margin: 0 4px; 63 | padding: 6px 8px; 64 | border-radius: 8px; 65 | transition: all 0.3s; 66 | } 67 | 68 | .social-title { 69 | display: none; 70 | color: #ffffff; 71 | margin-right: 6px; 72 | } 73 | 74 | .social-logo { 75 | width: 24px; 76 | height: 24px; 77 | transition: all 0.3s; 78 | fill: #ffffffd0; 79 | } 80 | .social-logo:hover { 81 | fill: #ffffff; 82 | } 83 | 84 | button { 85 | padding: 0.5em 1em; 86 | margin: 2px; 87 | border: #79797979 solid 1px; 88 | border-radius: 4px; 89 | background-color: #79797979; 90 | color: var(--color); 91 | font-size: 0.9em; 92 | transition: all 0.3s; 93 | cursor: pointer; 94 | } 95 | 96 | button:hover { 97 | background-color: #797979b9; 98 | } 99 | 100 | button:disabled { 101 | background-color: #79797979; 102 | color: #79797979; 103 | cursor: not-allowed; 104 | } 105 | 106 | img { 107 | width: 100%; 108 | max-width: 700px; 109 | height: auto; 110 | } 111 | 112 | .images { 113 | display: flex; 114 | flex-wrap: wrap; 115 | justify-content: center; 116 | margin: 0.75em 32px; 117 | } 118 | 119 | .image { 120 | text-align: center; 121 | transition: all 0.3s; 122 | } -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | ## RedirectTube © 2025 by Michał Stankiewicz is licensed under [CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/). 2 | 3 | 4 | 5 | #### Name *FreeTube* and FreeTube logo are the property of the [creators of FreeTube](https://docs.freetubeapp.io/credits/). Neither I nor the extension are associated with them. [FreeTube](https://freetubeapp.io/) is licensed under [AGPL-3.0 license](https://github.com/FreeTubeApp/FreeTube/blob/master/LICENSE). 6 | 7 | ### You are free to: 8 | 9 | - Share — copy and redistribute the material in any medium or format 10 | - Adapt — remix, transform, and build upon the material 11 | - The licensor cannot revoke these freedoms as long as you follow the license terms. 12 | 13 | ### Under the following terms: 14 | 15 | - Attribution — You must give appropriate credit , provide a link to the license, and indicate if changes were made . You may do so in any reasonable manner, but not in any way that suggests the licensor endorses you or your use. 16 | - NonCommercial — You may not use the material for commercial purposes . 17 | - ShareAlike — If you remix, transform, or build upon the material, you must distribute your contributions under the same license as the original. 18 | - No additional restrictions — You may not apply legal terms or technological measures that legally restrict others from doing anything the license permits. 19 | 20 | ### Notices: 21 | 22 | You do not have to comply with the license for elements of the material in the public domain or where your use is permitted by an applicable exception or limitation . 23 | 24 | No warranties are given. The license may not give you all of the permissions necessary for your intended use. For example, other rights such as publicity, privacy, or moral rights may limit how you use the material. 25 | 26 | ### To view a copy of this license, visit https://creativecommons.org/licenses/by-nc-sa/4.0/ 27 | -------------------------------------------------------------------------------- /src/Gecko/i18n/locales/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "ui": { 3 | "close": "Close", 4 | "createdby": "Created with ❤️ by Michał Stankiewicz", 5 | "button": { 6 | "redirect": "Open this site in FreeTube", 7 | "options": "Options", 8 | "opinion": "Give your opinion", 9 | "suggestion": "Make a suggestion", 10 | "issue": "Report an issue" 11 | }, 12 | "contextMenu": { 13 | "redirect": "Open in FreeTube" 14 | }, 15 | "yes": "Yes", 16 | "no": "No", 17 | "iframeButton": { 18 | "redirect": "Watch on" 19 | }, 20 | "beta": { 21 | "label": "Beta" 22 | }, 23 | "error": { 24 | "e404": "Cannot open this page in FreeTube." 25 | } 26 | }, 27 | "introduction": { 28 | "page": { 29 | "title": "RedirectTube • Introduction" 30 | }, 31 | "usage": { 32 | "title": "Usage", 33 | "toolbar": "Click the icon when you are on a YouTube video page", 34 | "contextMenu": "or right-click on the url of a YouTube video" 35 | } 36 | }, 37 | "options": { 38 | "page": { 39 | "title": "RedirectTube" 40 | }, 41 | "options": { 42 | "title": "Options" 43 | }, 44 | "popupBehavior": { 45 | "label": "When clicking the extension icon", 46 | "showPopup": "Show menu", 47 | "redirect": "Redirect to FreeTube" 48 | }, 49 | "autoRedirectLinks": { 50 | "label": "Automatically redirect YouTube links when clicked" 51 | }, 52 | "iframeButton": { 53 | "label": "Show button in iframes" 54 | }, 55 | "extensionIcon": { 56 | "label": "Extension icon" 57 | }, 58 | "footer": { 59 | "version": "RedirectTube vX.Y.Z", 60 | "kofi": "If you like this extension, consider buying me a tea 🍵", 61 | "affiliation": "RedirectTube is not affiliated with FreeTube or its creators.
FreeTube is licensed under the AGPL-3.0 licence.
The name \"FreeTube\" and FreeTube logo are the property of the creators of FreeTube." 62 | } 63 | }, 64 | "popup": { 65 | "page": { 66 | "title": "RedirectTube" 67 | } 68 | } 69 | } -------------------------------------------------------------------------------- /src/Chromium/i18n/locales/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "ui": { 3 | "close": "Close", 4 | "createdby": "Created with ❤️ by Michał Stankiewicz", 5 | "button": { 6 | "redirect": "Open this site in FreeTube", 7 | "options": "Options", 8 | "opinion": "Give your opinion", 9 | "suggestion": "Make a suggestion", 10 | "issue": "Report an issue" 11 | }, 12 | "contextMenu": { 13 | "redirect": "Open in FreeTube" 14 | }, 15 | "yes": "Yes", 16 | "no": "No", 17 | "iframeButton": { 18 | "redirect": "Watch on" 19 | }, 20 | "beta": { 21 | "label": "Beta" 22 | }, 23 | "error": { 24 | "e404": "Cannot open this page in FreeTube." 25 | } 26 | }, 27 | "introduction": { 28 | "page": { 29 | "title": "RedirectTube • Introduction" 30 | }, 31 | "usage": { 32 | "title": "Usage", 33 | "toolbar": "Click the icon when you are on a YouTube video page", 34 | "contextMenu": "or right-click on the url of a YouTube video" 35 | } 36 | }, 37 | "options": { 38 | "page": { 39 | "title": "RedirectTube" 40 | }, 41 | "options": { 42 | "title": "Options" 43 | }, 44 | "popupBehavior": { 45 | "label": "When clicking the extension icon", 46 | "showPopup": "Show menu", 47 | "redirect": "Redirect to FreeTube" 48 | }, 49 | "autoRedirectLinks": { 50 | "label": "Automatically redirect YouTube links when clicked" 51 | }, 52 | "iframeButton": { 53 | "label": "Show button in iframes" 54 | }, 55 | "extensionIcon": { 56 | "label": "Extension icon" 57 | }, 58 | "footer": { 59 | "version": "RedirectTube vX.Y.Z", 60 | "kofi": "If you like this extension, consider buying me a tea 🍵", 61 | "affiliation": "RedirectTube is not affiliated with FreeTube or its creators.
FreeTube is licensed under the AGPL-3.0 licence.
The name \"FreeTube\" and FreeTube logo are the property of the creators of FreeTube." 62 | } 63 | }, 64 | "popup": { 65 | "page": { 66 | "title": "RedirectTube" 67 | } 68 | } 69 | } -------------------------------------------------------------------------------- /src/Chromium/i18n/locales/fi.json: -------------------------------------------------------------------------------- 1 | { 2 | "options": { 3 | "extensionIcon": { 4 | "label": "Laajennuskuvake" 5 | }, 6 | "options": { 7 | "title": "Vaihtoehdot" 8 | }, 9 | "popupBehavior": { 10 | "label": "Kun napsautat laajennuskuvaketta", 11 | "redirect": "Uudelleenohjaa FreeTubeen", 12 | "showPopup": "Näytä valikko" 13 | }, 14 | "autoRedirectLinks": { 15 | "label": "Automatically redirect YouTube links when clicked" 16 | }, 17 | "footer": { 18 | "kofi": "Jos pidät tästä laajennuksesta, harkitse teen ostamista minulle 🍵", 19 | "version": "RedirectTube vX.Y.Z", 20 | "affiliation": "RedirectTube ei ole sidoksissa FreeTubeen tai sen luojiin.
FreeTube on lisensoitu AGPL-3.0-lisenssillä.
Nimi \"FreeTube\" ja FreeTube-logo ovat FreeTuben luojien omaisuutta." 21 | }, 22 | "iframeButton": { 23 | "label": "Näytä painike iframe-kehyksissä" 24 | }, 25 | "page": { 26 | "title": "RedirectTube" 27 | } 28 | }, 29 | "ui": { 30 | "beta": { 31 | "label": "Beeta" 32 | }, 33 | "createdby": "Michał Stankiewiczin luoma ❤️ kanssa", 34 | "button": { 35 | "opinion": "Kerro mielipiteesi", 36 | "redirect": "Avaa tämä sivusto FreeTubessa", 37 | "issue": "Ilmoita viasta", 38 | "options": "Vaihtoehdot", 39 | "suggestion": "Tee ehdotus" 40 | }, 41 | "error": { 42 | "e404": "Tätä sivua ei voi avata FreeTubessa." 43 | }, 44 | "yes": "Kyllä", 45 | "no": "Ei", 46 | "iframeButton": { 47 | "redirect": "Katso" 48 | }, 49 | "close": "Sulje", 50 | "contextMenu": { 51 | "redirect": "Avaa FreeTubessa" 52 | } 53 | }, 54 | "introduction": { 55 | "page": { 56 | "title": "RedirectTube • Johdanto" 57 | }, 58 | "usage": { 59 | "title": "Käyttö", 60 | "toolbar": "Napsauta kuvaketta, kun olet YouTube-videosivulla", 61 | "contextMenu": "tai napsauta hiiren kakkospainikkeella YouTube-videon URL-osoitetta" 62 | } 63 | }, 64 | "popup": { 65 | "page": { 66 | "title": "RedirectTube" 67 | } 68 | } 69 | } -------------------------------------------------------------------------------- /src/Gecko/i18n/locales/fi.json: -------------------------------------------------------------------------------- 1 | { 2 | "options": { 3 | "extensionIcon": { 4 | "label": "Laajennuskuvake" 5 | }, 6 | "options": { 7 | "title": "Vaihtoehdot" 8 | }, 9 | "popupBehavior": { 10 | "label": "Kun napsautat laajennuskuvaketta", 11 | "redirect": "Uudelleenohjaa FreeTubeen", 12 | "showPopup": "Näytä valikko" 13 | }, 14 | "autoRedirectLinks": { 15 | "label": "Automatically redirect YouTube links when clicked" 16 | }, 17 | "footer": { 18 | "kofi": "Jos pidät tästä laajennuksesta, harkitse teen ostamista minulle 🍵", 19 | "version": "RedirectTube vX.Y.Z", 20 | "affiliation": "RedirectTube ei ole sidoksissa FreeTubeen tai sen luojiin.
FreeTube on lisensoitu AGPL-3.0-lisenssillä.
Nimi \"FreeTube\" ja FreeTube-logo ovat FreeTuben luojien omaisuutta." 21 | }, 22 | "iframeButton": { 23 | "label": "Näytä painike iframe-kehyksissä" 24 | }, 25 | "page": { 26 | "title": "RedirectTube" 27 | } 28 | }, 29 | "ui": { 30 | "beta": { 31 | "label": "Beeta" 32 | }, 33 | "createdby": "Michał Stankiewiczin luoma ❤️ kanssa", 34 | "button": { 35 | "opinion": "Kerro mielipiteesi", 36 | "redirect": "Avaa tämä sivusto FreeTubessa", 37 | "issue": "Ilmoita viasta", 38 | "options": "Vaihtoehdot", 39 | "suggestion": "Tee ehdotus" 40 | }, 41 | "error": { 42 | "e404": "Tätä sivua ei voi avata FreeTubessa." 43 | }, 44 | "yes": "Kyllä", 45 | "no": "Ei", 46 | "iframeButton": { 47 | "redirect": "Katso" 48 | }, 49 | "close": "Sulje", 50 | "contextMenu": { 51 | "redirect": "Avaa FreeTubessa" 52 | } 53 | }, 54 | "introduction": { 55 | "page": { 56 | "title": "RedirectTube • Johdanto" 57 | }, 58 | "usage": { 59 | "title": "Käyttö", 60 | "toolbar": "Napsauta kuvaketta, kun olet YouTube-videosivulla", 61 | "contextMenu": "tai napsauta hiiren kakkospainikkeella YouTube-videon URL-osoitetta" 62 | } 63 | }, 64 | "popup": { 65 | "page": { 66 | "title": "RedirectTube" 67 | } 68 | } 69 | } -------------------------------------------------------------------------------- /src/Chromium/i18n/locales/pl.json: -------------------------------------------------------------------------------- 1 | { 2 | "ui": { 3 | "close": "Zamknij", 4 | "createdby": "Stworzone z ❤️ przez Michała Stankiewicza", 5 | "button": { 6 | "redirect": "Otwórz tę stronę we FreeTube", 7 | "options": "Opcje", 8 | "opinion": "Wyraź opinię", 9 | "suggestion": "Napisz sugestię", 10 | "issue": "Zgłoś problem" 11 | }, 12 | "contextMenu": { 13 | "redirect": "Otwórz we FreeTube" 14 | }, 15 | "yes": "Tak", 16 | "no": "Nie", 17 | "iframeButton": { 18 | "redirect": "Obejrzyj na" 19 | }, 20 | "beta": { 21 | "label": "Beta" 22 | }, 23 | "error": { 24 | "e404": "Nie można otworzyć tej strony we FreeTube." 25 | } 26 | }, 27 | "introduction": { 28 | "page": { 29 | "title": "RedirectTube • Wprowadzenie" 30 | }, 31 | "usage": { 32 | "title": "Korzystanie", 33 | "toolbar": "Kliknij ikonę, gdy jesteś na stronie z filmem na YouTube", 34 | "contextMenu": "lub kliknij prawym przyciskiem link do filmu na YouTube" 35 | } 36 | }, 37 | "options": { 38 | "page": { 39 | "title": "RedirectTube" 40 | }, 41 | "options": { 42 | "title": "Opcje" 43 | }, 44 | "popupBehavior": { 45 | "label": "Po kliknięciu ikony rozszerzenia", 46 | "showPopup": "Pokaż menu", 47 | "redirect": "Przekieruj do FreeTube" 48 | }, 49 | "autoRedirectLinks": { 50 | "label": "Automatycznie przekierowuj linki YouTube po kliknięciu" 51 | }, 52 | "iframeButton": { 53 | "label": "Pokaż przycisk w ramkach iframe" 54 | }, 55 | "extensionIcon": { 56 | "label": "Ikona rozszerzenia" 57 | }, 58 | "footer": { 59 | "version": "RedirectTube vX.Y.Z", 60 | "kofi": "Jeśli podoba Ci się to rozszerzenie, rozważ postawienie mi herbaty 🍵", 61 | "affiliation": "RedirectTube nie jest powiązany z FreeTube ani jego twórcami. FreeTube jest licencjonowany na licencji AGPL-3.0. Nazwa \"FreeTube\" oraz logo FreeTube są własnością twórców FreeTube." 62 | } 63 | }, 64 | "popup": { 65 | "page": { 66 | "title": "RedirectTube" 67 | } 68 | } 69 | } -------------------------------------------------------------------------------- /src/Gecko/i18n/locales/pl.json: -------------------------------------------------------------------------------- 1 | { 2 | "ui": { 3 | "close": "Zamknij", 4 | "createdby": "Stworzone z ❤️ przez Michała Stankiewicza", 5 | "button": { 6 | "redirect": "Otwórz tę stronę we FreeTube", 7 | "options": "Opcje", 8 | "opinion": "Wyraź opinię", 9 | "suggestion": "Napisz sugestię", 10 | "issue": "Zgłoś problem" 11 | }, 12 | "contextMenu": { 13 | "redirect": "Otwórz we FreeTube" 14 | }, 15 | "yes": "Tak", 16 | "no": "Nie", 17 | "iframeButton": { 18 | "redirect": "Obejrzyj na" 19 | }, 20 | "beta": { 21 | "label": "Beta" 22 | }, 23 | "error": { 24 | "e404": "Nie można otworzyć tej strony we FreeTube." 25 | } 26 | }, 27 | "introduction": { 28 | "page": { 29 | "title": "RedirectTube • Wprowadzenie" 30 | }, 31 | "usage": { 32 | "title": "Korzystanie", 33 | "toolbar": "Kliknij ikonę, gdy jesteś na stronie z filmem na YouTube", 34 | "contextMenu": "lub kliknij prawym przyciskiem link do filmu na YouTube" 35 | } 36 | }, 37 | "options": { 38 | "page": { 39 | "title": "RedirectTube" 40 | }, 41 | "options": { 42 | "title": "Opcje" 43 | }, 44 | "popupBehavior": { 45 | "label": "Po kliknięciu ikony rozszerzenia", 46 | "showPopup": "Pokaż menu", 47 | "redirect": "Przekieruj do FreeTube" 48 | }, 49 | "autoRedirectLinks": { 50 | "label": "Automatycznie przekierowuj linki YouTube po kliknięciu" 51 | }, 52 | "iframeButton": { 53 | "label": "Pokaż przycisk w ramkach iframe" 54 | }, 55 | "extensionIcon": { 56 | "label": "Ikona rozszerzenia" 57 | }, 58 | "footer": { 59 | "version": "RedirectTube vX.Y.Z", 60 | "kofi": "Jeśli podoba Ci się to rozszerzenie, rozważ postawienie mi herbaty 🍵", 61 | "affiliation": "RedirectTube nie jest powiązany z FreeTube ani jego twórcami. FreeTube jest licencjonowany na licencji AGPL-3.0. Nazwa \"FreeTube\" oraz logo FreeTube są własnością twórców FreeTube." 62 | } 63 | }, 64 | "popup": { 65 | "page": { 66 | "title": "RedirectTube" 67 | } 68 | } 69 | } -------------------------------------------------------------------------------- /src/Chromium/i18n/locales/lv.json: -------------------------------------------------------------------------------- 1 | { 2 | "options": { 3 | "extensionIcon": { 4 | "label": "Paplašinājuma ikona" 5 | }, 6 | "options": { 7 | "title": "Iestatījumi" 8 | }, 9 | "popupBehavior": { 10 | "label": "Kad noklikšķina paplašinājuma ikonu", 11 | "showPopup": "Rādīt izvēlni", 12 | "redirect": "Pārvirzīt uz FreeTube" 13 | }, 14 | "autoRedirectLinks": { 15 | "label": "Automatically redirect YouTube links when clicked" 16 | }, 17 | "footer": { 18 | "kofi": "Ja jums patīk šis paplašinājums, apsveriet iespēju uzcienāt mani ar tēju 🍵", 19 | "version": "RedirectTube X.Y.Z versija", 20 | "affiliation": "RedirectTube nav saistīts ar FreeTube vai tā izstrādātājiem.
FreeTube ir licencēts saskaņā ar AGPL-3.0 licenci.
Nosaukums \"FreeTube\" un FreeTube logotips ir FreeTube izstrādātāju īpašums." 21 | }, 22 | "iframeButton": { 23 | "label": "Rādīt pogu iframe ietvaros" 24 | }, 25 | "page": { 26 | "title": "RedirectTube" 27 | } 28 | }, 29 | "ui": { 30 | "beta": { 31 | "label": "Beta" 32 | }, 33 | "createdby": "Izstrādājis ar ❤️ Michał Stankiewicz", 34 | "button": { 35 | "opinion": "Sniegt savu viedokli", 36 | "options": "Iestatījumi", 37 | "suggestion": "Sniegt ieteikumu", 38 | "redirect": "Atvērt šo vietni FreeTube", 39 | "issue": "Ziņot par problēmu" 40 | }, 41 | "error": { 42 | "e404": "Nevar atvērt šo lapu FreeTube." 43 | }, 44 | "yes": "Jā", 45 | "no": "Nē", 46 | "iframeButton": { 47 | "redirect": "Skatīties uz" 48 | }, 49 | "close": "Aizvērt", 50 | "contextMenu": { 51 | "redirect": "Atvērt FreeTube" 52 | } 53 | }, 54 | "popup": { 55 | "page": { 56 | "title": "RedirectTube" 57 | } 58 | }, 59 | "introduction": { 60 | "usage": { 61 | "title": "Lietošana", 62 | "toolbar": "Noklikšķiniet uz ikonas, kad skatāties video YouTube lapā", 63 | "contextMenu": "vai ar peles labo pogu noklikšķiniet uz YouTube video URL" 64 | }, 65 | "page": { 66 | "title": "RedirectTube • Ievads" 67 | } 68 | } 69 | } -------------------------------------------------------------------------------- /src/Gecko/i18n/locales/lv.json: -------------------------------------------------------------------------------- 1 | { 2 | "options": { 3 | "extensionIcon": { 4 | "label": "Paplašinājuma ikona" 5 | }, 6 | "options": { 7 | "title": "Iestatījumi" 8 | }, 9 | "popupBehavior": { 10 | "label": "Kad noklikšķina paplašinājuma ikonu", 11 | "showPopup": "Rādīt izvēlni", 12 | "redirect": "Pārvirzīt uz FreeTube" 13 | }, 14 | "autoRedirectLinks": { 15 | "label": "Automatically redirect YouTube links when clicked" 16 | }, 17 | "footer": { 18 | "kofi": "Ja jums patīk šis paplašinājums, apsveriet iespēju uzcienāt mani ar tēju 🍵", 19 | "version": "RedirectTube X.Y.Z versija", 20 | "affiliation": "RedirectTube nav saistīts ar FreeTube vai tā izstrādātājiem.
FreeTube ir licencēts saskaņā ar AGPL-3.0 licenci.
Nosaukums \"FreeTube\" un FreeTube logotips ir FreeTube izstrādātāju īpašums." 21 | }, 22 | "iframeButton": { 23 | "label": "Rādīt pogu iframe ietvaros" 24 | }, 25 | "page": { 26 | "title": "RedirectTube" 27 | } 28 | }, 29 | "ui": { 30 | "beta": { 31 | "label": "Beta" 32 | }, 33 | "createdby": "Izstrādājis ar ❤️ Michał Stankiewicz", 34 | "button": { 35 | "opinion": "Sniegt savu viedokli", 36 | "options": "Iestatījumi", 37 | "suggestion": "Sniegt ieteikumu", 38 | "redirect": "Atvērt šo vietni FreeTube", 39 | "issue": "Ziņot par problēmu" 40 | }, 41 | "error": { 42 | "e404": "Nevar atvērt šo lapu FreeTube." 43 | }, 44 | "yes": "Jā", 45 | "no": "Nē", 46 | "iframeButton": { 47 | "redirect": "Skatīties uz" 48 | }, 49 | "close": "Aizvērt", 50 | "contextMenu": { 51 | "redirect": "Atvērt FreeTube" 52 | } 53 | }, 54 | "popup": { 55 | "page": { 56 | "title": "RedirectTube" 57 | } 58 | }, 59 | "introduction": { 60 | "usage": { 61 | "title": "Lietošana", 62 | "toolbar": "Noklikšķiniet uz ikonas, kad skatāties video YouTube lapā", 63 | "contextMenu": "vai ar peles labo pogu noklikšķiniet uz YouTube video URL" 64 | }, 65 | "page": { 66 | "title": "RedirectTube • Ievads" 67 | } 68 | } 69 | } -------------------------------------------------------------------------------- /src/Gecko/i18n/locales/it.json: -------------------------------------------------------------------------------- 1 | { 2 | "ui": { 3 | "createdby": "Creato con ❤️ da Michał Stankiewicz", 4 | "button": { 5 | "opinion": "Dai la tua opinione", 6 | "redirect": "Apri questo sito in FreeTube", 7 | "options": "Opzioni", 8 | "issue": "Segnala un problema", 9 | "suggestion": "Fai un suggerimento" 10 | }, 11 | "close": "Chiudi", 12 | "beta": { 13 | "label": "Beta" 14 | }, 15 | "error": { 16 | "e404": "Impossibile aprire questa pagina in FreeTube." 17 | }, 18 | "yes": "Sì", 19 | "no": "No", 20 | "iframeButton": { 21 | "redirect": "Guarda su" 22 | }, 23 | "contextMenu": { 24 | "redirect": "Apri in FreeTube" 25 | } 26 | }, 27 | "introduction": { 28 | "page": { 29 | "title": "RedirectTube • Introduzione" 30 | }, 31 | "usage": { 32 | "title": "Utilizzo", 33 | "toolbar": "Clicca sull'icona quando sei sulla pagina di un video YouTube", 34 | "contextMenu": "oppure fai clic con il tasto destro sull'URL di un video YouTube" 35 | } 36 | }, 37 | "options": { 38 | "options": { 39 | "title": "Opzioni" 40 | }, 41 | "popupBehavior": { 42 | "label": "Quando si clicca sull'icona dell'estensione", 43 | "redirect": "Reindirizza a FreeTube", 44 | "showPopup": "Mostra menu" 45 | }, 46 | "autoRedirectLinks": { 47 | "label": "Automatically redirect YouTube links when clicked" 48 | }, 49 | "extensionIcon": { 50 | "label": "Icona dell'estensione" 51 | }, 52 | "footer": { 53 | "kofi": "Se vi piace questa estensione, pensate a comprarmi un tè 🍵", 54 | "version": "RedirectTube vX.Y.Z", 55 | "affiliation": "RedirectTube non è affiliato a FreeTube o ai suoi creatori.
FreeTube è rilasciato sotto licenza AGPL-3.0.
Il nome \"FreeTube\" e il logo FreeTube sono di proprietà dei creatori di FreeTube." 56 | }, 57 | "page": { 58 | "title": "RedirectTube" 59 | }, 60 | "iframeButton": { 61 | "label": "Mostra il pulsante in iframes:" 62 | } 63 | }, 64 | "popup": { 65 | "page": { 66 | "title": "RedirectTube" 67 | } 68 | } 69 | } -------------------------------------------------------------------------------- /src/Chromium/i18n/locales/it.json: -------------------------------------------------------------------------------- 1 | { 2 | "ui": { 3 | "createdby": "Creato con ❤️ da Michał Stankiewicz", 4 | "button": { 5 | "opinion": "Dai la tua opinione", 6 | "redirect": "Apri questo sito in FreeTube", 7 | "options": "Opzioni", 8 | "issue": "Segnala un problema", 9 | "suggestion": "Fai un suggerimento" 10 | }, 11 | "close": "Chiudi", 12 | "beta": { 13 | "label": "Beta" 14 | }, 15 | "error": { 16 | "e404": "Impossibile aprire questa pagina in FreeTube." 17 | }, 18 | "yes": "Sì", 19 | "no": "No", 20 | "iframeButton": { 21 | "redirect": "Guarda su" 22 | }, 23 | "contextMenu": { 24 | "redirect": "Apri in FreeTube" 25 | } 26 | }, 27 | "introduction": { 28 | "page": { 29 | "title": "RedirectTube • Introduzione" 30 | }, 31 | "usage": { 32 | "title": "Utilizzo", 33 | "toolbar": "Clicca sull'icona quando sei sulla pagina di un video YouTube", 34 | "contextMenu": "oppure fai clic con il tasto destro sull'URL di un video YouTube" 35 | } 36 | }, 37 | "options": { 38 | "options": { 39 | "title": "Opzioni" 40 | }, 41 | "popupBehavior": { 42 | "label": "Quando si clicca sull'icona dell'estensione", 43 | "redirect": "Reindirizza a FreeTube", 44 | "showPopup": "Mostra menu" 45 | }, 46 | "autoRedirectLinks": { 47 | "label": "Automatically redirect YouTube links when clicked" 48 | }, 49 | "extensionIcon": { 50 | "label": "Icona dell'estensione" 51 | }, 52 | "footer": { 53 | "kofi": "Se vi piace questa estensione, pensate a comprarmi un tè 🍵", 54 | "version": "RedirectTube vX.Y.Z", 55 | "affiliation": "RedirectTube non è affiliato a FreeTube o ai suoi creatori.
FreeTube è rilasciato sotto licenza AGPL-3.0.
Il nome \"FreeTube\" e il logo FreeTube sono di proprietà dei creatori di FreeTube." 56 | }, 57 | "page": { 58 | "title": "RedirectTube" 59 | }, 60 | "iframeButton": { 61 | "label": "Mostra il pulsante in iframes:" 62 | } 63 | }, 64 | "popup": { 65 | "page": { 66 | "title": "RedirectTube" 67 | } 68 | } 69 | } -------------------------------------------------------------------------------- /src/Gecko/i18n/locales/fr.json: -------------------------------------------------------------------------------- 1 | { 2 | "ui": { 3 | "beta": { 4 | "label": "Bêta" 5 | }, 6 | "createdby": "Créé avec ❤️ par Michał Stankiewicz", 7 | "button": { 8 | "opinion": "Donner votre avis", 9 | "issue": "Signaler un problème", 10 | "redirect": "Ouvrir ce site dans FreeTube", 11 | "options": "Options", 12 | "suggestion": "Faire une suggestion" 13 | }, 14 | "error": { 15 | "e404": "Impossible d'ouvrir cette page dans FreeTube." 16 | }, 17 | "yes": "Oui", 18 | "no": "Non", 19 | "iframeButton": { 20 | "redirect": "Regarder sur" 21 | }, 22 | "close": "Fermer", 23 | "contextMenu": { 24 | "redirect": "Ouvrir dans FreeTube" 25 | } 26 | }, 27 | "options": { 28 | "extensionIcon": { 29 | "label": "Icône de l'extension" 30 | }, 31 | "footer": { 32 | "version": "RedirectTube vX.Y.Z", 33 | "kofi": "Si vous aimez cette extension, pensez à m'acheter un thé 🍵", 34 | "affiliation": "RedirectTube n'est pas affilié avec FreeTube ou ses créateurs.
FreeTube est sous licence AGPL-3.0.
Le nom \"FreeTube\" et le logo FreeTube sont la propriété des créateurs de FreeTube." 35 | }, 36 | "iframeButton": { 37 | "label": "Afficher le bouton dans les iframes" 38 | }, 39 | "options": { 40 | "title": "Options" 41 | }, 42 | "popupBehavior": { 43 | "label": "Lorsque vous cliquez sur l'icône de l'extension", 44 | "redirect": "Rediriger vers FreeTube", 45 | "showPopup": "Afficher le menu" 46 | }, 47 | "autoRedirectLinks": { 48 | "label": "Automatically redirect YouTube links when clicked" 49 | }, 50 | "page": { 51 | "title": "RedirectTube" 52 | } 53 | }, 54 | "introduction": { 55 | "page": { 56 | "title": "RedirectTube • Introduction" 57 | }, 58 | "usage": { 59 | "title": "Utilisation", 60 | "toolbar": "Cliquez sur l'icône quand vous êtes sur la page d'une vidéo YouTube", 61 | "contextMenu": "ou cliquez droit sur l'URL d'une vidéo YouTube" 62 | } 63 | }, 64 | "popup": { 65 | "page": { 66 | "title": "RedirectTube" 67 | } 68 | } 69 | } -------------------------------------------------------------------------------- /src/Chromium/i18n/locales/fr.json: -------------------------------------------------------------------------------- 1 | { 2 | "ui": { 3 | "beta": { 4 | "label": "Bêta" 5 | }, 6 | "createdby": "Créé avec ❤️ par Michał Stankiewicz", 7 | "button": { 8 | "opinion": "Donner votre avis", 9 | "issue": "Signaler un problème", 10 | "redirect": "Ouvrir ce site dans FreeTube", 11 | "options": "Options", 12 | "suggestion": "Faire une suggestion" 13 | }, 14 | "error": { 15 | "e404": "Impossible d'ouvrir cette page dans FreeTube." 16 | }, 17 | "yes": "Oui", 18 | "no": "Non", 19 | "iframeButton": { 20 | "redirect": "Regarder sur" 21 | }, 22 | "close": "Fermer", 23 | "contextMenu": { 24 | "redirect": "Ouvrir dans FreeTube" 25 | } 26 | }, 27 | "options": { 28 | "extensionIcon": { 29 | "label": "Icône de l'extension" 30 | }, 31 | "footer": { 32 | "version": "RedirectTube vX.Y.Z", 33 | "kofi": "Si vous aimez cette extension, pensez à m'acheter un thé 🍵", 34 | "affiliation": "RedirectTube n'est pas affilié avec FreeTube ou ses créateurs.
FreeTube est sous licence AGPL-3.0.
Le nom \"FreeTube\" et le logo FreeTube sont la propriété des créateurs de FreeTube." 35 | }, 36 | "iframeButton": { 37 | "label": "Afficher le bouton dans les iframes" 38 | }, 39 | "options": { 40 | "title": "Options" 41 | }, 42 | "popupBehavior": { 43 | "label": "Lorsque vous cliquez sur l'icône de l'extension", 44 | "redirect": "Rediriger vers FreeTube", 45 | "showPopup": "Afficher le menu" 46 | }, 47 | "autoRedirectLinks": { 48 | "label": "Automatically redirect YouTube links when clicked" 49 | }, 50 | "page": { 51 | "title": "RedirectTube" 52 | } 53 | }, 54 | "introduction": { 55 | "page": { 56 | "title": "RedirectTube • Introduction" 57 | }, 58 | "usage": { 59 | "title": "Utilisation", 60 | "toolbar": "Cliquez sur l'icône quand vous êtes sur la page d'une vidéo YouTube", 61 | "contextMenu": "ou cliquez droit sur l'URL d'une vidéo YouTube" 62 | } 63 | }, 64 | "popup": { 65 | "page": { 66 | "title": "RedirectTube" 67 | } 68 | } 69 | } -------------------------------------------------------------------------------- /src/Gecko/i18n/locales/nl.json: -------------------------------------------------------------------------------- 1 | { 2 | "options": { 3 | "extensionIcon": { 4 | "label": "Add-onpictogram" 5 | }, 6 | "options": { 7 | "title": "Instellingen" 8 | }, 9 | "popupBehavior": { 10 | "label": "Na aanklikken van add-onpictogram", 11 | "redirect": "Doorverwijzen naar FreeTube", 12 | "showPopup": "Menu openen" 13 | }, 14 | "autoRedirectLinks": { 15 | "label": "Automatically redirect YouTube links when clicked" 16 | }, 17 | "footer": { 18 | "kofi": "Als je deze add-on graag gebruikt, overweg dan een donatie (bijv. een kopje thee) 🍵", 19 | "version": "RedirectTube vX.Y.Z", 20 | "affiliation": "RedirectTube is niet verbonden aan FreeTube of de makers ervan.
FreeTube wordt uitgebracht onder de voorwaarden van de AGPL-3.0-licentie.
De naam ‘FreeTube’ en het FreeTube-logo zijn eigendom van de makers van FreeTube." 21 | }, 22 | "iframeButton": { 23 | "label": "Knop op iframes tonen" 24 | }, 25 | "page": { 26 | "title": "RedirectTube" 27 | } 28 | }, 29 | "ui": { 30 | "beta": { 31 | "label": "Bèta" 32 | }, 33 | "createdby": "Met ❤️ gemaakt door Michał Stankiewicz", 34 | "button": { 35 | "opinion": "Deel je mening", 36 | "redirect": "Website openen in FreeTube", 37 | "issue": "Meld een probleem", 38 | "options": "Instellingen", 39 | "suggestion": "Deel een idee" 40 | }, 41 | "error": { 42 | "e404": "Deze pagina kan niet worden geopend in FreeTube." 43 | }, 44 | "yes": "Ja", 45 | "no": "Nee", 46 | "iframeButton": { 47 | "redirect": "Bekijken op" 48 | }, 49 | "close": "Sluiten", 50 | "contextMenu": { 51 | "redirect": "Openen in FreeTube" 52 | } 53 | }, 54 | "introduction": { 55 | "page": { 56 | "title": "RedirectTube • Introductie" 57 | }, 58 | "usage": { 59 | "title": "Gebruik", 60 | "toolbar": "Open een YouTube-videopagina en klik op het pictogram", 61 | "contextMenu": "of klik met de rechtermuisknop op de url van een YouTube-video" 62 | } 63 | }, 64 | "popup": { 65 | "page": { 66 | "title": "RedirectTube" 67 | } 68 | } 69 | } -------------------------------------------------------------------------------- /src/Chromium/i18n/locales/nl.json: -------------------------------------------------------------------------------- 1 | { 2 | "options": { 3 | "extensionIcon": { 4 | "label": "Add-onpictogram" 5 | }, 6 | "options": { 7 | "title": "Instellingen" 8 | }, 9 | "popupBehavior": { 10 | "label": "Na aanklikken van add-onpictogram", 11 | "redirect": "Doorverwijzen naar FreeTube", 12 | "showPopup": "Menu openen" 13 | }, 14 | "autoRedirectLinks": { 15 | "label": "Automatically redirect YouTube links when clicked" 16 | }, 17 | "footer": { 18 | "kofi": "Als je deze add-on graag gebruikt, overweg dan een donatie (bijv. een kopje thee) 🍵", 19 | "version": "RedirectTube vX.Y.Z", 20 | "affiliation": "RedirectTube is niet verbonden aan FreeTube of de makers ervan.
FreeTube wordt uitgebracht onder de voorwaarden van de AGPL-3.0-licentie.
De naam ‘FreeTube’ en het FreeTube-logo zijn eigendom van de makers van FreeTube." 21 | }, 22 | "iframeButton": { 23 | "label": "Knop op iframes tonen" 24 | }, 25 | "page": { 26 | "title": "RedirectTube" 27 | } 28 | }, 29 | "ui": { 30 | "beta": { 31 | "label": "Bèta" 32 | }, 33 | "createdby": "Met ❤️ gemaakt door Michał Stankiewicz", 34 | "button": { 35 | "opinion": "Deel je mening", 36 | "redirect": "Website openen in FreeTube", 37 | "issue": "Meld een probleem", 38 | "options": "Instellingen", 39 | "suggestion": "Deel een idee" 40 | }, 41 | "error": { 42 | "e404": "Deze pagina kan niet worden geopend in FreeTube." 43 | }, 44 | "yes": "Ja", 45 | "no": "Nee", 46 | "iframeButton": { 47 | "redirect": "Bekijken op" 48 | }, 49 | "close": "Sluiten", 50 | "contextMenu": { 51 | "redirect": "Openen in FreeTube" 52 | } 53 | }, 54 | "introduction": { 55 | "page": { 56 | "title": "RedirectTube • Introductie" 57 | }, 58 | "usage": { 59 | "title": "Gebruik", 60 | "toolbar": "Open een YouTube-videopagina en klik op het pictogram", 61 | "contextMenu": "of klik met de rechtermuisknop op de url van een YouTube-video" 62 | } 63 | }, 64 | "popup": { 65 | "page": { 66 | "title": "RedirectTube" 67 | } 68 | } 69 | } -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report.yml: -------------------------------------------------------------------------------- 1 | name: Bug report 2 | description: Create a report to help me improve RedirectTube! 3 | title: '[Bug]: ' 4 | labels: 5 | - bug 6 | assignees: 7 | - MStankiewiczOfficial 8 | body: 9 | - type: markdown 10 | attributes: 11 | value: Accepted are answers in `English`, `Polish`. 12 | - type: textarea 13 | id: description 14 | attributes: 15 | label: Describe the bug 16 | description: A clear and concise description of what the bug is. 17 | validations: 18 | required: true 19 | - type: textarea 20 | id: reproduction 21 | attributes: 22 | label: To Reproduce 23 | description: Steps to reproduce the behavior 24 | placeholder: 1. Go to '...' 2. Click on '....' 3. Scroll down to '....' 4. See error 25 | validations: 26 | required: true 27 | - type: textarea 28 | id: expected 29 | attributes: 30 | label: Expected behavior 31 | description: A clear and concise description of what you expected to happen. 32 | validations: 33 | required: true 34 | - type: textarea 35 | id: screenshots 36 | attributes: 37 | label: Screenshots 38 | description: If applicable, add screenshots to help explain your problem. 39 | - type: dropdown 40 | id: platform 41 | attributes: 42 | label: Platform 43 | description: Choose on which platform the bug occurs. 44 | multiple: true 45 | options: 46 | - Linux 47 | - macOS 48 | - Windows 49 | validations: 50 | required: true 51 | - type: input 52 | id: browser-version 53 | attributes: 54 | label: Firefox version 55 | description: To check, go to [about:support](about:support). 56 | placeholder: 128.5.2esr 57 | validations: 58 | required: true 59 | - type: input 60 | id: app-version 61 | attributes: 62 | label: FreeTube version 63 | description: To check, go to "About" section. 64 | placeholder: 0.22.1 Beta 65 | validations: 66 | required: true 67 | - type: input 68 | id: extension-version 69 | attributes: 70 | label: RedirectTube version 71 | description: To check, go to [about:addons](about:addons) and click extension name. 72 | placeholder: 1.0.0 73 | validations: 74 | required: true 75 | - type: textarea 76 | id: context 77 | attributes: 78 | label: Additional context 79 | description: Add any other context about the problem here. 80 | - type: checkboxes 81 | id: statements 82 | attributes: 83 | label: Statements 84 | options: 85 | - label: I use the latest version of RedirectTube. 86 | required: true 87 | - label: I use the latest version of Mozilla Firefox. 88 | required: true 89 | - label: I verified that the error has not already been reported on GitHub. 90 | required: true -------------------------------------------------------------------------------- /src/Gecko/popup.js: -------------------------------------------------------------------------------- 1 | const extensionApi = typeof chrome !== "undefined" ? chrome : browser; 2 | 3 | var errorText = document.getElementById("errorText"); 4 | var redirectButton = document.getElementById("redirectButton"); 5 | var optionsButton = document.getElementById("optionsButton"); 6 | var opinionButton = document.getElementById("opinionButton"); 7 | var suggestionButton = document.getElementById("suggestionButton"); 8 | var issueButton = document.getElementById("issueButton"); 9 | 10 | document.addEventListener("DOMContentLoaded", function () { 11 | extensionApi.tabs.query({ active: true, currentWindow: true }, function (tabs) { 12 | var url = tabs[0].url; 13 | if ( 14 | url.startsWith("https://www.youtube.com/watch?v=") || 15 | url.startsWith("https://www.youtube.com/playlist?list=") || 16 | url.startsWith("https://www.youtube.com/@") || 17 | url.startsWith("https://www.youtube.com/channel/") 18 | ) { 19 | loadOptions(url, tabs); 20 | } else { 21 | fetch(`i18n/locales/${lang}.json`) 22 | .then((response) => response.json()) 23 | .then((data) => { 24 | errorText.innerHTML = data.ui.error.e404; 25 | redirectButton.disabled = true; 26 | }); 27 | } 28 | }); 29 | }); 30 | 31 | function loadOptions(url, tabs) { 32 | extensionApi.storage.local.get("popupBehavior", function (result) { 33 | if (result && result.popupBehavior === "redirect") { 34 | openInFreeTube(url, tabs); 35 | } 36 | }); 37 | } 38 | function openInFreeTube(url, tabs) { 39 | var freeTubeUrl = "freetube://" + url; 40 | extensionApi.tabs.update(tabs[0].id, { url: freeTubeUrl }); 41 | window.close(); 42 | } 43 | 44 | redirectButton.addEventListener("click", function () { 45 | if (redirectButton.disabled === false) { 46 | extensionApi.tabs.query( 47 | { active: true, currentWindow: true }, 48 | function (tabs) { 49 | var url = tabs[0].url; 50 | openInFreeTube(url, tabs); 51 | } 52 | ); 53 | } 54 | }); 55 | 56 | optionsButton.addEventListener("click", function () { 57 | extensionApi.runtime.openOptionsPage(); 58 | }); 59 | 60 | opinionButton.addEventListener("click", function () { 61 | extensionApi.tabs.create({ 62 | url: "https://addons.mozilla.org/firefox/addon/redirecttube/", 63 | }); 64 | }); 65 | 66 | suggestionButton.addEventListener("click", function () { 67 | extensionApi.tabs.create({ 68 | url: "https://github.com/MStankiewiczOfficial/RedirectTube/issues/new?assignees=MStankiewiczOfficial&labels=enhancement&projects=&template=feature-request.yml&title=%5BFR%5D%3A+", 69 | }); 70 | }); 71 | 72 | issueButton.addEventListener("click", function () { 73 | extensionApi.tabs.create({ 74 | url: "https://github.com/MStankiewiczOfficial/RedirectTube/issues/new?assignees=MStankiewiczOfficial&labels=bug&projects=&template=bug-report.yml&title=%5BBug%5D%3A+", 75 | }); 76 | }); 77 | -------------------------------------------------------------------------------- /src/Chromium/popup.js: -------------------------------------------------------------------------------- 1 | const extensionApi = typeof chrome !== "undefined" ? chrome : browser; 2 | 3 | var errorText = document.getElementById("errorText"); 4 | var redirectButton = document.getElementById("redirectButton"); 5 | var optionsButton = document.getElementById("optionsButton"); 6 | var opinionButton = document.getElementById("opinionButton"); 7 | var suggestionButton = document.getElementById("suggestionButton"); 8 | var issueButton = document.getElementById("issueButton"); 9 | 10 | document.addEventListener("DOMContentLoaded", function () { 11 | extensionApi.tabs.query({ active: true, currentWindow: true }, function (tabs) { 12 | var url = tabs[0].url; 13 | if ( 14 | url.startsWith("https://www.youtube.com/watch?v=") || 15 | url.startsWith("https://www.youtube.com/playlist?list=") || 16 | url.startsWith("https://www.youtube.com/@") || 17 | url.startsWith("https://www.youtube.com/channel/") 18 | ) { 19 | loadOptions(url, tabs); 20 | } else { 21 | fetch(`i18n/locales/${lang}.json`) 22 | .then((response) => response.json()) 23 | .then((data) => { 24 | errorText.innerHTML = data.ui.error.e404; 25 | redirectButton.disabled = true; 26 | }); 27 | } 28 | }); 29 | }); 30 | 31 | function loadOptions(url, tabs) { 32 | extensionApi.storage.local.get("popupBehavior", function (result) { 33 | if (result && result.popupBehavior === "redirect") { 34 | openInFreeTube(url, tabs); 35 | } 36 | }); 37 | } 38 | function openInFreeTube(url, tabs) { 39 | var freeTubeUrl = "freetube://" + url; 40 | extensionApi.tabs.update(tabs[0].id, { url: freeTubeUrl }); 41 | window.close(); 42 | } 43 | 44 | redirectButton.addEventListener("click", function () { 45 | if (redirectButton.disabled === false) { 46 | extensionApi.tabs.query( 47 | { active: true, currentWindow: true }, 48 | function (tabs) { 49 | var url = tabs[0].url; 50 | openInFreeTube(url, tabs); 51 | } 52 | ); 53 | } 54 | }); 55 | 56 | optionsButton.addEventListener("click", function () { 57 | extensionApi.runtime.openOptionsPage(); 58 | }); 59 | 60 | opinionButton.addEventListener("click", function () { 61 | extensionApi.tabs.create({ 62 | url: "https://addons.mozilla.org/firefox/addon/redirecttube/", 63 | }); 64 | }); 65 | 66 | suggestionButton.addEventListener("click", function () { 67 | extensionApi.tabs.create({ 68 | url: "https://github.com/MStankiewiczOfficial/RedirectTube/issues/new?assignees=MStankiewiczOfficial&labels=enhancement&projects=&template=feature-request.yml&title=%5BFR%5D%3A+", 69 | }); 70 | }); 71 | 72 | issueButton.addEventListener("click", function () { 73 | extensionApi.tabs.create({ 74 | url: "https://github.com/MStankiewiczOfficial/RedirectTube/issues/new?assignees=MStankiewiczOfficial&labels=bug&projects=&template=bug-report.yml&title=%5BBug%5D%3A+", 75 | }); 76 | }); 77 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Build, Sign and Publish 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | build-and-publish: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - name: Checkout code (main branch) 14 | uses: actions/checkout@v3 15 | with: 16 | ref: main 17 | 18 | - name: Install dependencies 19 | run: | 20 | sudo apt-get update 21 | sudo apt-get install -y zip jq 22 | npm install -g web-ext 23 | 24 | - name: Extract data from manifest.json 25 | id: extract-data 26 | run: | 27 | NAME=$(jq -r '.name' src/Gecko/manifest.json | tr ' ' '_') 28 | VERSION=$(jq -r '.version' src/Gecko/manifest.json) 29 | ID=$(jq -r '.browser_specific_settings.gecko.id' src/Gecko/manifest.json) 30 | echo "EXT_NAME=${NAME}" >> $GITHUB_ENV 31 | echo "EXT_VERSION=${VERSION}" >> $GITHUB_ENV 32 | echo "EXT_ID=${ID}" >> $GITHUB_ENV 33 | 34 | - name: Prepare artifacts directory 35 | run: | 36 | mkdir -p artifacts/unsigned 37 | mkdir -p artifacts/signed 38 | 39 | - name: Generate tag and release name 40 | id: generate-tag 41 | run: | 42 | YEAR=$(date +'%y') 43 | MONTH=$(date +'%m') 44 | RELEASE_COUNT=$(curl -s \ 45 | -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" \ 46 | https://api.github.com/repos/${{ github.repository }}/releases \ 47 | | jq "[.[] | select(.tag_name | startswith(\"${YEAR}${MONTH}\"))] | length + 1") 48 | TAG="${YEAR}${MONTH}$(printf "%0${#RELEASE_COUNT}d" ${RELEASE_COUNT})" 49 | RELEASE_NAME="${EXT_VERSION} (${TAG})" 50 | echo "RELEASE_TAG=${TAG}" >> $GITHUB_ENV 51 | echo "RELEASE_NAME=${RELEASE_NAME}" >> $GITHUB_ENV 52 | 53 | - name: Build unsigned XPI 54 | run: | 55 | web-ext build -s src/Gecko --artifacts-dir ./artifacts 56 | mv ./artifacts/*.zip ./artifacts/${EXT_NAME}-${EXT_VERSION}-gecko-unsigned.zip 57 | 58 | - name: Convert ZIP to XPI 59 | run: | 60 | mv ./artifacts/${EXT_NAME}-${EXT_VERSION}-gecko-unsigned.zip ./artifacts/unsigned/${EXT_NAME}-${EXT_VERSION}-gecko-unsigned.xpi 61 | 62 | - name: Build Chromium unsigned ZIP 63 | run: | 64 | (cd src/Chromium && zip -r ../../artifacts/unsigned/${EXT_NAME}-${EXT_VERSION}-chromium-unsigned.zip .) 65 | 66 | - name: Sign XPI 67 | env: 68 | WEB_EXT_API_KEY: ${{ secrets.AMO_API_KEY }} 69 | WEB_EXT_API_SECRET: ${{ secrets.AMO_API_SECRET }} 70 | run: | 71 | web-ext sign --channel=listed --api-key=$WEB_EXT_API_KEY --api-secret=$WEB_EXT_API_SECRET --artifacts-dir ./artifacts/signed --source-dir src/Gecko 72 | mv ./artifacts/signed/*.xpi ./artifacts/signed/${EXT_NAME}-${EXT_VERSION}-gecko-signed.xpi 73 | 74 | - name: Create GitHub Release 75 | env: 76 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 77 | run: | 78 | gh release create "$RELEASE_TAG" ./artifacts/unsigned/${EXT_NAME}-${EXT_VERSION}-gecko-unsigned.xpi ./artifacts/unsigned/${EXT_NAME}-${EXT_VERSION}-chromium-unsigned.zip ./artifacts/signed/${EXT_NAME}-${EXT_VERSION}-gecko-signed.xpi --title "$RELEASE_NAME" -F ./release-notes.md 79 | -------------------------------------------------------------------------------- /src/Chromium/options.js: -------------------------------------------------------------------------------- 1 | const extensionApi = typeof chrome !== "undefined" ? chrome : browser; 2 | 3 | var popupBehavior = "showPopup"; 4 | var autoRedirectLinks = "autoRedirectLinksNo"; 5 | var iframeButton = "iframeButtonYes"; 6 | var extensionIcon = "color"; 7 | 8 | function saveOptions(e) { 9 | setTimeout(() => { 10 | e.preventDefault(); 11 | extensionApi.storage.local.set({ 12 | popupBehavior: document.getElementById("popupBehavior").value, 13 | autoRedirectLinks: document.getElementById("autoRedirectLinks").value, 14 | iframeButton: document.getElementById("iframeButton").value, 15 | extensionIcon: document.querySelector( 16 | 'input[name="extensionIcon"]:checked' 17 | ).value, 18 | }); 19 | extensionApi.runtime.sendMessage({ message: "detectYT" }); 20 | }, 1); 21 | } 22 | 23 | function restoreOptions() { 24 | function setCurrentChoice(result) { 25 | document.getElementById("popupBehavior").value = 26 | result.popupBehavior || popupBehavior; 27 | document.getElementById("autoRedirectLinks").value = 28 | result.autoRedirectLinks || autoRedirectLinks; 29 | document.getElementById("iframeButton").value = 30 | result.iframeButton || iframeButton; 31 | document.querySelector( 32 | 'input[name="extensionIcon"][value="' + 33 | (result.extensionIcon || extensionIcon) + 34 | '"]' 35 | ).checked = true; 36 | } 37 | 38 | function onError(error) { 39 | console.log(`Error: ${error}`); 40 | } 41 | 42 | extensionApi.storage.local.get( 43 | [ 44 | "popupBehavior", 45 | "autoRedirectLinks", 46 | "iframeButton", 47 | "extensionIcon", 48 | ], 49 | function (result) { 50 | if (extensionApi.runtime.lastError) { 51 | onError(extensionApi.runtime.lastError); 52 | return; 53 | } 54 | setCurrentChoice(result || {}); 55 | } 56 | ); 57 | } 58 | 59 | opinionButton.addEventListener("click", function () { 60 | extensionApi.tabs.create({ 61 | url: "https://addons.mozilla.org/firefox/addon/redirecttube/", 62 | }); 63 | }); 64 | 65 | suggestionButton.addEventListener("click", function () { 66 | extensionApi.tabs.create({ 67 | url: "https://github.com/MStankiewiczOfficial/RedirectTube/issues/new?assignees=MStankiewiczOfficial&labels=enhancement&projects=&template=feature-request.yml&title=%5BFR%5D%3A+", 68 | }); 69 | }); 70 | 71 | issueButton.addEventListener("click", function () { 72 | extensionApi.tabs.create({ 73 | url: "https://github.com/MStankiewiczOfficial/RedirectTube/issues/new?assignees=MStankiewiczOfficial&labels=bug&projects=&template=bug-report.yml&title=%5BBug%5D%3A+", 74 | }); 75 | }); 76 | 77 | setTimeout(() => { 78 | document.querySelector("#version").textContent = 79 | extensionApi.runtime.getManifest().version; 80 | }, 10); 81 | 82 | document.addEventListener("DOMContentLoaded", restoreOptions); 83 | document 84 | .querySelector("#popupBehavior") 85 | .addEventListener("change", saveOptions); 86 | document 87 | .querySelector("#autoRedirectLinks") 88 | .addEventListener("change", saveOptions); 89 | document.querySelector("#iframeButton").addEventListener("change", saveOptions); 90 | document.querySelector("#colorIcon").addEventListener("click", saveOptions); 91 | document.querySelector("#monoIcon").addEventListener("click", saveOptions); 92 | -------------------------------------------------------------------------------- /src/Gecko/options.js: -------------------------------------------------------------------------------- 1 | const extensionApi = typeof chrome !== "undefined" ? chrome : browser; 2 | 3 | var popupBehavior = "showPopup"; 4 | var autoRedirectLinks = "autoRedirectLinksNo"; 5 | var iframeButton = "iframeButtonYes"; 6 | var extensionIcon = "color"; 7 | 8 | function saveOptions(e) { 9 | setTimeout(() => { 10 | e.preventDefault(); 11 | extensionApi.storage.local.set({ 12 | popupBehavior: document.getElementById("popupBehavior").value, 13 | autoRedirectLinks: document.getElementById("autoRedirectLinks").value, 14 | iframeButton: document.getElementById("iframeButton").value, 15 | extensionIcon: document.querySelector( 16 | 'input[name="extensionIcon"]:checked' 17 | ).value, 18 | }); 19 | extensionApi.runtime.sendMessage({ message: "detectYT" }); 20 | }, 1); 21 | } 22 | 23 | function restoreOptions() { 24 | function setCurrentChoice(result) { 25 | document.getElementById("popupBehavior").value = 26 | result.popupBehavior || popupBehavior; 27 | document.getElementById("autoRedirectLinks").value = 28 | result.autoRedirectLinks || autoRedirectLinks; 29 | document.getElementById("iframeButton").value = 30 | result.iframeButton || iframeButton; 31 | document.querySelector( 32 | 'input[name="extensionIcon"][value="' + 33 | (result.extensionIcon || extensionIcon) + 34 | '"]' 35 | ).checked = true; 36 | } 37 | 38 | function onError(error) { 39 | console.log(`Error: ${error}`); 40 | } 41 | 42 | extensionApi.storage.local.get( 43 | [ 44 | "popupBehavior", 45 | "autoRedirectLinks", 46 | "iframeButton", 47 | "extensionIcon", 48 | ], 49 | function (result) { 50 | if (extensionApi.runtime.lastError) { 51 | onError(extensionApi.runtime.lastError); 52 | return; 53 | } 54 | setCurrentChoice(result || {}); 55 | } 56 | ); 57 | } 58 | 59 | opinionButton.addEventListener("click", function () { 60 | extensionApi.tabs.create({ 61 | url: "https://addons.mozilla.org/firefox/addon/redirecttube/", 62 | }); 63 | }); 64 | 65 | suggestionButton.addEventListener("click", function () { 66 | extensionApi.tabs.create({ 67 | url: "https://github.com/MStankiewiczOfficial/RedirectTube/issues/new?assignees=MStankiewiczOfficial&labels=enhancement&projects=&template=feature-request.yml&title=%5BFR%5D%3A+", 68 | }); 69 | }); 70 | 71 | issueButton.addEventListener("click", function () { 72 | extensionApi.tabs.create({ 73 | url: "https://github.com/MStankiewiczOfficial/RedirectTube/issues/new?assignees=MStankiewiczOfficial&labels=bug&projects=&template=bug-report.yml&title=%5BBug%5D%3A+", 74 | }); 75 | }); 76 | 77 | setTimeout(() => { 78 | document.querySelector("#version").textContent = 79 | extensionApi.runtime.getManifest().version; 80 | }, 10); 81 | 82 | document.addEventListener("DOMContentLoaded", restoreOptions); 83 | document 84 | .querySelector("#popupBehavior") 85 | .addEventListener("change", saveOptions); 86 | document 87 | .querySelector("#autoRedirectLinks") 88 | .addEventListener("change", saveOptions); 89 | document.querySelector("#iframeButton").addEventListener("change", saveOptions); 90 | document.querySelector("#colorIcon").addEventListener("click", saveOptions); 91 | document.querySelector("#monoIcon").addEventListener("click", saveOptions); 92 | -------------------------------------------------------------------------------- /src/Chromium/options.css: -------------------------------------------------------------------------------- 1 | @import url('colors.css'); 2 | 3 | body { 4 | background-color: var(--background); 5 | color: var(--color); 6 | font-family: sans-serif; 7 | padding: 0px; 8 | margin: 0; 9 | min-width: 500px; 10 | } 11 | 12 | #title { 13 | width: 100%; 14 | padding: 0; 15 | text-align: center; 16 | background-color: #cb1314; 17 | } 18 | 19 | .logo { 20 | display: block; 21 | margin-left: auto; 22 | margin-right: auto; 23 | padding: 1.5em; 24 | } 25 | 26 | h1 { 27 | margin-top: 1em; 28 | margin-left: 32px; 29 | font-size: 1.7em; 30 | } 31 | 32 | a { 33 | color: var(--url); 34 | transition: all 0.3s; 35 | } 36 | 37 | a:hover { 38 | color: var(--url-hover); 39 | } 40 | 41 | .section { 42 | margin: 0.75em 32px; 43 | } 44 | 45 | select { 46 | position: absolute; 47 | right: 32px; 48 | padding: 0.5em 1em; 49 | margin: 2px; 50 | border: #79797979 solid 1px; 51 | border-radius: 4px; 52 | background-color: #79797979; 53 | color: var(--color); 54 | transform: translateY(-30%); 55 | transition: all 0.3s; 56 | font-size: 0.9em; 57 | } 58 | 59 | select:hover { 60 | background-color: #797979b9; 61 | } 62 | 63 | .extension-icon { 64 | width: min-content; 65 | display: flex; 66 | margin: 0.5em 0.5em 0.5em 0; 67 | } 68 | 69 | .extension-icons-list { 70 | display: flex; 71 | flex-wrap: wrap; 72 | } 73 | 74 | .extension-icon input[type="radio"] { 75 | display: none; 76 | } 77 | 78 | .extension-icons { 79 | padding: 6px 6px 3px 6px; 80 | border: 2px solid var(--background); 81 | border-radius: 8px; 82 | transition: all 0.15s; 83 | } 84 | 85 | .extension-icon input[type="radio"]:hover + .extension-icons { 86 | border: 2px solid var(--color2-transparent); 87 | } 88 | 89 | .extension-icon input[type="radio"]:checked + .extension-icons { 90 | border: 2px solid var(--color2); 91 | } 92 | 93 | .extension-icons img { 94 | height: 32px; 95 | } 96 | 97 | button { 98 | padding: 0.5em 1em; 99 | margin: 2px; 100 | border: #79797979 solid 1px; 101 | border-radius: 4px; 102 | background-color: #79797979; 103 | color: var(--color); 104 | transition: all 0.3s; 105 | font-size: 0.9em; 106 | } 107 | 108 | button:hover { 109 | background-color: #797979b9; 110 | } 111 | 112 | button:disabled { 113 | background-color: #79797979; 114 | color: #79797979; 115 | cursor: not-allowed; 116 | } 117 | 118 | footer { 119 | margin-top: 1em; 120 | padding: 0.5em; 121 | text-align: center; 122 | background-color: #7979793f; 123 | } 124 | 125 | footer p { 126 | font-size: 0.8em; 127 | } 128 | 129 | .socials { 130 | display: flex; 131 | position: fixed; 132 | flex-direction: column; 133 | align-items: flex-end; 134 | top: 8px; 135 | right: 8px; 136 | } 137 | 138 | .social { 139 | display: flex; 140 | align-items: center; 141 | margin: 0 4px; 142 | padding: 6px 8px; 143 | border-radius: 8px; 144 | transition: all 0.3s; 145 | } 146 | 147 | .social-title { 148 | display: none; 149 | color: #ffffff; 150 | margin-right: 6px; 151 | } 152 | 153 | .social-logo { 154 | width: 24px; 155 | height: 24px; 156 | fill: #ffffffd0; 157 | transition: all 0.3s; 158 | cursor: default; 159 | } 160 | .social-logo:hover { 161 | fill: #ffffff; 162 | } 163 | 164 | .beta-label { 165 | position: absolute; 166 | padding: 0.5em; 167 | background-color: #cb1314; 168 | color: #fff; 169 | font-size: 8px; 170 | text-transform: uppercase; 171 | border-radius: 8px; 172 | transform: translate(4px, -50%); 173 | } -------------------------------------------------------------------------------- /src/Gecko/options.css: -------------------------------------------------------------------------------- 1 | @import url('colors.css'); 2 | 3 | body { 4 | background-color: var(--background); 5 | color: var(--color); 6 | font-family: sans-serif; 7 | padding: 0px; 8 | margin: 0; 9 | min-width: 500px; 10 | } 11 | 12 | #title { 13 | width: 100%; 14 | padding: 0; 15 | text-align: center; 16 | background-color: #cb1314; 17 | } 18 | 19 | .logo { 20 | display: block; 21 | margin-left: auto; 22 | margin-right: auto; 23 | padding: 1.5em; 24 | } 25 | 26 | h1 { 27 | margin-top: 1em; 28 | margin-left: 32px; 29 | font-size: 1.7em; 30 | } 31 | 32 | a { 33 | color: var(--url); 34 | transition: all 0.3s; 35 | } 36 | 37 | a:hover { 38 | color: var(--url-hover); 39 | } 40 | 41 | .section { 42 | margin: 0.75em 32px; 43 | } 44 | 45 | select { 46 | position: absolute; 47 | right: 32px; 48 | padding: 0.5em 1em; 49 | margin: 2px; 50 | border: #79797979 solid 1px; 51 | border-radius: 4px; 52 | background-color: #79797979; 53 | color: var(--color); 54 | transform: translateY(-30%); 55 | transition: all 0.3s; 56 | font-size: 0.9em; 57 | } 58 | 59 | select:hover { 60 | background-color: #797979b9; 61 | } 62 | 63 | .extension-icon { 64 | width: min-content; 65 | display: flex; 66 | margin: 0.5em 0.5em 0.5em 0; 67 | } 68 | 69 | .extension-icons-list { 70 | display: flex; 71 | flex-wrap: wrap; 72 | } 73 | 74 | .extension-icon input[type="radio"] { 75 | display: none; 76 | } 77 | 78 | .extension-icons { 79 | padding: 6px 6px 3px 6px; 80 | border: 2px solid var(--background); 81 | border-radius: 8px; 82 | transition: all 0.15s; 83 | } 84 | 85 | .extension-icon input[type="radio"]:hover + .extension-icons { 86 | border: 2px solid var(--color2-transparent); 87 | } 88 | 89 | .extension-icon input[type="radio"]:checked + .extension-icons { 90 | border: 2px solid var(--color2); 91 | } 92 | 93 | .extension-icons img { 94 | height: 32px; 95 | } 96 | 97 | button { 98 | padding: 0.5em 1em; 99 | margin: 2px; 100 | border: #79797979 solid 1px; 101 | border-radius: 4px; 102 | background-color: #79797979; 103 | color: var(--color); 104 | transition: all 0.3s; 105 | font-size: 0.9em; 106 | } 107 | 108 | button:hover { 109 | background-color: #797979b9; 110 | } 111 | 112 | button:disabled { 113 | background-color: #79797979; 114 | color: #79797979; 115 | cursor: not-allowed; 116 | } 117 | 118 | footer { 119 | margin-top: 1em; 120 | padding: 0.5em; 121 | text-align: center; 122 | background-color: #7979793f; 123 | } 124 | 125 | footer p { 126 | font-size: 0.8em; 127 | } 128 | 129 | .socials { 130 | display: flex; 131 | position: fixed; 132 | flex-direction: column; 133 | align-items: flex-end; 134 | top: 8px; 135 | right: 8px; 136 | } 137 | 138 | .social { 139 | display: flex; 140 | align-items: center; 141 | margin: 0 4px; 142 | padding: 6px 8px; 143 | border-radius: 8px; 144 | transition: all 0.3s; 145 | } 146 | 147 | .social-title { 148 | display: none; 149 | color: #ffffff; 150 | margin-right: 6px; 151 | } 152 | 153 | .social-logo { 154 | width: 24px; 155 | height: 24px; 156 | fill: #ffffffd0; 157 | transition: all 0.3s; 158 | cursor: default; 159 | } 160 | .social-logo:hover { 161 | fill: #ffffff; 162 | } 163 | 164 | .beta-label { 165 | position: absolute; 166 | padding: 0.5em; 167 | background-color: #cb1314; 168 | color: #fff; 169 | font-size: 8px; 170 | text-transform: uppercase; 171 | border-radius: 8px; 172 | transform: translate(4px, -50%); 173 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | 4 |
5 | 6 | 7 |

8 | 9 | ## Open YouTube links in FreeTube 10 | 11 | RedirectTube is a browser extension that redirects YouTube links to FreeTube. It is available for Firefox and Chromium-based browsers (Chromium builds are distributed unsigned). 12 | 13 | ## Installation 14 | 15 | ### Mozilla Firefox 16 | 17 | #### Method 1: Firefox Add-ons (recommended) 18 | 19 | You can install RedirectTube from the Firefox Add-ons. 20 | 21 | [![Get the Add-on](https://extensionworkshop.com/assets/img/documentation/publish/get-the-addon-178x60px.dad84b42.png)](https://addons.mozilla.org/pl/firefox/addon/redirecttube/) 22 | 23 | #### Method 2: Manual Firefox installation 24 | 25 | 1. Download the latest release of RedirectTube (file that ends with `-signed.xpi`) from the [releases page](https://github.com/MStankiewiczOfficial/RedirectTube/releases/). If you see an alert about installing add-ons from untrusted sources, click "Continue installation" and don’t proceed with the next steps. 26 | 2. Open the downloaded file in Firefox. 27 | 3. Click "Add" to install the extension. 28 | And that's it! RedirectTube is now installed in your browser. 29 | 30 | #### Method 3: Firefox developer mode 31 | 32 | This method is for developers and advanced users. 33 | 34 | > [!IMPORTANT] 35 | > If you restart your browser, the extension will be disabled. 36 | 37 | 1. Clone the repository. 38 | 2. Go to `about:debugging#/runtime/this-firefox`. 39 | 3. Click "Load Temporary Add-on". 40 | 4. Select the `manifest.json` file from the cloned repository (it's in the `src` directory). 41 | The extension is now installed in your browser. 42 | 43 | ### Chromium-based browsers (unsigned) 44 | 45 | The Chromium package supports Chrome, Chromium, Edge, Brave, Vivaldi, and other Chromium-based browsers. 46 | 47 | > [!NOTE] 48 | > The Chromium build is unsigned and must be reloaded manually whenever you download a new release. 49 | 50 | 1. Download the latest release archive that ends with `-chromium-unsigned.zip` from the [releases page](https://github.com/MStankiewiczOfficial/RedirectTube/releases/). 51 | 2. Extract the ZIP file to a directory you want to keep (the browser needs to access the extracted files). 52 | 3. Open `chrome://extensions` (or the equivalent extensions page in your Chromium browser). 53 | 4. Enable **Developer mode**. 54 | 5. Click **Load unpacked** and select the directory you extracted in step 2. 55 | The extension will appear in the toolbar once the folder is loaded. 56 | 57 | ## Usage 58 | 59 | ### Via button 60 | 61 | Click the RedirectTube button in the toolbar to open the current YouTube video in FreeTube. 62 | ![](/assets/toolbar.jpg) 63 | 64 | ### Via context menu 65 | 66 | Right-click a YouTube link and select "Open in FreeTube" to open the video in FreeTube. 67 | ![](/assets/context-menu.jpg) 68 | 69 | ## Issues 70 | 71 | If you encounter any issues, please report them on the [issues page](https://github.com/MStankiewiczOfficial/RedirectTube/issues/). 72 | 73 | ## Translation 74 | 75 | If your language is not yet supported by RedirectTube, you can change that! Help develop the extension by translating it into your language. 76 | 77 | [![](https://translate.codeberg.org/widget/redirecttube/ui/open-graph.png)](https://translate.codeberg.org/engage/redirecttube/) 78 | 79 | ## License 80 | 81 | RedirectTube is licensed under CC BY-NC-SA 4.0. For details, please refer to the [LICENSE](LICENSE.md). 82 | 83 | > [!NOTE] 84 | > **RedirectTube** is not affiliated with FreeTube or its creators. FreeTube is licensed under the [AGPL-3.0 license](https://github.com/FreeTubeApp/FreeTube/blob/master/LICENSE). The name *FreeTube* and FreeTube logo are the property of the [creators of FreeTube](https://docs.freetubeapp.io/credits/). Neither I nor the extension are associated with them. 85 | -------------------------------------------------------------------------------- /src/Chromium/background.js: -------------------------------------------------------------------------------- 1 | const extensionApi = typeof chrome !== "undefined" ? chrome : browser; 2 | 3 | let lastUrlAllowed = false; 4 | let isDarkThemePreferred = false; 5 | let currentMenuLang = null; 6 | 7 | extensionApi.tabs.onUpdated.addListener((_tabId, changeInfo) => { 8 | if (changeInfo.url) { 9 | handleUrlChange(changeInfo.url); 10 | } 11 | createContextMenu(); 12 | updateContentScriptSettings(); 13 | }); 14 | 15 | extensionApi.tabs.onActivated.addListener((activeInfo) => { 16 | extensionApi.tabs.get(activeInfo.tabId, (tab) => { 17 | if (tab && tab.url) { 18 | handleUrlChange(tab.url); 19 | } 20 | createContextMenu(); 21 | updateContentScriptSettings(); 22 | }); 23 | }); 24 | 25 | extensionApi.runtime.onInstalled.addListener((details) => { 26 | if (details.reason === "install") { 27 | extensionApi.tabs.create({ url: "introduction.html" }); 28 | extensionApi.storage.local.set({ 29 | extensionIcon: "color", 30 | autoRedirectLinks: "autoRedirectLinksNo", 31 | }); 32 | } 33 | detectYTInThisTab(); 34 | createContextMenu(); 35 | updateContentScriptSettings(); 36 | }); 37 | 38 | extensionApi.runtime.onStartup.addListener(() => { 39 | detectYTInThisTab(); 40 | createContextMenu(); 41 | updateContentScriptSettings(); 42 | }); 43 | 44 | extensionApi.runtime.onMessage.addListener((request, sender) => { 45 | if (request.message === "detectYT") { 46 | detectYTInThisTab(); 47 | updateContentScriptSettings(); 48 | } 49 | 50 | if (request.message === "autoRedirectLink" && sender.tab && request.url) { 51 | if (!request.url.startsWith("freetube://")) { 52 | const newUrl = "freetube://" + request.url; 53 | extensionApi.tabs.update(sender.tab.id, { url: newUrl }); 54 | } 55 | } 56 | 57 | if (request.message === "redirecttubeTheme") { 58 | isDarkThemePreferred = Boolean(request.isDark); 59 | updateActionIcon(); 60 | } 61 | }); 62 | 63 | function detectYTInThisTab() { 64 | extensionApi.tabs.query({ active: true, currentWindow: true }, (tabs) => { 65 | if (!tabs || !tabs.length) { 66 | return; 67 | } 68 | const tab = tabs[0]; 69 | if (tab.url) { 70 | handleUrlChange(tab.url); 71 | } 72 | }); 73 | } 74 | 75 | function handleUrlChange(url) { 76 | if (!url) { 77 | return; 78 | } 79 | lastUrlAllowed = isYoutubeUrl(url); 80 | updateActionIcon(); 81 | } 82 | 83 | function isYoutubeUrl(url) { 84 | return ( 85 | url.startsWith("https://www.youtube.com/watch?v=") || 86 | url.startsWith("https://www.youtube.com/playlist?list=") || 87 | url.startsWith("https://www.youtube.com/@") || 88 | url.startsWith("https://www.youtube.com/channel/") 89 | ); 90 | } 91 | 92 | function updateActionIcon() { 93 | extensionApi.storage.local.get("extensionIcon", (result) => { 94 | const preference = (result && result.extensionIcon) || "color"; 95 | const path = getIconPath( 96 | preference, 97 | lastUrlAllowed, 98 | isDarkThemePreferred 99 | ); 100 | extensionApi.action.setIcon({ path }); 101 | }); 102 | } 103 | 104 | function getIconPath(preference, isAllowed, isDarkMode) { 105 | if (preference === "mono") { 106 | if (isDarkMode) { 107 | return isAllowed 108 | ? "img/icns/mono/white/allow/64.png" 109 | : "img/icns/mono/white/disallow/64.png"; 110 | } 111 | return isAllowed 112 | ? "img/icns/mono/black/allow/64.png" 113 | : "img/icns/mono/black/disallow/64.png"; 114 | } 115 | return isAllowed 116 | ? "img/icns/color/allow/64.png" 117 | : "img/icns/color/disallow/64.png"; 118 | } 119 | 120 | function updateContentScriptSettings() { 121 | extensionApi.storage.local.get( 122 | ["lang", "iframeButton", "autoRedirectLinks"], 123 | (result) => { 124 | const selectedLang = result.lang || "en"; 125 | const iframeButtonSetting = 126 | result.iframeButton || "iframeButtonYes"; 127 | const autoRedirectSetting = 128 | result.autoRedirectLinks || "autoRedirectLinksNo"; 129 | fetch(`i18n/locales/${selectedLang}.json`) 130 | .then((response) => response.json()) 131 | .then((data) => { 132 | const buttonName = data.ui.iframeButton.redirect; 133 | extensionApi.tabs.query( 134 | { active: true, currentWindow: true }, 135 | (tabs) => { 136 | if (!tabs || !tabs.length) { 137 | return; 138 | } 139 | extensionApi.tabs.sendMessage( 140 | tabs[0].id, 141 | { 142 | redirecttubeButtonName: buttonName, 143 | redirecttubeIframeButton: 144 | iframeButtonSetting, 145 | redirecttubeAutoRedirect: 146 | autoRedirectSetting, 147 | }, 148 | () => extensionApi.runtime.lastError 149 | ); 150 | } 151 | ); 152 | }) 153 | .catch(() => {}); 154 | } 155 | ); 156 | } 157 | 158 | extensionApi.contextMenus.onClicked.addListener((info) => { 159 | if (info.menuItemId === "openInFreeTube" && info.linkUrl) { 160 | const newUrl = "freetube://" + info.linkUrl; 161 | if (typeof info.tabId === "number") { 162 | extensionApi.tabs.update(info.tabId, { url: newUrl }); 163 | } else { 164 | extensionApi.tabs.update({ url: newUrl }); 165 | } 166 | } 167 | }); 168 | 169 | function createContextMenu() { 170 | extensionApi.storage.local.get("lang", (result) => { 171 | const lang = result && result.lang ? result.lang : "en"; 172 | if (lang === currentMenuLang) { 173 | return; 174 | } 175 | currentMenuLang = lang; 176 | extensionApi.contextMenus.removeAll(() => { 177 | fetch(`i18n/locales/${lang}.json`) 178 | .then((response) => response.json()) 179 | .then((data) => { 180 | extensionApi.contextMenus.create({ 181 | id: "openInFreeTube", 182 | title: data.ui.contextMenu.redirect, 183 | contexts: ["link"], 184 | targetUrlPatterns: [ 185 | "*://www.youtube.com/*", 186 | "*://youtube.com/*", 187 | "*://youtu.be/*", 188 | ], 189 | }); 190 | }) 191 | .catch(() => {}); 192 | }); 193 | }); 194 | } 195 | -------------------------------------------------------------------------------- /src/Gecko/background.js: -------------------------------------------------------------------------------- 1 | const extensionApi = typeof chrome !== "undefined" ? chrome : browser; 2 | 3 | let lastUrlAllowed = false; 4 | let isDarkThemePreferred = false; 5 | let currentMenuLang = null; 6 | 7 | extensionApi.tabs.onUpdated.addListener((_tabId, changeInfo) => { 8 | if (changeInfo.url) { 9 | handleUrlChange(changeInfo.url); 10 | } 11 | createContextMenu(); 12 | updateContentScriptSettings(); 13 | }); 14 | 15 | extensionApi.tabs.onActivated.addListener((activeInfo) => { 16 | extensionApi.tabs.get(activeInfo.tabId, (tab) => { 17 | if (tab && tab.url) { 18 | handleUrlChange(tab.url); 19 | } 20 | createContextMenu(); 21 | updateContentScriptSettings(); 22 | }); 23 | }); 24 | 25 | extensionApi.runtime.onInstalled.addListener((details) => { 26 | if (details.reason === "install") { 27 | extensionApi.tabs.create({ url: "introduction.html" }); 28 | extensionApi.storage.local.set({ 29 | extensionIcon: "color", 30 | autoRedirectLinks: "autoRedirectLinksNo", 31 | }); 32 | } 33 | detectYTInThisTab(); 34 | createContextMenu(); 35 | updateContentScriptSettings(); 36 | }); 37 | 38 | extensionApi.runtime.onStartup.addListener(() => { 39 | detectYTInThisTab(); 40 | createContextMenu(); 41 | updateContentScriptSettings(); 42 | }); 43 | 44 | extensionApi.runtime.onMessage.addListener((request, sender) => { 45 | if (request.message === "detectYT") { 46 | detectYTInThisTab(); 47 | updateContentScriptSettings(); 48 | } 49 | 50 | if (request.message === "autoRedirectLink" && sender.tab && request.url) { 51 | if (!request.url.startsWith("freetube://")) { 52 | const newUrl = "freetube://" + request.url; 53 | extensionApi.tabs.update(sender.tab.id, { url: newUrl }); 54 | } 55 | } 56 | 57 | if (request.message === "redirecttubeTheme") { 58 | isDarkThemePreferred = Boolean(request.isDark); 59 | updateActionIcon(); 60 | } 61 | }); 62 | 63 | function detectYTInThisTab() { 64 | extensionApi.tabs.query({ active: true, currentWindow: true }, (tabs) => { 65 | if (!tabs || !tabs.length) { 66 | return; 67 | } 68 | const tab = tabs[0]; 69 | if (tab.url) { 70 | handleUrlChange(tab.url); 71 | } 72 | }); 73 | } 74 | 75 | function handleUrlChange(url) { 76 | if (!url) { 77 | return; 78 | } 79 | lastUrlAllowed = isYoutubeUrl(url); 80 | updateActionIcon(); 81 | } 82 | 83 | function isYoutubeUrl(url) { 84 | return ( 85 | url.startsWith("https://www.youtube.com/watch?v=") || 86 | url.startsWith("https://www.youtube.com/playlist?list=") || 87 | url.startsWith("https://www.youtube.com/@") || 88 | url.startsWith("https://www.youtube.com/channel/") 89 | ); 90 | } 91 | 92 | function updateActionIcon() { 93 | extensionApi.storage.local.get("extensionIcon", (result) => { 94 | const preference = (result && result.extensionIcon) || "color"; 95 | const path = getIconPath( 96 | preference, 97 | lastUrlAllowed, 98 | isDarkThemePreferred 99 | ); 100 | extensionApi.action.setIcon({ path }); 101 | }); 102 | } 103 | 104 | function getIconPath(preference, isAllowed, isDarkMode) { 105 | if (preference === "mono") { 106 | if (isDarkMode) { 107 | return isAllowed 108 | ? "img/icns/mono/white/allow/64.png" 109 | : "img/icns/mono/white/disallow/64.png"; 110 | } 111 | return isAllowed 112 | ? "img/icns/mono/black/allow/64.png" 113 | : "img/icns/mono/black/disallow/64.png"; 114 | } 115 | return isAllowed 116 | ? "img/icns/color/allow/64.png" 117 | : "img/icns/color/disallow/64.png"; 118 | } 119 | 120 | function updateContentScriptSettings() { 121 | extensionApi.storage.local.get( 122 | ["lang", "iframeButton", "autoRedirectLinks"], 123 | (result) => { 124 | const selectedLang = result.lang || "en"; 125 | const iframeButtonSetting = 126 | result.iframeButton || "iframeButtonYes"; 127 | const autoRedirectSetting = 128 | result.autoRedirectLinks || "autoRedirectLinksNo"; 129 | fetch(`i18n/locales/${selectedLang}.json`) 130 | .then((response) => response.json()) 131 | .then((data) => { 132 | const buttonName = data.ui.iframeButton.redirect; 133 | extensionApi.tabs.query( 134 | { active: true, currentWindow: true }, 135 | (tabs) => { 136 | if (!tabs || !tabs.length) { 137 | return; 138 | } 139 | extensionApi.tabs.sendMessage( 140 | tabs[0].id, 141 | { 142 | redirecttubeButtonName: buttonName, 143 | redirecttubeIframeButton: 144 | iframeButtonSetting, 145 | redirecttubeAutoRedirect: 146 | autoRedirectSetting, 147 | }, 148 | () => extensionApi.runtime.lastError 149 | ); 150 | } 151 | ); 152 | }) 153 | .catch(() => {}); 154 | } 155 | ); 156 | } 157 | 158 | extensionApi.contextMenus.onClicked.addListener((info) => { 159 | if (info.menuItemId === "openInFreeTube" && info.linkUrl) { 160 | const newUrl = "freetube://" + info.linkUrl; 161 | if (typeof info.tabId === "number") { 162 | extensionApi.tabs.update(info.tabId, { url: newUrl }); 163 | } else { 164 | extensionApi.tabs.update({ url: newUrl }); 165 | } 166 | } 167 | }); 168 | 169 | function createContextMenu() { 170 | extensionApi.storage.local.get("lang", (result) => { 171 | const lang = result && result.lang ? result.lang : "en"; 172 | if (lang === currentMenuLang) { 173 | return; 174 | } 175 | currentMenuLang = lang; 176 | extensionApi.contextMenus.removeAll(() => { 177 | fetch(`i18n/locales/${lang}.json`) 178 | .then((response) => response.json()) 179 | .then((data) => { 180 | extensionApi.contextMenus.create({ 181 | id: "openInFreeTube", 182 | title: data.ui.contextMenu.redirect, 183 | contexts: ["link"], 184 | targetUrlPatterns: [ 185 | "*://www.youtube.com/*", 186 | "*://youtube.com/*", 187 | "*://youtu.be/*", 188 | ], 189 | }); 190 | }) 191 | .catch(() => {}); 192 | }); 193 | }); 194 | } 195 | -------------------------------------------------------------------------------- /src/Chromium/img/ft-logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/Gecko/img/ft-logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/Chromium/popup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | RedirectTube 7 | 8 | 9 | 10 |
11 | 64 |
65 |
66 | 67 | 68 | 69 | 70 | 71 |
72 |

73 | 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /src/Gecko/popup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | RedirectTube 7 | 8 | 9 | 10 |
11 | 64 |
65 |
66 | 67 | 68 | 69 | 70 | 71 |
72 |

73 | 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /src/Gecko/introduction.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | RedirectTube • Introduction 8 | 9 | 10 | 11 | 12 | 13 |
14 | 67 |
68 | 76 | 84 |
85 |
86 |
87 |
88 |

Usage

89 |
90 |
91 |

Click the icon when you are on a YouTube video page

92 | Toolbar 93 |
94 |
95 |

or right-click on the url of a YouTube video

96 | RedirectTube 97 |
98 |
99 | 100 |
101 |
102 | 103 | 104 | 105 | -------------------------------------------------------------------------------- /src/Chromium/introduction.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | RedirectTube • Introduction 8 | 9 | 10 | 11 | 12 | 13 |
14 | 67 |
68 | 76 | 84 |
85 |
86 |
87 |
88 |

Usage

89 |
90 |
91 |

Click the icon when you are on a YouTube video page

92 | Toolbar 93 |
94 |
95 |

or right-click on the url of a YouTube video

96 | RedirectTube 97 |
98 |
99 | 100 |
101 |
102 | 103 | 104 | 105 | -------------------------------------------------------------------------------- /src/Gecko/content.js: -------------------------------------------------------------------------------- 1 | const extensionApi = typeof chrome !== "undefined" ? chrome : browser; 2 | 3 | let redirecttubeAutoRedirect = "autoRedirectLinksNo"; 4 | let isTopLevelDocument = false; 5 | 6 | try { 7 | isTopLevelDocument = window.top === window; 8 | } catch (error) { 9 | isTopLevelDocument = true; 10 | } 11 | 12 | if (isTopLevelDocument) { 13 | document.addEventListener("click", handleDocumentClick, true); 14 | syncThemePreference(); 15 | } 16 | 17 | function syncThemePreference() { 18 | const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)"); 19 | 20 | function notifyBackground() { 21 | extensionApi.runtime.sendMessage({ 22 | message: "redirecttubeTheme", 23 | isDark: mediaQuery.matches, 24 | }); 25 | } 26 | 27 | notifyBackground(); 28 | if (typeof mediaQuery.addEventListener === "function") { 29 | mediaQuery.addEventListener("change", notifyBackground); 30 | } else if (typeof mediaQuery.addListener === "function") { 31 | mediaQuery.addListener(notifyBackground); 32 | } 33 | } 34 | 35 | function addDivIframe(iframe, buttonName) { 36 | if (!iframe || iframe.dataset.buttonAdded === "true") return; 37 | iframe.dataset.buttonAdded = "true"; 38 | 39 | const rect = iframe.getBoundingClientRect(); 40 | const ftLogo = ``; 41 | const div = document.createElement("div"); 42 | 43 | div.innerHTML = 44 | "" + 47 | buttonName + 48 | " " + 49 | ftLogo + 50 | ""; 51 | div.classList.add("redirecttube-redirection-div"); 52 | div.style.top = `${window.scrollY + rect.bottom - 110}px`; 53 | div.style.left = `${window.scrollX + rect.left}px`; 54 | 55 | document.body.appendChild(div); 56 | } 57 | 58 | window.addEventListener("resize", updateButtons); 59 | 60 | window.addEventListener("scroll", updateButtons); 61 | 62 | function updateButtons() { 63 | const buttons = document.querySelectorAll(".redirecttube-redirection-div"); 64 | const iframes = document.querySelectorAll("iframe"); 65 | iframes.forEach((iframe) => { 66 | if (iframe.dataset.buttonAdded === "true") { 67 | iframe.dataset.buttonAdded = "false"; 68 | } 69 | }); 70 | buttons.forEach((button) => button.remove()); 71 | processIframes(localStorage.getItem("redirecttubeButtonName")); 72 | } 73 | 74 | function processIframes(buttonName) { 75 | const iframes = document.querySelectorAll("iframe"); 76 | iframes.forEach((iframe) => { 77 | if ( 78 | iframe.src.includes("youtube.com/embed") || 79 | iframe.src.includes("youtube-nocookie.com/embed") 80 | ) { 81 | addDivIframe(iframe, buttonName); 82 | } 83 | }); 84 | } 85 | 86 | extensionApi.runtime.onMessage.addListener((request) => { 87 | const iframePreference = 88 | request.redirecttubeIframeButton || "iframeButtonYes"; 89 | const buttonName = 90 | request.redirecttubeButtonName || 91 | localStorage.getItem("redirecttubeButtonName"); 92 | 93 | if (iframePreference !== "iframeButtonNo" && buttonName) { 94 | processIframes(buttonName); 95 | } 96 | 97 | if (request.redirecttubeButtonName) { 98 | localStorage.setItem( 99 | "redirecttubeButtonName", 100 | request.redirecttubeButtonName 101 | ); 102 | } 103 | 104 | redirecttubeAutoRedirect = 105 | request.redirecttubeAutoRedirect || "autoRedirectLinksNo"; 106 | }); 107 | 108 | function handleDocumentClick(event) { 109 | if (redirecttubeAutoRedirect !== "autoRedirectLinksYes") { 110 | return; 111 | } 112 | if (event.defaultPrevented || event.button !== 0) { 113 | return; 114 | } 115 | if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) { 116 | return; 117 | } 118 | 119 | const anchor = event.target.closest("a[href]"); 120 | if (!anchor) { 121 | return; 122 | } 123 | 124 | if (anchor.hasAttribute("download")) { 125 | return; 126 | } 127 | 128 | const targetAttribute = 129 | (anchor.getAttribute("target") || "").toLowerCase(); 130 | if (targetAttribute && 131 | targetAttribute !== "_self" && 132 | targetAttribute !== "_top" && 133 | targetAttribute !== "_parent") { 134 | return; 135 | } 136 | 137 | const resolvedUrl = resolveAbsoluteUrl(anchor.getAttribute("href")); 138 | if (!resolvedUrl || !shouldRedirectUrl(resolvedUrl)) { 139 | return; 140 | } 141 | 142 | event.preventDefault(); 143 | event.stopPropagation(); 144 | extensionApi.runtime.sendMessage({ 145 | message: "autoRedirectLink", 146 | url: resolvedUrl, 147 | }); 148 | } 149 | 150 | function resolveAbsoluteUrl(href) { 151 | if (!href) { 152 | return null; 153 | } 154 | try { 155 | return new URL(href, window.location.href).toString(); 156 | } catch (error) { 157 | return null; 158 | } 159 | } 160 | 161 | function shouldRedirectUrl(url) { 162 | try { 163 | const parsedUrl = new URL(url); 164 | const host = parsedUrl.hostname.toLowerCase(); 165 | 166 | if (host === "youtu.be") { 167 | return parsedUrl.pathname.length > 1; 168 | } 169 | 170 | if (host.endsWith("youtube.com")) { 171 | if (parsedUrl.pathname.startsWith("/watch") && parsedUrl.searchParams.has("v")) { 172 | return true; 173 | } 174 | if (parsedUrl.pathname.startsWith("/playlist") && parsedUrl.searchParams.has("list")) { 175 | return true; 176 | } 177 | if (parsedUrl.pathname.startsWith("/@")) { 178 | return true; 179 | } 180 | if (parsedUrl.pathname.startsWith("/channel/")) { 181 | return true; 182 | } 183 | } 184 | } catch (error) { 185 | return false; 186 | } 187 | 188 | return false; 189 | } 190 | 191 | function redirecttubeOpenInFreeTube(src) { 192 | let newUrl = "freetube://" + src; 193 | window.open(newUrl, "_blank"); 194 | } 195 | -------------------------------------------------------------------------------- /src/Chromium/content.js: -------------------------------------------------------------------------------- 1 | const extensionApi = typeof chrome !== "undefined" ? chrome : browser; 2 | 3 | let redirecttubeAutoRedirect = "autoRedirectLinksNo"; 4 | let isTopLevelDocument = false; 5 | 6 | try { 7 | isTopLevelDocument = window.top === window; 8 | } catch (error) { 9 | isTopLevelDocument = true; 10 | } 11 | 12 | if (isTopLevelDocument) { 13 | document.addEventListener("click", handleDocumentClick, true); 14 | syncThemePreference(); 15 | } 16 | 17 | function syncThemePreference() { 18 | const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)"); 19 | 20 | function notifyBackground() { 21 | extensionApi.runtime.sendMessage({ 22 | message: "redirecttubeTheme", 23 | isDark: mediaQuery.matches, 24 | }); 25 | } 26 | 27 | notifyBackground(); 28 | if (typeof mediaQuery.addEventListener === "function") { 29 | mediaQuery.addEventListener("change", notifyBackground); 30 | } else if (typeof mediaQuery.addListener === "function") { 31 | mediaQuery.addListener(notifyBackground); 32 | } 33 | } 34 | 35 | function addDivIframe(iframe, buttonName) { 36 | if (!iframe || iframe.dataset.buttonAdded === "true") return; 37 | iframe.dataset.buttonAdded = "true"; 38 | 39 | const rect = iframe.getBoundingClientRect(); 40 | const ftLogo = ``; 41 | const div = document.createElement("div"); 42 | 43 | div.innerHTML = 44 | "" + 47 | buttonName + 48 | " " + 49 | ftLogo + 50 | ""; 51 | div.classList.add("redirecttube-redirection-div"); 52 | div.style.top = `${window.scrollY + rect.bottom - 110}px`; 53 | div.style.left = `${window.scrollX + rect.left}px`; 54 | 55 | document.body.appendChild(div); 56 | } 57 | 58 | window.addEventListener("resize", updateButtons); 59 | 60 | window.addEventListener("scroll", updateButtons); 61 | 62 | function updateButtons() { 63 | const buttons = document.querySelectorAll(".redirecttube-redirection-div"); 64 | const iframes = document.querySelectorAll("iframe"); 65 | iframes.forEach((iframe) => { 66 | if (iframe.dataset.buttonAdded === "true") { 67 | iframe.dataset.buttonAdded = "false"; 68 | } 69 | }); 70 | buttons.forEach((button) => button.remove()); 71 | processIframes(localStorage.getItem("redirecttubeButtonName")); 72 | } 73 | 74 | function processIframes(buttonName) { 75 | const iframes = document.querySelectorAll("iframe"); 76 | iframes.forEach((iframe) => { 77 | if ( 78 | iframe.src.includes("youtube.com/embed") || 79 | iframe.src.includes("youtube-nocookie.com/embed") 80 | ) { 81 | addDivIframe(iframe, buttonName); 82 | } 83 | }); 84 | } 85 | 86 | extensionApi.runtime.onMessage.addListener((request) => { 87 | const iframePreference = 88 | request.redirecttubeIframeButton || "iframeButtonYes"; 89 | const buttonName = 90 | request.redirecttubeButtonName || 91 | localStorage.getItem("redirecttubeButtonName"); 92 | 93 | if (iframePreference !== "iframeButtonNo" && buttonName) { 94 | processIframes(buttonName); 95 | } 96 | 97 | if (request.redirecttubeButtonName) { 98 | localStorage.setItem( 99 | "redirecttubeButtonName", 100 | request.redirecttubeButtonName 101 | ); 102 | } 103 | 104 | redirecttubeAutoRedirect = 105 | request.redirecttubeAutoRedirect || "autoRedirectLinksNo"; 106 | }); 107 | 108 | function handleDocumentClick(event) { 109 | if (redirecttubeAutoRedirect !== "autoRedirectLinksYes") { 110 | return; 111 | } 112 | if (event.defaultPrevented || event.button !== 0) { 113 | return; 114 | } 115 | if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) { 116 | return; 117 | } 118 | 119 | const anchor = event.target.closest("a[href]"); 120 | if (!anchor) { 121 | return; 122 | } 123 | 124 | if (anchor.hasAttribute("download")) { 125 | return; 126 | } 127 | 128 | const targetAttribute = 129 | (anchor.getAttribute("target") || "").toLowerCase(); 130 | if (targetAttribute && 131 | targetAttribute !== "_self" && 132 | targetAttribute !== "_top" && 133 | targetAttribute !== "_parent") { 134 | return; 135 | } 136 | 137 | const resolvedUrl = resolveAbsoluteUrl(anchor.getAttribute("href")); 138 | if (!resolvedUrl || !shouldRedirectUrl(resolvedUrl)) { 139 | return; 140 | } 141 | 142 | event.preventDefault(); 143 | event.stopPropagation(); 144 | extensionApi.runtime.sendMessage({ 145 | message: "autoRedirectLink", 146 | url: resolvedUrl, 147 | }); 148 | } 149 | 150 | function resolveAbsoluteUrl(href) { 151 | if (!href) { 152 | return null; 153 | } 154 | try { 155 | return new URL(href, window.location.href).toString(); 156 | } catch (error) { 157 | return null; 158 | } 159 | } 160 | 161 | function shouldRedirectUrl(url) { 162 | try { 163 | const parsedUrl = new URL(url); 164 | const host = parsedUrl.hostname.toLowerCase(); 165 | 166 | if (host === "youtu.be") { 167 | return parsedUrl.pathname.length > 1; 168 | } 169 | 170 | if (host.endsWith("youtube.com")) { 171 | if (parsedUrl.pathname.startsWith("/watch") && parsedUrl.searchParams.has("v")) { 172 | return true; 173 | } 174 | if (parsedUrl.pathname.startsWith("/playlist") && parsedUrl.searchParams.has("list")) { 175 | return true; 176 | } 177 | if (parsedUrl.pathname.startsWith("/@")) { 178 | return true; 179 | } 180 | if (parsedUrl.pathname.startsWith("/channel/")) { 181 | return true; 182 | } 183 | } 184 | } catch (error) { 185 | return false; 186 | } 187 | 188 | return false; 189 | } 190 | 191 | function redirecttubeOpenInFreeTube(src) { 192 | let newUrl = "freetube://" + src; 193 | window.open(newUrl, "_blank"); 194 | } 195 | --------------------------------------------------------------------------------