您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
在GF脚本页直接编辑收藏集
当前为
/* eslint-disable no-multi-spaces */ /* eslint-disable no-return-assign */ // ==UserScript== // @name Greasyfork script-set-edit button // @name:zh-CN Greasyfork 快捷编辑收藏 // @name:zh-TW Greasyfork 快捷編輯收藏 // @name:en Greasyfork script-set-edit button // @name:en-US Greasyfork script-set-edit button // @name:fr Greasyfork Set Edit+ // @namespace Greasyfork-Favorite // @version 0.2.4.2 // @description Add / Remove script into / from script set directly in GF script info page // @description:zh-CN 在GF脚本页直接编辑收藏集 // @description:zh-TW 在GF腳本頁直接編輯收藏集 // @description:en Add / Remove script into / from script set directly in GF script info page // @description:en-US Add / Remove script into / from script set directly in GF script info page // @description:fr Ajouter un script à un jeu de scripts / supprimer un script d'un jeu de scripts directement sur la page d'informations sur les scripts GF // @author PY-DNG // @license GPL-3.0-or-later // @match http*://*.greasyfork.org/* // @match http*://*.sleazyfork.org/* // @match http*://greasyfork.org/* // @match http*://sleazyfork.org/* // @require https://update.greasyfork.org/scripts/456034/1303041/Basic%20Functions%20%28For%20userscripts%29.js // @require https://update.greasyfork.org/scripts/449583/1324274/ConfigManager.js // @require https://greasyfork.org/scripts/460385-gm-web-hooks/code/script.js?version=1221394 // @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAbBJREFUOE+Vk7GKGlEUhr8pAiKKDlqpCDpLUCzWBxCENBa+hBsL9wHsLWxXG4tNtcGH0MIiWopY7JSGEUWsbESwUDMw4Z7siLsZDbnlPff/7n/+e67G38sA6sAXIPVWXgA/gCdgfinRPuhfCoXCw3Q65XA4eLBl6zvw1S2eAZqmvTqOc5/NZhkMBqRSKWzbvgYxgbwquoAX4MGyLHK5HIlEgtFo9C+IOFEAo1gsWsvlUmyPx2MymYxAhsMh6XT6lpM7BXjWdf1xNpuRz+fl8GQywTAMGo0G1WpVnJxOJ692vinADPgcDAaZz+cCOR6PmKZJPB4XUb/fp1wuewF+KoBCf1JVBVE5dDodms3mWdDtdqlUKl6AX+8ALmS9XgtM0/5kvNlspKX9fv8RIgBp4bISCoXo9XqsVitKpRK6rrPb7STQ7XZ7eVRaeAYerz14OBxGOfL7/eIgmUwKzHEcJZEQ1eha1wBqPxqNihufzyeQWCzmtiPPqJYM0jWIyiISibBYLAgEAtTrdVqt1nmQXN0rcH/LicqmVqvRbrdN27bfjbKru+nk7ZD3Z7q4+b++82/YPKIrXsKZ3AAAAABJRU5ErkJggg== // @grant GM_xmlhttpRequest // @grant GM_setValue // @grant GM_getValue // @grant GM_listValues // @grant GM_deleteValue // ==/UserScript== /* global LogLevel DoLog Err $ $All $CrE $AEL $$CrE addStyle detectDom destroyEvent copyProp copyProps parseArgs escJsStr replaceText getUrlArgv dl_browser dl_GM AsyncManager */ /* global GMXHRHook GMDLHook ConfigManager */ const GFScriptSetAPI = (function() { const API = { async getScriptSets() { const userpage = API.getUserpage(); const oDom = await API.getDocument(userpage); const script_sets = Array.from($(oDom, 'ul#user-script-sets').children).map(li => { try { return { name: li.children[0].innerText, link: li.children[0].href, linkedit: li.children[1].href, id: getUrlArgv(li.children[0].href, 'set') } } catch(err) { DoLog(LogLevel.Error, [li, err, li.children.length, li.children[0]?.innerHTML, li.children[1]?.innerHTML], 'error'); Err(err); } }); return script_sets; }, async getSetScripts(url) { return [...$All(await API.getDocument(url), '#script-set-scripts>input[name="scripts-included[]"]')].map(input => input.value); }, getUserpage() { const a = $('#nav-user-info>.user-profile-link>a'); return a ? a.href : null; }, // editCallback recieves: // true: edit doc load success // false: already in set // finishCallback recieves: // text: successfully added to set with text tip `text` // true: successfully loaded document but no text tip found // false: xhr error addFav(url, sid, editCallback, finishCallback) { API.modifyFav(url, oDom => { const existingInput = [...$All(oDom, '#script-set-scripts>input[name="scripts-included[]"][type="hidden"]')].find(input => input.value === sid); if (existingInput) { editCallback(false); return false; } const input = $CrE('input'); input.value = sid; input.name = 'scripts-included[]'; input.type = 'hidden'; $(oDom, '#script-set-scripts').appendChild(input); editCallback(true); }, oDom => { const status = $(oDom, 'p.notice'); const status_text = status ? status.innerText : true; finishCallback(status_text); }, err => finishCallback(false)); }, // editCallback recieves: // true: edit doc load success // false: already not in set // finishCallback recieves: // text: successfully removed from set with text tip `text` // true: successfully loaded document but no text tip found // false: xhr error removeFav(url, sid, editCallback, finishCallback) { API.modifyFav(url, oDom => { const existingInput = [...$All(oDom, '#script-set-scripts>input[name="scripts-included[]"][type="hidden"]')].find(input => input.value === sid); if (!existingInput) { editCallback(false); return false; } existingInput.remove(); editCallback(true); }, oDom => { const status = $(oDom, 'p.notice'); const status_text = status ? status.innerText : true; finishCallback(status_text); }, err => finishCallback(false)); }, async modifyFav(url, editCallback, finishCallback, onerror) { const oDom = await API.getDocument(url); if (editCallback(oDom) === false) { return false; } const form = $(oDom, '.change-script-set'); const data = new FormData(form); data.append('save', '1'); // Use XMLHttpRequest insteadof GM_xmlhttpRequest because there's unknown issue with GM_xmlhttpRequest // Use XMLHttpRequest insteadof GM_xmlhttpRequest before Tampermonkey 5.0.0 because of FormData posting issues if (true || GM_info.scriptHandler === 'Tampermonkey' && !API.GM_hasVersion('5.0')) { const xhr = new XMLHttpRequest(); xhr.open('POST', API.toAbsoluteURL(form.getAttribute('action'))); xhr.responseType = 'blob'; xhr.onload = async e => finishCallback(await API.parseDocument(xhr.response)); xhr.onerror = onerror; xhr.send(data); } else { GM_xmlhttpRequest({ method: 'POST', url: API.toAbsoluteURL(form.getAttribute('action')), data, responseType: 'blob', onload: async response => finishCallback(await API.parseDocument(response.response)), onerror }); } }, // Download and parse a url page into a html document(dom). // Returns a promise fulfills with dom getDocument(url, retry=5) { return new Promise((resolve, reject) => { GM_xmlhttpRequest({ method : 'GET', url : url, responseType : 'blob', onload : function(response) { if (response.status === 200) { const htmlblob = response.response; API.parseDocument(htmlblob).then(resolve).catch(reject); } else { re(response); } }, onerror: err => re(err) }); function re(err) { DoLog(`Get document failed, retrying: (${retry}) ${url}`); --retry > 0 ? API.getDocument(url, retry).then(resolve).catch(reject) : reject(err); } }); }, // Returns a promise fulfills with dom parseDocument(htmlblob) { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = function(e) { const htmlText = reader.result; const dom = new DOMParser().parseFromString(htmlText, 'text/html'); resolve(dom); } reader.onerror = err => reject(err); reader.readAsText(htmlblob, document.characterSet); }); }, toAbsoluteURL(relativeURL, base=`${location.protocol}//${location.host}/`) { return new URL(relativeURL, base).href; }, GM_hasVersion(version) { return hasVersion(GM_info?.version || '0', version); function hasVersion(ver1, ver2) { return compareVersions(ver1.toString(), ver2.toString()) >= 0; // https://greasyfork.org/app/javascript/versioncheck.js // https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/version/format function compareVersions(a, b) { if (a == b) { return 0; } let aParts = a.split('.'); let bParts = b.split('.'); for (let i = 0; i < aParts.length; i++) { let result = compareVersionPart(aParts[i], bParts[i]); if (result != 0) { return result; } } // If all of a's parts are the same as b's parts, but b has additional parts, b is greater. if (bParts.length > aParts.length) { return -1; } return 0; } function compareVersionPart(partA, partB) { let partAParts = parseVersionPart(partA); let partBParts = parseVersionPart(partB); for (let i = 0; i < partAParts.length; i++) { // "A string-part that exists is always less than a string-part that doesn't exist" if (partAParts[i].length > 0 && partBParts[i].length == 0) { return -1; } if (partAParts[i].length == 0 && partBParts[i].length > 0) { return 1; } if (partAParts[i] > partBParts[i]) { return 1; } if (partAParts[i] < partBParts[i]) { return -1; } } return 0; } // It goes number, string, number, string. If it doesn't exist, then // 0 for numbers, empty string for strings. function parseVersionPart(part) { if (!part) { return [0, "", 0, ""]; } let partParts = /([0-9]*)([^0-9]*)([0-9]*)([^0-9]*)/.exec(part) return [ partParts[1] ? parseInt(partParts[1]) : 0, partParts[2], partParts[3] ? parseInt(partParts[3]) : 0, partParts[4] ]; } } } }; return API; }) (); (function __MAIN__() { 'use strict'; const CONST = { Text: { 'zh-CN': { FavEdit: '收藏集:', Add: '加入此集', Remove: '移出此集', Edit: '手动编辑', EditIframe: '页内编辑', CloseIframe: '关闭编辑', CopySID: '复制脚本ID', Sync: '同步', Working: ['工作中...', '就快好了...'], InSetStatus: ['[ ]', '[✔]'], Refreshing: { List: '获取收藏集列表...', Script: '获取收藏集内容...' }, Error: { AlreadyExist: '脚本已经在此收藏集中了', NotExist: '脚本不在此收藏集中', NetworkError: '网络错误', Unknown: '未知错误' } }, 'zh-TW': { FavEdit: '收藏集:', Add: '加入此集', Remove: '移出此集', Edit: '手動編輯', EditIframe: '頁內編輯', CloseIframe: '關閉編輯', CopySID: '複製腳本ID', Sync: '同步', Working: ['工作中...', '就快好了...'], InSetStatus: ['[ ]', '[✔]'], Refreshing: { List: '獲取收藏集清單...', Script: '獲取收藏集內容...' }, Error: { AlreadyExist: '腳本已經在此收藏集中了', NotExist: '腳本不在此收藏集中', NetworkError: '網絡錯誤', Unknown: '未知錯誤' } }, 'en': { FavEdit: 'Script set: ', Add: 'Add', Remove: 'Remove', Edit: 'Edit Manually', EditIframe: 'In-Page Edit', CloseIframe: 'Close Editor', CopySID: 'Copy Script-ID', Sync: 'Sync', Working: ['Working...', 'Just a moment...'], InSetStatus: ['[ ]', '[✔]'], Refreshing: { List: 'Fetching script sets...', Script: 'Fetching set content...' }, Error: { AlreadyExist: 'Script is already in set', NotExist: 'Script is not in set yet', NetworkError: 'Network Error', Unknown: 'Unknown Error' } }, 'default': { FavEdit: 'Script set: ', Add: 'Add', Remove: 'Remove', Edit: 'Edit Manually', EditIframe: 'In-Page Edit', CloseIframe: 'Close Editor', CopySID: 'Copy Script-ID', Sync: 'Sync', Working: ['Working...', 'Just a moment...'], InSetStatus: ['[ ]', '[✔]'], Refreshing: { List: 'Fetching script sets...', Script: 'Fetching set content...' }, Error: { AlreadyExist: 'Script is already in set', NotExist: 'Script is not in set yet', NetworkError: 'Network Error', Unknown: 'Unknown Error' } }, }, ConfigRule: { 'version-key': 'config-version', ignores: [], defaultValues: { 'script-sets': { 'config-version': 1, }, }, 'updaters': { /*'config-key': [ function() { // This function contains updater for config['config-key'] from v0 to v1 }, function() { // This function contains updater for config['config-key'] from v1 to v2 } ]*/ 'script-sets': [ config => { // Fill set.id const sets = config.sets; sets.forEach(set => { const id = getUrlArgv(set.link, 'set'); set.id = id; set.scripts = null; // After first refresh, it should be an array of SIDs:string }); // Delete old version identifier delete config.version; return config; } ] }, } } // Get i18n code let i18n = $('#language-selector-locale') ? $('#language-selector-locale').value : navigator.language; if (!Object.keys(CONST.Text).includes(i18n)) {i18n = 'default';} const CM = new ConfigManager(CONST.ConfigRule); const CONFIG = CM.Config; CM.updateAllConfigs(); loadFuncs([{ name: 'Hook GM_xmlhttpRequest', checker: { type: 'switch', value: true }, func: () => GMXHRHook(5) }, { name: 'Favorite panel', checker: { type: 'func', value: () => { const path = location.pathname.split('/').filter(p=>p); const index = path.indexOf('scripts'); return [0,1].includes(index) && [undefined, 'code', 'feedback'].includes(path[index+2]) } }, func: addFavPanel }]); function addFavPanel() { if (!GFScriptSetAPI.getUserpage()) {return false;} class FavoritePanel { #CM; #sid; #sets; #elements; constructor(CM) { this.#CM = CM; this.#sid = location.pathname.match(/scripts\/(\d+)/)[1]; this.#sets = this.#CM.getConfig('script-sets').sets; this.#elements = {}; const script_after = $('#script-feedback-suggestion+*') || $('#new-script-discussion'); const script_parent = script_after.parentElement; // Container const script_favorite = this.#elements.container = $$CrE({ tagName: 'div', props: { id: 'script-favorite', innerHTML: CONST.Text[i18n].FavEdit }, styles: { margin: '0.75em 0' } }); // Selecter const favorite_groups = this.#elements.groups = $$CrE({ tagName: 'select', props: { id: 'favorite-groups' }, styles: { maxWidth: '40vw' }, listeners: [['change', e => { const set = this.#sets.find(set => set.id === favorite_groups.value); favorite_edit.href = set.linkedit; this.#refreshButtonDisplay(); }]] }); favorite_groups.id = 'favorite-groups'; // Buttons const makeBtn = (id, innerHTML, onClick, isLink=false) => $$CrE({ tagName: 'a', props: { id, innerHTML, [isLink ? 'target' : 'href']: isLink ? '_blank' : 'javascript:void(0);' }, styles: { margin: '0px 0.5em' }, listeners: [['click', onClick]] }); const favorite_add = this.#elements.btnAdd = makeBtn('favorite-add', CONST.Text[i18n].Add, e => this.#addFav()); const favorite_remove = this.#elements.btnRemove = makeBtn('favorite-remove', CONST.Text[i18n].Remove, e => this.#removeFav()); const favorite_edit = this.#elements.btnEdit = makeBtn('favorite-edit', CONST.Text[i18n].Edit, e => {}, true); const favorite_iframe = this.#elements.btnIframe = makeBtn('favorite-edit-in-page', CONST.Text[i18n].EditIframe, e => this.#editInPage(e)); const favorite_copy = this.#elements.btnCopy = makeBtn('favorite-add', CONST.Text[i18n].CopySID, e => copyText(this.#sid)); const favorite_sync = this.#elements.btnSync = makeBtn('favorite-sync', CONST.Text[i18n].Sync, e => this.#refresh()); script_favorite.appendChild(favorite_groups); script_after.before(script_favorite); [favorite_add, favorite_remove, favorite_edit, favorite_iframe, favorite_copy, favorite_sync].forEach(button => script_favorite.appendChild(button)); // Text tip const tip = this.#elements.tip = $CrE('span'); script_favorite.appendChild(tip); // Display cached sets first this.#displaySets(); // Request GF document to update sets this.#refresh(); } get sid() { return this.#sid; } get sets() { return FavoritePanel.#deepClone(this.#sets); } get elements() { return FavoritePanel.#lightClone(this.#elements); } // Request document: get sets list and async #refresh() { this.#disable(); this.#tip(CONST.Text[i18n].Refreshing.List); // Refresh sets list this.#sets = CONFIG['script-sets'].sets = await GFScriptSetAPI.getScriptSets(); this.#displaySets(); // Refresh each set's script list this.#tip(CONST.Text[i18n].Refreshing.Script); await Promise.all(this.#sets.map(async set => { // Fetch scripts set.scripts = await GFScriptSetAPI.getSetScripts(set.linkedit); this.#displaySets(); // Save to GM_storage const setIndex = CONFIG['script-sets'].sets.findIndex(s => s.id === set.id); CONFIG['script-sets'].sets[setIndex].scripts = set.scripts; })); this.#tip(); this.#enable(); } #addFav() { const set = this.#getCurrentSet(); const option = set.elmOption; this.#displayNotice(CONST.Text[i18n].Working[0]); GFScriptSetAPI.addFav(this.#getCurrentSet().linkedit, this.#sid, editStatus => { if (!editStatus) { this.#displayNotice(CONST.Text[i18n].Error.AlreadyExist); option.innerText = `${CONST.Text[i18n].InSetStatus[1]} ${set.name}`; } else { this.#displayNotice(CONST.Text[i18n].Working[1]); } }, finishStatus => { if (finishStatus) { // Save to this.#sets and GM_storage const setIndex = CONFIG['script-sets'].sets.findIndex(s => s.id === set.id); CONFIG['script-sets'].sets[setIndex].scripts.push(this.#sid); this.#sets = CM.getConfig('script-sets').sets; // Display this.#displayNotice(typeof finishStatus === 'string' ? finishStatus : CONST.Text[i18n].Error.Unknown); set.elmOption.innerText = `${CONST.Text[i18n].InSetStatus[1]} ${set.name}`; this.#displaySets(); } else { this.#displayNotice(CONST.Text[i18n].Error.NetworkError); } }); } #removeFav() { const set = this.#getCurrentSet(); const option = set.elmOption; this.#displayNotice(CONST.Text[i18n].Working[0]); GFScriptSetAPI.removeFav(this.#getCurrentSet().linkedit, this.#sid, editStatus => { if (!editStatus) { this.#displayNotice(CONST.Text[i18n].Error.NotExist); option.innerText = `${CONST.Text[i18n].InSetStatus[0]} ${set.name}`; } else { this.#displayNotice(CONST.Text[i18n].Working[1]); } }, finishStatus => { if (finishStatus) { // Save to this.#sets and GM_storage const setIndex = CONFIG['script-sets'].sets.findIndex(s => s.id === set.id); const scriptIndex = CONFIG['script-sets'].sets[setIndex].scripts.indexOf(this.#sid); CONFIG['script-sets'].sets[setIndex].scripts.splice(scriptIndex, 1); this.#sets = CM.getConfig('script-sets').sets; // Display this.#displayNotice(typeof finishStatus === 'string' ? finishStatus : CONST.Text[i18n].Error.Unknown); set.elmOption.innerText = `${CONST.Text[i18n].InSetStatus[0]} ${set.name}`; this.#displaySets(); } else { this.#displayNotice(CONST.Text[i18n].Error.NetworkError); } }); } #editInPage(e) { e.preventDefault(); const _iframes = [...$All(this.#elements.container, '.script-edit-page')]; if (_iframes.length) { // Iframe exists, close iframe this.#elements.btnIframe.innerText = CONST.Text[i18n].EditIframe; _iframes.forEach(ifr => ifr.remove()); this.#refresh(); } else { // Iframe not exist, make iframe this.#elements.btnIframe.innerText = CONST.Text[i18n].CloseIframe; const iframe = $$CrE({ tagName: 'iframe', props: { src: this.#getCurrentSet().linkedit }, styles: { width: '100%', height: '60vh' }, classes: ['script-edit-page'], listeners: [['load', e => { //this.#refresh(); //iframe.style.height = iframe.contentDocument.body.parentElement.offsetHeight + 'px'; }]] }); this.#elements.container.appendChild(iframe); } } #displayNotice(text) { const notice = $CrE('p'); notice.classList.add('notice'); notice.id = 'fav-notice'; notice.innerText = text; const old_notice = $('#fav-notice'); old_notice && old_notice.parentElement.removeChild(old_notice); $('#script-content').insertAdjacentElement('afterbegin', notice); } #tip(text='', timeout=0) { this.#elements.tip.innerText = text; timeout > 0 && setTimeout(() => this.#elements.tip.innerText = '', timeout); } // Apply this.#sets to gui #displaySets() { // Save selected set const old_value = this.#elements.groups.value; [...this.#elements.groups.children].forEach(child => child.remove()); // Make <option>s this.#sets.forEach(set => { // Create <option> set.elmOption = $$CrE({ tagName: 'option', props: { innerText: set.name, value: set.id } }); // Display inset status if (set.scripts) { const inSet = set.scripts.includes(this.#sid); set.elmOption.innerText = `${CONST.Text[i18n].InSetStatus[inSet+0]} ${set.name}`; } // Append <option> into <select> this.#elements.groups.appendChild(set.elmOption); }); // Adjust <select> width this.#elements.groups.style.width = Math.max.apply(null, Array.from(this.#elements.groups.children).map(o => o.innerText.length)).toString() + 'em'; // Select previous selected set's <option> const selected = old_value ? [...this.#elements.groups.children].find(option => option.value === old_value) : null; selected && (selected.selected = true); // Set edit-button.href const curset = this.#sets.find(set => set.id === this.#elements.groups.value); this.#elements.btnEdit.href = curset.linkedit; // Display correct button this.#refreshButtonDisplay(); } // Display only add button when script in current set, otherwise remove button #refreshButtonDisplay() { const set = this.#getCurrentSet(); if (!set?.scripts) { return null; } if (set.scripts.includes(this.#sid)) { this.#elements.btnAdd.style.setProperty('display', 'none'); this.#elements.btnRemove.style.removeProperty('display'); return true; } else { this.#elements.btnRemove.style.setProperty('display', 'none'); this.#elements.btnAdd.style.removeProperty('display'); return false; } } // Returns null if no <option>s yet #getCurrentSet() { return this.#sets.find(set => set.id === this.#elements.groups.value) || null; } #disable() { [ this.#elements.groups, this.#elements.btnAdd, this.#elements.btnRemove, this.#elements.btnEdit, this.#elements.btnIframe, this.#elements.btnCopy, this.#elements.btnSync ].forEach(element => FavoritePanel.#disableElement(element)); } #enable() { [ this.#elements.groups, this.#elements.btnAdd, this.#elements.btnRemove, this.#elements.btnEdit, this.#elements.btnIframe, this.#elements.btnCopy, this.#elements.btnSync ].forEach(element => FavoritePanel.#enableElement(element)); } static #disableElement(element) { element.style.filter = 'grayscale(1) brightness(0.95)'; element.style.opacity = '0.25'; element.style.pointerEvents = 'none'; element.tabIndex = -1; } static #enableElement(element) { element.style.removeProperty('filter'); element.style.removeProperty('opacity'); element.style.removeProperty('pointer-events'); element.tabIndex = 0; } static #deepClone(val) { if (typeof structuredClone === 'function') { return structuredClone(val); } else { return JSON.parse(JSON.stringify(val)); } } static #lightClone(val) { if (['string', 'number', 'boolean', 'undefined', 'bigint', 'symbol', 'function'].includes(val) || val === null) { return val; } if (Array.isArray(val)) { return val.slice(); } if (typeof val === 'object') { return Object.fromEntries(Object.entries(val)); } } } const panel = new FavoritePanel(CM); } // Basic functions // Copy text to clipboard (needs to be called in an user event) function copyText(text) { // Create a new textarea for copying const newInput = document.createElement('textarea'); document.body.appendChild(newInput); newInput.value = text; newInput.select(); document.execCommand('copy'); document.body.removeChild(newInput); } // Check whether current page url matches FuncInfo.checker rule // This code is copy and modified from FunctionLoader.check function testChecker(checker) { if (!checker) {return true;} const values = Array.isArray(checker.value) ? checker.value : [checker.value] return values.some(value => { switch (checker.type) { case 'regurl': { return !!location.href.match(value); } case 'func': { try { return value(); } catch (err) { DoLog(LogLevel.Error, CONST.Text.Loader.CheckerError); DoLog(LogLevel.Error, err); return false; } } case 'switch': { return value; } case 'starturl': { return location.href.startsWith(value); } case 'startpath': { return location.pathname.startsWith(value); } default: { DoLog(LogLevel.Error, CONST.Text.Loader.CheckerInvalid); return false; } } }); } // Load all function-objs provided in funcs asynchronously, and merge return values into one return obj // funcobj: {[checker], [detectDom], func} function loadFuncs(oFuncs) { const returnObj = {}; oFuncs.forEach(oFunc => { if (!oFunc.checker || testChecker(oFunc.checker)) { if (oFunc.detectDom) { detectDom(oFunc.detectDom, e => execute(oFunc)); } else { setTimeout(e => execute(oFunc), 0); } } }); return returnObj; function execute(oFunc) { setTimeout(e => { const rval = oFunc.func(returnObj) || {}; copyProps(rval, returnObj); }, 0); } } function randint(min, max) { return Math.floor(Math.random() * (max - min + 1)) + min; } })();