您需要先安装一个扩展,例如 篡改猴、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 // @name:en-US Greasyfork script-set-edit button // @namespace Greasyfork-Favorite // @version 0.1.6 // @description 在GF脚本页添加快速打开收藏集编辑页面功能 // @description:zh-CN 在GF脚本页添加快速打开收藏集编辑页面功能 // @description:zh-TW 在GF腳本頁添加快速打開收藏集編輯頁面功能 // @description:en Add open script-set-edit-page button in GF script page // @description:en-US Add open script-set-edit-page button in GF script page // @author PY-DNG // @license GPL-3 // @match http*://greatest.deepsurf.us/* // @match http*://sleazyfork.org/* // @include http*://greatest.deepsurf.us/* // @include http*://sleazyfork.org/* // @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 // ==/UserScript== (function __MAIN__() { 'use strict'; // function DoLog() {} // Arguments: level=LogLevel.Info, logContent, trace=false const [LogLevel, DoLog] = (function() { const LogLevel = { None: 0, Error: 1, Success: 2, Warning: 3, Info: 4, }; return [LogLevel, DoLog]; function DoLog() { // Get window const win = (typeof(unsafeWindow) === 'object' && unsafeWindow !== null) ? unsafeWindow : window; const LogLevelMap = {}; LogLevelMap[LogLevel.None] = { prefix: '', color: 'color:#ffffff' } LogLevelMap[LogLevel.Error] = { prefix: '[Error]', color: 'color:#ff0000' } LogLevelMap[LogLevel.Success] = { prefix: '[Success]', color: 'color:#00aa00' } LogLevelMap[LogLevel.Warning] = { prefix: '[Warning]', color: 'color:#ffa500' } LogLevelMap[LogLevel.Info] = { prefix: '[Info]', color: 'color:#888888' } LogLevelMap[LogLevel.Elements] = { prefix: '[Elements]', color: 'color:#000000' } // Current log level DoLog.logLevel = (win.isPY_DNG && win.userscriptDebugging) ? LogLevel.Info : LogLevel.Warning; // Info Warning Success Error // Log counter DoLog.logCount === undefined && (DoLog.logCount = 0); // Get args let [level, logContent, trace] = parseArgs([...arguments], [ [2], [1,2], [1,2,3] ], [LogLevel.Info, 'DoLog initialized.', false]); // Log when log level permits if (level <= DoLog.logLevel) { let msg = '%c' + LogLevelMap[level].prefix + (typeof GM_info === 'object' ? `[${GM_info.script.name}]` : '') + (LogLevelMap[level].prefix ? ' ' : ''); let subst = LogLevelMap[level].color; switch (typeof(logContent)) { case 'string': msg += '%s'; break; case 'number': msg += '%d'; break; default: msg += '%o'; break; } if (++DoLog.logCount > 512) { console.clear(); DoLog.logCount = 0; } console[trace ? 'trace' : 'log'](msg, subst, logContent); } } }) (); const CONST = { Text: { 'zh-CN': { FavEdit: '收藏集:', Add: '加入此集', Edit: '手动编辑', CopySID: '复制脚本ID', Working: ['正在添加...', '就快好了...'], Error: { Unknown: '未知错误' } }, 'zh-TW': { FavEdit: '收藏集:', Add: '加入此集', Edit: '手動編輯', CopySID: '複製腳本ID', Working: ['正在添加...', '就快好了...'], Error: { Unknown: '未知錯誤' } }, 'en': { FavEdit: 'Add to/Remove from favorite list: ', Add: 'Add', Edit: 'Edit Manually', CopySID: 'Copy-Script-ID', Working: ['Working...', 'Just a moment...'], Error: { Unknown: 'Unknown Error' } }, 'default': { FavEdit: 'Add to/Remove from favorite list: ', Add: 'Add', Edit: 'Edit Manually', CopySID: 'Copy-Script-ID', Working: ['Working...', 'Just a moment...'], Error: { Unknown: 'Unknown Error' } }, } } // 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": API[2] && 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 = $CrE('div'); script_favorite.id = 'script-favorite'; script_favorite.style.margin = '0.75em 0'; script_favorite.innerHTML = CONST.Text[i18n].FavEdit; const favorite_groups = $CrE('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 = $CrE('option'); option.innerText = set.name; option.value = set.linkedit; $APD(favorite_groups, option); } adjustWidth(); getScriptSets(function(sets) { clearChildnodes(favorite_groups); for (const set of sets) { // Make <option> const option = $CrE('option'); option.innerText = set.name; option.value = set.linkedit; $APD(favorite_groups, option); } adjustWidth(); // Set edit-button.href favorite_edit.href = favorite_groups.value; }) favorite_groups.addEventListener('change', function(e) { favorite_edit.href = favorite_groups.value; }); const favorite_add = $CrE('a'); favorite_add.id = 'favorite-add'; favorite_add.innerHTML = CONST.Text[i18n].Add; favorite_add.style.margin = favorite_add.style.margin = '0px 0.5em'; favorite_add.href = 'javascript:void(0);' favorite_add.addEventListener('click', function(e) { addFav(); }); const favorite_edit = $CrE('a'); favorite_edit.id = 'favorite-edit'; 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 = $CrE('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 $APD(script_favorite, favorite_groups); script_parent.insertBefore(script_favorite, script_after); $APD(script_favorite, favorite_add); $APD(script_favorite, favorite_edit); $APD(script_favorite, favorite_copy); function adjustWidth() { favorite_groups.style.width = Math.max.apply(null, Array.from(favorite_groups.children).map((o) => (o.innerText.length))).toString() + 'em'; favorite_groups.style.maxWidth = '40vw'; } function addFav() { const iframe = $CrE('iframe'); iframe.style.width = iframe.style.height = iframe.style.border = '0'; iframe.addEventListener('load', edit_onload, {once: true}); iframe.src = favorite_groups.value; $APD(document.body, iframe); displayNotice(CONST.Text[i18n].Working[0]); function edit_onload() { const oDom = iframe.contentDocument; const input = $CrE('input'); input.value = getStrSID(); input.name = 'scripts-included[]'; input.type = 'hidden'; $APD($(oDom, '#script-set-scripts'), input); $(oDom, 'button[name="save"]').click(); iframe.addEventListener('load', finish_onload, {once: true}); displayNotice(CONST.Text[i18n].Working[1]); } function finish_onload() { const status = $(iframe.contentDocument, 'p.notice'); const status_text = status ? status.innerText : CONST.Text[i18n].Error.Unknown; displayNotice(status_text); iframe.parentElement.removeChild(iframe); } function 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); } } } } 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] ? li.children[1].href : 'https://greatest.deepsurf.us/' + $('#language-selector-locale').value + '/users/' + $('#nav-user-info>.user-profile-link>a').href.match(/zh-CN\/users\/([^\/]*)/)[1] + '/sets/' + li.children[0].href.match(/[\?&]set=(\d+)/)[1] + '/edit'; // 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)); } // Basic functions // querySelector function $() { switch(arguments.length) { case 2: return arguments[0].querySelector(arguments[1]); break; default: return document.querySelector(arguments[0]); } } // querySelectorAll function $All() { switch(arguments.length) { case 2: return arguments[0].querySelectorAll(arguments[1]); break; default: return document.querySelectorAll(arguments[0]); } } // createElement function $CrE() { switch(arguments.length) { case 2: return arguments[0].createElement(arguments[1]); break; default: return document.createElement(arguments[0]); } } function $APD(a,b) {return a.appendChild(b);} // Object1[prop] ==> Object2[prop] function copyProp(obj1, obj2, prop) {obj1[prop] !== undefined && (obj2[prop] = obj1[prop]);} function copyProps(obj1, obj2, props) {props.forEach((prop) => (copyProp(obj1, obj2, prop)));} // Just stopPropagation and preventDefault function destroyEvent(e) { if (!e) {return false;}; if (!e instanceof Event) {return false;}; e.stopPropagation(); e.preventDefault(); } // 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); } } // 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); } // Get a url argument from lacation.href // also recieve a function to deal the matched string // returns defaultValue if name not found // Args: {url=location.href, name, dealFunc=((a)=>{return a;}), defaultValue=null} or 'name' 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); } // 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; } function AsyncManager() { const AM = this; // Ongoing xhr count this.taskCount = 0; // Whether generate finish events let finishEvent = false; Object.defineProperty(this, 'finishEvent', { configurable: true, enumerable: true, get: () => (finishEvent), set: (b) => { finishEvent = b; b && AM.taskCount === 0 && AM.onfinish && AM.onfinish(); } }); // Add one task this.add = () => (++AM.taskCount); // Finish one task this.finish = () => ((--AM.taskCount === 0 && AM.finishEvent && AM.onfinish && AM.onfinish(), AM.taskCount)); } function randint(min, max) { return Math.floor(Math.random() * (max - min + 1)) + min; } function parseArgs(args, rules, defaultValues=[]) { // args and rules should be array, but not just iterable (string is also iterable) if (!Array.isArray(args) || !Array.isArray(rules)) { throw new TypeError('parseArgs: args and rules should be array') } // fill rules[0] (!Array.isArray(rules[0]) || rules[0].length === 1) && rules.splice(0, 0, []); // max arguments length const count = rules.length - 1; // args.length must <= count if (args.length > count) { throw new TypeError(`parseArgs: args has more elements(${args.length}) longer than ruless'(${count})`); } // rules[i].length should be === i if rules[i] is an array, otherwise it should be a function for (let i = 1; i <= count; i++) { const rule = rules[i]; if (Array.isArray(rule)) { if (rule.length !== i) { throw new TypeError(`parseArgs: rules[${i}](${rule}) should have ${i} numbers, but given ${rules[i].length}`); } if (!rule.every((num) => (typeof num === 'number' && num <= count))) { throw new TypeError(`parseArgs: rules[${i}](${rule}) should contain numbers smaller than count(${count}) only`); } } else if (typeof rule !== 'function') { throw new TypeError(`parseArgs: rules[${i}](${rule}) should be an array or a function.`) } } // Parse const rule = rules[args.length]; let parsed; if (Array.isArray(rule)) { parsed = [...defaultValues]; for (let i = 0; i < rule.length; i++) { parsed[rule[i]-1] = args[i]; } } else { parsed = rule(args, defaultValues); } return parsed; } })();