├── README.md ├── reddit_apollo.js ├── autoclick_shared.txt ├── hn_georgia.css ├── smartbanner_block.txt ├── hyperweb_feature_flags.json ├── open_hyperweb.js ├── autoclick_google_consent.txt ├── delete_annoyances_mobile.txt ├── barrel_roll.js ├── sci_hub_injector.js ├── google_cookie_consent.txt ├── pip.js ├── ios_feature_flags.json ├── roam_quick_note.js ├── self_block.js ├── gm_darkmode.js ├── yt_redirect.js ├── shake_page.js ├── disable_amp.js ├── remove_upsells.js ├── open_top_results_gm.js ├── autoclick_annoyances_mobile.txt ├── yt_skip_add.js ├── obsidian_web_clip.js ├── open_top_results.js ├── probable_adblock.js ├── probable_autoclick.js ├── LICENSE ├── disable-auto-play.js ├── wordle-solver.js ├── yt_sponsor_block.js └── invert-colors-at-start.js /README.md: -------------------------------------------------------------------------------- 1 | # scripts -------------------------------------------------------------------------------- /reddit_apollo.js: -------------------------------------------------------------------------------- 1 | window.location.href = `apollo://${window.location.host}${window.location.pathname}`; 2 | -------------------------------------------------------------------------------- /autoclick_shared.txt: -------------------------------------------------------------------------------- 1 | ! Title: Autoclick Shared 2 | ! Homepage: 3 | ! Licence: https://creativecommons.org/licenses/by-sa/4.0/ 4 | ! Version: 20211110 5 | -------------------------------------------------------------------------------- /hn_georgia.css: -------------------------------------------------------------------------------- 1 | tbody tr td { 2 | background-color: #ffffff; 3 | } 4 | 5 | a.storylink { 6 | font-family: georgia; 7 | font-size: 20px; 8 | margin-top: 30px; 9 | } 10 | 11 | -------------------------------------------------------------------------------- /smartbanner_block.txt: -------------------------------------------------------------------------------- 1 | [Adblock Plus 2.0] 2 | ! Title: Smart banner block 3 | ! Homepage: 4 | ! Licence: https://creativecommons.org/licenses/by-sa/4.0/ 5 | ! Version: 1 6 | 7 | *##div.smartbanner 8 | -------------------------------------------------------------------------------- /hyperweb_feature_flags.json: -------------------------------------------------------------------------------- 1 | { 2 | "features": [ 3 | { 4 | "name": "ios_referral_system", 5 | "enabled": false 6 | }, 7 | { 8 | "name": "ios_notification_system", 9 | "enabled": false 10 | } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /open_hyperweb.js: -------------------------------------------------------------------------------- 1 | try { 2 | const array = Array.from(document.getElementsByTagName('a')); 3 | array.forEach(a => { 4 | if (a.href.includes('insightbrowser://open-augmentation')) { 5 | a.href = a.href.replace('insightbrowser', 'hyperweb'); 6 | } 7 | }) 8 | } catch {} 9 | -------------------------------------------------------------------------------- /autoclick_google_consent.txt: -------------------------------------------------------------------------------- 1 | [Adblock Plus 2.0] 2 | ! Title: Google EU Cookie Consent 3 | ! Homepage: 4 | ! Licence: https://creativecommons.org/licenses/by-sa/4.0/ 5 | ! Version: 20210303 6 | 7 | google.*##.VDity button:last-child 8 | m.youtube.*##.dialog-buttons c3-material-button:last-child button 9 | -------------------------------------------------------------------------------- /delete_annoyances_mobile.txt: -------------------------------------------------------------------------------- 1 | [Adblock Plus 2.0] 2 | ! Title: Mobile Annoyances Delete 3 | ! Homepage: 4 | ! Licence: https://creativecommons.org/licenses/by-sa/4.0/ 5 | ! Version: 20210303 6 | 7 | www.reddit.com##.TopNav .TopNav__promoButton 8 | www.quora.com##.spacing_log_header_main button 9 | ##.dualpartinterstitial 10 | ##.topbutton 11 | ##.xpromopill 12 | ##.xpromopopup 13 | ##.addlinkbar 14 | ##.upsell_banner 15 | www.google.com##.FAVrq.mB14jb 16 | -------------------------------------------------------------------------------- /barrel_roll.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | (() => { 4 | var doABarrelRoll = function(){var a="-webkit-",b='transform:rotate(1turn);',c='transition:4s;';document.head.innerHTML+='` 26 | 27 | document.body.className += ' shake'; 28 | } 29 | 30 | if (document.readyState === 'complete' || document.readyState === 'interactive') { 31 | doABarrelRoll(); 32 | } else { 33 | document.addEventListener( 34 | 'DOMContentLoaded', 35 | doABarrelRoll, 36 | false, 37 | ); 38 | } 39 | })(); 40 | -------------------------------------------------------------------------------- /disable_amp.js: -------------------------------------------------------------------------------- 1 | (() => { 2 | const isProbableAmpPage = () => { 3 | const url = document.location; 4 | 5 | return url.pathname.includes('/amp/') 6 | || (url.hostname.startsWith('amp.') && url.hostname.split('.').length > 2); 7 | }; 8 | 9 | const hasAmpAttr = () => { 10 | return document.documentElement.attributes.getNamedItem('amp') 11 | || document.documentElement.attributes.getNamedItem('\u26A1'); 12 | } 13 | 14 | const redirectIfInAmpPage = () => { 15 | if (!isProbableAmpPage() || !hasAmpAttr()) { 16 | return false; 17 | } 18 | 19 | const canonicalEl = document.head.querySelector("link[rel~='canonical'][href]"); 20 | if (!canonicalEl) { 21 | return false; 22 | } 23 | 24 | try { 25 | const newURL = new URL(canonicalEl.href); 26 | if (newURL.toString() === document.referrer || document.referrer === document.location.toString()) { 27 | return false; 28 | } 29 | 30 | window.location.replace(newURL); 31 | return true; 32 | } catch { 33 | return false; 34 | } 35 | } 36 | 37 | if (redirectIfInAmpPage()) { return; } 38 | 39 | const cleanupAmp = () => { 40 | document.querySelectorAll('a.amp_r').forEach(function(a) { 41 | a.href = a.getAttribute('href'); 42 | if (!a.href.indexOf('?') !== -1) a.href = a.href + '?'; 43 | a.removeAttribute('data-amp'); 44 | a.removeAttribute('data-amp-cur'); 45 | a.removeAttribute('ping'); 46 | }); 47 | 48 | document.querySelectorAll('span[aria-label=\"AMP logo\"]').forEach(function(a) { 49 | a.style.display='none'; 50 | }); 51 | } 52 | 53 | document.addEventListener('DOMNodeInserted', cleanupAmp); 54 | cleanupAmp(); 55 | })(); 56 | -------------------------------------------------------------------------------- /remove_upsells.js: -------------------------------------------------------------------------------- 1 | // not exactly ad blocking but removing known bad components 2 | let toRemove = { 3 | 'reddit.com': ['.FooterAppUpsell', '.upsell_banner', '.TopNav__promoButton'], 4 | 'google.com': ['.FooterAppUpsell', '.upsell_banner', '.TopNav__promoButton'], 5 | 'nytimes.com': ['.expanded-dock'], 6 | 'duckduckgo.com': ['.js-atb-banner', '.atb-banner'], 7 | } 8 | 9 | let toClick = { 10 | 'quora.com': ['.qu-bg--blue button'], 11 | 'nytimes.com': ['.ReactModal__Overlay button', '#vi_welcome_close'], 12 | 'instagram.com': ['button span[aria-label=Close]'], 13 | 'google.com': ['div[aria-label=promo] g-flat-button', '#continueButton', '.XPromoPopup__actions .XPromoPopup__action:nth-of-type(2) button', '.XPromoPill__closeButton'], 14 | 'reddit.com': ['#continueButton', '.XPromoPopup__actions .XPromoPopup__action:nth-of-type(2) button', '.XPromoPill__closeButton'] 15 | } 16 | 17 | const waitUntilElementExists = (selector, callback) => { 18 | const el = document.querySelector(selector); 19 | if (el){ 20 | return callback(el); 21 | } 22 | setTimeout(() => waitUntilElementExists(selector, callback), 500); 23 | } 24 | 25 | const removeElement = (el) => { 26 | el.parentNode.removeChild(el) 27 | } 28 | 29 | const clickElement = (el) => { 30 | el.click() 31 | } 32 | 33 | let hostname = new URL(window.location.href).hostname; 34 | if (hostname.startsWith('www.')) { 35 | hostname = hostname.slice(4) 36 | } 37 | if (hostname in toRemove) { 38 | for(const element of toRemove[hostname]) { 39 | waitUntilElementExists(element, (el) => removeElement(el)); 40 | } 41 | } 42 | if (hostname in toClick) { 43 | for(const element of toClick[hostname]) { 44 | waitUntilElementExists(element, (el) => clickElement(el)); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /open_top_results_gm.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name Open top 5 3 | // @version 0.0.1 4 | // @description Open new tabs for the first 5 results in a search result page. 5 | // @author Felipe 6 | // @match * 7 | // @resource queries https://raw.githubusercontent.com/insightbrowser/augmentations/main/serp_query_selectors.json 8 | // @grant GM.openInTab 9 | // @grant GM.registerButton 10 | // @grant GM.getResourceText 11 | // @noframes 12 | // ==/UserScript== 13 | 14 | (() => { 15 | const LINKS_SELECTOR = '.mnr-c a.cz3goc.BmP5tf'; 16 | const LINKS_LIMIT = 5; 17 | 18 | const isMobile = window.navigator.userAgent.toLocaleLowerCase().includes('iphone'); 19 | const queriesStr = GM.getResourceText('queries'); 20 | const queries = JSON.parse(queriesStr); 21 | const se = Object 22 | .values(queries) 23 | .find((query) => { 24 | const se = query.search_engine_json; 25 | 26 | if (!se || !se.is_web_search) { return false; } 27 | 28 | return se.match_prefix && document.location.href.match(se.match_prefix); 29 | }); 30 | 31 | if (!se) { return; } 32 | 33 | const linkQuery = se.querySelector[isMobile ? 'phone' : 'desktop']; 34 | 35 | if (!linkQuery) { return; } 36 | 37 | const icon = ''; 38 | GM.registerButton('open-top-5', 'Open top 5 results', icon, () => { 39 | Array 40 | .from(document.querySelectorAll(linkQuery)) 41 | .map((e) => { 42 | if (e instanceof HTMLAnchorElement) { 43 | return e; 44 | } 45 | 46 | return e.closest('a'); 47 | }) 48 | .filter((l) => !!l) 49 | .map((l) => l.href) 50 | .filter((l) => !!l) 51 | .slice(0, LINKS_LIMIT).forEach(GM.openInTab); 52 | }); 53 | })(); 54 | -------------------------------------------------------------------------------- /autoclick_annoyances_mobile.txt: -------------------------------------------------------------------------------- 1 | [Adblock Plus 2.0] 2 | ! Title: Mobile Annoyances Autoclick 3 | ! Homepage: 4 | ! Licence: https://creativecommons.org/licenses/by-sa/4.0/ 5 | ! Version: 20210303 6 | 7 | example.com###a 8 | m.youtube.com##.mealbar-promo-renderer .ytm-mealbar-promo-button:first-of-type button 9 | www.reddit.com##.AppSelectorModal__actionButton #continueButton 10 | www.reddit.com##.XPromoPill__closeButton 11 | www.reddit.com##button.XPromoPopup__actionButton 12 | www.quora.com##.kPwyaT button 13 | www.nytimes.com##.ReactModal__Overlay button 14 | www.nytimes.com###vi_welcome_close 15 | www.nytimes.com##.gdpr.expanded-dock button[data-testid="expanded-dock-btn-selector"] 16 | www.nytimes.com##.banner__container .banner__container__closeAsset 17 | instagram.com##button span[aria-label=Close] 18 | instagram.com##.Z_Gl2 button.wpO6b 19 | www.bbc.*##button.tp-close.tp-active 20 | www.google.*###stUuGf div div.FAVrq.VDgVie.it9gVe.gdwOPe.mB14jb.eofmDb div.uysxde.KKUuQd div.LXauYe div.MSvubc span.i6Jlpe.z1asCe.wuXmqc 21 | zoom.us###onetrust-banner-sdk .onetrust-close-btn-handler 22 | tiktok.com##.guide-container .not-now 23 | weather.com###truste-consent-content #truste-consent-button 24 | *###onetrust-consent-sdk .banner-close-button 25 | *###onetrust-banner-sdk #onetrust-accept-btn-handler 26 | espn.com##div.ad-slot.ad-slot-overlay iframe%hw%div.GoogleActiveViewElement section#overlay div#overlaybg p a.sprite.close 27 | search.yahoo.com##iframe#guce-inline-consent-iframe%hw%div#shadow-wrapper > div.reg > form.consent-form > button.btn.consent-button.cta 28 | accuweather.com##div.template-root div.privacy-policy-banner div.banner-body div.banner-button.policy-accept 29 | zillow.com##.sc-hKgILt.gTLZXx .StyledButton-c11n-8-63-0__sc-wpcbcc-0.hUGNhV.sc-fFubgz.bjNVbG 30 | stackoverflow.com##button.js-accept-cookies.js-consent-banner-hide 31 | globo.com##button.cookie-banner-lgpd_accept-button 32 | ups.com##div.implicit_privacy_prompt.implicit_consent button.close_btn_thick span.icon.ups-icon-close 33 | kohls.com###hp-banner2-drawer .drawer-controls button 34 | lowes.com###branch-banner-iframe%hw%.branch-banner-close 35 | t-mobile.com##.xpr-cookieModal .phx-modal__close 36 | healthline.com###ccpa-banner .accept 37 | healthline.com##.css-yya393 .window-close-button 38 | healthline.com##.css-pdceuf .icon-hl-close-thick 39 | jivox.com###catapult-cookie-bar #catapultCookie 40 | cnbc.com##.fEy1Z2XT ._4ao5eF8B 41 | yelp.com##.banner__09f24__A12gg .icon--16-close-v2 42 | archiveofourown.org###tos_prompt .confirmation #tos_agree 43 | archiveofourown.org###tos_prompt .submit #accept_tos 44 | -------------------------------------------------------------------------------- /yt_skip_add.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name Skips YouTube ads 3 | // @version 0.0.1 4 | // @description Automatically skips YouTube ads 5 | // @author Hyperweb 6 | // @match * 7 | // @noframes 8 | // ==/UserScript== 9 | 10 | const ADS_CLASS = 'video-ads'; 11 | const ADS_SELECTOR = `.${ ADS_CLASS }`; 12 | const SKIP_SELECTOR = '.ytp-ad-skip-button'; 13 | const VIDEO_SELECTOR = '.html5-main-video'; 14 | const BANNER_OVERLAY_CLASS = 'ytp-ad-overlay-close-button'; 15 | 16 | const run = () => { 17 | const tryClick = (attempt, buttonOnly) => { 18 | if (attempt > 5) { return; } 19 | 20 | const button = document.querySelectorAll(SKIP_SELECTOR)[0]; 21 | const video = document.querySelectorAll(VIDEO_SELECTOR)[0]; 22 | 23 | if (video && !buttonOnly) { 24 | video.currentTime = video.duration; 25 | } 26 | 27 | if (button) { 28 | button.click(); 29 | } else { 30 | setTimeout(() => tryClick(attempt + 1, true), 100);; 31 | } 32 | }; 33 | 34 | const skip = () => { 35 | if (document.querySelectorAll(ADS_SELECTOR)[0]?.innerHTML !== '') { 36 | let banner = false; 37 | 38 | for(let i = 0; i < document.getElementsByClassName(BANNER_OVERLAY_CLASS).length; i++) { 39 | document.getElementsByClassName(BANNER_OVERLAY_CLASS)[i]?.click(); 40 | banner = true; 41 | } 42 | 43 | if (banner === false) { 44 | tryClick(0); 45 | } 46 | } 47 | } 48 | 49 | const obsVideoAds = new MutationObserver((mutations) => { 50 | mutations.forEach((mutation) => { 51 | if (mutation.addedNodes.length === 0) { return; } 52 | skip(); 53 | }); 54 | }); 55 | 56 | 57 | const obs = new MutationObserver((mutations) => { 58 | mutations.forEach((mutation) => { 59 | mutation.addedNodes.forEach((added) => { 60 | const target = added.classList?.contains(ADS_CLASS) ? added : added?.querySelector?.(ADS_SELECTOR); 61 | 62 | if (!target) { return; } 63 | 64 | obsVideoAds.disconnect(); 65 | obsVideoAds.observe(target, { 66 | childList: true, 67 | subtree: true, 68 | }); 69 | 70 | skip(); 71 | }); 72 | }); 73 | }); 74 | 75 | const container = document.querySelector(ADS_SELECTOR); 76 | 77 | if (container) { 78 | skip(); 79 | obsVideoAds.observe(container, { 80 | childList: true, 81 | subtree: true, 82 | }); 83 | } else { 84 | obs.observe(document.body, { 85 | childList: true, 86 | subtree: true, 87 | }); 88 | } 89 | 90 | }; 91 | 92 | run(); 93 | -------------------------------------------------------------------------------- /obsidian_web_clip.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name Obsidian Web Clipper 3 | // @version 0.0.1 4 | // @description Save articles and pages from the web 5 | // @author Felipe 6 | // @match * 7 | // @grant GM.registerButton 8 | // @noframes 9 | // ==/UserScript== 10 | 11 | Promise.all([import('https://unpkg.com/turndown@6.0.0?module'), import('https://unpkg.com/@tehshrike/readability@0.2.0'), ]).then(async ([{ 12 | default: Turndown 13 | }, { 14 | default: Readability 15 | }]) => { 16 | /* Optional vault name */ 17 | const vault = ""; 18 | 19 | /* Optional folder name such as "Clippings/" */ 20 | const folder = ""; 21 | 22 | /* Optional tags */ 23 | const tags = "#clippings"; 24 | 25 | const run = () => { 26 | function getSelectionHtml() { 27 | var html = ""; 28 | if (typeof window.getSelection != "undefined") { 29 | var sel = window.getSelection(); 30 | if (sel.rangeCount) { 31 | var container = document.createElement("div"); 32 | for (var i = 0, len = sel.rangeCount; i < len; ++i) { 33 | container.appendChild(sel.getRangeAt(i).cloneContents()); 34 | } 35 | html = container.innerHTML; 36 | } 37 | } else if (typeof document.selection != "undefined") { 38 | if (document.selection.type == "Text") { 39 | html = document.selection.createRange().htmlText; 40 | } 41 | } 42 | return html; 43 | } 44 | 45 | const selection = getSelectionHtml(); 46 | 47 | const { 48 | title, 49 | byline, 50 | content 51 | } = new Readability(document.cloneNode(true)).parse(); 52 | 53 | function getFileName(fileName) { 54 | var userAgent = window.navigator.userAgent, 55 | platform = window.navigator.platform, 56 | windowsPlatforms = ['Win32', 'Win64', 'Windows', 'WinCE']; 57 | 58 | if (windowsPlatforms.indexOf(platform) !== -1) { 59 | fileName = fileName.replace(':', '').replace(/[/\\?%*|"<>]/g, '-'); 60 | } else { 61 | fileName = fileName.replace(':', '').replace(/\//g, '-').replace(/\\/g, '-'); 62 | } 63 | return fileName; 64 | } 65 | const fileName = getFileName(title); 66 | 67 | if (selection) { 68 | var markdownify = selection; 69 | } else { 70 | var markdownify = content; 71 | } 72 | 73 | if (vault) { 74 | var vaultName = '&vault=' + encodeURIComponent(`${vault}`); 75 | } else { 76 | var vaultName = ''; 77 | } 78 | 79 | const markdownBody = new Turndown({ 80 | headingStyle: 'atx', 81 | hr: '---', 82 | bulletListMarker: '-', 83 | codeBlockStyle: 'fenced', 84 | emDelimiter: '*', 85 | }).turndown(markdownify); 86 | 87 | var date = new Date(); 88 | 89 | function convertDate(date) { 90 | var yyyy = date.getFullYear().toString(); 91 | var mm = (date.getMonth()+1).toString(); 92 | var dd = date.getDate().toString(); 93 | var mmChars = mm.split(''); 94 | var ddChars = dd.split(''); 95 | return yyyy + '-' + (mmChars[1]?mm:"0"+mmChars[0]) + '-' + (ddChars[1]?dd:"0"+ddChars[0]); 96 | } 97 | 98 | const today = convertDate(date); 99 | 100 | const fileContent = 101 | "author:: " + byline + "\n" 102 | + "source:: [" + title + "](" + document.URL + ")\n" 103 | + "clipped:: [[" + today + "]]\n" 104 | + "published:: \n\n" 105 | + tags + "\n\n" 106 | + markdownBody ; 107 | 108 | document.location.href = "obsidian://new?" 109 | + "file=" + encodeURIComponent(folder + fileName) 110 | + "&content=" + encodeURIComponent(fileContent) 111 | + vaultName ; 112 | }; 113 | 114 | GM.registerButton({ 115 | id: 'obsidia-web-clip', 116 | caption: 'Send clip to Obsidian', 117 | callback: run, 118 | }); 119 | }); 120 | -------------------------------------------------------------------------------- /open_top_results.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name Open top 5 3 | // @version 0.0.1 4 | // @description Open new tabs for the first 5 results in a search result page. 5 | // @author Felipe 6 | // @match * 7 | // @grant GM.openInTab 8 | // ==/UserScript== 9 | 10 | (async () => { 11 | const COUNTER_SELECTOR = '.hyperweb-notification-center-counter span'; 12 | const SHORTCUTS_SELECTOR = '.hyperweb-notification-center-shortcuts'; 13 | const FULL_MESSAGE_SELECTOR = '.hyperweb-message-notification'; 14 | const QUERIES_URL = 'https://raw.githubusercontent.com/insightbrowser/augmentations/main/serp_query_selectors.json'; 15 | const LINKS_LIMIT = 5; 16 | 17 | const isMobile = window.navigator.userAgent.toLocaleLowerCase().includes('iphone'); 18 | const request = await fetch(QUERIES_URL); 19 | const queries = await request.json(); 20 | const se = Object 21 | .values(queries) 22 | .find((query) => { 23 | const se = query.search_engine_json; 24 | 25 | if (!se || !se.is_web_search) { return false; } 26 | 27 | return se.match_prefix && document.location.href.match(se.match_prefix); 28 | }); 29 | 30 | if (!se) { return; } 31 | 32 | const linkQuery = se.querySelector[isMobile ? 'phone' : 'desktop']; 33 | 34 | if (!linkQuery) { return; } 35 | 36 | const buildAnchor = (text) => { 37 | const icon = ''; 38 | const anchor = document.createElement('a'); 39 | anchor.classList.add('hw-link'); 40 | anchor.innerHTML = [ icon, text ].filter((el) => !!el).join(' '); 41 | anchor.addEventListener('click', () => { 42 | Array.from(document.querySelectorAll(linkQuery)) 43 | .map((l) => l.href) 44 | .filter((l) => !!l) 45 | .slice(0, LINKS_LIMIT).forEach(GM.openInTab); 46 | }); 47 | 48 | return anchor; 49 | }; 50 | 51 | const waitForFullMessage = () => { 52 | const fullMessage = document.querySelector(FULL_MESSAGE_SELECTOR); 53 | 54 | if (fullMessage) { 55 | const children = Array.from(fullMessage.childNodes) 56 | const unwantedChildren = children 57 | .filter((c) => c.textContent.trim() === 'or' || c.textContent.trim() === ',') 58 | .filter((c) => !(c instanceof HTMLAnchorElement)); 59 | const requiredDhildren = children 60 | .filter((c) => c.textContent.trim() !== 'or' && c.textContent.trim() !== ',') 61 | const anchors = requiredDhildren.filter((el) => el instanceof HTMLAnchorElement); 62 | 63 | unwantedChildren.forEach((a) => a.remove()); 64 | anchors.forEach((a) => a.remove()); 65 | anchors.push(buildAnchor('Open top 5 results')); 66 | 67 | anchors.forEach((a, index) => { 68 | if (index === 0) { 69 | fullMessage.insertBefore(a, requiredDhildren[1].nextSibling); 70 | } else { 71 | const separator = document.createTextNode(index === anchors.length - 1 ? ' or ' : ', '); 72 | fullMessage.insertBefore(separator, anchors[index - 1].nextSibling); 73 | fullMessage.insertBefore(a, separator.nextSibling); 74 | } 75 | }); 76 | } else { 77 | setTimeout(waitForFullMessage, 50); 78 | } 79 | }; 80 | 81 | const waitForShortcuts = () => { 82 | const shortcuts = document.querySelector(SHORTCUTS_SELECTOR); 83 | 84 | if (shortcuts) { 85 | shortcuts.appendChild(buildAnchor()); 86 | 87 | const counter = document.querySelector(COUNTER_SELECTOR); 88 | 89 | if (counter && counter.textContent) { 90 | counter.textContent = parseInt(counter.textContent) + 1; 91 | } 92 | } else { 93 | setTimeout(waitForShortcuts, 50); 94 | } 95 | }; 96 | 97 | waitForFullMessage(); 98 | waitForShortcuts(); 99 | })(); 100 | -------------------------------------------------------------------------------- /probable_adblock.js: -------------------------------------------------------------------------------- 1 | const adBlockers = [ 2 | { 3 | "site": "twitter.com", 4 | "adText": "Promoted", 5 | "adElementSelector": "article" 6 | }, 7 | { 8 | "site": "facebook.com", 9 | "adText": "Sponsored", 10 | "adElementSelector": "article" 11 | }, 12 | { 13 | "site": "reddit.com", 14 | "adText": "Promoted, Sponsored", 15 | "adElementSelector": "article" 16 | }, 17 | { 18 | "site": "m.facebook.com", 19 | "adText": "Sponsored", 20 | "adElementSelector": "article" 21 | }, 22 | { 23 | "site": "instagram.com", 24 | "adText": "Sponsored", 25 | "adElementSelector": "article" 26 | }, 27 | { 28 | "site": "mobile.twitter.com", 29 | "adText": "Promoted", 30 | "adElementSelector": "article" 31 | }, 32 | { 33 | "site": "amazon.com", 34 | "adText": "Sponsored", 35 | "adElementSelector": "div.s-result-item" 36 | }, 37 | { 38 | "site": "m.youtube.com", 39 | "adTextContainer": "ytm-badge", 40 | "adText": "AD", 41 | "adElementSelector": "ytm-item-section-renderer" 42 | }, 43 | { 44 | "site": "m.youtube.com", 45 | "adTextContainer": "ytm-badge", 46 | "adText": "AD", 47 | "adElementSelector": "ytm-rich-item-renderer" 48 | }, 49 | { 50 | "site": "linkedin.com", 51 | "adText": "Promoted", 52 | "adElementSelector": "li.feed-item" 53 | }, 54 | { 55 | "site": "google.com", 56 | "adText": "Ad,Ad·", 57 | "adElementSelector": "#tads" 58 | }, 59 | { 60 | "site": "google.com", 61 | "adText": "Ads", 62 | "adElementSelector": ".mnr-c" 63 | }, 64 | { 65 | "site": "google.com", 66 | "adText": "Ads", 67 | "adElementSelector": "._-is" 68 | }, 69 | { 70 | "site": "google.com", 71 | "adText": "Ads", 72 | "adElementSelector": ".commercial-unit-mobile-top" 73 | }, 74 | { 75 | "site": "google.com", 76 | "adText": "Ads,Ads·", 77 | "adElementSelector": ".ptJHdc" 78 | }, 79 | { 80 | "site": "duckduckgo.com", 81 | "adText": "Ad", 82 | "adElementSelector": ".result--ad" 83 | } 84 | ] 85 | 86 | 87 | const host = document.location.host.replace('www.', ''); 88 | 89 | const adBlocks = adBlockers.filter(adBlock => { 90 | var site = adBlock.site.split(','); 91 | return site.includes(host); 92 | }); 93 | 94 | adBlocks.forEach((adBlock) => { 95 | var adText = adBlock.adText.split(','); 96 | var adTextContainer = adBlock.adTextContainer || 'span'; 97 | var adElementSelector = adBlock.adElementSelector; 98 | 99 | setInterval(function() { 100 | var search = adText.map(adText => 'normalize-space()=\'' + adText + '\'').join(' or '); 101 | var xpath = "//" + adTextContainer + "[" + search + "]"; 102 | var matchingElements = document.evaluate(xpath, document, null, XPathResult.ANY_TYPE, null); 103 | var nodes = []; 104 | var node; 105 | while(node = matchingElements.iterateNext()) { 106 | nodes.push(node) 107 | } 108 | adBlockNodes(nodes, adElementSelector); 109 | }, 1000); 110 | }); 111 | 112 | const parentIsAdBlocked = (element) => { 113 | if (element === document.body) { return false; } 114 | return element.getAttribute('adblocked') === 'true' || 115 | element.parentNode && parentIsAdBlocked(element.parentNode); 116 | }; 117 | 118 | const adBlockNodes = (nodes, adElementSelector) => { 119 | for (let node of nodes) { 120 | let adstory = node.closest(adElementSelector) 121 | 122 | // Preventing same adstory to be handled more than once 123 | if (!adstory || adstory.getAttribute('adblocked') === 'true') { 124 | continue; 125 | } 126 | 127 | // Preventing ad block inside ad block 128 | if (adstory.parentNode && parentIsAdBlocked(adstory.parentNode)) { 129 | continue; 130 | } 131 | 132 | adstory.setAttribute('adblocked', 'true'); 133 | 134 | let overlay = document.createElement('div') 135 | overlay.setAttribute('style', ` 136 | font-family: -apple-system, BlinkMacSystemFont, sans-serif; 137 | position: absolute; left: 0; top: 0; right: 0; bottom: 0; 138 | background: linear-gradient(hsla(0,0%,100%,.9) 0%,#fff); 139 | z-index: 2147483647`); 140 | overlay.setAttribute('class', 'adblock'); 141 | let overlaytext = document.createElement('div'); 142 | overlaytext.setAttribute('style', ` 143 | position: absolute; left: 20px; top: 30px; 144 | font-weight: bold; 145 | font-size: 24px; 146 | color:#444;`) 147 | overlaytext.innerText = 'Ad'; 148 | let overlaytextinner = document.createElement('div'); 149 | overlaytextinner.setAttribute('style', ` 150 | font-weight: normal; 151 | margin-top: 10px; 152 | font-size: 16px;`); 153 | overlaytextinner.innerText = 'Blocked by Hyperweb. Tap to show likely ad.' 154 | overlay.appendChild(overlaytext); 155 | overlaytext.appendChild(overlaytextinner); 156 | overlay.addEventListener("click", (e) => { 157 | if (adstory.getAttribute('adblock-protected') !== 'true') { 158 | e.preventDefault(); 159 | let ol = e.target.closest('.adblock'); 160 | ol.parentElement.style.maxHeight = 'none'; 161 | ol.parentElement.style.overflow = 'auto'; 162 | ol.parentNode.removeChild(ol); 163 | adstory.setAttribute('adblock-protected', 'true'); 164 | } 165 | }); 166 | if (adstory.querySelectorAll('.adblock').length === 0 && adstory.getAttribute('adblock-protected') !== 'true') { 167 | adstory.style.position = "relative" 168 | adstory.style.maxHeight = '120px'; 169 | adstory.style.overflow = 'hidden'; 170 | adstory.insertBefore(overlay, adstory.firstChild); 171 | } 172 | } 173 | }; 174 | -------------------------------------------------------------------------------- /probable_autoclick.js: -------------------------------------------------------------------------------- 1 | (() => { 2 | const COOKIES_MODAL_MIN_HEIGHT = 100.0; 3 | 4 | const buildSelector = (element) => { 5 | let currentElement = element; 6 | let selectors = []; 7 | 8 | while (currentElement) { 9 | let id; 10 | 11 | // Selector rule should not start with number 12 | if (currentElement.id.trim() && !currentElement.id.trim().match('^\\d')) { 13 | id = `#${ currentElement.id.trim() }`; 14 | } 15 | 16 | let selector = id || currentElement.tagName.toLowerCase(); 17 | 18 | const classes = [ ...currentElement.classList ]; 19 | if (classes.length) { 20 | selector = `${ selector }.${ classes.join('.') }`; 21 | } 22 | 23 | selectors.unshift(selector); 24 | 25 | if (currentElement === document.body || 26 | currentElement.parentElement && currentElement.parentElement === document.body) { 27 | break; 28 | } 29 | 30 | currentElement = currentElement.parentElement; 31 | } 32 | 33 | return selectors; 34 | }; 35 | 36 | const clickElement = (el, selector, tryCount) => { 37 | el.click(); 38 | // If element still exists maybe it did not work correctly, try again 39 | setTimeout(() => document.querySelector(selector) && tryCount < 3 && clickElement(el, selector, tryCount + 1), 250); 40 | }; 41 | 42 | const hasFormAncestor = (element) => { 43 | let parent = element.parentElement; 44 | let hasForm = false; 45 | 46 | while(parent) { 47 | hasForm = parent instanceof HTMLFormElement 48 | 49 | if (hasForm) { break; } 50 | 51 | parent = parent.parentElement; 52 | } 53 | 54 | return hasForm 55 | }; 56 | 57 | const isPossibleAcceptCookies = (element) => { 58 | // We don't want to autoclick elements inside forms 59 | if (hasFormAncestor(element)) { 60 | return null; 61 | } 62 | 63 | // If anchor element, check that is does not have an href that navigates out of the page 64 | if (element instanceof HTMLAnchorElement && element.href && !element.href.startsWith('#')) { 65 | const href = element.href.replace(document.location.href, ''); 66 | 67 | if (!href.startsWith('#')) { 68 | return null; 69 | } 70 | } 71 | 72 | const mustHaveWords = [ 73 | 'ok', 'accept', 'yes', 'continue', 'agree', 'allow', 74 | 'aceito', 'aceitar', 'sim', 'continuar', 'concordo', 'permitir', 'prosseguir', 75 | 'akzeptieren', 'ja', 'weiter', 'zustimmen', 'erlauben', 76 | '好的', '接受', '是的', '继续', '同意', '允许', 77 | ]; 78 | 79 | // Since we don't know the order of the element we are testing in the modal 80 | // Let's look for the ones with positive words 81 | const innerText = element.innerText.toLocaleLowerCase(); 82 | 83 | if (!mustHaveWords.some((word) => innerText.match(`\\b${ word }\\b`))) { 84 | return null; 85 | } 86 | 87 | const highestParent = () => { 88 | let parent = element.parentElement; 89 | 90 | if (parent === document.body) { 91 | return null; 92 | } 93 | 94 | while (parent) { 95 | if (!parent.parentElement || 96 | parent.parentElement === document.body || 97 | parent.parentElement.clientHeight === 0) { break; } 98 | 99 | parent = parent.parentElement; 100 | } 101 | 102 | return parent; 103 | }; 104 | 105 | const parent = highestParent(); 106 | const parentInnerText = parent.innerText.toLocaleLowerCase(); 107 | const foundCookies = parentInnerText.includes('cookie') || parentInnerText.includes('cookies'); 108 | const hasEnoughSize = parent.clientHeight >= COOKIES_MODAL_MIN_HEIGHT; 109 | 110 | return foundCookies && hasEnoughSize ? element : null; 111 | }; 112 | 113 | const run = () => { 114 | const checkElementsIn = (element) => { 115 | try { 116 | const elements = Array.from(element.querySelectorAll('button, a')); 117 | 118 | for (let element of elements) { 119 | if (isPossibleAcceptCookies(element)) { 120 | return element; 121 | } 122 | } 123 | } catch {} 124 | }; 125 | 126 | const buildRuleAndClick = (element) => { 127 | if (!element) { return; } 128 | 129 | const selector = buildSelector(element).join(' > '); 130 | clickElement(element, selector, 0); 131 | }; 132 | 133 | const possibleElement = checkElementsIn(document.body); 134 | 135 | if (possibleElement) { 136 | buildRuleAndClick(possibleElement); 137 | } else { 138 | const observer = new MutationObserver((mutationsList) => { 139 | const findPossibleCookie = () => { 140 | for(const mutation of mutationsList) { 141 | if (mutation.type === 'childList') { 142 | const nodes = Array.from(mutation.addedNodes); 143 | 144 | for (const node of nodes) { 145 | const isTarget = node instanceof HTMLButtonElement || node instanceof HTMLAnchorElement; 146 | 147 | if (isTarget && isPossibleAcceptCookies(node)) { 148 | return node; 149 | } else if (node.nodeType == Node.ELEMENT_NODE) { 150 | const possibleElement = checkElementsIn(node); 151 | 152 | if (possibleElement) { 153 | return possibleElement; 154 | } 155 | } 156 | } 157 | } 158 | } 159 | } 160 | 161 | const element = findPossibleCookie(); 162 | 163 | if (element) { 164 | buildRuleAndClick(element); 165 | observer.disconnect(); 166 | } 167 | }); 168 | 169 | observer.observe(document, { childList: true, subtree: true }); 170 | 171 | setTimeout(() => observer.disconnect(), 10 * 1000); 172 | } 173 | }; 174 | 175 | run(); 176 | })(); 177 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | ------- 2 | yt_sponsor_block.js licensed under LGPL-3.0-or-later 3 | 4 | GNU LESSER GENERAL PUBLIC LICENSE 5 | Version 3, 29 June 2007 6 | 7 | Copyright (C) 2007 Free Software Foundation, Inc. 8 | Everyone is permitted to copy and distribute verbatim copies 9 | of this license document, but changing it is not allowed. 10 | 11 | 12 | This version of the GNU Lesser General Public License incorporates 13 | the terms and conditions of version 3 of the GNU General Public 14 | License, supplemented by the additional permissions listed below. 15 | 16 | 0. Additional Definitions. 17 | 18 | As used herein, "this License" refers to version 3 of the GNU Lesser 19 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 20 | General Public License. 21 | 22 | "The Library" refers to a covered work governed by this License, 23 | other than an Application or a Combined Work as defined below. 24 | 25 | An "Application" is any work that makes use of an interface provided 26 | by the Library, but which is not otherwise based on the Library. 27 | Defining a subclass of a class defined by the Library is deemed a mode 28 | of using an interface provided by the Library. 29 | 30 | A "Combined Work" is a work produced by combining or linking an 31 | Application with the Library. The particular version of the Library 32 | with which the Combined Work was made is also called the "Linked 33 | Version". 34 | 35 | The "Minimal Corresponding Source" for a Combined Work means the 36 | Corresponding Source for the Combined Work, excluding any source code 37 | for portions of the Combined Work that, considered in isolation, are 38 | based on the Application, and not on the Linked Version. 39 | 40 | The "Corresponding Application Code" for a Combined Work means the 41 | object code and/or source code for the Application, including any data 42 | and utility programs needed for reproducing the Combined Work from the 43 | Application, but excluding the System Libraries of the Combined Work. 44 | 45 | 1. Exception to Section 3 of the GNU GPL. 46 | 47 | You may convey a covered work under sections 3 and 4 of this License 48 | without being bound by section 3 of the GNU GPL. 49 | 50 | 2. Conveying Modified Versions. 51 | 52 | If you modify a copy of the Library, and, in your modifications, a 53 | facility refers to a function or data to be supplied by an Application 54 | that uses the facility (other than as an argument passed when the 55 | facility is invoked), then you may convey a copy of the modified 56 | version: 57 | 58 | a) under this License, provided that you make a good faith effort to 59 | ensure that, in the event an Application does not supply the 60 | function or data, the facility still operates, and performs 61 | whatever part of its purpose remains meaningful, or 62 | 63 | b) under the GNU GPL, with none of the additional permissions of 64 | this License applicable to that copy. 65 | 66 | 3. Object Code Incorporating Material from Library Header Files. 67 | 68 | The object code form of an Application may incorporate material from 69 | a header file that is part of the Library. You may convey such object 70 | code under terms of your choice, provided that, if the incorporated 71 | material is not limited to numerical parameters, data structure 72 | layouts and accessors, or small macros, inline functions and templates 73 | (ten or fewer lines in length), you do both of the following: 74 | 75 | a) Give prominent notice with each copy of the object code that the 76 | Library is used in it and that the Library and its use are 77 | covered by this License. 78 | 79 | b) Accompany the object code with a copy of the GNU GPL and this license 80 | document. 81 | 82 | 4. Combined Works. 83 | 84 | You may convey a Combined Work under terms of your choice that, 85 | taken together, effectively do not restrict modification of the 86 | portions of the Library contained in the Combined Work and reverse 87 | engineering for debugging such modifications, if you also do each of 88 | the following: 89 | 90 | a) Give prominent notice with each copy of the Combined Work that 91 | the Library is used in it and that the Library and its use are 92 | covered by this License. 93 | 94 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 95 | document. 96 | 97 | c) For a Combined Work that displays copyright notices during 98 | execution, include the copyright notice for the Library among 99 | these notices, as well as a reference directing the user to the 100 | copies of the GNU GPL and this license document. 101 | 102 | d) Do one of the following: 103 | 104 | 0) Convey the Minimal Corresponding Source under the terms of this 105 | License, and the Corresponding Application Code in a form 106 | suitable for, and under terms that permit, the user to 107 | recombine or relink the Application with a modified version of 108 | the Linked Version to produce a modified Combined Work, in the 109 | manner specified by section 6 of the GNU GPL for conveying 110 | Corresponding Source. 111 | 112 | 1) Use a suitable shared library mechanism for linking with the 113 | Library. A suitable mechanism is one that (a) uses at run time 114 | a copy of the Library already present on the user's computer 115 | system, and (b) will operate properly with a modified version 116 | of the Library that is interface-compatible with the Linked 117 | Version. 118 | 119 | e) Provide Installation Information, but only if you would otherwise 120 | be required to provide such information under section 6 of the 121 | GNU GPL, and only to the extent that such information is 122 | necessary to install and execute a modified version of the 123 | Combined Work produced by recombining or relinking the 124 | Application with a modified version of the Linked Version. (If 125 | you use option 4d0, the Installation Information must accompany 126 | the Minimal Corresponding Source and Corresponding Application 127 | Code. If you use option 4d1, you must provide the Installation 128 | Information in the manner specified by section 6 of the GNU GPL 129 | for conveying Corresponding Source.) 130 | 131 | 5. Combined Libraries. 132 | 133 | You may place library facilities that are a work based on the 134 | Library side by side in a single library together with other library 135 | facilities that are not Applications and are not covered by this 136 | License, and convey such a combined library under terms of your 137 | choice, if you do both of the following: 138 | 139 | a) Accompany the combined library with a copy of the same work based 140 | on the Library, uncombined with any other library facilities, 141 | conveyed under the terms of this License. 142 | 143 | b) Give prominent notice with the combined library that part of it 144 | is a work based on the Library, and explaining where to find the 145 | accompanying uncombined form of the same work. 146 | 147 | 6. Revised Versions of the GNU Lesser General Public License. 148 | 149 | The Free Software Foundation may publish revised and/or new versions 150 | of the GNU Lesser General Public License from time to time. Such new 151 | versions will be similar in spirit to the present version, but may 152 | differ in detail to address new problems or concerns. 153 | 154 | Each version is given a distinguishing version number. If the 155 | Library as you received it specifies that a certain numbered version 156 | of the GNU Lesser General Public License "or any later version" 157 | applies to it, you have the option of following the terms and 158 | conditions either of that published version or of any later version 159 | published by the Free Software Foundation. If the Library as you 160 | received it does not specify a version number of the GNU Lesser 161 | General Public License, you may choose any version of the GNU Lesser 162 | General Public License ever published by the Free Software Foundation. 163 | 164 | If the Library as you received it specifies that a proxy can decide 165 | whether future versions of the GNU Lesser General Public License shall 166 | apply, that proxy's public statement of acceptance of any version is 167 | permanent authorization for you to choose that version for the 168 | Library. 169 | 170 | ---- 171 | Other files licensed under MIT License 172 | 173 | MIT License 174 | 175 | Copyright (c) 2020 insightbrowser 176 | 177 | Permission is hereby granted, free of charge, to any person obtaining a copy 178 | of this software and associated documentation files (the "Software"), to deal 179 | in the Software without restriction, including without limitation the rights 180 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 181 | copies of the Software, and to permit persons to whom the Software is 182 | furnished to do so, subject to the following conditions: 183 | 184 | The above copyright notice and this permission notice shall be included in all 185 | copies or substantial portions of the Software. 186 | 187 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 188 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 189 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 190 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 191 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 192 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 193 | SOFTWARE. 194 | -------------------------------------------------------------------------------- /disable-auto-play.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name AutoPlay Disabled for HTML5 Videos + Pause on Switch Tab 3 | // @namespace xeonx1 4 | // @version 1.83 5 | // @description Prevents auto-play HTML5 videos in new tabs on any page (not just YouTube) and pauses videos when leave/switch/change the current tab, to prevent any video playing in the background. This auto play blocker is an alternative to Click-to-Play / click to play. On some times like YouTube, click twice to begin first playback — From Dan Moorehead (xeonx1), developer of PowerAccess™ (http://PowerAccessDB.com) 6 | // @author Dan Moorehead (xeonx1), developer of PowerAccess™ (http://PowerAccessDB.com) (https://twitter.com/PowerAccessDB) and 7 | // @match http://*/* 8 | // @match https://*/* 9 | // @grant none 10 | // ==/UserScript== 11 | 12 | (function () { 13 | 'use strict'; 14 | 15 | // ******** User Preferences ******** 16 | // Whether to pause videos when leaving a tab they are playing on 17 | var pauseOnLeaveTab = true; 18 | 19 | // Number of milliseconds after clicking where a video is allowed to autoplay. 20 | var allowAutoPlayWithinMillisecondsOfClick = 500; 21 | 22 | // For websites you won't to disable this script on, you can add domains (with or without subdomain, must not include http://, etc.) to always allow auto-play (doesn't affect pause on leave tab) 23 | var autoPlaySitesWhitelist = [ 24 | // "youtube.com" 25 | ]; 26 | // For video hosting sources (eg. YouTube used for videos embedded on other sites), you can add domains (with or without subdomain, must not include http://, etc.) to always allow auto-play (doesn't affect pause on leave tab) 27 | var autoPlaySourcesWhitelist = [ 28 | // "youtube.com" 29 | ]; 30 | 31 | //Advanced preferences from controlling side compatibility / testing: 32 | //seems required for: 33 | var handlePlayingInAdditionToPlayEvent = false; 34 | var allowPauseAgainAfterFirstFound = false; 35 | var treatPlayingLikeOnPlay = false; 36 | 37 | // ******** End Preferences ******** 38 | 39 | /* Test Pages: 40 | https://www.youtube.com/watch?v=OMOVFvcNfvE 41 | http://www.imdb.com/title/tt2527336/videoplayer/vi1488632089 42 | https://trailers.apple.com/trailers/lucasfilm/star-wars-the-last-jedi/ 43 | https://www.theguardian.com/film/video/2017/apr/14/star-wars-last-jedi-trailer-film-current-sequel-trilogy-video 44 | http://www.politico.com/video 45 | http://www.nfl.com/videos 46 | 47 | Known Issues: 48 | Have to click twice the first time to start: 49 | https://www.youtube.com/watch?v=OMOVFvcNfvE 50 | https://www.theguardian.com/film/video/2017/apr/14/star-wars-last-jedi-trailer-film-current-sequel-trilogy-video 51 | Clicking anywhere except Play button causes to pause (so seeking, or click on video itself to unpause) 52 | https://www.usatoday.com/story/life/movies/2017/04/14/star-wars-the-last-jedi-trailer-analysis/100466154/ 53 | 54 | Still Auto Plays: 55 | http://www.cnn.com/videos (eventually auto-plays, stops for long time first, seems to switch between a few videos) 56 | 57 | 58 | Test JS Snippets: 59 | document.querySelector("video.html5-main-video").pause() //for pause on YouTube 60 | 61 | TODO (MAYBE): 62 | Allow pressing play after pressing spacebar? 63 | Delay YouTube pausing to avoid having to double click first time to play? 64 | */ 65 | 66 | //TODO-MAYBE: We could add support for click anywhere on video to play/pause, but some video players may not update their play button status and therefore could be out-of-sync and many of them already support that (though not Apple Trailers, etc.) 67 | 68 | var hasAutoPlaySourcesWhitelist = autoPlaySourcesWhitelist.length > 0; 69 | var hasAutoPlaySitesWhitelist = autoPlaySitesWhitelist.length > 0; 70 | var lastClickTimeMs = 0; 71 | 72 | 73 | function isUrlMatch(url, pattern) { 74 | var regex = "https?\:\/\/[a-zA-Z0-9\.\-]*?\.?" + pattern.replace(/\./, "\.") + "\/"; 75 | var reg = new RegExp(regex, "i"); 76 | return url.match(reg) !== null; 77 | } 78 | 79 | //returns true if auto-play is always allowed for the *website* video is shown on (not source its hosted on) 80 | function isAutoPlayAllowedForSite(url) { // Check if video src is whitelisted. 81 | 82 | if (hasAutoPlaySitesWhitelist) { 83 | for (var i = 0; i < autoPlaySitesWhitelist.length; i++) { 84 | if (isUrlMatch(url, autoPlaySitesWhitelist[i])) 85 | return true; 86 | } 87 | } 88 | return false; 89 | } 90 | 91 | //exit, if the page shown in tab is whitelisted (regardless of where video is hosted / embedded from) 92 | if (isAutoPlayAllowedForSite(document.url)) { 93 | return; 94 | } 95 | 96 | //determine name of event for switched away from tab, based on the browser 97 | var tabHiddenPropertyName, tabVisibleChangedEventName; 98 | 99 | if ("undefined" !== typeof document.hidden) { 100 | tabHiddenPropertyName = "hidden"; 101 | tabVisibleChangedEventName = "visibilitychange"; 102 | } else if ("undefined" !== typeof document.webkitHidden) { 103 | tabHiddenPropertyName = "webkitHidden"; 104 | tabVisibleChangedEventName = "webkitvisibilitychange"; 105 | } else if ("undefined" !== typeof document.msHidden) { 106 | tabHiddenPropertyName = "msHidden"; 107 | tabVisibleChangedEventName = "msvisibilitychange"; 108 | } 109 | 110 | function safeAddHandler(element, event, handler) { 111 | element.removeEventListener(event, handler); 112 | element.addEventListener(event, handler); 113 | } 114 | function getVideos() { 115 | //OR: Can also add audio elements 116 | return document.getElementsByTagName("video"); 117 | } 118 | 119 | function isPlaying(vid) { 120 | return !!(vid.currentTime > 0 && !vid.paused && !vid.ended && vid.readyState > 2); 121 | } 122 | 123 | function onTabVisibleChanged() { 124 | 125 | //console.log("Tab visibility changed for Video auto-player disabling user script. Document is hidden status: ", document[tabHiddenPropertyName]); 126 | 127 | var videos = getVideos(); 128 | 129 | //if doc is hidden (switched away from that tab), then pause all its videos 130 | if (document[tabHiddenPropertyName]) { 131 | 132 | //remember had done this 133 | document.wasPausedOnChangeTab = true; 134 | 135 | //pause all videos, since 136 | for (var i = 0; i < videos.length; i++) { 137 | var vid = videos[i]; 138 | 139 | pauseVideo(vid, true); 140 | } 141 | } 142 | //document is now the active tab 143 | else { 144 | document.wasPausedOnChangeTab = false; //reset state (unless need to use this field or delay this) 145 | 146 | //TODO-MAYBE: if want to auto-play once switch back to a tab if had paused before, then uncomment below, after changing from forEach() to for loop 147 | // getVideos().forEach( function(vid) { 148 | // if (vid.wasPausedOnChangeTab == true) { 149 | // vid.wasPausedOnChangeTab = false; 150 | // vid.play(); 151 | // } 152 | // } ); 153 | } 154 | } 155 | 156 | //handle active tab change events for this document/tab 157 | if (pauseOnLeaveTab) { 158 | safeAddHandler(document, tabVisibleChangedEventName, onTabVisibleChanged); 159 | } 160 | 161 | 162 | //returns true if auto-play is always allowed for the *website* video is shown on (not source its hosted on) 163 | //so YouTube videos embedded onto other sites will be blocked if YouTube is blocked here 164 | function isAutoPlayAllowedForSource(url) { // Check if video src is whitelisted. 165 | //NOTE: URL can start with blob: like on YouTube 166 | if (hasAutoPlaySourcesWhitelist) { 167 | for (var i = 0; i < autoPlaySitesWhitelist.length; i++) { 168 | if (isUrlMatch(url, hasAutoPlaySourcesWhitelist[i])) 169 | return true; 170 | } 171 | } 172 | return false; 173 | } 174 | 175 | //on pause or ended/finished, change playing state back to not-playing, so know to start preventing playback again unless after a click 176 | function onPaused(e) 177 | { 178 | e.target.isPlaying = false; 179 | } 180 | 181 | function pauseVideo(vid, isLeavingTab) { 182 | 183 | var eventName = "auto-play"; 184 | 185 | if (isLeavingTab == true) //also handle undefind/unknown states 186 | { 187 | //OR: if wan't to avoid logging in some cases if not sure if playing: 188 | //if {vid.isPlaying != false) console.log("Paused video playback because switched away from this tab for video with source: ", vid.currentSrc); else logIt = false; 189 | 190 | vid.wasPausedOnChangeTab = true; 191 | 192 | eventName = "on leaving tab"; 193 | //console.log("Paused video playback because switched away from this tab for video with source: ", vid.currentSrc); 194 | 195 | } 196 | 197 | console.log("Paused video " + eventName + " from source: ", vid.currentSrc); 198 | 199 | //remember video is no longer playing - just in case, though event handler for pause should also set this 200 | vid.isPlaying = false; 201 | 202 | //always pause regardless of isPlaying or isVideoPlaying() since aren't always reliable 203 | vid.pause(); 204 | 205 | } 206 | 207 | function onPlay(e) 208 | { 209 | if (e.isTrusted) { return; } // If user initiated, allow play 210 | onPlayOrLoaded(e, true); 211 | } 212 | function onPlaying(e) 213 | { 214 | onPlayOrLoaded(e, false); 215 | } 216 | function onPlayOrLoaded(e, isPlayConfirmed) { // React when a video begins playing 217 | 218 | var msSinceLastClick = Date.now() - lastClickTimeMs; 219 | var vid = e.target; 220 | 221 | //exit, do nothing if is already playing (but not if undefined/unknown), in case clicked on seekbar, volume, etc. - don't toggle to paused state on each click 222 | if(vid.isPlaying == true) { 223 | //return; 224 | } 225 | 226 | //if haven't clicked recently on video, consider it auto-started, so prevent playback by pausing it (unless whitelisted source domain to always play from) 227 | if (msSinceLastClick > allowAutoPlayWithinMillisecondsOfClick && !isAutoPlayAllowedForSource(vid.currentSrc)) { 228 | 229 | pauseVideo(vid); 230 | } else 231 | { 232 | vid.isPlaying = isPlayConfirmed || treatPlayingLikeOnPlay; 233 | } 234 | } 235 | 236 | 237 | function addListenersToVideo(vid, srcChanged) 238 | { 239 | var pauseNow = false; 240 | //if this is first time found this video 241 | if (vid.hasAutoPlayHandlers != true) { 242 | vid.hasAutoPlayHandlers = true; 243 | 244 | safeAddHandler(vid, "play", onPlay); 245 | //NOTE: Seems playing is needed in addition to play event, but isn't this just supposed to occur whenever play, plus after play once buffering is finished? 246 | if (handlePlayingInAdditionToPlayEvent) 247 | safeAddHandler(vid, "playing", onPlaying); 248 | 249 | 250 | safeAddHandler(vid, "pause", onPaused); 251 | safeAddHandler(vid, "ended", onPaused); 252 | 253 | pauseNow = true; 254 | } 255 | //if video source URL has NOT changed and had already hooked up and paused this video before, then exit, don't pause again (in case user had clicked to play it earlier but another video injected into the page caused inspecting all videos again) 256 | //else if (srcChanged != true) 257 | // return; //exit, don't pause it again 258 | 259 | //pause the video since this is the first time was found OR src attribute had changed 260 | if (pauseNow || srcChanged == true) { 261 | 262 | pauseVideo(vid); 263 | 264 | if (allowPauseAgainAfterFirstFound) { 265 | vid.isPlaying = false; //allow upcoming first play event to cause pausing too this first time 266 | } 267 | } 268 | } 269 | function addListeners() { 270 | 271 | var videos = getVideos(); 272 | //OR: Can get audio elements too 273 | 274 | for (var i = 0; i < videos.length; i++) { 275 | // Due to the way some sites dynamically add videos, the "playing" event is not always sufficient. 276 | // Also, in order to handle dynamically added videos, this function may be called on the same elements. 277 | // Must remove any existing instances of this event listener before adding. Prevent duplicate listeners. 278 | var vid = videos[i]; 279 | 280 | addListenersToVideo(vid); 281 | } 282 | } 283 | 284 | //handle click event so can limit auto play until X time after a click 285 | safeAddHandler(document, "click", function () { 286 | lastClickTimeMs = Date.now(); 287 | }); 288 | 289 | var observer = new MutationObserver(function(mutations) { 290 | // Listen for elements being added. Add event listeners when video elements are added. 291 | mutations.forEach(function(mutation) { 292 | 293 | if (mutation.type == "attributes" && mutation.target.tagName == "VIDEO") { //&& mutation.attributeName == "src" 294 | 295 | videoAdded = true; 296 | 297 | addListenersToVideo(mutation.target, true); 298 | } 299 | 300 | if (mutation.addedNodes.length > 0) { 301 | 302 | addListeners(); 303 | 304 | //faster to use getElementsByTagName() for rarely added types vs. iterating over all added elements, checking tagName 305 | // for (var i = 0; i < mutation.addedNodes.length; i++) { 306 | // var added = mutation.addedNodes[i]; 307 | // if (added.nodeType == 1 && added.tagName == "VIDEO") { 308 | // videoAdded = true; 309 | // } 310 | // } 311 | } 312 | }); 313 | }); 314 | 315 | //subscribe to documents events for node added and src attribute changed via MutatorObserver, limiting to only src attribute changes 316 | observer.observe(document, { attributes: true, childList: true, subtree: true, characterData: false, attributeFilter: ['src'] }); 317 | 318 | //don't also need to handle "spfdone" event 319 | 320 | //hookup event handlers for all videos that exist now (will still add to any that are inserted later) 321 | addListeners(); 322 | 323 | })(); 324 | -------------------------------------------------------------------------------- /wordle-solver.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name Wordle Solver 3 | // @version 0.0.1 4 | // @description Solves wordle puzzle. 5 | // @author Felipe 6 | // @match * 7 | // @grant GM.registerButton 8 | // @noframes 9 | // ==/UserScript== 10 | 11 | (() => { 12 | const solve = () => { 13 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 14 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 15 | return new (P || (P = Promise))(function (resolve, reject) { 16 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 17 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 18 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 19 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 20 | }); 21 | }; 22 | // prettier-ignore 23 | const VALID_SOLUTIONS = ['cigar', 'rebut', 'sissy', 'humph', 'awake', 'blush', 'focal', 'evade', 'naval', 'serve', 'heath', 'dwarf', 'model', 'karma', 'stink', 'grade', 'quiet', 'bench', 'abate', 'feign', 'major', 'death', 'fresh', 'crust', 'stool', 'colon', 'abase', 'marry', 'react', 'batty', 'pride', 'floss', 'helix', 'croak', 'staff', 'paper', 'unfed', 'whelp', 'trawl', 'outdo', 'adobe', 'crazy', 'sower', 'repay', 'digit', 'crate', 'cluck', 'spike', 'mimic', 'pound', 'maxim', 'linen', 'unmet', 'flesh', 'booby', 'forth', 'first', 'stand', 'belly', 'ivory', 'seedy', 'print', 'yearn', 'drain', 'bribe', 'stout', 'panel', 'crass', 'flume', 'offal', 'agree', 'error', 'swirl', 'argue', 'bleed', 'delta', 'flick', 'totem', 'wooer', 'front', 'shrub', 'parry', 'biome', 'lapel', 'start', 'greet', 'goner', 'golem', 'lusty', 'loopy', 'round', 'audit', 'lying', 'gamma', 'labor', 'islet', 'civic', 'forge', 'corny', 'moult', 'basic', 'salad', 'agate', 'spicy', 'spray', 'essay', 'fjord', 'spend', 'kebab', 'guild', 'aback', 'motor', 'alone', 'hatch', 'hyper', 'thumb', 'dowry', 'ought', 'belch', 'dutch', 'pilot', 'tweed', 'comet', 'jaunt', 'enema', 'steed', 'abyss', 'growl', 'fling', 'dozen', 'boozy', 'erode', 'world', 'gouge', 'click', 'briar', 'great', 'altar', 'pulpy', 'blurt', 'coast', 'duchy', 'groin', 'fixer', 'group', 'rogue', 'badly', 'smart', 'pithy', 'gaudy', 'chill', 'heron', 'vodka', 'finer', 'surer', 'radio', 'rouge', 'perch', 'retch', 'wrote', 'clock', 'tilde', 'store', 'prove', 'bring', 'solve', 'cheat', 'grime', 'exult', 'usher', 'epoch', 'triad', 'break', 'rhino', 'viral', 'conic', 'masse', 'sonic', 'vital', 'trace', 'using', 'peach', 'champ', 'baton', 'brake', 'pluck', 'craze', 'gripe', 'weary', 'picky', 'acute', 'ferry', 'aside', 'tapir', 'troll', 'unify', 'rebus', 'boost', 'truss', 'siege', 'tiger', 'banal', 'slump', 'crank', 'gorge', 'query', 'drink', 'favor', 'abbey', 'tangy', 'panic', 'solar', 'shire', 'proxy', 'point', 'robot', 'prick', 'wince', 'crimp', 'knoll', 'sugar', 'whack', 'mount', 'perky', 'could', 'wrung', 'light', 'those', 'moist', 'shard', 'pleat', 'aloft', 'skill', 'elder', 'frame', 'humor', 'pause', 'ulcer', 'ultra', 'robin', 'cynic', 'agora', 'aroma', 'caulk', 'shake', 'pupal', 'dodge', 'swill', 'tacit', 'other', 'thorn', 'trove', 'bloke', 'vivid', 'spill', 'chant', 'choke', 'rupee', 'nasty', 'mourn', 'ahead', 'brine', 'cloth', 'hoard', 'sweet', 'month', 'lapse', 'watch', 'today', 'focus', 'smelt', 'tease', 'cater', 'movie', 'lynch', 'saute', 'allow', 'renew', 'their', 'slosh', 'purge', 'chest', 'depot', 'epoxy', 'nymph', 'found', 'shall', 'harry', 'stove', 'lowly', 'snout', 'trope', 'fewer', 'shawl', 'natal', 'fibre', 'comma', 'foray', 'scare', 'stair', 'black', 'squad', 'royal', 'chunk', 'mince', 'slave', 'shame', 'cheek', 'ample', 'flair', 'foyer', 'cargo', 'oxide', 'plant', 'olive', 'inert', 'askew', 'heist', 'shown', 'zesty', 'hasty', 'trash', 'fella', 'larva', 'forgo', 'story', 'hairy', 'train', 'homer', 'badge', 'midst', 'canny', 'fetus', 'butch', 'farce', 'slung', 'tipsy', 'metal', 'yield', 'delve', 'being', 'scour', 'glass', 'gamer', 'scrap', 'money', 'hinge', 'album', 'vouch', 'asset', 'tiara', 'crept', 'bayou', 'atoll', 'manor', 'creak', 'showy', 'phase', 'froth', 'depth', 'gloom', 'flood', 'trait', 'girth', 'piety', 'payer', 'goose', 'float', 'donor', 'atone', 'primo', 'apron', 'blown', 'cacao', 'loser', 'input', 'gloat', 'awful', 'brink', 'smite', 'beady', 'rusty', 'retro', 'droll', 'gawky', 'hutch', 'pinto', 'gaily', 'egret', 'lilac', 'sever', 'field', 'fluff', 'hydro', 'flack', 'agape', 'wench', 'voice', 'stead', 'stalk', 'berth', 'madam', 'night', 'bland', 'liver', 'wedge', 'augur', 'roomy', 'wacky', 'flock', 'angry', 'bobby', 'trite', 'aphid', 'tryst', 'midge', 'power', 'elope', 'cinch', 'motto', 'stomp', 'upset', 'bluff', 'cramp', 'quart', 'coyly', 'youth', 'rhyme', 'buggy', 'alien', 'smear', 'unfit', 'patty', 'cling', 'glean', 'label', 'hunky', 'khaki', 'poker', 'gruel', 'twice', 'twang', 'shrug', 'treat', 'unlit', 'waste', 'merit', 'woven', 'octal', 'needy', 'clown', 'widow', 'irony', 'ruder', 'gauze', 'chief', 'onset', 'prize', 'fungi', 'charm', 'gully', 'inter', 'whoop', 'taunt', 'leery', 'class', 'theme', 'lofty', 'tibia', 'booze', 'alpha', 'thyme', 'eclat', 'doubt', 'parer', 'chute', 'stick', 'trice', 'alike', 'sooth', 'recap', 'saint', 'liege', 'glory', 'grate', 'admit', 'brisk', 'soggy', 'usurp', 'scald', 'scorn', 'leave', 'twine', 'sting', 'bough', 'marsh', 'sloth', 'dandy', 'vigor', 'howdy', 'enjoy', 'valid', 'ionic', 'equal', 'unset', 'floor', 'catch', 'spade', 'stein', 'exist', 'quirk', 'denim', 'grove', 'spiel', 'mummy', 'fault', 'foggy', 'flout', 'carry', 'sneak', 'libel', 'waltz', 'aptly', 'piney', 'inept', 'aloud', 'photo', 'dream', 'stale', 'vomit', 'ombre', 'fanny', 'unite', 'snarl', 'baker', 'there', 'glyph', 'pooch', 'hippy', 'spell', 'folly', 'louse', 'gulch', 'vault', 'godly', 'threw', 'fleet', 'grave', 'inane', 'shock', 'crave', 'spite', 'valve', 'skimp', 'claim', 'rainy', 'musty', 'pique', 'daddy', 'quasi', 'arise', 'aging', 'valet', 'opium', 'avert', 'stuck', 'recut', 'mulch', 'genre', 'plume', 'rifle', 'count', 'incur', 'total', 'wrest', 'mocha', 'deter', 'study', 'lover', 'safer', 'rivet', 'funny', 'smoke', 'mound', 'undue', 'sedan', 'pagan', 'swine', 'guile', 'gusty', 'equip', 'tough', 'canoe', 'chaos', 'covet', 'human', 'udder', 'lunch', 'blast', 'stray', 'manga', 'melee', 'lefty', 'quick', 'paste', 'given', 'octet', 'risen', 'groan', 'leaky', 'grind', 'carve', 'loose', 'sadly', 'spilt', 'apple', 'slack', 'honey', 'final', 'sheen', 'eerie', 'minty', 'slick', 'derby', 'wharf', 'spelt', 'coach', 'erupt', 'singe', 'price', 'spawn', 'fairy', 'jiffy', 'filmy', 'stack', 'chose', 'sleep', 'ardor', 'nanny', 'niece', 'woozy', 'handy', 'grace', 'ditto', 'stank', 'cream', 'usual', 'diode', 'valor', 'angle', 'ninja', 'muddy', 'chase', 'reply', 'prone', 'spoil', 'heart', 'shade', 'diner', 'arson', 'onion', 'sleet', 'dowel', 'couch', 'palsy', 'bowel', 'smile', 'evoke', 'creek', 'lance', 'eagle', 'idiot', 'siren', 'built', 'embed', 'award', 'dross', 'annul', 'goody', 'frown', 'patio', 'laden', 'humid', 'elite', 'lymph', 'edify', 'might', 'reset', 'visit', 'gusto', 'purse', 'vapor', 'crock', 'write', 'sunny', 'loath', 'chaff', 'slide', 'queer', 'venom', 'stamp', 'sorry', 'still', 'acorn', 'aping', 'pushy', 'tamer', 'hater', 'mania', 'awoke', 'brawn', 'swift', 'exile', 'birch', 'lucky', 'freer', 'risky', 'ghost', 'plier', 'lunar', 'winch', 'snare', 'nurse', 'house', 'borax', 'nicer', 'lurch', 'exalt', 'about', 'savvy', 'toxin', 'tunic', 'pried', 'inlay', 'chump', 'lanky', 'cress', 'eater', 'elude', 'cycle', 'kitty', 'boule', 'moron', 'tenet', 'place', 'lobby', 'plush', 'vigil', 'index', 'blink', 'clung', 'qualm', 'croup', 'clink', 'juicy', 'stage', 'decay', 'nerve', 'flier', 'shaft', 'crook', 'clean', 'china', 'ridge', 'vowel', 'gnome', 'snuck', 'icing', 'spiny', 'rigor', 'snail', 'flown', 'rabid', 'prose', 'thank', 'poppy', 'budge', 'fiber', 'moldy', 'dowdy', 'kneel', 'track', 'caddy', 'quell', 'dumpy', 'paler', 'swore', 'rebar', 'scuba', 'splat', 'flyer', 'horny', 'mason', 'doing', 'ozone', 'amply', 'molar', 'ovary', 'beset', 'queue', 'cliff', 'magic', 'truce', 'sport', 'fritz', 'edict', 'twirl', 'verse', 'llama', 'eaten', 'range', 'whisk', 'hovel', 'rehab', 'macaw', 'sigma', 'spout', 'verve', 'sushi', 'dying', 'fetid', 'brain', 'buddy', 'thump', 'scion', 'candy', 'chord', 'basin', 'march', 'crowd', 'arbor', 'gayly', 'musky', 'stain', 'dally', 'bless', 'bravo', 'stung', 'title', 'ruler', 'kiosk', 'blond', 'ennui', 'layer', 'fluid', 'tatty', 'score', 'cutie', 'zebra', 'barge', 'matey', 'bluer', 'aider', 'shook', 'river', 'privy', 'betel', 'frisk', 'bongo', 'begun', 'azure', 'weave', 'genie', 'sound', 'glove', 'braid', 'scope', 'wryly', 'rover', 'assay', 'ocean', 'bloom', 'irate', 'later', 'woken', 'silky', 'wreck', 'dwelt', 'slate', 'smack', 'solid', 'amaze', 'hazel', 'wrist', 'jolly', 'globe', 'flint', 'rouse', 'civil', 'vista', 'relax', 'cover', 'alive', 'beech', 'jetty', 'bliss', 'vocal', 'often', 'dolly', 'eight', 'joker', 'since', 'event', 'ensue', 'shunt', 'diver', 'poser', 'worst', 'sweep', 'alley', 'creed', 'anime', 'leafy', 'bosom', 'dunce', 'stare', 'pudgy', 'waive', 'choir', 'stood', 'spoke', 'outgo', 'delay', 'bilge', 'ideal', 'clasp', 'seize', 'hotly', 'laugh', 'sieve', 'block', 'meant', 'grape', 'noose', 'hardy', 'shied', 'drawl', 'daisy', 'putty', 'strut', 'burnt', 'tulip', 'crick', 'idyll', 'vixen', 'furor', 'geeky', 'cough', 'naive', 'shoal', 'stork', 'bathe', 'aunty', 'check', 'prime', 'brass', 'outer', 'furry', 'razor', 'elect', 'evict', 'imply', 'demur', 'quota', 'haven', 'cavil', 'swear', 'crump', 'dough', 'gavel', 'wagon', 'salon', 'nudge', 'harem', 'pitch', 'sworn', 'pupil', 'excel', 'stony', 'cabin', 'unzip', 'queen', 'trout', 'polyp', 'earth', 'storm', 'until', 'taper', 'enter', 'child', 'adopt', 'minor', 'fatty', 'husky', 'brave', 'filet', 'slime', 'glint', 'tread', 'steal', 'regal', 'guest', 'every', 'murky', 'share', 'spore', 'hoist', 'buxom', 'inner', 'otter', 'dimly', 'level', 'sumac', 'donut', 'stilt', 'arena', 'sheet', 'scrub', 'fancy', 'slimy', 'pearl', 'silly', 'porch', 'dingo', 'sepia', 'amble', 'shady', 'bread', 'friar', 'reign', 'dairy', 'quill', 'cross', 'brood', 'tuber', 'shear', 'posit', 'blank', 'villa', 'shank', 'piggy', 'freak', 'which', 'among', 'fecal', 'shell', 'would', 'algae', 'large', 'rabbi', 'agony', 'amuse', 'bushy', 'copse', 'swoon', 'knife', 'pouch', 'ascot', 'plane', 'crown', 'urban', 'snide', 'relay', 'abide', 'viola', 'rajah', 'straw', 'dilly', 'crash', 'amass', 'third', 'trick', 'tutor', 'woody', 'blurb', 'grief', 'disco', 'where', 'sassy', 'beach', 'sauna', 'comic', 'clued', 'creep', 'caste', 'graze', 'snuff', 'frock', 'gonad', 'drunk', 'prong', 'lurid', 'steel', 'halve', 'buyer', 'vinyl', 'utile', 'smell', 'adage', 'worry', 'tasty', 'local', 'trade', 'finch', 'ashen', 'modal', 'gaunt', 'clove', 'enact', 'adorn', 'roast', 'speck', 'sheik', 'missy', 'grunt', 'snoop', 'party', 'touch', 'mafia', 'emcee', 'array', 'south', 'vapid', 'jelly', 'skulk', 'angst', 'tubal', 'lower', 'crest', 'sweat', 'cyber', 'adore', 'tardy', 'swami', 'notch', 'groom', 'roach', 'hitch', 'young', 'align', 'ready', 'frond', 'strap', 'puree', 'realm', 'venue', 'swarm', 'offer', 'seven', 'dryer', 'diary', 'dryly', 'drank', 'acrid', 'heady', 'theta', 'junto', 'pixie', 'quoth', 'bonus', 'shalt', 'penne', 'amend', 'datum', 'build', 'piano', 'shelf', 'lodge', 'suing', 'rearm', 'coral', 'ramen', 'worth', 'psalm', 'infer', 'overt', 'mayor', 'ovoid', 'glide', 'usage', 'poise', 'randy', 'chuck', 'prank', 'fishy', 'tooth', 'ether', 'drove', 'idler', 'swath', 'stint', 'while', 'begat', 'apply', 'slang', 'tarot', 'radar', 'credo', 'aware', 'canon', 'shift', 'timer', 'bylaw', 'serum', 'three', 'steak', 'iliac', 'shirk', 'blunt', 'puppy', 'penal', 'joist', 'bunny', 'shape', 'beget', 'wheel', 'adept', 'stunt', 'stole', 'topaz', 'chore', 'fluke', 'afoot', 'bloat', 'bully', 'dense', 'caper', 'sneer', 'boxer', 'jumbo', 'lunge', 'space', 'avail', 'short', 'slurp', 'loyal', 'flirt', 'pizza', 'conch', 'tempo', 'droop', 'plate', 'bible', 'plunk', 'afoul', 'savoy', 'steep', 'agile', 'stake', 'dwell', 'knave', 'beard', 'arose', 'motif', 'smash', 'broil', 'glare', 'shove', 'baggy', 'mammy', 'swamp', 'along', 'rugby', 'wager', 'quack', 'squat', 'snaky', 'debit', 'mange', 'skate', 'ninth', 'joust', 'tramp', 'spurn', 'medal', 'micro', 'rebel', 'flank', 'learn', 'nadir', 'maple', 'comfy', 'remit', 'gruff', 'ester', 'least', 'mogul', 'fetch', 'cause', 'oaken', 'aglow', 'meaty', 'gaffe', 'shyly', 'racer', 'prowl', 'thief', 'stern', 'poesy', 'rocky', 'tweet', 'waist', 'spire', 'grope', 'havoc', 'patsy', 'truly', 'forty', 'deity', 'uncle', 'swish', 'giver', 'preen', 'bevel', 'lemur', 'draft', 'slope', 'annoy', 'lingo', 'bleak', 'ditty', 'curly', 'cedar', 'dirge', 'grown', 'horde', 'drool', 'shuck', 'crypt', 'cumin', 'stock', 'gravy', 'locus', 'wider', 'breed', 'quite', 'chafe', 'cache', 'blimp', 'deign', 'fiend', 'logic', 'cheap', 'elide', 'rigid', 'false', 'renal', 'pence', 'rowdy', 'shoot', 'blaze', 'envoy', 'posse', 'brief', 'never', 'abort', 'mouse', 'mucky', 'sulky', 'fiery', 'media', 'trunk', 'yeast', 'clear', 'skunk', 'scalp', 'bitty', 'cider', 'koala', 'duvet', 'segue', 'creme', 'super', 'grill', 'after', 'owner', 'ember', 'reach', 'nobly', 'empty', 'speed', 'gipsy', 'recur', 'smock', 'dread', 'merge', 'burst', 'kappa', 'amity', 'shaky', 'hover', 'carol', 'snort', 'synod', 'faint', 'haunt', 'flour', 'chair', 'detox', 'shrew', 'tense', 'plied', 'quark', 'burly', 'novel', 'waxen', 'stoic', 'jerky', 'blitz', 'beefy', 'lyric', 'hussy', 'towel', 'quilt', 'below', 'bingo', 'wispy', 'brash', 'scone', 'toast', 'easel', 'saucy', 'value', 'spice', 'honor', 'route', 'sharp', 'bawdy', 'radii', 'skull', 'phony', 'issue', 'lager', 'swell', 'urine', 'gassy', 'trial', 'flora', 'upper', 'latch', 'wight', 'brick', 'retry', 'holly', 'decal', 'grass', 'shack', 'dogma', 'mover', 'defer', 'sober', 'optic', 'crier', 'vying', 'nomad', 'flute', 'hippo', 'shark', 'drier', 'obese', 'bugle', 'tawny', 'chalk', 'feast', 'ruddy', 'pedal', 'scarf', 'cruel', 'bleat', 'tidal', 'slush', 'semen', 'windy', 'dusty', 'sally', 'igloo', 'nerdy', 'jewel', 'shone', 'whale', 'hymen', 'abuse', 'fugue', 'elbow', 'crumb', 'pansy', 'welsh', 'syrup', 'terse', 'suave', 'gamut', 'swung', 'drake', 'freed', 'afire', 'shirt', 'grout', 'oddly', 'tithe', 'plaid', 'dummy', 'broom', 'blind', 'torch', 'enemy', 'again', 'tying', 'pesky', 'alter', 'gazer', 'noble', 'ethos', 'bride', 'extol', 'decor', 'hobby', 'beast', 'idiom', 'utter', 'these', 'sixth', 'alarm', 'erase', 'elegy', 'spunk', 'piper', 'scaly', 'scold', 'hefty', 'chick', 'sooty', 'canal', 'whiny', 'slash', 'quake', 'joint', 'swept', 'prude', 'heavy', 'wield', 'femme', 'lasso', 'maize', 'shale', 'screw', 'spree', 'smoky', 'whiff', 'scent', 'glade', 'spent', 'prism', 'stoke', 'riper', 'orbit', 'cocoa', 'guilt', 'humus', 'shush', 'table', 'smirk', 'wrong', 'noisy', 'alert', 'shiny', 'elate', 'resin', 'whole', 'hunch', 'pixel', 'polar', 'hotel', 'sword', 'cleat', 'mango', 'rumba', 'puffy', 'filly', 'billy', 'leash', 'clout', 'dance', 'ovate', 'facet', 'chili', 'paint', 'liner', 'curio', 'salty', 'audio', 'snake', 'fable', 'cloak', 'navel', 'spurt', 'pesto', 'balmy', 'flash', 'unwed', 'early', 'churn', 'weedy', 'stump', 'lease', 'witty', 'wimpy', 'spoof', 'saner', 'blend', 'salsa', 'thick', 'warty', 'manic', 'blare', 'squib', 'spoon', 'probe', 'crepe', 'knack', 'force', 'debut', 'order', 'haste', 'teeth', 'agent', 'widen', 'icily', 'slice', 'ingot', 'clash', 'juror', 'blood', 'abode', 'throw', 'unity', 'pivot', 'slept', 'troop', 'spare', 'sewer', 'parse', 'morph', 'cacti', 'tacky', 'spool', 'demon', 'moody', 'annex', 'begin', 'fuzzy', 'patch', 'water', 'lumpy', 'admin', 'omega', 'limit', 'tabby', 'macho', 'aisle', 'skiff', 'basis', 'plank', 'verge', 'botch', 'crawl', 'lousy', 'slain', 'cubic', 'raise', 'wrack', 'guide', 'foist', 'cameo', 'under', 'actor', 'revue', 'fraud', 'harpy', 'scoop', 'climb', 'refer', 'olden', 'clerk', 'debar', 'tally', 'ethic', 'cairn', 'tulle', 'ghoul', 'hilly', 'crude', 'apart', 'scale', 'older', 'plain', 'sperm', 'briny', 'abbot', 'rerun', 'quest', 'crisp', 'bound', 'befit', 'drawn', 'suite', 'itchy', 'cheer', 'bagel', 'guess', 'broad', 'axiom', 'chard', 'caput', 'leant', 'harsh', 'curse', 'proud', 'swing', 'opine', 'taste', 'lupus', 'gumbo', 'miner', 'green', 'chasm', 'lipid', 'topic', 'armor', 'brush', 'crane', 'mural', 'abled', 'habit', 'bossy', 'maker', 'dusky', 'dizzy', 'lithe', 'brook', 'jazzy', 'fifty', 'sense', 'giant', 'surly', 'legal', 'fatal', 'flunk', 'began', 'prune', 'small', 'slant', 'scoff', 'torus', 'ninny', 'covey', 'viper', 'taken', 'moral', 'vogue', 'owing', 'token', 'entry', 'booth', 'voter', 'chide', 'elfin', 'ebony', 'neigh', 'minim', 'melon', 'kneed', 'decoy', 'voila', 'ankle', 'arrow', 'mushy', 'tribe', 'cease', 'eager', 'birth', 'graph', 'odder', 'terra', 'weird', 'tried', 'clack', 'color', 'rough', 'weigh', 'uncut', 'ladle', 'strip', 'craft', 'minus', 'dicey', 'titan', 'lucid', 'vicar', 'dress', 'ditch', 'gypsy', 'pasta', 'taffy', 'flame', 'swoop', 'aloof', 'sight', 'broke', 'teary', 'chart', 'sixty', 'wordy', 'sheer', 'leper', 'nosey', 'bulge', 'savor', 'clamp', 'funky', 'foamy', 'toxic', 'brand', 'plumb', 'dingy', 'butte', 'drill', 'tripe', 'bicep', 'tenor', 'krill', 'worse', 'drama', 'hyena', 'think', 'ratio', 'cobra', 'basil', 'scrum', 'bused', 'phone', 'court', 'camel', 'proof', 'heard', 'angel', 'petal', 'pouty', 'throb', 'maybe', 'fetal', 'sprig', 'spine', 'shout', 'cadet', 'macro', 'dodgy', 'satyr', 'rarer', 'binge', 'trend', 'nutty', 'leapt', 'amiss', 'split', 'myrrh', 'width', 'sonar', 'tower', 'baron', 'fever', 'waver', 'spark', 'belie', 'sloop', 'expel', 'smote', 'baler', 'above', 'north', 'wafer', 'scant', 'frill', 'awash', 'snack', 'scowl', 'frail', 'drift', 'limbo', 'fence', 'motel', 'ounce', 'wreak', 'revel', 'talon', 'prior', 'knelt', 'cello', 'flake', 'debug', 'anode', 'crime', 'salve', 'scout', 'imbue', 'pinky', 'stave', 'vague', 'chock', 'fight', 'video', 'stone', 'teach', 'cleft', 'frost', 'prawn', 'booty', 'twist', 'apnea', 'stiff', 'plaza', 'ledge', 'tweak', 'board', 'grant', 'medic', 'bacon', 'cable', 'brawl', 'slunk', 'raspy', 'forum', 'drone', 'women', 'mucus', 'boast', 'toddy', 'coven', 'tumor', 'truer', 'wrath', 'stall', 'steam', 'axial', 'purer', 'daily', 'trail', 'niche', 'mealy', 'juice', 'nylon', 'plump', 'merry', 'flail', 'papal', 'wheat', 'berry', 'cower', 'erect', 'brute', 'leggy', 'snipe', 'sinew', 'skier', 'penny', 'jumpy', 'rally', 'umbra', 'scary', 'modem', 'gross', 'avian', 'greed', 'satin', 'tonic', 'parka', 'sniff', 'livid', 'stark', 'trump', 'giddy', 'reuse', 'taboo', 'avoid', 'quote', 'devil', 'liken', 'gloss', 'gayer', 'beret', 'noise', 'gland', 'dealt', 'sling', 'rumor', 'opera', 'thigh', 'tonga', 'flare', 'wound', 'white', 'bulky', 'etude', 'horse', 'circa', 'paddy', 'inbox', 'fizzy', 'grain', 'exert', 'surge', 'gleam', 'belle', 'salvo', 'crush', 'fruit', 'sappy', 'taker', 'tract', 'ovine', 'spiky', 'frank', 'reedy', 'filth', 'spasm', 'heave', 'mambo', 'right', 'clank', 'trust', 'lumen', 'borne', 'spook', 'sauce', 'amber', 'lathe', 'carat', 'corer', 'dirty', 'slyly', 'affix', 'alloy', 'taint', 'sheep', 'kinky', 'wooly', 'mauve', 'flung', 'yacht', 'fried', 'quail', 'brunt', 'grimy', 'curvy', 'cagey', 'rinse', 'deuce', 'state', 'grasp', 'milky', 'bison', 'graft', 'sandy', 'baste', 'flask', 'hedge', 'girly', 'swash', 'boney', 'coupe', 'endow', 'abhor', 'welch', 'blade', 'tight', 'geese', 'miser', 'mirth', 'cloud', 'cabal', 'leech', 'close', 'tenth', 'pecan', 'droit', 'grail', 'clone', 'guise', 'ralph', 'tango', 'biddy', 'smith', 'mower', 'payee', 'serif', 'drape', 'fifth', 'spank', 'glaze', 'allot', 'truck', 'kayak', 'virus', 'testy', 'tepee', 'fully', 'zonal', 'metro', 'curry', 'grand', 'banjo', 'axion', 'bezel', 'occur', 'chain', 'nasal', 'gooey', 'filer', 'brace', 'allay', 'pubic', 'raven', 'plead', 'gnash', 'flaky', 'munch', 'dully', 'eking', 'thing', 'slink', 'hurry', 'theft', 'shorn', 'pygmy', 'ranch', 'wring', 'lemon', 'shore', 'mamma', 'froze', 'newer', 'style', 'moose', 'antic', 'drown', 'vegan', 'chess', 'guppy', 'union', 'lever', 'lorry', 'image', 'cabby', 'druid', 'exact', 'truth', 'dopey', 'spear', 'cried', 'chime', 'crony', 'stunk', 'timid', 'batch', 'gauge', 'rotor', 'crack', 'curve', 'latte', 'witch', 'bunch', 'repel', 'anvil', 'soapy', 'meter', 'broth', 'madly', 'dried', 'scene', 'known', 'magma', 'roost', 'woman', 'thong', 'punch', 'pasty', 'downy', 'knead', 'whirl', 'rapid', 'clang', 'anger', 'drive', 'goofy', 'email', 'music', 'stuff', 'bleep', 'rider', 'mecca', 'folio', 'setup', 'verso', 'quash', 'fauna', 'gummy', 'happy', 'newly', 'fussy', 'relic', 'guava', 'ratty', 'fudge', 'femur', 'chirp', 'forte', 'alibi', 'whine', 'petty', 'golly', 'plait', 'fleck', 'felon', 'gourd', 'brown', 'thrum', 'ficus', 'stash', 'decry', 'wiser', 'junta', 'visor', 'daunt', 'scree', 'impel', 'await', 'press', 'whose', 'turbo', 'stoop', 'speak', 'mangy', 'eying', 'inlet', 'crone', 'pulse', 'mossy', 'staid', 'hence', 'pinch', 'teddy', 'sully', 'snore', 'ripen', 'snowy', 'attic', 'going', 'leach', 'mouth', 'hound', 'clump', 'tonal', 'bigot', 'peril', 'piece', 'blame', 'haute', 'spied', 'undid', 'intro', 'basal', 'shine', 'gecko', 'rodeo', 'guard', 'steer', 'loamy', 'scamp', 'scram', 'manly', 'hello', 'vaunt', 'organ', 'feral', 'knock', 'extra', 'condo', 'adapt', 'willy', 'polka', 'rayon', 'skirt', 'faith', 'torso', 'match', 'mercy', 'tepid', 'sleek', 'riser', 'twixt', 'peace', 'flush', 'catty', 'login', 'eject', 'roger', 'rival', 'untie', 'refit', 'aorta', 'adult', 'judge', 'rower', 'artsy', 'rural', 'shave']; 24 | // Compute the frequency of each letter in the list of valid solutions 25 | const LETTER_FREQUENCIES = (() => { 26 | const frequencies = {}; 27 | for (const word of VALID_SOLUTIONS) { 28 | for (const char of word.toLowerCase().split('')) { 29 | if (!frequencies[char]) { 30 | frequencies[char] = 0; 31 | } 32 | frequencies[char]++; 33 | } 34 | } 35 | return frequencies; 36 | })(); 37 | const VARIANT = window.location.host === 'qntm.org' ? 'absurdle' : 'wordle'; 38 | /** 39 | * Replaces a character in a string at a specific index. 40 | * Borrowed from https://stackoverflow.com/a/1431113/1063392 41 | */ 42 | const replaceAt = (str, index, replacement) => { 43 | return (str.substring(0, index) + 44 | replacement + 45 | str.substring(index + replacement.length)); 46 | }; 47 | /** 48 | * Gets the next best guess, based on the current 49 | * state of the game board 50 | */ 51 | const getNextGuess = (gameState) => { 52 | // This holds the guess with the highest score 53 | const bestGuess = { score: 0, word: null }; 54 | // This holds the guess with the highest score 55 | // that doesn't include any repeated letters 56 | const bestGuessWithoutRepeatingLetters = { score: 0, word: null }; 57 | wordLoop: for (let option of VALID_SOLUTIONS) { 58 | // I think the words are all lowercase, but just to be safe... 59 | option = option.toLowerCase(); 60 | for (const guess of gameState.guesses) { 61 | // This string start out exactly equal to `option`, but we'll blank out 62 | // any correct or partially correct letters. (i.e. "hello" --> "h__lo"). 63 | // This allows us to test against this word using letters we know 64 | // _don't_ exist in the word without eliminating words in which the 65 | // letters appears more than once. 66 | let optionWithBlanks = option; 67 | for (let i = 0; i < guess.letters.length; i++) { 68 | const letter = guess.letters[i]; 69 | // Ignore this option if a previous guess has a correct guess 70 | // (right letter, right position) that conflicts with this option 71 | if (letter.evaluation === 'correct' && option[i] !== letter.letter) { 72 | continue wordLoop; 73 | } 74 | // Ignore this option if a previous guess has a partially 75 | // correct guess (right letter, wrong position), but this 76 | // option does not include the guessed letter 77 | if (letter.evaluation === 'present' && 78 | !option.includes(letter.letter)) { 79 | continue wordLoop; 80 | } 81 | // Ignore this option if a previous guess has a partially 82 | // correct guess (right letter, wrong position), and this 83 | // option includes this letter in the same position 84 | if (letter.evaluation === 'present' && option[i] === letter.letter) { 85 | continue wordLoop; 86 | } 87 | if (letter.evaluation === 'correct') { 88 | // If the current letter is correct, replace the letter with a '_' 89 | // in `optionWithBlanks` so that we can test against this letter 90 | // negatively in the future. 91 | optionWithBlanks = replaceAt(optionWithBlanks, i, '_'); 92 | } 93 | else if (letter.evaluation === 'present') { 94 | // If the letter is correct, but in the wrong position, blank 95 | // out the first occurrence of the letter (if it exists), for the 96 | // same reason mentioned above. 97 | const indexOfLetter = optionWithBlanks.indexOf(letter.letter); 98 | if (indexOfLetter !== -1) { 99 | optionWithBlanks = replaceAt(optionWithBlanks, indexOfLetter, '_'); 100 | } 101 | } 102 | } 103 | // Now, test the option against all the letters we know should _not_ be 104 | // in the word. We can't simply compare the "absent" letters against the 105 | // original word, since it may be the case that a valid guess includes 106 | // multiples of the repeated letter. For example, if the solution was 107 | // "party", and we previously guessed "poppy", the second two p's would 108 | // be considered "absent". However, we can't eliminate "party" just 109 | // because it includes a "p". We get around this by "blanking out" the 110 | // correct and partially-correct guesses above, so that by the time we 111 | // get to this step, we test against a string like "_arty", avoiding the 112 | // false negative scenario described above. 113 | for (let i = 0; i < guess.letters.length; i++) { 114 | const letter = guess.letters[i]; 115 | if (letter.evaluation === 'absent' && 116 | optionWithBlanks.includes(letter.letter)) { 117 | continue wordLoop; 118 | } 119 | } 120 | } 121 | // If we've made it here, we know the option doesn't 122 | // conflict with any previous guesses 123 | // Compare the likelihood of this option being correct 124 | // to our previous guesses, based on the letter frequency 125 | // of the potential solutions 126 | let wordScore = 0; 127 | for (const char of option.split('')) { 128 | wordScore += LETTER_FREQUENCIES[char]; 129 | } 130 | if (wordScore > bestGuess.score) { 131 | bestGuess.score = wordScore; 132 | bestGuess.word = option; 133 | } 134 | const wordHasNoRepeatingLetters = new Set(option).size === option.length; 135 | if (wordHasNoRepeatingLetters && 136 | wordScore > bestGuessWithoutRepeatingLetters.score) { 137 | bestGuessWithoutRepeatingLetters.score = wordScore; 138 | bestGuessWithoutRepeatingLetters.word = option; 139 | } 140 | } 141 | return bestGuessWithoutRepeatingLetters.word || bestGuess.word; 142 | }; 143 | /** 144 | * Scrapes the game board from Wordle: https://www.powerlanguage.co.uk/wordle/ 145 | */ 146 | const getWordleGameState = () => { 147 | const gameRows = Array.from(document.querySelector('game-app').shadowRoot.querySelectorAll('game-row')); 148 | const guesses = gameRows.map((row) => { 149 | const gameTiles = Array.from(row.shadowRoot.querySelectorAll('game-tile')); 150 | const letters = gameTiles.map((tile) => { 151 | var _a; 152 | return { 153 | letter: (_a = tile.getAttribute('letter')) === null || _a === void 0 ? void 0 : _a.toLowerCase(), 154 | evaluation: tile.getAttribute('evaluation'), 155 | }; 156 | }); 157 | return { letters }; 158 | }); 159 | // Filter out any guesses that don't have evaluations for every letter 160 | const nonEmptyGuesses = guesses.filter((guess) => guess.letters.every((l) => l.evaluation)); 161 | return { guesses: nonEmptyGuesses }; 162 | }; 163 | /** 164 | * Scrapes the game board from Absurdle: https://qntm.org/files/wordle/index.html 165 | */ 166 | const getAbsurdleGameState = () => { 167 | const gameRows = Array.from(document.querySelectorAll('table.absurdle__guess-table tr')); 168 | const guesses = gameRows.map((row) => { 169 | const gameTiles = Array.from(row.querySelectorAll('td')); 170 | const letters = gameTiles.map((tile) => { 171 | let evaluation; 172 | if (tile.classList.contains('absurdle__guess-box--inexact')) { 173 | evaluation = 'present'; 174 | } 175 | else if (tile.classList.contains('absurdle__guess-box--exact')) { 176 | evaluation = 'correct'; 177 | } 178 | else if (tile.classList.contains('absurdle__guess-box--wrong')) { 179 | evaluation = 'absent'; 180 | } 181 | else { 182 | evaluation = null; 183 | } 184 | return { 185 | letter: tile.innerText.toLowerCase().trim(), 186 | evaluation, 187 | }; 188 | }); 189 | return { letters }; 190 | }); 191 | // Filter out any guesses that don't have evaluations for every letter 192 | const nonEmptyGuesses = guesses.filter((guess) => guess.letters.every((l) => l.evaluation)); 193 | return { guesses: nonEmptyGuesses }; 194 | }; 195 | /** 196 | * Scrapes the game board and gets the current 197 | * state of the guesses and their evaluations. 198 | */ 199 | const getGameState = () => { 200 | if (VARIANT === 'wordle') { 201 | return getWordleGameState(); 202 | } 203 | else { 204 | return getAbsurdleGameState(); 205 | } 206 | }; 207 | /** 208 | * Plays the game! 209 | */ 210 | const playGame = () => __awaiter(this, void 0, void 0, function* () { 211 | const asyncTimeout = (timeout) => new Promise((resolve) => setTimeout(() => resolve(), timeout)); 212 | for (let i = 0; i < 7; i++) { 213 | const gameState = getGameState(); 214 | const lastGuess = gameState.guesses[gameState.guesses.length - 1]; 215 | if (lastGuess && 216 | lastGuess.letters.every((l) => l.evaluation === 'correct')) { 217 | console.log('Solved!'); 218 | return; 219 | } 220 | const guess = getNextGuess(gameState); 221 | console.log(`Guessing "${guess}"...`); 222 | const objectToDispatch = VARIANT === 'absurdle' ? document : window; 223 | for (const char of guess.split('')) { 224 | objectToDispatch.dispatchEvent(new KeyboardEvent('keydown', { key: char })); 225 | yield asyncTimeout(10); 226 | } 227 | if (VARIANT === 'absurdle') { 228 | const enterButton = Array.from(document.querySelectorAll('button.absurdle__button')).find((btn) => btn.innerText.toLowerCase().trim() === 'enter'); 229 | enterButton.click(); 230 | } 231 | else { 232 | objectToDispatch.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter' })); 233 | } 234 | yield asyncTimeout(VARIANT === 'absurdle' ? 100 : 2200); 235 | } 236 | console.log('Failed!'); 237 | }); 238 | playGame(); 239 | } 240 | 241 | GM.registerButton('solve-wordle', 'Solve Wordle', null, solve); 242 | })(); 243 | -------------------------------------------------------------------------------- /yt_sponsor_block.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name YouTube ads skip with SponsorBlock 3 | // @version 0.0.1 4 | // @description Skip ads on YouTube with SponsorBlock. 5 | // @author Hyperweb 6 | // @match m.youtube.com 7 | // @match www.youtube.com 8 | // @match www.youtube-nocookie.com 9 | // @match music.youtube.com 10 | // @grant GM.notification 11 | // @license LGPL-3.0-or-later 12 | // @noframes 13 | // ==/UserScript== 14 | 15 | const API_URL = "https://sponsor.ajay.app/api/skipSegments/"; 16 | const USER_AGENT_QUERY = "userAgent"; 17 | const CATEGORY_QUERY = "category"; 18 | const CATEGORIES = ["sponsor", "intro"]; 19 | 20 | const ActionType = { 21 | Skip: "skip", 22 | Mute: "mute", 23 | Full: "full", 24 | Poi: "poi", 25 | }; 26 | 27 | let sponsorDataFound = false; 28 | //the actual sponsorTimes if loaded and UUIDs associated with them 29 | let sponsorTimes = null; 30 | //what video id are these sponsors for 31 | let sponsorVideoID = null; 32 | /** Has the sponsor been skipped */ 33 | let sponsorSkipped = []; 34 | 35 | let sponsorTimesSubmitting = []; 36 | 37 | // It resumes with a slightly later time on chromium 38 | let lastTimeFromWaitingEvent = null; 39 | 40 | // Is the video currently being switched 41 | let switchingVideos = null; 42 | 43 | // Made true every videoID change 44 | let firstEvent = false; 45 | 46 | // Used by the play and playing listeners to make sure two aren't 47 | // called at the same time 48 | let lastCheckTime = 0; 49 | let lastCheckVideoTime = -1; 50 | let lastKnownVideoTime; 51 | 52 | let waitingMutationObserver; 53 | let waitingElements; 54 | 55 | // List of videos that have had event listeners added to them 56 | const videosWithEventListeners = []; 57 | 58 | // Skips are scheduled to ensure precision. 59 | // Skips are rescheduled every seeking event. 60 | // Skips are canceled every seeking event 61 | let currentSkipSchedule = null; 62 | let currentSkipInterval = null; 63 | 64 | let isAdPlaying = false; 65 | 66 | //the video 67 | let video; 68 | let videoMuted = false; // Has it been attempted to be muted 69 | let videoMutationObserver = null; 70 | 71 | let isSafari = true; 72 | 73 | const waitForElement = async (selector) => { 74 | return await new Promise((resolve) => { 75 | waitingElements.push({ 76 | selector, 77 | callback: resolve, 78 | }); 79 | 80 | if (!waitingMutationObserver) { 81 | waitingMutationObserver = new MutationObserver(() => { 82 | const foundSelectors = []; 83 | for (const { selector, callback } of waitingElements) { 84 | const element = document.querySelector(selector); 85 | if (element) { 86 | callback(element); 87 | foundSelectors.push(selector); 88 | } 89 | } 90 | 91 | waitingElements = waitingElements.filter( 92 | (element) => !foundSelectors.includes(element.selector) 93 | ); 94 | 95 | if (waitingElements.length === 0) { 96 | waitingMutationObserver.disconnect(); 97 | waitingMutationObserver = null; 98 | } 99 | }); 100 | 101 | waitingMutationObserver.observe(document.body, { 102 | childList: true, 103 | subtree: true, 104 | }); 105 | } 106 | }); 107 | }; 108 | 109 | const isVisible = (element) => { 110 | return element && element.offsetWidth > 0 && element.offsetHeight > 0; 111 | }; 112 | 113 | const findValidElement = (elements) => { 114 | for (const el of elements) { 115 | if (el && isVisible(el)) { 116 | return el; 117 | } 118 | } 119 | 120 | return null; 121 | }; 122 | 123 | const refreshVideoAttachments = () => { 124 | const newVideo = findValidElement(document.querySelectorAll("video")); 125 | if (newVideo && newVideo !== video) { 126 | video = newVideo; 127 | 128 | if (!videosWithEventListeners.includes(video)) { 129 | videosWithEventListeners.push(video); 130 | 131 | setupVideoListeners(); 132 | } 133 | } 134 | }; 135 | 136 | const addPageListeners = () => { 137 | const refreshListners = () => { 138 | if (!isVisible(video)) { 139 | refreshVideoAttachments(); 140 | } 141 | }; 142 | 143 | document.addEventListener("yt-navigate-finish", refreshListners); 144 | }; 145 | 146 | const skipToTime = ({ v, skipTime, skippingSegments }) => { 147 | const autoSkip = true; 148 | 149 | if (autoSkip && v.currentTime !== skipTime[1]) { 150 | switch (skippingSegments[0].actionType) { 151 | case ActionType.Poi: 152 | case ActionType.Skip: { 153 | // Fix for looped videos not working when skipping to the end #426 154 | // for some reason you also can't skip to 1 second before the end 155 | if (v.loop && v.duration > 1 && skipTime[1] >= v.duration - 1) { 156 | v.currentTime = 0; 157 | } else if ( 158 | navigator.vendor === "Apple Computer, Inc." && 159 | v.duration > 1 && 160 | skipTime[1] >= v.duration 161 | ) { 162 | // MacOS will loop otherwise #1027 163 | v.currentTime = v.duration - 0.001; 164 | } else { 165 | try { 166 | const delta = parseInt(skipTime[1] - skipTime[0]); 167 | 168 | GM.notification({ 169 | text: `${ delta } second(s) skipped with SponsorBlock via Hyperweb`, 170 | position: 'bottom', 171 | style: 'bar', 172 | timeout: 5000, 173 | }); 174 | } catch {} 175 | 176 | v.currentTime = skipTime[1]; 177 | } 178 | 179 | break; 180 | } 181 | case ActionType.Mute: { 182 | if (!v.muted) { 183 | v.muted = true; 184 | videoMuted = true; 185 | } 186 | break; 187 | } 188 | } 189 | } 190 | }; 191 | 192 | const updateVirtualTime = () => { 193 | lastKnownVideoTime = { 194 | videoTime: video.currentTime, 195 | preciseTime: performance.now(), 196 | }; 197 | }; 198 | 199 | /** 200 | * Update the isAdPlaying flag and hide preview bar/controls if ad is playing 201 | */ 202 | const updateAdFlag = () => { 203 | isAdPlaying = document.getElementsByClassName("ad-showing").length > 0; 204 | }; 205 | 206 | const cancelSponsorSchedule = () => { 207 | if (currentSkipSchedule !== null) { 208 | clearTimeout(currentSkipSchedule); 209 | currentSkipSchedule = null; 210 | } 211 | 212 | if (currentSkipInterval !== null) { 213 | clearInterval(currentSkipInterval); 214 | currentSkipInterval = null; 215 | } 216 | }; 217 | 218 | const inMuteSegment = (currentTime) => { 219 | const checkFunction = (segment) => 220 | segment.actionType === ActionType.Mute && 221 | segment.segment[0] <= currentTime && 222 | segment.segment[1] > currentTime; 223 | return sponsorTimes?.some(checkFunction); 224 | }; 225 | 226 | /** 227 | * This makes sure the videoID is still correct and if the sponsorTime is included 228 | */ 229 | const incorrectVideoCheck = (videoID = null, sponsorTime = null) => { 230 | const currentVideoID = getYouTubeVideoID(document); 231 | if ( 232 | currentVideoID !== (videoID || sponsorVideoID) || 233 | (sponsorTime && 234 | (!sponsorTimes || 235 | !sponsorTimes?.some((time) => time.segment === sponsorTime.segment)) && 236 | !sponsorTimesSubmitting.some( 237 | (time) => time.segment === sponsorTime.segment 238 | )) 239 | ) { 240 | // Something has really gone wrong 241 | console.error( 242 | "[SponsorBlock] The videoID recorded when trying to skip is different than what it should be." 243 | ); 244 | console.error( 245 | "[SponsorBlock] VideoID recorded: " + 246 | sponsorVideoID + 247 | ". Actual VideoID: " + 248 | currentVideoID 249 | ); 250 | 251 | // Video ID change occured 252 | videoIDChange(currentVideoID); 253 | 254 | return true; 255 | } else { 256 | return false; 257 | } 258 | }; 259 | 260 | /** 261 | * This returns index if the skip option is not AutoSkip 262 | * 263 | * Finds the last endTime that occurs in a segment that the given 264 | * segment skips into that is part of an AutoSkip category. 265 | * 266 | * Used to find where a segment should truely skip to if there are intersecting submissions due to 267 | * them having different categories. 268 | * 269 | * @param sponsorTimes 270 | * @param index Index of the given sponsor 271 | * @param hideHiddenSponsors 272 | */ 273 | const getLatestEndTimeIndex = ( 274 | sponsorTimes, 275 | index, 276 | hideHiddenSponsors = true 277 | ) => { 278 | // Only combine segments for AutoSkip 279 | if ( 280 | index == -1 || // || 281 | // !shouldAutoSkip(sponsorTimes[index]) 282 | sponsorTimes[index].actionType !== ActionType.Skip 283 | ) { 284 | return index; 285 | } 286 | 287 | // Default to the normal endTime 288 | let latestEndTimeIndex = index; 289 | 290 | for (let i = 0; i < sponsorTimes?.length; i++) { 291 | const currentSegment = sponsorTimes[i].segment; 292 | const latestEndTime = sponsorTimes[latestEndTimeIndex].segment[1]; 293 | 294 | if ( 295 | currentSegment[0] <= latestEndTime && 296 | currentSegment[1] > latestEndTime && 297 | // && (!hideHiddenSponsors || sponsorTimes[i].hidden === SponsorHideType.Visible) 298 | // && shouldAutoSkip(sponsorTimes[i]) 299 | sponsorTimes[i].actionType === ActionType.Skip 300 | ) { 301 | // Overlapping segment 302 | latestEndTimeIndex = i; 303 | } 304 | } 305 | 306 | // Keep going if required 307 | if (latestEndTimeIndex !== index) { 308 | latestEndTimeIndex = getLatestEndTimeIndex( 309 | sponsorTimes, 310 | latestEndTimeIndex, 311 | hideHiddenSponsors 312 | ); 313 | } 314 | 315 | return latestEndTimeIndex; 316 | }; 317 | 318 | /** 319 | * Gets just the start times from a sponsor times array. 320 | * Optionally specify a minimum 321 | * 322 | * @param sponsorTimes 323 | * @param minimum 324 | * @param hideHiddenSponsors 325 | * @param includeIntersectingSegments If true, it will include segments that start before 326 | * the current time, but end after 327 | */ 328 | const getStartTimes = ( 329 | sponsorTimes, 330 | includeIntersectingSegments, 331 | includeNonIntersectingSegments, 332 | minimum = undefined, 333 | onlySkippableSponsors = false, 334 | hideHiddenSponsors = false 335 | ) => { 336 | if (!sponsorTimes) return { includedTimes: [], scheduledTimes: [] }; 337 | 338 | const includedTimes = []; 339 | const scheduledTimes = []; 340 | 341 | const possibleTimes = sponsorTimes.map((sponsorTime) => ({ 342 | ...sponsorTime, 343 | scheduledTime: sponsorTime.segment[0], 344 | })); 345 | 346 | // Schedule at the end time to know when to unmute 347 | sponsorTimes 348 | .filter((sponsorTime) => sponsorTime.actionType === ActionType.Mute) 349 | .forEach((sponsorTime) => { 350 | if ( 351 | !possibleTimes.some( 352 | (time) => sponsorTime.segment[1] === time.scheduledTime 353 | ) 354 | ) { 355 | possibleTimes.push({ 356 | ...sponsorTime, 357 | scheduledTime: sponsorTime.segment[1], 358 | }); 359 | } 360 | }); 361 | 362 | for (let i = 0; i < possibleTimes.length; i++) { 363 | if ( 364 | (minimum === undefined || 365 | (includeNonIntersectingSegments && 366 | possibleTimes[i].scheduledTime >= minimum) || 367 | (includeIntersectingSegments && 368 | possibleTimes[i].scheduledTime < minimum && 369 | possibleTimes[i].segment[1] > minimum)) && 370 | // (!onlySkippableSponsors || shouldSkip(possibleTimes[i])) && 371 | !hideHiddenSponsors && // || 372 | // possibleTimes[i].hidden === SponsorHideType.Visible) && 373 | possibleTimes[i].actionType !== ActionType.Poi 374 | ) { 375 | scheduledTimes.push(possibleTimes[i].scheduledTime); 376 | includedTimes.push(possibleTimes[i]); 377 | } 378 | } 379 | 380 | return { includedTimes, scheduledTimes }; 381 | }; 382 | 383 | /** 384 | * Returns info about the next upcoming sponsor skip 385 | */ 386 | const getNextSkipIndex = ( 387 | currentTime, 388 | includeIntersectingSegments, 389 | includeNonIntersectingSegments 390 | ) => { 391 | const { includedTimes: submittedArray, scheduledTimes: sponsorStartTimes } = 392 | getStartTimes( 393 | sponsorTimes, 394 | includeIntersectingSegments, 395 | includeNonIntersectingSegments 396 | ); 397 | const { scheduledTimes: sponsorStartTimesAfterCurrentTime } = getStartTimes( 398 | sponsorTimes, 399 | includeIntersectingSegments, 400 | includeNonIntersectingSegments, 401 | currentTime, 402 | true, 403 | false //true 404 | ); 405 | 406 | const minSponsorTimeIndex = sponsorStartTimes.indexOf( 407 | Math.min(...sponsorStartTimesAfterCurrentTime) 408 | ); 409 | const endTimeIndex = getLatestEndTimeIndex( 410 | submittedArray, 411 | minSponsorTimeIndex 412 | ); 413 | 414 | const { 415 | includedTimes: unsubmittedArray, 416 | scheduledTimes: unsubmittedSponsorStartTimes, 417 | } = getStartTimes( 418 | sponsorTimesSubmitting, 419 | includeIntersectingSegments, 420 | includeNonIntersectingSegments 421 | ); 422 | const { scheduledTimes: unsubmittedSponsorStartTimesAfterCurrentTime } = 423 | getStartTimes( 424 | sponsorTimesSubmitting, 425 | includeIntersectingSegments, 426 | includeNonIntersectingSegments, 427 | currentTime, 428 | false, 429 | false 430 | ); 431 | 432 | const minUnsubmittedSponsorTimeIndex = unsubmittedSponsorStartTimes.indexOf( 433 | Math.min(...unsubmittedSponsorStartTimesAfterCurrentTime) 434 | ); 435 | const previewEndTimeIndex = getLatestEndTimeIndex( 436 | unsubmittedArray, 437 | minUnsubmittedSponsorTimeIndex 438 | ); 439 | 440 | if ( 441 | (minUnsubmittedSponsorTimeIndex === -1 && minSponsorTimeIndex !== -1) || 442 | sponsorStartTimes[minSponsorTimeIndex] < 443 | unsubmittedSponsorStartTimes[minUnsubmittedSponsorTimeIndex] 444 | ) { 445 | return { 446 | array: submittedArray, 447 | index: minSponsorTimeIndex, 448 | endIndex: endTimeIndex, 449 | openNotice: true, 450 | }; 451 | } else { 452 | return { 453 | array: unsubmittedArray, 454 | index: minUnsubmittedSponsorTimeIndex, 455 | endIndex: previewEndTimeIndex, 456 | openNotice: false, 457 | }; 458 | } 459 | }; 460 | 461 | const startSponsorSchedule = ( 462 | includeIntersectingSegments = false, 463 | currentTime = null, 464 | includeNonIntersectingSegments = true 465 | ) => { 466 | cancelSponsorSchedule(); 467 | 468 | // Don't skip if advert playing and reset last checked time 469 | if (isAdPlaying) { 470 | // Reset lastCheckVideoTime 471 | lastCheckVideoTime = -1; 472 | lastCheckTime = 0; 473 | 474 | return; 475 | } 476 | 477 | if (!video || video.paused) return; 478 | 479 | if (currentTime === undefined || currentTime === null) { 480 | const virtualTime = 481 | lastTimeFromWaitingEvent ?? 482 | (lastKnownVideoTime?.videoTime 483 | ? (performance.now() - lastKnownVideoTime.preciseTime) / 1000 + 484 | lastKnownVideoTime.videoTime 485 | : null); 486 | if ( 487 | // (lastTimeFromWaitingEvent || !utils.isFirefox()) && 488 | !isSafari && 489 | virtualTime && 490 | Math.abs(virtualTime - video.currentTime) < 0.6 491 | ) { 492 | currentTime = virtualTime; 493 | } else { 494 | currentTime = video.currentTime; 495 | } 496 | } 497 | lastTimeFromWaitingEvent = null; 498 | 499 | if (videoMuted && !inMuteSegment(currentTime)) { 500 | video.muted = false; 501 | videoMuted = false; 502 | } 503 | 504 | if (incorrectVideoCheck()) return; 505 | 506 | const skipInfo = getNextSkipIndex( 507 | currentTime, 508 | includeIntersectingSegments, 509 | includeNonIntersectingSegments 510 | ); 511 | 512 | if (skipInfo.index === -1) return; 513 | 514 | const currentSkip = skipInfo.array[skipInfo.index]; 515 | const skipTime = [ 516 | currentSkip.scheduledTime, 517 | skipInfo.array[skipInfo.endIndex].segment[1], 518 | ]; 519 | const timeUntilSponsor = skipTime[0] - currentTime; 520 | const videoID = sponsorVideoID; 521 | 522 | // Find all indexes in between the start and end 523 | let skippingSegments = [skipInfo.array[skipInfo.index]]; 524 | if (skipInfo.index !== skipInfo.endIndex) { 525 | skippingSegments = []; 526 | 527 | for (const segment of skipInfo.array) { 528 | if ( 529 | // shouldAutoSkip(segment) && 530 | segment.segment[0] >= skipTime[0] && 531 | segment.segment[1] <= skipTime[1] 532 | ) { 533 | skippingSegments.push(segment); 534 | } 535 | } 536 | } 537 | 538 | const skippingFunction = (forceVideoTime) => { 539 | let forcedSkipTime = null; 540 | let forcedIncludeIntersectingSegments = false; 541 | let forcedIncludeNonIntersectingSegments = true; 542 | 543 | if (incorrectVideoCheck(videoID, currentSkip)) return; 544 | forceVideoTime ||= video.currentTime; 545 | 546 | if (forceVideoTime >= skipTime[0] && forceVideoTime < skipTime[1]) { 547 | skipToTime({ 548 | v: video, 549 | skipTime, 550 | skippingSegments, 551 | // openNotice: skipInfo.openNotice, 552 | }); 553 | 554 | if ( 555 | // utils.getCategorySelection(currentSkip.category)?.option === 556 | // CategorySkipOption.ManualSkip || 557 | currentSkip.actionType === ActionType.Mute 558 | ) { 559 | forcedSkipTime = skipTime[0] + 0.001; 560 | } else { 561 | forcedSkipTime = skipTime[1]; 562 | forcedIncludeIntersectingSegments = true; 563 | forcedIncludeNonIntersectingSegments = false; 564 | } 565 | } 566 | 567 | startSponsorSchedule( 568 | forcedIncludeIntersectingSegments, 569 | forcedSkipTime, 570 | forcedIncludeNonIntersectingSegments 571 | ); 572 | }; 573 | 574 | if (timeUntilSponsor < 0.003) { 575 | skippingFunction(currentTime); 576 | } else { 577 | const delayTime = timeUntilSponsor * 1000 * (1 / video.playbackRate); 578 | if (delayTime < 300) { 579 | // For Firefox, use interval instead of timeout near the end to combat imprecise video time 580 | const startIntervalTime = performance.now(); 581 | const startVideoTime = Math.max(currentTime, video.currentTime); 582 | currentSkipInterval = setInterval(() => { 583 | const intervalDuration = performance.now() - startIntervalTime; 584 | if (intervalDuration >= delayTime || video.currentTime >= skipTime[0]) { 585 | clearInterval(currentSkipInterval); 586 | if (!video.muted) { 587 | // Workaround for more accurate skipping on Chromium 588 | video.muted = true; 589 | video.muted = false; 590 | } 591 | 592 | skippingFunction( 593 | Math.max( 594 | video.currentTime, 595 | startVideoTime + (video.playbackRate * intervalDuration) / 1000 596 | ) 597 | ); 598 | } 599 | }, 1); 600 | } else { 601 | // Schedule for right before to be more precise than normal timeout 602 | currentSkipSchedule = setTimeout( 603 | skippingFunction, 604 | Math.max(0, delayTime - 100) 605 | ); 606 | } 607 | } 608 | }; 609 | 610 | /** 611 | * Triggered every time the video duration changes. 612 | * This happens when the resolution changes or at random time to clear memory. 613 | */ 614 | const durationChangeListener = () => { 615 | updateAdFlag(); 616 | }; 617 | 618 | const setupVideoListeners = () => { 619 | video.addEventListener("durationchange", durationChangeListener); 620 | 621 | switchingVideos = false; 622 | 623 | video.addEventListener("play", () => { 624 | // If it is not the first event, then the only way to get to 0 is if there is a seek event 625 | // This check makes sure that changing the video resolution doesn't cause the extension to think it 626 | // gone back to the begining 627 | if (!firstEvent && video.currentTime === 0) return; 628 | firstEvent = false; 629 | 630 | updateVirtualTime(); 631 | 632 | if (switchingVideos) { 633 | switchingVideos = false; 634 | // If already segments loaded before video, retry to skip starting segments 635 | if (sponsorTimes) startSkipScheduleCheckingForStartSponsors(); 636 | } 637 | 638 | // Check if an ad is playing 639 | updateAdFlag(); 640 | 641 | // Make sure it doesn't get double called with the playing event 642 | if ( 643 | Math.abs(lastCheckVideoTime - video.currentTime) > 0.3 || 644 | (lastCheckVideoTime !== video.currentTime && 645 | Date.now() - lastCheckTime > 2000) 646 | ) { 647 | lastCheckTime = Date.now(); 648 | lastCheckVideoTime = video.currentTime; 649 | 650 | startSponsorSchedule(); 651 | } 652 | }); 653 | video.addEventListener("playing", () => { 654 | updateVirtualTime(); 655 | 656 | // Make sure it doesn't get double called with the play event 657 | if ( 658 | Math.abs(lastCheckVideoTime - video.currentTime) > 0.3 || 659 | (lastCheckVideoTime !== video.currentTime && 660 | Date.now() - lastCheckTime > 2000) 661 | ) { 662 | lastCheckTime = Date.now(); 663 | lastCheckVideoTime = video.currentTime; 664 | 665 | startSponsorSchedule(); 666 | } 667 | }); 668 | video.addEventListener("seeking", () => { 669 | if (!video.paused) { 670 | // Reset lastCheckVideoTime 671 | lastCheckTime = Date.now(); 672 | lastCheckVideoTime = video.currentTime; 673 | 674 | updateVirtualTime(); 675 | lastTimeFromWaitingEvent = null; 676 | 677 | startSponsorSchedule(); 678 | } 679 | }); 680 | video.addEventListener("ratechange", () => startSponsorSchedule()); 681 | // Used by videospeed extension (https://github.com/igrigorik/videospeed/pull/740) 682 | video.addEventListener("videoSpeed_ratechange", () => startSponsorSchedule()); 683 | const paused = () => { 684 | // Reset lastCheckVideoTime 685 | lastCheckVideoTime = -1; 686 | lastCheckTime = 0; 687 | 688 | lastKnownVideoTime = { 689 | videoTime: null, 690 | preciseTime: null, 691 | }; 692 | lastTimeFromWaitingEvent = video.currentTime; 693 | 694 | cancelSponsorSchedule(); 695 | }; 696 | video.addEventListener("pause", paused); 697 | video.addEventListener("waiting", paused); 698 | 699 | startSponsorSchedule(); 700 | }; 701 | 702 | const getYouTubeVideoID = (document) => { 703 | const url = document.URL; 704 | // clips should never skip, going from clip to full video has no indications. 705 | if (url.includes("youtube.com/clip/")) return false; 706 | // skip to document and don't hide if on /embed/ 707 | if (url.includes("/embed/") && url.includes("youtube.com")) 708 | return getYouTubeVideoIDFromDocument(document, false); 709 | // skip to URL if matches youtube watch or invidious or matches youtube pattern 710 | if ( 711 | !url.includes("youtube.com") || 712 | url.includes("/watch") || 713 | url.includes("/shorts/") || 714 | url.includes("playlist") 715 | ) 716 | return getYouTubeVideoIDFromURL(url); 717 | // skip to document if matches pattern 718 | if ( 719 | url.includes("/channel/") || 720 | url.includes("/user/") || 721 | url.includes("/c/") 722 | ) 723 | return getYouTubeVideoIDFromDocument(document); 724 | // not sure, try URL then document 725 | return ( 726 | getYouTubeVideoIDFromURL(url) || 727 | getYouTubeVideoIDFromDocument(document, false) 728 | ); 729 | }; 730 | 731 | const getYouTubeVideoIDFromDocument = (document, hideIcon = true) => { 732 | // get ID from document (channel trailer / embedded playlist) 733 | const videoURL = document 734 | .querySelector('[data-sessionlink="feature=player-title"]') 735 | ?.getAttribute("href"); 736 | if (videoURL) { 737 | return getYouTubeVideoIDFromURL(videoURL); 738 | } else { 739 | return false; 740 | } 741 | }; 742 | 743 | const getYouTubeVideoIDFromURL = (url) => { 744 | if (url.startsWith("https://www.youtube.com/tv#/")) 745 | url = url.replace("#", ""); 746 | 747 | //Attempt to parse url 748 | let urlObject = null; 749 | try { 750 | urlObject = new URL(url); 751 | } catch (e) { 752 | console.error("[SB] Unable to parse URL: " + url); 753 | return false; 754 | } 755 | 756 | // Check if valid hostname 757 | if ( 758 | ![ 759 | "m.youtube.com", 760 | "www.youtube.com", 761 | "www.youtube-nocookie.com", 762 | "music.youtube.com", 763 | ].includes(urlObject.host) 764 | ) { 765 | return false; 766 | } 767 | 768 | //Get ID from searchParam 769 | if ( 770 | (urlObject.searchParams.has("v") && 771 | ["/watch", "/watch/"].includes(urlObject.pathname)) || 772 | urlObject.pathname.startsWith("/tv/watch") 773 | ) { 774 | const id = urlObject.searchParams.get("v"); 775 | return id.length == 11 ? id : false; 776 | } else if ( 777 | urlObject.pathname.startsWith("/embed/") || 778 | urlObject.pathname.startsWith("/shorts/") 779 | ) { 780 | try { 781 | const id = urlObject.pathname.split("/")[2]; 782 | if (id?.length >= 11) return id.slice(0, 11); 783 | } catch (e) { 784 | console.error("[SB] Video ID not valid for " + url); 785 | return false; 786 | } 787 | } 788 | return false; 789 | }; 790 | 791 | const resetValues = () => { 792 | lastCheckTime = 0; 793 | lastCheckVideoTime = -1; 794 | 795 | //reset sponsor times 796 | sponsorTimes = null; 797 | sponsorSkipped = []; 798 | 799 | if (switchingVideos === null) { 800 | // When first loading a video, it is not switching videos 801 | switchingVideos = false; 802 | } else { 803 | switchingVideos = true; 804 | } 805 | 806 | firstEvent = true; 807 | 808 | // Reset advert playing flag 809 | isAdPlaying = false; 810 | }; 811 | 812 | const setupVideoMutationListener = () => { 813 | const videoContainer = document.querySelector(".html5-video-container"); 814 | if (!videoContainer || videoMutationObserver !== null) return; 815 | 816 | videoMutationObserver = new MutationObserver(refreshVideoAttachments); 817 | 818 | videoMutationObserver.observe(videoContainer, { 819 | attributes: true, 820 | childList: true, 821 | subtree: true, 822 | }); 823 | }; 824 | 825 | const getHashParams = () => { 826 | const windowHash = window.location.hash.slice(1); 827 | if (windowHash) { 828 | const params = windowHash.split("&").reduce((acc, param) => { 829 | const [key, value] = param.split("="); 830 | const decoded = decodeURIComponent(value); 831 | try { 832 | acc[key] = decoded?.match(/{|\[/) ? JSON.parse(decoded) : value; 833 | } catch (e) { 834 | console.error(`Failed to parse hash parameter ${key}: ${value}`); 835 | } 836 | 837 | return acc; 838 | }, {}); 839 | 840 | return params; 841 | } 842 | 843 | return {}; 844 | }; 845 | 846 | const getHash = async (value, times = 5000) => { 847 | if (times <= 0) return ""; 848 | 849 | let hashHex = value; 850 | for (let i = 0; i < times; i++) { 851 | const hashBuffer = await crypto.subtle.digest( 852 | "SHA-256", 853 | new TextEncoder().encode(hashHex).buffer 854 | ); 855 | 856 | const hashArray = Array.from(new Uint8Array(hashBuffer)); 857 | hashHex = hashArray.map((b) => b.toString(16).padStart(2, "0")).join(""); 858 | } 859 | 860 | return hashHex; 861 | }; 862 | 863 | const urlTimeToSeconds = (time) => { 864 | if (!time) { 865 | return 0; 866 | } 867 | 868 | const re = /(?:(\d{1,3})h)?(?:(\d{1,2})m)?(\d+)s?/; 869 | const match = re.exec(time); 870 | 871 | if (match) { 872 | const hours = parseInt(match[1] ?? "0", 10); 873 | const minutes = parseInt(match[2] ?? "0", 10); 874 | const seconds = parseInt(match[3] ?? "0", 10); 875 | 876 | return hours * 3600 + minutes * 60 + seconds; 877 | } else if (/\d+/.test(time)) { 878 | return parseInt(time, 10); 879 | } 880 | }; 881 | 882 | const getStartTimeFromUrl = (url) => { 883 | const urlParams = new URLSearchParams(url); 884 | const time = urlParams?.get("t") || urlParams?.get("time_continue"); 885 | 886 | return urlTimeToSeconds(time); 887 | }; 888 | 889 | /** 890 | * Only should be used when it is okay to skip a sponsor when in the middle of it 891 | * 892 | * Ex. When segments are first loaded 893 | */ 894 | const startSkipScheduleCheckingForStartSponsors = () => { 895 | // switchingVideos is ignored in Safari due to event fire order. See #1142 896 | if ((!switchingVideos || isSafari) && sponsorTimes) { 897 | // See if there are any starting sponsors 898 | let startingSegmentTime = getStartTimeFromUrl(document.URL) || -1; 899 | let found = false; 900 | let startingSegment = null; 901 | for (const time of sponsorTimes) { 902 | if ( 903 | time.segment[0] <= video.currentTime && 904 | time.segment[0] > startingSegmentTime && 905 | time.segment[1] > video.currentTime && 906 | time.actionType !== ActionType.Poi 907 | ) { 908 | startingSegmentTime = time.segment[0]; 909 | startingSegment = time; 910 | found = true; 911 | break; 912 | } 913 | } 914 | if (!found) { 915 | for (const time of sponsorTimesSubmitting) { 916 | if ( 917 | time.segment[0] <= video.currentTime && 918 | time.segment[0] > startingSegmentTime && 919 | time.segment[1] > video.currentTime && 920 | time.actionType !== ActionType.Poi 921 | ) { 922 | startingSegmentTime = time.segment[0]; 923 | startingSegment = time; 924 | found = true; 925 | break; 926 | } 927 | } 928 | } 929 | 930 | // For highlight category 931 | const poiSegments = sponsorTimes 932 | .filter( 933 | (time) => 934 | time.segment[1] > video.currentTime && 935 | time.actionType === ActionType.Poi 936 | ) 937 | .sort((a, b) => b.segment[0] - a.segment[0]); 938 | for (const time of poiSegments) { 939 | // const skipOption = utils.getCategorySelection(time.category)?.option; 940 | // if (skipOption !== CategorySkipOption.ShowOverlay) { 941 | skipToTime({ 942 | v: video, 943 | skipTime: time.segment, 944 | skippingSegments: [time], 945 | // openNotice: true, 946 | // unskipTime: video.currentTime, 947 | }); 948 | // if (skipOption === CategorySkipOption.AutoSkip) break; 949 | //} 950 | } 951 | 952 | if (false && startingSegmentTime !== -1) { 953 | startSponsorSchedule(undefined, startingSegmentTime); 954 | } else { 955 | startSponsorSchedule(); 956 | } 957 | } 958 | }; 959 | 960 | const retryFetch = () => { 961 | sponsorDataFound = false; 962 | 963 | setTimeout(() => { 964 | if (sponsorVideoID && sponsorTimes?.length === 0) { 965 | sponsorsLookup(); 966 | } 967 | }, 10000 + Math.random() * 30000); 968 | }; 969 | 970 | const sponsorsLookup = async (keepOldSubmissions = true) => { 971 | if (!video || !isVisible(video)) { 972 | refreshVideoAttachments(); 973 | } 974 | //there is still no video here 975 | if (!video) { 976 | setTimeout(() => sponsorsLookup(), 100); 977 | return; 978 | } 979 | 980 | setupVideoMutationListener(); 981 | 982 | const response = await getVideoSegments(); 983 | if (response?.ok) { 984 | const recievedSegments = (await response.json()) 985 | ?.filter((video) => video.videoID === sponsorVideoID) 986 | ?.map((video) => video.segments)[0]; 987 | if (!recievedSegments || !recievedSegments.length) { 988 | // return if no video found 989 | retryFetch(); 990 | return; 991 | } 992 | 993 | sponsorDataFound = true; 994 | 995 | // Check if any old submissions should be kept 996 | if (sponsorTimes !== null && keepOldSubmissions) { 997 | for (let i = 0; i < sponsorTimes.length; i++) { 998 | if (sponsorTimes[i].source === SponsorSourceType.Local) { 999 | // This is a user submission, keep it 1000 | recievedSegments.push(sponsorTimes[i]); 1001 | } 1002 | } 1003 | } 1004 | 1005 | const oldSegments = sponsorTimes || []; 1006 | sponsorTimes = recievedSegments; 1007 | 1008 | // Hide all submissions smaller than the minimum duration 1009 | // if (Config.config.minDuration !== 0) { 1010 | // for (const segment of sponsorTimes) { 1011 | // const duration = segment.segment[1] - segment.segment[0]; 1012 | // if (duration > 0 && duration < Config.config.minDuration) { 1013 | // segment.hidden = SponsorHideType.MinimumDuration; 1014 | // } 1015 | // } 1016 | // } 1017 | 1018 | if (keepOldSubmissions) { 1019 | for (const segment of oldSegments) { 1020 | const otherSegment = sponsorTimes.find( 1021 | (other) => segment.UUID === other.UUID 1022 | ); 1023 | if (otherSegment) { 1024 | // If they downvoted it, or changed the category, keep it 1025 | otherSegment.hidden = segment.hidden; 1026 | otherSegment.category = segment.category; 1027 | } 1028 | } 1029 | } 1030 | startSkipScheduleCheckingForStartSponsors(); 1031 | } else if (response?.status === 404) { 1032 | retryFetch(); 1033 | } 1034 | 1035 | // if (Config.config.isVip) { 1036 | // lockedCategoriesLookup(); 1037 | // } 1038 | }; 1039 | 1040 | const videoIDChange = async (id) => { 1041 | //if the id has not changed return unless the video element has changed 1042 | if (sponsorVideoID === id && (isVisible(video) || !video)) return; 1043 | 1044 | //set the global videoID 1045 | sponsorVideoID = id; 1046 | 1047 | resetValues(); 1048 | 1049 | //id is not valid 1050 | if (!id) return; 1051 | 1052 | sponsorsLookup(id); 1053 | }; 1054 | 1055 | const getVideoSegments = async () => { 1056 | const hashPrefix = (await getHash(sponsorVideoID, 1)).slice(0, 4); 1057 | 1058 | const url = new URL(API_URL + hashPrefix); 1059 | // url.searchParams.append(VIDEO_QUERY, videoId); 1060 | url.searchParams.append(USER_AGENT_QUERY, window.navigator.userAgent); 1061 | CATEGORIES.forEach((c) => url.searchParams.append(CATEGORY_QUERY, c)); 1062 | 1063 | const hashParams = getHashParams(); 1064 | if (hashParams.requiredSegment) { 1065 | url.searchParams.append("requiredSegment", hashParams.requiredSegment); 1066 | } 1067 | 1068 | try { 1069 | return await fetch(url.href); 1070 | } catch { 1071 | console.log("Error fetching segments for video"); 1072 | return []; 1073 | } 1074 | }; 1075 | 1076 | const run = async () => { 1077 | waitForElement(".ytp-inline-preview-ui").then(() => { 1078 | refreshVideoAttachments(); 1079 | }); 1080 | 1081 | const videoId = getYouTubeVideoID(document); 1082 | 1083 | if (!videoId) { 1084 | return; 1085 | } 1086 | 1087 | sponsorsLookup(); 1088 | }; 1089 | 1090 | videoIDChange(getYouTubeVideoID(document)); 1091 | addPageListeners(); 1092 | run(); 1093 | -------------------------------------------------------------------------------- /invert-colors-at-start.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Minified by jsDelivr using Terser v5.3.5. 3 | * Original file: /npm/darkreader@4.9.22/darkreader.js 4 | * 5 | * Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files 6 | */ 7 | !function(e,r){"object"==typeof exports&&"undefined"!=typeof module&&r(exports)&&typeof DarkReader=="object"||"function"==typeof define&&define.amd&&define(["exports"],r)&&typeof DarkReader=="object"||r((e="undefined"!=typeof globalThis?globalThis:e||self).DarkReader={})}(this,(function(e){"use strict"; 8 | /*! ***************************************************************************** 9 | Copyright (c) Microsoft Corporation. 10 | 11 | Permission to use, copy, modify, and/or distribute this software for any 12 | purpose with or without fee is hereby granted. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 15 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 16 | AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 17 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 18 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 19 | OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 20 | PERFORMANCE OF THIS SOFTWARE. 21 | ***************************************************************************** */var r=function(){return(r=Object.assign||function(e){for(var r,t=1,n=arguments.length;t0&&o[o.length-1])||6!==a[0]&&2!==a[0])){i=0;continue}if(3===a[0]&&(!o||a[1]>o[0]&&a[1]=e.length&&(e=void 0),{value:e&&e[n++],done:!e}}};throw new TypeError(r?"Object is not iterable.":"Symbol.iterator is not defined.")}function a(e,r){var t="function"==typeof Symbol&&e[Symbol.iterator];if(!t)return e;var n,o,a=t.call(e),i=[];try{for(;(void 0===r||r-- >0)&&!(n=a.next()).done;)i.push(n.value)}catch(e){o={error:e}}finally{try{n&&!n.done&&(t=a.return)&&t.call(a)}finally{if(o)throw o.error}}return i}function i(){for(var e=[],r=0;r Promise))`.","See if using `DarkReader.setFetchMethod(window.fetch)`","before `DarkReader.enable()` works."].join(" ")))]}))}))},w=y;function S(e){return t(this,void 0,void 0,(function(){return n(this,(function(r){switch(r.label){case 0:return[4,w(e)];case 1:return[2,r.sent()]}}))}))}window.chrome||(window.chrome={}),chrome.runtime||(chrome.runtime={});var k=new Set;function _(){for(var e=[],r=0;r=10){if(f-c1e3)return!0;for(var r=0,t=0;t1e3)return!0;return!1}(r))!a||ne()?n.forEach((function(r){return(0,r.onHugeMutations)(e)})):i||(ae(o=function(){return n.forEach((function(r){return(0,r.onHugeMutations)(e)}))}),i=!0),a=!0;else{var t=function(e){var r=new Set,t=new Set,n=new Set;e.forEach((function(e){P(e.addedNodes,(function(e){e instanceof Element&&e.isConnected&&r.add(e)})),P(e.removedNodes,(function(e){e instanceof Element&&(e.isConnected?n.add(e):t.add(e))}))})),n.forEach((function(e){return r.delete(e)}));var o=[],a=[];return r.forEach((function(e){r.has(e.parentElement)&&o.push(e)})),t.forEach((function(e){t.has(e.parentElement)&&a.push(e)})),o.forEach((function(e){return r.delete(e)})),a.forEach((function(e){return t.delete(e)})),{additions:r,moves:n,deletions:t}}(r);n.forEach((function(e){return(0,e.onMinorMutations)(t)}))}}))).observe(e,{childList:!0,subtree:!0}),ce.set(e,t),n=new Set,se.set(t,n)}return n.add(r),{disconnect:function(){n.delete(r),o&&ie(o),0===n.size&&(t.disconnect(),se.delete(t),ce.delete(e))}}}function de(e){var r=e.h,t=e.s,n=e.l,o=e.a,i=void 0===o?1:o;if(0===t){var u=a([n,n,n].map((function(e){return Math.round(255*e)})),3),c=u[0],s=u[1];return{r:c,g:u[2],b:s,a:i}}var l=(1-Math.abs(2*n-1))*t,d=l*(1-Math.abs(r/60%2-1)),f=n-l/2,h=a((r<60?[l,d,0]:r<120?[d,l,0]:r<180?[0,l,d]:r<240?[0,d,l]:r<300?[d,0,l]:[l,0,d]).map((function(e){return Math.round(255*(e+f))})),3);return{r:h[0],g:h[1],b:h[2],a:i}}function fe(e){var r=e.r,t=e.g,n=e.b,o=e.a,a=void 0===o?1:o,i=r/255,u=t/255,c=n/255,s=Math.max(i,u,c),l=Math.min(i,u,c),d=s-l,f=(s+l)/2;if(0===d)return{h:0,s:0,l:f,a:a};var h=60*(s===i?(u-c)/d%6:s===u?(c-i)/d+2:(i-u)/d+4);return h<0&&(h+=360),{h:h,s:d/(1-Math.abs(2*f-1)),l:f,a:a}}function he(e,r){void 0===r&&(r=0);var t=e.toFixed(r);if(0===r)return t;var n=t.indexOf(".");if(n>=0){var o=t.match(/0+$/);if(o)return o.index===n+1?t.substring(0,n):t.substring(0,o.index)}return t}function pe(e){var r=e.h,t=e.s,n=e.l,o=e.a;return null!=o&&o<1?"hsla("+he(r)+", "+he(100*t)+"%, "+he(100*n)+"%, "+he(o,2)+")":"hsl("+he(r)+", "+he(100*t)+"%, "+he(100*n)+"%)"}var me=/^rgba?\([^\(\)]+\)$/,ve=/^hsla?\([^\(\)]+\)$/,ge=/^#[0-9a-f]+$/i;function be(e){var r,t,n,o,i,u=e.trim().toLowerCase();if(u.match(me))return r=a(ye(u,we,Se,ke),4),t=r[0],n=r[1],o=r[2],i=r[3],{r:t,g:n,b:o,a:void 0===i?1:i};if(u.match(ve))return function(e){var r=a(ye(e,_e,xe,Ee),4),t=r[0],n=r[1],o=r[2],i=r[3];return de({h:t,s:n,l:o,a:void 0===i?1:i})}(u);if(u.match(ge))return function(e){var r=e.substring(1);switch(r.length){case 3:case 4:var t=a([0,1,2].map((function(e){return parseInt(""+r[e]+r[e],16)})),3);return{r:t[0],g:t[1],b:t[2],a:3===r.length?1:parseInt(""+r[3]+r[3],16)/255};case 6:case 8:var n=a([0,2,4].map((function(e){return parseInt(r.substring(e,e+2),16)})),3);return{r:n[0],g:n[1],b:n[2],a:6===r.length?1:parseInt(r.substring(6,8),16)/255}}throw new Error("Unable to parse "+e)}(u);if(Ce.has(u))return function(e){var r=Ce.get(e);return{r:r>>16&255,g:r>>8&255,b:r>>0&255,a:1}}(u);if(Me.has(u))return function(e){var r=Me.get(e);return{r:r>>16&255,g:r>>8&255,b:r>>0&255,a:1}}(u);if("transparent"===e)return{r:0,g:0,b:0,a:0};throw new Error("Unable to parse "+e)}function ye(e,r,t,n){var o=e.split(r).filter((function(e){return e})),i=Object.entries(n);return o.map((function(e){return e.trim()})).map((function(e,r){var n,o=i.find((function(r){var t=a(r,1)[0];return e.endsWith(t)}));return n=o?parseFloat(e.substring(0,e.length-o[0].length))/o[1]*t[r]:parseFloat(e),t[r]>1?Math.round(n):n}))}var we=/rgba?|\(|\)|\/|,|\s/gi,Se=[255,255,255,1],ke={"%":100};var _e=/hsla?|\(|\)|\/|,|\s/gi,xe=[360,1,1,1],Ee={"%":100,deg:360,rad:2*Math.PI,turn:1};var Ce=new Map(Object.entries({aliceblue:15792383,antiquewhite:16444375,aqua:65535,aquamarine:8388564,azure:15794175,beige:16119260,bisque:16770244,black:0,blanchedalmond:16772045,blue:255,blueviolet:9055202,brown:10824234,burlywood:14596231,cadetblue:6266528,chartreuse:8388352,chocolate:13789470,coral:16744272,cornflowerblue:6591981,cornsilk:16775388,crimson:14423100,cyan:65535,darkblue:139,darkcyan:35723,darkgoldenrod:12092939,darkgray:11119017,darkgrey:11119017,darkgreen:25600,darkkhaki:12433259,darkmagenta:9109643,darkolivegreen:5597999,darkorange:16747520,darkorchid:10040012,darkred:9109504,darksalmon:15308410,darkseagreen:9419919,darkslateblue:4734347,darkslategray:3100495,darkslategrey:3100495,darkturquoise:52945,darkviolet:9699539,deeppink:16716947,deepskyblue:49151,dimgray:6908265,dimgrey:6908265,dodgerblue:2003199,firebrick:11674146,floralwhite:16775920,forestgreen:2263842,fuchsia:16711935,gainsboro:14474460,ghostwhite:16316671,gold:16766720,goldenrod:14329120,gray:8421504,grey:8421504,green:32768,greenyellow:11403055,honeydew:15794160,hotpink:16738740,indianred:13458524,indigo:4915330,ivory:16777200,khaki:15787660,lavender:15132410,lavenderblush:16773365,lawngreen:8190976,lemonchiffon:16775885,lightblue:11393254,lightcoral:15761536,lightcyan:14745599,lightgoldenrodyellow:16448210,lightgray:13882323,lightgrey:13882323,lightgreen:9498256,lightpink:16758465,lightsalmon:16752762,lightseagreen:2142890,lightskyblue:8900346,lightslategray:7833753,lightslategrey:7833753,lightsteelblue:11584734,lightyellow:16777184,lime:65280,limegreen:3329330,linen:16445670,magenta:16711935,maroon:8388608,mediumaquamarine:6737322,mediumblue:205,mediumorchid:12211667,mediumpurple:9662683,mediumseagreen:3978097,mediumslateblue:8087790,mediumspringgreen:64154,mediumturquoise:4772300,mediumvioletred:13047173,midnightblue:1644912,mintcream:16121850,mistyrose:16770273,moccasin:16770229,navajowhite:16768685,navy:128,oldlace:16643558,olive:8421376,olivedrab:7048739,orange:16753920,orangered:16729344,orchid:14315734,palegoldenrod:15657130,palegreen:10025880,paleturquoise:11529966,palevioletred:14381203,papayawhip:16773077,peachpuff:16767673,peru:13468991,pink:16761035,plum:14524637,powderblue:11591910,purple:8388736,rebeccapurple:6697881,red:16711680,rosybrown:12357519,royalblue:4286945,saddlebrown:9127187,salmon:16416882,sandybrown:16032864,seagreen:3050327,seashell:16774638,sienna:10506797,silver:12632256,skyblue:8900331,slateblue:6970061,slategray:7372944,slategrey:7372944,snow:16775930,springgreen:65407,steelblue:4620980,tan:13808780,teal:32896,thistle:14204888,tomato:16737095,turquoise:4251856,violet:15631086,wheat:16113331,white:16777215,whitesmoke:16119285,yellow:16776960,yellowgreen:10145074})),Me=new Map(Object.entries({ActiveBorder:3906044,ActiveCaption:0,AppWorkspace:11184810,Background:6513614,ButtonFace:16777215,ButtonHighlight:15329769,ButtonShadow:10461343,ButtonText:0,CaptionText:0,GrayText:8355711,Highlight:11720703,HighlightText:0,InactiveBorder:16777215,InactiveCaption:16777215,InactiveCaptionText:0,InfoBackground:16514245,InfoText:0,Menu:16185078,MenuText:16777215,Scrollbar:11184810,ThreeDDarkShadow:0,ThreeDFace:12632256,ThreeDHighlight:16777215,ThreeDLightShadow:16777215,ThreeDShadow:0,Window:15527148,WindowFrame:11184810,WindowText:0,"-webkit-focus-ring-color":15046400}).map((function(e){var r=a(e,2),t=r[0],n=r[1];return[t.toLowerCase(),n]})));function Ae(e,r,t,n,o){return(e-r)*(o-n)/(t-r)+n}function Re(e,r,t){return Math.min(t,Math.max(r,e))}function Le(e,r){for(var t=[],n=0,o=e.length;n.8&&(o>200&&o<280);var s=o,l=i;return n&&(c?(s=r.h,l=r.s):(s=t.h,l=t.s)),{h:s,s:l,l:Ae(i,0,1,r.l,t.l),a:u}}function He(e,r){var t=e.h,n=e.s,o=e.l,a=e.a,i=n<.12||o>.8&&(t>200&&t<280);if(o<.5){var u=Ae(o,0,.5,0,.4);return i?{h:r.h,s:r.s,l:u,a:a}:{h:t,s:n,l:u,a:a}}var c=Ae(o,.5,1,.4,r.l);if(i)return{h:r.h,s:r.s,l:c,a:a};var s=t;t>60&&t<180&&(s=t>120?Ae(t,120,180,135,180):Ae(t,60,120,60,105));return{h:s,s:n,l:c,a:a}}function Ve(e,t){if(0===t.mode)return De(e,t);var n=We(t);return Ue(e,r(r({},t),{mode:0}),He,n)}var Ge,Ke=.55;function Je(e){return Ae(e,205,245,205,220)}function Qe(e,r){var t=e.h,n=e.s,o=e.l,a=e.a,i=o<.2||n<.24,u=!i&&t>205&&t<245;if(o>.5){var c=Ae(o,.5,1,Ke,r.l);if(i)return{h:r.h,s:r.s,l:c,a:a};var s=t;return u&&(s=Je(t)),{h:s,s:n,l:c,a:a}}if(i)return{h:r.h,s:r.s,l:Ae(o,0,.5,r.l,Ke),a:a};var l,d=t;return u?(d=Je(t),l=Ae(o,0,.5,r.l,Math.min(1,.6000000000000001))):l=Ae(o,0,.5,r.l,Ke),{h:d,s:n,l:l,a:a}}function Xe(e,t){if(0===t.mode)return De(e,t);var n=Oe(t);return Ue(e,r(r({},t),{mode:0}),Qe,n)}function Ye(e,r,t){var n=e.h,o=e.s,a=e.l,i=e.a,u=n,c=o;return(a<.2||o<.24)&&(a<.5?(u=r.h,c=r.s):(u=t.h,c=t.s)),{h:u,s:c,l:Ae(a,0,1,.5,.2),a:i}}function Ze(e,t){if(0===t.mode)return De(e,t);var n=Oe(t),o=We(t);return Ue(e,r(r({},t),{mode:0}),Ye,n,o)}function er(e,r){return Ve(e,r)}function rr(e){var r=[];return e.mode===Ge.dark&&r.push("invert(100%) hue-rotate(180deg)"),100!==e.brightness&&r.push("brightness("+e.brightness+"%)"),100!==e.contrast&&r.push("contrast("+e.contrast+"%)"),0!==e.grayscale&&r.push("grayscale("+e.grayscale+"%)"),0!==e.sepia&&r.push("sepia("+e.sepia+"%)"),0===r.length?null:r.join(" ")}!function(e){e[e.light=0]="light",e[e.dark=1]="dark"}(Ge||(Ge={}));var tr=0,nr=new Map,or=new Map;function ar(e){return new Promise((function(r,t){var n=++tr;nr.set(n,r),or.set(n,t),chrome.runtime.sendMessage({type:"fetch",data:e,id:n})}))}function ir(e){return t(this,void 0,void 0,(function(){var t,o,a;return n(this,(function(n){switch(n.label){case 0:return e.startsWith("data:")?(t=e,[3,3]):[3,1];case 1:return[4,ur(e)];case 2:t=n.sent(),n.label=3;case 3:return[4,cr(t)];case 4:return o=n.sent(),a=function(e){sr||(r=dr,t=dr,(sr=document.createElement("canvas")).width=r,sr.height=t,(lr=sr.getContext("2d")).imageSmoothingEnabled=!1);var r,t;var n=e.naturalWidth,o=e.naturalHeight;if(0===o||0===n)return B("logWarn(Image is empty "+e.currentSrc+")"),null;var a=n*o,i=Math.min(1,Math.sqrt(dr/a)),u=Math.ceil(n*i),c=Math.ceil(o*i);lr.clearRect(0,0,u,c),lr.drawImage(e,0,0,n,o,0,0,u,c);var s,l,d,f,h,p,m,v=lr.getImageData(0,0,u,c).data,g=.05,b=.4,y=.7,w=0,S=0,k=0;for(d=0;dy&&k++);var _=u*c,x=_-w;return{isDark:S/x>=.7,isLight:k/x>=.7,isTransparent:w/_>=.1,isLarge:a>=48e4}}(o),[2,r({src:e,dataURL:t,width:o.naturalWidth,height:o.naturalHeight},a)]}}))}))}function ur(e){return t(this,void 0,void 0,(function(){return n(this,(function(r){switch(r.label){case 0:return function(e){var r=new URL(e);return r.host?r.host:r.protocol}(e)!==(location.host||location.protocol)?[3,2]:[4,g(e)];case 1:return[2,r.sent()];case 2:return[4,ar({url:e,responseType:"data-url"})];case 3:return[2,r.sent()]}}))}))}function cr(e){return t(this,void 0,void 0,(function(){return n(this,(function(r){return[2,new Promise((function(r,t){var n=new Image;n.onload=function(){return r(n)},n.onerror=function(){return t("Unable to load image "+e)},n.src=e}))]}))}))}chrome.runtime.onMessage.addListener((function(e){var r=e.type,t=e.data,n=e.error,o=e.id;if("fetch-response"===r){var a=nr.get(o),i=or.get(o);nr.delete(o),or.delete(o),n?i&&i(n):a&&a(t)}}));var sr,lr,dr=1024;var fr=new Set;function hr(e,r){for(var t=e.dataURL,n=e.width,o=e.height,a=['',"",'','',"","",'',""].join(""),i=new Uint8Array(a.length),u=0;u=0&&"-webkit-print-color-adjust"!==e||"fill"===e||"stroke"===e){if(l=function(e,r){if(yr.has(r.toLowerCase()))return r;try{var t=Sr(r);return e.indexOf("background")>=0?function(e){return Ve(t,e)}:e.indexOf("border")>=0||e.indexOf("outline")>=0?function(e){return Ze(t,e)}:function(e){return Xe(t,e)}}catch(e){return B("Color parse error",e),null}}(e,o))return{property:e,value:l,important:c,sourceValue:s}}else if("background-image"===e||"list-style-image"===e){if(l=function(e,o,a,i){var u=this;try{var c=Te(_r,e),s=Te($,e);if(0===s.length&&0===c.length)return e;var l=function(r){var t=0;return r.map((function(r){var n=e.indexOf(r,t);return t=n+r.length,{match:r,index:n}}))},d=l(s).map((function(e){return r({type:"url"},e)})).concat(l(c).map((function(e){return r({type:"gradient"},e)}))).sort((function(e,r){return e.index-r.index})),f=function(e){var r=e.match(/^(.*-gradient)\((.*)\)$/),t=r[1],n=r[2],o=/^(from|color-stop|to)\(([^\(\)]*?,\s*)?(.*?)\)$/,a=Te(/([^\(\),]+(\([^\(\)]*(\([^\(\)]*\)*[^\(\)]*)?\))?[^\(\),]*),?/g,n,1).map((function(e){var r=kr(e=e.trim());if(r)return function(e){return er(r,e)};var t=e.lastIndexOf(" ");if(r=kr(e.substring(0,t)))return function(n){return er(r,n)+" "+e.substring(t+1)};var n=e.match(o);return n&&(r=kr(n[3]))?function(e){return n[1]+"("+(n[2]?n[2]+", ":"")+er(r,e)+")"}:function(){return e}}));return function(e){return t+"("+a.map((function(r){return r(e)})).join(", ")+")"}},h=function(e){var r=V(e);if(o.parentStyleSheet.href){var c=G(o.parentStyleSheet.href);r=q(c,r)}else r=o.parentStyleSheet.ownerNode&&o.parentStyleSheet.ownerNode.baseURI?q(o.parentStyleSheet.ownerNode.baseURI,r):q(location.origin,r);var s='url("'+r+'")';return function(e){return t(u,void 0,void 0,(function(){var t,u;return n(this,(function(n){switch(n.label){case 0:return xr.has(r)?(t=xr.get(r),[3,7]):[3,1];case 1:return n.trys.push([1,6,,7]),function(e,r){if(!e||0===r.length)return!1;if(r.some((function(e){return"*"===e})))return!0;for(var t=e.selectorText.split(/,\s*/g),n=function(e){var n=r[e];if(t.some((function(e){return e===n})))return{value:!0}},o=0;o2)F("Inverting dark image "+e.src),n='url("'+hr(e,r(r({},t),{sepia:Re(t.sepia+10,0,100)}))+'")';else if(a&&!i&&1===t.mode){if(u)n="none";else F("Dimming light image "+e.src),n='url("'+hr(e,t)+'")'}else if(0===t.mode&&a&&!u){F("Applying filter to image "+e.src),n='url("'+hr(e,r(r({},t),{brightness:Re(t.brightness-10,5,200),sepia:Re(t.sepia+10,0,100)}))+'")'}else n=null;return n},m=[],v=0;return d.forEach((function(r,t){var n=r.match,o=r.type,a=r.index,i=v,u=a+n.length;v=u,m.push((function(){return e.substring(i,a)})),m.push("url"===o?h(n):f(n)),t===d.length-1&&m.push((function(){return e.substring(u)}))})),function(e){var r=m.map((function(r){return r(e)}));return r.some((function(e){return e instanceof Promise}))?Promise.all(r).then((function(e){return e.join("")})):r.join("")}}catch(r){return B("Unable to parse gradient "+e,r),null}}(o,a,i,u))return{property:e,value:l,important:c,sourceValue:s}}else if(e.indexOf("shadow")>=0){var l;if(l=function(e,r){try{var t=0,n=Te(/(^|\s)([a-z]+\(.+?\)|#[0-9a-f]+|[a-z]+)(.*?(inset|outset)?($|,))/gi,r,2),o=n.map((function(e,o){var a=t,i=r.indexOf(e,t),u=i+e.length;t=u;var c=kr(e);return c?function(e){return""+r.substring(a,i)+function(e,r){return Ve(e,r)}(c,e)+(o===n.length-1?r.substring(u):"")}:function(){return r.substring(a,u)}}));return function(e){return o.map((function(r){return r(e)})).join("")}}catch(e){return B("Unable to parse shadow "+r,e),null}}(0,o))return{property:e,value:l,important:c,sourceValue:s}}return null}function vr(e,t,n){var o=[];return t||(o.push("html {"),o.push(" background-color: "+Ve({r:255,g:255,b:255},e)+" !important;"),o.push("}")),o.push((t?"":"html, body, ")+(n?"input, textarea, select, button":"")+" {"),o.push(" background-color: "+Ve({r:255,g:255,b:255},e)+";"),o.push("}"),o.push("html, body, "+(n?"input, textarea, select, button":"")+" {"),o.push(" border-color: "+Ze({r:76,g:76,b:76},e)+";"),o.push(" color: "+Xe({r:0,g:0,b:0},e)+";"),o.push("}"),o.push("a {"),o.push(" color: "+Xe({r:0,g:64,b:255},e)+";"),o.push("}"),o.push("table {"),o.push(" border-color: "+Ze({r:128,g:128,b:128},e)+";"),o.push("}"),o.push("::placeholder {"),o.push(" color: "+Xe({r:169,g:169,b:169},e)+";"),o.push("}"),o.push("input:-webkit-autofill,"),o.push("textarea:-webkit-autofill,"),o.push("select:-webkit-autofill {"),o.push(" background-color: "+Ve({r:250,g:255,b:189},e)+" !important;"),o.push(" color: "+Xe({r:0,g:0,b:0},e)+" !important;"),o.push("}"),e.scrollbarColor&&o.push(function(e){var t,n,o,a,i,u,c=[];if("auto"===e.scrollbarColor)t=Ve({r:241,g:241,b:241},e),n=Xe({r:96,g:96,b:96},e),o=Ve({r:176,g:176,b:176},e),a=Ve({r:144,g:144,b:144},e),i=Ve({r:96,g:96,b:96},e),u=Ve({r:255,g:255,b:255},e);else{var s=fe(be(e.scrollbarColor)),l=s.l>.5,d=function(e){return r(r({},s),{l:Re(s.l+e,0,1)})},f=function(e){return r(r({},s),{l:Re(s.l-e,0,1)})};t=pe(f(.4)),n=pe(l?f(.4):d(.4)),o=pe(s),a=pe(d(.1)),i=pe(d(.2))}return c.push("::-webkit-scrollbar {"),c.push(" background-color: "+t+";"),c.push(" color: "+n+";"),c.push("}"),c.push("::-webkit-scrollbar-thumb {"),c.push(" background-color: "+o+";"),c.push("}"),c.push("::-webkit-scrollbar-thumb:hover {"),c.push(" background-color: "+a+";"),c.push("}"),c.push("::-webkit-scrollbar-thumb:active {"),c.push(" background-color: "+i+";"),c.push("}"),c.push("::-webkit-scrollbar-corner {"),c.push(" background-color: "+u+";"),c.push("}"),c.push("* {"),c.push(" scrollbar-color: "+t+" "+o+";"),c.push("}"),c.join("\n")}(e)),e.selectionColor&&o.push(function(e){var r=[],t=gr(e),n=t.backgroundColorSelection,o=t.foregroundColorSelection;return["::selection","::-moz-selection"].forEach((function(e){r.push(e+" {"),r.push(" background-color: "+n+" !important;"),r.push(" color: "+o+" !important;"),r.push("}")})),r.join("\n")}(e)),o.join("\n")}function gr(e){var t,n;if("auto"===e.selectionColor)t=Ve({r:0,g:96,b:212},r(r({},e),{grayscale:0})),n=Xe({r:255,g:255,b:255},r(r({},e),{grayscale:0}));else{var o=fe(be(e.selectionColor));t=e.selectionColor,n=o.l<.5?"#FFF":"#000"}return{backgroundColorSelection:t,foregroundColorSelection:n}}function br(e,r){var t=r.strict,n=[];return n.push("html, body, "+(t?"body :not(iframe)":"body > :not(iframe)")+" {"),n.push(" background-color: "+Ve({r:255,g:255,b:255},e)+" !important;"),n.push(" border-color: "+Ze({r:64,g:64,b:64},e)+" !important;"),n.push(" color: "+Xe({r:0,g:0,b:0},e)+" !important;"),n.push("}"),n.join("\n")}var yr=new Set(["inherit","transparent","initial","currentcolor","none","unset"]),wr=new Map;function Sr(e){if(e=e.trim(),wr.has(e))return wr.get(e);var r=be(e);return wr.set(e,r),r}function kr(e){try{return Sr(e)}catch(e){return null}}var _r=/[\-a-z]+gradient\(([^\(\)]*(\(([^\(\)]*(\(.*?\)))*[^\(\)]*\))){0,15}[^\(\)]*\)/g,xr=new Map,Er=new Map;function Cr(){wr.clear(),qe.clear(),Fe.clear(),xr.clear(),pr(),Er.clear()}var Mr={"background-color":{customProp:"--darkreader-inline-bgcolor",cssProp:"background-color",dataAttr:"data-darkreader-inline-bgcolor",store:new WeakSet},"background-image":{customProp:"--darkreader-inline-bgimage",cssProp:"background-image",dataAttr:"data-darkreader-inline-bgimage",store:new WeakSet},"border-color":{customProp:"--darkreader-inline-border",cssProp:"border-color",dataAttr:"data-darkreader-inline-border",store:new WeakSet},"border-bottom-color":{customProp:"--darkreader-inline-border-bottom",cssProp:"border-bottom-color",dataAttr:"data-darkreader-inline-border-bottom",store:new WeakSet},"border-left-color":{customProp:"--darkreader-inline-border-left",cssProp:"border-left-color",dataAttr:"data-darkreader-inline-border-left",store:new WeakSet},"border-right-color":{customProp:"--darkreader-inline-border-right",cssProp:"border-right-color",dataAttr:"data-darkreader-inline-border-right",store:new WeakSet},"border-top-color":{customProp:"--darkreader-inline-border-top",cssProp:"border-top-color",dataAttr:"data-darkreader-inline-border-top",store:new WeakSet},"box-shadow":{customProp:"--darkreader-inline-boxshadow",cssProp:"box-shadow",dataAttr:"data-darkreader-inline-boxshadow",store:new WeakSet},color:{customProp:"--darkreader-inline-color",cssProp:"color",dataAttr:"data-darkreader-inline-color",store:new WeakSet},fill:{customProp:"--darkreader-inline-fill",cssProp:"fill",dataAttr:"data-darkreader-inline-fill",store:new WeakSet},stroke:{customProp:"--darkreader-inline-stroke",cssProp:"stroke",dataAttr:"data-darkreader-inline-stroke",store:new WeakSet},"outline-color":{customProp:"--darkreader-inline-outline",cssProp:"outline-color",dataAttr:"data-darkreader-inline-outline",store:new WeakSet}},Ar=Object.values(Mr),Rr=["style","fill","stroke","bgcolor","color"],Lr=Rr.map((function(e){return"["+e+"]"})).join(", ");function Tr(){return Ar.map((function(e){var r=e.dataAttr,t=e.customProp;return["["+r+"] {"," "+e.cssProp+": var("+t+") !important;","}"].join("\n")})).join("\n")}var Pr=new Map,jr=new Map;function Wr(e,r,t){Pr.has(e)&&(Pr.get(e).disconnect(),jr.get(e).disconnect());var n=new WeakSet;function o(e){(function(e){var r=[];return e instanceof Element&&e.matches(Lr)&&r.push(e),(e instanceof Element||h&&e instanceof ShadowRoot||e instanceof Document)&&j(r,e.querySelectorAll(Lr)),r})(e).forEach((function(e){n.has(e)||(n.add(e),r(e))})),te(e,(function(o){n.has(e)||(n.add(e),t(o.shadowRoot),Wr(o.shadowRoot,r,t))}))}var a=le(e,{onMinorMutations:function(e){e.additions.forEach((function(e){return o(e)}))},onHugeMutations:function(){o(e)}});Pr.set(e,a);var u=0,c=null,s=Z({seconds:10}),l=Z({seconds:2}),d=[],f=null,p=Y((function(e){e.forEach((function(e){Rr.includes(e.attributeName)&&r(e.target),Ar.filter((function(r){var t=r.store,n=r.dataAttr;return t.has(e.target)&&!e.target.hasAttribute(n)})).forEach((function(r){var t=r.dataAttr;return e.target.setAttribute(t,"")}))}))})),m=new MutationObserver((function(e){if(f)d.push.apply(d,i(e));else{u++;var r=Date.now();if(null==c)c=r;else if(u>=50){if(r-c0&&function(e,r){for(var t=0,n=r.length;t32||s>32}l("fill",i?"background-color":"color",a)}if(e.hasAttribute("stroke")){a=e.getAttribute("stroke");l("stroke",e instanceof SVGLineElement||e instanceof SVGTextElement?"border-color":"color",a)}e.style&&I(e.style,(function(e,r){"background-image"===e&&r.indexOf("url")>=0||Mr.hasOwnProperty(e)&&l(e,e,r)})),e.style&&e instanceof SVGTextElement&&e.style.fill&&l("fill","color",e.style.getPropertyValue("fill")),P(o,(function(r){var t=Mr[r],n=t.store,o=t.dataAttr;n.delete(e),e.removeAttribute(o)})),Or.set(e,Fr(e,r))}}function l(t,a,i){var u=Mr[t],c=u.customProp,s=u.dataAttr,l=mr(a,i,null,n,null);if(l){var d=l.value;"function"==typeof d&&(d=d(r)),e.style.setProperty(c,d),e.hasAttribute(s)||e.setAttribute(s,""),o.delete(t)}}}var Nr="theme-color",Ir='meta[name="theme-color"]',Ur=null,zr=null;function Dr(e,r){Ur=Ur||e.content;try{var t=be(Ur);e.content=Ve(t,r)}catch(e){B(e)}}var $r=["mode","brightness","contrast","grayscale","sepia","darkSchemeBackgroundColor","darkSchemeTextColor","lightSchemeBackgroundColor","lightSchemeTextColor"];var Hr=null;var Vr=function(){var e=[],r=null;function t(){for(var t;t=e.shift();)t();r=null}return{add:function(n){e.push(n),r||(r=requestAnimationFrame(t))},cancel:function(){e.splice(0),cancelAnimationFrame(r),r=null}}}();function Gr(){var e=0,r=new Map,t=new Map,n=null;return{modifySheet:function(o){var a=o.sourceCSSRules,i=o.theme,u=o.variables,c=o.ignoreImageAnalysis,s=o.force,l=o.prepareSheet,d=o.isAsyncCancelled,f=0===t.size,h=new Set(t.keys()),p=function(e){return $r.map((function(r){return r+":"+e[r]})).join(";")}(i),v=p!==n,g=[];if(N(a,(function(e){var n,o=e.cssText,a=!1;h.delete(o),r.has(o)||(r.set(o,o),a=!0);var i=null;if(u.size>0||o.includes("var(")){var s=X(o,u);r.get(o)!==s&&(r.set(o,s),a=!0,(n=function(){if(Hr)return Hr;if(m)return Hr=new CSSStyleSheet;var e=document.createElement("style");return document.head.append(e),Hr=e.sheet,document.head.removeChild(e),Hr}()).insertRule(s),i=n.cssRules[0])}if(a){f=!0;var l=[],p=i||e;p&&p.style&&I(p.style,(function(r,t){var n=mr(r,t,e,c,d);n&&l.push(n)}));var v=null;if(l.length>0){var b=e.parentRule;v={selector:e.selectorText,declarations:l,parentRule:b},g.push(v)}t.set(o,v),n&&n.deleteRule(0)}else g.push(t.get(o))})),h.forEach((function(e){r.delete(e),t.delete(e)})),n=p,s||f||v){e++;var b=new Map,y=0,w={rule:null,rules:[],isGroup:!0},S=new WeakMap;g.filter((function(e){return e})).forEach((function(r){var t=r.selector,n=r.declarations,o=x(r.parentRule),a={selector:t,declarations:[],isGroup:!1},u=a.declarations;o.rules.push(a),n.forEach((function(r){var t=r.property,n=r.value,o=r.important,a=r.sourceValue;if("function"==typeof n){var c=n(i);if(c instanceof Promise){var s=y++,l={property:t,value:null,important:o,asyncKey:s,sourceValue:a};u.push(l);var f=e;c.then((function(r){r&&!d()&&f===e&&(l.value=r,Vr.add((function(){d()||f!==e||function(e){var r=b.get(e),t=r.rule,n=r.target,o=r.index;n.deleteRule(o),_(n,o,t),b.delete(e)}(s)})))}))}else u.push({property:t,value:c,important:o,sourceValue:a})}else u.push({property:t,value:n,important:o,sourceValue:a})}))}));var k=l();!function e(r,t,n){r.rules.forEach((function(r){r.isGroup?e(r,function(e,r){var t=e.rule;if(t instanceof CSSMediaRule){var n=t.media,o=r.cssRules.length;return r.insertRule("@media "+n+" {}",o),r.cssRules[o]}return r}(r,t),n):n(r,t)}))}(w,k,(function(e,r){var t=r.cssRules.length;e.declarations.filter((function(e){return null==e.value})).forEach((function(n){var o=n.asyncKey;return b.set(o,{rule:e,target:r,index:t})})),_(r,t,e)}))}function _(e,r,t){var n=t.selector,o=t.declarations;e.insertRule(n+" {}",r);var a=e.cssRules[r].style;o.forEach((function(e){var r=e.property,t=e.value,n=e.important,o=e.sourceValue;a.setProperty(r,null==t?o:t,n?"important":"")}))}function x(e){if(null==e)return w;if(S.has(e))return S.get(e);var r={rule:e,rules:[],isGroup:!0};return S.set(e,r),x(e.parentRule).rules.push(r),r}}}}function Kr(e){return(e instanceof HTMLStyleElement||e instanceof SVGStyleElement||e instanceof HTMLLinkElement&&e.rel&&e.rel.toLowerCase().includes("stylesheet")&&!e.disabled)&&!e.classList.contains("darkreader")&&"print"!==e.media&&!e.classList.contains("stylus")}function Jr(e,r,t){return void 0===r&&(r=[]),void 0===t&&(t=!0),Kr(e)?r.push(e):(e instanceof Element||h&&e instanceof ShadowRoot||e===document)&&(P(e.querySelectorAll('style, link[rel*="stylesheet" i]:not([disabled])'),(function(e){return Jr(e,r,!1)})),t&&te(e,(function(e){return Jr(e.shadowRoot,r,!1)}))),r}var Qr=new WeakSet,Xr=new WeakSet;function Yr(e,r){for(var o=r.update,i=r.loadingStart,u=r.loadingEnd,c=[],s=e;(s=s.nextElementSibling)&&s.matches(".darkreader");)c.push(s);var l=c.find((function(e){return e.matches(".darkreader--cors")&&!Xr.has(e)}))||null,d=c.find((function(e){return e.matches(".darkreader--sync")&&!Qr.has(e)}))||null,f=null,h=null,p=!1,m=Gr(),v=new MutationObserver((function(){o()})),g={attributes:!0,childList:!0,characterData:!0};function b(){return e instanceof HTMLStyleElement&&e.textContent.trim().match(H)}function y(){return l?l.sheet.cssRules:b()?null:C()}function w(){l?(e.nextSibling!==l&&e.parentNode.insertBefore(l,e.nextSibling),l.nextSibling!==d&&e.parentNode.insertBefore(d,l.nextSibling)):e.nextSibling!==d&&e.parentNode.insertBefore(d,e.nextSibling)}var S=!1,k=!1;function _(){return t(this,void 0,void 0,(function(){var r,t,o,i,u,c,s;return n(this,(function(n){switch(n.label){case 0:if(!(e instanceof HTMLLinkElement))return[3,7];if(o=a(E(),2),i=o[0],(u=o[1])&&B(u),!(i&&!u||(h=u,h&&h.message&&h.message.includes("loading"))))return[3,5];n.label=1;case 1:return n.trys.push([1,3,,4]),[4,(d=e,new Promise((function(e,r){var t=function(){d.removeEventListener("load",n),d.removeEventListener("error",o)},n=function(){t(),e()},o=function(){t(),r("Link loading failed "+d.href)};d.addEventListener("load",n),d.addEventListener("error",o)})))];case 2:return n.sent(),[3,4];case 3:return B(n.sent()),k=!0,[3,4];case 4:if(p)return[2,null];s=a(E(),2),i=s[0],(u=s[1])&&B(u),n.label=5;case 5:return null!=i?[2,i]:[4,Zr(e.href)];case 6:return r=n.sent(),t=G(e.href),p?[2,null]:[3,8];case 7:if(!b())return[2,null];r=e.textContent.trim(),t=G(location.href),n.label=8;case 8:if(!r)return[3,13];n.label=9;case 9:return n.trys.push([9,11,,12]),[4,et(r,t)];case 10:return c=n.sent(),l=function(e,r){if(!r)return null;var t=document.createElement("style");return t.classList.add("darkreader"),t.classList.add("darkreader--cors"),t.media="screen",t.textContent=r,e.parentNode.insertBefore(t,e.nextSibling),t.sheet.disabled=!0,Xr.add(t),t}(e,c),[3,12];case 11:return B(n.sent()),[3,12];case 12:if(l)return f=re(l,"prev-sibling"),[2,l.sheet.cssRules];n.label=13;case 13:return[2,null]}var d,h}))}))}var x=!1;function E(){try{return null==e.sheet?[null,null]:[e.sheet.cssRules,null]}catch(e){return[null,e]}}function C(){var e=a(E(),2),r=e[0],t=e[1];return t?(B(t),null):r}var M=null,A=null;function R(){var e=C();e&&(M=e.length)}function L(){R(),T();var e=function(){var r;(r=C())&&r.length!==M&&(R(),o()),A=requestAnimationFrame(e)};e()}function T(){cancelAnimationFrame(A)}function P(){v.disconnect(),p=!0,f&&f.stop(),h&&h.stop(),T()}var j=0;return{details:function(){var e=y();return e?{variables:z(e)}:(S||k||(S=!0,i(),_().then((function(e){S=!1,u(),e&&o()})).catch((function(e){B(e),S=!1,u()}))),null)},render:function(r,t,n){var o=y();function a(){d||((d=e instanceof SVGStyleElement?document.createElementNS("http://www.w3.org/2000/svg","style"):document.createElement("style")).classList.add("darkreader"),d.classList.add("darkreader--sync"),d.media="screen",Qr.add(d)),h&&h.stop(),null==d.sheet&&(d.textContent=""),w();for(var r=d.sheet,t=r.cssRules.length-1;t>=0;t--)r.deleteRule(t);return h?h.run():h=re(d,"prev-sibling",(function(){x=!0,i()})),d.sheet}function i(){var e=x;x=!1,m.modifySheet({prepareSheet:a,sourceCSSRules:o,theme:r,variables:t,ignoreImageAnalysis:n,force:e,isAsyncCancelled:function(){return p}})}o&&(p=!1,i())},pause:P,destroy:function(){P(),ee(l),ee(d)},watch:function(){v.observe(e,g),e instanceof HTMLStyleElement&&L()},restore:function(){if(d)if(++j>10)B("Style sheet was moved multiple times",e);else{B("Restore style",d,e);var r=null==d.sheet||d.sheet.cssRules.length>0;w(),f&&f.skip(),h&&h.skip(),r&&(x=!0,R(),o())}}}}function Zr(e){return t(this,void 0,void 0,(function(){return n(this,(function(r){switch(r.label){case 0:return e.startsWith("data:")?[4,fetch(e)]:[3,3];case 1:return[4,r.sent().text()];case 2:return[2,r.sent()];case 3:return[4,ar({url:e,responseType:"text",mimeType:"text/css"})];case 4:return[2,r.sent()]}}))}))}function et(e,r,a){return void 0===a&&(a=new Map),t(this,void 0,void 0,(function(){var t,i,u,c,s,l,d,f,h,p;return n(this,(function(n){switch(n.label){case 0:e=function(e,r){return e.replace($,(function(e){var t=V(e);return'url("'+q(r,t)+'")'}))}(e=function(e){return e.replace(J,"")}(e=e.replace(K,"")),r),t=Te(H,e),n.label=1;case 1:n.trys.push([1,10,11,12]),i=o(t),u=i.next(),n.label=2;case 2:return u.done?[3,9]:(c=u.value,s=V(c.substring(8).replace(/;$/,"")),l=q(r,s),d=void 0,a.has(l)?(d=a.get(l),[3,7]):[3,3]);case 3:return n.trys.push([3,6,,7]),[4,Zr(l)];case 4:return d=n.sent(),a.set(l,d),[4,et(d,G(l),a)];case 5:return d=n.sent(),[3,7];case 6:return B(n.sent()),d="",[3,7];case 7:e=e.split(c).join(d),n.label=8;case 8:return u=i.next(),[3,2];case 9:return[3,12];case 10:return f=n.sent(),h={error:f},[3,12];case 11:try{u&&!u.done&&(p=i.return)&&p.call(i)}finally{if(h)throw h.error}return[7];case 12:return[2,e=e.trim()]}}))}))}var rt,tt,nt=[],ot=new Map;function at(e){p&&P(e.querySelectorAll(":not(:defined)"),(function(e){var r=e.tagName.toLowerCase();ot.has(r)||(ot.set(r,new Set),function(e){return new Promise((function(r){if(window.customElements&&"function"==typeof window.customElements.whenDefined)customElements.whenDefined(e).then(r);else{var t=function(){var n=ot.get(e);n&&n.size>0&&(n.values().next().value.matches(":defined")?r():requestAnimationFrame(t))};requestAnimationFrame(t)}}))}(r).then((function(){if(tt){var e=ot.get(r);ot.delete(r),tt(Array.from(e))}}))),ot.get(r).add(e)}))}function it(e,r,t){ut();var n=new Set(e),o=new WeakMap,a=new WeakMap;function i(e){o.set(e,e.previousElementSibling),a.set(e,e.nextElementSibling)}function u(e){var t=e.createdStyles,u=e.removedStyles,c=e.movedStyles;t.forEach((function(e){return i(e)})),c.forEach((function(e){return i(e)})),u.forEach((function(e){return r=e,o.delete(r),void a.delete(r);var r})),t.forEach((function(e){return n.add(e)})),u.forEach((function(e){return n.delete(e)})),t.size+u.size+c.size>0&&r({created:Array.from(t),removed:Array.from(u),moved:Array.from(c),updated:[]})}function c(e){var r=e.additions,t=e.moves,n=e.deletions,o=new Set,a=new Set,i=new Set;r.forEach((function(e){return Jr(e).forEach((function(e){return o.add(e)}))})),n.forEach((function(e){return Jr(e).forEach((function(e){return a.add(e)}))})),t.forEach((function(e){return Jr(e).forEach((function(e){return i.add(e)}))})),u({createdStyles:o,removedStyles:a,movedStyles:i}),r.forEach((function(e){te(e,f),at(e)}))}function s(e){var r=new Set(Jr(e)),t=new Set,i=new Set,c=new Set;r.forEach((function(e){n.has(e)||t.add(e)})),n.forEach((function(e){r.has(e)||i.add(e)})),r.forEach((function(e){var r;t.has(e)||i.has(e)||(r=e).previousElementSibling===o.get(r)&&r.nextElementSibling===a.get(r)||c.add(e)})),u({createdStyles:t,removedStyles:i,movedStyles:c}),te(e,f),at(e)}function l(e){var t=new Set;e.forEach((function(e){Kr(e.target)&&e.target.isConnected&&t.add(e.target)})),t.size>0&&r({updated:Array.from(t),created:[],removed:[],moved:[]})}function d(e){var r=le(e,{onMinorMutations:c,onHugeMutations:s}),t=new MutationObserver(l);t.observe(e,{attributes:!0,attributeFilter:["rel","disabled","media"],subtree:!0}),nt.push(r,t),rt.add(e)}function f(e){var r=e.shadowRoot;null==r||rt.has(r)||(d(r),t(r))}e.forEach(i),d(document),te(document.documentElement,f),tt=function(e){var t=[];e.forEach((function(e){return j(t,Jr(e.shadowRoot))})),r({created:t,updated:[],removed:[],moved:[]}),e.forEach((function(e){var r=e.shadowRoot;null!=r&&(f(e),te(r,f),at(r))}))},at(document)}function ut(){nt.forEach((function(e){return e.disconnect()})),nt.splice(0,nt.length),rt=new WeakSet,tt=null,ot.clear()}var ct=new WeakMap,st=new WeakSet;function lt(e){var r=!1;return{render:function(t,n,o){e.adoptedStyleSheets.forEach((function(a){if(!st.has(a)){var u=a.rules,c=new CSSStyleSheet,s=n;z(a.cssRules).forEach((function(e,r){return s.set(r,e)})),Gr().modifySheet({prepareSheet:function(){for(var r=c.cssRules.length-1;r>=0;r--)c.deleteRule(r);return function(r,t){var n=i(e.adoptedStyleSheets),o=n.indexOf(r),a=n.indexOf(t);o!==a-1&&(a>=0&&n.splice(a,1),n.splice(o+1,0,t),e.adoptedStyleSheets=n)}(a,c),ct.set(a,c),st.add(c),c},sourceCSSRules:u,theme:t,variables:s,ignoreImageAnalysis:o,force:!1,isAsyncCancelled:function(){return r}})}}))},destroy:function(){r=!0;var t=i(e.adoptedStyleSheets);e.adoptedStyleSheets.forEach((function(e){if(st.has(e)){var r=t.indexOf(e);r>=0&&t.splice(r,1),ct.delete(e),st.delete(e)}})),e.adoptedStyleSheets=t}}}var dt=new Map,ft=Array.from(crypto.getRandomValues(new Uint8Array(16))).map((function(e){return((r=e)<16?"0":"")+r.toString(16);var r})).join(""),ht=new Map,pt=[],mt=null,vt=null,gt=null;function bt(e,r){void 0===r&&(r=document.head||document);var t=r.querySelector("."+e);return t||((t=document.createElement("style")).classList.add("darkreader"),t.classList.add(e),t.media="screen"),t}var yt=new Map;function wt(e,r){yt.has(r)&&yt.get(r).stop(),yt.set(r,re(e,"parent"))}var St=new Set;function kt(e){var r=bt("darkreader--inline",e);r.textContent=Tr(),e.insertBefore(r,e.firstChild);var t=bt("darkreader--override",e);t.textContent=vt&&vt.css?_t(vt.css):"",e.insertBefore(t,r.nextSibling),St.add(e)}function _t(e){return e.replace(/\${(.+?)}/g,(function(e,r){try{var t=Sr(r);return Ue(t,mt,ze)}catch(e){return B(e),r}}))}function xt(){var e=document.querySelector(".darkreader--fallback");e&&(e.textContent="")}function Et(){return vt&&Array.isArray(vt.ignoreImageAnalysis)?vt.ignoreImageAnalysis:[]}var Ct=0,Mt=new Set;function At(e){var r=++Ct;var t=Yr(e,{update:function(){var e=t.details();e&&(0===e.variables.size?t.render(mt,dt,Et()):(Rt(e.variables),Tt()))},loadingStart:function(){if(!ne()||!Ot){Mt.add(r);var e=document.querySelector(".darkreader--fallback");e.textContent||(e.textContent=br(mt,{strict:!1}))}},loadingEnd:function(){Mt.delete(r),0===Mt.size&&ne()&&xt()}});return ht.set(e,t),t}function Rt(e){0!==e.size&&(e.forEach((function(e,r){dt.set(r,e)})),dt.forEach((function(e,r){dt.set(r,X(e,dt))})))}function Lt(e){var r=ht.get(e);r&&(r.destroy(),ht.delete(e))}var Tt=Y((function(e){ht.forEach((function(e){return e.render(mt,dt,Et())})),pt.forEach((function(e){return e.render(mt,dt,Et())})),e&&e()})),Pt=function(){Tt.cancel()};function jt(){0===Mt.size&&xt()}var Wt=null,Ot=!document.hidden;function qt(){document.removeEventListener("visibilitychange",Wt),Wt=null}function Ft(){function e(){!function(){Pt(),Rt(D(document.documentElement));var e=Jr(document).filter((function(e){return!ht.has(e)})).map((function(e){return At(e)})),r=e.map((function(e){return e.details()})).filter((function(e){return e&&e.variables.size>0})).map((function(e){return e.variables}));0===r.length?(ht.forEach((function(e){return e.render(mt,dt,Et())})),0===Mt.size&&xt()):(r.forEach((function(e){return Rt(e)})),Tt((function(){0===Mt.size&&xt()}))),e.forEach((function(e){return e.watch()}));var t=function(e){for(var r=[],t=0,n=e.length;t0&&(kt(e.shadowRoot),j(t,r))}));var n=vt&&Array.isArray(vt.ignoreInlineStyle)?vt.ignoreInlineStyle:[];t.forEach((function(e){return Br(e,mt,Et(),n)})),Bt(document)}(),function(){it(Array.from(ht.keys()),(function(e){var r=e.created,t=e.updated,n=e.removed,o=e.moved,a=n,i=r.concat(t).concat(o).filter((function(e){return!ht.has(e)})),u=o.filter((function(e){return ht.has(e)}));a.forEach((function(e){return Lt(e)}));var c=i.map((function(e){return At(e)})),s=c.map((function(e){return e.details()})).filter((function(e){return e&&e.variables.size>0})).map((function(e){return e.variables}));0===s.length?c.forEach((function(e){return e.render(mt,dt,Et())})):(s.forEach((function(e){return Rt(e)})),Tt()),c.forEach((function(e){return e.watch()})),u.forEach((function(e){return ht.get(e).restore()}))}),(function(e){kt(e),Bt(e)}));var e=vt&&Array.isArray(vt.ignoreInlineStyle)?vt.ignoreInlineStyle:[];r=function(r){if(Br(r,mt,e,Et()),r===document.documentElement){var t=D(document.documentElement);t.size>0&&(Rt(t),Tt())}},t=function(r){var t=r.querySelectorAll(Lr);t.length>0&&(kt(r),P(t,(function(r){return Br(r,mt,Et(),e)})))},Wr(document,r,t),te(document.documentElement,(function(e){Wr(e.shadowRoot,r,t)})),ae(jt);var r,t}()}var t,n,o,a;!function(){var e=bt("darkreader--fallback",document);e.textContent=br(mt,{strict:!0}),document.head.insertBefore(e,document.head.firstChild),wt(e,"fallback");var t=bt("darkreader--user-agent");t.textContent=vr(mt,gt,mt.styleSystemControls),document.head.insertBefore(t,e.nextSibling),wt(t,"user-agent");var n,o,a=bt("darkreader--text");mt.useFont||mt.textStroke>0?a.textContent=(n=mt,(o=[]).push("*:not(pre) {"),n.useFont&&n.fontFamily&&o.push(" font-family: "+n.fontFamily+" !important;"),n.textStroke>0&&(o.push(" -webkit-text-stroke: "+n.textStroke+"px !important;"),o.push(" text-stroke: "+n.textStroke+"px !important;")),o.push("}"),o.join("\n")):a.textContent="",document.head.insertBefore(a,e.nextSibling),wt(a,"text");var i=bt("darkreader--invert");vt&&Array.isArray(vt.invert)&&vt.invert.length>0?i.textContent=[vt.invert.join(", ")+" {"," filter: "+rr(r(r({},mt),{contrast:0===mt.mode?mt.contrast:Re(mt.contrast-10,0,100)}))+" !important;","}"].join("\n"):i.textContent="",document.head.insertBefore(i,a.nextSibling),wt(i,"invert");var u=bt("darkreader--inline");u.textContent=Tr(),document.head.insertBefore(u,i.nextSibling),wt(u,"inline");var c=bt("darkreader--override");c.textContent=vt&&vt.css?_t(vt.css):"",document.head.appendChild(c),wt(c,"override");var s=bt("darkreader--variables"),l=gr(mt),d=mt.darkSchemeBackgroundColor,f=mt.darkSchemeTextColor,h=mt.lightSchemeBackgroundColor,p=mt.lightSchemeTextColor;s.textContent=[":root {"," --darkreader-neutral-background: "+(0===mt.mode?h:d)+";"," --darkreader-neutral-text: "+(0===mt.mode?p:f)+";"," --darkreader-selection-background: "+l.backgroundColorSelection+";"," --darkreader-selection-text: "+l.foregroundColorSelection+";","}"].join("\n"),document.head.insertBefore(s,u.nextSibling),wt(s,"variables")}(),document.hidden?(t=e,n=Boolean(Wt),Wt=function(){document.hidden||(qt(),t(),Ot=!0)},n||document.addEventListener("visibilitychange",Wt)):e(),o=mt,(a=document.querySelector(Ir))?Dr(a,o):(zr&&zr.disconnect(),(zr=new MutationObserver((function(e){e:for(var r=0;r0){var r=lt(e);pt.push(r),r.render(mt,dt,Et())}}function Nt(){ht.forEach((function(e){return e.pause()})),P(yt.values(),(function(e){return e.stop()})),yt.clear(),ut(),Pr.forEach((function(e){return e.disconnect()})),jr.forEach((function(e){return e.disconnect()})),Pr.clear(),jr.clear(),ie(jt)}function It(){var e,r=document.querySelector('meta[name="darkreader"]');return r?r.content!==ft:((e=document.createElement("meta")).name="darkreader",e.content=ft,document.head.appendChild(e),!1)}function Ut(e,r,t){if(mt=e,vt=r,gt=t,document.head){if(It())return;Ft()}else{if(!l){var n=bt("darkreader--fallback");document.documentElement.appendChild(n),n.textContent=br(mt,{strict:!0})}var o=new MutationObserver((function(){if(document.head){if(o.disconnect(),It())return void zt();Ft()}}));o.observe(document,{childList:!0,subtree:!0})}}function zt(){qt(),Pt(),Nt(),Cr(),ee(document.querySelector(".darkreader--fallback")),document.head&&(!function(){zr&&(zr.disconnect(),zr=null);var e=document.querySelector(Ir);e&&Ur&&(e.content=Ur)}(),ee(document.head.querySelector(".darkreader--user-agent")),ee(document.head.querySelector(".darkreader--text")),ee(document.head.querySelector(".darkreader--invert")),ee(document.head.querySelector(".darkreader--inline")),ee(document.head.querySelector(".darkreader--override")),ee(document.head.querySelector('meta[name="darkreader"]'))),St.forEach((function(e){ee(e.querySelector(".darkreader--inline")),ee(e.querySelector(".darkreader--override"))})),St.clear(),P(ht.keys(),(function(e){return Lt(e)})),P(document.querySelectorAll(".darkreader"),ee),pt.forEach((function(e){e.destroy()})),pt.splice(0)}var Dt=/url\(\"(blob\:.*?)\"\)/g;function $t(e){return t(this,void 0,void 0,(function(){var r,t;return n(this,(function(n){switch(n.label){case 0:return r=[],Te(Dt,e,1).forEach((function(e){var t=g(e);r.push(t)})),[4,Promise.all(r)];case 1:return t=n.sent(),[2,e.replace(Dt,(function(){return'url("'+t.shift()+'")'}))]}}))}))}function Ht(){return t(this,void 0,void 0,(function(){function e(e,t){var n=document.querySelector(e);n&&n.textContent&&(r.push("/* "+t+" */"),r.push(n.textContent),r.push(""))}var r,t,o,a,i;return n(this,(function(n){switch(n.label){case 0:return r=['/*\n _______\n / \\\n .==. .==.\n (( ))==(( ))\n / "==" "=="\\\n /____|| || ||___\\\n ________ ____ ________ ___ ___\n | ___ \\ / \\ | ___ \\ | | / /\n | | \\ \\ / /\\ \\ | | \\ \\| |_/ /\n | | ) / /__\\ \\ | |__/ /| ___ \\\n | |__/ / ______ \\| ____ \\| | \\ \\\n_______|_______/__/ ____ \\__\\__|___\\__\\__|___\\__\\____\n| ___ \\ | ____/ / \\ | ___ \\ | ____| ___ \\\n| | \\ \\| |___ / /\\ \\ | | \\ \\| |___| | \\ \\\n| |__/ /| ____/ /__\\ \\ | | ) | ____| |__/ /\n| ____ \\| |__/ ______ \\| |__/ /| |___| ____ \\\n|__| \\__\\____/__/ \\__\\_______/ |______|__| \\__\\\n https://darkreader.org\n*/'],e(".darkreader--fallback","Fallback Style"),e(".darkreader--user-agent","User-Agent Style"),e(".darkreader--text","Text Style"),e(".darkreader--invert","Invert Style"),e(".darkreader--variables","Variables Style"),t=[],document.querySelectorAll(".darkreader--sync").forEach((function(e){P(e.sheet.cssRules,(function(e){e&&e.cssText&&t.push(e.cssText)}))})),0==t.length?[3,2]:(o=function(e){function r(e){return e.replace(/^\s+/,"")}function t(e){return 0===e?"":" ".repeat(4*e)}for(var n=/[^{}]+{\s*}/g;n.test(e);)e=e.replace(n,"");for(var o=e.replace(/\s{2,}/g," ").replace(/\{/g,"{\n").replace(/\}/g,"\n}\n").replace(/\;(?![^\(|\"]*(\)|\"))/g,";\n").replace(/\,(?![^\(|\"]*(\)|\"))/g,",\n").replace(/\n\s*\n/g,"\n").split("\n"),a=0,i=[],u=0,c=o.length;u { 24 | DarkReader.setFetchMethod(window.fetch) 25 | DarkReader.auto({ 26 | brightness: 100, 27 | contrast: 90, 28 | sepia: 10 29 | }); 30 | })(); 31 | --------------------------------------------------------------------------------