// ==UserScript== // @name Magic Userscript+ : Show Site All UserJS // @name:zh Magic Userscript+ : 显示当前网站所有可用的UserJS脚本 Jaeger // @name:zh-CN Magic Userscript+ : 显示当前网站所有可用的UserJS脚本 Jaeger // @name:zh-TW Magic Userscript+ : 顯示當前網站所有可用的UserJS腳本 Jaeger // @name:ja Magic Userscript+ : 現在のサイトの利用可能なすべてのUserJSスクリプトを表示するJaeger // @name:ru-RU Magic Userscript+ : Показать пользовательские скрипты (UserJS) для сайта. Jaeger // @name:ru Magic Userscript+ : Показать пользовательские скрипты (UserJS) для сайта. Jaeger // @description Show current site all UserJS, the easier way to install UserJs for Tampermonkey. // @description:zh 显示当前网站的所有可用UserJS(Tampermonkey)脚本,交流QQ群:104267383 // @description:zh-CN 显示当前网站的所有可用UserJS(Tampermonkey)脚本,交流QQ群:104267383 // @description:zh-TW 顯示當前網站的所有可用UserJS(Tampermonkey)腳本,交流QQ群:104267383 // @description:ja 現在のサイトで利用可能なすべてのUserJS(Tampermonkey)スクリプトを表示します。 // @description:ru-RU Показывает пользовательские скрипты (UserJS) для сайта. Легкий способ установить пользовательские скрипты для Tampermonkey. // @description:ru Показывает пользовательские скрипты (UserJS) для сайта. Легкий способ установить пользовательские скрипты для Tampermonkey. // @author Magic // @version 6.0.0 // @icon data:image/png;base64,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 // @supportURL https://github.com/magicoflolis/Userscript-Plus/issues/new // @namespace https://github.com/magicoflolis/Userscript-Plus // @homepageURL https://github.com/magicoflolis/Userscript-Plus // @license MIT // @connect greasyfork.org // @connect sleazyfork.org // @connect github.com // @connect openuserjs.org // @match https://*/* // @grant GM_xmlhttpRequest // @grant GM_openInTab // @grant GM_getValue // @grant GM_setValue // @grant GM_info // @compatible chrome // @compatible firefox // @compatible edge // @compatible opera // @compatible safari // @noframes // @run-at document-start // @downloadURL none // ==/UserScript== /** Injected stylesheet https://github.com/magicoflolis/Userscript-Plus/tree/master/userscript/src/sass */ const main_css = `mujs-root *{scrollbar-color:#fff #2e323d;scrollbar-width:thin;background:#495060;color:#fff}@supports not (scrollbar-width: thin){mujs-root * ::-webkit-scrollbar{width:1.4vw;height:3.3vh}mujs-root * ::-webkit-scrollbar-track{background-color:#2e323d;border-radius:10px;margin-top:3px;margin-bottom:3px;box-shadow:inset 0 0 6px rgba(0,0,0,.3)}mujs-root * ::-webkit-scrollbar-thumb{border-radius:10px;background-color:#fff;background-image:-webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.2) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.2) 50%, rgba(255, 255, 255, 0.2) 75%, transparent 75%, transparent)}mujs-root * ::-webkit-scrollbar-thumb:hover{background-color:#fff}}mu-js{line-height:normal}.mujs-cfg{line-height:1.5}body.webext-page,.main{font-size:14px}mujs-column,mujs-row,.mujs-sty-flex{display:flex}mujs-column,mujs-row{gap:10px}@media screen and (max-width: 800px){mujs-column{flex-flow:row wrap}}mujs-row{flex-direction:column}mu-js{cursor:default}.hidden{display:none !important;z-index:-1 !important}.main{width:100%;width:-moz-available;width:-webkit-fill-available;background:#495060 !important;border:1px solid rgba(0,0,0,0);border-radius:10px;font-family:Arial,Helvetica,sans-serif}@media screen and (max-height: 450px){.main:not(.webext-page){height:100% !important;bottom:0rem !important;margin-left:0rem !important;margin-right:0rem !important;right:0rem !important}}.main.expanded{height:100% !important;bottom:0rem !important}.main:not(.webext-page){position:fixed;height:492px}.main:not(.webext-page):not(.expanded){margin-left:1rem;margin-right:1rem;right:1rem;bottom:1rem}.main:not(.webext-page):not(.expanded).auto-height{height:auto}.main:not(.hidden){z-index:100000000000000000 !important;display:flex !important;flex-direction:column !important}.mainframe{background:rgba(0,0,0,0);position:fixed;bottom:1rem;right:1rem}.mainframe count-frame{width:2em;height:1em}.mainframe:not(.hidden){z-index:100000000000000000 !important;display:block}count-frame{border-radius:16px;padding:5px;border:2px solid rgba(0,0,0,0);font-size:16px;font-weight:400;display:inline-block;text-align:center;min-width:1em}.mujs-header-prim{order:0;display:flex;gap:10px;border-bottom:1px solid #fff;border-top-left-radius:10px;border-top-right-radius:10px;height:-webkit-fit-content;height:-moz-fit-content;height:fit-content;padding:10px;font-size:1em;place-content:space-between}.mujs-body{overflow-x:hidden;order:1}.mujs-body .mujs-ratings{padding:0 .25em;border:1px solid #fff;border-radius:10px}.mujs-body mu-jsbtn svg{fill:#fff;width:14px;height:14px;background:rgba(0,0,0,0)}.mujs-cfg,.mujs-body{border:1px solid rgba(0,0,0,0);border-bottom-left-radius:10px;border-bottom-right-radius:10px}@media screen and (max-width: 1150px){.mujs-cfg{margin:0px auto 1rem auto !important}}.mujs-cfg{height:-webkit-fit-content;height:-moz-fit-content;height:fit-content}@media screen and (max-height: 812px){.mujs-cfg:not(.webext-page){flex-wrap:wrap;flex-direction:row !important}}.mujs-cfg mujs-section>label{display:flex;justify-content:space-between}.mujs-cfg mujs-section>label input:not([type=checkbox]){position:relative;border-radius:4px;border:1px solid #fff}.mujs-cfg .mujs-inlab{position:relative;width:38px}.mujs-cfg .mujs-inlab input[type*=checkbox]{display:none}.mujs-cfg .mujs-inlab input[type*=checkbox]:checked+label{margin-left:0;background-color:rgba(255,255,255,.568)}.mujs-cfg .mujs-inlab input[type*=checkbox]:checked+label:before{right:0px}.mujs-cfg .mujs-inlab input[type*=checkbox][id=greasyfork]:checked+label,.mujs-cfg .mujs-inlab input[type*=checkbox][id=sleazyfork]:checked+label{background-color:rgba(0,183,255,.568)}.mujs-cfg .mujs-inlab input[type*=checkbox][id=openuserjs]:checked+label{background-color:rgba(237,63,20,.568)}.mujs-cfg .mujs-inlab input[type*=checkbox][id=github]:checked+label{background-color:rgba(36,41,47,.568)}.mujs-cfg .mujs-inlab label{padding:0;display:block;overflow:hidden;height:16px;border-radius:20px;border:1px solid #fff;background-color:#495060}.mujs-cfg .mujs-inlab label:before{content:"";display:block;width:20px;height:20px;margin:-2px;background:#fff;position:absolute;top:0;right:20px;border-radius:20px}.mujs-cfg [id=blacklist]{overflow-y:auto;background:#000;color:#fff;resize:vertical;outline:none;border-style:none;font-family:monospace}.mujs-cfg [id=blacklist]:focus{outline:none}.mujs-cfg:not(.webext-page){order:2;margin:0px 25rem 1rem 25rem}table{width:100%;width:-moz-available;width:-webkit-fill-available}@media screen and (max-width: 800px){table thead>tr{display:grid;grid-auto-flow:column}}@media screen and (max-width: 500px){table thead>tr{display:none !important}}table th,table td{border-bottom:1px solid #fff}table td.mujs-uframe,table td.mujs-list,table td.install-btn{text-align:center}table th{position:-webkit-sticky;position:sticky;top:0}table th.mujs-header-name{width:50%}@media screen and (max-width: 800px){table th.mujs-header-name{width:auto !important}}mujs-a{display:inline-block}mujs-a.mujs-euser{padding-left:.5rem;padding-right:.5rem}@media screen and (max-width: 800px){.frame:not(.webext-page){display:grid}.frame:not(.webext-page) mu-jsbtn{margin-left:25%;margin-right:25%}}.frame.sf mujs-a{color:#e75531 !important}.frame.sf mu-jsbtn{background-color:#ed3f14 !important;border-color:#ed3f14 !important}.frame:not(.sf) mujs-a{color:#00b7ff}.frame:not(.sf) mu-jsbtn{color:#fff;background-color:#2d8cf0;border-color:#2d8cf0}.mujs-name{display:grid}.mujs-name>*:not(.mujs-homepage){margin-top:3px}.mujs-name span{font-size:.8em !important}mujs-btn{font-style:normal;font-weight:400;font-variant:normal;text-transform:none;text-rendering:auto;border:1px solid #fff;font-size:16px;border-radius:4px;line-height:1;padding:6px 15px}mujs-btn svg{fill:#fff;width:14px;height:14px}mu-jsbtn{font-size:14px;border-radius:4px;font-style:normal;padding:7px 15%;font-weight:400;font-variant:normal;line-height:normal;display:block}input:not([type=checkbox]){border:rgba(0,0,0,0);outline:none !important}mujs-a,mu-jsbtn,.mujs-pointer,.mujs-cfg mujs-section *:not(input[type=password],input[type=text],input[type=number]),.mainbtn,.mainframe,mujs-btn{cursor:pointer !important}th,.mujs-cfg *:not(input[type=password],input[type=text],input[type=number]){-webkit-user-select:none !important;-moz-user-select:none !important;-ms-user-select:none !important;user-select:none !important}mujs-btn,input,.mujs-homepage{width:-webkit-fit-content;width:-moz-fit-content;width:fit-content;height:-webkit-fit-content;height:-moz-fit-content;height:fit-content}.mujs-fltlist{width:170px}.mujs-searcher{width:100px}.mujs-sty-flex>mujs-btn{margin:auto}.mujs-invalid{border-radius:8px !important;border-width:2px !important;border-style:solid !important;border-color:red !important}`; (() => { let userjs = (self.userjs = {}) // Skip text/plain documents. if ( (document instanceof Document || (document instanceof XMLDocument && document.createElement('div') instanceof HTMLDivElement)) && /^image\/|^text\/plain/.test(document.contentType || '') === false && (self.userjs instanceof Object === false || userjs.UserJS !== true) ) { userjs = self.userjs = { UserJS: true } } let cfg = {} let lang = {} let legacyMsg = null //#region Console const err = (...msg) => { console.error( '[%cUserJS%c] %cERROR', 'color: rgb(29, 155, 240);', '', 'color: rgb(249, 24, 128);', ...msg, ) } const info = (...msg) => { console.info( '[%cUserJS%c] %cINF', 'color: rgb(29, 155, 240);', '', 'color: rgb(0, 186, 124);', ...msg, ) } const log = (...msg) => { console.log( '[%cUserJS%c] %cLOG', 'color: rgb(29, 155, 240);', '', 'color: rgb(219, 160, 73);', ...msg, ) } //#endregion const hasOwn = Object.hasOwn || Object.prototype.hasOwnProperty.call const normalizeTarget = (target, root = document) => { if (typeof target === 'string') { return Array.from(root.querySelectorAll(target)) } if (target instanceof Element) { return [target] } if (target === null) { return [] } if (Array.isArray(target)) { return target } return Array.from(target) } class dom { static attr(target, attr, value = undefined) { for (const elem of normalizeTarget(target)) { if (value === undefined) { return elem.getAttribute(attr) } if (value === null) { elem.removeAttribute(attr) } else { elem.setAttribute(attr, value) } } } static create(a) { if (typeof a === 'string') { return document.createElement(a) } } static prop(target, prop, value = undefined) { for (const elem of normalizeTarget(target)) { if (value === undefined) { return elem[prop] } elem[prop] = value } } static text(target, text) { const targets = normalizeTarget(target) if (text === undefined) { return targets.length !== 0 ? targets[0].textContent : undefined } for (const elem of targets) { elem.textContent = text } } } dom.cl = class { static add(target, name) { if (Array.isArray(name)) { for (const elem of normalizeTarget(target)) { elem.classList.add(...name) } } else { for (const elem of normalizeTarget(target)) { elem.classList.add(name) } } } static remove(target, name) { if (Array.isArray(name)) { for (const elem of normalizeTarget(target)) { elem.classList.remove(...name) } } else { for (const elem of normalizeTarget(target)) { elem.classList.remove(name) } } } static toggle(target, name, state) { let r for (const elem of normalizeTarget(target)) { r = elem.classList.toggle(name, state) } return r } static has(target, name) { for (const elem of normalizeTarget(target)) { if (elem.classList.contains(name)) { return true } } return false } } const isGM = typeof GM !== 'undefined' const isMobile = /Mobile|Tablet/.test(navigator.userAgent) const navLang = navigator.language.split('-')[0] ?? 'en' /** * Object is Null * @param {Object} obj - Object * @returns {boolean} Returns if statement true or false */ const isNull = (obj) => { return Object.is(obj, null) || Object.is(obj, undefined) } /** * Object is Blank * @param {(Object|Object[]|string)} obj - Array, Set, Object or String * @returns {boolean} Returns if statement true or false */ const isBlank = (obj) => { return ( (typeof obj === 'string' && Object.is(obj.trim(), '')) || (obj instanceof Set && Object.is(obj.size, 0)) || (Array.isArray(obj) && Object.is(obj.length, 0)) || (obj instanceof Object && typeof obj.entries !== 'function' && Object.is(Object.keys(obj).length, 0)) ) } /** * Object is Empty * @param {(Object|Object[]|string)} obj - Array, object or string * @returns {boolean} Returns if statement true or false */ const isEmpty = (obj) => isNull(obj) || isBlank(obj) const isFunction = (obj) => { return typeof obj === 'function' || obj instanceof Function } /** * setTimeout w/ Promise * @param {number} ms - Timeout in milliseconds (ms) * @returns {Promise} Promise object */ const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms)) const alang = [] const defcfg = { injection: 'interactive', cache: true, autoexpand: false, filterlang: false, sleazyredirect: false, time: 10000, blacklist: [ { enabled: true, regex: true, flags: '', name: 'Blacklist 1', url: '(gov|cart|checkout|login|join|signin|signup|sign-up|password|reset|password_reset)', }, { enabled: true, regex: true, flags: '', name: 'Blacklist 2', url: '(pay|bank|money|localhost|authorize|checkout|bill|wallet|router)', }, { enabled: true, regex: false, flags: '', name: 'Blacklist 3', url: 'https://home.bluesnap.com', }, { enabled: true, regex: false, flags: '', name: 'Blacklist 4', url: ['zalo.me', 'skrill.com'], }, ], engines: [ { enabled: true, name: 'greasyfork', url: 'https://greasyfork.org', }, { enabled: true, name: 'sleazyfork', url: 'https://sleazyfork.org', }, { enabled: false, name: 'openuserjs', url: 'https://openuserjs.org/?q=', }, { enabled: false, name: 'github', url: 'https://api.github.com/search/code?q=', token: '', }, ], } const langs = { en: { legacy: 'PLEASE RESET YOUR CONFIG!', createdby: 'Created by', name: 'Name', daily: 'Daily Installs', close: 'Close', filterA: 'Filter', max: 'Maximize', min: 'Minimize', search: 'Search', searcher: 'Title | Description | Author...', install: 'Install', issue: 'New Issue', version: 'Version', updated: 'Last Updated', total: 'Total Installs', rating: 'Ratings', good: 'Good', ok: 'Ok', bad: 'Bad', created: 'Created', redirect: 'Greasy Fork for adults', filter: 'Filter out other languages', dtime: 'Display Timeout', save: 'Save', }, es: { legacy: 'PLEASE RESET YOUR CONFIG!', createdby: 'Created by', name: 'Name', daily: 'Instalaciones diarias', close: 'Ya no se muestra', filterA: 'Filtro', max: 'Maximizar', min: 'Minimizar', search: 'Busque en', searcher: 'Título | Descripción | Autor...', install: 'Instalar', issue: 'Nueva edición', version: 'Versión', updated: 'Última actualización', total: 'Total de instalaciones', rating: 'Clasificaciones', good: 'Bueno', ok: 'Ok', bad: 'Malo', created: 'Creado', redirect: 'Greasy Fork para adultos', filter: 'Filtrar otros idiomas', dtime: 'Mostrar el tiempo de espera', save: 'Guardar', }, ru: { legacy: 'PLEASE RESET YOUR CONFIG!', createdby: 'Created by', name: 'Name', daily: 'Ежедневные установки', close: 'Больше не показывать', filterA: 'Фильтр', max: 'Максимизировать', min: 'Минимизировать', search: 'Поиск', searcher: 'Название | Описание | Автор...', install: 'Установите', issue: 'Новый выпуск', version: 'Версия', updated: 'Последнее обновление', total: 'Всего установок', rating: 'Рейтинги', good: 'Хорошо', ok: 'Хорошо', bad: 'Плохо', created: 'Создано', redirect: 'Greasy Fork для взрослых', filter: 'Отфильтровать другие языки', dtime: 'Тайм-аут отображения', save: 'Сохранить', }, ja: { legacy: 'PLEASE RESET YOUR CONFIG!', createdby: 'Created by', name: 'Name', daily: 'デイリーインストール', close: '表示されなくなりました', filterA: 'フィルター', max: '最大化', min: 'ミニマム', search: '検索', searcher: 'タイトル|説明|著者...', install: 'インストール', issue: '新刊のご案内', version: 'バージョン', updated: '最終更新日', total: '総インストール数', rating: 'レーティング', good: 'グッド', ok: '良い', bad: '悪い', created: '作成', redirect: '大人のGreasyfork', filter: '他の言語をフィルタリングする', dtime: '表示タイムアウト', save: '拯救', }, fr: { createdby: 'Created by', name: 'Name', daily: 'Installations quotidiennes', close: 'Ne plus montrer', filterA: 'Filtre', max: 'Maximiser', min: 'Minimiser', search: 'Recherche', searcher: 'Titre | Description | Auteur...', install: 'Installer', issue: 'Nouveau numéro', version: 'Version', updated: 'Dernière mise à jour', total: 'Total des installations', rating: 'Notations', good: 'Bon', ok: 'Ok', bad: 'Mauvais', created: 'Créé', redirect: 'Greasy Fork pour les adultes', filter: 'Filtrer les autres langues', dtime: "Délai d'affichage", save: 'Sauvez', }, zh: { legacy: 'PLEASE RESET YOUR CONFIG!', createdby: 'Created by', name: 'Name', daily: '日常安装', close: '不再显示', filterA: '过滤器', max: '最大化', min: '最小化', search: '搜索', searcher: '标题|描述|作者...', install: '安装', issue: '新问题', version: '版本', updated: '最后更新', total: '总安装量', rating: '评级', good: '好的', ok: '好的', bad: '不好', created: '创建', redirect: '大人的Greasyfork', filter: '过滤掉其他语言', dtime: '显示超时', save: '拯救', }, nl: { legacy: 'PLEASE RESET YOUR CONFIG!', createdby: 'Created by', name: 'Name', daily: 'Dagelijkse Installaties', close: 'Sluit', filterA: 'Filter', max: 'Maximaliseer', min: 'Minimaliseer', search: 'Zoek', searcher: 'Titel | Beschrijving | Auteur...', install: 'Installeer', issue: 'Nieuw Issue', version: 'Versie', updated: 'Laatste Update', total: 'Totale Installaties', rating: 'Beoordeling', good: 'Goed', ok: 'Ok', bad: 'Slecht', created: 'Aangemaakt', redirect: 'Greasy Fork voor volwassenen', filter: 'Filter andere talen', dtime: 'Weergave timeout', save: 'Opslaan', }, } /** * preventDefault + stopPropagation * @param {Object} e - Selected Element */ const halt = (e) => { e.preventDefault() e.stopPropagation() } /** * Add Event Listener * @param {Object} root - Selected Element * @param {string} type - root Event Listener * @param {Function} callback - Callback function * @param {Object} [options={}] - (Optional) Options * @returns {Object} Returns selected Element */ const ael = (root = document, type, callback, options = {}) => { try { root = root || document.documentElement || document.body || document.head || document.querySelector(':root') if (isMobile && type === 'click') { type = 'mouseup' root.addEventListener('touchstart', callback) root.addEventListener('touchend', callback) } if (type === 'fclick') { type = 'click' } return root.addEventListener(type, callback, options) } catch (ex) { return err(ex) } } /** * Prefix for document.querySelectorAll() * @param {Object} element - Elements for query selection * @param {Object} [root=document] - Root selector Element * @returns {Object} Returns root.querySelectorAll(element) */ const qsA = (element, root = document) => { root = root || document.documentElement || document.body || document.head || document.querySelector(':root') return root.querySelectorAll(element) } /** * Prefix for document.querySelector() * @param {Object} element - Element for query selection * @param {Object} [root=document] - Root selector Element * @returns {Object} Returns root.querySelector(element) */ const qs = (element, root = document) => { root = root || document.documentElement || document.body || document.head || document.querySelector(':root') return root.querySelector(element) } /** * Prefix for document.querySelector() w/ Promise * @param {Object} element - Element for query selection * @param {Object} [root=document] - Root selector Element * @returns {Object} Returns root.querySelector(element) */ const query = async (element, root = document) => { const waitForElement = async () => { while (isNull(root.querySelector(element))) { await new Promise((resolve) => requestAnimationFrame(resolve)) } return root.querySelector(element) } return Promise.any([ waitForElement(), delay(5000).then(() => Promise.reject(new Error('Unable to locate element')), ), ]) } /** * Create/Make Element * @param {string} element - Element to create * @param {string} cname - (Optional) Element class name * @param {Object} [attrs={}] - (Optional) Element attributes * @returns {Object} Returns created Element */ const make = (element, cname, attrs = {}) => { let el try { el = dom.create(element) if (!isEmpty(cname)) { if (typeof cname === 'string') { el.className = cname } } if (!isEmpty(attrs)) { /** * Form Attributes of Element * @param {Object} element - Element * @param {Object} [attrib={}] - (Optional) Element attributes */ const formAttrs = (element, attr = {}) => { for (const key in attr) { if (typeof attr[key] === 'object') { formAttrs(element[key], attr[key]) } else if (typeof attr[key] === 'function') { if (key === 'container') { key() continue } if (/^on/.test(key)) { element[key] = attr[key] continue } ael(element, key, attrs[key]) } else { element[key] = attr[key] } } } formAttrs(el, attrs) } return el } catch (ex) { err(ex) return el } } const iconSVG = { cfg: ' ', close: '', filter: ' ', fsClose: ' ', fsOpen: ' ', fullscreen: '', gf: '', gh: '', hide: ' ', install: '', issue: '', nav: ' ', plus: ' ', search: ' ', } const Timeout = class { constructor() { this.ids = [] } set(delay, reason) { return new Promise((resolve, reject) => { const id = setTimeout(() => { isNull(reason) ? resolve() : reject(reason) this.clear(id) }, delay) this.ids.push(id) }) } clear(...ids) { this.ids = this.ids.filter((id) => { if (ids.includes(id)) { clearTimeout(id) return false } return true }) } } const MU = { /** * Get Value * @param {string} key - Key to get the value of * @param {Object} def - Fallback default value of key * @returns {Object} Value or default value of key * @link https://violentmonkey.github.io/api/gm/#gm_getvalue * @link https://developer.mozilla.org/docs/Web/API/Web_Storage_API/Using_the_Web_Storage_API */ getValue(key, def = {}) { try { const params = JSON.stringify(def) if (isGM) { return JSON.parse(GM_getValue(key, params)) } return window.localStorage.getItem(`MUJS-${key}`) ? JSON.parse(window.localStorage.getItem(`MUJS-${key}`)) : def } catch (ex) { err(ex) } }, /** * Get info of script * @returns {Object} Script info * @link https://violentmonkey.github.io/api/gm/#gm_info */ info: isGM ? GM_info : { script: { icon: '', name: 'Magic Userscript+', namespace: 'https://github.com/magicoflolis/Userscript-Plus', updateURL: 'https://github.com/magicoflolis/Userscript-Plus/releases', version: 'Bookmarklet', }, }, /** * Open a new window * @param {string} url - URL of webpage to open * @param {object} params - GM parameters * @returns {object} GM_openInTab object with Window object as a fallback * @link https://violentmonkey.github.io/api/gm/#gm_openintab * @link https://developer.mozilla.org/docs/Web/API/Window/open */ openInTab( url, params = { active: true, insert: true, }, features, ) { if (!isGM && isBlank(params)) { params = '_blank' } if (features) { return window.open(url, params, features) } return isGM ? GM_openInTab(url, params) : window.open(url, params) }, /** * Set value * @param {string} key - Key to set the value of * @param {Object} v - Value of key * @returns {Promise} Saves key to either GM managed storage or webpages localstorage * @link https://violentmonkey.github.io/api/gm/#gm_setvalue * @link https://developer.mozilla.org/docs/Web/API/Web_Storage_API/Using_the_Web_Storage_API */ setValue(key, v) { return new Promise((resolve) => { v = typeof v !== 'string' ? JSON.stringify(v ?? {}) : v if (isGM) { resolve(GM_setValue(key, v)) } else { resolve(window.localStorage.setItem(`MUJS-${key}`, v)) } }) }, xmlRequest: isGM ? GM_xmlhttpRequest : () => { return {} }, /** * Fetch a URL with fetch API as fallback * * When GM is supported, makes a request like XMLHttpRequest, with some special capabilities, not restricted by same-origin policy * @param {string} url - The URL to fetch * @param {string} method - Fetch method * @param {string} responseType - Response type * @param {Object} data - Fetch parameters * @param {boolean} forcefetch - Force use fetch API * @returns {*} Fetch results * @link https://violentmonkey.github.io/api/gm/#gm_xmlhttprequest * @link https://developer.mozilla.org/docs/Web/API/Fetch_API */ fetchURL( url = '', method = 'GET', responseType = 'json', data = {}, forcefetch = false, ) { return new Promise((resolve, reject) => { responseType = responseType.toLocaleLowerCase() const params = { method: method.toLocaleUpperCase(), ...data, } if (isGM && !forcefetch) { if (params.credentials) { Object.assign(params, { anonymous: false, }) if (Object.is(params.credentials, 'omit')) { Object.assign(params, { anonymous: true, }) } delete params.credentials } } else { if (params.onprogress) { delete params.onprogress } } if (/buffer/i.test(responseType)) { fetch(url, params) .then((response) => { if (!response.ok) reject(response) resolve(response.arrayBuffer()) }) .catch(reject) } else if (isGM && !forcefetch) { MU.xmlRequest({ url, responseType, ...params, onerror: reject, onload: (r) => { if (r.status !== 200) reject(new Error(`${r.status} ${url}`)) if (/basic/i.test(responseType)) resolve(r) resolve(r.response) }, }) } else { fetch(url, params) .then((response) => { if (!response.ok) reject(response) if (/json/i.test(responseType)) { resolve(response.json()) } else if (/text/i.test(responseType)) { resolve(response.text()) } else if (/blob/i.test(responseType)) { resolve(response.blob()) } else if (/document/i.test(responseType)) { const data = new DOMParser().parseFromString( response.text(), 'text/html', ) resolve(data) } resolve(response) }) .catch(reject) } }) }, } MU.storage = class { static getItem(key) { return window.localStorage.getItem(key) } static has(key) { return !isNull(this.getItem(key)) } static setItem(key, value) { return window.localStorage.setItem(key, value) } static remove(key) { return window.localStorage.removeItem(key) } } const Container = class { constructor() { this.remove = this.remove.bind(this) this.onFrameLoad = this.onFrameLoad.bind(this) this.supported = isFunction( document.createElement('main-userjs').attachShadow, ) this.ready = false if (this.supported) { this.frame = make('main-userjs', '', { dataset: { insertedBy: 'userscript-plus', role: 'primary-container', }, }) this.root = this.frame.attachShadow({ mode: 'open' }) this.ready = true } else { this.frame = make('iframe', 'mujs-iframe', { dataset: { insertedBy: 'userscript-plus', role: 'primary-iframe', }, loading: 'lazy', src: 'about:blank', style: 'position: fixed;bottom: 1rem;right: 1rem;height: 525px;width: 90%;margin: 0px 1rem;z-index: 100000000000000020 !important;', onload: this.onFrameLoad, }) } ael(window.self, 'beforeunload', this.remove) // info('Container:', this) } inject() { try { if (this.ready === false) { this.waitFor(this.ready === false).then(this.inject) } if (!document.body) { query('body').then(this.inject) return } document.body.appendChild(this.frame) } catch (ex) { err(ex) } } remove() { this.frame.remove() } async onReady(callback) { await this.waitFor(this.ready === false) if (isFunction(callback)) { callback(this.root) } } onFrameLoad(iFrame) { this.root = iFrame.target.contentDocument.documentElement this.ready = true this.root.classList.add('mujs-iframe') iFrame.target.contentDocument.body.classList.add('mujs-iframe') } async waitFor(obj) { while (obj) { await new Promise((resolve) => requestAnimationFrame(resolve)) } return true } } const container = new Container() const sleazyRedirect = () => { if (!/greasyfork\.org/.test(location.hostname) && cfg.sleazyredirect) { return } const otherSite = /greasyfork\.org/.test(location.hostname) ? 'sleazyfork' : 'greasyfork' qs('span.sign-in-link') ? /scripts\/\d+/.test(location.href) ? !qs('#script-info') && (otherSite == 'greasyfork' || qs('div.width-constraint>section>p>a')) ? location.assign( location.href.replace( /\/\/([^.]+\.)?(greasyfork|sleazyfork)\.org/, '//$1' + otherSite + '.org', ), ) : false : false : false } const main = (injCon) => { try { //#region Static Elements const table = make('table') const tabbody = make('tbody') const tabhead = make('thead') const main = make('mu-js', 'main hidden') const tbody = make('mu-js', 'mujs-body') const header = make('mu-js', 'mujs-header-prim') const cfgpage = make('mujs-row', 'mujs-cfg hidden') const countframe = make('mujs-column') const btnframe = make('mujs-column') const btnHandles = make('mujs-column', 'btn-handles') const gfcounter = make('count-frame', '', { title: 'https://greasyfork.org + https://sleazyfork.org', style: 'background: #00b7ff;', }) const sfcounter = make('count-frame', '', { title: 'https://openuserjs.org', style: 'background: #ed3f14;', }) const fsearch = make('mujs-btn', 'hidden') const ssearch = make('mujs-btn', 'hidden') const mainbtn = make('count-frame', 'mainbtn', { innerHTML: '0', }) const rateContainer = make('mujs-column', 'rate-container') const usercss = make('style', '', { dataset: { insertedBy: 'userscript-plus', role: 'primary-stylesheet', }, innerHTML: main_css, }) //#endregion const template = { bad_ratings: 0, good_ratings: 0, ok_ratings: 0, daily_installs: 0, total_installs: 0, name: 'NOT FOUND', description: 'NOT FOUND', version: '0.0.0', url: 'about:blank', code_url: 'about:blank', created_at: Date.now(), code_updated_at: Date.now(), users: [ { name: '', url: '', }, ], } const ContainerHandler = class { constructor() { this.cache = new Map() this.host = location.hostname.split('.').splice(-2).join('.') this.site = window.top.document.location.href this.unsaved = false this.isBlacklisted = false this.switchRows = true this.rebuild = false this.siteujs = [] this.forkCount = 0 this.customCount = 0 this.showError = this.showError.bind(this) this.cleanup = this.cleanup.bind(this) ael(window.self, 'beforeunload', this.cleanup) } checkBlacklist() { const blacklist = cfg.blacklist.filter((b) => b.enabled) for (const b of blacklist) { if (b.regex) { const reg = new RegExp(b.url, b.flags) const testurl = reg.test(this.site) if (!testurl) continue MUJS.isBlacklisted = true } if (!Array.isArray(b.url)) { if (!this.site.includes(b.url)) continue MUJS.isBlacklisted = true } for (const c of b.url) { if (!this.site.includes(c)) continue this.isBlacklisted = true } } if (this.isBlacklisted) { this.showError('Blacklisted') timeoutFrame() } return this.isBlacklisted } addCustomCnt(cnt) { this.customCount += cnt this.updateCounters() } addForkCnt(cnt) { this.forkCount += cnt this.updateCounters() } updateCounters() { sfcounter.innerHTML = this.customCount gfcounter.innerHTML = this.forkCount mainbtn.innerHTML = this.customCount + this.forkCount } save() { this.unsaved = false MU.setValue('Config', cfg) log('Saved:', cfg) } showError(ex) { err(ex) const txt = make('mujs-row', 'error', { innerHTML: `ERROR: ${typeof ex === 'string' ? ex : ex.message}`, }) tbody.prepend(txt) } refresh() { this.siteujs.length = 0 this.forkCount = 0 this.customCount = 0 this.updateCounters() tabbody.innerHTML = '' rateContainer.innerHTML = '' if (sh('.error')) { sh('.error').remove() } } cleanup() { this.cache.clear() } } const MUJS = new ContainerHandler() const timeout = new Timeout() const timeoutFrame = async () => { if (typeof cfg.time === 'number' && !isNaN(cfg.time)) { timeout.clear(...timeout.ids) await timeout.set(MUJS.isBlacklisted ? cfg.time / 2 : cfg.time) container.remove() return timeout.clear(...timeout.ids) } } const sh = (elem) => injCon.querySelector(elem) const shA = (elem) => injCon.querySelectorAll(elem) const sortRowBy = (cellIndex) => { const rows = normalizeTarget(tabbody.rows).sort((tr1, tr2) => { const t1cell = tr1.cells[cellIndex] const t2cell = tr2.cells[cellIndex] const tr1Text = (t1cell.firstElementChild ?? t1cell).textContent const tr2Text = (t2cell.firstElementChild ?? t2cell).textContent const t1pDate = Date.parse(tr1Text) const t2pDate = Date.parse(tr2Text) if (!Number.isNaN(t1pDate) && !Number.isNaN(t2pDate)) { return new Date(t1pDate) - new Date(t2pDate) } if (Number(tr1Text) && Number(tr2Text)) { return tr1Text - tr2Text } return tr1Text.localeCompare(tr2Text) }) if (MUJS.switchRows) { rows.reverse() } MUJS.switchRows = !MUJS.switchRows tabbody.append(...rows) } const createjs = (ujs, issleazy) => { for (const key in template) { if (!hasOwn(ujs, key)) { ujs[key] = template[key] } } const eframe = make('td', 'install-btn') const uframe = make('td', 'mujs-uframe') const fdaily = make('td', 'mujs-list', { innerHTML: ujs.daily_installs, }) const fupdated = make('td', 'mujs-list', { innerHTML: new Intl.DateTimeFormat(navigator.language).format( new Date(ujs.code_updated_at), ), }) const fname = make('td', 'mujs-name') const ftitle = make('mujs-a', 'mujs-homepage', { title: ujs.name, innerHTML: ujs.name, onclick(e) { halt(e) MU.openInTab(ujs.url) }, }) const fver = make('mu-js', 'mujs-list', { innerHTML: `${lang.version}: ${ujs.version}`, }) const fcreated = make('mu-js', 'mujs-list', { innerHTML: `${lang.created}: ${new Intl.DateTimeFormat( navigator.language, ).format(new Date(ujs.created_at))}`, }) const fmore = make('mujs-column', 'mujs-list hidden') const ftotal = make('mu-js', 'mujs-list', { innerHTML: `${lang.total}: ${ujs.total_installs}`, }) const fratings = make('mu-js', 'mujs-list', { title: lang.rating, innerHTML: `${lang.rating}:`, }) const fgood = make('mu-js', 'mujs-list mujs-ratings', { title: lang.good, innerHTML: ujs.good_ratings, style: 'border-color: rgb(51, 155, 51); background-color: #339b331a; color: rgb(51, 255, 51);', }) const fok = make('mu-js', 'mujs-list mujs-ratings', { title: lang.ok, innerHTML: ujs.ok_ratings, style: 'border-color: rgb(155, 155, 0); background-color: #9b9b001a; color: rgb(255, 255, 0);', }) const fbad = make('mu-js', 'mujs-list mujs-ratings', { title: lang.bad, innerHTML: ujs.bad_ratings, style: 'border-color: rgb(155, 0, 0); background-color: #9b33331a; color: rgb(255, 0, 0);', }) const fdesc = make('mu-js', 'mujs-list mujs-pointer', { title: ujs.description, innerHTML: ujs.description, onclick(e) { halt(e) if (fmore.classList.contains('hidden')) { fmore.classList.remove('hidden') } else { fmore.classList.add('hidden') } }, }) const fdwn = make('mu-jsbtn', 'install', { title: `${lang.install} { ${ujs.name} }`, innerHTML: `${iconSVG.install} ${lang.install}`, onclick(e) { halt(e) MU.openInTab(ujs.code_url) }, }) for (const u of ujs.users) { const user = make('mujs-a', 'mujs-euser', { innerHTML: u.name, onclick(e) { halt(e) MU.openInTab(u.url) }, }) uframe.append(user) } eframe.append(fdwn) fmore.append(ftotal, fratings, fgood, fok, fbad, fver, fcreated) fname.append(ftitle, fdesc, fmore) const tr = make('tr', `frame${issleazy ? ' sf' : ''}`) for (const e of [fname, uframe, fdaily, fupdated, eframe]) { tr.append(e) } tabbody.append(tr) } if (!isEmpty(navigator.languages)) { for (const nlang of navigator.languages) { const lg = nlang.split('-')[0] if (alang.indexOf(lg) === -1) { alang.push(lg) } } } const makerow = (desc, type, nm, attrs = {}) => { const sec = make('mujs-section', '', { style: !isGM && nm === 'cache' ? 'display: none;' : '', }) const lb = make('label') const divDesc = make('mu-js', '', { innerHTML: desc, }) lb.append(divDesc) sec.append(lb) cfgpage.append(sec) if (isNull(type)) { return sec } const inp = make('input', '', { type, id: nm, name: nm, ...attrs, }) if (type === 'checkbox') { const inlab = make('mu-js', 'mujs-inlab') const la = make('label', '', { onclick() { inp.dispatchEvent(new MouseEvent('click')) }, }) inlab.append(inp, la) lb.append(inlab) if (/(greasy|sleazy)fork|openuserjs|gi(thub|st)/gi.test(nm)) { for (const i of cfg.engines) { if (i.name === nm) { inp.checked = i.enabled ael(inp, 'change', (e) => { MUJS.unsaved = true i.enabled = e.target.checked }) } } } else { inp.checked = cfg[nm] ael(inp, 'change', (e) => { MUJS.unsaved = true if (/filterlang/i.test(nm)) { MUJS.rebuild = true } cfg[nm] = e.target.checked }) } } else { lb.append(inp) } return inp } //#region Build List const buildlist = async (host) => { try { if (isEmpty(host)) { host = MUJS.host } MUJS.refresh() if (MUJS.checkBlacklist()) return const template = {} for (const engine of cfg.engines) { template[engine.name] = [] } const engines = cfg.engines.filter((e) => e.enabled) if (isEmpty(MUJS.cache.get(host))) { MUJS.cache.set(host, template) } const cache = MUJS.cache.get(host) const customRecords = [] const rateFN = (data) => { try { for (const [key, value] of Object.entries( data.resources.code_search, )) { const txt = make('mujs-row', 'rate-info', { innerHTML: `${key.toLocaleUpperCase()}: ${value}`, }) rateContainer.append(txt) } } catch (ex) { MUJS.showError(ex) } } info('Building list', { cache, MUJS, engines }) if (!isNull(legacyMsg)) { const txt = make('mujs-row', 'legacy-config', { innerHTML: legacyMsg, }) rateContainer.append(txt) return } for (const engine of engines) { const forkFN = async (data) => { const hideData = [] const filterDeleted = data.filter((ujs) => !ujs.deleted) const filterLang = filterDeleted.filter((d) => { if (!cfg.filterlang) { return true } const dlocal = d.locale.split('-')[0] ?? d.locale if (alang.length > 1) { for (const a of alang) { if (dlocal.includes(a)) { return true } } } else if (dlocal.includes(navLang)) { return true } hideData.push(d) return false }) let finalList = filterLang if (!isBlank(hideData)) { const hds = [] for (const h of hideData) { const txt = await MU.fetchURL(h.code_url, 'GET', 'text') const headers = txt.match(/\/\/\s@[\w][\s\S]+/g) || [] if (headers.length > 0) { const regName = new RegExp(`// @name:${navLang}\\s+.+`, 'gi') const findName = (regName.exec(headers[0]) ?? []).join('') if (isEmpty(findName)) { continue } const cReg = new RegExp(`// @name:${navLang}\\s+`, 'gi') const cutName = findName.replace(cReg, '') Object.assign(h, { name: cutName, }) const regDesc = new RegExp( `// @description:${navLang}\\s+.+`, 'gi', ) const findDesc = (regDesc.exec(headers[0]) ?? []).join('') if (isEmpty(findDesc)) { continue } Object.assign(h, { description: findDesc.replace( new RegExp(`// @description:${navLang}\\s+`, 'gi'), '', ), }) hds.push(h) } } finalList = [...new Set([...hds, ...filterLang])] } for (const ujs of finalList) { MUJS.siteujs.push(ujs) createjs(ujs, false) } cache[engine.name].push(...finalList) MUJS.addForkCnt(finalList.length) } const customFN = async (data) => { const parser = new DOMParser() const htmlDocument = parser.parseFromString(data, 'text/html') const selected = htmlDocument.documentElement if (qs('.col-sm-8 .tr-link', selected)) { log('.col-sm-8 .tr-link', qsA('.col-sm-8 .tr-link', selected)) for (const i of qsA('.col-sm-8 .tr-link', selected)) { await query('.script-version', i) const fixurl = qs('.tr-link-a', i).href.replace( new RegExp(document.location.origin, 'gi'), 'https://openuserjs.org', ) const layout = { name: qs('.tr-link-a', i).textContent, description: qs('p', i).textContent, version: qs('.script-version', i).textContent, url: fixurl, code_url: `${fixurl.replace( /\/scripts/gi, '/install', )}.user.js`, total_installs: qs('td:nth-child(2) p', i).textContent, created_at: qs('td:nth-child(4) time', i).getAttribute( 'datetime', ), code_updated_at: qs('td:nth-child(4) time', i).getAttribute( 'datetime', ), users: [ { name: qs('.inline-block a', i).textContent, url: qs('.inline-block a', i).href, }, ], } createjs(layout, true) // MUJS.addCustomCnt(1) customRecords.push(layout) } } if (qs('div.gist-snippet', selected)) { log('div.gist-snippet', qsA('div.gist-snippet', selected)) for (const g of qsA('div.gist-snippet', selected)) { if ( qs('span > a:nth-child(2)', g).textContent.includes( '.user.js', ) ) { const fixurl = qs('span > a:nth-child(2)', g).href.replace( new RegExp(document.location.origin, 'gi'), 'https://gist.github.com', ) const layout = {} Object.assign(layout, { url: fixurl, code_url: `${fixurl}/raw/${ qs('span > a:nth-child(2)', g).textContent }`, created_at: qs('time-ago.no-wrap', g).getAttribute( 'datetime', ), users: [ { name: qs('span > a[data-hovercard-type]', g) .textContent, url: qs( 'span > a[data-hovercard-type]', g, ).href.replace( new RegExp(document.location.origin, 'gi'), 'https://gist.github.com', ), }, ], }) for (const i of qsA('.file-box table tr .blob-code', g)) { const txt = i.textContent const headers = txt.match(/\/\/\s@[\w][\s\S]+/gi) || [] if (headers.length > 0) { const crop = headers[0].split( /\/\/\s@(name|description|author|version)\s+/gi, ) if ( headers[0].includes('@name') && !headers[0].includes('@namespace') ) { Object.assign(layout, { name: crop[2].trim(), }) } if (headers[0].includes('@description')) { Object.assign(layout, { description: crop[2].trim(), }) } if (headers[0].includes('@version')) { Object.assign(layout, { version: crop[2].trim(), }) } } } createjs(layout, true) // MUJS.addCustomCnt(1) customRecords.push(layout) } } } cache[engine.name].push(...customRecords) MUJS.addCustomCnt(customRecords.length) } const gitFN = async (data) => { try { if (isBlank(data.items)) return for (const r of data.items) { const layout = { name: r.name, description: isEmpty(r.repository.description) ? 'No Description' : r.repository.description, url: r.html_url, code_url: r.html_url.replace(/\/blob\//g, '/raw/'), code_updated_at: Date.now(), // r.commit total_installs: r.score, users: [ { name: r.repository.owner.login, url: r.repository.owner.html_url, }, ], } createjs(layout, true) customRecords.push(layout) } MUJS.addCustomCnt(data.items.length) cache[engine.name].push(...customRecords) } catch (ex) { MUJS.showError(ex) } } const eURL = engine.url const cEngine = cache[`${engine.name}`] if (engine.name.match(/fork/gi)) { if (!isEmpty(cEngine)) { for (const ujs of cEngine) { createjs(ujs, false) } MUJS.addForkCnt(cEngine.length) continue } if (cfg.filterlang) { if (alang.length > 1) { for (const a of alang) { MU.fetchURL( `${eURL}/${a}/scripts/by-site/${host}.json?page=1`, ) .then(forkFN) .catch(MUJS.showError) } continue } MU.fetchURL( `${eURL}/${navLang}/scripts/by-site/${host}.json?page=1`, ) .then(forkFN) .catch(MUJS.showError) continue } MU.fetchURL(`${eURL}/scripts/by-site/${host}.json`) .then(forkFN) .catch(MUJS.showError) } else if (engine.name.match(/(openuserjs|github)/gi)) { if (!isEmpty(cEngine)) { for (const ujs of cEngine) { createjs(ujs, true) } MUJS.addCustomCnt(cEngine.length) continue } if (/github/gi.test(engine.name)) { MU.fetchURL( `${eURL}"// ==UserScript=="+${host}+ "// ==/UserScript=="+in:file+language:js&per_page=30`, 'GET', 'json', { headers: { Accept: 'application/vnd.github+json', Authorization: `Bearer ${engine.token}`, 'X-GitHub-Api-Version': '2022-11-28', }, }, ) .then(gitFN) .then(() => { MU.fetchURL( 'https://api.github.com/rate_limit', 'GET', 'json', { headers: { Accept: 'application/vnd.github+json', Authorization: `Bearer ${engine.token}`, 'X-GitHub-Api-Version': '2022-11-28', }, }, ) .then(rateFN) .catch(MUJS.showError) }) .catch(MUJS.showError) } else { MU.fetchURL(`${eURL}${host}`, 'GET', 'text') .then(customFN) .catch(MUJS.showError) } } } sortRowBy(2) } catch (ex) { MUJS.showError(ex) } } //#endregion //#region Make Config const makecfg = () => { makerow('Sync with GM', 'checkbox', 'cache') makerow('Auto Fullscreen', 'checkbox', 'autoexpand', { onchange(e) { if (e.target.checked) { btnfullscreen.classList.add('expanded') main.classList.add('expanded') btnfullscreen.innerHTML = iconSVG.fsClose } else { btnfullscreen.classList.remove('expanded') main.classList.remove('expanded') btnfullscreen.innerHTML = iconSVG.fsOpen } }, }) makerow(lang.redirect, 'checkbox', 'sleazyredirect') makerow(lang.filter, 'checkbox', 'filterlang') makerow('Greasy Fork', 'checkbox', 'greasyfork') makerow('Sleazy Fork', 'checkbox', 'sleazyfork') makerow('Open UserJS', 'checkbox', 'openuserjs') makerow('GitHub API', 'checkbox', 'github') const cfgAPI = cfg.engines.filter((c) => c.name === 'github')[0] makerow('GitHub API (Token)', 'password', 'github', { defaultValue: '', value: cfgAPI.token ?? '', placeholder: 'Paste Access Token', onchange(e) { MUJS.unsaved = true MUJS.rebuild = true if (isNull(legacyMsg)) { cfgAPI.token = e.target.value } }, }) makerow(`${lang.dtime} (ms)`, 'number', 'time', { defaultValue: 10000, value: cfg.time, min: 0, step: 500, onbeforeinput(e) { if (e.target.validity.badInput) { dom.cl.add(e.target, 'mujs-invalid') dom.prop(savebtn, 'disabled', true) } else { dom.cl.remove(e.target, 'mujs-invalid') dom.prop(savebtn, 'disabled', false) } }, oninput(e) { MUJS.unsaved = true const t = e.target if ( t.validity.badInput || (t.validity.rangeUnderflow && t.value !== '-1') ) { dom.cl.add(t, 'mujs-invalid') dom.prop(savebtn, 'disabled', true) } else { dom.cl.remove(t, 'mujs-invalid') dom.prop(savebtn, 'disabled', false) cfg.time = isEmpty(t.value) ? cfg.time : parseFloat(t.value) } }, }) const cbtn = make('mu-js', 'mujs-sty-flex') const savebtn = make('mujs-btn', 'save', { disabled: false, innerHTML: lang.save, onclick(e) { halt(e) if (sh('.saveerror')) { sh('.saveerror').remove() } if (!isNull(legacyMsg)) { legacyMsg = null MUJS.rebuild = true rateContainer.innerHTML = '' } if (!dom.prop(e.target, 'disabled')) { MUJS.save() sleazyRedirect() if (MUJS.rebuild) { MUJS.cache.clear() buildlist() } MUJS.unsaved = false MUJS.rebuild = false } }, }) const txta = make('textarea', 'tarea', { name: 'blacklist', id: 'blacklist', rows: '10', autocomplete: false, spellcheck: false, wrap: 'soft', value: JSON.stringify(cfg.blacklist, null, ' '), oninput(e) { let isvalid = true try { cfg.blacklist = JSON.parse(e.target.value) isvalid = true } catch (ex) { err(ex) isvalid = false } finally { if (isvalid) { dom.cl.remove(e.target, 'mujs-invalid') dom.prop(savebtn, 'disabled', false) } else { dom.cl.add(e.target, 'mujs-invalid') dom.prop(savebtn, 'disabled', true) } } }, }) const resetbtn = make('mujs-btn', 'reset', { innerHTML: 'Reset', onclick(e) { halt(e) cfg = defcfg MUJS.unsaved = true txta.value = JSON.stringify(cfg.blacklist, null, ' ') for (const i of cfg.engines) { if (sh(`[id="${i.name}"]`)) { sh(`[id="${i.name}"]`).checked = i.enabled } } for (const i of shA('.mujs-inlab input[type="checkbox"]')) { if ( !i.name.match(/((greasy|sleazy)fork|openuserjs|gi(thub|st))/gi) ) { i.checked = cfg[i.name] } } }, }) cbtn.append(savebtn, resetbtn) cfgpage.append(txta, cbtn) } //#endregion const makeTHead = (rows) => { const tr = make('tr') for (const r of normalizeTarget(rows)) { const tparent = make('th', r.class ?? '', r) tr.append(tparent) } tabhead.append(tr) table.append(tabhead, tabbody) } const btnHide = make('mujs-btn', 'hide-list', { title: lang.min, innerHTML: iconSVG.hide, onclick(e) { halt(e) main.classList.add('hidden') mainframe.classList.remove('hidden') timeoutFrame() }, }) const btnfullscreen = make('mujs-btn', 'fullscreen', { title: lang.max, innerHTML: iconSVG.fullscreen, onclick(e) { halt(e) if (btnfullscreen.classList.contains('expanded')) { btnfullscreen.classList.remove('expanded') main.classList.remove('expanded') btnfullscreen.innerHTML = iconSVG.fsOpen return } btnfullscreen.classList.add('expanded') main.classList.add('expanded') btnfullscreen.innerHTML = iconSVG.fsClose }, }) const mainframe = make('mu-js', 'mainframe', { onclick(e) { e.preventDefault() timeout.clear(...timeout.ids) main.classList.remove('hidden') mainframe.classList.add('hidden') if (cfg.autoexpand) { btnfullscreen.classList.add('expanded') main.classList.add('expanded') btnfullscreen.innerHTML = iconSVG.fsClose } }, }) const filterList = make('input', 'mujs-fltlist', { autocomplete: 'off', spellcheck: false, type: 'text', placeholder: lang.searcher, oninput(e) { e.preventDefault() if (!isEmpty(e.target.value)) { const reg = new RegExp(e.target.value, 'gi') for (const ujs of shA('.frame')) { const m = ujs.children[0] const n = ujs.children[1] const final = m.textContent.match(reg) || n.textContent.match(reg) || [] if (final.length === 0) { ujs.classList.add('hidden') } else { ujs.classList.remove('hidden') } } } else { for (const ujs of shA('.frame')) { ujs.classList.remove('hidden') } } }, }) const filterBtn = make('mujs-btn', 'filter', { title: lang.filterA, innerHTML: iconSVG.filter, onclick(e) { e.preventDefault() fsearch.classList.toggle('hidden') }, }) const siteSearcher = make('input', 'mujs-searcher', { autocomplete: 'off', spellcheck: false, type: 'text', placeholder: MUJS.host, onchange(e) { e.preventDefault() buildlist(e.target.value) }, }) const siteSearchbtn = make('mujs-btn', 'search', { title: lang.search, innerHTML: iconSVG.search, onclick(e) { e.preventDefault() ssearch.classList.toggle('hidden') }, }) const closebtn = make('mujs-btn', 'close', { title: lang.close, innerHTML: iconSVG.close, onclick: async (e) => { halt(e) container.remove() }, }) const btncfg = make('mujs-btn', 'settings', { title: 'Settings', innerHTML: iconSVG.cfg, onclick(e) { e.preventDefault() if (MUJS.unsaved && !sh('.saveerror')) { const txt = make('mujs-row', 'saveerror', { innerHTML: 'Unsaved changes', }) countframe.insertAdjacentHTML('afterend', txt.outerHTML.toString()) } if (dom.cl.has(cfgpage, 'hidden')) { dom.cl.remove(cfgpage, 'hidden') dom.cl.add(tbody, 'hidden') dom.cl.add(main, 'auto-height') if (!container.supported) { dom.attr(container.frame, 'style', 'height: 100%;') } } else { dom.cl.add(cfgpage, 'hidden') dom.cl.remove(tbody, 'hidden') dom.cl.remove(main, 'auto-height') main.classList.remove('auto-height') if (!container.supported) { dom.attr(container.frame, 'style', '') } } MUJS.rebuild = false }, }) const btnhome = make('mujs-btn', 'github hidden', { title: `GitHub (v${ MU.info.script.version.includes('.') || MU.info.script.version.includes('Book') ? MU.info.script.version : MU.info.script.version.slice(0, 5) })`, innerHTML: iconSVG.gh, onclick(e) { halt(e) MU.openInTab('https://github.com/magicoflolis/Userscript-Plus') }, }) const btnissue = make('mujs-btn', 'issue hidden', { title: lang.issue, innerHTML: iconSVG.issue, onclick(e) { e.preventDefault() MU.openInTab( 'https://github.com/magicoflolis/Userscript-Plus/issues/new', ) }, }) const btngreasy = make('mujs-btn', 'greasy hidden', { title: 'Greasy Fork', innerHTML: iconSVG.gf, onclick(e) { e.preventDefault() MU.openInTab('https://greasyfork.org/scripts/421603') }, }) const btnnav = make('mujs-btn', 'nav', { title: 'Navigation', innerHTML: iconSVG.nav, onclick(e) { halt(e) if (btngreasy.classList.contains('hidden')) { btnissue.classList.remove('hidden') btnhome.classList.remove('hidden') btngreasy.classList.remove('hidden') } else { btnissue.classList.add('hidden') btnhome.classList.add('hidden') btngreasy.classList.add('hidden') } }, }) countframe.append(gfcounter, sfcounter) fsearch.append(filterList) ssearch.append(siteSearcher) btnHandles.append(btnHide, btnfullscreen, closebtn) btnframe.append( fsearch, filterBtn, ssearch, siteSearchbtn, btncfg, btnissue, btnhome, btngreasy, btnnav, btnHandles, ) header.append(countframe, rateContainer, btnframe) tbody.append(table) makeTHead([ { class: 'mujs-header-name', textContent: lang.name, }, { textContent: lang.createdby, }, { textContent: lang.daily, }, { textContent: lang.updated, }, { textContent: lang.install, }, ]) for (const th of tabhead.rows[0].cells) { if (dom.text(th) === lang.install) continue dom.cl.add(th, 'mujs-pointer') ael(th, 'click', () => { sortRowBy(th.cellIndex) }) } main.append(header, tbody, cfgpage) mainframe.append(mainbtn) const mujsRoot = make('mujs-root') mujsRoot.append(usercss, mainframe, main) injCon.append(mujsRoot) makecfg() buildlist() timeoutFrame() } catch (ex) { err(ex) } } const onDomReady = () => { try { container.inject() // Remove legacy config storage if (isGM) { if (MU.storage.has('MUJSConfig')) { MU.storage.remove('MUJSConfig') } } cfg = MU.getValue('Config', defcfg) const setConfig = (config) => { for (const key in config) { if (typeof config[key] === 'object') { setConfig(cfg[key], config[key]) } else if (!hasOwn(cfg, key)) { cfg[key] = defcfg[key] } } } setConfig(defcfg) sleazyRedirect() lang = langs[cfg.language] || langs[navLang] || langs.en // Remove legacy engines const engines = cfg.engines.filter((c) => c.name === 'gist') for (const engine of engines) { if (isNull(engine.token)) { if (isGM) { legacyMsg = lang.legacy } else { MU.setValue('Config', defcfg) cfg = defcfg } } } log('Config:', cfg) container.onReady(main) } catch (ex) { err(ex) } } if (typeof userjs === 'object' && userjs.UserJS && window.self === window.top) { const readyState = document.readyState if (readyState === 'interactive' || readyState === 'complete') { onDomReady() } else { document.addEventListener('DOMContentLoaded', onDomReady, { once: true }) } } })();