您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Adds features to PlanetRomeo user and visitor tiles.
当前为
// ==UserScript== // @name Romeo Additions // @namespace https://greasyfork.org/en/users/723211-ray/ // @version 1.3 // @description Adds features to PlanetRomeo user and visitor tiles. // @description:de Neue Funktionen für PlanetRomeo Benutzer- und Besucherkacheln. // @author -Ray-, Djamana // @include *://*.planetromeo.com/* // @grant GM_addStyle // @require https://code.jquery.com/jquery-3.5.1.slim.min.js // ==/UserScript== // ==== Dependencies ==== /*! waitForKeyElements | https://gist.github.com/BrockA/2625891 */ /*--- waitForKeyElements(): A utility function, for Greasemonkey scripts, that detects and handles AJAXed content. Usage example: waitForKeyElements ( "div.comments" , commentCallbackFunction ); //--- Page-specific function to do what we want when the node is found. function commentCallbackFunction (jNode) { jNode.text ("This comment changed by waitForKeyElements()."); } IMPORTANT: This function requires your script to have loaded jQuery. */ function waitForKeyElements( selectorTxt, /* Required: The jQuery selector string that specifies the desired element(s). */ actionFunction, /* Required: The code to run when elements are found. It is passed a jNode to the matched element. */ bWaitOnce, /* Optional: If false, will continue to scan for new elements even after the first match is found. */ iframeSelector /* Optional: If set, identifies the iframe to search. */ ) { var targetNodes, btargetsFound; if (typeof iframeSelector == "undefined") targetNodes = $(selectorTxt); else targetNodes = $(iframeSelector).contents() .find(selectorTxt); if (targetNodes && targetNodes.length > 0) { btargetsFound = true; /*--- Found target node(s). Go through each and act if they are new. */ targetNodes.each(function () { var jThis = $(this); var alreadyFound = jThis.data('alreadyFound') || false; if (!alreadyFound) { //--- Call the payload function. var cancelFound = actionFunction(jThis); if (cancelFound) btargetsFound = false; else jThis.data('alreadyFound', true); } }); } else { btargetsFound = false; } //--- Get the timer-control variable for this selector. var controlObj = waitForKeyElements.controlObj || {}; var controlKey = selectorTxt.replace(/[^\w]/g, "_"); var timeControl = controlObj[controlKey]; //--- Now set or clear the timer as appropriate. if (btargetsFound && bWaitOnce && timeControl) { //--- The only condition where we need to clear the timer. clearInterval(timeControl); delete controlObj[controlKey] } else { //--- Set a timer, if needed. if (!timeControl) { timeControl = setInterval(function () { waitForKeyElements(selectorTxt, actionFunction, bWaitOnce, iframeSelector ); }, 300 ); controlObj[controlKey] = timeControl; } } waitForKeyElements.controlObj = controlObj; } // ==== CSS ==== GM_addStyle(` #visits > .layer__container--wider { width:unset; max-width:1227px; } div[class*='tile--loading--'] .tile__image { background-image:url(/assets/05c2dc53b86dcd7abdb1d8a50346876b.svg); } .tile__bar { position:absolute; bottom:0; right:0; visibility:hidden; } .tile__bar_action { background:rgba(0,0,0,0.4); display: inline-block; color:white; margin-left: 1px; padding: 0.25rem 0.5rem; } .tile__bar_action:hover { background-color:#00A3E4; } .tile__bar_action:active { background-color:#06648B; } .tile__link:hover .tile__bar { visibility:visible; } `); // ==== Script ==== (function () { 'use strict'; proxyXHR(); })(); function toggleTile(tile, tileImage, hide) { tile.css("filter", hide ? "blur(1px) grayscale(100%) opacity(25%)" : ""); tileImage.css("filter", hide ? "blur(16px) contrast(66%)" : ""); } function proxyXHR() { // Added from "Poor Men's Plus" as requested by Djamana let oldXHROpen = window.XMLHttpRequest.prototype.open; window.XMLHttpRequest.prototype.open = function (method, url, async, user, password) { // do something with the method, url and etc. this.addEventListener("load", () => { // do something with the response text isResponseAString = (typeof this.response === "string") try { this.xhr = this.response if (isResponseAString) this.xhr = JSON.parse(this.xhr) // remove displaylimit in visitors tab <- that's the core patch ! if (this.xhr.items_limited) delete this.xhr.items_limited; try { // cosmetic patch #1 (not really needed) if (this.xhr.is_plus == false) { this.xhr.is_plus = true this.is_free_plus = true // maybe not needed this.xhr.payment_group = "PLUS" } // cosmetic patch #2 (not really needed) if (this.xhr.inferface) { this.xhr.show_plus_badge = true // maybe not needed this.xhr.show_ads = false // maybe not needed } // cosmetic patch #3 (not really needed) if (this.xhr.show_plus_badge) { this.xhr.show_plus_badge = true // maybe not needed } } catch (e) { console.log("RomeoAdditions > restoreVisitorTiles (cosmetic patches) - ERROR: " + e) } // make responseText writeable Object.defineProperty(this, 'responseText', { writable: true }); // set responseText if (isResponseAString) this.responseText = JSON.stringify(this.xhr) else this.responseText = this.xhr } catch (e) { if (!(e instanceof SyntaxError)) console.log("RomeoAdditions > restoreVisitorTiles - ERROR: " + e) } }); return oldXHROpen.apply(this, arguments); } } // ---- Settings ---- const settingNs = "RA_SETTINGS:"; function getHiddenMaxAge() { return parseInt(localStorage.getItem(settingNs + "hiddenMaxAge")) || 99; } function getHiddenMinAge() { return parseInt(localStorage.getItem(settingNs + "hiddenMinAge")) || 18; } function getHiddenUsers() { return JSON.parse(localStorage.getItem(settingNs + "hiddenUsers")) || []; } function getUserHidden(username, age) { return age < getHiddenMinAge() || age > getHiddenMaxAge() || getHiddenUsers().includes(username); } function setHiddenMaxAge(value) { localStorage.setItem(settingNs + "hiddenMaxAge", value); } function setHiddenMinAge(value) { localStorage.setItem(settingNs + "hiddenMinAge", value); } function setUserHidden(username, hide) { let hiddenUsers = getHiddenUsers(); if (hide) { if (hiddenUsers.length < hiddenUsers.push(username)) { hiddenUsers.sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase())); localStorage.setItem(settingNs + "hiddenUsers", JSON.stringify(hiddenUsers)); } } else { const prevLength = hiddenUsers.length; hiddenUsers = hiddenUsers.filter(e => e != username); if (prevLength > hiddenUsers.length) localStorage.setItem(settingNs + "hiddenUsers", JSON.stringify(hiddenUsers)); } } // ---- Language ---- const _strings = { "display": { "de": "Anzeige", "en": "Display" }, "extensionTitle": { "en": "Romeo Additions" }, "hideUsers": { "de": "Benutzer ausblenden", "en": "Hide users" }, "maxAge": { "de": "Maximales Alter", "en": "Maximal age" }, "minAge": { "de": "Minimales Alter", "en": "Minimal age" }, "toggleUser": { "de": "Benutzer ausblenden / anzeigen", "en": "Hide / show user" }, "viewFullImage": { "de": "Bild vergrößern", "en": "View full image" }, } function getString(key) { const lang = document.documentElement.getAttribute("lang") || "en"; const translations = _strings[key]; if (translations) return translations[lang] || translations["en"] || "%" + key + "%"; return "%" + key + "%"; } // ---- Tile handler ---- waitForKeyElements( "a.tile__link, " + "div.js-profiles a.listresult, " + "div.js-wrapper a.tile__link > div.tile__image, " + "#visits a.listresult", jNode => { // Determine tile properties. const tile = jNode.parent(".tile"); const tileLink = tile.children(".tile__link").first(); if (!tileLink) // ignore placeholders return; const tileImage = tileLink.children(".tile__image").first(); const age = parseInt(tileLink.find(".typo-figure").first().text()); const username = tileImage.attr("aria-label"); // Add action bar. const tileBar = $("<div class='tile__bar'></div>").appendTo(tileLink); addShowImageAction(tileBar, tileImage); addToggleUserAction(tileBar, tile, tileImage, username); // Set initial state. toggleTile(tile, tileImage, getUserHidden(username, age)); }); function addShowImageAction(tileBar, tileImage) { const style = tileImage.attr("style"); if (!style) return; const url = style.substring(style.lastIndexOf("/") + 1, style.lastIndexOf(")")); if (url.endsWith(".svg")) // ignore "no photo" placeholders return; const origUrl = "/img/usr/original/0x0/" + url; $("<a class='tile__bar_action' href='" + origUrl + "' title='" + getString("viewFullImage") + "'><span class='icon icon-picture'></a>") .on("click", e => { e.preventDefault(); window.open(origUrl, "_blank"); }) .appendTo(tileBar); } function addToggleUserAction(tileBar, tile, tileImage, username) { $("<a class='tile__bar_action' href='#' title='" + getString("toggleUser") + "'><span class='icon icon-hide-visit'></a>") .on("click", e => { e.preventDefault(); hide = !getUserHidden(username); setUserHidden(username, hide); toggleTile(tile, tileImage, hide); }) .appendTo(tileBar); } // ---- Settings handler ---- waitForKeyElements("li.js-settings > div.accordion > ul", jNode => { let itemClass = jNode.find("a").attr("class"); $("<li><div><a class='" + itemClass + "'>" + getString("extensionTitle") + "</a></div></li>") .on("click", e => { // Force open the setting pane and clear any existing contents. $("#offcanvas-nav > .js-layer-content").addClass("is-open"); const pane = $(".js-side-content"); pane.empty(); // Add pane and list. pane.append(` <div class='layout layout--vertical layout--consume'> <div class='layout-item layout-item--consume layout layout--vertical'> <div class='layout-item settings__navigation p l-hidden-sm'> <div class='js-title typo-section-navigation'>` + getString("extensionTitle") + `</div> </div> <div class='layout-item layout-item--consume'> <div class='js-content js-scrollable fit scrollable'> <div class="p"> <div class="settings__key"> <div> <span>` + getString("hideUsers") + `</span> </div> <div class="separator separator--alt separator--narrow [ mb ] "></div> <div class="settings__key"> <div class="layout layout--v-center"> <div class="layout-item [ 6/12--sm ]"> <span>` + getString("minAge") + `</span> </div> <div class="layout-item [ 6/12--sm ]"> <input class="input input--block" id="ra_hiddenMinAge" type="number" min="18" max="99"/> </div> </div> </div> <div class="settings__key"> <div class="layout layout--v-center"> <div class="layout-item [ 6/12--sm ]"> <span>` + getString("maxAge") + `</span> </div> <div class="layout-item [ 6/12--sm ]"> <input class="input input--block" id="ra_hiddenMaxAge" type="number" min="18" max="99"/> </div> </div> </div> <div class="settings__key"> <div class="js-grid-stats-selector"> <div> <ul class="js-list tags-list tags-list--centered" id="ra_hiddenUsers"/> </div> </div> </div> </div> </div> </div> </div> </div> </div>`); // Handle age. let minAge = getHiddenMinAge(); let maxAge = getHiddenMaxAge(); let inMinAge = $("#ra_hiddenMinAge"); let inMaxAge = $("#ra_hiddenMaxAge"); inMinAge.val(minAge); inMaxAge.val(maxAge); inMinAge.on("change", e => { minAge = parseInt(e.target.value); setHiddenMinAge(minAge); if (minAge > maxAge) { maxAge = minAge; setHiddenMaxAge(maxAge); inMaxAge.val(maxAge); } }); inMaxAge.on("change", e => { maxAge = parseInt(e.target.value); setHiddenMaxAge(maxAge); if (maxAge < minAge) { minAge = maxAge; setHiddenMinAge(minAge); inMinAge.val(minAge); } }); // Handle user list. const ul = $("#ra_hiddenUsers"); for (const item of getHiddenUsers()) { const li = $("<li class='tags-list__item'/>").appendTo(ul); $("<a class='js-tag ui-tag ui-tag--removable ui-tag--selected' href='#'><span class='ui-tag__label'>" + item + "</span></a>") .on("click", e => { setUserHidden(e.target.innerHTML, false); $(e.target).closest(".tags-list__item").css("display", "none"); // breaks if fully removed? }) .appendTo(li); }; }) .appendTo(jNode); });