返回首頁 

Greasy Fork is available in English.

Greasy Fork++

添加各种功能并改善 Greasy Fork 体验


安装此脚本?
// ==UserScript==// @name               Greasy Fork++// @namespace          https://github.com/iFelix18// @version            3.3.4// @author             CY Fung <https://greasyfork.org/users/371179> & Davide <[email protected]>// @icon               https://www.google.com/s2/favicons?domain=https://greasyfork.org// @description        Adds various features and improves the Greasy Fork experience// @description:de     Fügt verschiedene Funktionen hinzu und verbessert das Greasy Fork-Erlebnis// @description:es     Agrega varias funciones y mejora la experiencia de Greasy Fork// @description:fr     Ajoute diverses fonctionnalités et améliore l'expérience Greasy Fork// @description:it     Aggiunge varie funzionalità e migliora l'esperienza di Greasy Fork// @description:ru     Добавляет различные функции и улучшает работу с Greasy Fork// @description:zh-CN  添加各种功能并改善 Greasy Fork 体验// @description:zh-TW  加入多種功能並改善Greasy Fork的體驗// @description:ja     Greasy Forkの体験を向上させる様々な機能を追加// @description:ko     Greasy Fork 경험을 향상시키고 다양한 기능을 추가// @copyright          2023, CY Fung (https://greasyfork.org/users/371179); 2021, Davide (https://github.com/iFelix18)// @license            MIT// @require            https://fastly.jsdelivr.net/gh/sizzlemctwizzle/GM_config@06f2015c04db3aaab9717298394ca4f025802873/gm_config.min.js// @require            https://fastly.jsdelivr.net/npm/@violentmonkey/[email protected]/dist/index.min.js// @require            https://fastly.jsdelivr.net/gh/cyfung1031/userscript-supports@3fa07109efca28a21094488431363862ccd52d7c/library/WinComm.min.js// @match              *://greasyfork.org/*// @match              *://sleazyfork.org/*// @match              *://cn-greasyfork.org/*// @match              *://api.greasyfork.org/*// @match              *://api.sleazyfork.org/*// @match              *://api.cn-greasyfork.org/*// @connect            greasyfork.org// @connect            sleazyfork.org// @connect            cn-greasyfork.org// @compatible         chrome// @compatible         edge// @compatible         firefox// @compatible         safari// @compatible         brave// @grant              GM.deleteValue// @grant              GM.getValue// @grant              GM.notification// @grant              GM.registerMenuCommand// @grant              GM.setValue// @grant              unsafeWindow// @run-at             document-start// @inject-into        content// ==/UserScript==/* global GM_config, VM, GM, WinComm */const isInIframe = window !== top;/*** @typedef { typeof import("./library/WinComm.js")  } WinComm*/// console.log(GM)/** @type {WinComm} */const WinComm = this.WinComm;//  -------- UU Fucntion - original code: https://fastly.jsdelivr.net/npm/@ifelix18/[email protected]/lib/index.min.js  --------// optimized by CY Fung to remove $ dependency and observe creationconst UU = isInIframe || (function () {const scriptName = GM.info.script.name; // not name_i18nconst scriptVersion = GM.info.script.version;const authorMatch = /^(.*?)\s<\S[^\s@]*@\S[^\s.]*\.\S+>$/.exec(GM.info.script.author);const author = authorMatch ? authorMatch[1] : GM.info.script.author;let scriptId = scriptName.toLowerCase().replace(/\s/g, "-");let loggingEnabled = false;const log = (message) => {if (loggingEnabled) {console.log(`${scriptName}:`, message);}};const error = (message) => {console.error(`${scriptName}:`, message);};const warn = (message) => {console.warn(`${scriptName}:`, message);};const alert = (message) => {window.alert(`${scriptName}: ${message}`);};/** @param {string} text */const short = (text, length) => {const s = text.split(" ");const l = Number(length);return s.length > l? `${s.slice(0, l).join(" ")} [...]`: text;};const addStyle = (css) => {const head = document.head || document.querySelector("head");const style = document.createElement("style");style.textContent = css;head.appendChild(style);};const init = async (options = {}) => {scriptId = options.id || scriptId;loggingEnabled = typeof options.logging === "boolean" ? options.logging : false;console.info(`%c${scriptName}\n%cv${scriptVersion}${author ? ` by ${author}` : ""} is running!`,"color:red;font-weight:700;font-size:18px;text-transform:uppercase","");};return {init,log,error,warn,alert,short,addStyle};})();//  -------- UU Fucntion - original code: https://fastly.jsdelivr.net/npm/@ifelix18/[email protected]/lib/index.min.js  --------const mWindow = isInIframe || (() => {const fields = {hideBlacklistedScripts: {label: 'Hide blacklisted scripts:<br><span>Choose which lists to activate in the section below, press <b>Ctrl + Alt + B</b> to show Blacklisted scripts</span>',section: ['Features'],labelPos: 'right',type: 'checkbox',default: true},hideHiddenScript: {label: 'Hide scripts:<br><span>Add a button to hide the script<br>See and edit the list of hidden scripts below, press <b>Ctrl + Alt + H</b> to show Hidden script',labelPos: 'right',type: 'checkbox',default: true},showInstallButton: {label: 'Install button:<br><span>Add to the scripts list a button to install the script directly</span>',labelPos: 'right',type: 'checkbox',default: true},showTotalInstalls: {label: 'Installations:<br><span>Shows the number of daily and total installations on the user profile</span>',labelPos: 'right',type: 'checkbox',default: true},milestoneNotification: {label: 'Milestone notifications:<br><span>Get notified whenever your total installs got over any of these milestone<br>Separate milestones with a comma, leave blank to turn off notifications</span>',labelPos: 'left',type: 'text',title: 'Separate milestones with a comma!',size: 150,default: '10, 100, 500, 1000, 2500, 5000, 10000, 100000, 1000000'},nonLatins: {label: 'Non-Latin:<br><span>This list blocks all scripts with non-Latin characters in the title/description</span>',section: ['Lists'],labelPos: 'right',type: 'checkbox',default: false // not true},blacklist: {label: 'Blacklist:<br><span>A "non-opinionable" list that blocks all scripts with specific words in the title/description, references to "bots", "cheats" and some online game sites, and other "bullshit"</span>',labelPos: 'right',type: 'checkbox',default: true},customBlacklist: {label: 'Custom Blacklist:<br><span>Personal blacklist defined by a set of unwanted words<br>Separate unwanted words with a comma (example: YouTube, Facebook, pizza), leave blank to disable this list</span>',labelPos: 'left',type: 'text',title: 'Separate unwanted words with a comma!',size: 150,default: ''},hiddenList: {label: 'Hidden Scripts:<br><span>Block individual undesired scripts by their unique IDs<br>Separate IDs with a comma</span>',labelPos: 'left',type: 'textarea',title: 'Separate IDs with a comma!',default: '',save: false},hideRecentUsersWithin: {label: 'Hide Recent Users:<br><span>Hide new regeistered users within the last N hours - to avoid seeing comments from spam accounts</span>',labelPos: 'left',type: 'text',title: 'Number only. 0 means disabled. maximum is 168. (Suggested value: 48)',default: '0',size: 150},logging: {label: 'Logging',section: ['Developer options'],labelPos: 'right',type: 'checkbox',default: false},debugging: {label: 'Debugging',labelPos: 'right',type: 'checkbox',default: false}}const logo = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGAAAABgCAMAAADVRocKAAAASFBMVEVHcEwBAQEDAwMAAAACAgIBAQEAAAAREREDAwMBAQH///8WFhYuLi7U1NSdnZ1bW1vExMTq6uqtra309PRERETf399ycnKGhoaVOQEOAAAACnRSTlMAg87/rjLgE1rzhWrqxgAABexJREFUaN61WouSpCAMVPEJKCqi//+nF4IKKig6e1SduzfupEkT8oIkiRlVVdRpnmdlQ0hTZnme1kVV4Zvk96Fla8nH0ZSI8rP0Ks2uwi1Ilv4EURW5K5xS0slhMb/BkD0hrMk/q1HVeSP6QVILMFIY8wagn6ojTV5Xn8RnbFZaoAPQc9bR3gXQ/yaWvYYA8VfKKeXACZVnAE1V9o4on/izWPsb/q9Ji3j5OcrjhiCXohsAQso6lh6QL9qOEd6GAAbKYAInAFAiiqYC5LMeLIaFKeppR3h/BiAkj6CpLuEPmbbHngUBhFZsdAGiaUL5xLBzRrAAZBlk5wpnVJEohHTbuZoAD0uhMUu+uY/bLZHaryBCH4vQCuugbnSoYf5sk+llKWaEETT/Qu2TecmSHaF1KPT6gmkM4hNLLkIR2l/guAZK1fQrS3kVXmChEX5mKb0xICH/gKXrQtf2pbhlyfqFoL/1LUOVEbFwcsuSs5GfAcjJ8dVkknbafpYUfUXSQYWqRP81THcs8fbVMmTVaQU6ENNOdyxNgGRYmFsp2/mQaFiKzGeC1IcVmAjrDjq4LAF9RgdF13CAo3cTDRcAP2OOCjX6UAwCPpbWyGZsCWTMAM0YTGF2Eg0XAD8bramue9jocGVpi5y7LbUUVRO0dRINF2D9bN/PBSqgAizt8gHByJAUddEyTqa7rYF57oZkkgiYj48lYeVTuuh4Hw1A8pWhxr68snQYioOxHSm6A2gq1wuZz68suUMKELst8oCLfAew+rzMecmOLO251wYwa4CDmd4B8GyPM1YDlyXeUp8Gx412A9Chy6vP9cXO0kW+5e6N104vH68sXeW/jwzptss8OihFf1UAY2dVkgDCdQz8dfiv1m3sZek62rcIsJlr/5uADv1bhNqzxrcIb3VIkzz06m9YykMAM39kidIoAG+5R7icHlm6BViUVDqSZknpfd8NZh2MO1Xz+JKlcY###eK3UqjBTDRexn680PVoSxMFBiCST6RJJmXzg2FTegaPzyRWRWu9cERAHW4o6jANmPU0Ewwqe36wa8j1wyQLADHyk1FphM760H1sBY/+PtS5ECQTvucHynoapYPiZJKFDoSNnFxZYl0QYG2gQExtcJFN8LNl1voHOA++5yQelh5yVPhRopma8M3OALMO8p0GhgDT+lgKDatBhhvN5gcuRWaZJeQ8CzVBLmBLd2tgdrLND9xFxh9CW8JABYRSNQVYugJYK8rB2bn5gWOmaM4dzmXQVjvuidMzS3YfpEm9uPnBtp5yNFRJLRUTb9OaiN1x+06uk0q4+cG+U+SqCeoKLmMwrYkp1pYWRbUvgoDjDZng7EScG3/wSxAyK7+/Xvrgl974JZ1gp69r1Bc7LvUlXhEIsSxh4lWU5Ecdwixh6lhlhPwvlkyZlpIvCFEspW4B8h9YWguQYOZynzZEsJTvRWBPxwDABnKuXWJY2ovAKu8H9h7gkSXblqqFIB8AHlhyekbGUk2PYUbXtvgAXGnYjfWwNA+QcDHN3+x2Q2rngENgiSeeAUZfjDMVHkSn1m2GGBVwCh0d8NlfhJ4owiyE+VjiPV0WKQ7tHCxD1h6DeQ7PAMKWvUcERtt2PDakkio9f/1pkdcsxMOSLq7ldD5LAJf3BeCaCfQmDl57s/Xak4sHEJiPjOcdN4f61+n8CDDQaX/iIk8KcrOTDqCC4Km3tdw9AeBM1+dq1IqRE0stI8LbWk6K7AmAjYPeX/jEdF/qJtgpX+pDzfH9eCVunFyt1UEQUt8dUHwE2BE6b2f8A8I1WMxqGLQfyqu7I8zmOwBh08TJrfy36+ANw1XcQdrHEXOeWeTf5edRJ7JV+t/o+UKTc+hRxx8oF+lLaxKCvTmw1vcRshcAbGFZ8eFUv4kF4NnHewn5pM91sauv7z9gumDPPNgoobBq54/XHraLGyAZXPLqaFrnzIMpKoeR/3BxY7t6woWY2hYqZZ0u2DOPeZzZr1dP7OUZbk4MVE+wecrmqcn+5vLMevsneP3ncfwDNtu0vRpuz80AAAAASUVORK5CYII='const locales = { /* cSpell: disable */de: {downgrade: 'Auf zurückstufen',hide: '❌ Dieses skript ausblenden',install: 'Installieren',notHide: '✔️ Dieses skript nicht ausblenden',milestone: 'Herzlichen Glückwunsch, Ihre Skripte haben den Meilenstein von insgesamt $1 Installationen überschritten!',reinstall: 'Erneut installieren',update: 'Auf aktualisieren'},en: {downgrade: 'Downgrade to',hide: '❌ Hide this script',install: 'Install',notHide: '✔️ Not hide this script',milestone: 'Congrats, your scripts got over the milestone of $1 total installs!',reinstall: 'Reinstall',update: 'Update to'},es: {downgrade: 'Degradar a',hide: '❌ Ocultar este script',install: 'Instalar',notHide: '✔️ No ocultar este script',milestone: '¡Felicidades, sus scripts superaron el hito de $1 instalaciones totales!',reinstall: 'Reinstalar',update: 'Actualizar a'},fr: {downgrade: 'Revenir à',hide: '❌ Cacher ce script',install: 'Installer',notHide: '✔️ Ne pas cacher ce script',milestone: 'Félicitations, vos scripts ont franchi le cap des $1 installations au total!',reinstall: 'Réinstaller',update: 'Mettre à'},it: {downgrade: 'Riporta a',hide: '❌ Nascondi questo script',install: 'Installa',notHide: '✔️ Non nascondere questo script',milestone: 'Congratulazioni, i tuoi script hanno superato il traguardo di $1 installazioni totali!',reinstall: 'Reinstalla',update: 'Aggiorna a'},ru: {downgrade: 'Откатить до',hide: '❌ Скрыть этот скрипт',install: 'Установить',notHide: '✔️ Не скрывать этот сценарий',milestone: 'Поздравляем, ваши скрипты преодолели рубеж в $1 установок!',reinstall: 'Переустановить',update: 'Обновить до'},'zh-CN': {downgrade: '降级到',hide: '❌ 隐藏此脚本',install: '安装',notHide: '✔️ 不隐藏此脚本',milestone: '恭喜,您的脚本超过了 $1 次总安装的里程碑!',reinstall: '重新安装',update: '更新到'},'zh-TW': {downgrade: '降級至',hide: '❌ 隱藏此腳本',install: '安裝',notHide: '✔️ 不隱藏此腳本',milestone: '恭喜,您的腳本安裝總數已超過 $1!',reinstall: '重新安裝',update: '更新至'},'ja': {downgrade: 'ダウングレードする',hide: '❌ このスクリプトを隠す',install: 'インストール',notHide: '✔️ このスクリプトを隠さない',milestone: 'おめでとうございます、あなたのスクリプトの合計インストール回数が $1 を超えました!',reinstall: '再インストール',update: '更新する'},'ko': {downgrade: '다운그레이드하기',hide: '❌ 이 스크립트 숨기기',install: '설치',notHide: '✔️ 이 스크립트 숨기지 않기',milestone: '축하합니다, 스크립트의 총 설치 횟수가 $1을 넘었습니다!',reinstall: '재설치',update: '업데이트하기'}};const blacklist = ['\\bagar((\\.)?io)?\\b', '\\bagma((\\.)?io)?\\b', '\\baimbot\\b', '\\barras((\\.)?io)?\\b', '\\bbot(s)?\\b','\\bbubble((\\.)?am)?\\b', '\\bcheat(s)?\\b', '\\bdiep((\\.)?io)?\\b', '\\bfreebitco((\\.)?in)?\\b', '\\bgota((\\.)?io)?\\b','\\bhack(s)?\\b', '\\bkrunker((\\.)?io)?\\b', '\\blostworld((\\.)?io)?\\b', '\\bmoomoo((\\.)?io)?\\b', '\\broblox(\\.com)?\\b','\\bshell\\sshockers\\b', '\\bshellshock((\\.)?io)?\\b', '\\bshellshockers\\b', '\\bskribbl((\\.)?io)?\\b', '\\bslither((\\.)?io)?\\b','\\bsurviv((\\.)?io)?\\b', '\\btaming((\\.)?io)?\\b', '\\bvenge((\\.)?io)?\\b', '\\bvertix((\\.)?io)?\\b', '\\bzombs((\\.)?io)?\\b',// '\\p{Extended_Pictographic}'];const settingsCSS = `/*#greasyfork-plus label::before {content:'';display:block;position:absolute;left:0;right:0;top:0;bottom:0;z-index:1;}#greasyfork-plus label {position:relative;z-index:0;}*/html {color: #222;background: #f9f9f9;}#greasyfork-plus{--config-var-display: flex;}#greasyfork-plus * {font-family:Open Sans,sans-serif,Segoe UI Emoji !important;font-size:12px}#greasyfork-plus .section_header[class] {background-color:#670000;background-image:linear-gradient(#670000,#900);border:1px solid transparent;color:#fff}#greasyfork-plus .field_label[class]{margin-bottom:4px}#greasyfork-plus .field_label[class] span{font-size:95%;font-style:italic;opacity:.8;}#greasyfork-plus .field_label[class] b{color:#670000}#greasyfork-plus_logging_var[class],#greasyfork-plus_debugging_var[class] {--config-var-display: inline-flex;}#greasyfork-plus #greasyfork-plus_logging_var label.field_label[class],#greasyfork-plus #greasyfork-plus_debugging_var label.field_label[class] {margin-bottom:0;align-self: center;}#greasyfork-plus .config_var[class]{display:var(--config-var-display);position: relative;}#greasyfork-plus_customBlacklist_var[class],#greasyfork-plus_hiddenList_var[class],#greasyfork-plus_milestoneNotification_var[class],#greasyfork-plus_hideRecentUsersWithin_var[class]{flex-direction:column;margin-left:21px;}#greasyfork-plus_customBlacklist_var[class]::before,#greasyfork-plus_hiddenList_var[class]::before,#greasyfork-plus_milestoneNotification_var[class]::before,#greasyfork-plus_hideRecentUsersWithin_var[class]::before{/* content: "◉"; */content: "◎";position: absolute;left: auto;top: auto;margin-left: -16px;}#greasyfork-plus_field_customBlacklist[class],#greasyfork-plus_field_milestoneNotification[class]{flex:1;}#greasyfork-plus_field_hiddenList[class]{box-sizing:border-box;overflow:hidden;resize:none;width:100%}body > #greasyfork-plus_wrapper:only-child {box-sizing: border-box;overflow: auto;max-height: calc(100vh - 72px);padding: 12px;/* overflow: auto; */scrollbar-gutter: both-edges;background: rgba(127,127,127,0.05);border: 1px solid rgba(127,127,127,0.5);}#greasyfork-plus_wrapper > #greasyfork-plus_buttons_holder:last-child {position: fixed;bottom: 0;right: 0;margin: 0 12px 6px 0;}#greasyfork-plus .saveclose_buttons[class] {padding: 4px 14px;margin: 6px;}#greasyfork-plus .section_header_holder#greasyfork-plus_section_2[class] {position: fixed;left: 0;bottom: 0;margin: 8px;}#greasyfork-plus .section_header#greasyfork-plus_section_header_2[class] {background: #000;color: #eee;}#greasyfork-plus_header[class]{font-size: 16pt;font-weight: bold;}`;const pageCSS = `.script-list li.blacklisted{display:none;background:#321919;color:#e8e6e3}.script-list li.hidden{display:none;background:#321932;color:#e8e6e3}.script-list li.blacklisted a:not(.install-link),.script-list li.hidden a:not(.install-link){color:#ff8484}#script-info.hidden,#script-info.hidden .user-content{background:#321932;color:#e8e6e3}#script-info.hidden a:not(.install-link):not(.install-help-link){color:#ff8484}#script-info.hidden code{background-color:transparent}html {--block-btn-color:#111;--block-btn-bgcolor:#eee;}#script-info.hidden, #script-info.hidden .user-content {--block-btn-color:#eee;--block-btn-bgcolor:#111;}[style-54998]{float:right;font-size: 70%;text-decoration:none;}[style-16377]{cursor:pointer;font-size:70%;white-space:nowrap;border: 1px solid #888;background: var(--block-btn-bgcolor, #eee);color: var(--block-btn-color);border-radius: 4px;padding: 0px 6px;margin: 0 8px;}[style-77329] {cursor: pointer;margin-left: 1ex;white-space: nowrap;float: right;border: 1px solid #888;background: var(--block-btn-bgcolor, #eee);color: var(--block-btn-color);border-radius: 4px;padding: 0px 6px;}a#hyperlink-35389,a#hyperlink-40361,a#hyperlink-35389:visited,a#hyperlink-40361:visited,a#hyperlink-35389:hover,a#hyperlink-40361:hover,a#hyperlink-35389:focus,a#hyperlink-40361:focus,a#hyperlink-35389:active,a#hyperlink-40361:active {border: none !important;outline: none !important;box-shadow: none !important;appearance: none !important;background: none !important;color:inherit !important;}a#hyperlink-35389{opacity: var(--hyperlink-blacklisted-option-opacity);}a#hyperlink-40361{opacity: var(--hyperlink-hidden-option-opacity);}html {--hyperlink-blacklisted-option-opacity: 0.5;--hyperlink-hidden-option-opacity: 0.5;}.list-option.list-current[class] > a[href] {text-decoration:none;}html {--blacklisted-display: none;--hidden-display: none;}[blacklisted-shown] {--blacklisted-display: list-item;--hyperlink-blacklisted-option-opacity: 1;}[hidden-shown] {--hidden-display: list-item;--hyperlink-hidden-option-opacity: 1;}.script-list li.blacklisted{display: var(--blacklisted-display);}.script-list li.hidden{display: var(--hidden-display);}.install-link.install-status-checking,.install-link.install-status-checking:visited,.install-link.install-status-checking:active,.install-link.install-status-checking:hover,.install-help-link.install-status-checking {background-color: #405458;}div.previewable{display: flex;flex-direction: column;}.script-version-ainfo-span {align-self:end;font-size: 90%;padding: 4px 8px;margin: 0;}[style*="display:"] + .script-version-ainfo-span{display: none;}/* Greasy Fork Enhance - Flat Layout  */[greasyfork-enhance-k37*="|flat-layout|"] ol.script-list > li > article > h2 {width: 0;flex-grow: 1;flex-basis: 60%;}[greasyfork-enhance-k37*="|flat-layout|"] ol.script-list > li > article > div.script-meta-block {width: auto;flex-basis: 40%;flex-shrink: 0;flex-grow: 0;}[greasyfork-enhance-k37*="|flat-layout|"] .script-list li:not(.ad-entry) {padding: 1em;margin: 0;}[greasyfork-enhance-k37*="|flat-layout|"] .script-list li:not(.ad-entry) article {padding: 0;margin: 0;}[greasyfork-enhance-k37*="|flat-layout|"]  #script-info div.script-meta-block + #additional-info {max-width: calc( 100% - 340px );min-height: 300px;box-sizing: border-box;}[greasyfork-enhance-k37*="|basic|"] ul.outline {margin-bottom: -99vh;}.discussion-list .hidden {display: none;}/* Greasy Fork Empty Ad Block */.ethical-ads-text[class]:empty {min-height: unset;}/* additional css */.discussion-item-by-recent-user{opacity: 0.2;}.discussion-list-item {position: relative;}.discussion-list-item .discussion-meta .discussion-meta-item{display: flex;flex-direction: row;flex-wrap: nowrap;align-items: center;gap: 4px;}.discussion-list-item .discussion-meta .discussion-meta-item:last-of-type .discussion-meta-item{justify-content: end;}.discussion-list-item .discussion-title{display: flex;flex-direction: row;flex-wrap: nowrap;}a.discussion-list-item-report-comment[class] {all: reset;position: relative;margin: 0 0 0 0;background: inherit;color: inherit;border: 0;opacity: 0.8;text-decoration: none;font-size: 100%;}a.discussion-list-item-report-comment[class]:hover {opacity: 1.0;text-decoration: underline;}.discussion-meta-item-script-name + .discussion-meta-item {display: inline-flex;flex-direction: row;gap: 4px;align-items: center;justify-content: flex-start;justify-items: center;}li[data-script-id] .install-link[class] {border-radius: 0;opacity: 0.8;cursor: pointer;display: inline-flex;white-space: nowrap;position: relative;z-index: 99;}li[data-script-id] .install-link[class]:hover {opacity: 1.0;cursor: pointer;display: inline-flex;white-space: nowrap;}.discussion-list-item span.discussion-snippet[class] {text-overflow: ellipsis;overflow: hidden;}div#script-list-cd[id]{/* all: revert; */padding: initial;width: initial;margin: initial;}`const window = {};/** @param {typeof WinComm.createInstance} createInstance */function contentScriptText(shObject, createInstance) {// avoid setupEthicalAdsFallback loopingif (typeof window.ethicalads === "undefined") {const p = Promise.resolve([]);window.ethicalads = { wait: p };}/**return new Promise((resolve, reject) => {const external = unsafeWindow.external;console.log(334, external)const scriptHandler = GM.info.scriptHandler;if (external && external.Violentmonkey && (scriptHandler || 'Violentmonkey') === 'Violentmonkey' ) {external.Violentmonkey.isInstalled(name, namespace).then((data) => resolve(data));return;}if (external && external.Tampermonkey && (scriptHandler || 'Tampermonkey') === 'Tampermonkey') {external.Tampermonkey.isInstalled(name, namespace, (data) => {(data.installed) ? resolve(data.version) : resolve();});return;}resolve();});*/if (document.querySelector('#greasyfork-enhance-basic')) {const setScriptOnDisabled = async (style) => {try {const pd = Object.getOwnPropertyDescriptor(style.constructor.prototype, 'disabled');const { get, set } = pd;Object.defineProperty(style, 'disabled', {get() {return get.call(this);},set(nv) {let r = set.call(this, nv);Promise.resolve().then(chHead);return r;}})} catch (e) {}};document.addEventListener('style-s48', function (evt) {const target = (evt || 0).target || 0;if (!target) return;setScriptOnDisabled(target)}, true);const isScriptEnabled = (style) => {if (style instanceof HTMLStyleElement) {if (!style.hasAttribute('s48')) {style.setAttribute('s48', '');style.dispatchEvent(new CustomEvent('style-s48'));// setScriptOnDisabled(style);}return style.disabled !== true;}return false;}const chHead = () => {let p = [];if (isScriptEnabled(document.getElementById('greasyfork-enhance-basic')))p.push('basic');if (isScriptEnabled(document.getElementById('greasyfork-enhance-flat-layout')))p.push('flat-layout');if (isScriptEnabled(document.getElementById('greasyfork-enhance-animation')))p.push('animation');if (p.length >= 1)document.documentElement.setAttribute('greasyfork-enhance-k37', `|${p.join('|')}|`);elsedocument.documentElement.removeAttribute('greasyfork-enhance-k37');}const moHead = new MutationObserver(chHead);moHead.observe(document.head, { subtree: false, childList: true });chHead();/*const outline = document.querySelector('aside.panel > ul.outline');if(outline) {const div = document.createElement('div');//outline.replaceWith(div);//div.appendChild(outline)}*///         Promise.resolve().then(()=>{//           let outline = document.querySelector('[greasyfork-enhance-k37*="|basic|"] header + aside.panel ul.outline');//           if(outline){//             let aside = outline.closest('aside.panel');//           let header = aside.parentNode.querySelector('header');//             let p = header.getBoundingClientRect().height;//             document.body.parentNode.insertBefore(aside, document.body);//             // outline.style.top='0'//             p+=(parseFloat(getComputedStyle(outline).marginTop.replace('px',''))||0)//             outline.style.marginTop= p.toFixed(2)+'px';//           }//         })}const { scriptHandler, scriptName, scriptVersion, scriptNamespace, communicationId } = shObject;const wincomm = createInstance(communicationId);const external = window.external;if (external[scriptHandler]) 1;else if (external && external.Violentmonkey && (scriptHandler || 'Violentmonkey') === 'Violentmonkey') scriptHandler = 'Violentmonkey';else if (external && external.Tampermonkey && (scriptHandler || 'Tampermonkey') === 'Tampermonkey') scriptHandler = 'Tampermonkey';const manager = external[scriptHandler];if (!manager) {wincomm.send('userScriptManagerNotDetected', {code: 1});return;}const promiseWrap = (x) => {// bug in FireFox + Violentmonkeyif (typeof (x || 0) === 'object' && typeof x.then === 'function') return x; else return Promise.resolve(x);};const pnIsInstalled2 = (type, scriptName, scriptNamespace) => new Promise((resolve, reject) => {const r###ltPr = promiseWrap(manager.isInstalled(scriptName, scriptNamespace));r###ltPr.then((r###lt) => resolve({type,r###lt: typeof r###lt === 'string' ? { version: r###lt } : r###lt})).catch(reject);}).catch(console.warn);const pnIsInstalled3 = (type, scriptName, scriptNamespace) => new Promise((resolve, reject) => {try {manager.isInstalled(scriptName, scriptNamespace, (r###lt) => {resolve({type,r###lt: typeof r###lt === 'string' ? { version: r###lt } : r###lt});});} catch (e) {reject(e);}}).catch(console.warn);const enableScriptInstallChecker = (r) => {const { type, r###lt } = r;let version = r###lt.version;// console.log(type, r###lt, version)if (version !== scriptVersion) return;const pnIsInstalled = type < 25 ? pnIsInstalled2 : pnIsInstalled3;wincomm.hook('_$GreasyFork$Msg$OnScriptInstallCheck', {'installedVersion.req': (d, evt) => {pnIsInstalled(type, d.data.name, d.data.namespace).then((r) => {if (r && 'r###lt' in r) {wincomm.response(evt, 'installedVersion.res', {version: r.r###lt ? (r.r###lt.version || '') : ''});}})}});wincomm.send('ready', { type });// console.log('enableScriptInstallChecker', r)}const kl = manager.isInstalled.length;if (!(kl === 2 || kl === 3)) return;const puds = kl === 2 ? [pnIsInstalled2(21, scriptName, scriptNamespace), // scriptName is GM.info.script.name not GM.info.script.name_i18npnIsInstalled2(20, scriptName, '')] : [pnIsInstalled3(31, scriptName, scriptNamespace),pnIsInstalled3(30, scriptName, '')];Promise.all(puds).then((rs) => {const [r1, r0] = rs;if (r0 && r0.r###lt && r0.r###lt.version) enableScriptInstallChecker(r0); // '3.1.4'else if (r1 && r1.r###lt && r1.r###lt.version) enableScriptInstallChecker(r1);});// console.log(327, shObject, scriptHandler);}return { fields, logo, locales, blacklist, settingsCSS, pageCSS, contentScriptText }})();const inIframeFn = isInIframe ? async () => {if (window.name) {const uo = new URL(location.href);const id38 = uo.searchParams.get('id38');if (id38 && `iframe-${id38}` === window.name) {const p38 = uo.searchParams.get('p38');const h38 = uo.searchParams.get('h38');if (`${p38}:` === uo.protocol && `${h38}` === uo.hostname) {window.addEventListener('message', (evt)=>{if(evt && evt.data){const {id38: id38_, msg, args, fetchId} = evt.data;if(id38_ === id38){if(msg === 'fetch' && fetchId){const [url, options] = args;if(options && options.headers){options.headers = new Headers(options.headers);}fetch(url, options).then(async (response) => {let json = null;if (response.ok === true) {try {json = await response.json();} catch (e) { }}const res = {status: response.status,url: response.url,ok: response.ok,json};evt.source.postMessage({id38,fetchId,msg: 'fetchResponse',args: [res]}, '*')})}}}});top.postMessage({id38: id38,msg: 'ready'}, '*');}}}} : () => { };inIframeFn() || (async () => {let rafPromise = null;const getRafPromise = () => rafPromise || (rafPromise = new Promise(resolve => {requestAnimationFrame(hRes => {rafPromise = null;resolve(hRes);});}));const isVaildURL = (url) => {if (!url || typeof url !== 'string' || url.length < 23) return;let obj = null;try {obj = new URL(url);} catch (e) {return false;}if (obj && obj.host === obj.hostname && !obj.port && (obj.protocol || '').startsWith('http') && obj.pathname) {return true;}return false;};const installLinkPointerDownHandler = function (e) {if (!e || !e.isTrusted) return;const button = e.target || this;if (button.hasAttribute('a###d')) return;const href = button.href;if (!href || !isVaildURL(href)) return;if (/\.js[^-.\w\d\s:\/\\]*$/.test(href)) {0 && fetch(href, {method: "GET",cache: 'reload',redirect: "follow"}).then(() => {console.debug('code url reloaded', href);}).catch((e) => {console.debug(e);});const m = /^(https\:\/\/(cn-greasyfork|greasyfork|sleazyfork)\.org\/[_-\w\/]*scripts\/(\d+)[-\w%]*)(\/|$)/.exec(location.href)if (m && m[1]) {const href = `${m[1]}/code`0 && fetch(href, {method: "GET",cache: 'reload',redirect: "follow"}).then(() => {console.debug('code url reloaded', href);}).catch((e) => {console.debug(e);});}if (m && m[3] && href.includes('.user.js')) {const href = `https://${location.hostname}/scripts/${m[3]}-fetching/code/${crypto.randomUUID()}.user.js?version_=${Date.now()}`0 && fetch(href, {method: "GET",cache: 'reload',redirect: "follow"}).then(() => {console.debug('code url reloaded', href);}).catch((e) => {console.debug(e);});}}button.setAttribute('a###d', '');};const setupInstallLink = (button) => {if (!button || button.className !== 'install-link' || button.nodeName !== "A" || !button.href) return button;button.addEventListener('pointerdown', installLinkPointerDownHandler);return button;};function fixValue(key, def, test) {return GM.getValue(key, def).then((v) => test(v) || GM.deleteValue(key))}const isNaNx = Number.isNaN;function numberArr(arrVal) {if (!arrVal || typeof arrVal.length !== 'number') return [];return arrVal.filter(e => typeof e === 'number' && !isNaNx(e))}const isScriptFirstUse = await GM.getValue('firstUse', true);await Promise.all([fixValue('hiddenList', [], v => v && typeof v === 'object' && typeof v.length === 'number' && (v.length === 0 || typeof v[0] === 'number')),fixValue('lastMilestone', 0, v => v && typeof v === 'number' && v >= 0)])function createRE(t, ...opt) {try {return new RegExp(t, ...opt);} catch (e) { }return null;}const ruleFn = function (text) {/** @type {String[]} */const { rules, regExpArr } = this;let text0 = text.replace(/\uE084/g, '\uE084x');let j = 0;for (const rule of rules) {let r = false;if (!rule.includes('\uE084')) {r = (text.toLocaleLowerCase("en-US").includes(rule.toLocaleLowerCase("en-US")));} else {const s = rule.split(/\uE084(\d+)r/);r = s.every((t, i) => {if (t === undefined || t.length === 0) return true;if (i % 2) {return regExpArr[+t].test(text0);} else {return text0.includes(t.trim());}});}if (r) return j;j++;}}/** @param {String} txtRule */const preprocessRule = (txtRule) => {const regExpArr = [];txtRule = txtRule.replace(/\uE084/g, '\uE084x');let maxCount = 800; // avoid deadloopwhile (maxCount--) {const idx1 = txtRule.search(/\bre\//);if (idx1 < 0) break;const str = txtRule.substring(idx1 + 3);let idx2 = -1;const searcher = /(.?)\//g;let m;while (m = searcher.exec(str)) {if (m[1] === '\\') continue;idx2 = searcher.lastIndex + idx1 + 3;break;}if (idx2 < 0) break;const optionStr = txtRule.substring(idx2);const optionM = /^[a-z]+/.exec(optionStr);const option = optionM ? optionM[0] : '';const regexContent = txtRule.substring(idx1 + 2 + 1, idx2 - 1);txtRule = `${txtRule.substring(0, idx1)}${('\uE084' + regExpArr.length + 'r')}${txtRule.substring(idx2 + option.length)}`;regExpArr.push(new RegExp(regexContent, option));}const rules = txtRule.split(',').map(e => e.trim());return ruleFn.bind({ rules, regExpArr });}const useHashedScriptName = true;const fixLibraryScriptCodeLink = true;const addAdditionInfoLengthHint = true;const id = 'greasyfork-plus';const title = `${GM.info.script.name} v${GM.info.script.version} Settings`;const fields = mWindow.fields;const logo = mWindow.logo;const nonLatins = /[^\p{Script=Latin}\p{Script=Common}\p{Script=Inherited}]/gu;const blacklist = createRE((mWindow.blacklist || []).filter(e => !!e).join('|'), 'giu');const hiddenList = numberArr(await GM.getValue('hiddenList', []));const lang = document.documentElement.lang;const locales = mWindow.locales;const _isBlackList = (text) => {if (!text || typeof text !== 'string') return false;if (text.includes('hack') && (text.includes('EXPERIMENT_FLAGS') || text.includes('yt.'))) return false;return blacklist.test(text);}const isBlackList = (name, description) => {// To be reviewedif (!blacklist) return false;return _isBlackList(name) || _isBlackList(description);}function hiddenListStrToArr(str) {if (!str || typeof str !== 'string') str = '';return [...new Set(str ? numberArr(str.split(',').map(e => parseInt(e))) : [])];}const gmc = new GM_config({id,title,fields,css: mWindow.settingsCSS,events: {init: () => {gmc.initializedResolve && gmc.initializedResolve();gmc.initializedResolve = null;},/** @param {Document} document */open: async (document) => {const textarea = document.querySelector(`#${id}_field_hiddenList`);const hiddenSet = new Set(numberArr(await GM.getValue('hiddenList', [])));if (hiddenSet.size !== 0) {const unsavedHiddenList = hiddenListStrToArr(gmc.get('hiddenList'));const unsavedHiddenSet = new Set(unsavedHiddenList);const hasDifferentItems = [...hiddenSet].some(item => !unsavedHiddenSet.has(item)) || [...unsavedHiddenSet].some(item => !hiddenSet.has(item));if (hasDifferentItems) {gmc.fields.hiddenList.value = [...hiddenSet].sort((a, b) => a - b).join(', ');gmc.close();gmc.open();}}const resize = (target) => {target.style.height = '';target.style.height = `${target.scrollHeight}px`;};if (textarea) {resize(textarea);textarea.addEventListener('input', (event) => resize(event.target));}document.body.addEventListener('mousedown', (event) => {if (event.detail > 1 && !event.ctrlKey && !event.altKey && !event.metaKey && !event.shiftKey && !event.defaultPrevented) {event.preventDefault();event.stopPropagation();event.stopImmediatePropagation();}}, true);},save: async (forgotten) => {if (gmc.isOpen) {await GM.setValue('hiddenList', hiddenListStrToArr(forgotten.hiddenList));UU.alert('settings saved');gmc.close();setTimeout(() => window.location.reload(false), 500);}}}});gmc.initialized = new Promise(r => (gmc.initializedResolve = r));await gmc.initialized.then();const customBlacklistRF = preprocessRule(gmc.get('customBlacklist') || '');const valHideRecentUsersWithin_ = Math.floor(+gmc.get('hideRecentUsersWithin'));const valHideRecentUsersWithin = valHideRecentUsersWithin_ > 168 ? 168 : valHideRecentUsersWithin_ > 0 ? valHideRecentUsersWithin_ : 0;/**** Inserts element into the sorted array arr while maintaining order based on a comparator.* Uses binary search to find the insertion point and then splices the element into the array.** @param {Array} arr - The sorted array. (ascending order)* @param {number} value - The number to compare.* @param {Function} keyFn - Obtain the comparable value of the element.*/function binarySearchLeft(arr, value, keyFn) {let left = 0;let right = arr.length;while (left < right) {const mid = Math.floor((left + right) / 2);if (keyFn(arr[mid]) < value) {left = mid + 1;} else {right = mid;}}return left;}/*** Finds the smallest index i such that arr[i][1] >= targetTime.* Used to locate the first user in userCreations whose creation time is recent enough.** @param {Array} arr - The sorted array. (ascending order)* @param {number} targetTime - targetTime*/function findFirstIndex(arr, targetTime) {return binarySearchLeft(arr, targetTime, e => e[1]);}/*** Finds the insertion point for element in arr to maintain sorted order.* Used to find the range of uncertain requests in networkRequestsRCTake.** @param {Array} arr - The sorted array. (ascending order)* @param {*} element - The element to be inserted.* @param {Function} keyFn - Obtain the comparable value of the element.*/function insertSorted(arr, element, keyFn) {const idx = binarySearchLeft(arr, keyFn(element), keyFn);arr.splice(idx, 0, element);return arr;}// Assume targetHiddenRecentDateTime is set as Date.now() - valHideRecentUsersWithin * 3600000let targetHiddenRecentDateTime = 0;let userCreations = [];// [userId, creationTime] sorted by creationTimelet networkRequestsRC = [];// [userId, processFn, r###lt] sorted by userIdlet recentUserMP = Promise.resolve(0);const fetchUserCreations = () => {if (sessionStorage.__TMP_userCreations682__) {try {return JSON.parse(sessionStorage.__TMP_userCreations682__);// console.log(388, userCreations);} catch (e) {console.warn(e);}}return [];}userCreations = fetchUserCreations();// Clean up userCreations: merge with sessionStorage and trimconst cleanupUserCreations = () => {// Merge with sessionStorage data// in case the record in sessionStorage is modified by other instances as well.const stored = fetchUserCreations();const currentSet = new Set(userCreations.map(e => e.join(',')));const missing = stored.filter(e => !currentSet.has(e.join(',')));for (const element of missing) {insertSorted(userCreations, element, e => e[1]);}// Remove redundant old entries// since targetHiddenRecentDateTime is expected monotonic increasing, small values are useless in checking.let deleteCount = 0;for (let i = 0; i < userCreations.length - 1; i++) {if (userCreations[i][1] < targetHiddenRecentDateTime && userCreations[i + 1][1] < targetHiddenRecentDateTime) {deleteCount++;} else {break;}}if (deleteCount > 0) {deleteCount === 1 ? userCreations.shift() : userCreations.splice(0, deleteCount);}// Trim to max 16 elements, keeping boundary-relevant entrieswhile (userCreations.length > 16) {const leftIdx = 1;const rightIdx = userCreations.length - 2;userCreations = userCreations.filter((e, idx) => ((idx <= leftIdx) || (idx >= rightIdx) || ((idx % 2) === 1)));}sessionStorage.__TMP_userCreations682__ = JSON.stringify(userCreations);};// Test if a user is recent using cached dataconst testByUserCreations = (userId, targetTime)=>{const idxJ = findFirstIndex(userCreations, targetTime);let newFrom = Infinity, oldFrom = 0;if (idxJ < userCreations.length) {newFrom = userCreations[idxJ][0];if (userId >= newFrom) return true; // User is recent}if (idxJ > 0) {oldFrom = userCreations[idxJ - 1][0];if (userId <= oldFrom) return false; // User is not recent}return { newFrom, oldFrom }; // Uncertain, need network request}// Select the next network request from the uncertain range/** @returns {Promise | null} */function networkRequestsRCTake() {if (networkRequestsRC.length === 0) return null;let oldFrom = 0;let newFrom = Infinity;if (userCreations.length > 0) {const idx = findFirstIndex(userCreations, targetHiddenRecentDateTime);if (idx < userCreations.length) newFrom = userCreations[idx][0];if (idx > 0) oldFrom = userCreations[idx - 1][0];}// Find range of requests in uncertain zone (oldFrom < userId < newFrom)const left = binarySearchLeft(networkRequestsRC, oldFrom + 1, e => e[0]);// Prioritize certain not recent requests (at the beginning)if (left > 0) {return networkRequestsRC.shift(); // Take the first request (userId <= oldFrom)}const right = binarySearchLeft(networkRequestsRC, newFrom, e => e[0]);// Prioritize certain recent requests (at the end)if (right < networkRequestsRC.length) {return networkRequestsRC.pop(); // Take the last request (userId >= newFrom)}// No certain requests left, process an uncertain one// The entire remaining array is uncertain (left == 0, right == length)const midIdx = Math.floor(networkRequestsRC.length / 2);return networkRequestsRC.splice(midIdx, 1)[0];}// Main function to check if a user is recentfunction determineRecentUserAsync(userId) {return new Promise(resolve => {// Check cache firstconst initialCheck = testByUserCreations(userId, targetHiddenRecentDateTime);if (typeof initialCheck === 'boolean') return resolve(initialCheck);// Schedule network requestconst processAsyncFn = async () => {const check = testByUserCreations(userId, targetHiddenRecentDateTime);// console.log('processAsyncFn', userId, targetHiddenRecentDateTime, check)if (typeof check === 'boolean') return resolve(check);// console.log('network request', userId)const userData = await getUserData(userId, false); // Assume this existsif (userData.id !== userId) return resolve(false);const creationTime = +new Date(userData.created_at);insertSorted(userCreations, [userId, creationTime], e => e[1]);resolve(creationTime >= targetHiddenRecentDateTime);cleanupUserCreations();};const request = [userId, processAsyncFn, null];insertSorted(networkRequestsRC, request, e => e[0]);// Process requests sequentiallyrecentUserMP = recentUserMP.then(async () => {const entity = networkRequestsRCTake();if (entity) await entity[1]();});});}if (typeof GM.registerMenuCommand === 'function') {GM.registerMenuCommand('Configure', () => gmc.open());GM.registerMenuCommand('Reset Everything', () => {Promise.all([GM.deleteValue('hiddenList'),GM.deleteValue('lastMilestone'),GM.deleteValue('firstUse')]).then(() => {setTimeout(() => window.location.reload(false), 50);})});}UU.init({ id, logging: gmc.get('logging') });UU.log(nonLatins);UU.log(blacklist);UU.log(hiddenList);const _VM = (typeof VM !== 'undefined' ? VM : null) || {shortcut: {register: () => { }}};const isGPUAccelerationAvailable = (() => {// https://gist.github.com/cvan/042b2448fcecefafbb6a91469484cdf8try {const canvas = document.createElement('canvas');return !!(canvas.getContext('webgl') || canvas.getContext('experimental-webgl'));} catch (e) {return false;}})();const runLater = isGPUAccelerationAvailable ? (f) => {requestAnimationFrame(f);} : (f) => {setTimeout(f, 100);};const mutationRunner = (gn, elm, options) => {let rid = 0;(new MutationObserver((entries) => {if (entries && entries.length >= 1) {const tid = rid = (rid & 1073741823) + 1;runLater(() => {if (tid === rid) gn();});}})).observe(elm, options);gn();}function fixLibraryCodeURL(code_url) {if (/\/scripts\/(\d+)(\-[^\/]+)\/code\//.test(code_url)) {code_url = code_url.replace(/\/scripts\/(\d+)(\-[^\/]+)\/code\//, '/scripts/$1/code/');let qm = code_url.indexOf('?');let s1 = code_url.substring(0, qm);let s2 = code_url.substring(qm + 1);if (qm > 0) {code_url = `${decodeURI(s1)}?${s2}`;}}return code_url;}function setClickToSelect(elm) {elm.addEventListener('click', function () {if (`${window.getSelection()}` === "") {if (typeof this.select === 'function') {this.select();} else {const range = document.createRange();  // Create a range objectrange.selectNode(this);        // Select the text within the elementconst selection = window.getSelection(); // Get the selection objectselection.removeAllRanges();  // First clear any existing selectionsselection.addRange(range);    // Add the new range to the selection}}});elm.addEventListener('drag', function (evt) {evt.preventDefault();});elm.addEventListener('drop', function (evt) {evt.preventDefault();});elm.addEventListener('dragstart', function (evt) {evt.preventDefault();});}const copyText = typeof (((window.navigator || 0).clipboard || 0).writeText) === 'function' ? (text) => {navigator.clipboard.writeText(text).then(function () {//}).catch(function (err) {alert("Unable to Copy");});} : (text) => {const textToCopy = document.createElement('strong');textToCopy.style.position = 'fixed';textToCopy.style.opacity = '0';textToCopy.style.top = '-900vh';textToCopy.textContent = text;document.body.appendChild(textToCopy);const range = document.createRange();  // Create a range objectrange.selectNode(textToCopy);        // Select the text within the elementconst selection = window.getSelection(); // Get the selection objectselection.removeAllRanges();  // First clear any existing selectionsselection.addRange(range);    // Add the new range to the selectiontry {document.execCommand('copy');  // Try to copy the selected text} catch (err) {alert("Unable to Copy");}selection.removeAllRanges();  // Remove the selection range after copyingtextToCopy.remove();};let avoidDuplication = 0;const avoidDuplicationF = () => {const p = avoidDuplication;avoidDuplication = Date.now();if (avoidDuplication - p < 30) return false;return true;}// https://violentmonkey.github.io/vm-shortcut/const shortcuts = [['ctrlcmd-alt-keys', () => avoidDuplicationF() && gmc.open()],['ctrlcmd-alt-keyb', () => avoidDuplicationF() && toggleListDisplayingItem('blacklisted')],['ctrlcmd-alt-keyh', () => avoidDuplicationF() && toggleListDisplayingItem('hidden')]]for (const [scKey, scFn] of shortcuts) {_VM.shortcut.register(scKey, scFn);}const addSettingsToMenu = () => {const nav = document.querySelector('#site-nav > nav')if (!nav) return;const scriptName = GM.info.script.name;const scriptVersion = GM.info.script.version;const menu = document.createElement('li');menu.classList.add(id);menu.setAttribute('alt', `${scriptName} ${scriptVersion}`);menu.setAttribute('title', `${scriptName} ${scriptVersion}`);const link = document.createElement('a');link.setAttribute('href', '#');link.textContent = GM.info.script.name;menu.appendChild(link);nav.insertBefore(menu, document.querySelector('#site-nav > nav > li:first-of-type'));menu.addEventListener('click', (e) => {e.preventDefault();e.stopPropagation();e.stopImmediatePropagation();gmc.open();});};const toggleListDisplayingItem = (t) => {const m = document.documentElement;const p = t + '-shown';let currentIsShown = m.hasAttribute(p)if (!currentIsShown) {m.setAttribute(p, '')} else {m.removeAttribute(p)}}const createListOptionGroup = () => {const html = `<div class="list-option-group" id="${id}-options">${GM.info.script.name} Lists:<ul><li class="list-option blacklisted"><a href="#" id="hyperlink-35389"></a></li><li class="list-option hidden"><a href="#" id="hyperlink-40361"></a></li></ul></div>`;const firstOptionGroup = document.querySelector('.list-option-groups > div');firstOptionGroup && firstOptionGroup.insertAdjacentHTML('beforebegin', html);const blacklistedOption = document.querySelector(`#${id}-options li.blacklisted`);blacklistedOption && blacklistedOption.addEventListener('click', (evt) => {evt.preventDefault();toggleListDisplayingItem('blacklisted');}, false);const hiddenOption = document.querySelector(`#${id}-options li.hidden`);hiddenOption && hiddenOption.addEventListener('click', (evt) => {evt.preventDefault();toggleListDisplayingItem('hidden');}, false);}const addOptions = (scriptList) => {if (!scriptList) return;createListOptionGroup();mutationRunner(() => {let aBlackList = document.querySelector('#hyperlink-35389');let aHidden = document.querySelector('#hyperlink-40361');if (!aBlackList || !aHidden) return;aBlackList.textContent = `Blacklisted scripts (${document.querySelectorAll('.script-list li.blacklisted').length})`;aHidden.textContent = `Hidden scripts (${document.querySelectorAll('.script-list li.hidden').length})`;}, scriptList, { childList: true, subtree: true });};const PromiseExternal = ((resolve_, reject_) => {const h = (resolve, reject) => { resolve_ = resolve; reject_ = reject };return class PromiseExternal extends Promise {constructor(cb = h) {super(cb);if (cb === h) {/** @type {(value: any) => void} */this.resolve = resolve_;/** @type {(reason?: any) => void} */this.reject = reject_;}}};})();const corsFetchMap = new Map();const corsFetch = async (url, options) => {if (top !== window) return;const uo = new URL(url);const protocol = uo.protocol.replace(/[^\w]+/g, '');const hostname = uo.hostname;const origin0 = `${protocol}://${hostname}`;let promiseF = null;let prFn = corsFetchMap.get(origin0);for (let i = 0; i < 2; i++) {if (!prFn) {prFn = new Promise((resolve) => {let iframe = document.createElement('iframe');const rid = `${Math.floor(Math.random() * 314159265359 + 314159265359).toString(36)}`;iframe.id = `iframe-${rid}`;iframe.name = `iframe-${rid}`;window.addEventListener('message', (evt) => {if (evt && evt.origin === origin0) {const data = evt.data;if (data && data.id38) {const { id38, msg, fetchId: fetchId_, args } = data;if (msg === 'ready') {const iframeWindow = evt.source;resolve((...args) => {if (!iframe.isConnected) return -1;const fetchId = `${Math.floor(Math.random() * 314159265359 + 314159265359).toString(36)}`;const promise = new PromiseExternal();corsFetchMap.set(`${id38}-${fetchId}`, promise);iframeWindow.postMessage({id38,msg: 'fetch',fetchId,args}, '*');return promise;});} else if (msg === 'fetchResponse') {const promise = corsFetchMap.get(`${id38}-${fetchId_}`);if (promise) {corsFetchMap.delete(`${id38}-${fetchId_}`);promise.resolve(args[0]);}}}}});iframe.src = `${protocol}://${hostname}/robots.txt?id38=${rid}&p38=${protocol}&h38=${hostname}`;Object.assign(iframe.style, {'position': 'fixed','left': '-300px','top': '-300px','width': '30px','height': '30px','pointerEvents': 'none','zIndex': '-1','contain': 'strict'});(document.body || document.documentElement).appendChild(iframe);});corsFetchMap.set(origin0, prFn);}const fetchFn = await prFn.then();const promise = fetchFn(url, options);if (promise === -1) {corsFetchMap.delete(origin0);prFn = null;continue;}if (promise && typeof promise.then === 'function') {promiseF = promise;break;}}if (!promiseF) return null;const promiseR###lt = await promiseF.then();return promiseR###lt;};const standardFetch = async (url, options) => {if (options && options.headers) {options.headers = new Headers(options.headers);}const response = await fetch(url, options);let json = null;if (response.ok === true) {try {json = await response.json();} catch (e) { }}const res = {status: response.status,url: response.url,ok: response.ok,json};return res;}/*** Get script data from Greasy Fork API** @param {number} id Script ID* @returns {Promise} Script data*/let networkMP1 = Promise.resolve();let networkMP2 = Promise.resolve();let previousIsCache = false;// let ss = [];// var sum = function(nums) {//   var total = 0;//   for (var i = 0, len = nums.length; i < len; i++) total += nums[i];//   return total;// };let reqStoresA = new Map();let reqStoresB = new Map();const getOldestEntry = (noCache)=>{const reqStores = noCache ? reqStoresB : reqStoresA;const oldestEntry = reqStores.entries().next();if(!oldestEntry || !oldestEntry.value) return [];const id = oldestEntry.value[0]const req = oldestEntry.value[1]reqStores.delete(id);return [id, req];}let mutexC = Promise.resolve();const getScriptDataAN = (noCache)=>{mutexC = mutexC.then(async () => {const [id, req] = getOldestEntry(noCache);if (!(id > 0)) return;const DO_CORS = /^(cn-greasyfork|greasyfork|sleazyfork)\.org$/.test(window.location.hostname) ? `api.${window.location.hostname}` : '';const url = `https://${DO_CORS || window.location.hostname}/scripts/${id}.json`;const fetchUrl = sessionStorage.getItem(`redirect41-${url}`) || url;const onPageElement = document.querySelector(`[data-script-namespace][data-script-id="${id || 'null'}"][data-script-name][data-script-version][href]`)if (onPageElement && /^https\:\/\/update\.\w+\.org\/scripts\/\d+\/[^.?\/]+\.user\.js$/.test(onPageElement.getAttribute('href') || '')) {const r###lt = {"id": +onPageElement.getAttribute('data-script-id'),// "created_at": "2023-08-24T21:16:50.000Z",// "daily_installs": 21,// "total_installs": 3310,// "code_updated_at": "2023-12-20T07:46:54.000Z",// "support_url": null,// "fan_score": "74.1","namespace": `${onPageElement.getAttribute('data-script-namespace')}`,// "contribution_url": null,// "contribution_amount": null,// "good_ratings": 11,// "ok_ratings": 0,// "bad_ratings": 0,// "users": [//     {//         "id": 371179,//         "name": "𝖢𝖸 𝖥𝗎𝗇𝗀",//         "url": "https://greasyfork.org/users/371179-%F0%9D%96%A2%F0%9D%96%B8-%F0%9D%96%A5%F0%9D%97%8E%F0%9D%97%87%F0%9D%97%80"//     }// ],"name": `${onPageElement.getAttribute('data-script-name')}`,// "description": "Adds various features and improves the Greasy Fork experience",// "url": "https://greasyfork.org/scripts/473830-greasy-fork",// "code_url": "https://update.greasyfork.org/scripts/473830/Greasy%20Fork%2B%2B.user.js","code_url": `${onPageElement.getAttribute('href')}`,// "license": "MIT License","version": `${onPageElement.getAttribute('data-script-version')}`,// "locale": "en",// "deleted": false};req.resolve(r###lt);return;}await (networkMP1 = networkMP1.then(() => new Promise(unlock => {const maxAgeInSeconds = 900;const rd = previousIsCache ? 1 : Math.floor(Math.random() * 80 + 80);let fetchStart = 0;const fetchOptions = noCache ? {method: 'GET',cache: 'reload',credentials: 'omit',headers: {'Cache-Control': `max-age=${maxAgeInSeconds}`,}} : {method: 'GET',cache: 'force-cache',credentials: 'omit',headers: {'Cache-Control': `max-age=${maxAgeInSeconds}`,}};new Promise(r => setTimeout(r, rd)).then(() => {fetchStart = Date.now();}).then(() => DO_CORS ? corsFetch(fetchUrl, fetchOptions): standardFetch(fetchUrl, fetchOptions)).then((response) => {if (fetchUrl !== response.url) {sessionStorage.setItem(`redirect41-${url}`, response.url);sessionStorage.setItem(`redirect41-${fetchUrl}`, response.url);}let fetchStop = Date.now();// const dd = fetchStop - fetchStart;// dd (cache) = {min: 1, max: 8, avg: 3.7}// dd (normal) = {min: 136, max: 316, avg: 162.62}// ss.push(dd)// ss.maxValue = Math.max(...ss);// ss.minValue = Math.min(...ss);// ss.avgValue = sum(ss)/ss.length;// console.log(dd)// console.log(ss)previousIsCache = (fetchStop - fetchStart) < (3.7 + 162.62) / 2;UU.log(`${response.status}: ${response.url}`)// UU.log(response)if (response.ok === true) {unlock();return response.json;}if (response.status === 503) {return new Promise(r => setTimeout(r, 270 + rd)).then(() => {unlock();return getScriptData(id, true);});}if (response.status === 404) {// script XXXX has been reported and is pending review by a moderator.unlock();return null}console.warn(response.status, response);new Promise(r => setTimeout(r, 470)).then(unlock); // reload later}).then((data) => req.resolve(data)).catch((e) => {unlock();UU.log(id, url)console.warn(e)// reject(e)})})).catch(() => { }))});}const getScriptData = (id, noCache) => {if (!(+id > 0)) return Promise.resolve();id = +id;const reqStores = noCache ? reqStoresB : reqStoresA;const cachedReq = reqStores.get(id);if (cachedReq) return cachedReq;const req = new PromiseExternal();reqStores.set(id, req);getScriptDataAN(noCache);return req;}/*** Get user data from Greasy Fork API** @param {string} userID User ID* @returns {Promise} User data*/const getUserData = (userID, noCache) => {if (!(userID >= 0)) return Promise.resolve()const DO_CORS = /^(cn-greasyfork|greasyfork|sleazyfork)\.org$/.test(window.location.hostname) ? `api.${window.location.hostname}` : '';const url = `https://${DO_CORS || window.location.hostname}/users/${userID}.json`;const fetchUrl = sessionStorage.getItem(`redirect41-${url}`) || url;return new Promise((resolve, reject) => {networkMP2 = networkMP2.then(() => new Promise(unlock => {const maxAgeInSeconds = 900;const rd = Math.floor(Math.random() * 80 + 80);const fetchOptions = noCache ? {method: 'GET',cache: 'reload',credentials: 'omit',headers: {'Cache-Control': `max-age=${maxAgeInSeconds}`,}} : {method: 'GET',cache: 'force-cache',credentials: 'omit',headers: {'Cache-Control': `max-age=${maxAgeInSeconds}`,}};new Promise(r => setTimeout(r, rd)).then(() => DO_CORS ? corsFetch(fetchUrl, fetchOptions) : standardFetch(fetchUrl, fetchOptions)).then((response) => {if (fetchUrl !== response.url) {sessionStorage.setItem(`redirect41-${url}`, response.url);sessionStorage.setItem(`redirect41-${fetchUrl}`, response.url);}UU.log(`${response.status}: ${response.url}`)if (response.ok === true) {unlock();return response.json;}if (response.status === 503) {return new Promise(r => setTimeout(r, 270 + rd)).then(() => {unlock();return getUserData(userID, true); // reload later});}if (response.status === 404) {// user XXXX has been reported and is pending review by a moderator. ????unlock();return null}console.warn(response.status, response);new Promise(r => setTimeout(r, 470)).then(unlock);}).then((data) => resolve(data)).catch((e) => {setTimeout(() => {unlock()}, 270)UU.log(userID, url)console.warn(e)// reject(e)})})).catch(() => { })});}const getTotalInstalls = (data) => {if (!data || !data.scripts) return;return new Promise((resolve, reject) => {const totalInstalls = [];data.scripts.forEach((element) => {totalInstalls.push(parseInt(element.total_installs, 10));});resolve(totalInstalls.reduce((a, b) => a + b, 0));});};const communicationId = WinComm.newCommunicationId();const wincomm = WinComm.createInstance(communicationId);const isInstalled = (script) => {return new Promise((resolve, reject) => {promiseScriptCheck.then(d => {if (!d) return null;const data = d.data;const al = data.type % 10;if (al === 0) {// no namespaceresolve([null, script.name, '']);} else if (al === 1) {// namespaceif (!script.namespace) {getRafPromise() // foreground.then(() => getScriptData(script.id)).then((script) => {resolve([null, script.name, script.namespace]);});} else {resolve([null, script.name, script.namespace]);}}})}).then((res) => {return new Promise((resolve, reject) => {if (!res) return '';const [_, name, namespace] = res;wincomm.request('installedVersion.req', {name,namespace}).then(d => {resolve(d.data.version)})})})/*const external = unsafeWindow.external;const scriptHandler = GM.info.scriptHandler;if (external && external.Violentmonkey && (scriptHandler || 'Violentmonkey') === 'Violentmonkey') {external.Violentmonkey.isInstalled(name, namespace).then((data) => resolve(data));return;}if (external && external.Tampermonkey && (scriptHandler || 'Tampermonkey') === 'Tampermonkey') {external.Tampermonkey.isInstalled(name, namespace, (data) => {(data.installed) ? resolve(data.version) : resolve();});return;}*/};const compareVersions = (v1, v2) => {if (!v1 || !v2) return NaN;if (v1 === null || v2 === null) return NaN;if (v1 === v2) return 0;const sv1 = v1.split('.').map((index) => parseInt(index));const sv2 = v2.split('.').map((index) => parseInt(index));const count = Math.max(sv1.length, sv2.length);for (let index = 0; index < count; index++) {if (isNaNx(sv1[index]) || isNaNx(sv2[index])) return NaN;if (sv1[index] > sv2[index]) return 1;if (sv1[index] < sv2[index]) return -1;}return 0;};/*** Return label for the hide script button** @param {boolean} hidden Is hidden* @returns {string} Label*/const blockLabel = (hidden) => {return hidden ? (locales[lang] ? locales[lang].notHide : locales.en.notHide) : (locales[lang] ? locales[lang].hide : locales.en.hide)}/*** Return label for the install button** @param {number} update Update value* @returns {string} Label*/const installLabel = (update) => {switch (update) {case 0: {return locales[lang] ? locales[lang].reinstall : locales.en.reinstall}case 1: {return locales[lang] ? locales[lang].update : locales.en.update}case -1: {return locales[lang] ? locales[lang].downgrade : locales.en.downgrade}default: {return locales[lang] ? locales[lang].install : locales.en.install}}}const hideBlacklistedDiscussion = (element, list) => {const scriptLink = element.querySelector('a.script-link')const m = /\/scripts\/(\d+)/.exec(scriptLink);const id = m ? +m[1] : 0;if (!(id > 0)) return;switch (list) {case 'hiddenList': {const container = element.closest('.discussion-list-container') || element;if (hiddenList.indexOf(id) >= 0) {container.classList.add('hidden');}// if (customBlacklist && (customBlacklist.test(name) || customBlacklist.test(description)) && !element.classList.contains('blacklisted')) {//     element.classList.add('blacklisted', 'custom-blacklist');//     if (gmc.get('hideBlacklistedScripts') && gmc.get('debugging')) {//         let scriptLink = element.querySelector('.script-link');//         if (scriptLink) { scriptLink.textContent += ' (custom-blacklist)'; }//     }// }break;}default:UU.log('No blacklists');break;}}const hideBlacklistedScript = (element, list) => {if (!element) return;const scriptLink = element.querySelector('.script-link')const name = scriptLink ? scriptLink.textContent : '';const descriptionElem = element.querySelector('.script-description')const description = descriptionElem ? descriptionElem.textContent : '';if (!name) return;switch (list) {case 'nonLatins':if ((nonLatins.test(name) || nonLatins.test(description)) && !element.classList.contains('blacklisted')) {element.classList.add('blacklisted', 'non-latins');if (gmc.get('hideBlacklistedScripts') && gmc.get('debugging')) {let scriptLink = element.querySelector('.script-link');if (scriptLink) { scriptLink.textContent += ' (non-latin)'; }}}break;case 'blacklist':if (isBlackList(name, description) && !element.classList.contains('blacklisted')) {element.classList.add('blacklisted', 'blacklist');if (gmc.get('hideBlacklistedScripts') && gmc.get('debugging')) {let scriptLink = element.querySelector('.script-link');if (scriptLink) { scriptLink.textContent += ' (blacklist)'; }}}break;case 'customBlacklist': {const customBlacklist = customBlacklistRF;if (customBlacklist && (customBlacklist(name) >= 0 || customBlacklist(description) >= 0) && !element.classList.contains('blacklisted')) {element.classList.add('blacklisted', 'custom-blacklist');if (gmc.get('hideBlacklistedScripts') && gmc.get('debugging')) {let scriptLink = element.querySelector('.script-link');if (scriptLink) { scriptLink.textContent += ' (custom-blacklist)'; }}}break;}default:UU.log('No blacklists');break;}};const hideHiddenScript = (element, id, list) => {id = +id;if (!(id >= 0)) return;const isInHiddenList = () => hiddenList.indexOf(id) !== -1;const updateScriptLink = (shouldHide) => {if (gmc.get('hideHiddenScript') && gmc.get('debugging')) {let scriptLink = element.querySelector('.script-link');if (scriptLink) {if (shouldHide) {scriptLink.innerHTML += ' (hidden)';} else {scriptLink.innerHTML = scriptLink.innerHTML.replace(' (hidden)', '');}}}};// Check for initial state and set itif (isInHiddenList()) {element.classList.add('hidden');updateScriptLink(true);}// Add button to hide the scriptconst insertButtonHTML = (selector, html) => {const target = element.querySelector(selector);if (!target) return;let p = document.createElement('template');p.innerHTML = html;target.parentNode.insertBefore(p.content.firstChild, target.nextSibling);};const isHidden = element.classList.contains('hidden');const blockButtonHTML = `<span class=block-button role=button style-16377>${blockLabel(isHidden)}</span>`;const blockButtonHeaderHTML = `<span class=block-button role=button style-77329 style="">${blockLabel(isHidden)}</span>`;insertButtonHTML('.badge-js, .badge-css', blockButtonHTML);insertButtonHTML('header h2', blockButtonHeaderHTML);// Add event listenerconst button = element.querySelector('.block-button');if (button) {button.addEventListener('click', (event) => {event.stopPropagation();event.stopImmediatePropagation();if (!isInHiddenList()) {hiddenList.push(id);GM.setValue('hiddenList', hiddenList);element.classList.add('hidden');updateScriptLink(true);} else {const index = hiddenList.indexOf(id);hiddenList.splice(index, 1);GM.setValue('hiddenList', hiddenList);element.classList.remove('hidden');updateScriptLink(false);}const blockBtn = element.querySelector('.block-button');if (blockBtn) blockBtn.textContent = blockLabel(element.classList.contains('hidden'));});}};const insertButtonHTML = (element, selector, html) => {const target = element.querySelector(selector);if (!target) return;let p = document.createElement('template');p.innerHTML = html;let button = p.content.firstChildtarget.parentNode.insertBefore(button, target.nextSibling);return button;};const addInstallButton = (element, url) => {return setupInstallLink(insertButtonHTML(element, '.badge-js, .badge-css', `<a class="install-link" href="${url}" style-54998></a>`));};async function digestMessage(message, algo) {const encoder = new TextEncoder();const data = encoder.encode(message);const hash = await crypto.subtle.digest(algo, data);return hash;}function qexString(buffer) {const byteArray = new Uint8Array(buffer);const len = byteArray.length;const hexCodes = new Array(len * 2);const chars = 'a4b3c5d7e6f9h2t';for (let i = 0, j = 0; i < len; i++) {const byte = byteArray[i];hexCodes[j++] = chars[byte >> 4];hexCodes[j++] = chars[byte & 0x0F];};return hexCodes.join('');}const encodeFileName = (s) => {if (!s || typeof s !== 'string') return s;s = s.replace(/[.!~*'"();\/\\?@&=$,#]/g, '-').replace(/\s+/g, ' ');return encodeURI(s);}const isLibraryURLWithVersion = (url) => {if (!url || typeof url !== 'string') return;if (url.includes('.js?version=')) return true;if (/\/scripts\/\d+\/\d+\/[^.!~*'"();\/\\?@&=$,#]+\.js/.test(url)) return true;return false;}const showInstallButton = async (scriptID, element) => {await getRafPromise().then();// if(document.querySelector(`li[data-script-id="${scriptID}"]`))let _baseScript = null;if (element.nodeName === 'LI' && element.hasAttribute('data-script-id') && element.getAttribute('data-script-id') === `${scriptID}` && element.getAttribute('data-script-language') === 'js') {const version = element.getAttribute('data-script-version') || ''let scriptCodeURL = element.getAttribute('data-code-url');if (!scriptCodeURL || !isVaildURL(scriptCodeURL)) {const name = element.getAttribute('data-script-name') || ''// if (!/[^\x00-\x7F]/.test(name)) {// const scriptName = useHashedScriptName ? qexString(await digestMessage(`${+scriptID} ${version}`, 'SHA-1')).substring(0, 8) : encodeURI(name);// const token = useHashedScriptName ? `${scriptName.substring(0, 2)}${scriptName.substring(scriptName.length - 2, scriptName.length)}` : String.fromCharCode(Date.now() % 26 + 97) + Math.floor(Math.random() * 19861 + 19861).toString(36);const scriptFilename = element.getAttribute('data-script-type') === 'library' ? `${encodeFileName(name)}.js` : `${encodeFileName(name)}.user.js`;// const scriptFilename = `${scriptName}.user.js`;// code_url: `https://${location.hostname}/scripts/${scriptID}-${token}/code/${scriptFilename}`,// code_url: `https://update.${location.hostname}/scripts/${scriptID}.user.js`,scriptCodeURL = `https://update.${location.hostname}/scripts/${scriptID}/${scriptFilename}`}_baseScript = {id: +scriptID,// name: name,code_url: scriptCodeURL,version: version}// }}const baseScript = _baseScript || (await getScriptData(scriptID));if ((element.nodeName === 'LI' && element.getAttribute('data-script-type') === 'library') || (baseScript.code_url.includes('.js?version='))) {let scriptCodeURL = element.getAttribute('data-code-url');if (!scriptCodeURL || !isVaildURL(scriptCodeURL)) {const script = baseScript.code_url.includes('.js?version=') ? baseScript : (await getScriptData(scriptID));scriptCodeURL = script.code_url;}if (scriptCodeURL && isLibraryURLWithVersion(scriptCodeURL)) {const code_url = fixLibraryCodeURL(scriptCodeURL);const button = addInstallButton(element, code_url);button.textContent = `Copy URL`;button.addEventListener('click', function (evt) {const target = (evt || 0).target;if (!target) return;let a = target.nodeName === 'A' ? target : target.querySelector('a[href]');if (!a) return;let href = target.getAttribute('href');if (!href) return;evt.preventDefault();copyText(href);});}} else {if (!baseScript || !baseScript.code_url || !baseScript.version) return;const button = addInstallButton(element, baseScript.code_url);button.classList.add('install-status-checking');button.textContent = `${installLabel()} ${baseScript.version}`;const script = baseScript && baseScript.name && baseScript.namespace ? baseScript : (await getScriptData(scriptID));if (!script) return;const installed = await isInstalled(script);const version = (baseScript.version && script.version && compareVersions(baseScript.version, script.version) === 1) ? baseScript.version : script.version;const update = compareVersions(version, installed);  // NaN  1  -1  0const label = installLabel(update);button.textContent = `${label} ${version}`;button.classList.remove('install-status-checking');}}const updateReqStoresWithElementsOrder = (x) => {try {const reqStoresA_ = reqStoresA;const reqStoresB_ = reqStoresB;const order2 = [...reqStoresA_.keys()];const order3 = [...reqStoresB_.keys()];const orders1 = x;const orders = new Set([...orders1, ...order2, ...order3]);const reqStoresA2 = new Map();const reqStoresB2 = new Map();for (const id of orders) {const reqA = reqStoresA_.get(id);if (reqA) reqStoresA2.set(id, reqA);const reqB = reqStoresB_.get(id);if (reqB) reqStoresB2.set(id, reqB);}reqStoresA = reqStoresA2;reqStoresB = reqStoresB2;reqStoresA_.clear();reqStoresB_.clear();} catch (e) {console.warn(e)}};let lastIdArrString = '';const foundScriptList = async (scriptList) => {// add options and style for blacklisted/hidden scriptsif (gmc.get('hideBlacklistedScripts') || gmc.get('hideHiddenScript')) {addOptions(scriptList);}mutationRunner(() => {if (!scriptList || scriptList.isConnected !== true) return;const scriptElements = scriptList.querySelectorAll('li[data-script-id]:not([e8kk])');for (const element of scriptElements) {element.setAttribute('e8kk', '1');const scriptID = +element.getAttribute('data-script-id');if (!(scriptID > 0)) continue;// blacklisted scriptsif (gmc.get('nonLatins')) hideBlacklistedScript(element, 'nonLatins');if (gmc.get('blacklist')) hideBlacklistedScript(element, 'blacklist');if (gmc.get('customBlacklist')) hideBlacklistedScript(element, 'customBlacklist');// hidden scriptsif (gmc.get('hideHiddenScript')) hideHiddenScript(element, scriptID, true);// install buttonif (gmc.get('showInstallButton')) {showInstallButton(scriptID, element)}}const idArr = [...scriptList.querySelectorAll('li[data-script-id]')].map(e => +e.getAttribute('data-script-id'));const idArrString = idArr.join(',');if (lastIdArrString !== idArrString) {lastIdArrString = idArrString;updateReqStoresWithElementsOrder(idArr);}}, scriptList, { subtree: true, childList: true });}const foundDiscussionList = (discussionsList) => {targetHiddenRecentDateTime = Date.now() - valHideRecentUsersWithin * 3600000;mutationRunner(() => {if (!discussionsList || discussionsList.isConnected !== true) return;const scriptElements = discussionsList.querySelectorAll('.discussion-list-item:not([e8kk])');for (const element of scriptElements) {element.setAttribute('e8kk', '1');// blacklisted scriptsif (gmc.get('hideHiddenScript')) hideBlacklistedDiscussion(element, 'hiddenList');let t;let userId = 0;if (t = element.querySelector('a.user-link[href*="/users/"]')) {const m = /\/users\/(\d+)/.exec(`${t.getAttribute('href')}`);if (m) {userId = +m[1];}}if (userId > 0) {determineRecentUserAsync(userId).then((isNewUser) => {element.classList.toggle('discussion-item-by-recent-user', isNewUser);});}let discussionId = 0;if (t = element.querySelector('a.discussion-title[href*="/discussions/')) {const m = /\/\w+\/(\d+)/.exec(`${t.getAttribute('href')}`);if (m) {discussionId = +m[1];}}let btnContainer = null;const meta = element.querySelector('div.discussion-meta');if (meta) {btnContainer = document.createElement('additional-buttons');meta.appendChild(btnContainer);}if (btnContainer) {if (discussionId > 0) {const btn = document.createElement('a');btn.classList = 'discussion-list-item-report-comment'btn.textContent = 'Report Comment';btnContainer.appendChild(btn);const m = /^(https?:\/\/[a-z-]{10,15}\.org\/(([a-z]{2,3}(-[a-zA-Z0-9]{2,3})?)\/)?)\w+/.exec(location.href);if (m) {btn.href = `${m[1]}reports/new?item_class=discussion&item_id=${discussionId}`;}}}}}, discussionsList, { subtree: true, childList: true });}const foundScriptDiscussionList = (discussionsList) => {targetHiddenRecentDateTime = Date.now() - valHideRecentUsersWithin * 3600000;mutationRunner(() => {if (!discussionsList || discussionsList.isConnected !== true) return;const scriptElements = discussionsList.querySelectorAll('.discussion-list-item:not([e8kk])');for (const element of scriptElements) {element.setAttribute('e8kk', '1');let t;let userId = 0;if (t = element.querySelector('a.user-link[href*="/users/"]')) {const m = /\/users\/(\d+)/.exec(`${t.getAttribute('href')}`);if (m) {userId = +m[1];}}if (userId > 0) {determineRecentUserAsync(userId).then((isNewUser) => {element.classList.toggle('discussion-item-by-recent-user', isNewUser);});}let discussionId = 0;if (t = element.querySelector('a.discussion-title[href*="/discussions/')) {const m = /\/\w+\/(\d+)/.exec(`${t.getAttribute('href')}`);if (m) {discussionId = +m[1];}}let btnContainer = null;const meta = element.querySelector('div.discussion-meta');if(meta){btnContainer = document.createElement('additional-buttons');meta.appendChild(btnContainer);}if (btnContainer) {if (discussionId > 0) {const btn = document.createElement('a');btn.classList = 'discussion-list-item-report-comment'btn.textContent = 'Report Comment';btnContainer.appendChild(btn);const m = /^(https?:\/\/[a-z-]{10,15}\.org\/(([a-z]{2,3}(-[a-zA-Z0-9]{2,3})?)\/)?)\w+/.exec(location.href);if (m) {btn.href = `${m[1]}reports/new?item_class=discussion&item_id=${discussionId}`;}}}}}, discussionsList, { subtree: true, childList: true });}let promiseScriptCheckResolve = null;const promiseScriptCheck = new Promise(resolve => {promiseScriptCheckResolve = resolve});const milestoneNotificationFn = async (o) => {const { userLink, userID } = o;const milestones = gmc.get('milestoneNotification').replace(/\s/g, '').split(',').map(Number);if (!userID) return;await new Promise(resolve => setTimeout(resolve, 800)); // delay for reducing server burdenawait new Promise(resolve => requestAnimationFrame(resolve)); // foregroundconst userData = await getUserData(+userID.match(/\d+(?=\D)/g));if (!userData) return;const [totalInstalls, lastMilestone] = await Promise.all([getTotalInstalls(userData),GM.getValue('lastMilestone', 0)]);const milestone = milestones.filter(milestone => totalInstalls >= milestone).pop();UU.log(`total installs are "${totalInstalls}", milestone reached is "${milestone}", last milestone reached is "${lastMilestone}"`);if (milestone <= lastMilestone) return;if (milestone && milestone >= 0) {GM.setValue('lastMilestone', milestone);const lang = document.documentElement.lang;const text = (locales[lang] ? locales[lang].milestone : locales.en.milestone).replace('$1', milestone.toLocaleString());if (typeof GM.notification === 'function') {GM.notification({text,title: GM.info.script.name,image: logo,onclick: () => {window.location = `https://${window.location.hostname}${userID}#user-script-list-section`;}});} else {UU.alert(text);}}}const onReady = async () => {try {const gminfo = GM.info || 0;if (gminfo) {const gminfoscript = gminfo.script || 0;const scriptHandlerObject = {scriptHandler: gminfo.scriptHandler || '',scriptName: gminfoscript.name || '', // not name_i18nscriptVersion: gminfoscript.version || '',scriptNamespace: gminfoscript.namespace || '',communicationId};wincomm.hook('_$GreasyFork$Msg$OnScriptInstallFeedback',{ready: (d, evt) => promiseScriptCheckResolve(d),userScriptManagerNotDetected: (d, evt) => promiseScriptCheckResolve(null),'installedVersion.res': wincomm.handleResponse})document.head.appendChild(document.createElement('script')).textContent = `;(${mWindow.contentScriptText})(${JSON.stringify(scriptHandlerObject)}, ${WinComm.createInstance});`;}addSettingsToMenu();setTimeout(() => {getRafPromise().then(() => {let installBtn = document.querySelector('a[data-script-id][data-script-version]')let scriptID = installBtn && installBtn.textContent ? +installBtn.getAttribute('data-script-id') : 0;if (scriptID > 0) {getScriptData(scriptID, true);} else {const userLink = document.querySelector('#site-nav .user-profile-link a[href]');let userID = userLink ? userLink.getAttribute('href') : '';userID = userID ? /users\/(\d+)/.exec(userID) : null;if (userID) userID = userID[1];if (userID) {userID = +userID;if (userID > 0) {getUserData(userID, true);}}}});}, 740);const userLink = document.querySelector('.user-profile-link a[href]');const userID = userLink ? userLink.getAttribute('href') : undefined;const urlMatch = (url1, url2) => {url1 = `${url1}`url2 = `${url2}`;if (url1.includes(location.hostname)) {url1 = url1.replace(`https://${location.hostname}/`, '/')url1 = url1.replace(`http://${location.hostname}/`, '/')url1 = url1.replace(/^\/+/, '/')} else if (!url1.startsWith('/')) {url1 = `/${url1}`;}if (url2.includes(location.hostname)) {url2 = url2.replace(`https://${location.hostname}/`, '/')url2 = url2.replace(`http://${location.hostname}/`, '/')url2 = url2.replace(/^\/+/, '/')} else if (!url2.startsWith('/')) {url2 = `/${url2}`;}url1 = url1.replace(/\?\w+=\w+(&\w+=\w+)*$/, '');url2 = url2.replace(/\?\w+=\w+(&\w+=\w+)*$/, '');return url1.toLowerCase() === url2.toLowerCase();}UU.addStyle(mWindow.pageCSS);const elementLookup = (selector, fn) => {const elm0 = document.querySelector(selector);if (elm0) {fn(elm0);} else {const timeout = Date.now() + 3000;(new MutationObserver((_, observer) => {const elm = document.querySelector(selector);if (elm && elm.childElementCount >= 1) {observer.disconnect();observer.takeRecords();fn(elm);} else if (Date.now() > timeout) {observer.disconnect();observer.takeRecords();}})).observe(document, { subtree: true, childList: true });}};// blacklisted scripts / hidden scripts / install buttonconst isPageUnderScript = location.pathname.includes('/scripts/');const pageType_ = /\/([a-z-]+)$/.exec(window.location.pathname);const pageType = pageType_ ? pageType_[1] : '';const isDiscussionListPage = !isPageUnderScript && (pageType === 'discussions' || (pageType_ && /\/discussions\/[a-z-]+$/.test(location.pathname)));const isFeedbackListPage = isPageUnderScript && pageType === 'feedback';const isScriptListPage = !isPageUnderScript && pageType === 'scripts';const isUserIDPage = !isPageUnderScript && urlMatch(window.location.pathname, userID);if (!isUserIDPage && !isDiscussionListPage && !isFeedbackListPage && (gmc.get('hideBlacklistedScripts') || gmc.get('hideHiddenScript') || gmc.get('showInstallButton'))) {if (isScriptListPage) {elementLookup('.script-list', foundScriptList);} else if (isPageUnderScript) {// hidden scripts on details pageconst installLinkElement = document.querySelector('#script-info .install-link[data-script-id]');if (installLinkElement) {setupInstallLink(installLinkElement);if (gmc.get('hideHiddenScript')) {const id = +installLinkElement.getAttribute('data-script-id');hideHiddenScript(document.querySelector('#script-info'), id, false);}installLinkElement.addEventListener('click', async function (e) {if (e && e.isTrusted && location.pathname.includes('/scripts/')) {await new Promise(r => setTimeout(r, 800));await new Promise(r => window.requestAnimationFrame(r));await new Promise(r => setTimeout(r, 100));// let ethicalads497 = 'ethicalads' in window ? window.ethicalads : undefined;// window.ethicalads = { wait: new Promise() }document.dispatchEvent(new Event("DOMContentLoaded"));document.documentElement.dispatchEvent(new Event("turbo:load"));// if (ethicalads497 === undefined) delete window.ethicalads; else window.ethicalads = ethicalads497;}})}}} else if (isDiscussionListPage) {elementLookup('.discussion-list', foundDiscussionList);} else if (isFeedbackListPage) {elementLookup('.script-discussion-list', foundScriptDiscussionList);}// total installsif (gmc.get('showTotalInstalls') && document.querySelector('#user-script-list')) {const dailyInstalls = [];const totalInstalls = [];const dailyInstallElements = document.querySelectorAll('#user-script-list li dd.script-list-daily-installs');for (const element of dailyInstallElements) {dailyInstalls.push(parseInt(element.textContent.replace(/\D/g, ''), 10));}const totalInstallElements = document.querySelectorAll('#user-script-list li dd.script-list-total-installs');for (const element of totalInstallElements) {totalInstalls.push(parseInt(element.textContent.replace(/\D/g, ''), 10));}const dailyInstallsSum = dailyInstalls.reduce((a, b) => a + b, 0);const totalInstallsSum = totalInstalls.reduce((a, b) => a + b, 0);const convertLi = (li) => {if (!li) return null;const a = li.firstElementChildif (a === null) return li;if (a === li.lastElementChild && a.nodeName === 'A') return a;return null;}const plusSign = document.querySelector('#user-script-list-section a[rel="next"][href*="page="], #user-script-list-section a[rel="prev"][href*="page="]') ? '+' : '';const dailyOption = convertLi(document.querySelector('#script-list-sort .list-option:nth-child(1)'));dailyOption && dailyOption.insertAdjacentHTML('beforeend', `<span> (${dailyInstallsSum.toLocaleString()}${plusSign})</span>`);const totalOption = convertLi(document.querySelector('#script-list-sort .list-option:nth-child(2)'));totalOption && totalOption.insertAdjacentHTML('beforeend', `<span> (${totalInstallsSum.toLocaleString()}${plusSign})</span>`);}// milestone notificationif (gmc.get('milestoneNotification')) {milestoneNotificationFn({ userLink, userID });}if (isScriptFirstUse) GM.setValue('firstUse', false).then(() => {gmc.open();});if (fixLibraryScriptCodeLink) {let xpath = "//code[contains(text(), '.js?version=') or contains(text(), '// @require https://')]";let snapshot = document.evaluate(xpath, document, null, XPathR###lt.ORDERED_NODE_SNAPSHOT_TYPE, null);for (let i = 0; i < snapshot.snapshotLength; i++) {let element = snapshot.snapshotItem(i);if (element.firstElementChild) continue;element.textContent = element.textContent.replace(/\bhttps:\/\/(cn-greasyfork|greasyfork|sleazyfork)\.org\/scripts\/\d+\-[^\/]+\/code\/[^\.]+\.js\?version=\d+\b/, (_) => {return fixLibraryCodeURL(_);});element.parentNode.insertBefore(document.createTextNode('\u200B'), element);element.style.display = 'inline-flex';setClickToSelect(element);}}if (addAdditionInfoLengthHint && location.pathname.includes('/scripts/') && location.pathname.includes('/versions')) {function contentLength(text) {return text.replace(/\n/g, '  ').length;}function contentLengthMax() {return 50000;}let _spanContent = null;function updateText(ainfo, span) {const value = ainfo.value;if (typeof value !== 'string') return;if (_spanContent !== value) {_spanContent = value;span.textContent = `Text Length: ${contentLength(value)} / ${contentLengthMax()}`;}}function onChange(evt) {let ainfo = (evt || 0).target;if (!ainfo) return;let span = ainfo.parentNode.querySelector('.script-version-ainfo-span');if (!span) return;updateText(ainfo, span);}function kbEvent(evt) {Promise.resolve().then(() => {onChange(evt);})}for (const ainfo of document.querySelectorAll('textarea[id^="script-version-additional-info"]')) {let span = document.createElement('span');span.classList.add('script-version-ainfo-span');ainfo.addEventListener('change', onChange, false);ainfo.addEventListener('keydown', kbEvent, false);ainfo.addEventListener('keypress', kbEvent, false);ainfo.addEventListener('keyup', kbEvent, false);updateText(ainfo, span);ainfo.parentNode.insertBefore(span, ainfo.nextSibling);}}} catch (e) {console.log(e);}}Promise.resolve().then(() => {if (document.readyState !== 'loading') {onReady();} else {window.addEventListener("DOMContentLoaded", onReady, false);}});})();