NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript== // @name Greasy Fork tweaks // @namespace almaceleste // @version 0.6.3 // @description various tweaks for greasyfork.org site for enhanced usability and additional features // @description:ru различные твики для сайта greasyfork.org для повышения удобства использования и дополнительных функций // @author (ɔ) almaceleste (https://almaceleste.github.io) // @license AGPL-3.0-or-later; http://www.gnu.org/licenses/agpl.txt // @icon https://greasyfork.org/assets/blacklogo16-bc64b9f7afdc9be4cbfa58bdd5fc2e5c098ad4bca3ad513a27b15602083fd5bc.png // @icon64 https://greasyfork.org/assets/blacklogo96-e0c2c76180916332b7516ad47e1e206b42d131d36ff4afe98da3b1ba61fd5d6c.png // @homepageURL https://greasyfork.org/en/users/174037-almaceleste // @homepageURL https://openuserjs.org/users/almaceleste // @homepageURL https://github.com/almaceleste/userscripts // @supportURL https://github.com/almaceleste/userscripts/issues // @updateURL https://github.com/almaceleste/userscripts/raw/master/src/Greasy_Fork_tweaks.user.js // @downloadURL https://github.com/almaceleste/userscripts/raw/master/src/Greasy_Fork_tweaks.user.js // @downloadURL https://openuserjs.org/install/almaceleste/Greasy_Fork_tweaks.user.js // @require https://code.jquery.com/jquery-3.3.1.js // @require https://code.jquery.com/ui/1.12.1/jquery-ui.js // @require https://openuserjs.org/src/libs/sizzle/GM_config.js // @grant GM_getValue // @grant GM_setValue // @grant GM_registerMenuCommand // @grant GM_openInTab // @grant GM_getResourceText // @resource css https://github.com/almaceleste/userscripts/raw/master/css/default.css // @match https://greasyfork.org/*/users/* // @match https://greasyfork.org/*/scripts* // ==/UserScript== // ==OpenUserJS== // @author almaceleste // ==/OpenUserJS== const route = {}; route.userpage = /^\/.*\/users\/.*/; route.scriptpage = /^\/.*\/scripts\/[^\/]*$/; //(?!\/)*/; //.*/; route.searchpage = /^\/.*\/scripts$/; const maincontainer = 'body > .width-constraint'; const listitem = '.script-list > li'; const separator = '.name-description-separator'; const scriptversion = 'data-script-version'; const scriptrating = 'dd.script-list-ratings'; const scriptstats = '.inline-script-stats'; const dailyinstalls = '.script-list-daily-installs'; const totalinstalls = '.script-list-total-installs'; const createddate = '.script-list-created-date'; const updateddate = '.script-list-updated-date'; const scripturl = 'article h2 a'; const userprofile = {}; userprofile.path = '#user-profile'; userprofile.header = 'body > div.width-constraint > section:first-of-type > h2:first-of-type'; const sections = {}; sections.controlpanel = '#control-panel'; sections.discussions = '#user-discussions-on-scripts-written'; sections.scriptsets = 'section:has(h3:contains("Script Sets"))'; const configId = 'greasyforktweaksCfg'; const iconUrl = GM_info.script.icon64; const pattern = {}; pattern[`#${configId}`] = /#configId/g; pattern[`${iconUrl}`] = /iconUrl/g; let css = GM_getResourceText('css'); Object.keys(pattern).forEach((key) => { css = css.replace(pattern[key], key); }); const windowcss = css; const iframecss = ` height: 590px; width: 435px; border: 1px solid; border-radius: 3px; position: fixed; z-index: 9999; `; GM_registerMenuCommand(`${GM_info.script.name} Settings`, () => { GM_config.open(); GM_config.frame.style = iframecss; }); GM_config.init({ id: `${configId}`, title: `${GM_info.script.name} ${GM_info.script.version}`, fields: { width: { section: ['', 'All pages options'], label: 'page width', labelPos: 'left', type: 'text', default: '70%', }, version: { label: 'add script version number', labelPos: 'right', type: 'checkbox', default: true, }, ratingscore: { label: 'display script rating score', labelPos: 'right', type: 'checkbox', default: true, }, updates: { label: 'display update checks information', labelPos: 'right', type: 'checkbox', default: true, }, updatesperiods: { type: 'multiselect', options: { daily: 1, weekly: 7, monthly: 30, total: 0 }, default: {daily: 1, weekly: 7, monthly: 30, total: 0}, }, installs: { label: 'display alternative installs information', labelPos: 'right', type: 'checkbox', default: true, }, installsperiods: { type: 'multiselect', options: { daily: 1, weekly: 7, monthly: 30, total: 0 }, default: {daily: 1, weekly: 7, monthly: 30, total: 0}, }, compact: { label: 'compact script information', labelPos: 'right', type: 'checkbox', default: true, }, userprofile: { section: ['', 'User page options (own page and other users`)'], label: 'collapse user profile info on user page', labelPos: 'right', type: 'checkbox', default: true, }, controlpanel: { label: 'collapse control panel on user page', labelPos: 'right', type: 'checkbox', default: true, }, discussions: { label: 'collapse discussions on user page', labelPos: 'right', type: 'checkbox', default: true, }, scriptsets: { label: 'collapse script sets on user page', labelPos: 'right', type: 'checkbox', default: true, }, displayimage: { label: 'display script image (experimental)', labelPos: 'right', type: 'checkbox', default: true, }, newtab: { section: ['', 'Other options'], label: 'open script page in new tab', labelPos: 'right', type: 'checkbox', default: true, }, background: { label: 'open new tab in background', labelPos: 'right', type: 'checkbox', default: false, }, insert: { label: 'insert new tab next to the current instead of the right end', labelPos: 'right', type: 'checkbox', default: true, }, setParent: { label: 'return to the current tab after new tab closed', labelPos: 'right', type: 'checkbox', default: true, }, support: { section: ['', 'Support'], label: 'almaceleste.github.io', title: 'more info on almaceleste.github.io', type: 'button', click: () => { GM_openInTab('https://almaceleste.github.io', { active: true, insert: true, setParent: true }); } }, }, types: { multiselect: { default: {}, toNode: function() { let field = this.settings, value = this.value, options = field.options, id = this.id, configId = this.configId, labelPos = field.labelPos, create = this.create; // console.log('toNode:', field, value, options); function addLabel(pos, labelEl, parentNode, beforeEl) { if (!beforeEl) beforeEl = parentNode.firstChild; switch (pos) { case 'right': case 'below': if (pos == 'below') parentNode.appendChild(create('br', {})); parentNode.appendChild(labelEl); break; default: if (pos == 'above') parentNode.insertBefore(create('br', {}), beforeEl); parentNode.insertBefore(labelEl, beforeEl); } } let retNode = create('div', { className: 'config_var multiselect', id: `${configId}_${id}_var`, title: field.title || '' }), firstProp; // Retrieve the first prop for (let i in field) { firstProp = i; break; } let label = field.label ? create('label', { className: 'field_label', id: `${configId}_${id}_field_label`, for: `${configId}_field_${id}`, }, field.label) : null; let wrap = create('ul', { id: `${configId}_field_${id}` }); this.node = wrap; for (const key in options) { // console.log('toNode:', key); const inputId = `${configId}_${id}_${key}_checkbox`; const li = wrap.appendChild(create('li', { })); li.appendChild(create('input', { checked: value.hasOwnProperty(key), id: inputId, type: 'checkbox', value: options[key], })); li.appendChild(create('label', { className: 'option_label', for: inputId, }, key)); } retNode.appendChild(wrap); if (label) { // If the label is passed first, insert it before the field // else insert it after if (!labelPos) labelPos = firstProp == "label" ? "left" : "right"; addLabel(labelPos, label, retNode); } return retNode; }, toValue: function() { let node = this.node, id = node.id, options = this.settings.options, rval = {}; // console.log('toValue:', node, options, this); if (!node) return rval; let nodelist = node.querySelectorAll(`#${id} input:checked`); // console.log('nodelist:', document.querySelectorAll(`#${id} input:checked`), nodelist); nodelist.forEach((input) => { // console.log('toValue:', input); const value = input.value; const key = Object.keys(options).find((key) => options[key] == value); rval[key] = value; }); // console.log('toValue:', rval); return rval; }, reset: function() { let node = this.node, values = this.default; // console.log('reset:', node, values, Object.values(values)); const inputs = node.getElementsByTagName('input'); for (const index in inputs) { const input = inputs[index]; // console.log('reset:', input.value, Object.values(values).includes(input.value) || Object.values(values).includes(+input.value)); if (Object.values(values).includes(input.value) || Object.values(values).includes(+input.value)) { if (!input.checked) input.click(); } else { if (input.checked) input.click(); } } } } }, css: windowcss, events: { save: function() { GM_config.close(); } }, }); function arrow(element){ const arrow = $(` <svg viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"> <style> .collapsed { transform: rotate(0deg); } .expanded { transform: rotate(180deg); } </style> <text x='0' y='18'>▼</text> </svg> `).css({ fill: 'whitesmoke', height: '20px', width: '30px', }); $(element).append(arrow); } function collapse(element, header){ $(element).css({ cursor: 'pointer', }); arrow($(element).find(header)); $(element).accordion({ collapsible: true, active: false, beforeActivate: () => { rotate($(element).find('svg')); } }); } function rotate(element){ if ($(element).hasClass('expanded')) { $(element).animate({ transform: 'rotate(0deg)', }); } else { $(element).animate({ transform: 'rotate(180deg)', }); } $(element).toggleClass('expanded'); } function compact(first, second){ $('dt' + first).each(function(){ $(this).css('display','none'); $(this).siblings('dt' + second).find('span').append(' (' + $(this).find('span').text() + ')'); }); $('dd' + first).each(function(){ $(this).css('display','none'); $(this).siblings('dd' + second).find('span').append(' (' + $(this).find('span').text() + ')'); }); } function newtaber(e){ const options = {active: !GM_config.get('background'), insert: GM_config.get('insert'), setParent: GM_config.get('setParent')}; e.preventDefault(); e.stopPropagation(); GM_openInTab(e.target.href, options); } function getjson(url){ fetch(url).then((response) => { // console.log('getjson:', response); response.json().then((json) => { console.log('getjson:', json); }); }); } function sumlast(array, number, prop){ if (number != 0) { array = array.slice(-number); } let result = array.reduce((sum, next) => { return sum + next[prop]; }, 0); return result; } function getjsondata(url, prop, periods, target){ fetch(url).then((response) => { response.json().then((json) => { const data = Object.values(json); for (const period in periods) { const result = sumlast(data, periods[period], prop); $('<span></span>', { title: period, }).text(result).appendTo(target); } }); }); } function doCompact(){ if (GM_config.get('compact')){ $(scriptstats).children().css('width','auto'); compact(totalinstalls, dailyinstalls); compact(updateddate, createddate); } } function doRating(page){ switch (page) { case 'user': case 'search': $(scriptrating).each(function(){ let rating = $(this).attr('data-rating-score'); $(this).children('span').after(` - ${rating}`); }); break; case 'script': $(scriptrating).each(function(){ const author = '#script-stats > .script-show-author > span > a'; const url = `${window.location.origin}${$(author).attr('href')}`; const scriptId = '#script-content > .script-in-sets > input[name="script_id"]'; const id = $(scriptId).val(); fetch(url).then((response) => { response.text().then((data) => { const parser = new DOMParser(); const doc = parser.parseFromString(data, 'text/html'); const el = doc.querySelector(`#user-script-list li[data-script-id="${id}"]`); $(this).children('span').after(` - ${el.dataset.scriptRatingScore}`); }); }); }); break; default: break; } } function doCollapse(){ Object.keys(sections).forEach((section) => { if (GM_config.get(section)) { collapse(sections[section], 'header h3'); } }); } function doProfile(){ $(userprofile.path).slideUp(); arrow($(userprofile.header)); $(userprofile.header).css({ cursor: 'pointer', }) .click(function(){ $(userprofile.path).slideToggle(); rotate($(this).find('svg')); }); } function doList(){ const version = GM_config.get('version'); const newtab = GM_config.get('newtab'); $(listitem).each(function(){ if (version){ $(this).find(separator).before(` ${$(this).attr(scriptversion)}`); } if (newtab){ $(this).find(separator).prev('a').click(newtaber); } }); } function doUpdates(page){ let parent, target, url; switch (page) { case 'user': case 'search': parent = listitem; target = scriptstats; break; case 'script': parent = `#script-meta`; target = `#script-stats`; url = `${window.location.href}/stats.json`; break; default: break; } $(parent).each((index, item) => { $(item).css({ maxWidth: 'unset', }); const stats = $(item).find(target); if (page != 'script') url = `${$(item).find(scripturl).attr('href')}/stats.json`; const updatesperiods = GM_config.get('updatesperiods'); if (Object.keys(updatesperiods).length > 0) { const dt = $('<dt></dt>', { class: 'script-list-update-checks', style: 'cursor: default', width: 'auto', }); let text = 'Updates ('; let title = 'Update checks ('; for (const period in updatesperiods) { text += `${period.charAt(0)}|`; title +=`${period}|`; }; text = text.replace(/\|$/, '):'); title = title.replace(/\|$/, ')'); dt.text(text).attr('title', title).append(` <style> .inline-script-stats dt, .inline-script-stats dd, .inline-script-stats span { cursor: default; width: auto !important; } .script-list-update-checks span { padding: 0 5px; } .script-list-update-checks span:not(:last-child) { border-right: 1px dotted whitesmoke; } </style>`).appendTo($(stats)); const updatechecks = $('<dd></dd>', { class: 'script-list-update-checks', }); $(stats).append(updatechecks); getjsondata(url, 'update_checks', updatesperiods, $(updatechecks)); } }); } function doInstalls(page){ let daily, parent, target, total, url; switch (page) { case 'user': case 'search': daily = dailyinstalls; parent = listitem; target = scriptstats; total = totalinstalls; break; case 'script': daily = '.script-show-daily-installs'; parent = `#script-meta`; target = `#script-stats`; total = '.script-show-total-installs'; url = `${window.location.href}/stats.json`; break; default: break; } $(daily).css({ display: 'none', }); $(total).css({ display: 'none', }); $(parent).each((index, item) => { $(item).css({ maxWidth: 'unset', }); const stats = $(item).find(target); if (page != 'script') url = `${$(item).find(scripturl).attr('href')}/stats.json`; const installsperiods = GM_config.get('installsperiods'); if (Object.keys(installsperiods).length > 0) { const dt = $('<dt></dt>', { class: 'script-list-installs', style: 'cursor: default', width: 'auto', }); let text = 'Installs ('; let title = 'Installs ('; for (const period in installsperiods) { text += `${period.charAt(0)}|`; title +=`${period}|`; }; text = text.replace(/\|$/, '):'); title = title.replace(/\|$/, ')'); dt.text(text).attr('title', title).append(` <style> .inline-script-stats dt, .inline-script-stats dd, .inline-script-stats span { cursor: default; width: auto !important; } .script-list-installs span { padding: 0 5px; } .script-list-installs span:not(:last-child) { border-right: 1px dotted whitesmoke; } </style>`).appendTo($(stats)); const installs = $('<dd></dd>', { class: 'script-list-installs', }); $(stats).append(installs); getjsondata(url, 'installs', installsperiods, $(installs)); } }); } function isImage(url){ const types = ['apng', 'bmp', 'gif', 'ico', 'jfi', 'jfif', 'jif', 'jpe', 'jpeg', 'jpg', 'pjp', 'pjpeg', 'png', 'psd', 'svg', 'tif', 'tiff', 'webp']; const ext = url.split('/').pop().split('#').shift().split('?').shift().split('.').pop(); return types.includes(ext); } function displayImage(){ $(listitem).each((index, item) => { const url = `${$(item).find(scripturl).attr('href')}`; const height = $(item).height(); $(item).children('article').css({ display: 'inline-block', margin: '0', width: '75%', }); const div = $('<div></div>').appendTo($(item)).css({ display: 'inline-block', height: `${height}px`, float: 'right', margin: '0', overflow: 'hidden', padding: '5px', width: '20%', }); fetch(url).then((response) => { response.text().then((data) => { const parser = new DOMParser(); const doc = parser.parseFromString(data, 'text/html'); const el = doc.querySelector(`#additional-info img:first-child`); let src = el.getAttribute('src'); if (el.parentElement.hasAttribute('href')) { const href = el.parentElement.getAttribute('href'); if (isImage(href)) src = href; } const width = $(div).width(); const img = $('<img/>', { src: src, width: `${width}px`, }); $(div).append(` <style> ${listitem}::after { clear: both; } </style> `).append(img); }); }); }); } function router(path){ const ratingscore = GM_config.get('ratingscore'); const displayimage = GM_config.get('displayimage'); const installs = GM_config.get('installs'); const updates = GM_config.get('updates'); const userprofile = GM_config.get('userprofile'); switch (true) { case route.userpage.test(path): console.log('router:', 'user', path); if (userprofile) doProfile(); doCollapse(); doCompact(); if (ratingscore) doRating('user'); doList(); if (installs) doInstalls('user'); if (updates) doUpdates('user'); if (displayimage) displayImage(); break; case route.searchpage.test(path): console.log('router:', 'search', path); if (ratingscore) doRating('search'); doCompact(); doList(); if (installs) doInstalls('search'); if (updates) doUpdates('search'); if (displayimage) displayImage(); break; case route.scriptpage.test(path): console.log('router:', 'script', path); if (ratingscore) doRating('script'); if (installs) doInstalls('script'); if (updates) doUpdates('script'); break; default: console.log('router:', 'default', path); break; } } (function() { 'use strict'; $(document).ready(() => { const width = GM_config.get('width'); $(maincontainer).css({ maxWidth: width, }); router(window.location.pathname); }); })();