diff --git a/.gitignore b/.gitignore index cfaad76..cb560a1 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ *.pem +*.pdf +*.pptx \ No newline at end of file diff --git a/background.js b/background.js new file mode 100644 index 0000000..db04542 --- /dev/null +++ b/background.js @@ -0,0 +1,32 @@ +let manifest = function() { + try { return browser.runtime.getManifest(); } catch (e) { console.debug(e); } + try { return chrome.runtime.getManifest(); } catch (e) { console.debug(e); } +}(); + +function handleOnInstalled(details) { + if (details.reason == "install") { + let notificationOptions = { + type: "basic", + title: "Thanks!", + message: "Thanks for installing Better 9gag.", + iconUrl: "icons/icon-48.png" + }; + try { browser.notifications.create(notificationOptions); } catch (e) { console.debug(e); } + try { chrome.notifications.create(notificationOptions); } catch (e) { console.debug(e); } + } + else if (details.reason == "update") { + let notificationOptions = { + type: "basic", + title: `Version ${manifest.version} Changelog`, + message: `Better 9gag was updated. Here is what has changed: + • This notification was added + • The extension now removes the sticky button in the bottom right + • Under-the-hood changes in preparation for version 2.0`, + iconUrl: "icons/icon-48.png" + }; + try { browser.notifications.create(notificationOptions); } catch (e) { console.debug(e); } + try { chrome.notifications.create(notificationOptions); } catch (e) { console.debug(e); } + } +} +try { browser.runtime.onInstalled.addListener(handleOnInstalled); } catch (e) { console.debug(e); } +try { chrome.runtime.onInstalled.addListener(handleOnInstalled); } catch (e) { console.debug(e); } diff --git a/components/night-mode.js b/components/night-mode.js new file mode 100644 index 0000000..5a76f7a --- /dev/null +++ b/components/night-mode.js @@ -0,0 +1,152 @@ +/* #### Dark/Night theme on desktop #### */ +function getResourceURL(resource) { + if (typeof(browser) == "undefined") { + return chrome.runtime.getURL(resource); + } + else { + return chrome.runtime.getURL(resource); + } +} + +const LowBrightness = getResourceURL("icons/low-brightness-symbol.png"); +const HighBrightness = getResourceURL("icons/high-brightness-symbol.png"); + +function addThemeSwitch() { + // Code to inject into the site; triggers the body observer + const switchFunctionTrigger = 'function switchThemeTrigger() { let trigger = document.createElement("div"); trigger.id = "switch-theme-trigger"; let stylesheet = document.getElementById("dark-theme"); if (stylesheet) { trigger.setAttribute("data-switch-to", "reset"); } else { trigger.setAttribute("data-switch-to", "toDark"); } document.body.append(trigger); }'; + // Inject code (end of body) + let switchFunctionTag = document.createElement("script"); + switchFunctionTag.id = "theme-switch-function"; + switchFunctionTag.appendChild(document.createTextNode(switchFunctionTrigger)); + document.head.append(switchFunctionTag); + + // Create the switch button + let themeSwitchWrapper = document.createElement("div"); + themeSwitchWrapper.classList.add("general-function"); + themeSwitchWrapper.id = "theme-switch-wrapper"; + + let themeSwitchLink = document.createElement("a"); + themeSwitchLink.id = "theme-switch"; + themeSwitchLink.href= "javascript:void(0)"; + themeSwitchLink.setAttribute("onclick", "switchThemeTrigger()"); + + let themeSwitchImg = document.createElement("img"); + themeSwitchImg.src = LowBrightness; + + themeSwitchLink.append(themeSwitchImg); + themeSwitchWrapper.appendChild(themeSwitchLink); + + // ... and add it to the site (in the header, next to the search) + let wrapper = document.getElementsByClassName("function-wrap")[0]; + wrapper.insertBefore(themeSwitchWrapper, wrapper.childNodes[0]); +} + +// Actual function to switch the theme; is run by the body observer +function switchTheme(target) { + let themeSwitch = document.getElementById("theme-switch"); + // If the target theme is not dark, means back to normal + if (target != "toDark") { + let stylesheet = document.getElementById("dark-theme"); + // remove the dark stylesheet (if present) and change the switch icon + if (stylesheet) { + stylesheet.parentNode.removeChild(stylesheet); + themeSwitch.firstChild.src = LowBrightness; + } + // finally save the state in extension storage + if (typeof(browser) == "undefined") { + chrome.storage.local.set({gagIsDark: false}); + } + else { + browser.storage.local.set({gagIsDark: false}); + } + } + // If the target theme is dark + else if (target == "toDark") { + let stylesheet = document.getElementById("dark-theme"); + // create and add a link to the dark stylesheet (if not already present) and change the switch icon + if (!stylesheet) { + stylesheet = document.createElement("link"); + stylesheet.id = "dark-theme"; + stylesheet.href = getResourceURL("stylesheets/darken-9gag.css"); + stylesheet.rel = "stylesheet"; + stylesheet.type = "text/css"; + document.getElementsByTagName("head")[0].appendChild(stylesheet); + themeSwitch.firstChild.src = HighBrightness; + } + // finally save the state in extension storage + if (typeof(browser) == "undefined") { + chrome.storage.local.set({gagIsDark: true}); + } + else { + browser.storage.local.set({gagIsDark: true}); + } + } +} + +function registerThemeSwitchObserver() { + // function to call, if body observer fires + let callback = function() { + // function to be run by the below promise on success + function onGot(item) { + if (item.gagIsDark == undefined) { + if (typeof(browser) == "undefined") { chrome.storage.local.set({gagIsDark: false}); } + else { browser.storage.local.set({gagIsDark: false}); } + } + // check if the trigger is present + var trigger = document.getElementById("switch-theme-trigger"); + // if the trigger is present (user requested theme change) + if (trigger != null) { + // if storage is dark and request is smth else (reset) => switch theme to normal + if (item.gagIsDark === true && trigger.getAttribute("data-switch-to") != "toDark") { + switchTheme("reset"); + } + // if storage is normal and request change to dark => switch theme to dark + else if (item.gagIsDark === false && trigger.getAttribute("data-switch-to") == "toDark") { + switchTheme("toDark"); + } + //finally remove the trigger + trigger.parentNode.removeChild(trigger); + } + //if the trigger is not present (first load or user navigated) + else { + // switch theme according to the storage item + if (item.gagIsDark === true) { + switchTheme("toDark"); + } else { + switchTheme("reset"); + } + } + } + // function to be run by the below promise on an error + function onError(error) { + console.debug(`Error: ${error}`); + } + // Chrome: uses "chrome" namespace, doesn't support promises + if (typeof(browser) == "undefined" && typeof(chrome) != "undefined") { + // read the extension storage and run onGot() as callback + chrome.storage.local.get(null, onGot); + } + // Edge: uses "browser" namespace, doesn't support promises + else if (typeof(chrome) == "undefined" && typeof(browser) != "undefined") { + // read the extension storage and run onGot() as callback + browser.storage.local.get(null, onGot); + } + // Firefox: supports both namespaces and promises + else { + // read the extension storage (it's a promise) + let get_storage = browser.storage.local.get(); + // if successful, run onGot(); if not run onError() + get_storage.then(onGot, onError); + } + }; + // elements to be observed + let config = { childList: true, subtree: true }; + // create and attach the observer + let body_observer = new MutationObserver(callback); + body_observer.observe(document.body, config); +} + +// Add the switch to the site when DOM is ready +document.addEventListener("DOMContentLoaded", addThemeSwitch); +// Register the observer +document.addEventListener("DOMContentLoaded", registerThemeSwitchObserver); diff --git a/components/no-ora-tv.js b/components/no-ora-tv.js new file mode 100644 index 0000000..5080127 --- /dev/null +++ b/components/no-ora-tv.js @@ -0,0 +1,12 @@ +/* ### Remove annoying paid video post from ora.tv ### */ +function rmOraVid() { + // find it + let ora = document.querySelector('iframe[src^="https://www.ora.tv"]'); + // and delete it + if (ora) { + ora = ora.parentNode.parentNode; + ora.parentNode.removeChild(ora); + } +} +// TODO: Replace event by Mutation Observer +document.addEventListener("scroll", rmOraVid); \ No newline at end of file diff --git a/components/no-social-buttons.js b/components/no-social-buttons.js new file mode 100644 index 0000000..90cf970 --- /dev/null +++ b/components/no-social-buttons.js @@ -0,0 +1,18 @@ +/* #### Remove sharing buttons #### */ +function rmShareBtns() { + // get all of the buttons (actually the wrappers) + let shareBtnsWrap = document.getElementsByClassName("share"); + // ...and remove 'em + for (let i = 0; i < shareBtnsWrap.length; i++) { + shareBtnsWrap[i].parentNode.removeChild(shareBtnsWrap[i]); + } +} + +function rmStickyBtn() { + let stickyBtn = document.getElementById("jsid-sticky-button"); + stickyBtn.parentNode.removeChild(stickyBtn); +} + +// TODO: replace onscroll event with Mutation Observer +document.addEventListener("scroll", rmShareBtns); +document.addEventListener("DOMContentLoaded", rmStickyBtn); \ No newline at end of file diff --git a/components/nsfw-unlock.js b/components/nsfw-unlock.js new file mode 100644 index 0000000..b7b9a7c --- /dev/null +++ b/components/nsfw-unlock.js @@ -0,0 +1,39 @@ +/* #### Show NSFW posts when not logged in #### */ +// Helper function to support both image and video/gif posts +function hasVideo(postId, parentNode) { + const srcUrlBase = "https://img-9gag-fun.9cache.com/photo/" + postId; + // create a video node with the unlocked post as source + var video = document.createElement("video"); + video.src = srcUrlBase + "_460sv.mp4"; + video.loop = true; + video.controls = true; + video.volume = 0.5; + // inject it into the site asap + video.onloadedmetadata = function() { + parentNode.appendChild(video); + } + // on error == if no video exists, replace it with the still image + video.onerror = function() { + var image = document.createElement("img"); + image.src= srcUrlBase + "_460s.jpg"; + parentNode.appendChild(image); + } +} + +// Main function that detects and replaces NSFW posts +function unlockNsfwPosts() { + // get all NSFW post + let nsfwPosts = document.getElementsByClassName("nsfw-post"); + // for each of them: + for (let i = 0; i < nsfwPosts.length; i++) { + let parent = nsfwPosts[i].parentNode; + // get the 9gag id + let postId = parent.parentNode.parentNode.parentNode.getAttribute("id").split('-')[2]; + // inject unlocked post + hasVideo(postId, parent); + // finally remove the placeholder + parent.removeChild(nsfwPosts[i]); + } +} +// TODO: replace onscroll event with Mutation Observer +document.addEventListener("scroll", unlockNsfwPosts); diff --git a/components/video-controls.js b/components/video-controls.js new file mode 100644 index 0000000..e72e96c --- /dev/null +++ b/components/video-controls.js @@ -0,0 +1,14 @@ +/* #### Add controls to all videos/gifs #### */ +function addControls() { + let videos = document.getElementsByTagName("video"); + for (let i = 0; i < videos.length; i++) { + videos[i].controls = true; + videos[i].volume = 0.5; // TODO: default video volume customizable + if (videos[i].nextElementSibling.classList[0] == "sound-toggle") { + videos[i].parentNode.removeChild(videos[i].nextElementSibling); + } + // TODO: remove a.badge-track around videos (div.post-container > div > a.badge-track > video) + } +} +// TODO: replace onscroll event with Mutation Observer +document.addEventListener("scroll", addControls); diff --git a/content.js b/content.js index 7c0c367..9fd83dc 100644 --- a/content.js +++ b/content.js @@ -1,213 +1,7 @@ -/* #### Add controls to all videos/gifs #### */ -document.addEventListener("scroll", function() { - var videos = document.getElementsByTagName("video"); - for (var i = 0; i < videos.length; i++) { - videos[i].controls = true; - videos[i].volume = 0.5; - if (videos[i].nextElementSibling.classList[0] == "sound-toggle") { - videos[i].parentNode.removeChild(videos[i].nextElementSibling); - } - } -}); - -/* #### Dark/Night theme on desktop #### */ -const low_brightness = function() { - if (typeof(browser) == "undefined") { return chrome.runtime.getURL("icons/low-brightness-symbol.png"); } - else { return chrome.runtime.getURL("icons/low-brightness-symbol.png"); } -}(); -const high_brightness = function(){ - if (typeof(browser) == "undefined") { return chrome.runtime.getURL("icons/high-brightness-symbol.png"); } - else { return chrome.runtime.getURL("icons/high-brightness-symbol.png"); } -}(); - -document.addEventListener("DOMContentLoaded", function() { - // Code to inject into the site; triggers the body observer - const switch_function_trigger = 'function switch_theme_trigger() { let trigger = document.createElement("div"); trigger.id = "switch_theme_trigger"; let stylesheet = document.getElementById("dark-theme"); if (stylesheet) { trigger.setAttribute("data-switch-to", "reset"); } else { trigger.setAttribute("data-switch-to", "to_dark"); } document.body.append(trigger); }'; - // Inject code (end of body) - var switch_function_tag = document.createElement("script"); - switch_function_tag.id = "theme-switch-function"; - switch_function_tag.appendChild(document.createTextNode(switch_function_trigger)); - document.body.appendChild(switch_function_tag); - - // Create the switch button - var theme_switch_img = document.createElement("img"); - theme_switch_img.style = "height:30px; width:30px;"; - theme_switch_img.src = low_brightness; - var theme_switch_a = document.createElement("a"); - theme_switch_a.id = "theme-switch"; - theme_switch_a.style = "display:block; height:30px; width:30px; float:left;"; - theme_switch_a.setAttribute("onclick", "switch_theme_trigger()"); - theme_switch_a.href= "javascript:void(0)"; - theme_switch_a.append(theme_switch_img); - var theme_switch_wrap = document.createElement("div"); - theme_switch_wrap.classList.add("general-function"); - theme_switch_wrap.style = "text-align:center; line-height:30px; margin-right:10px;"; - theme_switch_wrap.appendChild(theme_switch_a); - // ... and add it to the site (in the header, next to the search) - var wrapper = document.getElementsByClassName("function-wrap")[0]; - wrapper.insertBefore(theme_switch_wrap, wrapper.childNodes[0]); -}); - -// Actual function to switch the theme; is run by the body observer -function switch_theme(target) { - var theme_switch = document.getElementById("theme-switch"); - // If the target theme is not dark, means back to normal - if (target != "to_dark") { - var stylesheet = document.getElementById("dark-theme"); - // remove the dark stylesheet (if present) and change the switch icon - if (stylesheet) { - stylesheet.parentNode.removeChild(stylesheet); - theme_switch.firstChild.src = low_brightness; - } - // finally save the state in extension storage - if (typeof(browser) == "undefined") { chrome.storage.local.set({gag_is_dark: false}); } - else { browser.storage.local.set({gag_is_dark: false}); } - } - // If the target theme is dark - else if (target == "to_dark") { - var stylesheet = document.getElementById("dark-theme"); - // create and add a link to the dark stylesheet (if not already present) and change the switch icon - if (!stylesheet) { - stylesheet = document.createElement("link"); - stylesheet.setAttribute("id", "dark-theme"); - if (typeof(browser) == "undefined") { stylesheet.setAttribute("href", chrome.runtime.getURL("darken_9gag.css")); } - else { stylesheet.setAttribute("href", browser.runtime.getURL("darken_9gag.css")); } - stylesheet.setAttribute("rel", "stylesheet"); - stylesheet.setAttribute("type", "text/css"); - document.getElementsByTagName("head")[0].appendChild(stylesheet); - theme_switch.firstChild.src = high_brightness; - } - // finally save the state in extension storage - if (typeof(browser) == "undefined") { chrome.storage.local.set({gag_is_dark: true}); } - else { browser.storage.local.set({gag_is_dark: true}); } - } -} - -document.addEventListener("DOMContentLoaded", function() { - // function to call, if body observer fires - var callback = function() { - // function to be run by the below promise on success - function onGot(item) { - if (item == undefined) { - if (typeof(browser) == "undefined") { chrome.storage.local.set({gag_is_dark: false}); } - else { browser.storage.local.set({gag_is_dark: false}); } - } - // check if the trigger is present - var trigger = document.getElementById("switch_theme_trigger"); - // if the trigger is present (user requested theme change) - if (trigger != null) { - // if storage is dark and request is smth else (reset) => switch theme to normal - if (item.gag_is_dark === true && trigger.getAttribute("data-switch-to") != "to_dark") { - switch_theme("reset"); - } - // if storage is normal and request change to dark => switch theme to dark - else if (item.gag_is_dark === false && trigger.getAttribute("data-switch-to") == "to_dark") { - switch_theme("to_dark"); - } - //console.debug(trigger); - //finally remove the trigger - trigger.parentNode.removeChild(trigger); - } - //if the trigger is not present (first load or user navigated) - else { - // switch theme according to the storage item - if (item.gag_is_dark === true) { - switch_theme("to_dark"); - } else { - switch_theme("reset"); - } - } - //console.debug(item); - } - // function to be run by the below promise on an error - function onError(error) { - console.debug(`Error: ${error}`); - } - if (typeof(browser) == "undefined" && typeof(chrome) != "undefined") { // Chrome: uses "chrome" namespace, doesn't support promises - chrome.storage.local.get(null, onGot); - } - else if (typeof(chrome) == "undefined" && typeof(browser) != "undefined") { // Edge: uses "browser" namespace, doesn't support promises - browser.storage.local.get(null, onGot); - } - else { // Firefox: supports both namespaces and promises - // read the extension storage (it's a promise) - let get_storage = browser.storage.local.get(); - // if successful, run onGot(); if not run onError() - get_storage.then(onGot, onError); - } - }; - // elements to be observed - var config = { childList: true, subtree: true }; - // create and attach the observer - var body_observer = new MutationObserver(callback); - body_observer.observe(document.body, config); -}); - -/* #### Show NSFW posts when not logged in #### */ -document.addEventListener("scroll", function() { - // get all NSFW post - var nsfw_posts = document.getElementsByClassName("nsfw-post"); - // for each of them: - for (i=0; ia { border-radius: 2px; } - .popup-menu.postpage-share { top: 43px; left: 164px; }` - style_tag.append(style); - head.append(style_tag); -}); -/* ### Remove annoying paid video post from ora.tv ### */ -document.addEventListener("scroll", function() { - // find it - var ora = document.querySelector('iframe[src^="https://www.ora.tv"]'); - // and delete it - if (ora) { - ora = ora.parentNode.parentNode; - ora.parentNode.removeChild(ora); - } -}); // initial scroll to trigger the event once -window.onload = function () { window.scrollBy(0,1); } +window.onload = function() { window.scrollBy(0,1); } + +// TODO: Add a Changelog/First use popup +// TODO: create a settings page +// TODO: create a page action (for launching the settings page) +// TODO: remove or hide Youtube embededs diff --git a/manifest.json b/manifest.json index 78b665f..a2274e7 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 2, "name": "Better 9gag", - "version": "1.5.1", + "version": "1.6.1", "author": "Fabian Große", "homepage_url": "https://github.com/Saphareas/Better-9gag", @@ -18,15 +18,32 @@ "content_scripts": [ { "matches": ["*://*.9gag.com/*"], - "js": ["content.js"], + "js": [ + "components/night-mode.js", + "components/video-controls.js", + "components/nsfw-unlock.js", + "components/no-social-buttons.js", + "components/no-ora-tv.js", + "content.js" + ], + "css": ["stylesheets/ui-tweaks.css"], "run_at": "document_start" } ], + "background": { + "scripts": ["background.js"], + "persistent": false + }, + "web_accessible_resources": [ - "darken_9gag.css", + "stylesheets/darken-9gag.css", "icons/low-brightness-symbol.png", - "icons/high-brightness-symbol.png"], + "icons/high-brightness-symbol.png" + ], - "permissions": ["storage"] + "permissions": [ + "storage", + "notifications" + ] } diff --git a/darken_9gag.css b/stylesheets/darken-9gag.css similarity index 95% rename from darken_9gag.css rename to stylesheets/darken-9gag.css index 70ba186..2da18b9 100644 --- a/darken_9gag.css +++ b/stylesheets/darken-9gag.css @@ -1,22 +1,23 @@ -/* Dark background for whole site (same grey as YouTubes dark theme) */ -.background-white, -.post-afterbar-a.in-post-top, -.CS3 { background: rgb(35,35,35); } -/* White font for everything */ -.section-sidebar .nav .label, -.featured-tag a, -section#list-view-2 header a h1, -section.block-feature-cover a, -#individual-post h1, -.profile-header h2, -.profile .tab-bar ul.menu a.selected, -.CS3 .comment-entry .payload .username, -.CS3 .collapsed-comment, -.CS3 { color: white; } -.profile .tab-bar ul.menu a.selected { border-bottom: 2px solid white; } -/* Make icons of 9gag sections white */ -.section-sidebar .nav i.icon { filter: invert(1); } -/* ...except the favorites star */ -i.icon.star { filter: none !important; } -/* Black bg when hovering a 9gag section */ -.section-sidebar .nav li:hover .label { background: black; } +/* Dark background for whole site (same grey as YouTubes dark theme) */ +.background-white, +.post-afterbar-a.in-post-top, +.CS3 { background: rgb(35,35,35); } +/* White font for everything */ +.section-sidebar .nav .label, +.featured-tag a, +section#list-view-2 header a h1, +section.block-feature-cover a, +#individual-post h1, +.profile-header h2, +.profile .tab-bar ul.menu a.selected, +.CS3 .comment-entry .payload .username, +.CS3 .collapsed-comment, +.CS3 { color: white; } +.profile .tab-bar ul.menu a.selected { border-bottom: 2px solid white; } +/* Make icons of 9gag sections white */ +.section-sidebar .nav i.icon { filter: invert(1); } +/* ...except the favorites star */ +i.icon.star { filter: none !important; } +/* Black bg when hovering a 9gag section */ +.section-sidebar .nav li:hover .label { background: black; } +/* TODO: rename to darken-9gag.css diff --git a/stylesheets/ui-tweaks.css b/stylesheets/ui-tweaks.css new file mode 100644 index 0000000..ba7fe60 --- /dev/null +++ b/stylesheets/ui-tweaks.css @@ -0,0 +1,14 @@ +#theme-switch-wrapper { + margin-right: 10px; +} +#theme-switch > img { + height: 30px; + width: 30px; +} +#jsid-header-user-menu-avatar > a { + border-radius: 2px; +} +.popup-menu.postpage-share { + top: 43px; left: 164px; +} +/* TODO: rename to ui-tweaks.css