├── 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 |
--------------------------------------------------------------------------------