diff --git a/flake.nix b/flake.nix index 66eb94e..9cc414a 100644 --- a/flake.nix +++ b/flake.nix @@ -52,7 +52,20 @@ nixpkgs.lib.nixosSystem { modules = [ { - nixpkgs.overlays = []; + nixpkgs.overlays = [ + (final: prev: { + # FIXME: Workaround: Mesa crash with AMD GPU + Wayland + Qt 6.11.0 + qutebrowser = prev.symlinkJoin { + name = "qutebrowser"; + paths = [prev.qutebrowser]; + buildInputs = [prev.makeWrapper]; + postBuild = '' + wrapProgram $out/bin/qutebrowser \ + --set LIBGL_ALWAYS_SOFTWARE 1 + ''; + }; + }) + ]; _module.args = { inherit inputs; }; diff --git a/home/programs/qutebrowser/greasemonkey/dont-track-me-google.user.js b/home/programs/qutebrowser/greasemonkey/dont-track-me-google.user.js deleted file mode 100644 index 0b1ede4..0000000 --- a/home/programs/qutebrowser/greasemonkey/dont-track-me-google.user.js +++ /dev/null @@ -1,826 +0,0 @@ -// ==UserScript== -// @name Don't track me Google -// @namespace Rob W -// @description Removes the annoying link-conversion at Google Search/maps/... -// @version 4.28 -// @icon https://raw.githubusercontent.com/Rob--W/dont-track-me-google/master/icon48.png -// @supportURL https://github.com/Rob--W/dont-track-me-google/issues -// @license MIT -// @run-at document-start -// @match *://*.google.com/* -// @match *://*.google.ad/* -// @match *://*.google.ae/* -// @match *://*.google.com.af/* -// @match *://*.google.com.ag/* -// @match *://*.google.com.ai/* -// @match *://*.google.al/* -// @match *://*.google.am/* -// @match *://*.google.co.ao/* -// @match *://*.google.com.ar/* -// @match *://*.google.as/* -// @match *://*.google.at/* -// @match *://*.google.com.au/* -// @match *://*.google.az/* -// @match *://*.google.ba/* -// @match *://*.google.com.bd/* -// @match *://*.google.be/* -// @match *://*.google.bf/* -// @match *://*.google.bg/* -// @match *://*.google.com.bh/* -// @match *://*.google.bi/* -// @match *://*.google.bj/* -// @match *://*.google.com.bn/* -// @match *://*.google.com.bo/* -// @match *://*.google.com.br/* -// @match *://*.google.bs/* -// @match *://*.google.bt/* -// @match *://*.google.co.bw/* -// @match *://*.google.by/* -// @match *://*.google.com.bz/* -// @match *://*.google.ca/* -// @match *://*.google.cd/* -// @match *://*.google.cf/* -// @match *://*.google.cg/* -// @match *://*.google.ch/* -// @match *://*.google.ci/* -// @match *://*.google.co.ck/* -// @match *://*.google.cl/* -// @match *://*.google.cm/* -// @match *://*.google.cn/* -// @match *://*.google.com.co/* -// @match *://*.google.co.cr/* -// @match *://*.google.com.cu/* -// @match *://*.google.cv/* -// @match *://*.google.com.cy/* -// @match *://*.google.cz/* -// @match *://*.google.de/* -// @match *://*.google.dj/* -// @match *://*.google.dk/* -// @match *://*.google.dm/* -// @match *://*.google.com.do/* -// @match *://*.google.dz/* -// @match *://*.google.com.ec/* -// @match *://*.google.ee/* -// @match *://*.google.com.eg/* -// @match *://*.google.es/* -// @match *://*.google.com.et/* -// @match *://*.google.fi/* -// @match *://*.google.com.fj/* -// @match *://*.google.fm/* -// @match *://*.google.fr/* -// @match *://*.google.ga/* -// @match *://*.google.ge/* -// @match *://*.google.gg/* -// @match *://*.google.com.gh/* -// @match *://*.google.com.gi/* -// @match *://*.google.gl/* -// @match *://*.google.gm/* -// @match *://*.google.gp/* -// @match *://*.google.gr/* -// @match *://*.google.com.gt/* -// @match *://*.google.gy/* -// @match *://*.google.com.hk/* -// @match *://*.google.hn/* -// @match *://*.google.hr/* -// @match *://*.google.ht/* -// @match *://*.google.hu/* -// @match *://*.google.co.id/* -// @match *://*.google.ie/* -// @match *://*.google.co.il/* -// @match *://*.google.im/* -// @match *://*.google.co.in/* -// @match *://*.google.iq/* -// @match *://*.google.is/* -// @match *://*.google.it/* -// @match *://*.google.je/* -// @match *://*.google.com.jm/* -// @match *://*.google.jo/* -// @match *://*.google.co.jp/* -// @match *://*.google.co.ke/* -// @match *://*.google.com.kh/* -// @match *://*.google.ki/* -// @match *://*.google.kg/* -// @match *://*.google.co.kr/* -// @match *://*.google.com.kw/* -// @match *://*.google.kz/* -// @match *://*.google.la/* -// @match *://*.google.com.lb/* -// @match *://*.google.li/* -// @match *://*.google.lk/* -// @match *://*.google.co.ls/* -// @match *://*.google.lt/* -// @match *://*.google.lu/* -// @match *://*.google.lv/* -// @match *://*.google.com.ly/* -// @match *://*.google.co.ma/* -// @match *://*.google.md/* -// @match *://*.google.me/* -// @match *://*.google.mg/* -// @match *://*.google.mk/* -// @match *://*.google.ml/* -// @match *://*.google.com.mm/* -// @match *://*.google.mn/* -// @match *://*.google.ms/* -// @match *://*.google.com.mt/* -// @match *://*.google.mu/* -// @match *://*.google.mv/* -// @match *://*.google.mw/* -// @match *://*.google.com.mx/* -// @match *://*.google.com.my/* -// @match *://*.google.co.mz/* -// @match *://*.google.com.na/* -// @match *://*.google.com.nf/* -// @match *://*.google.com.ng/* -// @match *://*.google.com.ni/* -// @match *://*.google.ne/* -// @match *://*.google.nl/* -// @match *://*.google.no/* -// @match *://*.google.com.np/* -// @match *://*.google.nr/* -// @match *://*.google.nu/* -// @match *://*.google.co.nz/* -// @match *://*.google.com.om/* -// @match *://*.google.com.pa/* -// @match *://*.google.com.pe/* -// @match *://*.google.com.pg/* -// @match *://*.google.com.ph/* -// @match *://*.google.com.pk/* -// @match *://*.google.pl/* -// @match *://*.google.pn/* -// @match *://*.google.com.pr/* -// @match *://*.google.ps/* -// @match *://*.google.pt/* -// @match *://*.google.com.py/* -// @match *://*.google.com.qa/* -// @match *://*.google.ro/* -// @match *://*.google.ru/* -// @match *://*.google.rw/* -// @match *://*.google.com.sa/* -// @match *://*.google.com.sb/* -// @match *://*.google.sc/* -// @match *://*.google.se/* -// @match *://*.google.com.sg/* -// @match *://*.google.sh/* -// @match *://*.google.si/* -// @match *://*.google.sk/* -// @match *://*.google.com.sl/* -// @match *://*.google.sn/* -// @match *://*.google.so/* -// @match *://*.google.sm/* -// @match *://*.google.sr/* -// @match *://*.google.st/* -// @match *://*.google.com.sv/* -// @match *://*.google.td/* -// @match *://*.google.tg/* -// @match *://*.google.co.th/* -// @match *://*.google.com.tj/* -// @match *://*.google.tk/* -// @match *://*.google.tl/* -// @match *://*.google.tm/* -// @match *://*.google.tn/* -// @match *://*.google.to/* -// @match *://*.google.com.tr/* -// @match *://*.google.tt/* -// @match *://*.google.com.tw/* -// @match *://*.google.co.tz/* -// @match *://*.google.com.ua/* -// @match *://*.google.co.ug/* -// @match *://*.google.co.uk/* -// @match *://*.google.com.uy/* -// @match *://*.google.co.uz/* -// @match *://*.google.com.vc/* -// @match *://*.google.co.ve/* -// @match *://*.google.vg/* -// @match *://*.google.co.vi/* -// @match *://*.google.com.vn/* -// @match *://*.google.vu/* -// @match *://*.google.ws/* -// @match *://*.google.rs/* -// @match *://*.google.co.za/* -// @match *://*.google.co.zm/* -// @match *://*.google.co.zw/* -// @match *://*.google.cat/* -// @match *://*.google.ng/* -// @downloadURL https://update.greasyfork.org/scripts/428243/Don%27t%20track%20me%20Google.user.js -// @updateURL https://update.greasyfork.org/scripts/428243/Don%27t%20track%20me%20Google.meta.js -// ==/UserScript== - -document.addEventListener('mousedown', handlePointerPress, true); -document.addEventListener('touchstart', handlePointerPress, true); -document.addEventListener('click', handleClick, true); -var scriptCspNonce; -var needsCspNonce = typeof browser !== 'undefined'; // Firefox. -var preferenceObservers = []; -setupAggresiveUglyLinkPreventer(); - -var forceNoReferrer = true; -var noping = true; -if (typeof chrome == 'object' && chrome.storage) { - (chrome.storage.sync || chrome.storage.local).get({ - forceNoReferrer: true, - // From version 4.7 until 4.11, the preference was the literal value of - // the referrer policy. - referrerPolicy: 'no-referrer', - noping: true, - }, function(items) { - if (items) { - // Migration code (to be removed in the future). - if (items.referrerPolicy === '') { - // User explicitly allowed referrers to be sent, respect that. - items.forceNoReferrer = false; - } - forceNoReferrer = items.forceNoReferrer; - noping = items.noping; - callPreferenceObservers(); - } - }); - chrome.storage.onChanged.addListener(function(changes) { - if (changes.forceNoReferrer) { - forceNoReferrer = changes.forceNoReferrer.newValue; - } - if (changes.noping) { - noping = changes.noping.newValue; - } - callPreferenceObservers(); - }); -} - -function callImmediatelyAndOnPreferenceUpdate(callback) { - callback(); - preferenceObservers.push(callback); -} -function callPreferenceObservers() { - // This method is usually once, and occasionally more than once if the user - // changes a preference. For simplicity we don't check whether a pref was - // changed before calling a callback - these are cheap anyway. - preferenceObservers.forEach(function(callback) { - callback(); - }); -} - -function getReferrerPolicy() { - return forceNoReferrer ? 'origin' : ''; -} - -function updateReferrerPolicy(a) { - if (a.referrerPolicy === 'no-referrer') { - // "no-referrer" is more privacy-friendly than "origin". - return; - } - var referrerPolicy = getReferrerPolicy(); - if (referrerPolicy) { - a.referrerPolicy = referrerPolicy; - } -} - -function handlePointerPress(e) { - var a = e.target; - while (a && !a.href) { - a = a.parentElement; - } - if (!a) { - return; - } - var inlineMousedown = a.getAttribute('onmousedown'); - // return rwt(....); // E.g Google search results. - // return google.rwt(...); // E.g. sponsored search results - // return google.arwt(this); // E.g. sponsored search results (dec 2016). - if (inlineMousedown && /\ba?rwt\(/.test(inlineMousedown)) { - a.removeAttribute('onmousedown'); - // Just in case: - a.removeAttribute('ping'); - // In Chrome, removing onmousedown during event dispatch does not - // prevent the inline listener from running... So we have to cancel - // event propagation just in case. - e.stopImmediatePropagation(); - } - if (noping) { - a.removeAttribute('ping'); - } - var realLink = getRealLinkFromGoogleUrl(a); - if (realLink) { - a.href = realLink; - // Sometimes, two fixups are needed, on old mobile user agents: - // /url?q=https://googleweblight.com/fp?u=... -> ... - realLink = getRealLinkFromGoogleUrl(a); - if (realLink) { - a.href = realLink; - } - } - updateReferrerPolicy(a); - - if (e.eventPhase === Event.CAPTURING_PHASE) { - // Our event listener runs first, to sanitize the link. - // But the page may have an event handler that modifies the link again. - // We can append a listener to the bubbling phase of the (current) - // event dispatch to fix the link up again, provided that the page did - // not call stopPropagation() or stopImmediatePropagation(). - var eventOptions = { capture: false, once: true }; - a.addEventListener(e.type, handlePointerPress, eventOptions); - document.addEventListener(e.type, handlePointerPress, eventOptions); - } -} - -// This is specifically designed for catching clicks in Gmail. -// Gmail binds a click handler to a
and cancels the event after opening -// a window with an ugly URL. It uses a blank window + meta refresh in Firefox, -// which is too crazy to patch. So we just make sure that the browser's default -// click handler is activated (=open link in new tab). -// The entry point for this crazy stuff is shown in my comment at -// https://github.com/Rob--W/dont-track-me-google/issues/2 -function handleClick(e) { - if (e.button !== 0) { - return; - } - var a = e.target; - while (a && !a.href) { - a = a.parentElement; - } - if (!a) { - return; - } - if (a.dataset && a.dataset.url) { - var realLink = getSanitizedIntentUrl(a.dataset.url); - if (realLink) { - a.dataset.url = realLink; - } - } - if (!location.hostname.startsWith('mail.')) { - // This hack was designed for Gmail, but broke other Google sites: - // - https://github.com/Rob--W/dont-track-me-google/issues/6 - // - https://github.com/Rob--W/dont-track-me-google/issues/19 - // So let's disable it for every domain except Gmail. - return; - } - // TODO: Consider using a.baseURI instead of location in case Gmail ever - // starts using ? - if (a.origin === location.origin) { - // Same-origin link. - // E.g. an in-page navigation at Google Docs (#...) - // or an attachment at Gmail (https://mail.google.com/mail/u/0?ui=2&...) - return; - } - if (a.protocol !== 'http:' && - a.protocol !== 'https:' && - a.protocol !== 'ftp:') { - // Be conservative and don't block too much. E.g. Gmail has special - // handling for mailto:-URLs, and using stopPropagation now would - // cause mailto:-links to be opened by the platform's default mailto - // handler instead of Gmail's handler (=open in new window). - return; - } - if (a.target === '_blank') { - e.stopPropagation(); - updateReferrerPolicy(a); - } -} - -/** - * @param {URL|HTMLHyperlinkElementUtils} a - * @returns {String} the real URL if the given link is a Google redirect URL. - */ -function getRealLinkFromGoogleUrl(a) { - if (a.protocol !== 'https:' && a.protocol !== 'http:') { - return; - } - var url; - if ((a.hostname === location.hostname || a.hostname === 'www.google.com') && - (a.pathname === '/url' || a.pathname === '/local_url' || - a.pathname === '/searchurl/rr.html' || - a.pathname === '/linkredirect')) { - // Google Maps / Dito (/local_url?q=) - // Mobile (/url?q=) - // Google Meet's chat (/linkredirect?authuser=0&dest=) - url = /[?&](?:q|url|dest)=((?:https?|ftp)[%:][^&]+)/.exec(a.search); - if (url) { - return decodeURIComponent(url[1]); - } - // Help pages, e.g. safe browsing (/url?...&q=%2Fsupport%2Fanswer...) - url = /[?&](?:q|url)=((?:%2[Ff]|\/)[^&]+)/.exec(a.search); - if (url) { - return a.origin + decodeURIComponent(url[1]); - } - // Redirect pages for Android intents (/searchurl/rr.html#...&url=...) - // rr.html only supports http(s). So restrict to http(s) only. - url = /[#&]url=(https?[:%][^&]+)/.exec(a.hash); - if (url) { - return decodeURIComponent(url[1]); - } - } - // Google Search with old mobile UA (e.g. Firefox 41). - if (a.hostname === 'googleweblight.com' && a.pathname === '/fp') { - url = /[?&]u=((?:https?|ftp)[%:][^&]+)/.exec(a.search); - if (url) { - return decodeURIComponent(url[1]); - } - } -} - -/** - * @param {string} intentUrl - * @returns {string|undefined} The sanitized intent:-URL if it was an intent URL - * with embedded tracking link. - */ -function getSanitizedIntentUrl(intentUrl) { - if (!intentUrl.startsWith('intent:')) { - return; - } - // https://developer.chrome.com/multidevice/android/intents#syntax - var BROWSER_FALLBACK_URL = ';S.browser_fallback_url='; - var indexStart = intentUrl.indexOf(BROWSER_FALLBACK_URL); - if (indexStart === -1) { - return; - } - indexStart += BROWSER_FALLBACK_URL.length; - var indexEnd = intentUrl.indexOf(';', indexStart); - indexEnd = indexEnd === -1 ? intentUrl.length : indexEnd; - - var url = decodeURIComponent(intentUrl.substring(indexStart, indexEnd)); - var realUrl = getRealLinkFromGoogleUrl(newURL(url)); - if (!realUrl) { - return; - } - return intentUrl.substring(0, indexStart) + - encodeURIComponent(realUrl) + - intentUrl.substring(indexEnd); -} - -/** - * Intercept the .href setter in the page so that the page can never change the - * URL to a tracking URL. Just intercepting mousedown/touchstart is not enough - * because e.g. on Google Maps, the page rewrites the URL in the contextmenu - * event at the bubbling event stage and then stops the event propagation. So - * there is no event-driven way to fix the URL. The DOMAttrModified event could - * be used, but the event is deprecated, so not a viable long-term solution. - */ -function setupAggresiveUglyLinkPreventer() { - // This content script runs as document_start, so we can have some assurance - // that the methods in the page are reliable. - var s = document.createElement('script'); - if (getScriptCspNonce()) { - s.setAttribute('nonce', scriptCspNonce); - } else if (document.readyState !== 'complete' && needsCspNonce) { - // In Firefox, a page's CSP is enforced for content scripts, so we need - // to wait for the document to be loaded (we may be at document_start) - // and find a fitting CSP nonce. - findScriptCspNonce(setupAggresiveUglyLinkPreventer); - return; - } - s.textContent = '(' + function(getRealLinkFromGoogleUrl) { - var proto = HTMLAnchorElement.prototype; - // The link target can be changed in many ways, but let's only consider - // the .href attribute since it's probably the only used setter. - var hrefProp = Object.getOwnPropertyDescriptor(proto, 'href'); - var hrefGet = Function.prototype.call.bind(hrefProp.get); - var hrefSet = Function.prototype.call.bind(hrefProp.set); - - Object.defineProperty(proto, 'href', { - configurable: true, - enumerable: true, - get() { - return hrefGet(this); - }, - set(v) { - hrefSet(this, v); - try { - v = getRealLinkFromGoogleUrl(this); - if (v) { - hrefSet(this, v); - } - } catch (e) { - // Not expected to happen, but don't break the setter if for - // some reason the (hostile) page broke the link APIs. - } - updateReferrerPolicy(this); - }, - }); - function replaceAMethod(methodName, methodFunc) { - // Overwrite the methods without triggering setters, because that - // may inadvertently overwrite the prototype, as observed in - // https://github.com/Rob--W/dont-track-me-google/issues/52#issuecomment-1596207655 - Object.defineProperty(proto, methodName, { - configurable: true, - // All methods that we are overriding are not part of - // HTMLAnchorElement.prototype, but inherit. - enumerable: false, - writable: true, - value: methodFunc, - }); - } - - // proto inherits Element.prototype.setAttribute: - var setAttribute = Function.prototype.call.bind(proto.setAttribute); - replaceAMethod('setAttribute', function(name, value) { - // Attribute names are not case-sensitive, but weird capitalizations - // are unlikely, so only check all-lowercase and all-uppercase. - if (name === 'href' || name === 'HREF') { - this.href = value; - } else { - setAttribute(this, name, value); - } - }); - - // proto inherits EventTarget.prototype.dispatchEvent: - var aDispatchEvent = Function.prototype.apply.bind(proto.dispatchEvent); - replaceAMethod('dispatchEvent', function() { - updateReferrerPolicy(this); - return aDispatchEvent(this, arguments); - }); - - // proto inherits HTMLElement.prototype.click: - var aClick = Function.prototype.apply.bind(proto.click); - replaceAMethod('click', function() { - updateReferrerPolicy(this); - return aClick(this, arguments); - }); - - var rpProp = Object.getOwnPropertyDescriptor(proto, 'referrerPolicy'); - var rpGet = Function.prototype.call.bind(rpProp.get); - var rpSet = Function.prototype.call.bind(rpProp.set); - - var currentScript = document.currentScript; - var getReferrerPolicy = Object.getOwnPropertyDescriptor( - HTMLScriptElement.prototype, - 'referrerPolicy' - ).get.bind(currentScript); - - function updateReferrerPolicy(a) { - try { - if (rpGet(a) === 'no-referrer') { - // "no-referrer" is more privacy-friendly than "origin". - return; - } - var referrerPolicy = getReferrerPolicy(); - if (referrerPolicy) { - rpSet(a, referrerPolicy); - } - } catch (e) { - // Not expected to happen, but don't break callers if it happens - // anyway. - } - } - currentScript.dataset.jsEnabled = 1; - } + ')(' + getRealLinkFromGoogleUrl + ');'; - callImmediatelyAndOnPreferenceUpdate(function forceNoReferrerChanged() { - // Send the desired referrerPolicy value to the injected script. - s.referrerPolicy = getReferrerPolicy(); - }); - (document.head || document.documentElement).appendChild(s); - s.remove(); - if (!s.dataset.jsEnabled) { - cleanLinksWhenJsIsDisabled(); - if (!needsCspNonce) { - needsCspNonce = true; - // This is not Firefox, but the script was blocked. Perhaps a CSP - // nonce is needed anyway. - findScriptCspNonce(function() { - if (scriptCspNonce) { - setupAggresiveUglyLinkPreventer(); - } - }); - } - } else { - // Scripts enabled (not blocked by CSP), run other inline scripts. - blockTrackingBeacons(); - overwriteWindowOpen(); - - if (location.hostname === 'docs.google.com') { - // Google Docs have simple non-JS interfaces where the ugly links - // are hard-coded in the HTML. Remove them (#51). - // https://docs.google.com/document/d/.../mobilebasic - // https://docs.google.com/spreadsheets/d/.../htmlview - cleanLinksWhenJsIsDisabled(); - } - } -} - -// Block sendBeacon requests with destination /gen_204, because Google -// asynchronously sends beacon requests in response to mouse events on links: -// https://github.com/Rob--W/dont-track-me-google/issues/20 -// -// This implementation also blocks other forms of tracking via gen_204 as a side -// effect. That is not fully intentional, but given the lack of obvious ways to -// discern such link-tracking events from others, I will block all of them. -function blockTrackingBeacons() { - var s = document.createElement('script'); - if (getScriptCspNonce()) { - s.setAttribute('nonce', scriptCspNonce); - } - s.textContent = '(' + function() { - var navProto = window.Navigator.prototype; - var navProtoSendBeacon = navProto.sendBeacon; - if (!navProtoSendBeacon) { - return; - } - var sendBeacon = Function.prototype.apply.bind(navProtoSendBeacon); - - // Blocks the following: - // gen_204 - // /gen_204 - // https://www.google.com/gen_204 - var isTrackingUrl = RegExp.prototype.test.bind( - /^(?:(?:https?:\/\/[^\/]+)?\/)?gen_204(?:[?#]|$)/); - - navProto.sendBeacon = function(url, data) { - if (isTrackingUrl(url) && isNoPingEnabled()) { - // Lie that the data has been transmitted to avoid fallbacks. - return true; - } - return sendBeacon(this, arguments); - }; - - var currentScript = document.currentScript; - var getElementId = Object.getOwnPropertyDescriptor( - Element.prototype, - 'id' - ).get.bind(currentScript); - function isNoPingEnabled() { - try { - return getElementId() !== '_dtmg_do_not_touch_ping'; - } catch (e) { - return true; - } - } - } + ')();'; - callImmediatelyAndOnPreferenceUpdate(function nopingChanged() { - // Send the noping value to the injected script. The "id" property is - // mirrored and can have an arbitrary (string) value, so we use that: - s.id = noping ? '' : '_dtmg_do_not_touch_ping'; - }); - (document.head || document.documentElement).appendChild(s); - s.remove(); -} - -// Google sometimes uses window.open() to open ugly links. -// https://github.com/Rob--W/dont-track-me-google/issues/18 -// https://github.com/Rob--W/dont-track-me-google/issues/41 -function overwriteWindowOpen() { - var s = document.createElement('script'); - if (getScriptCspNonce()) { - s.setAttribute('nonce', scriptCspNonce); - } - s.textContent = '(' + function() { - var open = window.open; - window.open = function(url, windowName, windowFeatures) { - var isBlankUrl = !url || url === "about:blank"; - try { - if (!isBlankUrl) { - var a = document.createElement('a'); - // Triggers getRealLinkFromGoogleUrl via the href setter in - // setupAggresiveUglyLinkPreventer. - a.href = url; - url = a.href; - // The origin check exists to avoid adding "noreferrer" to - // same-origin popups. That implies noopener and causes - // https://github.com/Rob--W/dont-track-me-google/issues/43 - // And allow any Google domain to support auth popups: - // https://github.com/Rob--W/dont-track-me-google/issues/45 - // And don't bother editing the list if it already contains - // "opener" (it would be disabled by "noreferrer"). - if (a.referrerPolicy && a.origin !== location.origin && - !/\.google\.([a-z]+)$/.test(a.hostname) && - !/\bopener|noreferrer/.test(windowFeatures)) { - if (windowFeatures) { - windowFeatures += ','; - } else { - windowFeatures = ''; - } - windowFeatures += 'noreferrer'; - } - } - } catch (e) { - // Not expected to happen, but don't break callers if it does. - } - var win = open(url, windowName, windowFeatures); - try { - if (isBlankUrl && win) { - // In Google Docs, sometimes a blank document is opened, - // and document.write is used to insert a redirector. - // https://github.com/Rob--W/dont-track-me-google/issues/41 - var doc = win.document; - var docWrite = win.Function.prototype.call.bind(doc.write); - doc.write = function(markup) { - try { - markup = fixupDocMarkup(markup); - } catch (e) { - // Not expected, but don't break callers otherwise. - } - return docWrite(this, markup); - }; - } - } catch (e) { - // Not expected to happen, but don't break callers if it does. - } - return win; - }; - function fixupDocMarkup(html) { - html = html || ''; - html += ''; - return html.replace( - /]*http-equiv=(["']?)refresh\1[^>]*>/i, - function(m) { - var doc = new DOMParser().parseFromString(m, 'text/html'); - var meta = doc.querySelector('meta[http-equiv=refresh]'); - return meta && fixupMetaUrl(meta) || m; - }); - } - function fixupMetaUrl(meta) { - var parts = /^(\d*;\s*url=)(.+)$/i.exec(meta.content); - if (!parts) { - return; - } - var metaPrefix = parts[1]; - var url = parts[2]; - var a = document.createElement('a'); - // Triggers getRealLinkFromGoogleUrl via the href setter in - // setupAggresiveUglyLinkPreventer. - a.href = url; - url = a.href; - meta.content = metaPrefix + url; - - var html = meta.outerHTML; - if (a.referrerPolicy) { - // Google appears to already append the no-referrer - // meta tag, but add one just in case it doesn't. - html = '' + html; - } - return html; - } - } + ')();'; - (document.head || document.documentElement).appendChild(s); - s.remove(); -} - -function cleanLinksWhenJsIsDisabled() { - // When JavaScript is disabled, Google sets the "href" attribute's value to - // an ugly URL. Although the link is rewritten on click, we still need to - // rewrite the link even earlier because otherwise the ugly URL is shown in - // the tooltip upon hover. - - if (document.readyState == 'complete') { - cleanAllLinks(); - return; - } - - // When JS is disabled, the links won't change after the document finishes - // loading. Until the DOM has finished loading, use the mouseover event to - // beautify links (the DOMContentLoaded may be delayed on slow networks). - document.addEventListener('mouseover', handleMouseOver); - document.addEventListener('DOMContentLoaded', function() { - document.removeEventListener('mouseover', handleMouseOver); - cleanAllLinks(); - }, {once: true}); - - function cleanAllLinks() { - var as = document.querySelectorAll('a[href]'); - for (var i = 0; i < as.length; ++i) { - var href = getRealLinkFromGoogleUrl(as[i]); - if (href) { - as[i].href = href; - } - } - } - - function handleMouseOver(e) { - var a = e.target; - var href = a.href && getRealLinkFromGoogleUrl(a); - if (href) { - a.href = href; - } - } -} - -function getScriptCspNonce() { - var scripts = document.querySelectorAll('script[nonce]'); - for (var i = 0; i < scripts.length && !scriptCspNonce; ++i) { - scriptCspNonce = scripts[i].nonce; - } - return scriptCspNonce; -} - -function findScriptCspNonce(callback) { - var timer; - function checkDOM() { - if (getScriptCspNonce() || document.readyState === 'complete') { - document.removeEventListener('DOMContentLoaded', checkDOM, true); - if (timer) { - clearTimeout(timer); - } - callback(); - return; - } - timer = setTimeout(checkDOM, 50); - } - document.addEventListener('DOMContentLoaded', checkDOM, true); - checkDOM(); -} - -function newURL(href) { - try { - return new URL(href); - } catch (e) { - var a = document.createElement('a'); - a.href = href; - return a; - } -} diff --git a/home/programs/qutebrowser/greasemonkey/i-dont-care-about-cookies.user.js b/home/programs/qutebrowser/greasemonkey/i-dont-care-about-cookies.user.js deleted file mode 100644 index 025d961..0000000 --- a/home/programs/qutebrowser/greasemonkey/i-dont-care-about-cookies.user.js +++ /dev/null @@ -1,984 +0,0 @@ -// ==UserScript== -// @name I don't care about cookies -// @name:vi Tôi không quan tâm về cookie -// @name:zh-CN 我不关心cookie -// @name:zh-TW 我不關心cookie -// @name:ja クッキーについては気にしない -// @name:ru Я не забочусь о куки -// @namespace http://tampermonkey.net/ -// @version 2025.01.03.2 -// @description Remove cookie warnings from almost all websites! Auto accept cookies and remove annoying cookie popups -// @description:vi Loại bỏ cảnh báo cookie từ hầu hết các trang web! Tự động chấp nhận cookie và xóa các popup cookie phiền phức -// @description:zh-CN 自动接受cookie并移除烦人的cookie弹窗 -// @description:zh-TW 自動接受cookie並移除煩人的cookie彈窗 -// @description:ru Автоматическое принятие cookie и удаление надоедливых всплывающих окон -// @description:ja 自動承認cookieと迷惑なポップアップを削除 -// @author Yuusei -// @match *://*/* -// @grant none -// @icon https://lh3.googleusercontent.com/sCLTYpGX0VcVootQ_XaFQ9saRIhVWu79ngSzY5eTZ5evRpJ_Q27OdvxA4RrOoZXP7Q4enFh-u6VhxObcJLfARw1g=s60 -// @compatible chrome -// @compatible edge -// @compatible firefox -// @compatible safari -// @run-at document-start -// @license gpl-3.0-only -// @downloadURL https://update.greasyfork.org/scripts/522645/I%20don%27t%20care%20about%20cookies.user.js -// @updateURL https://update.greasyfork.org/scripts/522645/I%20don%27t%20care%20about%20cookies.meta.js -// ==/UserScript== - -(function () { - 'use strict'; - - const CONSENT_TEXTS = { - en: ['accept', 'accept all', 'agree', 'continue', 'got it', 'reject all', 'decline', 'necessary only', 'required only', 'manage', 'customize'], - vi: ['chấp nhận', 'chấp nhận tất cả', 'đồng ý', 'tiếp tục', 'từ chối tất cả', 'từ chối', 'chỉ cần thiết', 'tùy chỉnh', 'quản lý', 'cho phép', 'đồng ý và tiếp tục', 'tôi đồng ý', 'xác nhận', 'tôi chấp nhận', 'đồng ý tất cả', 'chấp nhận và tiếp tục', 'cho phép tất cả'], - zh: ['接受', '接受全部', '同意', '继续', '拒绝全部', '拒绝', '仅必要', '管理', '自定义'], - ru: ['принять', 'принять все', 'согласен', 'продолжить', 'отклонить все', 'отклонить', 'только необходимые', 'настроить', 'управлять'], - ja: ['承認', '同意', '続ける', 'すべて拒否', '拒否', '必要のみ', 'カスタマイズ', '管理'], - de: ['akzeptieren', 'einverstanden', 'fortfahren', 'alle ablehnen', 'ablehnen', 'nur notwendige', 'anpassen', 'verwalten'], - fr: ['accepter', 'accepter tout', 'accepte', 'continuer', 'tout refuser', 'refuser', 'uniquement nécessaire', 'personnaliser', 'gérer'], - es: ['aceptar', 'acepto todo', 'acepto', 'continuar', 'rechazar todo', 'rechazar', 'solo necesario', 'personalizar', 'gestionar'], - it: ['accetta', 'accetto tutto', 'accetto', 'continua', 'rifiuta tutto', 'rifiuta', 'solo necessari', 'personalizza', 'gestisci'], - pl: ['akceptuj', 'akceptuj wszystko', 'zgadzam się', 'kontynuuj', 'odrzuć wszystko', 'odrzuć', 'tylko niezbędne', 'dostosuj', 'zarządzaj'], - nl: ['accepteren', 'accepteren', 'doorgaan', 'alles weigeren', 'weigeren', 'alleen noodzakelijk', 'aanpassen', 'beheren'], - ko: ['동의', '모두 동의', '계속하기', '모두 거부', '거부', '필수만', '설정', '관리'], - th: ['ยอมรับ', 'ยอมรับทั้งหมด', 'ตกลง', 'ปฏิเสธทั้งหมด', 'ปฏิเสธ', 'จำเป็นเท่านั้น', 'ตั้งค่า', 'จัดการ'], - id: ['setuju', 'setuju semua', 'lanjutkan', 'tolak semua', 'tolak', 'wajib saja', 'pengaturan', 'kelola'], - ms: ['terima', 'terima semua', 'teruskan', 'tolak semua', 'tolak', 'perlu sahaja', 'tetapan', 'urus'], - pt: ['aceitar', 'aceitar tudo', 'continuar', 'rejeitar tudo', 'rejeitar', 'necessário', 'configurar', 'gerir'], - sv: ['godkänn', 'godkänn alla', 'fortsätt', 'neka alla', 'neka', 'nödvändiga', 'inställningar', 'hantera'], - da: ['accepter', 'accepter alle', 'fortsæt', 'afvis alle', 'afvis', 'nødvendige', 'indstillinger', 'administrer'], - fi: ['hyväksy', 'hyväksy kaikki', 'jatka', 'hylkää kaikki', 'hylkää', 'välttämätön', 'asetukset', 'hallitse'], - 'zh-CN': ['接受', '接受全部', '同意', '继续', '我同意', '确定', '确认', '知道了', '好的', '拒绝全部', '拒绝', '仅必要', '设置', '自定义', '管理', '保存设置', '允许', '允许全部', '接受并继续', '同意并继续', '保存并继续'], - 'zh-TW': ['接受', '接受全部', '同意', '繼續', '我同意', '確定', '確認', '知道了', '好的', '拒絕全部', '拒絕', '僅必要', '設置', '自定義', '管理', '保存設置', '允許', '允許全部', '接受並繼續', '同意並繼續', '保存並繼續'], - ko: ['동의', '모두 동의', '수락', '계속하기', '확인', '거부', '거부하기', '필수만', '설정', '관리', '저장', '허용', '모두 허용'], - th: ['ยอมรับ', 'ยอมรับทั้งหมด', 'ตกลง', 'ดำเนินการต่อ', 'ปฏิเสธ', 'ปฏิเสธทั้งหมด', 'จำเป็นเท่านั้น', 'ตั้งค่า', 'จัดการ', 'บันทึก', 'อนุญาต', 'อนุญาตทั้งหมด'], - id: ['terima', 'terima semua', 'setuju', 'lanjutkan', 'tolak', 'tolak semua', 'wajib saja', 'pengaturan', 'kelola', 'simpan', 'izinkan', 'izinkan semua'], - ms: ['terima', 'terima semua', 'setuju', 'teruskan', 'tolak', 'tolak semua', 'perlu sahaja', 'tetapan', 'urus', 'simpan', 'benarkan', 'benarkan semua'], - }; - - function matchesConsentText(element) { - const text = element.textContent.toLowerCase(); - const lang = document.documentElement.lang || 'en'; - const texts = CONSENT_TEXTS[lang.split('-')[0]] || CONSENT_TEXTS['en']; - return texts.some(t => text.includes(t)); - } - - // Utility functions - function _sl(selector, container) { - return (container || document).querySelector(selector); - } - - function _id(id) { - return document.getElementById(id); - } - - function _ev(selector, container, full) { - return document.evaluate((typeof full == 'undefined' ? '//' : '') + selector, container || document, null, XPathResult.ANY_TYPE, null).iterateNext(); - } - - function _if(condition, ...selectors) { - return _sl(condition) ? _chain(...selectors) : false; - } - - function _if_else(condition, if_selectors, else_selectors) { - if (_sl(condition)) return _chain(...if_selectors); - - return _chain(...else_selectors); - } - - function _chain(...selectors) { - let elements, - l = selectors.length; - let flagUnique = false, - flagOptional = false, - flagAllMatches = false; - - for (let i = currentChainElement; i < l; i++) { - if (/^FLAG\:/.test(selectors[i])) { - selectors[i] - .split(':')[1] - .split(',') - .forEach(function (flag) { - if (flag == 'UNIQUE') flagUnique = true; - else if (flag == 'OPTIONAL') flagOptional = true; - else if (flag == 'REQUIRED') flagOptional = false; - else if (flag == 'ALL-MATCHES') flagAllMatches = true; - else if (flag == 'SINGLE-MATCH') flagAllMatches = false; - }); - - continue; - } - - if (flagUnique) selectors[i] += selectors[i].startsWith('//') ? '[not(contains(@class, "' + classname + '"))]' : ':not(.' + classname + ')'; - - if (i == l - 1) return selectors[i]; - - elements = _sl(selectors[i], false, flagAllMatches); - - if (!flagAllMatches) elements = elements ? [elements] : []; - - if (!elements.length) { - if (flagOptional) { - currentChainElement++; - continue; - } - - return false; - } - - currentChainElement++; - - elements.forEach(function (element) { - if (flagUnique) element.classList.add(classname); - - if (element.nodeName == 'OPTION') element.selected = true; - else element.click(); - }); - } - - return false; - } - - function getItem(hostname) { - switch (hostname) { - case 'youtube.com': - case 'www.youtube.com': - return { strict: true, key: 'CONSENT', value: 'PENDING+999' }; - - case 'google.com': - case 'www.google.com': - return { strict: true, key: 'CONSENT', value: 'YES+' }; - - case 'twitter.com': - case 'www.twitter.com': - return { strict: false, key: 'twtr_cookie_consent', value: '1' }; - - case 'pepephone.com': - case 'lyricsbox.com': - return { strict: true, key: 'cookieconsent', value: '1111' }; - - case 'kontaktbazar.at': - case 'hoernews.de': - return { strict: false, key: 'cookieconsent_status', value: 'dismiss' }; - - case 'vodafoneziggo.nl': - return { strict: false, key: 'cookies-accepted', value: 'true' }; - case 'frankfurt.de': - return { strict: false, key: 'cookieAccepted', value: 'needed---piwik' }; - case 'hackerrank.com': - return { strict: false, key: 'show_cookie_banner', value: 'false' }; - - case 'facebook.com': - case 'www.facebook.com': - return { strict: true, key: 'datr', value: 'accepted' }; - - case 'instagram.com': - case 'www.instagram.com': - return { strict: true, key: 'ig_did', value: 'accepted' }; - - case 'linkedin.com': - case 'www.linkedin.com': - return { strict: false, key: 'lidc', value: 'accepted' }; - - case 'reddit.com': - case 'www.reddit.com': - return { strict: false, key: 'eu_cookie', value: '{"opted":true}' }; - - case 'tiktok.com': - case 'www.tiktok.com': - return { strict: true, key: 'tt_webid', value: 'accepted' }; - - case 'netflix.com': - case 'www.netflix.com': - return { strict: false, key: 'netflix-cookie-consent', value: 'accepted' }; - - case 'spotify.com': - case 'www.spotify.com': - return { strict: false, key: 'sp_dc', value: 'accepted' }; - - case 'amazon.com': - case 'www.amazon.com': - return { strict: false, key: 'amazon-cookie-consent', value: 'accepted' }; - - case 'pinterest.com': - case 'www.pinterest.com': - return { strict: false, key: '_pinterest_sess', value: 'accepted' }; - - case 'twitch.tv': - case 'www.twitch.tv': - return { strict: false, key: 'twitch_cookie_consent', value: 'accepted' }; - - case 'github.com': - case 'www.github.com': - return { strict: false, key: '_gh_sess', value: 'accepted' }; - - case 'medium.com': - case 'www.medium.com': - return { strict: false, key: 'medium_cookie_consent', value: 'accepted' }; - - case 'quora.com': - case 'www.quora.com': - return { strict: false, key: 'm-b', value: 'accepted' }; - - case 'stackoverflow.com': - case 'www.stackoverflow.com': - return { strict: false, key: 'acct', value: 'accepted' }; - - case 'microsoft.com': - case 'www.microsoft.com': - return { strict: false, key: 'MUID', value: 'accepted' }; - - case 'apple.com': - case 'www.apple.com': - return { strict: false, key: 'geo', value: 'accepted' }; - - case 'dropbox.com': - case 'www.dropbox.com': - return { strict: false, key: 'dbx-consent', value: 'accepted' }; - - case 'booking.com': - case 'www.booking.com': - return { strict: false, key: 'bkng_consent', value: 'accepted' }; - - case 'vnexpress.net': - case 'www.vnexpress.net': - return { strict: false, key: 'vnexpress_cookie_consent', value: 'accepted' }; - - case 'thanhnien.vn': - case 'www.thanhnien.vn': - return { strict: false, key: 'thanhnien_cookie', value: 'accepted' }; - - case 'tuoitre.vn': - case 'www.tuoitre.vn': - return { strict: false, key: 'tuoitre_cookie', value: 'accepted' }; - - case 'tiki.vn': - case 'www.tiki.vn': - return { strict: false, key: 'tiki_cookie', value: 'accepted' }; - - case 'shopee.vn': - case 'www.shopee.vn': - return { strict: false, key: 'shopee_cookie', value: 'accepted' }; - - case 'lazada.vn': - case 'www.lazada.vn': - return { strict: false, key: 'lzd_cookie', value: 'accepted' }; - - case 'sendo.vn': - case 'www.sendo.vn': - return { strict: false, key: 'sendo_cookie', value: 'accepted' }; - - case 'thegioididong.com': - case 'www.thegioididong.com': - return { strict: false, key: 'tgdd_cookie', value: 'accepted' }; - - case 'fptshop.com.vn': - case 'www.fptshop.com.vn': - return { strict: false, key: 'fpt_cookie', value: 'accepted' }; - - case 'cellphones.com.vn': - case 'www.cellphones.com.vn': - return { strict: false, key: 'cps_cookie', value: 'accepted' }; - - case 'aliexpress.com': - case 'www.aliexpress.com': - return { strict: false, key: 'aep_usuc_f', value: 'accepted' }; - - case 'ebay.com': - case 'www.ebay.com': - return { strict: false, key: 'ebay_cookie_consent', value: 'accepted' }; - - case 'coursera.org': - case 'www.coursera.org': - return { strict: false, key: 'coursera_cookie', value: 'accepted' }; - - case 'udemy.com': - case 'www.udemy.com': - return { strict: false, key: 'ud_cookie', value: 'accepted' }; - - case 'bachhoaxanh.com': - case 'www.bachhoaxanh.com': - return { strict: false, key: 'bhx_cookie', value: 'accepted' }; - - case 'dienmayxanh.com': - case 'www.dienmayxanh.com': - return { strict: false, key: 'dmx_cookie', value: 'accepted' }; - - case 'nguyenkim.com': - case 'www.nguyenkim.com': - return { strict: false, key: 'nk_cookie', value: 'accepted' }; - - case 'dantri.com.vn': - case 'www.dantri.com.vn': - return { strict: false, key: 'dantri_cookie', value: 'accepted' }; - - case 'vietnamnet.vn': - case 'www.vietnamnet.vn': - return { strict: false, key: 'vietnamnet_cookie', value: 'accepted' }; - - case '24h.com.vn': - case 'www.24h.com.vn': - return { strict: false, key: '24h_cookie', value: 'accepted' }; - - case 'vietcombank.com.vn': - case 'www.vietcombank.com.vn': - return { strict: false, key: 'vcb_cookie', value: 'accepted' }; - - case 'techcombank.com.vn': - case 'www.techcombank.com.vn': - return { strict: false, key: 'tcb_cookie', value: 'accepted' }; - - case 'mbbank.com.vn': - case 'www.mbbank.com.vn': - return { strict: false, key: 'mb_cookie', value: 'accepted' }; - - case 'amazon.co.jp': - case 'www.amazon.co.jp': - return { strict: false, key: 'amazon_jp_cookie', value: 'accepted' }; - - case 'rakuten.co.jp': - case 'www.rakuten.co.jp': - return { strict: false, key: 'rakuten_jp_cookie', value: 'accepted' }; - - case 'taobao.com': - case 'www.taobao.com': - return { strict: false, key: 'taobao_cookie', value: 'accepted' }; - - case 'line.me': - case 'www.line.me': - return { strict: false, key: 'line_cookie', value: 'accepted' }; - - case 'weibo.com': - case 'www.weibo.com': - return { strict: false, key: 'weibo_cookie', value: 'accepted' }; - - case 'kakao.com': - case 'www.kakao.com': - return { strict: false, key: 'kakao_cookie', value: 'accepted' }; - - case 'yahoo.co.jp': - case 'www.yahoo.co.jp': - return { strict: false, key: 'yahoo_jp_cookie', value: 'accepted' }; - - case 'naver.com': - case 'www.naver.com': - return { strict: false, key: 'naver_cookie', value: 'accepted' }; - - case 'baidu.com': - case 'www.baidu.com': - return { strict: false, key: 'baidu_cookie', value: 'accepted' }; - - case 'zalando.com': - case 'www.zalando.com': - return { strict: false, key: 'zalando_cookie', value: 'accepted' }; - - case 'asos.com': - case 'www.asos.com': - return { strict: false, key: 'asos_cookie', value: 'accepted' }; - - case 'zara.com': - case 'www.zara.com': - return { strict: false, key: 'zara_cookie', value: 'accepted' }; - - case 'tmall.com': - case 'www.tmall.com': - return { strict: false, key: 'tmall_cookie', value: 'accepted' }; - - case 'jd.com': - case 'www.jd.com': - return { strict: false, key: 'jd_cookie', value: 'accepted' }; - - case 'sina.com.cn': - case 'www.sina.com.cn': - return { strict: false, key: 'sina_cookie', value: 'accepted' }; - - case 'qq.com': - case 'www.qq.com': - return { strict: false, key: 'qq_cookie', value: 'accepted' }; - - case '163.com': - case 'www.163.com': - return { strict: false, key: 'netease_cookie', value: 'accepted' }; - - case 'sohu.com': - case 'www.sohu.com': - return { strict: false, key: 'sohu_cookie', value: 'accepted' }; - - case 'bilibili.com': - case 'www.bilibili.com': - return { strict: false, key: 'bilibili_cookie', value: 'accepted' }; - } - - const parts = hostname.split('.'); - if (parts.length > 2) { - parts.shift(); - return getItem(parts.join('.')); - } - return false; - } - - function _parent(element) { - if (element && element.parentNode) return element.parentNode; - return false; - } - - function _iframe(iframe_selector, selector) { - var e = _sl(iframe_selector); - return e ? _sl(selector, e.contentDocument || e.contentWindow.contentDocument) : e; - } - - // Cookie consent handling logic - let searchPairs = { - '.vn-cookie-banner': ['.vn-cookie-banner__reject', '.vn-cookie-banner__customize'], - '.vn-cookie-notice': ['.vn-cookie-notice__reject', '.vn-cookie-notice__settings'], - '.vn-cookie-consent': ['.vn-cookie-consent__reject', '.vn-cookie-consent__customize'], - '.vn-cookie-popup': ['.vn-cookie-popup__reject', '.vn-cookie-popup__settings'], - - '.shopee-cookie-banner': ['.shopee-cookie-banner__reject', '.shopee-cookie-banner__settings'], - '.lazada-cookie-notice': ['.lazada-cookie-notice__reject', '.lazada-cookie-notice__settings'], - '.tiki-cookie-popup': ['.tiki-cookie-popup__reject', '.tiki-cookie-popup__settings'], - '.sendo-cookie-consent': ['.sendo-cookie-consent__reject', '.sendo-cookie-consent__settings'], - - '.vnexpress-cookie': ['.vnexpress-cookie__reject', '.vnexpress-cookie__settings'], - '.tuoitre-cookie': ['.tuoitre-cookie__reject', '.tuoitre-cookie__settings'], - '.thanhnien-cookie': ['.thanhnien-cookie__reject', '.thanhnien-cookie__settings'], - '.dantri-cookie': ['.dantri-cookie__reject', '.dantri-cookie__settings'], - - '.tgdd-cookie-notice': ['.tgdd-cookie-notice__reject', '.tgdd-cookie-notice__settings'], - '.fpt-cookie-popup': ['.fpt-cookie-popup__reject', '.fpt-cookie-popup__settings'], - '.cps-cookie-consent': ['.cps-cookie-consent__reject', '.cps-cookie-consent__settings'], - - '.vcb-cookie-notice': ['.vcb-cookie-notice__reject', '.vcb-cookie-notice__settings'], - '.tcb-cookie-popup': ['.tcb-cookie-popup__reject', '.tcb-cookie-popup__settings'], - '.mb-cookie-consent': ['.mb-cookie-consent__reject', '.mb-cookie-consent__settings'], - - 'div[class*="consent"]': ['button[class*="reject"]', 'button[class*="decline"]', 'button[class*="settings"]'], - 'div[class*="notice"]': ['button[class*="reject"]', 'button[class*="decline"]', 'button[class*="settings"]'], - 'div[class*="popup"]': ['button[class*="reject"]', 'button[class*="decline"]', 'button[class*="settings"]'], - 'div[class*="banner"]': ['button[class*="reject"]', 'button[class*="decline"]', 'button[class*="settings"]'], - }; - - let searchGroups = [ - '.qc-cmp2-summary-buttons button[mode="secondary"],\ - .qc-cmp2-buttons-desktop > button:first-child,\ - #didomi-popup .didomi-button-highlight:not([class*="paywall"]):not([class*="disagree"]),\ - #rgpd_video .rgpd-mask a[data-rgpd-consent],\ - .cli-barmodal-open #wt-cli-privacy-save-btn,\ - .js--modal[style*="block"] .cookie-permission--accept-button,\ - .gdpr-modal-rider .btn-cookieaccept,\ - .js-cookiewall #sel-test-accept-cookies-button,\ - #mpo[style*="block"] .submit.modal-privacy__btn[onclick*="privacyframe.accept"],\ - .lightbox--cookie-consent .btn-cta,\ - .lightbox.cookie-consent .cookie-consent-button-decline,\ - .js-modal-gdpr.is-active .btn[data-level="2"],\ - #CybotCookiebotDialogBodyLevelButtonLevelOptinAllowallSelection,\ - #cookieNotificationModal.in .btn.accept-cookie,\ - .has-ccwindow .cc-compliance .cc-dismiss,\ - .ds2-cookie-disclaimer--slidedown .ds2-cookie-disclaimer-js--submit,\ - #mdlCookieCompliance.in .cookieClose,\ - #cookieModal.in .js-acceptDefaultCookie,\ - .c-cookiebutton .c-cookiebutton__close,\ - #normativa_cookies.in .btn,\ - #cookiewall.in .btn-primary,\ - .outerCookieBar .EuCookieBar__cookieButton,\ - #TOS-POPUP .rhododendron-popup__button--agree,\ - #cookie-wall #accept-cookies,\ - #popup-wrapper .button[href*="/cookies.consent.php"],\ - .reveal.cookies[style*="block"] button[click*="aceptaCookies"],\ - .mnd-cookie-modal[style*="block"] .btn.is--primary,\ - .cookieHandler.cookieHandler--modalOpen #acceptAllCookies,\ - .gdpr-modal--active .btn--primary,\ - #dpi-banner:not(.hidden) #btn-agree-cookie,\ - .gh-banner.gh-banner-active #gh-cookiebanner-close,\ - #mrktpref.notification-bar .btn-success,\ - #PopinGDPRCookie[style*="block"] .jsbd-popin-ok,\ - #modal-rodo.in .btn-primary,\ - .cookie-compliance-modal.in .btn-primary,\ - .cookieconsent.show .btn[data-dm*="accept"],\ - .cookie-wall-modal.in .btn.ja,\ - #modal-consent.in .modal-consent-accept,\ - .rodo #cookies.in .btn-primary,\ - .js-cookie-alert.in .js-cookie-alert-accept,\ - #modal_gdpr_intro_popup.in #gdpr-modal-btn-ok-agree,\ - #consentButtonContainer > button[onclick*="sendAndRedirect"],\ - #eu-consent[style*="block"] .btn.yes,\ - .modal--gdpr.is-open .js-gdpr-consent,\ - #cookiePopupModal.in .cookiepopup-agreed,\ - .polityka-cookie-rodo[style*="block"] .button-zgoda,\ - .ui-dialog.consent-modal[style*="block"] .js-btn-agree,\ - #up-cookie.active .button[onclick*="setCookiePreference"],\ - .RodoModal.in .close,\ - .consent-popup form[action*="cookie-consent"] .consent-popup__button,\ - #consent form[action*="cookie-consent"] .one-btn,\ - #cookiewall-wrapper .button[href*="accept"],\ - #cookieChoiceButtonAccept,\ - .mod-cookie-consent[style*="block"] .btn-all-cookies,\ - .c-layer--consent .layer-button--accept,\ - .button.button-ok[onclick*="acceptAVG"],\ - #meredithGdprConsentFormButton,\ - #advanced-cookie-modal.in .cookie-accept,\ - .show-modal .cookie-settings-manager-container .initial-dialog .js-accept-button,\ - .cookie-settings-manager-container .initial-dialog[style*="block"] .js-accept-button,\ - .gdprLightbox[data-module="gdprLightbox"] ._type_gdpr_agree,\ - .cookie.showa #Row1_Column1_Cell1_CookieSettings_AdvancedSaveAccept,\ - #core-cookie-container[style*="block"] .btn--agree,\ - .cookie-consent-modal._show .action-primary,\ - #dsgvoModal.show #dsvgo-banner__button,\ - .basicLightbox--visible #accept-all-gdpr,\ - #gdpr-modal.in .gdpr-modal__btn--accept,\ - .cookiehint .btn.cookieagree,\ - #cookiealert .modal.in .btn[href*="accept"],\ - #lml-data-consent-accept,\ - #CBCookieMsg.in .btn[onclick*="approveCookies"],\ - #cookiewall-container .button[name="submit"],\ - #cookie_disclaimer.in .cookie_disclaimer_button,\ - .m-cookie.iziModal[style*="block"] .m-cookie__save2.button,\ - kamino-cookie-policy .mat-raised-button,\ - #surbma-gpga-modal[style*="block"] button,\ - #GDPR.overlayBox .menuButton,\ - #cookiebar .cookie-selection-button.accept,\ - .modal.in .btn.close-modal-cookie,\ - #consent-module[style*="block"] #consent-module-text-button,\ - .modal #consentButton,\ - #consent-modal[style*="block"] .lm_modal__modal__content__body__buttons__ok,\ - .cookiesOverlay3Box #cookiesConsentOK,\ - .bemCookieOverlay--activePopup .bemCookieOverlay__btn--save,\ - #root main ~ div [data-gi-selector="reject-all-cookies"] ~ div a,\ - .cookies-management .cookies-deny,\ - .offcanvas.is-open .js-offcanvas-cookie-submit,\ - .force--consent.show--consent #cs_save__btn,\ - .force--consent.show--consent #s-sv-bn,\ - #cookieNoticeModal.vrm-reveal[style*="block"] .vrm-reveal__icon--close', - '.cookie-banner .cookie-banner__buttons .cookie-banner__button--reject,\ - .cookie-consent-banner .cookie-consent-banner__reject,\ - .cookie-notification .cookie-notification__buttons .cookie-notification__reject,\ - .cookie-policy-banner .cookie-policy-banner__buttons .cookie-policy-banner__reject,\ - .cookie-warning .cookie-warning__buttons .cookie-warning__reject,\ - .cookie-notice .cookie-notice__buttons .cookie-notice__reject,\ - .cookie-alert .cookie-alert__buttons .cookie-alert__reject,\ - .cookie-popup .cookie-popup__buttons .cookie-popup__reject,\ - .cookie-modal .cookie-modal__buttons .cookie-modal__reject,\ - .cookie-dialog .cookie-dialog__buttons .cookie-dialog__reject,\ - .gdpr-banner .gdpr-banner__buttons .gdpr-banner__reject,\ - .gdpr-modal .gdpr-modal__buttons .gdpr-modal__reject,\ - .gdpr-notice .gdpr-notice__buttons .gdpr-notice__reject,\ - .gdpr-popup .gdpr-popup__buttons .gdpr-popup__reject,\ - .gdpr-dialog .gdpr-dialog__buttons .gdpr-dialog__reject,\ - .privacy-banner .privacy-banner__buttons .privacy-banner__reject,\ - .privacy-modal .privacy-modal__buttons .privacy-modal__reject,\ - .privacy-notice .privacy-notice__buttons .privacy-notice__reject,\ - .privacy-popup .privacy-popup__buttons .privacy-popup__reject,\ - .privacy-dialog .privacy-dialog__buttons .privacy-dialog__reject', - - '.consent-banner .consent-banner__buttons .consent-banner__reject,\ - .consent-modal .consent-modal__buttons .consent-modal__reject,\ - .consent-notice .consent-notice__buttons .consent-notice__reject,\ - .consent-popup .consent-popup__buttons .consent-popup__reject,\ - .consent-dialog .consent-dialog__buttons .consent-dialog__reject,\ - .tracking-banner .tracking-banner__buttons .tracking-banner__reject,\ - .tracking-modal .tracking-modal__buttons .tracking-modal__reject,\ - .tracking-notice .tracking-notice__buttons .tracking-notice__reject,\ - .tracking-popup .tracking-popup__buttons .tracking-popup__reject,\ - .tracking-dialog .tracking-dialog__buttons .tracking-dialog__reject', - - '.cookie-settings-modal .cookie-settings__reject,\ - .cookie-settings-modal .cookie-settings__customize,\ - .cookie-preferences-modal .cookie-preferences__reject,\ - .cookie-preferences-modal .cookie-preferences__customize,\ - .cookie-manager-modal .cookie-manager__reject,\ - .cookie-manager-modal .cookie-manager__customize,\ - .cookie-control-modal .cookie-control__reject,\ - .cookie-control-modal .cookie-control__customize,\ - .cookie-options-modal .cookie-options__reject,\ - .cookie-options-modal .cookie-options__customize', - - '.data-privacy-modal .data-privacy__reject,\ - .data-privacy-modal .data-privacy__customize,\ - .data-protection-modal .data-protection__reject,\ - .data-protection-modal .data-protection__customize,\ - .data-consent-modal .data-consent__reject,\ - .data-consent-modal .data-consent__customize,\ - .data-settings-modal .data-settings__reject,\ - .data-settings-modal .data-settings__customize,\ - .data-preferences-modal .data-preferences__reject,\ - .data-preferences-modal .data-preferences__customize', - - '.privacy-manager-modal .privacy-manager__reject,\ - .privacy-manager-modal .privacy-manager__customize,\ - .privacy-settings-modal .privacy-settings__reject,\ - .privacy-settings-modal .privacy-settings__customize,\ - .privacy-options-modal .privacy-options__reject,\ - .privacy-options-modal .privacy-options__customize,\ - .privacy-control-modal .privacy-control__reject,\ - .privacy-control-modal .privacy-control__customize,\ - .privacy-preferences-modal .privacy-preferences__reject,\ - .privacy-preferences-modal .privacy-preferences__customize', - - '.consent-manager-modal .consent-manager__reject,\ - .consent-manager-modal .consent-manager__customize,\ - .consent-settings-modal .consent-settings__reject,\ - .consent-settings-modal .consent-settings__customize,\ - .consent-options-modal .consent-options__reject,\ - .consent-options-modal .consent-options__customize,\ - .consent-control-modal .consent-control__reject,\ - .consent-control-modal .consent-control__customize,\ - .consent-preferences-modal .consent-preferences__reject,\ - .consent-preferences-modal .consent-preferences__customize', - - '.tracking-manager-modal .tracking-manager__reject,\ - .tracking-manager-modal .tracking-manager__customize,\ - .tracking-settings-modal .tracking-settings__reject,\ - .tracking-settings-modal .tracking-settings__customize,\ - .tracking-options-modal .tracking-options__reject,\ - .tracking-options-modal .tracking-options__customize,\ - .tracking-control-modal .tracking-control__reject,\ - .tracking-control-modal .tracking-control__customize,\ - .tracking-preferences-modal .tracking-preferences__reject,\ - .tracking-preferences-modal .tracking-preferences__customize', - - '.gdpr-manager-modal .gdpr-manager__reject,\ - .gdpr-manager-modal .gdpr-manager__customize,\ - .gdpr-settings-modal .gdpr-settings__reject,\ - .gdpr-settings-modal .gdpr-settings__customize,\ - .gdpr-options-modal .gdpr-options__reject,\ - .gdpr-options-modal .gdpr-options__customize,\ - .gdpr-control-modal .gdpr-control__reject,\ - .gdpr-control-modal .gdpr-control__customize,\ - .gdpr-preferences-modal .gdpr-preferences__reject,\ - .gdpr-preferences-modal .gdpr-preferences__customize', - - 'div[class*="cookie-banner"] button[class*="reject"],\ - div[class*="cookie-banner"] button[class*="decline"],\ - div[class*="cookie-banner"] button[class*="settings"],\ - div[id*="cookie-banner"] button[class*="reject"],\ - div[id*="cookie-banner"] button[class*="decline"],\ - div[id*="cookie-banner"] button[class*="settings"]', - - 'div[class*="cookie-consent"] button[class*="reject"],\ - div[class*="cookie-consent"] button[class*="decline"],\ - div[class*="cookie-consent"] button[class*="settings"],\ - div[id*="cookie-consent"] button[class*="reject"],\ - div[id*="cookie-consent"] button[class*="decline"],\ - div[id*="cookie-consent"] button[class*="settings"]', - - 'div[class*="cookie-notice"] button[class*="reject"],\ - div[class*="cookie-notice"] button[class*="decline"],\ - div[class*="cookie-notice"] button[class*="settings"],\ - div[id*="cookie-notice"] button[class*="reject"],\ - div[id*="cookie-notice"] button[class*="decline"],\ - div[id*="cookie-notice"] button[class*="settings"]', - - '[data-*="cookie"] button[data-*="reject"],\ - [data-*="cookie"] button[data-*="decline"],\ - [data-*="cookie"] button[data-*="settings"],\ - [data-*="gdpr"] button[data-*="reject"],\ - [data-*="gdpr"] button[data-*="decline"],\ - [data-*="gdpr"] button[data-*="settings"]', - - 'div[class*="cookie-layer"] button[class*="reject"],\ - div[class*="cookie-layer"] button[class*="decline"],\ - div[class*="cookie-layer"] button[class*="settings"],\ - div[id*="cookie-layer"] button[class*="reject"],\ - div[id*="cookie-layer"] button[class*="decline"],\ - div[id*="cookie-layer"] button[class*="settings"]', - - 'div[class*="cookie-section"] button[class*="reject"],\ - div[class*="cookie-section"] button[class*="decline"],\ - div[class*="cookie-section"] button[class*="settings"],\ - div[id*="cookie-section"] button[class*="reject"],\ - div[id*="cookie-section"] button[class*="decline"],\ - div[id*="cookie-section"] button[class*="settings"]', - - 'div[class*="cookie-container"] button[class*="reject"],\ - div[class*="cookie-container"] button[class*="decline"],\ - div[class*="cookie-container"] button[class*="settings"],\ - div[id*="cookie-container"] button[class*="reject"],\ - div[id*="cookie-container"] button[class*="decline"],\ - div[id*="cookie-container"] button[class*="settings"]', - - '[data-purpose*="cookie"] button[data-purpose*="reject"],\ - [data-purpose*="cookie"] button[data-purpose*="decline"],\ - [data-purpose*="cookie"] button[data-purpose*="settings"],\ - [data-ref*="cookie"] button[data-ref*="reject"],\ - [data-ref*="cookie"] button[data-ref*="decline"],\ - [data-ref*="cookie"] button[data-ref*="settings"]', - - '[id*="cookie-manager"] button[id*="reject"],\ - [id*="cookie-manager"] button[id*="decline"],\ - [id*="cookie-manager"] button[id*="settings"],\ - [id*="cookie-control"] button[id*="reject"],\ - [id*="cookie-control"] button[id*="decline"],\ - [id*="cookie-control"] button[id*="settings"]', - - 'div[class*="vn-cookie"] button[class*="reject"],\ - div[class*="vn-cookie"] button[class*="decline"],\ - div[class*="vn-cookie"] button[class*="settings"],\ - div[id*="vn-cookie"] button[class*="reject"],\ - div[id*="vn-cookie"] button[class*="decline"],\ - div[id*="vn-cookie"] button[class*="settings"]', - - 'div[class*="ecommerce"] button[class*="cookie-reject"],\ - div[class*="ecommerce"] button[class*="cookie-decline"],\ - div[class*="ecommerce"] button[class*="cookie-settings"],\ - div[id*="ecommerce"] button[class*="cookie-reject"],\ - div[id*="ecommerce"] button[class*="cookie-decline"],\ - div[id*="ecommerce"] button[class*="cookie-settings"]', - - 'div[class*="news"] button[class*="cookie-reject"],\ - div[class*="news"] button[class*="cookie-decline"],\ - div[class*="news"] button[class*="cookie-settings"],\ - div[id*="news"] button[class*="cookie-reject"],\ - div[id*="news"] button[class*="cookie-decline"],\ - div[id*="news"] button[class*="cookie-settings"]', - - 'div[class*="retail"] button[class*="cookie-reject"],\ - div[class*="retail"] button[class*="cookie-decline"],\ - div[class*="retail"] button[class*="cookie-settings"],\ - div[id*="retail"] button[class*="cookie-reject"],\ - div[id*="retail"] button[class*="cookie-decline"],\ - div[id*="retail"] button[class*="cookie-settings"]', - - 'div[class*="gdpr"] button[class*="reject"],\ - div[class*="gdpr"] button[class*="decline"],\ - div[class*="gdpr"] button[class*="settings"],\ - div[id*="gdpr"] button[class*="reject"],\ - div[id*="gdpr"] button[class*="decline"],\ - div[id*="gdpr"] button[class*="settings"]', - - 'div[class*="privacy"] button[class*="reject"],\ - div[class*="privacy"] button[class*="decline"],\ - div[class*="privacy"] button[class*="settings"],\ - div[id*="privacy"] button[class*="reject"],\ - div[id*="privacy"] button[class*="decline"],\ - div[id*="privacy"] button[class*="settings"]', - - 'div[class*="bank"] button[class*="cookie-reject"],\ - div[class*="bank"] button[class*="cookie-decline"],\ - div[class*="bank"] button[class*="cookie-settings"],\ - div[id*="bank"] button[class*="cookie-reject"],\ - div[id*="bank"] button[class*="cookie-decline"],\ - div[id*="bank"] button[class*="cookie-settings"]', - - 'div[class*="consent"] button[class*="reject"],\ - div[class*="consent"] button[class*="decline"],\ - div[class*="consent"] button[class*="settings"],\ - div[id*="consent"] button[class*="reject"],\ - div[id*="consent"] button[class*="decline"],\ - div[id*="consent"] button[class*="settings"]', - - 'div[class*="notice"] button[class*="reject"],\ - div[class*="notice"] button[class*="decline"],\ - div[class*="notice"] button[class*="settings"],\ - div[id*="notice"] button[class*="reject"],\ - div[id*="notice"] button[class*="decline"],\ - div[id*="notice"] button[class*="settings"]', - - 'div[class*="popup"] button[class*="reject"],\ - div[class*="popup"] button[class*="decline"],\ - div[class*="popup"] button[class*="settings"],\ - div[id*="popup"] button[class*="reject"],\ - div[id*="popup"] button[class*="decline"],\ - div[id*="popup"] button[class*="settings"]', - - 'div[class*="eu-cookie-banner"] button[class*="reject"],\ - div[class*="eu-cookie-banner"] button[class*="settings"]', - - 'div[class*="gdpr-notice"] button[class*="reject"],\ - div[class*="gdpr-notice"] button[class*="settings"]', - - 'div[class*="privacy-consent"] button[class*="reject"],\ - div[class*="privacy-consent"] button[class*="settings"]', - - 'div[class*="asia"] button[class*="cookie-reject"],\ - div[class*="asia"] button[class*="cookie-decline"],\ - div[class*="asia"] button[class*="cookie-settings"]', - - 'div[class*="europe"] button[class*="cookie-reject"],\ - div[class*="europe"] button[class*="cookie-decline"],\ - div[class*="europe"] button[class*="cookie-settings"]', - - 'div[class*="global"] button[class*="cookie-reject"],\ - div[class*="global"] button[class*="cookie-decline"],\ - div[class*="global"] button[class*="cookie-settings"]', - - 'div[class*="america"] button[class*="cookie-reject"],\ - div[class*="america"] button[class*="cookie-decline"],\ - div[class*="america"] button[class*="cookie-settings"]', - - 'div[class*="oceania"] button[class*="cookie-reject"],\ - div[class*="oceania"] button[class*="cookie-decline"],\ - div[class*="oceania"] button[class*="cookie-settings"]', - ]; - - let currentChainElement = 0; - const classname = Math.random() - .toString(36) - .replace(/[^a-z]+/g, ''); - - // Search loop function - let searchGroupsLength = searchGroups.length, - searchPairsKeys = Object.keys(searchPairs), - searchPairsJoinedKeys = searchPairsKeys.join(','), - timeoutDuration = 300; - - function querySelectorAllShadowRoot(selector, root = document) { - const elements = []; - const findElements = node => { - if (node.shadowRoot) { - node.shadowRoot.querySelectorAll(selector).forEach(el => elements.push(el)); - node.shadowRoot.querySelectorAll('*').forEach(findElements); - } - }; - root.querySelectorAll('*').forEach(findElements); - return elements; - } - - function searchLoop(counter) { - if (document.cookie.indexOf('cookie_consent') !== -1) { - return; - } - - const dynamicTimeout = Math.min(timeoutDuration * (counter + 1), 2000); - - setTimeout(function () { - document.querySelectorAll(searchPairsJoinedKeys).forEach(function (box) { - searchPairsKeys.forEach(function (selector) { - if (box.matches(selector)) { - const shadowElements = querySelectorAllShadowRoot(searchPairs[selector].join(','), box); - shadowElements.forEach(button => { - if (button.click && !button.classList.contains(classname)) { - button.classList.add(classname); - - if (typeof chrome == 'object' && chrome.runtime) chrome.runtime.sendMessage({ command: 'cookie_warning_dismissed', url: document.location.href }); - - button.click(); - - if (selector != '.message-container' && button.getAttribute('href') != '#cookieman-settings') - setTimeout(function () { - if (button) button.click(); - }, 500); - - timeoutDuration += 500; - } - }); - } - }); - }); - - document.querySelectorAll(searchGroups[counter % searchGroupsLength]).forEach(function (element) { - if (element.click && !element.classList.contains(classname)) { - element.classList.add(classname); - - if (typeof chrome == 'object' && chrome.runtime) chrome.runtime.sendMessage({ command: 'cookie_warning_dismissed', url: document.location.href }); - - element.click(); - - if (element.getAttribute('aria-pressed') != 'true') - setTimeout(function () { - if (element) element.click(); - }, 500); - - timeoutDuration += 500; - } - }); - - if (counter < 100 * searchGroupsLength) { - searchLoop(counter + 1); - } - }, dynamicTimeout); - } - - // Embeds handling - function handleEmbeds() { - var l = document.location, - is_audioboom = false, - is_dailymotion = false, - is_dailybuzz = false, - is_playerclipslaliga = false; - - switch (l.hostname) { - case 'embeds.audioboom.com': - is_audioboom = true; - break; - - case 'dailymotion.com': - case 'www.dailymotion.com': - is_dailymotion = l.pathname.indexOf('/embed') === 0; - break; - - case 'geo.dailymotion.com': - is_dailymotion = l.pathname.indexOf('/player') === 0; - break; - - case 'dailybuzz.nl': - is_dailybuzz = l.pathname.indexOf('/buzz/embed') === 0; - break; - - case 'playerclipslaliga.tv': - is_playerclipslaliga = true; - break; - } - - function searchEmbeds() { - setTimeout(function () { - if (is_audioboom) { - document.querySelectorAll('div[id^="cookie-modal"] .modal[style*="block"] .btn.mrs:not(.' + classname + ')').forEach(function (button) { - button.className += ' ' + classname; - button.click(); - }); - } else if (is_dailymotion) { - document.querySelectorAll('.np_DialogConsent-accept:not(.' + classname + '), .consent_screen-accept:not(.' + classname + ')').forEach(function (button) { - button.className += ' ' + classname; - button.click(); - }); - } else if (is_dailybuzz) { - document.querySelectorAll('#ask-consent #accept:not(.' + classname + ')').forEach(function (button) { - button.className += ' ' + classname; - button.click(); - }); - } else if (is_playerclipslaliga) { - document.querySelectorAll('#cookies button[onclick*="saveCookiesSelection"]:not(.' + classname + ')').forEach(function (button) { - button.className += ' ' + classname; - button.click(); - }); - } else { - return; - } - - searchEmbeds(); - }, 1000); - } - - searchEmbeds(); - } - - // Initialize - var start = setInterval(function () { - var html = document.querySelector('html'); - - if (!html || /idc0_350/.test(html.className)) return; - - clearInterval(start); - - html.className += ' idc0_350'; - searchLoop(0); - handleEmbeds(); - }, 500); - - var hostname = document.location.hostname.replace(/^w{2,3}\d*\./i, ''), - items = getItem(hostname); - - if (items) { - (items instanceof Array ? items : [items]).forEach(function (item) { - let value = localStorage.getItem(item.key); - if (value == null || (item.strict && value != item.value)) { - localStorage.setItem(item.key, item.value); - counter++; - } - }); - - if (counter > 0) document.location.reload(); - } -})(); diff --git a/home/programs/qutebrowser/greasemonkey/return-youtube-dislike.user.js b/home/programs/qutebrowser/greasemonkey/return-youtube-dislike.user.js deleted file mode 100644 index 50f3eff..0000000 --- a/home/programs/qutebrowser/greasemonkey/return-youtube-dislike.user.js +++ /dev/null @@ -1,704 +0,0 @@ -// ==UserScript== -// @name Return YouTube Dislike -// @namespace https://www.returnyoutubedislike.com/ -// @homepage https://www.returnyoutubedislike.com/ -// @version 3.1.5 -// @encoding utf-8 -// @description Return of the YouTube Dislike, Based off https://www.returnyoutubedislike.com/ -// @icon https://github.com/Anarios/return-youtube-dislike/raw/main/Icons/Return%20Youtube%20Dislike%20-%20Transparent.png -// @author Anarios & JRWR -// @match *://*.youtube.com/* -// @exclude *://music.youtube.com/* -// @exclude *://*.music.youtube.com/* -// @compatible chrome -// @compatible firefox -// @compatible opera -// @compatible safari -// @compatible edge -// @grant GM.xmlHttpRequest -// @connect youtube.com -// @grant GM_addStyle -// @run-at document-end -// @downloadURL https://update.greasyfork.org/scripts/436115/Return%20YouTube%20Dislike.user.js -// @updateURL https://update.greasyfork.org/scripts/436115/Return%20YouTube%20Dislike.meta.js -// ==/UserScript== - -const extConfig = { - // BEGIN USER OPTIONS - // You may change the following variables to allowed values listed in the corresponding brackets (* means default). Keep the style and keywords intact. - showUpdatePopup: false, // [true, false*] Show a popup tab after extension update (See what's new) - disableVoteSubmission: false, // [true, false*] Disable like/dislike submission (Stops counting your likes and dislikes) - disableLogging: true, // [true*, false] Disable Logging API Response in JavaScript Console. - coloredThumbs: false, // [true, false*] Colorize thumbs (Use custom colors for thumb icons) - coloredBar: false, // [true, false*] Colorize ratio bar (Use custom colors for ratio bar) - colorTheme: "classic", // [classic*, accessible, neon] Color theme (red/green, blue/yellow, pink/cyan) - numberDisplayFormat: "compactShort", // [compactShort*, compactLong, standard] Number format (For non-English locale users, you may be able to improve appearance with a different option. Please file a feature request if your locale is not covered) - numberDisplayRoundDown: true, // [true*, false] Round down numbers (Show rounded down numbers) - tooltipPercentageMode: "none", // [none*, dash_like, dash_dislike, both, only_like, only_dislike] Mode of showing percentage in like/dislike bar tooltip. - numberDisplayReformatLikes: false, // [true, false*] Re-format like numbers (Make likes and dislikes format consistent) - rateBarEnabled: false, // [true, false*] Enables ratio bar under like/dislike buttons - // END USER OPTIONS -}; - -const LIKED_STATE = "LIKED_STATE"; -const DISLIKED_STATE = "DISLIKED_STATE"; -const NEUTRAL_STATE = "NEUTRAL_STATE"; -let previousState = 3; //1=LIKED, 2=DISLIKED, 3=NEUTRAL -let likesvalue = 0; -let dislikesvalue = 0; -let preNavigateLikeButton = null; - -let isMobile = location.hostname == "m.youtube.com"; -let isShorts = () => location.pathname.startsWith("/shorts"); -let mobileDislikes = 0; -function cLog(text, subtext = "") { - if (!extConfig.disableLogging) { - subtext = subtext.trim() === "" ? "" : `(${subtext})`; - console.log(`[Return YouTube Dislikes] ${text} ${subtext}`); - } -} - -function isInViewport(element) { - const rect = element.getBoundingClientRect(); - const height = innerHeight || document.documentElement.clientHeight; - const width = innerWidth || document.documentElement.clientWidth; - return ( - // When short (channel) is ignored, the element (like/dislike AND short itself) is - // hidden with a 0 DOMRect. In this case, consider it outside of Viewport - !(rect.top == 0 && rect.left == 0 && rect.bottom == 0 && rect.right == 0) && - rect.top >= 0 && - rect.left >= 0 && - rect.bottom <= height && - rect.right <= width - ); -} - -function getButtons() { - if (isShorts()) { - let elements = document.querySelectorAll( - isMobile ? "ytm-like-button-renderer" : "#like-button > ytd-like-button-renderer", - ); - for (let element of elements) { - if (isInViewport(element)) { - return element; - } - } - } - if (isMobile) { - return ( - document.querySelector(".slim-video-action-bar-actions .segmented-buttons") ?? - document.querySelector(".slim-video-action-bar-actions") - ); - } - if (document.getElementById("menu-container")?.offsetParent === null) { - return ( - document.querySelector("ytd-menu-renderer.ytd-watch-metadata > div") ?? - document.querySelector("ytd-menu-renderer.ytd-video-primary-info-renderer > div") - ); - } else { - return document.getElementById("menu-container")?.querySelector("#top-level-buttons-computed"); - } -} - -function getDislikeButton() { - if (getButtons().children[0].tagName === "YTD-SEGMENTED-LIKE-DISLIKE-BUTTON-RENDERER") { - if (getButtons().children[0].children[1] === undefined) { - return document.querySelector("#segmented-dislike-button"); - } else { - return getButtons().children[0].children[1]; - } - } else { - if (getButtons().querySelector("segmented-like-dislike-button-view-model")) { - const dislikeViewModel = getButtons().querySelector("dislike-button-view-model"); - if (!dislikeViewModel) cLog("Dislike button wasn't added to DOM yet..."); - return dislikeViewModel; - } else { - return getButtons().children[1]; - } - } -} - -function getLikeButton() { - return getButtons().children[0].tagName === "YTD-SEGMENTED-LIKE-DISLIKE-BUTTON-RENDERER" - ? document.querySelector("#segmented-like-button") !== null - ? document.querySelector("#segmented-like-button") - : getButtons().children[0].children[0] - : getButtons().querySelector("like-button-view-model") ?? getButtons().children[0]; -} - -function getLikeTextContainer() { - return ( - getLikeButton().querySelector("#text") ?? - getLikeButton().getElementsByTagName("yt-formatted-string")[0] ?? - getLikeButton().querySelector("span[role='text']") - ); -} - -function getDislikeTextContainer() { - const dislikeButton = getDislikeButton(); - let result = - dislikeButton?.querySelector("#text") ?? - dislikeButton?.getElementsByTagName("yt-formatted-string")[0] ?? - dislikeButton?.querySelector("span[role='text']"); - if (result === null) { - let textSpan = document.createElement("span"); - textSpan.id = "text"; - textSpan.style.marginLeft = "6px"; - dislikeButton?.querySelector("button").appendChild(textSpan); - if (dislikeButton) dislikeButton.querySelector("button").style.width = "auto"; - result = textSpan; - } - return result; -} - -function createObserver(options, callback) { - const observerWrapper = new Object(); - observerWrapper.options = options; - observerWrapper.observer = new MutationObserver(callback); - observerWrapper.observe = function (element) { - this.observer.observe(element, this.options); - }; - observerWrapper.disconnect = function () { - this.observer.disconnect(); - }; - return observerWrapper; -} - -let shortsObserver = null; - -if (isShorts() && !shortsObserver) { - cLog("Initializing shorts mutation observer"); - shortsObserver = createObserver( - { - attributes: true, - }, - (mutationList) => { - mutationList.forEach((mutation) => { - if ( - mutation.type === "attributes" && - mutation.target.nodeName === "TP-YT-PAPER-BUTTON" && - mutation.target.id === "button" - ) { - cLog("Short thumb button status changed"); - if (mutation.target.getAttribute("aria-pressed") === "true") { - mutation.target.style.color = - mutation.target.parentElement.parentElement.id === "like-button" - ? getColorFromTheme(true) - : getColorFromTheme(false); - } else { - mutation.target.style.color = "unset"; - } - return; - } - cLog("Unexpected mutation observer event: " + mutation.target + mutation.type); - }); - }, - ); -} - -function isVideoLiked() { - if (isMobile) { - return getLikeButton().querySelector("button").getAttribute("aria-label") == "true"; - } - return getLikeButton().classList.contains("style-default-active"); -} - -function isVideoDisliked() { - if (isMobile) { - return getDislikeButton()?.querySelector("button").getAttribute("aria-label") == "true"; - } - return getDislikeButton()?.classList.contains("style-default-active"); -} - -function isVideoNotLiked() { - if (isMobile) { - return !isVideoLiked(); - } - return getLikeButton().classList.contains("style-text"); -} - -function isVideoNotDisliked() { - if (isMobile) { - return !isVideoDisliked(); - } - return getDislikeButton()?.classList.contains("style-text"); -} - -function checkForUserAvatarButton() { - if (isMobile) { - return; - } - if (document.querySelector("#avatar-btn")) { - return true; - } else { - return false; - } -} - -function getState() { - if (isVideoLiked()) { - return LIKED_STATE; - } - if (isVideoDisliked()) { - return DISLIKED_STATE; - } - return NEUTRAL_STATE; -} - -function setLikes(likesCount) { - if (isMobile) { - getButtons().children[0].querySelector(".button-renderer-text").innerText = likesCount; - return; - } - getLikeTextContainer().innerText = likesCount; -} - -function setDislikes(dislikesCount) { - if (isMobile) { - mobileDislikes = dislikesCount; - return; - } - - const _container = getDislikeTextContainer(); - _container?.removeAttribute("is-empty"); - if (_container?.innerText !== dislikesCount) { - _container.innerText = dislikesCount; - } -} - -function getLikeCountFromButton() { - try { - if (isShorts()) { - //Youtube Shorts don't work with this query. It's not necessary; we can skip it and still see the results. - //It should be possible to fix this function, but it's not critical to showing the dislike count. - return false; - } - let likeButton = - getLikeButton().querySelector("yt-formatted-string#text") ?? getLikeButton().querySelector("button"); - - let likesStr = likeButton.getAttribute("aria-label").replace(/\D/g, ""); - return likesStr.length > 0 ? parseInt(likesStr) : false; - } catch { - return false; - } -} - -(typeof GM_addStyle != "undefined" - ? GM_addStyle - : (styles) => { - let styleNode = document.createElement("style"); - styleNode.type = "text/css"; - styleNode.innerText = styles; - document.head.appendChild(styleNode); - })(` - #return-youtube-dislike-bar-container { - background: var(--yt-spec-icon-disabled); - border-radius: 2px; - } - - #return-youtube-dislike-bar { - background: var(--yt-spec-text-primary); - border-radius: 2px; - transition: all 0.15s ease-in-out; - } - - .ryd-tooltip { - position: absolute; - display: block; - height: 2px; - bottom: -10px; - } - - .ryd-tooltip-bar-container { - width: 100%; - height: 2px; - position: absolute; - padding-top: 6px; - padding-bottom: 12px; - top: -6px; - } - - ytd-menu-renderer.ytd-watch-metadata { - overflow-y: visible !important; - } - - #top-level-buttons-computed { - position: relative !important; - } - `); - -function createRateBar(likes, dislikes) { - if (isMobile || !extConfig.rateBarEnabled) { - return; - } - let rateBar = document.getElementById("return-youtube-dislike-bar-container"); - - const widthPx = getLikeButton().clientWidth + (getDislikeButton()?.clientWidth ?? 52); - - const widthPercent = likes + dislikes > 0 ? (likes / (likes + dislikes)) * 100 : 50; - - var likePercentage = parseFloat(widthPercent.toFixed(1)); - const dislikePercentage = (100 - likePercentage).toLocaleString(); - likePercentage = likePercentage.toLocaleString(); - - var tooltipInnerHTML; - switch (extConfig.tooltipPercentageMode) { - case "dash_like": - tooltipInnerHTML = `${likes.toLocaleString()} / ${dislikes.toLocaleString()}  -  ${likePercentage}%`; - break; - case "dash_dislike": - tooltipInnerHTML = `${likes.toLocaleString()} / ${dislikes.toLocaleString()}  -  ${dislikePercentage}%`; - break; - case "both": - tooltipInnerHTML = `${likePercentage}% / ${dislikePercentage}%`; - break; - case "only_like": - tooltipInnerHTML = `${likePercentage}%`; - break; - case "only_dislike": - tooltipInnerHTML = `${dislikePercentage}%`; - break; - default: - tooltipInnerHTML = `${likes.toLocaleString()} / ${dislikes.toLocaleString()}`; - } - - if (!rateBar && !isMobile) { - let colorLikeStyle = ""; - let colorDislikeStyle = ""; - if (extConfig.coloredBar) { - colorLikeStyle = "; background-color: " + getColorFromTheme(true); - colorDislikeStyle = "; background-color: " + getColorFromTheme(false); - } - - getButtons().insertAdjacentHTML( - "beforeend", - ` -
-
-
-
-
-
- - ${tooltipInnerHTML} - -
-`, - ); - let descriptionAndActionsElement = document.getElementById("top-row"); - descriptionAndActionsElement.style.borderBottom = "1px solid var(--yt-spec-10-percent-layer)"; - descriptionAndActionsElement.style.paddingBottom = "10px"; - } else { - document.querySelector(".ryd-tooltip").style.width = widthPx + "px"; - document.getElementById("return-youtube-dislike-bar").style.width = widthPercent + "%"; - - if (extConfig.coloredBar) { - document.getElementById("return-youtube-dislike-bar-container").style.backgroundColor = getColorFromTheme(false); - document.getElementById("return-youtube-dislike-bar").style.backgroundColor = getColorFromTheme(true); - } - } -} - -function setState() { - cLog("Fetching votes..."); - let statsSet = false; - - fetch(`https://returnyoutubedislikeapi.com/votes?videoId=${getVideoId()}`).then((response) => { - response.json().then((json) => { - if (json && !("traceId" in response) && !statsSet) { - const { dislikes, likes } = json; - cLog(`Received count: ${dislikes}`); - likesvalue = likes; - dislikesvalue = dislikes; - setDislikes(numberFormat(dislikes)); - if (extConfig.numberDisplayReformatLikes === true) { - const nativeLikes = getLikeCountFromButton(); - if (nativeLikes !== false) { - setLikes(numberFormat(nativeLikes)); - } - } - createRateBar(likes, dislikes); - if (extConfig.coloredThumbs === true) { - const dislikeButton = getDislikeButton(); - if (isShorts()) { - // for shorts, leave deactived buttons in default color - const shortLikeButton = getLikeButton().querySelector("tp-yt-paper-button#button"); - const shortDislikeButton = dislikeButton?.querySelector("tp-yt-paper-button#button"); - if (shortLikeButton.getAttribute("aria-pressed") === "true") { - shortLikeButton.style.color = getColorFromTheme(true); - } - if (shortDislikeButton && shortDislikeButton.getAttribute("aria-pressed") === "true") { - shortDislikeButton.style.color = getColorFromTheme(false); - } - shortsObserver.observe(shortLikeButton); - shortsObserver.observe(shortDislikeButton); - } else { - getLikeButton().style.color = getColorFromTheme(true); - if (dislikeButton) dislikeButton.style.color = getColorFromTheme(false); - } - } - } - }); - }); -} - -function updateDOMDislikes() { - setDislikes(numberFormat(dislikesvalue)); - createRateBar(likesvalue, dislikesvalue); -} - -function likeClicked() { - if (checkForUserAvatarButton() == true) { - if (previousState == 1) { - likesvalue--; - updateDOMDislikes(); - previousState = 3; - } else if (previousState == 2) { - likesvalue++; - dislikesvalue--; - updateDOMDislikes(); - previousState = 1; - } else if (previousState == 3) { - likesvalue++; - updateDOMDislikes(); - previousState = 1; - } - if (extConfig.numberDisplayReformatLikes === true) { - const nativeLikes = getLikeCountFromButton(); - if (nativeLikes !== false) { - setLikes(numberFormat(nativeLikes)); - } - } - } -} - -function dislikeClicked() { - if (checkForUserAvatarButton() == true) { - if (previousState == 3) { - dislikesvalue++; - updateDOMDislikes(); - previousState = 2; - } else if (previousState == 2) { - dislikesvalue--; - updateDOMDislikes(); - previousState = 3; - } else if (previousState == 1) { - likesvalue--; - dislikesvalue++; - updateDOMDislikes(); - previousState = 2; - if (extConfig.numberDisplayReformatLikes === true) { - const nativeLikes = getLikeCountFromButton(); - if (nativeLikes !== false) { - setLikes(numberFormat(nativeLikes)); - } - } - } - } -} - -function setInitialState() { - setState(); -} - -function getVideoId() { - const urlObject = new URL(window.location.href); - const pathname = urlObject.pathname; - if (pathname.startsWith("/clip")) { - return (document.querySelector("meta[itemprop='videoId']") || document.querySelector("meta[itemprop='identifier']")).content; - } else { - if (pathname.startsWith("/shorts")) { - return pathname.slice(8); - } - return urlObject.searchParams.get("v"); - } -} - -function isVideoLoaded() { - if (isMobile) { - return document.getElementById("player").getAttribute("loading") == "false"; - } - const videoId = getVideoId(); - - return ( - // desktop: spring 2024 UI - document.querySelector(`ytd-watch-grid[video-id='${videoId}']`) !== null || - // desktop: older UI - document.querySelector(`ytd-watch-flexy[video-id='${videoId}']`) !== null || - // mobile: no video-id attribute - document.querySelector('#player[loading="false"]:not([hidden])') !== null - ); -} - -function roundDown(num) { - if (num < 1000) return num; - const int = Math.floor(Math.log10(num) - 2); - const decimal = int + (int % 3 ? 1 : 0); - const value = Math.floor(num / 10 ** decimal); - return value * 10 ** decimal; -} - -function numberFormat(numberState) { - let numberDisplay; - if (extConfig.numberDisplayRoundDown === false) { - numberDisplay = numberState; - } else { - numberDisplay = roundDown(numberState); - } - return getNumberFormatter(extConfig.numberDisplayFormat).format(numberDisplay); -} - -function getNumberFormatter(optionSelect) { - let userLocales; - if (document.documentElement.lang) { - userLocales = document.documentElement.lang; - } else if (navigator.language) { - userLocales = navigator.language; - } else { - try { - userLocales = new URL( - Array.from(document.querySelectorAll("head > link[rel='search']")) - ?.find((n) => n?.getAttribute("href")?.includes("?locale=")) - ?.getAttribute("href"), - )?.searchParams?.get("locale"); - } catch { - cLog("Cannot find browser locale. Use en as default for number formatting."); - userLocales = "en"; - } - } - - let formatterNotation; - let formatterCompactDisplay; - switch (optionSelect) { - case "compactLong": - formatterNotation = "compact"; - formatterCompactDisplay = "long"; - break; - case "standard": - formatterNotation = "standard"; - formatterCompactDisplay = "short"; - break; - case "compactShort": - default: - formatterNotation = "compact"; - formatterCompactDisplay = "short"; - } - - const formatter = Intl.NumberFormat(userLocales, { - notation: formatterNotation, - compactDisplay: formatterCompactDisplay, - }); - return formatter; -} - -function getColorFromTheme(voteIsLike) { - let colorString; - switch (extConfig.colorTheme) { - case "accessible": - if (voteIsLike === true) { - colorString = "dodgerblue"; - } else { - colorString = "gold"; - } - break; - case "neon": - if (voteIsLike === true) { - colorString = "aqua"; - } else { - colorString = "magenta"; - } - break; - case "classic": - default: - if (voteIsLike === true) { - colorString = "lime"; - } else { - colorString = "red"; - } - } - return colorString; -} - -let smartimationObserver = null; - -function setEventListeners(evt) { - let jsInitChecktimer; - - function checkForJS_Finish() { - //console.log(); - if (isShorts() || (getButtons()?.offsetParent && isVideoLoaded())) { - const buttons = getButtons(); - const dislikeButton = getDislikeButton(); - - if (preNavigateLikeButton !== getLikeButton() && dislikeButton) { - cLog("Registering button listeners..."); - try { - getLikeButton().addEventListener("click", likeClicked); - dislikeButton?.addEventListener("click", dislikeClicked); - getLikeButton().addEventListener("touchstart", likeClicked); - dislikeButton?.addEventListener("touchstart", dislikeClicked); - dislikeButton?.addEventListener("focusin", updateDOMDislikes); - dislikeButton?.addEventListener("focusout", updateDOMDislikes); - preNavigateLikeButton = getLikeButton(); - - if (!smartimationObserver) { - smartimationObserver = createObserver( - { - attributes: true, - subtree: true, - childList: true, - }, - updateDOMDislikes, - ); - smartimationObserver.container = null; - } - - const smartimationContainer = buttons.querySelector("yt-smartimation"); - if (smartimationContainer && smartimationObserver.container != smartimationContainer) { - cLog("Initializing smartimation mutation observer"); - smartimationObserver.disconnect(); - smartimationObserver.observe(smartimationContainer); - smartimationObserver.container = smartimationContainer; - } - } catch { - return; - } //Don't spam errors into the console - } - if (dislikeButton) { - setInitialState(); - clearInterval(jsInitChecktimer); - } - } - } - - cLog("Setting up..."); - jsInitChecktimer = setInterval(checkForJS_Finish, 111); -} - -(function () { - "use strict"; - window.addEventListener("yt-navigate-finish", setEventListeners, true); - setEventListeners(); -})(); -if (isMobile) { - let originalPush = history.pushState; - history.pushState = function (...args) { - window.returnDislikeButtonlistenersSet = false; - setEventListeners(args[2]); - return originalPush.apply(history, args); - }; - setInterval(() => { - const dislikeButton = getDislikeButton(); - if (dislikeButton?.querySelector(".button-renderer-text") === null) { - getDislikeTextContainer().innerText = mobileDislikes; - } else { - if (dislikeButton) dislikeButton.querySelector(".button-renderer-text").innerText = mobileDislikes; - } - }, 1000); -} diff --git a/home/programs/qutebrowser/greasemonkey/sponsorblock-lite.user.js b/home/programs/qutebrowser/greasemonkey/sponsorblock-lite.user.js deleted file mode 100644 index 765097d..0000000 --- a/home/programs/qutebrowser/greasemonkey/sponsorblock-lite.user.js +++ /dev/null @@ -1,1146 +0,0 @@ -// ==UserScript== -// @name SponsorBlock Lite -// @name:en SponsorBlock Lite - Auto-skip sponsor segments on YouTube and Bilibili -// @name:zh-CN SponsorBlock Lite - 自动跳过 YouTube/Bilibili 赞助内容 -// @name:zh-TW SponsorBlock Lite - 自動跳過 YouTube/Bilibili 贊助內容 -// @name:ja SponsorBlock Lite - YouTube/Bilibili スポンサー自動スキップ -// @name:ko SponsorBlock Lite - YouTube/Bilibili 스폰서 자동 건너뛰기 -// @name:de SponsorBlock Lite - YouTube/Bilibili Sponsoren überspringen -// @name:fr SponsorBlock Lite - Ignorer les sponsors YouTube/Bilibili -// @name:es SponsorBlock Lite - Saltar patrocinadores YouTube/Bilibili -// @name:it SponsorBlock Lite - Salta sponsor YouTube/Bilibili -// @namespace https://github.com/hxueh -// @version 1.1.1 -// @description Auto-skip sponsor segments on YouTube and Bilibili using SponsorBlock API -// @description:en Auto-skip sponsor segments on YouTube and Bilibili using SponsorBlock API -// @description:zh-CN 基于 SponsorBlock API 自动跳过 YouTube 和 Bilibili 视频中的赞助片段 -// @description:zh-TW 基於 SponsorBlock API 自動跳過 YouTube 和 Bilibili 影片中的贊助片段 -// @description:ja SponsorBlock API を使用して YouTube と Bilibili 動画のスポンサーセグメントを自動的にスキップします -// @description:ko SponsorBlock API를 사용하여 YouTube 및 Bilibili 동영상의 스폰서 구간을 자동으로 건너뜁니다 -// @description:de Überspringen Sie Sponsorensegmente in YouTube- und Bilibili-Videos automatisch mit der SponsorBlock-API -// @description:fr Ignorez automatiquement les segments sponsorisés dans les vidéos YouTube et Bilibili via l'API SponsorBlock -// @description:es Salte automáticamente los segmentos de patrocinadores en videos de YouTube y Bilibili usando la API de SponsorBlock -// @description:it Salta automaticamente i segmenti degli sponsor nei video di YouTube e Bilibili utilizzando l'API SponsorBlock -// @author hxueh -// @match https://www.youtube.com/* -// @match https://music.youtube.com/* -// @match https://m.youtube.com/* -// @match https://*.bilibili.com/video/* -// @icon https://sponsor.ajay.app/LogoSponsorBlock256px.png -// @grant GM_xmlhttpRequest -// @grant GM_addStyle -// @connect sponsor.ajay.app -// @connect bsbsb.top -// @run-at document-idle -// @license LGPL-3.0-or-later -// @downloadURL https://update.greasyfork.org/scripts/560869/SponsorBlock%20Lite.user.js -// @updateURL https://update.greasyfork.org/scripts/560869/SponsorBlock%20Lite.meta.js -// ==/UserScript== - -(function () { - "use strict"; - - // ==================== CONSTANTS ==================== - - // Platform detection (must be first for other constants to use) - const IS_BILIBILI = window.location.hostname.includes("bilibili.com"); - - const API_BASE_YOUTUBE = "https://sponsor.ajay.app"; - const API_BASE_BILIBILI = "https://bsbsb.top"; - const API_BASE = IS_BILIBILI ? API_BASE_BILIBILI : API_BASE_YOUTUBE; - const CATEGORIES = [ - "sponsor", - "selfpromo", - "exclusive_access", - "interaction", - "outro", - "music_offtopic", - ]; - const ACTION_TYPES = ["skip", "full"]; - const SKIP_BUFFER = 0.003; - - // Colors for all categories (used in preview bar and category pill) - const CATEGORY_COLORS = { - sponsor: "#00d400", - selfpromo: "#ffff00", - exclusive_access: "#008a5c", - interaction: "#cc00ff", - outro: "#0202ed", - music_offtopic: "#ff9900", - }; - - const CATEGORY_LABELS = { - exclusive_access: "Exclusive Access", - music_offtopic: "Music: Non-Music", - }; - - // ==================== STATE ==================== - - let currentVideoID = null; - let segments = []; - let skippableSegments = []; - let skipScheduleTimer = null; - let video = null; - let lastSkippedUUID = null; - let currentSegmentIndex = 0; - let videoChangeDebounce = null; - let previewBarContainer = null; - let videoDuration = 0; - let lastUrl = location.href; - let urlPollInterval = null; - let videoObserver = null; - let rafSkipId = null; // For requestAnimationFrame-based skipping - let lastVideoSrc = null; // Track video element replacement - - // Platform detection - const IS_MUSIC_YOUTUBE = window.location.hostname === "music.youtube.com"; - const IS_MOBILE_YOUTUBE = window.location.hostname === "m.youtube.com"; - - // Vinegar detection - now a function that's called when needed - let IS_VINEGAR = false; - - function updateVinegarDetection() { - const hasVideo = document.querySelector("video") !== null; - const hasYouTubePlayer = document.querySelector("#movie_player, ytm-player, #player") !== null; - const hasYouTubeProgressBar = document.querySelector(".ytp-progress-bar, .progress-bar-line") !== null; - // Vinegar: video exists but no YouTube player components - const detected = hasVideo && !hasYouTubePlayer && !hasYouTubeProgressBar; - - if (detected && !IS_VINEGAR) { - IS_VINEGAR = true; - log("Vinegar/native video mode detected"); - } - - return IS_VINEGAR; - } - - // ==================== CSS INJECTION ==================== - - function injectStyles() { - const css = ` - /* Desktop YouTube styles */ - #sb-lite-previewbar { - position: absolute; - width: 100%; - height: 100%; - padding: 0; - margin: 0; - overflow: visible; - pointer-events: none; - z-index: 42; - list-style: none; - transform: scaleY(0.6); - transition: transform 0.1s cubic-bezier(0, 0, 0.2, 1); - } - - /* Expand on hover (desktop) */ - .ytp-progress-bar:hover #sb-lite-previewbar { - transform: scaleY(1); - } - - /* Fullscreen mode (desktop) */ - .ytp-big-mode #sb-lite-previewbar { - transform: scaleY(0.625); - } - - .ytp-big-mode .ytp-progress-bar:hover #sb-lite-previewbar { - transform: scaleY(1); - } - - /* Mobile YouTube styles */ - .advancement-bar-line #sb-lite-previewbar, - .advancement-bar #sb-lite-previewbar, - .progress-bar-line #sb-lite-previewbar { - position: absolute; - width: 100%; - height: 100%; - top: 0; - left: 0; - padding: 0; - margin: 0; - overflow: visible; - pointer-events: none; - z-index: 42; - list-style: none; - transform: none; - } - - .sb-lite-segment { - position: absolute; - height: 100%; - min-width: 1px; - display: inline-block; - opacity: 0.7; - } - - .sb-lite-segment:hover { - opacity: 1; - } - - #sb-lite-category-pill { - display: none; - align-items: center; - padding: 4px 12px; - border-radius: 4px; - font-size: 12px; - font-weight: 500; - margin-left: 8px; - color: white; - font-family: Roboto, Arial, sans-serif; - white-space: nowrap; - cursor: default; - user-select: none; - } - - /* Mobile category pill adjustments */ - .ytm-slim-owner-container #sb-lite-category-pill, - ytm-slim-owner-renderer #sb-lite-category-pill { - font-size: 10px; - padding: 2px 8px; - margin-left: 4px; - } - - /* Bilibili styles */ - .bpx-player-progress-wrap #sb-lite-previewbar, - .bpx-player-progress #sb-lite-previewbar, - .bilibili-player-video-progress #sb-lite-previewbar, - .squirtle-progress-wrap #sb-lite-previewbar { - position: absolute; - width: 100%; - height: 100%; - top: 0; - left: 0; - padding: 0; - margin: 0; - overflow: visible; - pointer-events: none; - z-index: 42; - list-style: none; - transform: none; - } - - /* Bilibili category pill adjustments */ - .video-title #sb-lite-category-pill, - .video-info-title #sb-lite-category-pill { - font-size: 12px; - padding: 2px 8px; - margin-left: 8px; - } - `; - - if (typeof GM_addStyle !== "undefined") { - GM_addStyle(css); - } else { - const style = document.createElement("style"); - style.textContent = css; - document.head.appendChild(style); - } - } - - // ==================== UTILITY FUNCTIONS ==================== - - async function sha256(message) { - const msgBuffer = new TextEncoder().encode(message); - const hashBuffer = await crypto.subtle.digest("SHA-256", msgBuffer); - const hashArray = Array.from(new Uint8Array(hashBuffer)); - return hashArray.map((b) => b.toString(16).padStart(2, "0")).join(""); - } - - async function getHashPrefix(videoID) { - const hash = await sha256(videoID); - return hash.slice(0, 4); - } - - function getBilibiliVideoID() { - const url = window.location.href; - const patterns = [ - /\/video\/([^/?#]+)/, - /\/BV([^/?#]+)/, - /bvid=([^&]+)/, - ]; - for (const pattern of patterns) { - const match = url.match(pattern); - if (match) { - let videoId = match[1]; - if (!videoId.startsWith("BV")) { - videoId = "BV" + videoId; - } - return videoId; - } - } - return null; - } - - function getVideoID() { - if (IS_BILIBILI) { - return getBilibiliVideoID(); - } - - const url = new URL(window.location.href); - - const vParam = url.searchParams.get("v"); - if (vParam && /^[a-zA-Z0-9_-]{11}$/.test(vParam)) { - return vParam; - } - - const shortsMatch = url.pathname.match(/\/shorts\/([a-zA-Z0-9_-]{11})/); - if (shortsMatch) return shortsMatch[1]; - - const embedMatch = url.pathname.match(/\/embed\/([a-zA-Z0-9_-]{11})/); - if (embedMatch) return embedMatch[1]; - - const liveMatch = url.pathname.match(/\/live\/([a-zA-Z0-9_-]{11})/); - if (liveMatch) return liveMatch[1]; - - // Mobile watch URL pattern - const watchMatch = url.pathname.match(/\/watch\/([a-zA-Z0-9_-]{11})/); - if (watchMatch) return watchMatch[1]; - - return null; - } - - function getVideoDuration() { - return video?.duration || 0; - } - - function log(message, ...args) { - console.log( - `[SB Lite${IS_BILIBILI ? " Bilibili" : IS_VINEGAR ? " Vinegar" : IS_MOBILE_YOUTUBE ? " Mobile" : ""}]`, - message, - ...args, - ); - } - - function logError(message, ...args) { - console.error( - `[SB Lite${IS_BILIBILI ? " Bilibili" : IS_VINEGAR ? " Vinegar" : IS_MOBILE_YOUTUBE ? " Mobile" : ""}]`, - message, - ...args, - ); - } - - // ==================== API FUNCTIONS ==================== - - function fetchSegments(videoID) { - return new Promise(async (resolve) => { - try { - const hashPrefix = await getHashPrefix(videoID); - const params = new URLSearchParams({ - categories: JSON.stringify(CATEGORIES), - actionTypes: JSON.stringify(ACTION_TYPES), - }); - - GM_xmlhttpRequest({ - method: "GET", - url: `${API_BASE}/api/skipSegments/${hashPrefix}?${params}`, - headers: { Accept: "application/json" }, - onload(response) { - if (response.status === 200) { - try { - const data = JSON.parse(response.responseText); - const videoData = data.find((v) => v.videoID === videoID); - const segs = videoData?.segments || []; - segs.sort((a, b) => a.segment[0] - b.segment[0]); - resolve(segs); - } catch { - resolve([]); - } - } else { - resolve([]); - } - }, - onerror() { - resolve([]); - }, - }); - } catch { - resolve([]); - } - }); - } - - // ==================== SKIP LOGIC ==================== - - function computeSkippableSegments() { - skippableSegments = segments.filter((s) => { - if (s.actionType === "full") return false; - if (s.category === "music_offtopic" && !IS_MUSIC_YOUTUBE) return false; - return true; - }); - currentSegmentIndex = 0; - } - - function skipToTime(targetTime, retryCount = 0) { - if (!video || targetTime === undefined) return false; - - const maxRetries = 3; - const previousTime = video.currentTime; - - try { - video.currentTime = targetTime; - - // On iOS/Vinegar, verify the skip worked after a short delay - if (IS_VINEGAR && retryCount < maxRetries) { - setTimeout(() => { - // Check if we're still in a segment that should be skipped - // (meaning the skip might have failed) - const currentTime = video.currentTime; - const timeDiff = Math.abs(currentTime - targetTime); - - // If we're more than 0.5s away from target and still before target, - // the skip likely failed - if (timeDiff > 0.5 && currentTime < targetTime - 0.5) { - log(`Skip verification failed (attempt ${retryCount + 1}), retrying...`); - skipToTime(targetTime, retryCount + 1); - } - }, 100); - } - - return true; - } catch (e) { - logError("Skip failed:", e); - return false; - } - } - - function findNextSegment(currentTime) { - if ( - currentSegmentIndex > 0 && - skippableSegments[currentSegmentIndex - 1] && - currentTime < skippableSegments[currentSegmentIndex - 1].segment[0] - ) { - currentSegmentIndex = 0; - } - - while (currentSegmentIndex < skippableSegments.length) { - const seg = skippableSegments[currentSegmentIndex]; - if (currentTime < seg.segment[1] - SKIP_BUFFER) { - return { segment: seg, index: currentSegmentIndex }; - } - currentSegmentIndex++; - } - return null; - } - - // RAF-based skip loop for Vinegar (more responsive than timeupdate) - function startRAFSkipLoop() { - if (rafSkipId) { - cancelAnimationFrame(rafSkipId); - } - - function checkAndSkip() { - if (!video || !skippableSegments.length) { - rafSkipId = null; - return; - } - - // Check for video element replacement - if (IS_VINEGAR) { - const currentVideo = document.querySelector("video"); - if (currentVideo && currentVideo !== video) { - log("Video element replaced, re-attaching..."); - video = currentVideo; - setupVideoListeners(); - } - } - - if (!video.paused) { - const currentTime = video.currentTime; - - for (const seg of skippableSegments) { - const [startTime, endTime] = seg.segment; - if ( - currentTime >= startTime - SKIP_BUFFER && - currentTime < endTime - SKIP_BUFFER && - lastSkippedUUID !== seg.UUID - ) { - lastSkippedUUID = seg.UUID; - log(`Skipping ${seg.category} segment at ${currentTime.toFixed(2)}s -> ${endTime.toFixed(2)}s`); - skipToTime(endTime); - break; - } - } - } - - rafSkipId = requestAnimationFrame(checkAndSkip); - } - - rafSkipId = requestAnimationFrame(checkAndSkip); - } - - function stopRAFSkipLoop() { - if (rafSkipId) { - cancelAnimationFrame(rafSkipId); - rafSkipId = null; - } - } - - function scheduleSkips() { - // For Vinegar/iOS, use RAF-based skipping for better responsiveness - if (IS_VINEGAR || IS_MOBILE_YOUTUBE) { - if (!video?.paused && skippableSegments.length > 0) { - startRAFSkipLoop(); - } - return; - } - - // Desktop: use timer-based approach - if (skipScheduleTimer) { - clearTimeout(skipScheduleTimer); - skipScheduleTimer = null; - } - - if (!video || video.paused || !skippableSegments.length) return; - - const currentTime = video.currentTime; - const result = findNextSegment(currentTime); - - if (!result) return; - - const { segment: nextSegment } = result; - const [startTime, endTime] = nextSegment.segment; - - if (currentTime >= startTime - SKIP_BUFFER) { - if (lastSkippedUUID !== nextSegment.UUID) { - lastSkippedUUID = nextSegment.UUID; - log(`Skipping ${nextSegment.category} segment`); - skipToTime(endTime); - currentSegmentIndex++; - } - setTimeout(scheduleSkips, 50); - return; - } - - const timeUntilStart = (startTime - currentTime) / video.playbackRate; - const delayMs = Math.max(0, timeUntilStart * 1000 - 50); - - skipScheduleTimer = setTimeout(() => { - if (!video || video.paused) return; - - const nowTime = video.currentTime; - if ( - nowTime >= startTime - SKIP_BUFFER && - nowTime < endTime - SKIP_BUFFER - ) { - if (lastSkippedUUID !== nextSegment.UUID) { - lastSkippedUUID = nextSegment.UUID; - log(`Skipping ${nextSegment.category} segment`); - skipToTime(endTime); - currentSegmentIndex++; - } - } - scheduleSkips(); - }, delayMs); - } - - // ==================== PREVIEW BAR ==================== - - function createPreviewBar() { - const container = document.createElement("ul"); - container.id = "sb-lite-previewbar"; - return container; - } - - function createSegmentBar(segment, duration) { - const bar = document.createElement("li"); - bar.className = "sb-lite-segment"; - - const startTime = segment.segment[0]; - const endTime = Math.min(segment.segment[1], duration); - - const startPercent = (startTime / duration) * 100; - const endPercent = (endTime / duration) * 100; - - bar.style.left = `${startPercent}%`; - bar.style.right = `${100 - endPercent}%`; - bar.style.backgroundColor = CATEGORY_COLORS[segment.category] || "#888"; - - // Add title tooltip - bar.title = segment.category.replace(/_/g, " "); - - return bar; - } - - function getProgressBar() { - // Bilibili - if (IS_BILIBILI) { - return ( - document.querySelector(".bpx-player-progress-wrap") || - document.querySelector(".bilibili-player-video-progress") || - document.querySelector(".squirtle-progress-wrap") || - document.querySelector(".bpx-player-progress") - ); - } - - // Desktop YouTube - let progressBar = document.querySelector(".ytp-progress-bar"); - - // YouTube Music - if (!progressBar && IS_MUSIC_YOUTUBE) { - progressBar = document.querySelector("#progress-bar"); - } - - // Mobile YouTube - try multiple selectors - if (!progressBar && IS_MOBILE_YOUTUBE) { - progressBar = - document.querySelector(".progress-bar-line") || - document.querySelector(".advancement-bar-line") || - document.querySelector(".advancement-bar") || - document.querySelector("ytm-player .progress-bar") || - document.querySelector(".player-controls-content .progress-bar-line") || - document.querySelector("[class*='progress-bar']"); - } - - return progressBar; - } - - function clearPreviewBar() { - if (previewBarContainer) { - previewBarContainer.innerHTML = ""; - } - } - - function removePreviewBar() { - if (previewBarContainer) { - previewBarContainer.remove(); - previewBarContainer = null; - } - } - - function updatePreviewBar() { - const duration = getVideoDuration(); - if (!duration || duration <= 0) return; - - videoDuration = duration; - - // Get or create container - if (!previewBarContainer) { - previewBarContainer = createPreviewBar(); - } - - // Attach to progress bar if not already attached - const progressBar = getProgressBar(); - if (progressBar && !progressBar.contains(previewBarContainer)) { - // Ensure progress bar has relative positioning for absolute children - const computedStyle = window.getComputedStyle(progressBar); - if (computedStyle.position === "static") { - progressBar.style.position = "relative"; - } - progressBar.appendChild(previewBarContainer); - } - - if (!progressBar) { - // For Vinegar, this is expected since native controls can't be modified - if (IS_VINEGAR) { - log("Preview bar not available (Vinegar/native controls)"); - } else { - log("Progress bar not found, will retry..."); - } - return; - } - - // Clear existing bars - clearPreviewBar(); - - // Filter segments for preview bar (exclude ActionType.Full) - const previewSegments = segments.filter((s) => s.actionType !== "full"); - - // Sort by duration (longer first) to render properly - const sortedSegments = [...previewSegments].sort( - (a, b) => b.segment[1] - b.segment[0] - (a.segment[1] - a.segment[0]), - ); - - // Create segment bars - for (const segment of sortedSegments) { - // Skip music_offtopic on non-music YouTube - if (segment.category === "music_offtopic" && !IS_MUSIC_YOUTUBE) { - continue; - } - - const bar = createSegmentBar(segment, duration); - previewBarContainer.appendChild(bar); - } - } - - // ==================== CATEGORY PILL ==================== - - function createCategoryPill() { - const pill = document.createElement("span"); - pill.id = "sb-lite-category-pill"; - return pill; - } - - function attachCategoryPill() { - let pill = document.getElementById("sb-lite-category-pill"); - if (!pill) { - pill = createCategoryPill(); - } - - let titleContainer = null; - - if (IS_BILIBILI) { - // Bilibili title selectors - titleContainer = - document.querySelector(".video-title") || - document.querySelector(".title-text") || - document.querySelector("h1.video-title") || - document.querySelector(".video-info-title"); - } else if (IS_MUSIC_YOUTUBE) { - titleContainer = document.querySelector("ytmusic-player-bar .title"); - } else if (IS_MOBILE_YOUTUBE) { - // Mobile YouTube title selectors - titleContainer = - document.querySelector( - ".slim-video-metadata-header .slim-owner-icon-and-title", - ) || - document.querySelector("ytm-slim-owner-renderer") || - document.querySelector(".slim-video-information-title") || - document.querySelector(".slim-video-metadata-title") || - document.querySelector("[class*='video-title']") || - document.querySelector("h2.slim-video-information-title"); - } else { - // Desktop YouTube - titleContainer = - document.querySelector("#above-the-fold #title h1") || - document.querySelector("ytd-watch-metadata #title h1") || - document.querySelector("#info-contents h1") || - document.querySelector("h1.ytd-video-primary-info-renderer"); - } - - if (titleContainer && !titleContainer.contains(pill)) { - titleContainer.style.display = "flex"; - titleContainer.style.alignItems = "center"; - titleContainer.style.flexWrap = "wrap"; - titleContainer.appendChild(pill); - } - - return pill; - } - - function showCategoryPill(segment) { - const pill = attachCategoryPill(); - if (!pill) return; - - const label = CATEGORY_LABELS[segment.category] || segment.category; - const color = CATEGORY_COLORS[segment.category] || "#008a5c"; - - pill.textContent = label; - pill.style.backgroundColor = color; - pill.style.display = "inline-flex"; - } - - function hideCategoryPill() { - const pill = document.getElementById("sb-lite-category-pill"); - if (pill) { - pill.style.display = "none"; - } - } - - function updateCategoryPill() { - const fullVideoSegment = segments.find((s) => s.actionType === "full"); - if (fullVideoSegment) { - showCategoryPill(fullVideoSegment); - } else { - hideCategoryPill(); - } - } - - // ==================== VIDEO LISTENERS ==================== - - function setupVideoListeners() { - if (!video) return; - - // Re-check Vinegar detection now that we have a video - updateVinegarDetection(); - - const videoId = video.getAttribute("data-sb-lite-initialized"); - const currentSrc = video.currentSrc || video.src; - - // Check if this is a new video or if the source changed - if (videoId === currentVideoID && lastVideoSrc === currentSrc) return; - - video.setAttribute("data-sb-lite-initialized", currentVideoID); - lastVideoSrc = currentSrc; - - log("Setting up video listeners" + (IS_VINEGAR ? " (Vinegar mode)" : "")); - - // Remove any existing listeners by cloning (for Vinegar video replacement scenario) - // We'll use named functions and track them instead - - const onPlay = () => { - log("Video play event"); - scheduleSkips(); - }; - - const onPlaying = () => { - log("Video playing event"); - scheduleSkips(); - }; - - const onSeeked = () => { - log("Video seeked event"); - lastSkippedUUID = null; - currentSegmentIndex = 0; - if (!video.paused) { - scheduleSkips(); - } - }; - - const onRateChange = () => { - scheduleSkips(); - }; - - const onPause = () => { - log("Video pause event"); - if (skipScheduleTimer) { - clearTimeout(skipScheduleTimer); - skipScheduleTimer = null; - } - stopRAFSkipLoop(); - }; - - const onDurationChange = () => { - if (segments.length > 0) { - updatePreviewBar(); - } - }; - - const onLoadedMetadata = () => { - log("Video loadedmetadata event"); - if (segments.length > 0) { - updatePreviewBar(); - } - }; - - video.addEventListener("play", onPlay); - video.addEventListener("playing", onPlaying); - video.addEventListener("seeked", onSeeked); - video.addEventListener("ratechange", onRateChange); - video.addEventListener("pause", onPause); - video.addEventListener("durationchange", onDurationChange); - video.addEventListener("loadedmetadata", onLoadedMetadata); - - // For Vinegar/iOS: also listen to timeupdate as backup - // (RAF loop is primary, but timeupdate helps when app is backgrounded) - if (IS_VINEGAR || IS_MOBILE_YOUTUBE) { - const onTimeUpdate = () => { - if (!video.paused && skippableSegments.length > 0) { - const currentTime = video.currentTime; - for (const seg of skippableSegments) { - const [startTime, endTime] = seg.segment; - if ( - currentTime >= startTime && - currentTime < endTime - SKIP_BUFFER && - lastSkippedUUID !== seg.UUID - ) { - lastSkippedUUID = seg.UUID; - log(`Skipping ${seg.category} segment (timeupdate backup)`); - skipToTime(endTime); - break; - } - } - } - }; - video.addEventListener("timeupdate", onTimeUpdate); - } - - // For Vinegar: Monitor for video element replacement - if (IS_VINEGAR) { - // Also start skip loop immediately if video is already playing - if (!video.paused && skippableSegments.length > 0) { - startRAFSkipLoop(); - } - } - } - - function findVideoElement() { - // Bilibili selectors - if (IS_BILIBILI) { - video = - document.querySelector(".bpx-player-video-area video") || - document.querySelector(".bilibili-player video") || - document.querySelector("video"); - return video; - } - - // For Vinegar (or when YouTube player is replaced), just find any video element - const anyVideo = document.querySelector("video"); - if (anyVideo) { - // Check if this looks like a Vinegar setup (no YouTube player elements) - const hasYouTubePlayer = document.querySelector("#movie_player, ytm-player") !== null; - if (!hasYouTubePlayer) { - if (!IS_VINEGAR) { - IS_VINEGAR = true; - log("Vinegar/native video detected"); - } - video = anyVideo; - return video; - } - } - - // Desktop selectors - video = - document.querySelector("video.html5-main-video") || - document.querySelector("video.video-stream") || - document.querySelector("#movie_player video"); - - // Mobile selectors - if (!video && IS_MOBILE_YOUTUBE) { - video = - document.querySelector("ytm-player video") || - document.querySelector(".player-container video") || - document.querySelector(".html5-video-container video") || - document.querySelector(".video-stream") || - document.querySelector("video[playsinline]") || - document.querySelector("video"); - } - - // Fallback - if (!video) { - video = document.querySelector("video"); - } - - return video; - } - - // ==================== MUTATION OBSERVER FOR VIDEO ==================== - - function setupVideoObserver() { - if (videoObserver) { - videoObserver.disconnect(); - } - - videoObserver = new MutationObserver((mutations) => { - // Check if video element was added or replaced - const currentVideo = document.querySelector("video"); - - if (currentVideo && currentVideo !== video) { - log("Video element change detected via observer"); - video = currentVideo; - - // Re-check Vinegar status - updateVinegarDetection(); - - if (currentVideoID) { - setupVideoListeners(); - if (segments.length > 0 && !video.paused) { - scheduleSkips(); - } - } - } else if (!currentVideo && video) { - log("Video element removed"); - video = null; - stopRAFSkipLoop(); - } - }); - - videoObserver.observe(document.body, { - childList: true, - subtree: true, - }); - } - - // ==================== NAVIGATION & INITIALIZATION ==================== - - function resetState() { - currentVideoID = null; - segments = []; - skippableSegments = []; - lastSkippedUUID = null; - currentSegmentIndex = 0; - videoDuration = 0; - lastVideoSrc = null; - - if (skipScheduleTimer) { - clearTimeout(skipScheduleTimer); - skipScheduleTimer = null; - } - - stopRAFSkipLoop(); - - hideCategoryPill(); - removePreviewBar(); - } - - async function loadSegmentsAndSetup() { - if (!currentVideoID) return; - - try { - segments = await fetchSegments(currentVideoID); - - if (segments.length > 0) { - log(`Found ${segments.length} segments for video ${currentVideoID}`); - } - - computeSkippableSegments(); - updateCategoryPill(); - updatePreviewBar(); - setupVideoListeners(); - - if (video && !video.paused) { - scheduleSkips(); - } - - // Retry preview bar attachment after a delay (for slow-loading UI) - if ((IS_MOBILE_YOUTUBE || IS_VINEGAR) && segments.length > 0) { - setTimeout(updatePreviewBar, 1000); - setTimeout(updatePreviewBar, 2000); - setTimeout(updateCategoryPill, 1000); - } - } catch (error) { - logError("Failed to load segments:", error); - } - } - - function handleVideoChangeImpl() { - const newVideoID = getVideoID(); - - if (!newVideoID || newVideoID === currentVideoID) { - return; - } - - log(`Video changed to: ${newVideoID}`); - resetState(); - currentVideoID = newVideoID; - - let attempts = 0; - const maxAttempts = 50; - - const checkVideo = setInterval(() => { - attempts++; - - // Re-check Vinegar detection on each attempt - updateVinegarDetection(); - - if (findVideoElement()) { - clearInterval(checkVideo); - log("Video element found after", attempts, "attempts"); - loadSegmentsAndSetup(); - } else if (attempts >= maxAttempts) { - clearInterval(checkVideo); - logError("Failed to find video element after max attempts"); - } - }, 100); - } - - function handleVideoChange() { - if (videoChangeDebounce) { - clearTimeout(videoChangeDebounce); - } - videoChangeDebounce = setTimeout(handleVideoChangeImpl, 50); - } - - function setupNavigationListener() { - // Standard YouTube navigation events (may not fire on mobile) - document.addEventListener("yt-navigate-finish", () => { - log("yt-navigate-finish event"); - handleVideoChange(); - }); - - document.addEventListener("yt-navigate-start", () => { - hideCategoryPill(); - removePreviewBar(); - stopRAFSkipLoop(); - }); - - // Mobile-specific events - if (IS_MOBILE_YOUTUBE) { - document.addEventListener("state-navigateend", () => { - log("state-navigateend event"); - handleVideoChange(); - }); - - document.addEventListener("yt-page-data-updated", () => { - log("yt-page-data-updated event"); - handleVideoChange(); - }); - } - - // History API interception - const originalPushState = history.pushState; - history.pushState = function (...args) { - originalPushState.apply(this, args); - log("pushState detected"); - handleVideoChange(); - }; - - const originalReplaceState = history.replaceState; - history.replaceState = function (...args) { - originalReplaceState.apply(this, args); - log("replaceState detected"); - handleVideoChange(); - }; - - window.addEventListener("popstate", () => { - log("popstate event"); - handleVideoChange(); - }); - - // URL polling fallback (essential for mobile and Vinegar) - urlPollInterval = setInterval(() => { - if (location.href !== lastUrl) { - log("URL change detected via polling:", location.href); - lastUrl = location.href; - handleVideoChange(); - } - - // For Vinegar: periodically check if video element was replaced - if (IS_VINEGAR && currentVideoID) { - const currentVideo = document.querySelector("video"); - if (currentVideo && currentVideo !== video) { - log("Video element replacement detected via polling"); - video = currentVideo; - setupVideoListeners(); - if (skippableSegments.length > 0 && !video.paused) { - scheduleSkips(); - } - } - } - }, 500); - } - - function init() { - log("Initializing SponsorBlock Lite"); - - // Initial Vinegar detection (may update later when video loads) - updateVinegarDetection(); - - log( - "Platform:", - IS_BILIBILI ? "Bilibili" : IS_VINEGAR ? "Vinegar" : IS_MOBILE_YOUTUBE ? "Mobile" : IS_MUSIC_YOUTUBE ? "Music" : "Desktop", - ); - - injectStyles(); - setupNavigationListener(); - setupVideoObserver(); - handleVideoChange(); - - // Multiple retry attempts for initial load - setTimeout(handleVideoChange, 500); - setTimeout(handleVideoChange, 1000); - setTimeout(handleVideoChange, 2000); - - // Additional retries for mobile/Vinegar - if (IS_MOBILE_YOUTUBE || IS_VINEGAR) { - setTimeout(handleVideoChange, 3000); - setTimeout(handleVideoChange, 5000); - } - - // For Vinegar: also retry after longer delays since the player loads differently - setTimeout(() => { - updateVinegarDetection(); - if (IS_VINEGAR) { - log("Late Vinegar detection check"); - handleVideoChange(); - } - }, 4000); - } - - // ==================== START ==================== - - if (document.readyState === "loading") { - document.addEventListener("DOMContentLoaded", init); - } else { - init(); - } -})(); diff --git a/home/programs/qutebrowser/greasemonkey/startpage-no-ads.user.js b/home/programs/qutebrowser/greasemonkey/startpage-no-ads.user.js deleted file mode 100644 index 90b2357..0000000 --- a/home/programs/qutebrowser/greasemonkey/startpage-no-ads.user.js +++ /dev/null @@ -1,18 +0,0 @@ -// ==UserScript== -// @name Startpage - Hide Ads -// @match https://www.startpage.com/* -// @run-at document-start -// ==/UserScript== - -new MutationObserver(function(mutations) { - mutations.forEach((mutation) => { - if (mutation.type === 'childList') { - mutation.addedNodes.forEach((node) => { - if (node.nodeType === 1 && node.nodeName === 'DIV' && node.id === 'gcsa-top') { - node.remove(); - this.disconnect(); - } - }); - } - }); -}).observe(document, { childList: true, subtree: true }); diff --git a/home/programs/qutebrowser/greasemonkey/tracking-token-stripper.user.js b/home/programs/qutebrowser/greasemonkey/tracking-token-stripper.user.js deleted file mode 100644 index a425a03..0000000 --- a/home/programs/qutebrowser/greasemonkey/tracking-token-stripper.user.js +++ /dev/null @@ -1,191 +0,0 @@ -// ==UserScript== -// @name TrackingTokenStripper -// @version 1.4 -// @description Remove most of the annoying tracking token from URL parameters -// @license MIT -// @homepage https://blog.miniasp.com/ -// @homepageURL https://blog.miniasp.com/ -// @website https://www.facebook.com/will.fans -// @source https://github.com/doggy8088/TrackingTokenStripper/raw/refs/heads/master/TrackingTokenStripper.user.js -// @namespace https://github.com/doggy8088/TrackingTokenStripper -// @author Will Huang -// @match *://*/* -// @run-at document-start -// ==/UserScript== - -(function () { - 'use strict'; - - const oldReplaceState = history.replaceState; - history.replaceState = function replaceState() { - let ret = oldReplaceState.apply(this, arguments); - window.dispatchEvent(new Event('replacestate')); - window.dispatchEvent(new Event('locationchange')); - return ret; - }; - - window.addEventListener('popstate', () => { - window.dispatchEvent(new Event('locationchange')); - }); - - window.addEventListener('locationchange', function () { - executeActions(); - }); - - executeActions(); - - let id = setInterval(executeActions, 500); - - setTimeout(() => { clearInterval(id); }, 2000); - - function executeActions() { - - var s = TrackingTokenStripper(location.href) - // facebook - .remove('fbclid') - .removeByDomain('www.facebook.com', 'privacy_mutation_token') - .removeByDomain('www.facebook.com', 'acontext') - .removeByDomain('www.facebook.com', '__xts__[0]') - .removeByDomain('www.facebook.com', 'notif_t') - .removeByDomain('www.facebook.com', 'notif_id') - .removeByDomain('www.facebook.com', 'notif_ids[0]') - .removeByDomain('www.facebook.com', 'notif_ids[1]') - .removeByDomain('www.facebook.com', 'notif_ids[2]') - .removeByDomain('www.facebook.com', 'notif_ids[3]') - .removeByDomain('www.facebook.com', 'ref', 'notif') - .removeByDomain('www.facebook.com', 'ref=watch_permalink') - - // Dropbox - .removeByDomain('www.dropbox.com', '_ad') - .removeByDomain('www.dropbox.com', '_camp') - .removeByDomain('www.dropbox.com', '_tk') - - // YouTube - // https://youtu.be/4f-Y9G5ENPc?si=SHSu2hEdSbXGy4_Q - // https://www.youtube.com/embed/4f-Y9G5ENPc?si=GQFJV_nKMXxpiQb6 - .removeByDomain('youtu.be', 'si') - .removeByDomain('www.youtube.com', 'si') - - // Google Analytics - // https://support.google.com/analytics/answer/1033863?hl=en - .remove('utm_id') - .remove('utm_source') - .remove('utm_medium') - .remove('utm_campaign') - .remove('utm_term') - .remove('utm_content') - .remove('_ga') - - // GA - others - .remove('utm_campaignid') - .remove('utm_cid') - .remove('utm_reader') - .remove('utm_referrer') - .remove('utm_name') - .remove('utm_social') - .remove('utm_social-type') - .remove('gclid') - .remove('igshid') - .remove('_hsenc') - .remove('_hsmi') - .remove('mc_cid') - .remove('mc_eid') - .remove('mkt_tok') - .remove('yclid') - .remove('_openstat') - - // devblogs.microsoft.com - .removeByDomain('devblogs.microsoft.com', 'utm_issue') - .removeByDomain('devblogs.microsoft.com', 'utm_position') - .removeByDomain('devblogs.microsoft.com', 'utm_topic') - .removeByDomain('devblogs.microsoft.com', 'utm_section') - .removeByDomain('devblogs.microsoft.com', 'utm_cta') - .removeByDomain('devblogs.microsoft.com', 'utm_description') - .removeByDomain('devblogs.microsoft.com', 'ocid') - - // Microsoft - .remove('wt.mc_id') - .removeByDomain('learn.microsoft.com', 'ocid') - .removeByDomain('learn.microsoft.com', 'redirectedfrom') - - .removeByDomain('azure.microsoft.com', 'OCID') - .removeByDomain('azure.microsoft.com', 'ef_id') - - .removeByDomain('www.msn.com', 'ocid') - .removeByDomain('www.msn.com', 'cvid') - - // bilibili - .removeByDomain('www.bilibili.com', 'share_source') - .removeByDomain('www.bilibili.com', 'share_medium') - - // Others - .remove('__tn__') - .remove('gclsrc') - .remove('itm_source') - .remove('itm_medium') - .remove('itm_campaign') - .remove('mc') // sendgrid.com - .remove('mcd') // sendgrid.com - .remove('cvosrc') // sendgrid.com - .remove('cr_cc') // https://blogs.microsoft.com/ - - .remove('sc_channel') - .remove('sc_campaign') - .remove('sc_geo') - .remove('trk') - .remove('sc_publisher') - .remove('trkCampaign') - .remove('sc_outcome') - .remove('sc_country') - - .remove('__hstc') - .remove('__hssc') - .remove('__hsfp') - .remove('_gl') - - // Yahoo News - .remove('guce_referrer') - .remove('guce_referrer_sig') - - .toString(); - - if (s && location.href !== s) { - // console.log('Changing URL', s); - // location.href = s; - oldReplaceState.apply(history, [{}, '', s]); - } - - function TrackingTokenStripper(url) { - const parsedUrl = new URL(url); - return { - remove(name, value) { - if (parsedUrl.searchParams.has(name)) { - if (value && value === parsedUrl.searchParams.get(name)) { - parsedUrl.searchParams.delete(name); - } - if (!value) { - parsedUrl.searchParams.delete(name); - } - } - return TrackingTokenStripper(parsedUrl.toString()); - }, - removeByDomain(domain, name) { - if (parsedUrl.hostname.toLocaleLowerCase() === domain.toLocaleLowerCase()) { - if (name.indexOf('=') >= 0) { - var [key, value] = name.split("="); - return this.remove(key, value); - } else { - return this.remove(name); - } - } else { - return this; - } - }, - toString() { - return parsedUrl.toString(); - } - } - } - } - -})(); diff --git a/home/programs/qutebrowser/userscripts.nix b/home/programs/qutebrowser/userscripts.nix index 3ce9611..57a432f 100644 --- a/home/programs/qutebrowser/userscripts.nix +++ b/home/programs/qutebrowser/userscripts.nix @@ -1,27 +1,67 @@ -{ +{pkgs, ...}: { xdg.dataFile = { - # Startpage - hide sponsored results - "qutebrowser/greasemonkey/startpage-no-ads.user.js".source = - ./greasemonkey/startpage-no-ads.user.js; + # Startpage: hide sponsored results (custom script, no upstream) + "qutebrowser/greasemonkey/startpage-no-ads.user.js".text = '' + // ==UserScript== + // @name Startpage - Hide Ads + // @match https://www.startpage.com/* + // @run-at document-start + // ==/UserScript== - # Return YouTube Dislike - "qutebrowser/greasemonkey/return-youtube-dislike.user.js".source = - ./greasemonkey/return-youtube-dislike.user.js; + new MutationObserver(function(mutations) { + mutations.forEach((mutation) => { + if (mutation.type === 'childList') { + mutation.addedNodes.forEach((node) => { + if (node.nodeType === 1 && node.nodeName === 'DIV' && node.id === 'gcsa-top') { + node.remove(); + this.disconnect(); + } + }); + } + }); + }).observe(document, { childList: true, subtree: true }); + ''; - # SponsorBlock Lite - auto-skip sponsors on YouTube - "qutebrowser/greasemonkey/sponsorblock-lite.user.js".source = - ./greasemonkey/sponsorblock-lite.user.js; + # Return YouTube Dislike: restore dislike counts on YouTube + "qutebrowser/greasemonkey/return-youtube-dislike.user.js".source = pkgs.fetchurl { + url = "https://update.greasyfork.org/scripts/436115/Return%20YouTube%20Dislike.user.js"; + hash = "sha256-P7dK3v1WbSQaJUo73iHrezkXE+6BOdIuDk/D6GJwwbM="; + }; - # Don't Track Me Google - remove Google tracking redirects - "qutebrowser/greasemonkey/dont-track-me-google.user.js".source = - ./greasemonkey/dont-track-me-google.user.js; + # SponsorBlock Lite: auto-skip sponsors on YouTube + "qutebrowser/greasemonkey/sponsorblock-lite.user.js".source = pkgs.fetchurl { + url = "https://update.greasyfork.org/scripts/560869/SponsorBlock%20Lite.user.js"; + hash = "sha256-8DTIRMn+cy/gZeeHa6xJDomQ5QN3lnaK0DG5ZcS5d00="; + }; - # I don't care about cookies - auto-dismiss cookie banners - "qutebrowser/greasemonkey/i-dont-care-about-cookies.user.js".source = - ./greasemonkey/i-dont-care-about-cookies.user.js; + # Don't Track Me Google: remove Google tracking redirects + "qutebrowser/greasemonkey/dont-track-me-google.user.js".source = pkgs.fetchurl { + url = "https://update.greasyfork.org/scripts/428243/Don%27t%20track%20me%20Google.user.js"; + hash = "sha256-yEjBZprSjHyDRpd+TJ1vUsSYHrwLspQOztpKunBLPig="; + }; - # TrackingTokenStripper - remove tracking params from URLs (utm_*, fbclid, etc.) - "qutebrowser/greasemonkey/tracking-token-stripper.user.js".source = - ./greasemonkey/tracking-token-stripper.user.js; + # I don't care about cookies: auto-dismiss cookie banners + "qutebrowser/greasemonkey/i-dont-care-about-cookies.user.js".source = pkgs.fetchurl { + url = "https://update.greasyfork.org/scripts/522645/I%20don%27t%20care%20about%20cookies.user.js"; + hash = "sha256-Ij7HyBfWemAO0EAGKYxWPPv7OX5twNtGKKPGhOAxM9w="; + }; + + # TrackingTokenStripper: remove tracking params from URLs (utm_*, fbclid, etc.) + "qutebrowser/greasemonkey/tracking-token-stripper.user.js".source = pkgs.fetchurl { + url = "https://github.com/doggy8088/TrackingTokenStripper/raw/refs/heads/master/TrackingTokenStripper.user.js"; + hash = "sha256-EX8xN2Vd8SE/RvMcF/YSGN4Epa5cm355IeD9agTP2h4="; + }; + + # Bypass Paywalls Clean: bypass news site paywalls (Le Monde, NY Times, etc.) + "qutebrowser/greasemonkey/bypass-paywalls-clean.user.js".source = pkgs.fetchurl { + url = "https://gitflic.ru/project/magnolia1234/bypass-paywalls-clean-filters/blob/raw?file=userscript/bpc.en.user.js"; + hash = "sha256-dUgwBkJi5Jrtpw5HJydRjY9xBpZXyD0ZtA/+hDzF97s="; + }; + + # Anti-Adblock Fuckoff: remove anti-adblock modals and restore scroll + "qutebrowser/greasemonkey/anti-adblock-fuckoff.user.js".source = pkgs.fetchurl { + url = "https://update.greasyfork.org/scripts/397070/Anti-AdBlocker%20Fuckoff.user.js"; + hash = "sha256-vFeWxqMg0gPHP7mGNZO9e9Me/2Z81biR5JEXC/Ct4fA="; + }; }; }