Greasyfork 快捷编辑收藏

在GF脚本页添加快速打开收藏集编辑页面功能

Stan na 03-11-2023. Zobacz najnowsza wersja.

Aby zainstalować ten skrypt, wymagana jest instalacje jednego z następujących rozszerzeń: Tampermonkey, Greasemonkey lub Violentmonkey.

Aby zainstalować ten skrypt, wymagana jest instalacje jednego z następujących rozszerzeń: Tampermonkey, Violentmonkey.

Aby zainstalować ten skrypt, wymagana jest instalacje jednego z następujących rozszerzeń: Tampermonkey, Violentmonkey.

Aby zainstalować ten skrypt, wymagana będzie instalacja rozszerzenia Tampermonkey lub Userscripts.

You will need to install an extension such as Tampermonkey to install this script.

Aby zainstalować ten skrypt, musisz zainstalować rozszerzenie menedżera skryptów użytkownika.

(Mam już menedżera skryptów użytkownika, pozwól mi to zainstalować!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

Będziesz musiał zainstalować rozszerzenie menedżera stylów użytkownika, aby zainstalować ten styl.

Będziesz musiał zainstalować rozszerzenie menedżera stylów użytkownika, aby zainstalować ten styl.

Musisz zainstalować rozszerzenie menedżera stylów użytkownika, aby zainstalować ten styl.

(Mam już menedżera stylów użytkownika, pozwól mi to zainstalować!)

/* 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.8.1
// @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*://*.greasyfork.org/*
// @match              http*://*.sleazyfork.org/*
// @require            https://greasyfork.org/scripts/456034-basic-functions-for-userscripts/code/script.js?version=1226884
// @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
// ==/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 */

(function __MAIN__() {
    'use strict';

	const CONST = {
		Text: {
			'zh-CN': {
				FavEdit: '收藏集:',
				Add: '加入此集',
				Edit: '手动编辑',
				CopySID: '复制脚本ID',
				Working: ['正在添加...', '就快好了...'],
				InSetStatus: ['[ ]', '[✔]'],
				Error: {
					Unknown: '未知错误'
				}
			},
			'zh-TW': {
				FavEdit: '收藏集:',
				Add: '加入此集',
				Edit: '手動編輯',
				CopySID: '複製腳本ID',
				Working: ['正在添加...', '就快好了...'],
				InSetStatus: ['[ ]', '[✔]'],
				Error: {
					Unknown: '未知錯誤'
				}
			},
			'en': {
				FavEdit: 'Add to/Remove from favorite list: ',
				Add: 'Add',
				Edit: 'Edit Manually',
				CopySID: 'Copy-Script-ID',
				Working: ['Working...', 'Just a moment...'],
				InSetStatus: ['[ ]', '[✔]'],
				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...'],
				InSetStatus: ['[ ]', '[✔]'],
				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...
		GMXHRHook(5);
	}

	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 = set.elmOption = $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;

				// Check script in-set status
				getInSets(sets, getStrSID(), inSets => {
					sets.forEach(set => {
						const inSet = inSets.includes(set);
						set.elmOption.innerText = `${CONST.Text[i18n].InSetStatus[inSet+0]} ${set.name}`;
					});
					adjustWidth();
				});
			})
			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 option = favorite_groups.selectedOptions[0];
				const set = GM_getValue('script-sets').sets.find(set => set.linkedit === option.value);
				const iframe = $CrE('iframe');
				iframe.style.width = iframe.style.height = iframe.style.border = '0';
				$AEL(iframe, 'load', edit_onload, {once: true});
				$AEL(iframe, 'error', iframe_onerror);
				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);
					option.innerText = `${CONST.Text[i18n].InSetStatus[1]} ${set.name}`;
				}

				function iframe_onerror() {
					iframe.src = iframe.src;
				}

				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://greasyfork.org/' + $('#language-selector-locale').value + '/users/' + $('#nav-user-info>.user-profile-link>a').href.match(/[a-zA-Z\-]+\/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
				});
			}
			*/
			const script_sets = Array.from($(oDom, 'ul#user-script-sets').children).map(li => ({
				name: li.children[0].innerText,
				link: li.children[0].href,
				linkedit: li.children[1].href
			}));

			// Save to GM_storage
			GM_setValue('script-sets', {
				sets: script_sets,
				time: (new Date()).getTime(),
				version: '0.2'
			});

			// 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 getInSet(set, sid, callback) {
		sid = sid.toString();
		getDocument(set.linkedit, oDom => {
			const inSet = [...$(oDom, '#script-set-scripts').children].some(input => input.value === sid);
			callback(inSet);
		});
	}

	function getInSets(sets, sid, callback) {
		const AM = new AsyncManager();
		const inSets = [];
		for (const set of sets) {
			AM.add();
			getInSet(set, sid, inSet => {
				inSet && inSets.push(set);
				AM.finish();
			});
		}
		AM.onfinish = e => {
			callback(inSets);
		};
		AM.finishEvent = true;
	}

	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
	function $APD(a,b) {return a.appendChild(b);}

	// 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);
	}

	// 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);
    }

	// 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 randint(min, max) {
		return Math.floor(Math.random() * (max - min + 1)) + min;
	}
})();