diff --git a/SB.js b/SB.js index 98388ab3..9fa8f29e 100644 --- a/SB.js +++ b/SB.js @@ -159,7 +159,8 @@ SB.defaults = { "hideInfoButtonPlayerControls": false, "hideDeleteButtonPlayerControls": false, "hideDiscordLaunches": 0, - "hideDiscordLink": false + "hideDiscordLink": false, + "invidiousInstances": ["invidio.us", "invidiou.sh"] } // Reset config diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 7ebbc3ab..3dca55b5 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -344,5 +344,35 @@ }, "keybindCurrentlySet": { "message": ". It is currently set to:" + }, + "supportInvidious": { + "message": "Support Invidious" + }, + "supportInvidiousDescription": { + "message": "Invidious (invidio.us) is a third party YouTube client. To enable support, you must accept the extra permissions. This does NOT work in incongnito on chrome and other chromium variants." + }, + "optionsInfo": { + "message": "Enable Invidious support, disable autoskip, hide buttons and more." + }, + "addInvidiousInstance": { + "message": "Add Invidious Instance" + }, + "addInvidiousInstanceDescription": { + "message": "Add a custom instance of Invidious. This must be formatted with JUST the domain. Example: invidious.ajay.app" + }, + "add": { + "message": "Add" + }, + "addInvidiousInstanceError": { + "message": "This is an invalid domain. This should JUST include the domain part. Example: invidious.ajay.app" + }, + "resetInvidiousInstance": { + "message": "Reset Invidious Instance List" + }, + "resetInvidiousInstanceAlert": { + "message": "You are about to reset the Invidious instance list" + }, + "currentInstances": { + "message": "Current Instances:" } } diff --git a/background.js b/background.js index 0c9a577e..f6d1d533 100644 --- a/background.js +++ b/background.js @@ -1,3 +1,15 @@ +isBackgroundScript = true; + +// Used only on Firefox, which does not support non persistent background pages. +var contentScriptRegistrations = {}; + +// Register content script if needed +if (isFirefox()) { + wait(() => SB.config !== undefined).then(function() { + if (SB.config.supportInvidious) setupExtraSiteContentScripts(); + }); +} + chrome.tabs.onUpdated.addListener(function(tabId) { chrome.tabs.sendMessage(tabId, { message: 'update', @@ -8,38 +20,46 @@ chrome.runtime.onMessage.addListener(async function (request, sender, callback) await wait(() => SB.config !== undefined); switch(request.message) { - case "submitTimes": - submitTimes(request.videoID, callback); - - //this allows the callback to be called later by the submitTimes function - return true; - case "addSponsorTime": - addSponsorTime(request.time, request.videoID, callback); - - //this allows the callback to be called later - return true; - - case "getSponsorTimes": - getSponsorTimes(request.videoID, function(sponsorTimes) { - callback({ - sponsorTimes: sponsorTimes - }) - }); - - //this allows the callback to be called later - return true; - case "submitVote": - submitVote(request.type, request.UUID, callback); - - //this allows the callback to be called later - return true; - case "alertPrevious": - chrome.notifications.create("stillThere" + Math.random(), { + case "submitTimes": + submitTimes(request.videoID, callback); + + //this allows the callback to be called later by the submitTimes function + return true; + case "addSponsorTime": + addSponsorTime(request.time, request.videoID, callback); + + //this allows the callback to be called later + return true; + + case "getSponsorTimes": + getSponsorTimes(request.videoID, function(sponsorTimes) { + callback({ + sponsorTimes: sponsorTimes + }) + }); + + //this allows the callback to be called later + return true; + case "submitVote": + submitVote(request.type, request.UUID, callback); + + //this allows the callback to be called later + return true; + case "alertPrevious": + chrome.notifications.create("stillThere" + Math.random(), { type: "basic", title: chrome.i18n.getMessage("wantToSubmit") + " " + request.previousVideoID + "?", message: chrome.i18n.getMessage("leftTimes"), iconUrl: "./icons/LogoSponsorBlocker256px.png" - }); + }); + case "registerContentScript": + registerFirefoxContentScript(request); + return false; + case "unregisterContentScript": + contentScriptRegistrations[request.id].unregister(); + delete contentScriptRegistrations[request.id]; + + return false; } }); @@ -61,6 +81,24 @@ chrome.runtime.onInstalled.addListener(function (object) { }, 1500); }); +/** + * Only works on Firefox. + * Firefox requires that it be applied after every extension restart. + * + * @param {JSON} options + */ +function registerFirefoxContentScript(options) { + let oldRegistration = contentScriptRegistrations[options.id]; + if (oldRegistration) oldRegistration.unregister(); + + browser.contentScripts.register({ + allFrames: options.allFrames, + js: options.js, + css: options.css, + matches: options.matches + }).then(() => void (contentScriptRegistrations[options.id] = registration)); +} + //gets the sponsor times from memory function getSponsorTimes(videoID, callback) { let sponsorTimes = []; diff --git a/content.js b/content.js index ab33f58a..b165dafd 100644 --- a/content.js +++ b/content.js @@ -69,78 +69,78 @@ var popupInitialised = false; chrome.runtime.onMessage.addListener(messageListener); function messageListener(request, sender, sendResponse) { - //messages from popup script - switch(request.message){ - case "update": - videoIDChange(getYouTubeVideoID(document.URL)); - break; - case "sponsorStart": - sponsorMessageStarted(sendResponse); + //messages from popup script + switch(request.message){ + case "update": + videoIDChange(getYouTubeVideoID(document.URL)); + break; + case "sponsorStart": + sponsorMessageStarted(sendResponse); - break; - case "sponsorDataChanged": - updateSponsorTimesSubmitting(); + break; + case "sponsorDataChanged": + updateSponsorTimesSubmitting(); - break; - case "isInfoFound": - //send the sponsor times along with if it's found - sendResponse({ - found: sponsorDataFound, - sponsorTimes: sponsorTimes, - hiddenSponsorTimes: hiddenSponsorTimes, - UUIDs: UUIDs - }); + break; + case "isInfoFound": + //send the sponsor times along with if it's found + sendResponse({ + found: sponsorDataFound, + sponsorTimes: sponsorTimes, + hiddenSponsorTimes: hiddenSponsorTimes, + UUIDs: UUIDs + }); - if (popupInitialised && document.getElementById("sponsorBlockPopupContainer") != null) { - //the popup should be closed now that another is opening - closeInfoMenu(); - } + if (popupInitialised && document.getElementById("sponsorBlockPopupContainer") != null) { + //the popup should be closed now that another is opening + closeInfoMenu(); + } - popupInitialised = true; - break; - case "getVideoID": - sendResponse({ - videoID: sponsorVideoID - }); + popupInitialised = true; + break; + case "getVideoID": + sendResponse({ + videoID: sponsorVideoID + }); - break; - case "getVideoDuration": - sendResponse({ - duration: v.duration - }); + break; + case "getVideoDuration": + sendResponse({ + duration: v.duration + }); - break; - case "skipToTime": - v.currentTime = request.time; - return - case "getCurrentTime": - sendResponse({ - currentTime: v.currentTime - }); + break; + case "skipToTime": + v.currentTime = request.time; + return + case "getCurrentTime": + sendResponse({ + currentTime: v.currentTime + }); - break; - case "getChannelURL": - sendResponse({ - channelURL: channelURL - }); + break; + case "getChannelURL": + sendResponse({ + channelURL: channelURL + }); - break; - case "isChannelWhitelisted": - sendResponse({ - value: channelWhitelisted - }); + break; + case "isChannelWhitelisted": + sendResponse({ + value: channelWhitelisted + }); - break; - case "whitelistChange": - channelWhitelisted = request.value; - sponsorsLookup(sponsorVideoID); + break; + case "whitelistChange": + channelWhitelisted = request.value; + sponsorsLookup(sponsorVideoID); - break; - case "changeStartSponsorButton": - changeStartSponsorButton(request.showStartSponsor, request.uploadButtonVisible); + break; + case "changeStartSponsorButton": + changeStartSponsorButton(request.showStartSponsor, request.uploadButtonVisible); - break; - } + break; + } } /** @@ -148,7 +148,7 @@ function messageListener(request, sender, sendResponse) { * * @param {String} changes */ -function configUpdateListener(changes) { +function contentConfigUpdateListener(changes) { for (const key in changes) { switch(key) { case "hideVideoPlayerControls": @@ -160,8 +160,8 @@ function configUpdateListener(changes) { } } -if (!SB.configListeners.includes(configUpdateListener)) { - SB.configListeners.push(configUpdateListener); +if (!SB.configListeners.includes(contentConfigUpdateListener)) { + SB.configListeners.push(contentConfigUpdateListener); } //check for hotkey pressed @@ -224,8 +224,22 @@ function videoIDChange(id) { if (previewBar == null) { //create it wait(getControls).then(result => { - let progressBar = document.getElementsByClassName("ytp-progress-bar-container")[0] || document.getElementsByClassName("no-model cue-range-markers")[0]; - previewBar = new PreviewBar(progressBar); + const progressElementSelectors = [ + // For YouTube + "ytp-progress-bar-container", + "no-model cue-range-markers", + // For Invidious/VideoJS + "vjs-progress-holder" + ]; + + for (const selector of progressElementSelectors) { + const el = document.getElementsByClassName(selector); + + if (el && el.length && el[0]) { + previewBar = new PreviewBar(el[0]); + break; + } + } }); } @@ -284,8 +298,10 @@ function videoIDChange(id) { } }); }); - updateVisibilityOfPlayerControlsButton(); - updateVisibilityOfPlayerControlsButton(false); + //see if video controls buttons should be added + if (!onInvidious) { + updateVisibilityOfPlayerControlsButton(); + } } function sponsorsLookup(id, channelIDPromise) { @@ -407,6 +423,9 @@ function getChannelID() { channelURLContainer = document.querySelector("#channel-name > #container > #text-container > #text"); if (channelURLContainer !== null) { channelURLContainer = channelURLContainer.firstElementChild; + } else if (onInvidious) { + // Unfortunately, the Invidious HTML doesn't have much in the way of element identifiers... + channelURLContainer = document.querySelector("body > div > div.pure-u-1.pure-u-md-20-24 div.pure-u-1.pure-u-lg-3-5 > div > a"); } else { //old YouTube theme let channelContainers = document.getElementsByClassName("yt-user-info"); @@ -425,6 +444,9 @@ function getChannelID() { let currentTitle = ""; if (titleInfoContainer != null) { currentTitle = titleInfoContainer.firstElementChild.firstElementChild.querySelector(".title").firstElementChild.innerText; + } else if (onInvidious) { + // Unfortunately, the Invidious HTML doesn't have much in the way of element identifiers... + currentTitle = document.querySelector("body > div > div.pure-u-1.pure-u-md-20-24 div.pure-u-1.pure-u-lg-3-5 > div > a > div > span").textContent; } else { //old YouTube theme currentTitle = document.getElementById("eow-title").innerText; @@ -595,7 +617,14 @@ function createButton(baseID, title, callback, imageName, isDraggable=false) { function getControls() { let controls = document.getElementsByClassName("ytp-right-controls"); - return (!controls || controls.length === 0) ? false : controls[controls.length - 1] + + if (!controls || controls.length === 0) { + // The invidious video element's controls element + controls = document.getElementsByClassName("vjs-control-bar"); + return (!controls || controls.length === 0) ? false : controls[controls.length - 1]; + } else { + return controls[controls.length - 1]; + } }; //adds all the player controls buttons @@ -618,7 +647,7 @@ async function updateVisibilityOfPlayerControlsButton() { await createButtons(); - if (SB.config.hideVideoPlayerControls) { + if (SB.config.hideVideoPlayerControls || onInvidious) { document.getElementById("startSponsorButton").style.display = "none"; document.getElementById("submitButton").style.display = "none"; } else { @@ -626,13 +655,13 @@ async function updateVisibilityOfPlayerControlsButton() { } //don't show the info button on embeds - if (SB.config.hideInfoButtonPlayerControls || document.URL.includes("/embed/")) { + if (SB.config.hideInfoButtonPlayerControls || document.URL.includes("/embed/") || onInvidious) { document.getElementById("infoButton").style.display = "none"; } else { document.getElementById("infoButton").style.removeProperty("display"); } - if (SB.config.hideDeleteButtonPlayerControls) { + if (SB.config.hideDeleteButtonPlayerControls || onInvidious) { document.getElementById("deleteButton").style.display = "none"; } } diff --git a/manifest.json b/manifest.json index f87d2d31..57548cc5 100644 --- a/manifest.json +++ b/manifest.json @@ -14,7 +14,7 @@ "all_frames": true, "js": [ "config.js", - "SB.js", + "SB.js", "utils/previewBar.js", "utils/skipNotice.js", "utils.js", @@ -48,15 +48,19 @@ "notifications", "https://sponsor.ajay.app/*" ], + "optional_permissions": [ + "*://*/*", + "declarativeContent" + ], "browser_action": { "default_title": "__MSG_Name__", "default_popup": "popup.html" }, "background": { "scripts":[ + "config.js", "SB.js", "utils.js", - "config.js", "background.js" ], "persistent": false @@ -68,6 +72,9 @@ "128": "icons/LogoSponsorBlocker128px.png", "256": "icons/LogoSponsorBlocker256px.png" }, - "options_page": "options/options.html", + "options_ui": { + "page": "options/options.html", + "open_in_tab": true + }, "manifest_version": 2 } diff --git a/options/options.css b/options/options.css index e67d956c..e746484a 100644 --- a/options/options.css +++ b/options/options.css @@ -7,12 +7,16 @@ body { text-align: center; } +.inline { + display: inline-block; +} + .bold { font-weight: bold; } .hidden { - display: none; + display: none !important; } .keybind-status { @@ -42,7 +46,7 @@ body { border-radius: 5px; font-size: 14px; - width: fit-content; + width: max-content; } .option-button:hover { diff --git a/options/options.html b/options/options.html index 2a763436..b76c3e0a 100644 --- a/options/options.html +++ b/options/options.html @@ -21,7 +21,59 @@

__MSG_Options__