您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Improvements for working with groups of tags + increased efficiency of new requests creation
当前为
// ==UserScript== // @name [GMT] Tags Helper // @version 1.01.5 // @author Anakunda // @copyright 2021, Anakunda (https://greasyfork.org/users/321857-anakunda) // @license GPL-3.0-or-later // @namespace https://greasyfork.org/users/321857-anakunda // @run-at document-end // @match https://*/artist.php?id=* // @match https://*/artist.php?*&id=* // @match https://*/requests.php // @match https://*/requests.php?submit=true&* // @match https://*/requests.php?type=* // @match https://*/requests.php?page=* // @match https://*/requests.php?action=new* // @match https://*/requests.php?action=view&id=* // @match https://*/requests.php?action=view&*&id=* // @match https://*/requests.php?action=edit&id=* // @match https://*/torrents.php?id=* // @match https://*/torrents.php // @match https://*/torrents.php?action=advanced // @match https://*/torrents.php?action=advanced&* // @match https://*/torrents.php?*&action=advanced // @match https://*/torrents.php?*&action=advanced&* // @match https://*/torrents.php?action=basic // @match https://*/torrents.php?action=basic&* // @match https://*/torrents.php?*&action=basic // @match https://*/torrents.php?*&action=basic&* // @match https://*/torrents.php?page=* // @match https://*/torrents.php?action=notify // @match https://*/torrents.php?action=notify&* // @match https://*/torrents.php?type=* // @match https://*/collages.php?id=* // @match https://*/collages.php?page=*&id=* // @match https://*/collages.php?action=new // @match https://*/collages.php?action=edit&collageid=* // @match https://*/bookmarks.php?type=* // @match https://*/bookmarks.php?page=* // @match https://*/upload.php // @match https://*/upload.php?url=* // @match https://*/upload.php?tags=* // @match https://*/bookmarks.php?type=torrents // @match https://*/bookmarks.php?page=*&type=torrents // @match https://*/top10.php // @match https://*/top10.php?* // @grant GM_getValue // @grant GM_setClipboard // @grant GM_registerMenuCommand // @grant GM_getValue // @grant GM_setValue // @grant GM_deleteValue // @require https://openuserjs.org/src/libs/Anakunda/libLocks.min.js // @require https://openuserjs.org/src/libs/Anakunda/gazelleApiLib.min.js // @require https://openuserjs.org/src/libs/Anakunda/QobuzLib.min.js // @require https://openuserjs.org/src/libs/Anakunda/GazelleTagManager.min.js // @description Improvements for working with groups of tags + increased efficiency of new requests creation // ==/UserScript== (function() { 'use strict'; const hasStyleSheet = styleSheet => document.styleSheetSets && document.styleSheetSets.contains(styleSheet); const isLightTheme = ['postmod', 'shiro', 'layer_cake', 'proton', 'red_light'].some(hasStyleSheet); const isDarkTheme = ['kuro', 'minimal', 'red_dark'].some(hasStyleSheet); const uriTest = /^(https?:\/\/.+)$/i; const isFirefox = /\b(?:Firefox)\b/.test(navigator.userAgent) || Boolean(window.InstallTrigger); const fieldNames = ['tags', 'tagname', 'taglist']; const exclusions = GM_getValue('exclusions', [ '/^(?:\\d{4}s)$/i', '/^(?:delete\.this\.tag)$/i', ]).map(function(expr) { const m = /^\/(.+)\/([dgimsuy]*)$/.exec(expr); if (m != null) return new RegExp(m[1], m[2]); }).filter(it => it instanceof RegExp); const getTagsFromIterable = iterable => Array.from(iterable) .filter(elem => elem.offsetWidth > 0 && elem.offsetHeight > 0 && elem.pathname && elem.search && fieldNames.some(URLSearchParams.prototype.has.bind(new URLSearchParams(elem.search)))) .map(elem => elem.textContent.trim()) .filter(tag => /^([a-z\d\.]+)$/.test(tag) && !exclusions.some(rx => rx.test(tag))); const contextId = 'cae67c72-9aa7-4b96-855e-73cb23f5c7f8'; let menuHooks = 0, menuInvoker; function createMenu() { const menu = document.createElement('menu'); menu.type = 'context'; menu.id = contextId; menu.className = 'tags-helper'; function addMenuItem(label, callback) { if (label) { let menuItem = document.createElement('MENUITEM'); menuItem.label = label; if (typeof callback == 'function') menuItem.onclick = callback; menu.append(menuItem); } return menu.children.length; } addMenuItem('Copy tags to clipboard', function(evt) { console.assert(menuInvoker instanceof HTMLElement, 'menuInvoker instanceof HTMLElement') if (!(menuInvoker instanceof HTMLElement)) throw 'Invalid invoker'; const tags = getTagsFromIterable(menuInvoker.getElementsByTagName('A')); if (tags.length > 0) GM_setClipboard(tags.join(', '), 'text'); }); addMenuItem('Make new request using these tags', function(evt) { console.assert(menuInvoker instanceof HTMLElement, 'menuInvoker instanceof HTMLElement') if (!(menuInvoker instanceof HTMLElement)) throw 'Invalid invoker'; const tags = getTagsFromIterable(menuInvoker.getElementsByTagName('A')); if (tags.length > 0) document.location.assign('/requests.php?' + new URLSearchParams({ action: 'new', tags: JSON.stringify(tags), }).toString()); }); addMenuItem('Make new upload using these tags', function(evt) { console.assert(menuInvoker instanceof HTMLElement, 'menuInvoker instanceof HTMLElement') if (!(menuInvoker instanceof HTMLElement)) throw 'Invalid invoker'; const tags = getTagsFromIterable(menuInvoker.getElementsByTagName('A')); if (tags.length > 0) document.location.assign('/upload.php?' + new URLSearchParams({ tags: JSON.stringify(tags), }).toString()); }); document.body.append(menu); } function setElemHandlers(elem, textCallback) { console.assert(elem instanceof HTMLElement); elem.addEventListener('click', function(evt) { if (evt.altKey) evt.preventDefault(); else return; const tags = getTagsFromIterable(evt.currentTarget.getElementsByTagName('A')); if (tags.length > 0) if (evt.ctrlKey) document.location.assign('/requests.php?' + new URLSearchParams({ action: 'new', tags: JSON.stringify(tags) }).toString()); else if (evt.shiftKey) document.location.assign('/upload.php?' + new URLSearchParams({ tags: JSON.stringify(tags) }).toString()); else { GM_setClipboard(tags.join(', '), 'text'); evt.currentTarget.style.backgroundColor = isDarkTheme ? 'darkgreen' : 'lightgreen'; setTimeout(elem => { elem.style.backgroundColor = null }, 1000, evt.currentTarget); } return false; }); elem.ondragover = evt => false; elem.ondragenter = evt => { evt.currentTarget.style.backgroundColor = 'lawngreen' }; elem[isFirefox ? 'ondragexit' : 'ondragleave'] = evt => { evt.currentTarget.style.backgroundColor = null }; elem.draggable = true; elem.ondragstart = function(evt) { //evt.dataTransfer.clearData('text/uri-list'); //evt.dataTransfer.clearData('text/x-moz-url'); evt.dataTransfer.setData('text/plain', getTagsFromIterable(evt.currentTarget.getElementsByTagName('A')).join(', ')); console.debug(evt.currentTarget, evt.currentTarget.getElementsByTagName('A'), getTagsFromIterable(evt.currentTarget.getElementsByTagName('A'))); }; elem.ondrop = function(evt) { evt.stopPropagation(); let links = evt.dataTransfer.getData('text/uri-list'); if (links) links = links.split(/\r?\n/); else { links = evt.dataTransfer.getData('text/x-moz-url'); if (links) links = links.split(/\r?\n/).filter((item, ndx) => ndx % 2 == 0); else if (links = evt.dataTransfer.getData('text/plain')) links = links.split(/\r?\n/).filter(RegExp.prototype.test.bind(uriTest)); } if (Array.isArray(links) && links.length > 0) { const tags = getTagsFromIterable(evt.currentTarget.getElementsByTagName('A')); if (tags.length > 0) if (evt.shiftKey) document.location.assign('/upload.php?' + new URLSearchParams({ url: links[0], tags: JSON.stringify(tags), }).toString()); else document.location.assign('/requests.php?' + new URLSearchParams({ action: 'new', url: links[0], tags: JSON.stringify(tags), }).toString()); } else if (typeof textCallback == 'function' && (links = evt.dataTransfer.getData('text/plain')) //&& (links = links.split(/[\r\n\,\;\|\>]+/).map(expr => expr.trim()).filter(Boolean)).length > 0 && (links = new TagManager(links)).length > 0) textCallback(evt, links); evt.currentTarget.style.backgroundColor = null; return false; }; elem.setAttribute('contextmenu', contextId); elem.oncontextmenu = evt => { menuInvoker = evt.currentTarget }; elem.style.cursor = 'context-menu'; ++menuHooks; elem.title = `Alt + click => copy tags to clipboard Ctrl + Alt + click => make new request using these tags Shift + Alt + click => make new upload using these tags --- Drag & drop active link here => make new request using these tags Shift + Drag & drop active link here => make new upload using these tags Drag this tags area and drop to any text input to get inserted all tags as comma-separated list --or-- use context menu (older browsers only)`; } switch (document.location.pathname) { case '/torrents.php': { if (!new URLSearchParams(document.location.search).has('id')) break; const urlParams = new URLSearchParams(document.location.search); try { let tags = urlParams.get('tags'); if (tags && (tags = JSON.parse(tags)).length > 0) { const input = document.getElementById('tagname'); if (input == null) throw 'Tags input not found'; tags = new TagManager(...tags); input.value = tags.toString(); input.scrollIntoView({ behavior: 'smooth', block: 'start' }); //if (input.nextElementSibling != null) input.nextElementSibling.click(); } } catch(e) { } break; } case '/requests.php': case '/upload.php': { const urlParams = new URLSearchParams(document.location.search); try { let tags = urlParams.get('tags'); if (tags && (tags = JSON.parse(tags)).length > 0) { const input = document.getElementById('tags'); if (input == null) throw 'Tags input not found'; tags = new TagManager(...tags); input.value = tags.toString(); } } catch(e) { } const url = urlParams.get('url'); if (uriTest.test(url)) { let ua = document.getElementById('ua-data'); function feedData() { ua.value = url; if ((ua = document.getElementById('autofill-form-2')) == null) return; // assertion failed if (typeof ua.onclick == 'function') ua.onclick(); else ua.click(); } if (ua != null) feedData(); else { const container = document.querySelector('form#request_form > table > tbody'); if (container != null) { let counters = [0, 0], timeStamp = Date.now(); const mo = new MutationObserver(function(mutationsList) { ++counters[0]; for (let mutation of mutationsList) for (let node of mutation.addedNodes) { ++counters[1]; if (node.nodeName != 'TR' || (ua = node.querySelector('textarea#ua-data')) == null) continue; console.log('Found UA data by trigger:', counters, (Date.now() - timeStamp) / 1000); clearTimeout(timer); return feedData(); } }), timer = setTimeout(mo => { mo.disconnect() }, 10000, mo); mo.observe(container, { childList: true }); } } } break; } } document.body.querySelectorAll('div.tags').forEach(div => { setElemHandlers(div, function(evt, tags) { const a = evt.currentTarget.parentNode.querySelector('a[href^="torrents.php?id="]'); if (a == null) return false; if (evt.ctrlKey && ajaxApiKey) { const tagsElement = evt.currentTarget, groupId = parseInt(new URLSearchParams(a.search).get('id')) || undefined; if (groupId) queryAjaxAPI('addtag', { groupid: groupId }, { tagname: tags.toString() }).then(function(response) { console.log(response); if (!['added', 'voted'].some(key => response[key].length > 0)) return; queryAjaxAPI('torrentgroup', { id: groupId }).then(function(response) { if (!response.group.tags) { document.location.reload(); return; } const urlParams = new URLSearchParams(tagsElement.childElementCount > 0 ? tagsElement.children[0].search : { action: 'advanced', searchsubmit: 1, }); while (tagsElement.childNodes.length > 0) tagsElement.removeChild(tagsElement.childNodes[0]); for (let tag of response.group.tags) { if (tagsElement.childElementCount > 0) tagsElement.append(', '); const a = document.createElement('A'); for (let param of fieldNames) if (urlParams.has(param)) urlParams.set(param, a.textContent = tag); a.setAttribute('href', 'torrents.php?' + urlParams.toString()); tagsElement.append(a); } }); }); } else document.location.assign(a.href + '&tags=' + encodeURIComponent(JSON.stringify(tags))); }) }); (function() { const tagsBox = document.body.querySelector('div.box_tags'); if (tagsBox != null) setElemHandlers(tagsBox, function(evt, tags) { function fallBack() { const input = document.getElementById('tagname'); if (input == null) throw 'Tags input not found'; input.value = tags.toString(); input.scrollIntoView({ behavior: 'smooth', block: 'start' }); //if (input.nextElementSibling != null) input.nextElementSibling.click(); } const groupId = document.location.pathname == '/torrents.php' && parseInt(new URLSearchParams(document.location.search).get('id')) || undefined; if (!groupId) return fallBack(); if (ajaxApiKey) { const tagsElement = evt.currentTarget.querySelector('ul') || evt.currentTarget.querySelector('ol'); if (tagsElement != null) queryAjaxAPI('addtag', { groupid: groupId }, { tagname: tags.toString() }).then(function(response) { console.log(response); if (['added', 'voted'].some(key => response[key].length > 0)) document.location.reload(); // queryAjaxAPI('torrentgroup', { id: groupId }).then(function(response) { // if (!response.group.tags) { // document.location.reload(); // return; // } // let a = tagsElement.querySelector('li > a'); // const urlParams = new URLSearchParams(a != null ? a.search : undefined); // while (tagsElement.childNodes.length > 0) tagsElement.removeChild(tagsElement.childNodes[0]); // for (let tag of response.group.tags) { // urlParams.set('taglist', (a = document.createElement('A')).textContent = tag); // a.setAttribute('href', 'torrents.php?' + encodeURIComponent(urlParams.toString())); // const li = document.createElement('LI'); // li.append(a); // tagsElement.append(li); // } // }); }); else fallBack(); } else fallBack(); }); else return; const head = tagsBox.querySelector('div.head'); if (head == null) return; const span = document.createElement('SPAN'), a = document.createElement('A'); span.style.float = 'right'; a.className = 'brackets'; a.textContent = 'Copy'; a.href = '#'; a.onclick = function(evt) { let tags = getTagsFromIterable(tagsBox.querySelectorAll('* > li > a')); if (tags.length <= 0) return false; GM_setClipboard(tags.join(', '), 'text'); evt.currentTarget.style.color = isDarkTheme ? 'darkgreen' : 'lightgreen'; setTimeout(elem => {elem.style.color = null }, 1000, evt.currentTarget); return false; }; span.append(a); head.append(span); })(); if (menuHooks > 0) createMenu(); function inputDataHandler(evt) { switch (evt.type) { case 'paste': var tags = evt.clipboardData; break; case 'drop': tags = evt.dataTransfer; break; } if (tags) tags = tags.getData('text/plain'); else return; //if (tags) tags = tags.split(/[\r\n\;\|\>]+|,(?:\s*&)?/).map(expr => expr.trim()).filter(Boolean); else return; if (tags.length > 0) switch (evt.type) { case 'paste': tags = new TagManager(tags); break; case 'drop': tags = new TagManager(evt.currentTarget.value, tags); break; } else return; if (tags.length > 0) tags = tags.toString(); else return; evt.stopPropagation(); switch (evt.type) { case 'paste': { const cursor = evt.currentTarget.selectionStart + tags.length; evt.currentTarget.value = evt.currentTarget.value.slice(0, evt.currentTarget.selectionStart) + tags + evt.currentTarget.value.slice(evt.currentTarget.selectionEnd); evt.currentTarget.selectionEnd = evt.currentTarget.selectionStart = cursor; break; } case 'drop': evt.currentTarget.value = tags; break; } return false; } for (let input of document.body.querySelectorAll(fieldNames.map(name => `input[name="${name}"]`).join(', '))) input.onpaste = input.ondrop = inputDataHandler; })();