diff --git a/package.json b/package.json index 77d68d945a..d24d293f21 100644 --- a/package.json +++ b/package.json @@ -105,7 +105,7 @@ "npm": "Please use yarn instead" }, "dependencies": { - "@cliqz/adblocker-electron": "1.26.3", + "@cliqz/adblocker-electron": "^1.26.5", "@ffmpeg/core": "^0.11.0", "@ffmpeg/ffmpeg": "^0.11.6", "@foobar404/wave": "^2.0.4", diff --git a/plugins/adblocker/back.js b/plugins/adblocker/back.js index 65e0df4602..d15aa69591 100644 --- a/plugins/adblocker/back.js +++ b/plugins/adblocker/back.js @@ -1,8 +1,13 @@ const { loadAdBlockerEngine } = require("./blocker"); -module.exports = (win, options) => - loadAdBlockerEngine( - win.webContents.session, - options.cache, - options.additionalBlockLists, - options.disableDefaultLists - ); +const config = require("./config"); + +module.exports = async (win, options) => { + if (await config.shouldUseBlocklists()) { + loadAdBlockerEngine( + win.webContents.session, + options.cache, + options.additionalBlockLists, + options.disableDefaultLists, + ); + } +}; diff --git a/plugins/adblocker/config.js b/plugins/adblocker/config.js new file mode 100644 index 0000000000..5c17a03a6f --- /dev/null +++ b/plugins/adblocker/config.js @@ -0,0 +1,13 @@ +const { PluginConfig } = require("../../config/dynamic"); + +const config = new PluginConfig("adblocker", { enableFront: true }); + +const blockers = { + WithBlocklists: "With blocklists", + InPlayer: "In player", +}; + +const shouldUseBlocklists = async () => + (await config.get("blocker")) !== blockers.InPlayer; + +module.exports = { shouldUseBlocklists, blockers, ...config }; diff --git a/plugins/adblocker/inject.js b/plugins/adblocker/inject.js new file mode 100644 index 0000000000..e296236fd9 --- /dev/null +++ b/plugins/adblocker/inject.js @@ -0,0 +1,289 @@ +// Source: https://addons.mozilla.org/en-US/firefox/addon/adblock-for-youtube/ +// https://robwu.nl/crxviewer/?crx=https%3A%2F%2Faddons.mozilla.org%2Fen-US%2Ffirefox%2Faddon%2Fadblock-for-youtube%2F + +/* + Parts of this code is derived from set-constant.js: + https://github.com/gorhill/uBlock/blob/5de0ce975753b7565759ac40983d31978d1f84ca/assets/resources/scriptlets.js#L704 + */ + +{ + let pruner = function (o) { + delete o.playerAds; + delete o.adPlacements; + // + if (o.playerResponse) { + delete o.playerResponse.playerAds; + delete o.playerResponse.adPlacements; + } + // + return o; + }; + + JSON.parse = new Proxy(JSON.parse, { + apply: function () { + return pruner(Reflect.apply(...arguments)); + }, + }); + + Response.prototype.json = new Proxy(Response.prototype.json, { + apply: function () { + return Reflect.apply(...arguments).then((o) => pruner(o)); + }, + }); +} + +(function () { + let cValue = "undefined"; + const chain = "playerResponse.adPlacements"; + const thisScript = document.currentScript; + // + if (cValue === "null") cValue = null; + else if (cValue === "''") cValue = ""; + else if (cValue === "true") cValue = true; + else if (cValue === "false") cValue = false; + else if (cValue === "undefined") cValue = undefined; + else if (cValue === "noopFunc") cValue = function () {}; + else if (cValue === "trueFunc") + cValue = function () { + return true; + }; + else if (cValue === "falseFunc") + cValue = function () { + return false; + }; + else if (/^\d+$/.test(cValue)) { + cValue = parseFloat(cValue); + // + if (isNaN(cValue)) return; + if (Math.abs(cValue) > 0x7fff) return; + } else { + return; + } + // + let aborted = false; + const mustAbort = function (v) { + if (aborted) return true; + aborted = + v !== undefined && + v !== null && + cValue !== undefined && + cValue !== null && + typeof v !== typeof cValue; + return aborted; + }; + + /* + Support multiple trappers for the same property: + https://github.com/uBlockOrigin/uBlock-issues/issues/156 + */ + + const trapProp = function (owner, prop, configurable, handler) { + if (handler.init(owner[prop]) === false) { + return; + } + // + const odesc = Object.getOwnPropertyDescriptor(owner, prop); + let prevGetter, prevSetter; + if (odesc instanceof Object) { + if (odesc.configurable === false) return; + if (odesc.get instanceof Function) prevGetter = odesc.get; + if (odesc.set instanceof Function) prevSetter = odesc.set; + } + // + Object.defineProperty(owner, prop, { + configurable, + get() { + if (prevGetter !== undefined) { + prevGetter(); + } + // + return handler.getter(); + }, + set(a) { + if (prevSetter !== undefined) { + prevSetter(a); + } + // + handler.setter(a); + }, + }); + }; + + const trapChain = function (owner, chain) { + const pos = chain.indexOf("."); + if (pos === -1) { + trapProp(owner, chain, false, { + v: undefined, + getter: function () { + return document.currentScript === thisScript ? this.v : cValue; + }, + setter: function (a) { + if (mustAbort(a) === false) return; + cValue = a; + }, + init: function (v) { + if (mustAbort(v)) return false; + // + this.v = v; + return true; + }, + }); + // + return; + } + // + const prop = chain.slice(0, pos); + const v = owner[prop]; + // + chain = chain.slice(pos + 1); + if (v instanceof Object || (typeof v === "object" && v !== null)) { + trapChain(v, chain); + return; + } + // + trapProp(owner, prop, true, { + v: undefined, + getter: function () { + return this.v; + }, + setter: function (a) { + this.v = a; + if (a instanceof Object) trapChain(a, chain); + }, + init: function (v) { + this.v = v; + return true; + }, + }); + }; + // + trapChain(window, chain); +})(); + +(function () { + let cValue = "undefined"; + const thisScript = document.currentScript; + const chain = "ytInitialPlayerResponse.adPlacements"; + // + if (cValue === "null") cValue = null; + else if (cValue === "''") cValue = ""; + else if (cValue === "true") cValue = true; + else if (cValue === "false") cValue = false; + else if (cValue === "undefined") cValue = undefined; + else if (cValue === "noopFunc") cValue = function () {}; + else if (cValue === "trueFunc") + cValue = function () { + return true; + }; + else if (cValue === "falseFunc") + cValue = function () { + return false; + }; + else if (/^\d+$/.test(cValue)) { + cValue = parseFloat(cValue); + // + if (isNaN(cValue)) return; + if (Math.abs(cValue) > 0x7fff) return; + } else { + return; + } + // + let aborted = false; + const mustAbort = function (v) { + if (aborted) return true; + aborted = + v !== undefined && + v !== null && + cValue !== undefined && + cValue !== null && + typeof v !== typeof cValue; + return aborted; + }; + + /* + Support multiple trappers for the same property: + https://github.com/uBlockOrigin/uBlock-issues/issues/156 + */ + + const trapProp = function (owner, prop, configurable, handler) { + if (handler.init(owner[prop]) === false) { + return; + } + // + const odesc = Object.getOwnPropertyDescriptor(owner, prop); + let prevGetter, prevSetter; + if (odesc instanceof Object) { + if (odesc.configurable === false) return; + if (odesc.get instanceof Function) prevGetter = odesc.get; + if (odesc.set instanceof Function) prevSetter = odesc.set; + } + // + Object.defineProperty(owner, prop, { + configurable, + get() { + if (prevGetter !== undefined) { + prevGetter(); + } + // + return handler.getter(); + }, + set(a) { + if (prevSetter !== undefined) { + prevSetter(a); + } + // + handler.setter(a); + }, + }); + }; + + const trapChain = function (owner, chain) { + const pos = chain.indexOf("."); + if (pos === -1) { + trapProp(owner, chain, false, { + v: undefined, + getter: function () { + return document.currentScript === thisScript ? this.v : cValue; + }, + setter: function (a) { + if (mustAbort(a) === false) return; + cValue = a; + }, + init: function (v) { + if (mustAbort(v)) return false; + // + this.v = v; + return true; + }, + }); + // + return; + } + // + const prop = chain.slice(0, pos); + const v = owner[prop]; + // + chain = chain.slice(pos + 1); + if (v instanceof Object || (typeof v === "object" && v !== null)) { + trapChain(v, chain); + return; + } + // + trapProp(owner, prop, true, { + v: undefined, + getter: function () { + return this.v; + }, + setter: function (a) { + this.v = a; + if (a instanceof Object) trapChain(a, chain); + }, + init: function (v) { + this.v = v; + return true; + }, + }); + }; + // + trapChain(window, chain); +})(); diff --git a/plugins/adblocker/menu.js b/plugins/adblocker/menu.js new file mode 100644 index 0000000000..1622df9a56 --- /dev/null +++ b/plugins/adblocker/menu.js @@ -0,0 +1,15 @@ +const config = require("./config"); + +module.exports = () => [ + { + label: "Blocker", + submenu: Object.values(config.blockers).map((blocker) => ({ + label: blocker, + type: "radio", + checked: (config.get("blocker") || config.blockers.WithBlocklists) === blocker, + click: () => { + config.set("blocker", blocker); + }, + })), + }, +]; diff --git a/plugins/adblocker/preload.js b/plugins/adblocker/preload.js index 2b5a12dfe9..e7650d05bf 100644 --- a/plugins/adblocker/preload.js +++ b/plugins/adblocker/preload.js @@ -1,4 +1,10 @@ -module.exports = () => { - // Preload adblocker to inject scripts/styles - require("@cliqz/adblocker-electron-preload"); +const config = require("./config"); + +module.exports = async () => { + if (await config.shouldUseBlocklists()) { + // Preload adblocker to inject scripts/styles + require("@cliqz/adblocker-electron-preload"); + } else if ((await config.get("blocker")) === config.blockers.InPlayer) { + require("./inject"); + } }; diff --git a/yarn.lock b/yarn.lock index b663ce505e..c5676fac7b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -57,7 +57,7 @@ __metadata: languageName: node linkType: hard -"@cliqz/adblocker-electron-preload@npm:^1.26.3": +"@cliqz/adblocker-electron-preload@npm:^1.26.5": version: 1.26.5 resolution: "@cliqz/adblocker-electron-preload@npm:1.26.5" dependencies: @@ -68,16 +68,16 @@ __metadata: languageName: node linkType: hard -"@cliqz/adblocker-electron@npm:1.26.3": - version: 1.26.3 - resolution: "@cliqz/adblocker-electron@npm:1.26.3" +"@cliqz/adblocker-electron@npm:^1.26.5": + version: 1.26.5 + resolution: "@cliqz/adblocker-electron@npm:1.26.5" dependencies: - "@cliqz/adblocker": ^1.26.3 - "@cliqz/adblocker-electron-preload": ^1.26.3 + "@cliqz/adblocker": ^1.26.5 + "@cliqz/adblocker-electron-preload": ^1.26.5 tldts-experimental: ^5.6.21 peerDependencies: electron: ">11" - checksum: 3a649ff7aaebeb4265f3d9f75ffed11a29b06005a437f92873b1b286fd742631cb8beaf83b7973adda334a1b91075ac27a864503ae5451ac3dddfe5c0bd59bb4 + checksum: 666f562a5745cf0e54b26253d6c9506a049c9a84a8a1002d3027a951bf195f094f7d506997872e0774dc4b6bc5b7b1a02db0c1aa9c44219ead283e0b90394d3c languageName: node linkType: hard @@ -88,7 +88,7 @@ __metadata: languageName: node linkType: hard -"@cliqz/adblocker@npm:^1.26.3": +"@cliqz/adblocker@npm:^1.26.5": version: 1.26.5 resolution: "@cliqz/adblocker@npm:1.26.5" dependencies: @@ -7325,7 +7325,7 @@ __metadata: version: 0.0.0-use.local resolution: "youtube-music@workspace:." dependencies: - "@cliqz/adblocker-electron": 1.26.3 + "@cliqz/adblocker-electron": ^1.26.5 "@ffmpeg/core": ^0.11.0 "@ffmpeg/ffmpeg": ^0.11.6 "@foobar404/wave": ^2.0.4