您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
在GF脚本页添加快速打开收藏集编辑页面功能
当前为
/* eslint-disable no-multi-spaces */ // ==UserScript== // @name Greasyfork 快捷编辑收藏 // @name:zh-CN Greasyfork 快捷编辑收藏 // @name:zh-TW Greasyfork 快捷編輯收藏 // @name:en Greasyfork script-set-edit button // @namespace Greasyfork-Favorite // @version 0.1 // @description 在GF脚本页添加快速打开收藏集编辑页面功能 // @description:zh-CN 在GF脚本页添加快速打开收藏集编辑页面功能 // @description:zh-TW 在GF腳本頁添加快速打開收藏集編輯頁面功能 // @description:en Add open script-set-edit-page button in GF script page // @author PY-DNG // @license GPL-3 // @match http*://greasyfork.org/* // @icon https://api.iowen.cn/favicon/get.php?url=greasyfork.org // @grant GM_xmlhttpRequest // @grant GM_setValue // @grant GM_getValue // ==/UserScript== (function() { 'use strict'; // Arguments: level=LogLevel.Info, logContent, asObject=false // Needs one call "DoLog();" to get it initialized before using it! function DoLog() { // Global log levels set window.LogLevel = { None: 0, Error: 1, Success: 2, Warning: 3, Info: 4, } window.LogLevelMap = {}; window.LogLevelMap[LogLevel.None] = {prefix: '' , color: 'color:#ffffff'} window.LogLevelMap[LogLevel.Error] = {prefix: '[Error]' , color: 'color:#ff0000'} window.LogLevelMap[LogLevel.Success] = {prefix: '[Success]' , color: 'color:#00aa00'} window.LogLevelMap[LogLevel.Warning] = {prefix: '[Warning]' , color: 'color:#ffa500'} window.LogLevelMap[LogLevel.Info] = {prefix: '[Info]' , color: 'color:#888888'} window.LogLevelMap[LogLevel.Elements] = {prefix: '[Elements]', color: 'color:#000000'} // Current log level DoLog.logLevel = (unsafeWindow ? unsafeWindow.isPY_DNG : window.isPY_DNG) ? LogLevel.Info : LogLevel.Warning; // Info Warning Success Error // Log counter DoLog.logCount === undefined && (DoLog.logCount = 0); if (++DoLog.logCount > 512) { console.clear(); DoLog.logCount = 0; } // Get args let level, logContent, asObject; switch (arguments.length) { case 1: level = LogLevel.Info; logContent = arguments[0]; asObject = false; break; case 2: level = arguments[0]; logContent = arguments[1]; asObject = false; break; case 3: level = arguments[0]; logContent = arguments[1]; asObject = arguments[2]; break; default: level = LogLevel.Info; logContent = 'DoLog initialized.'; asObject = false; break; } // Log when log level permits if (level <= DoLog.logLevel) { let msg = '%c' + LogLevelMap[level].prefix; let subst = LogLevelMap[level].color; if (asObject) { msg += ' %o'; } else { switch(typeof(logContent)) { case 'string': msg += ' %s'; break; case 'number': msg += ' %d'; break; case 'object': msg += ' %o'; break; } } console.log(msg, subst, logContent); } } DoLog(); bypassXB(); GM_PolyFill('default'); // Inner consts with i18n const CONST = { Text: { 'zh-CN': { FavEdit: '收藏集:', Edit: '编辑', CopySID: '复制脚本ID' }, 'zh-TW': { FavEdit: '收藏集:', Edit: '編輯', CopySID: '複製腳本ID' }, 'en': { FavEdit: 'Add to/Remove from favorite list: ', Edit: 'Edit', CopySID: 'Copy-Script-ID' }, 'default': { FavEdit: 'Add to/Remove from favorite list: ', Edit: 'Edit', CopySID: 'Copy-Script-ID' }, } } // Get i18n code let i18n = navigator.language; if (!Object.keys(CONST.Text).includes(i18n)) {i18n = 'default';} main() function main() { const HOST = getHost(); const API = getAPI(); // Common actions commons(); // API-based actions switch(API[1]) { case "scripts": centerScript(API); break; default: DoLog('API is {}'.replace('{}', API)); } } function centerScript(API) { switch(API[3]) { case undefined: pageScript(); break; case 'code': pageCode(); break; case 'feedback': pageFeedback(); break; } } function commons() { // Your common actions here... } function pageScript() { addFavPanel(); } function pageCode() { addFavPanel(); } function pageFeedback() { addFavPanel(); } function addFavPanel() { if (!getUserpage()) {return false;} GUI(); function GUI() { // Get elements const script_after = $('#script-feedback-suggestion+*') || $('#new-script-discussion'); const script_parent = script_after.parentElement; // My elements const script_favorite = $C('div'); script_favorite.id = 'script-favorite'; script_favorite.style.margin = '0.75em 0'; script_favorite.innerHTML = CONST.Text[i18n].FavEdit; const favorite_groups = $C('select'); favorite_groups.id = 'favorite-groups'; const stored_sets = GM_getValue('script-sets', {sets: []}).sets; for (const set of stored_sets) { // Make <option> const option = $C('option'); option.innerText = set.name; option.value = set.linkedit; $A(favorite_groups, option); } getScriptSets(function(sets) { clearChildnodes(favorite_groups); for (const set of sets) { // Make <option> const option = $C('option'); option.innerText = set.name; option.value = set.linkedit; $A(favorite_groups, option); } // Set edit-button.href favorite_edit.href = favorite_groups.value; }) favorite_groups.addEventListener('change', function(e) { favorite_edit.href = favorite_groups.value; }); const favorite_edit = $C('a'); favorite_edit.id = 'favorite-add'; favorite_edit.innerHTML = CONST.Text[i18n].Edit; favorite_edit.style.margin = favorite_edit.style.margin = '0px 0.5em'; favorite_edit.target = '_blank'; const favorite_copy = $C('a'); favorite_copy.id = 'favorite-copy'; favorite_copy.href = 'javascript: void(0);'; favorite_copy.innerHTML = CONST.Text[i18n].CopySID; favorite_copy.addEventListener('click', function() { copyText(getStrSID()); }); // Append to document $A(script_favorite, favorite_groups); $I(script_parent, script_favorite, script_after); $A(script_favorite, favorite_edit); $A(script_favorite, favorite_copy); } } function getScriptSets(callback, args=[]) { const userpage = getUserpage(); getDocument(userpage, function(oDom) { const user_script_sets = oDom.querySelector('#user-script-sets'); const script_sets = []; for (const li of user_script_sets.querySelectorAll('li')) { // Get fav info const name = li.childNodes[0].nodeValue.trimRight(); const link = li.children[0].href; const linkedit = li.children[1].href; // Append to script_sets script_sets.push({ name: name, link: link, linkedit: linkedit }); } // Save to GM_storage GM_setValue('script-sets', { sets: script_sets, time: (new Date()).getTime(), version: '0.1' }); // callback callback.apply(null, [script_sets].concat(args)); }); } function getUserpage() { const a = $('#nav-user-info>.user-profile-link>a'); return a ? a.href : null; } function getStrSID(url=location.href) { const API = getAPI(url); const strSID = API[2].match(/\d+/); return strSID; } function getSID(url=location.href) { return Number(getStrSID(url)); } function $(e) {return document.querySelector(e);} function $C(e) {return document.createElement(e);} function $A(a,b) {return a.appendChild(b);} function $I(a,b,c) {return a.insertBefore(b,c);} // Remove all childnodes from an element function clearChildnodes(element) { const cns = [] for (const cn of element.childNodes) { cns.push(cn); } for (const cn of cns) { element.removeChild(cn); } } // Just stopPropagation and preventDefault function destroyEvent(e) { if (!e) {return false;}; if (!e instanceof Event) {return false;}; e.stopPropagation(); e.preventDefault(); } // Download and parse a url page into a html document(dom). // when xhr onload: callback.apply([dom, args]) function getDocument(url, callback, args=[]) { GM_xmlhttpRequest({ method : 'GET', url : url, responseType : 'blob', onloadstart : function() { DoLog(LogLevel.Info, 'getting document, url=\'' + url + '\''); }, onload : function(response) { const htmlblob = response.response; parseDocument(htmlblob, callback, args); } }) } function parseDocument(htmlblob, callback, args=[]) { const reader = new FileReader(); reader.onload = function(e) { const htmlText = reader.result; const dom = new DOMParser().parseFromString(htmlText, 'text/html'); args = [dom].concat(args); callback.apply(null, args); //callback(dom, htmlText); } reader.readAsText(htmlblob, document.characterSet); } // GM_XHR HOOK: The number of running GM_XHRs in a time must under maxXHR // Returns the abort function to stop the request anyway(no matter it's still waiting, or requesting) // (If the request is invalid, such as url === '', will return false and will NOT make this request) // If the abort function called on a request that is not running(still waiting or finished), there will be NO onabort event // Requires: function delItem(){...} & function uniqueIDMaker(){...} function GMXHRHook(maxXHR=5) { const GM_XHR = GM_xmlhttpRequest; const getID = uniqueIDMaker(); let todoList = [], ongoingList = []; GM_xmlhttpRequest = safeGMxhr; function safeGMxhr() { // Get an id for this request, arrange a request object for it. const id = getID(); const request = {id: id, args: arguments, aborter: null}; // Deal onload function first dealEndingEvents(request); /* DO NOT DO THIS! KEEP ITS ORIGINAL PROPERTIES! // Stop invalid requests if (!validCheck(request)) { return false; } */ // Judge if we could start the request now or later? todoList.push(request); checkXHR(); return makeAbortFunc(id); // Decrease activeXHRCount while GM_XHR onload; function dealEndingEvents(request) { const e = request.args[0]; // onload event const oriOnload = e.onload; e.onload = function() { reqFinish(request.id); checkXHR(); oriOnload ? oriOnload.apply(null, arguments) : function() {}; } // onerror event const oriOnerror = e.onerror; e.onerror = function() { reqFinish(request.id); checkXHR(); oriOnerror ? oriOnerror.apply(null, arguments) : function() {}; } // ontimeout event const oriOntimeout = e.ontimeout; e.ontimeout = function() { reqFinish(request.id); checkXHR(); oriOntimeout ? oriOntimeout.apply(null, arguments) : function() {}; } // onabort event const oriOnabort = e.onabort; e.onabort = function() { reqFinish(request.id); checkXHR(); oriOnabort ? oriOnabort.apply(null, arguments) : function() {}; } } // Check if the request is invalid function validCheck(request) { const e = request.args[0]; if (!e.url) { return false; } return true; } // Call a XHR from todoList and push the request object to ongoingList if called function checkXHR() { if (ongoingList.length >= maxXHR) {return false;}; if (todoList.length === 0) {return false;}; const req = todoList.shift(); const reqArgs = req.args; const aborter = GM_XHR.apply(null, reqArgs); req.aborter = aborter; ongoingList.push(req); return req; } // Make a function that aborts a certain request function makeAbortFunc(id) { return function() { let i; // Check if the request haven't been called for (i = 0; i < todoList.length; i++) { const req = todoList[i]; if (req.id === id) { // found this request: haven't been called delItem(todoList, i); return true; } } // Check if the request is running now for (i = 0; i < ongoingList.length; i++) { const req = todoList[i]; if (req.id === id) { // found this request: running now req.aborter(); reqFinish(id); checkXHR(); } } // Oh no, this request is already finished... return false; } } // Remove a certain request from ongoingList function reqFinish(id) { let i; for (i = 0; i < ongoingList.length; i++) { const req = ongoingList[i]; if (req.id === id) { ongoingList = delItem(ongoingList, i); return true; } } return false; } } } // Get a url argument from lacation.href // also recieve a function to deal the matched string // returns defaultValue if name not found // Args: name, dealFunc=(function(a) {return a;}), defaultValue=null function getUrlArgv(details) { typeof(details) === 'string' && (details = {name: details}); typeof(details) === 'undefined' && (details = {}); if (!details.name) {return null;}; const url = details.url ? details.url : location.href; const name = details.name ? details.name : ''; const dealFunc = details.dealFunc ? details.dealFunc : ((a)=>{return a;}); const defaultValue = details.defaultValue ? details.defaultValue : null; const matcher = new RegExp(name + '=([^&]+)'); const result = url.match(matcher); const argv = result ? dealFunc(result[1]) : defaultValue; return argv; } // 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); } // Append a style text to document(<head>) with a <style> element function addStyle(css, id) { const style = document.createElement("style"); id && (style.id = id); style.textContent = css; for (const elm of document.querySelectorAll('#'+id)) { elm.parentElement && elm.parentElement.removeChild(elm); } document.head.appendChild(style); } // File download function // details looks like the detail of GM_xmlhttpRequest // onload function will be called after file saved to disk function downloadFile(details) { if (!details.url || !details.name) {return false;}; // Configure request object const requestObj = { url: details.url, responseType: 'blob', onload: function(e) { // Save file saveFile(URL.createObjectURL(e.response), details.name); // onload callback details.onload ? details.onload(e) : function() {}; } } if (details.onloadstart ) {requestObj.onloadstart = details.onloadstart;}; if (details.onprogress ) {requestObj.onprogress = details.onprogress;}; if (details.onerror ) {requestObj.onerror = details.onerror;}; if (details.onabort ) {requestObj.onabort = details.onabort;}; if (details.onreadystatechange) {requestObj.onreadystatechange = details.onreadystatechange;}; if (details.ontimeout ) {requestObj.ontimeout = details.ontimeout;}; // Send request GM_xmlhttpRequest(requestObj); } // get '/' splited API array from a url function getAPI(url=location.href) { return url.replace(/https?:\/\/(.*?\.){1,2}.*?\//, '').replace(/\?.*/, '').match(/[^\/]+?(?=(\/|$))/g); } // get host part from a url(includes '^https://', '/$') function getHost(url=location.href) { const match = location.href.match(/https?:\/\/[^\/]+\//); return match ? match[0] : match; } // Your code here... // Bypass xbrowser's useless GM_functions function bypassXB() { if (typeof(mbrowser) === 'object') { window.unsafeWindow = window.GM_setClipboard = window.GM_openInTab = window.GM_xmlhttpRequest = window.GM_getValue = window.GM_setValue = window.GM_listValues = window.GM_deleteValue = undefined; } } // GM_Polyfill By PY-DNG // 2021.07.18 - 2021.07.19 // Simply provides the following GM_functions using localStorage, XMLHttpRequest and window.open: // Returns object GM_POLYFILLED which has the following properties that shows you which GM_functions are actually polyfilled: // GM_setValue, GM_getValue, GM_deleteValue, GM_listValues, GM_xmlhttpRequest, GM_openInTab, GM_setClipboard, unsafeWindow(object) // All polyfilled GM_functions are accessable in window object/Global_Scope(only without Tempermonkey Sandboxing environment) function GM_PolyFill(name='default') { const GM_POLYFILL_KEY_STORAGE = 'GM_STORAGE_POLYFILL'; let GM_POLYFILL_storage; const GM_POLYFILLED = { GM_setValue: true, GM_getValue: true, GM_deleteValue: true, GM_listValues: true, GM_xmlhttpRequest: true, GM_openInTab: true, GM_setClipboard: true, unsafeWindow: true, once: false } // Ignore GM_PolyFill_Once window.GM_POLYFILLED && window.GM_POLYFILLED.once && (window.unsafeWindow = window.GM_setClipboard = window.GM_openInTab = window.GM_xmlhttpRequest = window.GM_getValue = window.GM_setValue = window.GM_listValues = window.GM_deleteValue = undefined); GM_setValue_polyfill(); GM_getValue_polyfill(); GM_deleteValue_polyfill(); GM_listValues_polyfill(); GM_xmlhttpRequest_polyfill(); GM_openInTab_polyfill(); GM_setClipboard_polyfill(); unsafeWindow_polyfill(); function GM_POLYFILL_getStorage() { let gstorage = localStorage.getItem(GM_POLYFILL_KEY_STORAGE); gstorage = gstorage ? JSON.parse(gstorage) : {}; let storage = gstorage[name] ? gstorage[name] : {}; return storage; } function GM_POLYFILL_saveStorage() { let gstorage = localStorage.getItem(GM_POLYFILL_KEY_STORAGE); gstorage = gstorage ? JSON.parse(gstorage) : {}; gstorage[name] = GM_POLYFILL_storage; localStorage.setItem(GM_POLYFILL_KEY_STORAGE, JSON.stringify(gstorage)); } // GM_setValue function GM_setValue_polyfill() { typeof (GM_setValue) === 'function' ? GM_POLYFILLED.GM_setValue = false: window.GM_setValue = PF_GM_setValue;; function PF_GM_setValue(name, value) { GM_POLYFILL_storage = GM_POLYFILL_getStorage(); name = String(name); GM_POLYFILL_storage[name] = value; GM_POLYFILL_saveStorage(); } } // GM_getValue function GM_getValue_polyfill() { typeof (GM_getValue) === 'function' ? GM_POLYFILLED.GM_getValue = false: window.GM_getValue = PF_GM_getValue; function PF_GM_getValue(name, defaultValue) { GM_POLYFILL_storage = GM_POLYFILL_getStorage(); name = String(name); if (GM_POLYFILL_storage.hasOwnProperty(name)) { return GM_POLYFILL_storage[name]; } else { return defaultValue; } } } // GM_deleteValue function GM_deleteValue_polyfill() { typeof (GM_deleteValue) === 'function' ? GM_POLYFILLED.GM_deleteValue = false: window.GM_deleteValue = PF_GM_deleteValue; function PF_GM_deleteValue(name) { GM_POLYFILL_storage = GM_POLYFILL_getStorage(); name = String(name); if (GM_POLYFILL_storage.hasOwnProperty(name)) { delete GM_POLYFILL_storage[name]; GM_POLYFILL_saveStorage(); } } } // GM_listValues function GM_listValues_polyfill() { typeof (GM_listValues) === 'function' ? GM_POLYFILLED.GM_listValues = false: window.GM_listValues = PF_GM_listValues; function PF_GM_listValues() { GM_POLYFILL_storage = GM_POLYFILL_getStorage(); return Object.keys(GM_POLYFILL_storage); } } // unsafeWindow function unsafeWindow_polyfill() { typeof (unsafeWindow) === 'object' ? GM_POLYFILLED.unsafeWindow = false: window.unsafeWindow = window; } // GM_xmlhttpRequest // not supported properties of details: synchronous binary nocache revalidate context fetch // not supported properties of response(onload arguments[0]): finalUrl // ---!IMPORTANT!--- DOES NOT SUPPORT CROSS-ORIGIN REQUESTS!!!!! ---!IMPORTANT!--- function GM_xmlhttpRequest_polyfill() { typeof (GM_xmlhttpRequest) === 'function' ? GM_POLYFILLED.GM_xmlhttpRequest = false: window.GM_xmlhttpRequest = PF_GM_xmlhttpRequest; // details.synchronous is not supported as Tempermonkey function PF_GM_xmlhttpRequest(details) { const xhr = new XMLHttpRequest(); // open request const openArgs = [details.method, details.url, true]; if (details.user && details.password) { openArgs.push(details.user); openArgs.push(details.password); } xhr.open.apply(xhr, openArgs); // set headers if (details.headers) { for (const key of Object.keys(details.headers)) { xhr.setRequestHeader(key, details.headers[key]); } } details.cookie ? xhr.setRequestHeader('cookie', details.cookie) : function () {}; details.anonymous ? xhr.setRequestHeader('cookie', '') : function () {}; // properties xhr.timeout = details.timeout; xhr.responseType = details.responseType; details.overrideMimeType ? xhr.overrideMimeType(details.overrideMimeType) : function () {}; // events xhr.onabort = details.onabort; xhr.onerror = details.onerror; xhr.onloadstart = details.onloadstart; xhr.onprogress = details.onprogress; xhr.onreadystatechange = details.onreadystatechange; xhr.ontimeout = details.ontimeout; xhr.onload = function (e) { const response = { readyState: xhr.readyState, status: xhr.status, statusText: xhr.statusText, responseHeaders: xhr.getAllResponseHeaders(), response: xhr.response }; (details.responseType === '' || details.responseType === 'text') ? (response.responseText = xhr.responseText) : function () {}; (details.responseType === '' || details.responseType === 'document') ? (response.responseXML = xhr.responseXML) : function () {}; details.onload(response); } // send request details.data ? xhr.send(details.data) : xhr.send(); return { abort: xhr.abort }; } } // NOTE: options(arg2) is NOT SUPPORTED! if provided, then will just be skipped. function GM_openInTab_polyfill() { typeof (GM_openInTab) === 'function' ? GM_POLYFILLED.GM_openInTab = false: window.GM_openInTab = PF_GM_openInTab; function PF_GM_openInTab(url) { window.open(url); } } // NOTE: needs to be called in an event handler function, and info(arg2) is NOT SUPPORTED! function GM_setClipboard_polyfill() { typeof (GM_setClipboard) === 'function' ? GM_POLYFILLED.GM_setClipboard = false: window.GM_setClipboard = PF_GM_setClipboard; function PF_GM_setClipboard(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); } } return GM_POLYFILLED; } // Makes a function that returns a unique ID number each time function uniqueIDMaker() { let id = 0; return makeID; function makeID() { id++; return id; } } // Del a item from an array using its index. Returns the array but can NOT modify the original array directly!! function delItem(arr, delIndex) { arr = arr.slice(0, delIndex).concat(arr.slice(delIndex+1)); return arr; } })();