From 527a82694964da6f1ef74be0ed46610a0c75eb0e Mon Sep 17 00:00:00 2001 From: Marvin Alexander Krebber Date: Tue, 23 Aug 2022 23:56:45 +0200 Subject: [PATCH] Added Freevee ad skipping and better mutation observing in v.1.0.6 --- .vscode/settings.json | 3 +- .../NetflixAmazon Auto-Skip simplified.svg | 12 ++ README.md | 3 +- chrome/manifest.json | 4 +- chrome/popup/settings.html | 11 +- chrome/popup/settings.js | 10 +- chrome/skipper.js | 151 +++++++++++------- downloaded HtmlButtons/AmazonIntro.html | 8 + downloaded HtmlButtons/freevee.html | 47 ++++++ firefox/manifest.json | 4 +- firefox/popup/settings.html | 11 +- firefox/popup/settings.js | 10 +- firefox/skipper.js | 151 +++++++++++------- .../netflix_prime_auto-skip-1.0.5.zip | Bin 8794 -> 0 bytes .../netflix_prime_auto-skip-1.0.6.zip | Bin 0 -> 9219 bytes 15 files changed, 295 insertions(+), 130 deletions(-) create mode 100644 Publish/NetflixAmazon Auto-Skip simplified.svg create mode 100644 downloaded HtmlButtons/AmazonIntro.html create mode 100644 downloaded HtmlButtons/freevee.html delete mode 100644 firefox/web-ext-artifacts/netflix_prime_auto-skip-1.0.5.zip create mode 100644 firefox/web-ext-artifacts/netflix_prime_auto-skip-1.0.6.zip diff --git a/.vscode/settings.json b/.vscode/settings.json index 14b935bc..56e782e6 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,4 @@ { - "editor.experimental.stickyScroll.enabled": true + "editor.experimental.stickyScroll.enabled": true, + "cSpell.words": ["Freevee"] } diff --git a/Publish/NetflixAmazon Auto-Skip simplified.svg b/Publish/NetflixAmazon Auto-Skip simplified.svg new file mode 100644 index 00000000..6064945d --- /dev/null +++ b/Publish/NetflixAmazon Auto-Skip simplified.svg @@ -0,0 +1,12 @@ + + + + + + N + + P + \ No newline at end of file diff --git a/README.md b/README.md index eb57e0c2..7f1b429b 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ This extension includes a content script, "skipper.js", that is injected into all pages, but will only run if under "amazon/*/video","netflix". -It automatically skips intros, Credits, recaps, and anything else you don't want to watch on Netflix and Amazon Prime video. +It automatically skips Ads, intros, Credits, recaps, and anything else you don't want to watch on Netflix and Amazon Prime video. You can configure what to watch and what to skip in the settings Page: @@ -28,6 +28,7 @@ Amazon Prime video Automatically skipping: * Intros * Credits: automatically goes to the next episode * Self promoting ads +* Freevee Ads: Watch series for free without ads Disclaimer diff --git a/chrome/manifest.json b/chrome/manifest.json index 2411b639..aec9c053 100644 --- a/chrome/manifest.json +++ b/chrome/manifest.json @@ -1,9 +1,9 @@ { "manifest_version": 3, "name": "Netflix/Prime Auto-Skip", - "version": "1.0.5", + "version": "1.0.6", - "description": "Automatically skip intros, Credits, Ads on Amazon Prime video and intros, recaps, credits and inactivity warnings on Netflix.", + "description": "Automatically skip Ads, intros, Credits on Amazon Prime video and intros, recaps, credits and inactivity on Netflix.", "homepage_url": "https://github.com/Dreamlinerm/Netflix-Prime-Auto-Skip", "icons": { "16": "icons/NetflixAmazon Auto-Skip--16.png", diff --git a/chrome/popup/settings.html b/chrome/popup/settings.html index b40b21ef..62504415 100644 --- a/chrome/popup/settings.html +++ b/chrome/popup/settings.html @@ -16,7 +16,7 @@

Auto-skip

-

Version: 1.0.4

+

Version: 1.0.6

@@ -42,12 +42,19 @@

Prime Video Auto-skips:

-

Skip Ads:

+

Skip Self Ads:

+
+

Block Freevee Ads:

+ +

Netflix Auto-skips:

diff --git a/chrome/popup/settings.js b/chrome/popup/settings.js index 294154be..88244eaa 100644 --- a/chrome/popup/settings.js +++ b/chrome/popup/settings.js @@ -1,11 +1,11 @@ // global variables in localStorage -let settings; const defaultSettings = { settings: { - Amazon: { skipIntro: true, skipCredits: true, skipAd: true }, + Amazon: { skipIntro: true, skipCredits: true, skipAd: true, blockFreevee: true }, Netflix: { skipIntro: true, skipRecap: true, skipCredits: true, skipBlocked: true }, }, }; +let settings = defaultSettings.settings; chrome.storage.sync.get("settings", function (result) { settings = result.settings; if (typeof settings !== "object") { @@ -31,6 +31,8 @@ function setCheckboxesToSettings() { if (button) button.checked = settings?.Amazon.skipCredits; button = document.querySelector("#AmazonAds"); if (button) button.checked = settings?.Amazon.skipAd; + button = document.querySelector("#AmazonFreevee"); + if (button) button.checked = settings?.Amazon.blockFreevee; button = document.querySelector("#NetflixIntro"); if (button) button.checked = settings?.Netflix.skipIntro; button = document.querySelector("#NetflixRecap"); @@ -61,6 +63,10 @@ function listenForClicks() { settings.Amazon.skipAd = !settings.Amazon.skipAd; console.log("settings.AmazonAd", settings); chrome.storage.sync.set({ settings: settings }, function () {}); + } else if (e.target.id === "AmazonFreevee") { + settings.Amazon.blockFreevee = !settings.Amazon.blockFreevee; + console.log("settings.blockFreevee", settings); + chrome.storage.sync.set({ settings: settings }, function () {}); } else if (e.target.id === "NetflixIntro") { settings.Netflix.skipIntro = !settings.Netflix.skipIntro; console.log("settings.NetflixIntro", settings); diff --git a/chrome/skipper.js b/chrome/skipper.js index 845c6bac..aa52ccf2 100644 --- a/chrome/skipper.js +++ b/chrome/skipper.js @@ -5,17 +5,17 @@ let url = window.location.href; let isAmazon = /amazon|primevideo/i.test(hostname); let isVideo = /video/i.test(title) || /video/i.test(url); let isNetflix = /netflix/i.test(hostname); -const version = "1.0.4"; +const version = "1.0.6"; if (isVideo || isNetflix) { // global variables in localStorage - let settings; const defaultSettings = { settings: { - Amazon: { skipIntro: true, skipCredits: true, skipAd: true }, + Amazon: { skipIntro: true, skipCredits: true, skipAd: true, blockFreevee: true }, Netflix: { skipIntro: true, skipRecap: true, skipCredits: true, skipBlocked: true }, }, }; + let settings = defaultSettings.settings; chrome.storage.sync.get("settings", function (result) { settings = result.settings; console.log("%cNetflix%c/%cPrime%c Auto-Skip", "color: #e60010;font-size: 2em;", "color: white;font-size: 2em;", "color: #00aeef;font-size: 2em;", "color: white;font-size: 2em;"); @@ -27,28 +27,34 @@ if (isVideo || isNetflix) { } else { if (isNetflix) { // start Observers depending on the settings - if (settings.Netflix.skipIntro) { + if (settings.Netflix.skipIntro === undefined || settings.Netflix.skipIntro) { startNetflixSkipIntroObserver(); } - if (settings.Netflix.skipRecap) { + if (settings.Netflix.skipRecap === undefined || settings.Netflix.skipRecap) { startNetflixSkipRecapObserver(); } - if (result.settings.Netflix.skipCredits) { + if (result.settings.Netflix.skipCredits === undefined || result.settings.Netflix.skipCredits) { startNetflixSkipCreditsObserver(); } - if (settings.Netflix.skipBlocked) { + if (settings.Netflix.skipBlocked === undefined || settings.Netflix.skipBlocked) { startNetflixSkipBlockedObserver(); } } else { - if (settings.Amazon.skipIntro) { + if (settings.Amazon.skipIntro === undefined || settings.Amazon.skipIntro) { startAmazonSkipIntroObserver(); } - if (settings.Amazon.skipCredits) { + if (settings.Amazon.skipCredits === undefined || settings.Amazon.skipCredits) { startAmazonSkipCreditsObserver(); } - if (settings.Amazon.skipAd) { + if (settings.Amazon.skipAd === undefined || settings.Amazon.skipAd) { startAmazonSkipAdObserver(); } + if (settings.Amazon.blockFreevee === undefined || settings.Amazon.blockFreevee) { + // timeout of 100 ms because the ad is not loaded fast enough and the video will crash + setTimeout(function () { + startAmazonBlockFreeveeObserver(); + }, 200); + } } } }); @@ -82,15 +88,19 @@ if (isVideo || isNetflix) { if (oldValue === undefined || newValue.Amazon.skipAd !== oldValue.Amazon.skipAd) { startAmazonSkipAdObserver(); } + if (oldValue === undefined || newValue.Amazon.blockFreevee !== oldValue.Amazon.blockFreevee) { + startAmazonBlockFreeveeObserver(); + } } } } }); // Observers - // Options for the observer (which mutations to observe) + // default Options for the observer (which mutations to observe) const config = { attributes: true, childList: true, subtree: true }; // Netflix Observers + const NetflixConfig = { attributes: true, attributeFilter: ["data-uia"], subtree: true, childList: true, attributeOldValue: false }; const NetflixSkipIntroObserver = new MutationObserver(Netflix_intro); function Netflix_intro(mutations, observer) { for (let mutation of mutations) { @@ -140,17 +150,20 @@ if (isVideo || isNetflix) { } // Amazon Observers + + const AmazonSkipIntroConfig = { attributes: true, attributeFilter: [".skipelement"], subtree: true, childList: true, attributeOldValue: false }; const AmazonSkipIntro = new RegExp("skipelement", "i"); const AmazonSkipIntroObserver = new MutationObserver(Amazon_Intro); function Amazon_Intro(mutations, observer) { for (let mutation of mutations) { - if (AmazonSkipIntro.test(mutation.target.classList)) { - mutation.target.click(); - console.log("Intro skipped", mutation.target); + if (AmazonSkipIntro.test(mutation.target.firstChild.classList)) { + mutation.target.firstChild.click(); + console.log("Intro skipped", mutation.target.firstChild); } } } + const AmazonSkipCreditsConfig = { attributes: true, attributeFilter: [".nextupcard"], subtree: true, childList: true, attributeOldValue: false }; const AmazonSkipCredits = new RegExp("nextupcard", "i"); const AmazonSkipCredits2 = new RegExp("nextupcard-button", "i"); const AmazonSkipCreditsObserver = new MutationObserver(Amazon_Credits); @@ -167,43 +180,48 @@ if (isVideo || isNetflix) { } } - // const SkipAdTranslation = { - // en: "Skip", - // de: "Überspringen", - // }; - // const AmazonSkipAdObserver = new MutationObserver(Amazon_Ad); - // async function Amazon_Ad(mutations, observer) { - // // the button classes are class="fu4rd6c f1cw2swo" but im not sure they are changed may need to refresh - // let button = document.querySelector(".fu4rd6c.f1cw2swo"); - // if (button) { - // button.click(); - // console.log("Ad skipped", button); - // } - // // alternative - // // let buttons = document.querySelector("[class*=webplayersdk-infobar-container]").getElementsByTagName("div"); - // // for (let i = 0; i < buttons.length; i++) { - // // if (buttons[i]?.firstChild?.textContent == SkipAdTranslation[language]) { - // // console.log("Ad skipped", buttons[i]); - // // buttons[i].click(); - // // setTimeout(function () { - // // console.log("Hello World"); - // // }, 2000); - // // } - // // } - // } + const FreeVeeConfig = { attributes: true, attributeFilter: [".atvwebplayersdk-adtimeindicator-text"], subtree: true, childList: true, attributeOldValue: false }; + const AmazonFreeVeeObserver = new MutationObserver(AmazonFreeVee); + function AmazonFreeVee(mutations, observer) { + // if (document.querySelector("[class*=infobar-container]").classList.contains("show")) { + let video = document.querySelector("#dv-web-player > div > div:nth-child(1) > div > div > div.scalingVideoContainer > div.scalingVideoContainerBottom > div > video"); + let adTimeText = document.querySelector(".atvwebplayersdk-adtimeindicator-text"); + // !document.querySelector(".fu4rd6c.f1cw2swo") + if (adTimeText.textContent.length > 7 && video != null && adTimeText != null) { + video.currentTime += parseInt(adTimeText.textContent.match(/\d+/)[0]); + console.log("FreeVee Ad skipped", adTimeText, video); + } + // } + } + const AmazonSkipAdObserver = new MutationObserver(Amazon_Ad); + async function Amazon_Ad(mutations, observer) { + for (let mutation of mutations) { + if (mutation.target.classList.contains("atvwebplayersdk-infobar-container")) { + if (mutation.target.classList.contains("show")) { + let button = mutation.target.querySelector(".fu4rd6c.f1cw2swo"); + if (button) { + button.click(); + console.log("Self Ad skipped", button); + } + } + } + } + } + + // a little to intense to do this every time but it works, not currently used async function Amazon_AdTimeout() { // set loop every 0.5 sec and check if ad is there setInterval(function () { // if infobar is shown - let infobar = document.querySelector("[class*=infobar-container]").classList.contains("show"); - if (infobar) { + if (document.querySelector("[class*=infobar-container]").classList.contains("show")) { // the button classes are class="fu4rd6c f1cw2swo" but im not sure they are changed may need to refresh - // adtimeindicator-text might be an alternative + // adtimeindicator-text might be an alternative like here + // document.querySelector(".atvwebplayersdk-adtimeindicator-text").parentNode.parentNode.querySelector("div:nth-child(3) > div:nth-child(2)") let button = document.querySelector(".fu4rd6c.f1cw2swo"); if (button) { button.click(); - console.log("Ad skipped", button); + console.log("Self Ad skipped", button); } } if (!settings.Amazon.skipAd) { @@ -213,95 +231,112 @@ if (isVideo || isNetflix) { } // start/stop the observers depending on settings async function startNetflixSkipIntroObserver() { - if (settings.Netflix.skipIntro) { + if (settings.Netflix.skipIntro === undefined || settings.Netflix.skipIntro) { console.log("started observing| intro"); let button = document.querySelector('[data-uia="player-skip-intro"]'); if (button) { button.click(); console.log("intro skipped", button); } - NetflixSkipIntroObserver.observe(document, config); + NetflixSkipIntroObserver.observe(document, NetflixConfig); } else { console.log("stopped observing | intro"); NetflixSkipIntroObserver.disconnect(); } } async function startNetflixSkipRecapObserver() { - if (settings.Netflix.skipRecap) { + if (settings.Netflix.skipRecap === undefined || settings.Netflix.skipRecap) { console.log("started observing| Recap"); let button = document.querySelector('[data-uia="player-skip-recap"]') || document.querySelector('[data-uia="player-skip-preplay"]'); if (button) { button.click(); console.log("Recap skipped", button); } - NetflixSkipRecapObserver.observe(document, config); + NetflixSkipRecapObserver.observe(document, NetflixConfig); } else { console.log("stopped observing| Recap"); NetflixSkipRecapObserver.disconnect(); } } async function startNetflixSkipCreditsObserver() { - if (settings.Netflix.skipCredits) { + if (settings.Netflix.skipCredits === undefined || settings.Netflix.skipCredits) { console.log("started observing| Credits"); let button = document.querySelector('[data-uia="next-episode-seamless-button"]'); if (button) { button.click(); console.log("Credits skipped", button); } - NetflixSkipCreditsObserver.observe(document, config); + NetflixSkipCreditsObserver.observe(document, NetflixConfig); } else { console.log("stopped observing| Credits"); NetflixSkipCreditsObserver.disconnect(); } } async function startNetflixSkipBlockedObserver() { - if (settings.Netflix.skipBlocked) { + if (settings.Netflix.skipBlocked === undefined || settings.Netflix.skipBlocked) { console.log("started observing| Blocked"); let button = document.querySelector('[data-uia="interrupt-autoplay-continue"]'); if (button) { button.click(); console.log("Blocked skipped", button); } - NetflixSkipBlockedObserver.observe(document, config); + NetflixSkipBlockedObserver.observe(document, NetflixConfig); } else { console.log("stopped observing| Blocked"); NetflixSkipBlockedObserver.disconnect(); } } async function startAmazonSkipIntroObserver() { - if (settings.Amazon.skipIntro) { + if (settings.Amazon.skipIntro === undefined || settings.Amazon.skipIntro) { console.log("started observing| Intro"); let button = document.querySelector("[class*=skipelement]"); if (button) { button.click(); console.log("Intro skipped", button); } - AmazonSkipIntroObserver.observe(document, config); + AmazonSkipIntroObserver.observe(document, AmazonSkipIntroConfig); } else { console.log("stopped observing| Intro"); AmazonSkipIntroObserver.disconnect(); } } async function startAmazonSkipCreditsObserver() { - if (settings.Amazon.skipCredits) { + if (settings.Amazon.skipCredits === undefined || settings.Amazon.skipCredits) { console.log("started observing| Credits"); let button = document.querySelector("[class*=nextupcard-button]"); if (button) { button.click(); console.log("Credits skipped", button); } - AmazonSkipCreditsObserver.observe(document, config); + AmazonSkipCreditsObserver.observe(document, AmazonSkipCreditsConfig); } else { console.log("stopped observing| Credits"); AmazonSkipCreditsObserver.disconnect(); } } async function startAmazonSkipAdObserver() { - if (settings.Amazon.skipAd) { - console.log("started observing| Ad"); - Amazon_AdTimeout(); + if (settings.Amazon.skipAd === undefined || settings.Amazon.skipAd) { + console.log("started observing| Self Ad"); + // Amazon_AdTimeout(); + AmazonSkipAdObserver.observe(document, config); + } else { + console.log("stopped observing| Self Ad"); + AmazonSkipAdObserver.disconnect(); + } + } + async function startAmazonBlockFreeveeObserver() { + if (settings.Amazon.blockFreevee === undefined || settings.Amazon.blockFreevee) { + console.log("started observing| FreeVee Ad"); + let video = document.querySelector("#dv-web-player > div > div:nth-child(1) > div > div > div.scalingVideoContainer > div.scalingVideoContainerBottom > div > video"); + let adTimeText = document.querySelector(".atvwebplayersdk-adtimeindicator-text"); + if (!document.querySelector(".fu4rd6c.f1cw2swo") && video != null && adTimeText != null) { + video.currentTime += parseInt(adTimeText.textContent.match(/\d+/)[0]); + console.log("FreeVee Ad skipped", adTimeText, video); + } + AmazonFreeVeeObserver.observe(document, FreeVeeConfig); } else { - console.log("stopped observing| Ad"); + console.log("stopped observing| FreeVee Ad"); + AmazonFreeVeeObserver.disconnect(); } } } diff --git a/downloaded HtmlButtons/AmazonIntro.html b/downloaded HtmlButtons/AmazonIntro.html new file mode 100644 index 00000000..1357fbb3 --- /dev/null +++ b/downloaded HtmlButtons/AmazonIntro.html @@ -0,0 +1,8 @@ +
+ +
diff --git a/downloaded HtmlButtons/freevee.html b/downloaded HtmlButtons/freevee.html new file mode 100644 index 00000000..705ecacf --- /dev/null +++ b/downloaded HtmlButtons/freevee.html @@ -0,0 +1,47 @@ +
+
+
+
+ + +
+
+ +
+
+
+
+
+
+
+
+
Ihr Programm wird in 97 Sek. fortgesetzt
+
+
+
+
+ +
diff --git a/firefox/manifest.json b/firefox/manifest.json index 88cb5c2e..9b9332a6 100644 --- a/firefox/manifest.json +++ b/firefox/manifest.json @@ -1,9 +1,9 @@ { "manifest_version": 2, "name": "Netflix/Prime Auto-Skip", - "version": "1.0.5", + "version": "1.0.6", - "description": "Automatically skip intros, Credits, Ads on Amazon Prime video and intros, recaps, credits and inactivity warnings on Netflix. You can configure what to watch and what to skip in the settings Page.", + "description": "Automatically skip Ads, intros, Credits on Amazon Prime video and intros, recaps, credits and inactivity on Netflix.", "homepage_url": "https://github.com/Dreamlinerm/Netflix-Prime-Auto-Skip", "icons": { "16": "icons/NetflixAmazon Auto-Skip.svg", diff --git a/firefox/popup/settings.html b/firefox/popup/settings.html index b40b21ef..62504415 100644 --- a/firefox/popup/settings.html +++ b/firefox/popup/settings.html @@ -16,7 +16,7 @@

Auto-skip

-

Version: 1.0.4

+

Version: 1.0.6

@@ -42,12 +42,19 @@

Prime Video Auto-skips:

-

Skip Ads:

+

Skip Self Ads:

+
+

Block Freevee Ads:

+ +

Netflix Auto-skips:

diff --git a/firefox/popup/settings.js b/firefox/popup/settings.js index ae60b9d5..67db3ca4 100644 --- a/firefox/popup/settings.js +++ b/firefox/popup/settings.js @@ -1,11 +1,11 @@ // global variables in localStorage -let settings; const defaultSettings = { settings: { - Amazon: { skipIntro: true, skipCredits: true, skipAd: true }, + Amazon: { skipIntro: true, skipCredits: true, skipAd: true, blockFreevee: true }, Netflix: { skipIntro: true, skipRecap: true, skipCredits: true, skipBlocked: true }, }, }; +let settings = defaultSettings.settings; browser.storage.sync.get("settings", function (result) { settings = result.settings; if (typeof settings !== "object") { @@ -31,6 +31,8 @@ function setCheckboxesToSettings() { if (button) button.checked = settings?.Amazon.skipCredits; button = document.querySelector("#AmazonAds"); if (button) button.checked = settings?.Amazon.skipAd; + button = document.querySelector("#AmazonFreevee"); + if (button) button.checked = settings?.Amazon.blockFreevee; button = document.querySelector("#NetflixIntro"); if (button) button.checked = settings?.Netflix.skipIntro; button = document.querySelector("#NetflixRecap"); @@ -61,6 +63,10 @@ function listenForClicks() { settings.Amazon.skipAd = !settings.Amazon.skipAd; console.log("settings.AmazonAd", settings); browser.storage.sync.set({ settings: settings }, function () {}); + } else if (e.target.id === "AmazonFreevee") { + settings.Amazon.blockFreevee = !settings.Amazon.blockFreevee; + console.log("settings.blockFreevee", settings); + browser.storage.sync.set({ settings: settings }, function () {}); } else if (e.target.id === "NetflixIntro") { settings.Netflix.skipIntro = !settings.Netflix.skipIntro; console.log("settings.NetflixIntro", settings); diff --git a/firefox/skipper.js b/firefox/skipper.js index cf9c7d1d..8da9088c 100644 --- a/firefox/skipper.js +++ b/firefox/skipper.js @@ -5,17 +5,17 @@ let url = window.location.href; let isAmazon = /amazon|primevideo/i.test(hostname); let isVideo = /video/i.test(title) || /video/i.test(url); let isNetflix = /netflix/i.test(hostname); -const version = "1.0.4"; +const version = "1.0.6"; if (isVideo || isNetflix) { // global variables in localStorage - let settings; const defaultSettings = { settings: { - Amazon: { skipIntro: true, skipCredits: true, skipAd: true }, + Amazon: { skipIntro: true, skipCredits: true, skipAd: true, blockFreevee: true }, Netflix: { skipIntro: true, skipRecap: true, skipCredits: true, skipBlocked: true }, }, }; + let settings = defaultSettings.settings; browser.storage.sync.get("settings", function (result) { settings = result.settings; console.log("%cNetflix%c/%cPrime%c Auto-Skip", "color: #e60010;font-size: 2em;", "color: white;font-size: 2em;", "color: #00aeef;font-size: 2em;", "color: white;font-size: 2em;"); @@ -27,28 +27,34 @@ if (isVideo || isNetflix) { } else { if (isNetflix) { // start Observers depending on the settings - if (settings.Netflix.skipIntro) { + if (settings.Netflix.skipIntro === undefined || settings.Netflix.skipIntro) { startNetflixSkipIntroObserver(); } - if (settings.Netflix.skipRecap) { + if (settings.Netflix.skipRecap === undefined || settings.Netflix.skipRecap) { startNetflixSkipRecapObserver(); } - if (result.settings.Netflix.skipCredits) { + if (result.settings.Netflix.skipCredits === undefined || result.settings.Netflix.skipCredits) { startNetflixSkipCreditsObserver(); } - if (settings.Netflix.skipBlocked) { + if (settings.Netflix.skipBlocked === undefined || settings.Netflix.skipBlocked) { startNetflixSkipBlockedObserver(); } } else { - if (settings.Amazon.skipIntro) { + if (settings.Amazon.skipIntro === undefined || settings.Amazon.skipIntro) { startAmazonSkipIntroObserver(); } - if (settings.Amazon.skipCredits) { + if (settings.Amazon.skipCredits === undefined || settings.Amazon.skipCredits) { startAmazonSkipCreditsObserver(); } - if (settings.Amazon.skipAd) { + if (settings.Amazon.skipAd === undefined || settings.Amazon.skipAd) { startAmazonSkipAdObserver(); } + if (settings.Amazon.blockFreevee === undefined || settings.Amazon.blockFreevee) { + // timeout of 100 ms because the ad is not loaded fast enough and the video will crash + setTimeout(function () { + startAmazonBlockFreeveeObserver(); + }, 200); + } } } }); @@ -82,15 +88,19 @@ if (isVideo || isNetflix) { if (oldValue === undefined || newValue.Amazon.skipAd !== oldValue.Amazon.skipAd) { startAmazonSkipAdObserver(); } + if (oldValue === undefined || newValue.Amazon.blockFreevee !== oldValue.Amazon.blockFreevee) { + startAmazonBlockFreeveeObserver(); + } } } } }); // Observers - // Options for the observer (which mutations to observe) + // default Options for the observer (which mutations to observe) const config = { attributes: true, childList: true, subtree: true }; // Netflix Observers + const NetflixConfig = { attributes: true, attributeFilter: ["data-uia"], subtree: true, childList: true, attributeOldValue: false }; const NetflixSkipIntroObserver = new MutationObserver(Netflix_intro); function Netflix_intro(mutations, observer) { for (let mutation of mutations) { @@ -140,17 +150,20 @@ if (isVideo || isNetflix) { } // Amazon Observers + + const AmazonSkipIntroConfig = { attributes: true, attributeFilter: [".skipelement"], subtree: true, childList: true, attributeOldValue: false }; const AmazonSkipIntro = new RegExp("skipelement", "i"); const AmazonSkipIntroObserver = new MutationObserver(Amazon_Intro); function Amazon_Intro(mutations, observer) { for (let mutation of mutations) { - if (AmazonSkipIntro.test(mutation.target.classList)) { - mutation.target.click(); - console.log("Intro skipped", mutation.target); + if (AmazonSkipIntro.test(mutation.target.firstChild.classList)) { + mutation.target.firstChild.click(); + console.log("Intro skipped", mutation.target.firstChild); } } } + const AmazonSkipCreditsConfig = { attributes: true, attributeFilter: [".nextupcard"], subtree: true, childList: true, attributeOldValue: false }; const AmazonSkipCredits = new RegExp("nextupcard", "i"); const AmazonSkipCredits2 = new RegExp("nextupcard-button", "i"); const AmazonSkipCreditsObserver = new MutationObserver(Amazon_Credits); @@ -167,43 +180,48 @@ if (isVideo || isNetflix) { } } - // const SkipAdTranslation = { - // en: "Skip", - // de: "Überspringen", - // }; - // const AmazonSkipAdObserver = new MutationObserver(Amazon_Ad); - // async function Amazon_Ad(mutations, observer) { - // // the button classes are class="fu4rd6c f1cw2swo" but im not sure they are changed may need to refresh - // let button = document.querySelector(".fu4rd6c.f1cw2swo"); - // if (button) { - // button.click(); - // console.log("Ad skipped", button); - // } - // // alternative - // // let buttons = document.querySelector("[class*=webplayersdk-infobar-container]").getElementsByTagName("div"); - // // for (let i = 0; i < buttons.length; i++) { - // // if (buttons[i]?.firstChild?.textContent == SkipAdTranslation[language]) { - // // console.log("Ad skipped", buttons[i]); - // // buttons[i].click(); - // // setTimeout(function () { - // // console.log("Hello World"); - // // }, 2000); - // // } - // // } - // } + const FreeVeeConfig = { attributes: true, attributeFilter: [".atvwebplayersdk-adtimeindicator-text"], subtree: true, childList: true, attributeOldValue: false }; + const AmazonFreeVeeObserver = new MutationObserver(AmazonFreeVee); + function AmazonFreeVee(mutations, observer) { + // if (document.querySelector("[class*=infobar-container]").classList.contains("show")) { + let video = document.querySelector("#dv-web-player > div > div:nth-child(1) > div > div > div.scalingVideoContainer > div.scalingVideoContainerBottom > div > video"); + let adTimeText = document.querySelector(".atvwebplayersdk-adtimeindicator-text"); + // !document.querySelector(".fu4rd6c.f1cw2swo") + if (adTimeText.textContent.length > 7 && video != null && adTimeText != null) { + video.currentTime += parseInt(adTimeText.textContent.match(/\d+/)[0]); + console.log("FreeVee Ad skipped", adTimeText, video); + } + // } + } + const AmazonSkipAdObserver = new MutationObserver(Amazon_Ad); + async function Amazon_Ad(mutations, observer) { + for (let mutation of mutations) { + if (mutation.target.classList.contains("atvwebplayersdk-infobar-container")) { + if (mutation.target.classList.contains("show")) { + let button = mutation.target.querySelector(".fu4rd6c.f1cw2swo"); + if (button) { + button.click(); + console.log("Self Ad skipped", button); + } + } + } + } + } + + // a little to intense to do this every time but it works, not currently used async function Amazon_AdTimeout() { // set loop every 0.5 sec and check if ad is there setInterval(function () { // if infobar is shown - let infobar = document.querySelector("[class*=infobar-container]").classList.contains("show"); - if (infobar) { + if (document.querySelector("[class*=infobar-container]").classList.contains("show")) { // the button classes are class="fu4rd6c f1cw2swo" but im not sure they are changed may need to refresh - // adtimeindicator-text might be an alternative + // adtimeindicator-text might be an alternative like here + // document.querySelector(".atvwebplayersdk-adtimeindicator-text").parentNode.parentNode.querySelector("div:nth-child(3) > div:nth-child(2)") let button = document.querySelector(".fu4rd6c.f1cw2swo"); if (button) { button.click(); - console.log("Ad skipped", button); + console.log("Self Ad skipped", button); } } if (!settings.Amazon.skipAd) { @@ -213,95 +231,112 @@ if (isVideo || isNetflix) { } // start/stop the observers depending on settings async function startNetflixSkipIntroObserver() { - if (settings.Netflix.skipIntro) { + if (settings.Netflix.skipIntro === undefined || settings.Netflix.skipIntro) { console.log("started observing| intro"); let button = document.querySelector('[data-uia="player-skip-intro"]'); if (button) { button.click(); console.log("intro skipped", button); } - NetflixSkipIntroObserver.observe(document, config); + NetflixSkipIntroObserver.observe(document, NetflixConfig); } else { console.log("stopped observing | intro"); NetflixSkipIntroObserver.disconnect(); } } async function startNetflixSkipRecapObserver() { - if (settings.Netflix.skipRecap) { + if (settings.Netflix.skipRecap === undefined || settings.Netflix.skipRecap) { console.log("started observing| Recap"); let button = document.querySelector('[data-uia="player-skip-recap"]') || document.querySelector('[data-uia="player-skip-preplay"]'); if (button) { button.click(); console.log("Recap skipped", button); } - NetflixSkipRecapObserver.observe(document, config); + NetflixSkipRecapObserver.observe(document, NetflixConfig); } else { console.log("stopped observing| Recap"); NetflixSkipRecapObserver.disconnect(); } } async function startNetflixSkipCreditsObserver() { - if (settings.Netflix.skipCredits) { + if (settings.Netflix.skipCredits === undefined || settings.Netflix.skipCredits) { console.log("started observing| Credits"); let button = document.querySelector('[data-uia="next-episode-seamless-button"]'); if (button) { button.click(); console.log("Credits skipped", button); } - NetflixSkipCreditsObserver.observe(document, config); + NetflixSkipCreditsObserver.observe(document, NetflixConfig); } else { console.log("stopped observing| Credits"); NetflixSkipCreditsObserver.disconnect(); } } async function startNetflixSkipBlockedObserver() { - if (settings.Netflix.skipBlocked) { + if (settings.Netflix.skipBlocked === undefined || settings.Netflix.skipBlocked) { console.log("started observing| Blocked"); let button = document.querySelector('[data-uia="interrupt-autoplay-continue"]'); if (button) { button.click(); console.log("Blocked skipped", button); } - NetflixSkipBlockedObserver.observe(document, config); + NetflixSkipBlockedObserver.observe(document, NetflixConfig); } else { console.log("stopped observing| Blocked"); NetflixSkipBlockedObserver.disconnect(); } } async function startAmazonSkipIntroObserver() { - if (settings.Amazon.skipIntro) { + if (settings.Amazon.skipIntro === undefined || settings.Amazon.skipIntro) { console.log("started observing| Intro"); let button = document.querySelector("[class*=skipelement]"); if (button) { button.click(); console.log("Intro skipped", button); } - AmazonSkipIntroObserver.observe(document, config); + AmazonSkipIntroObserver.observe(document, AmazonSkipIntroConfig); } else { console.log("stopped observing| Intro"); AmazonSkipIntroObserver.disconnect(); } } async function startAmazonSkipCreditsObserver() { - if (settings.Amazon.skipCredits) { + if (settings.Amazon.skipCredits === undefined || settings.Amazon.skipCredits) { console.log("started observing| Credits"); let button = document.querySelector("[class*=nextupcard-button]"); if (button) { button.click(); console.log("Credits skipped", button); } - AmazonSkipCreditsObserver.observe(document, config); + AmazonSkipCreditsObserver.observe(document, AmazonSkipCreditsConfig); } else { console.log("stopped observing| Credits"); AmazonSkipCreditsObserver.disconnect(); } } async function startAmazonSkipAdObserver() { - if (settings.Amazon.skipAd) { - console.log("started observing| Ad"); - Amazon_AdTimeout(); + if (settings.Amazon.skipAd === undefined || settings.Amazon.skipAd) { + console.log("started observing| Self Ad"); + // Amazon_AdTimeout(); + AmazonSkipAdObserver.observe(document, config); + } else { + console.log("stopped observing| Self Ad"); + AmazonSkipAdObserver.disconnect(); + } + } + async function startAmazonBlockFreeveeObserver() { + if (settings.Amazon.blockFreevee === undefined || settings.Amazon.blockFreevee) { + console.log("started observing| FreeVee Ad"); + let video = document.querySelector("#dv-web-player > div > div:nth-child(1) > div > div > div.scalingVideoContainer > div.scalingVideoContainerBottom > div > video"); + let adTimeText = document.querySelector(".atvwebplayersdk-adtimeindicator-text"); + if (!document.querySelector(".fu4rd6c.f1cw2swo") && video != null && adTimeText != null) { + video.currentTime += parseInt(adTimeText.textContent.match(/\d+/)[0]); + console.log("FreeVee Ad skipped", adTimeText, video); + } + AmazonFreeVeeObserver.observe(document, FreeVeeConfig); } else { - console.log("stopped observing| Ad"); + console.log("stopped observing| FreeVee Ad"); + AmazonFreeVeeObserver.disconnect(); } } } diff --git a/firefox/web-ext-artifacts/netflix_prime_auto-skip-1.0.5.zip b/firefox/web-ext-artifacts/netflix_prime_auto-skip-1.0.5.zip deleted file mode 100644 index f09fb78c0fa6c37096ff40ea37675ef198c20c2d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8794 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 A_5c6? literal 0 HcmV?d00001