// ==UserScript==
// @name Romeo Additions
// @namespace https://greasyfork.org/en/users/723211-ray/
// @version 1.2
// @description Adds some visitor-related features to PlanetRomeo.
// @description:de Fügt Besucher-bezogene Funktionen zu PlanetRomeo hinzu.
// @author -Ray-
// @include https://*.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(
// Overrides
"#visits > .layer__container--wider { width:unset; max-width:1227px; }" +
"div[class*='tile--loading--'] .tile__image { background-image:url(/assets/05c2dc53b86dcd7abdb1d8a50346876b.svg); }" +
// Custom styles
".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 ====
const settingNs = "RA_SETTINGS:";
function toggleTile(tile, tileImage, hide) {
tile.css("filter", hide ? "blur(1px) grayscale(100%) opacity(25%)" : "");
tileImage.css("filter", hide ? "blur(16px) contrast(66%)" : "");
}
function unblurTile(tile, tileLink, username) {
if (tile.hasClass("tile--blurred")) {
tile.removeClass("tile--blurred");
tileLink.attr("href", "/profile/" + username);
tileLink.append(
"<div class='bg-raise tile__info'>" +
" <div class='info info--middle txt-raise'>" +
" <div class='txt-truncate layout-item--consume'>" +
" <div class='lh-heading txt-truncate'>" +
" <div class='info__main-data'>" +
" <div class='info__username' title='" + username + "' lang>" + username + "</div>" +
" </div>" +
" </div>" +
" </div>" +
" </div>" +
"</div>" +
"<div class='tile__footprint'>" +
" <div class='tile__visit '> >1d </div>" +
"</div>");
}
}
// ---- Settings ----
var _visibleAgeMin = 0;
var _visibleAgeMax = 200;
function getHiddenUsers() {
return JSON.parse(localStorage.getItem(settingNs + "hiddenUsers")) || [];
}
function isUserHidden(username, age) {
return age < _visibleAgeMin || age > _visibleAgeMax || getHiddenUsers().includes(username);
}
function toggleUser(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 = {
"hiddenUsers": { "de": "Ausgeblendete Benutzer", "en": "Hidden users" },
"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");
// DEPRECATED: Restore oldest visitor's display names and links. Use Poor Men Plus instead to fully restore tiles.
//unblurTile(tile, tileLink, username);
// 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, isUserHidden(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-profile-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 = !isUserHidden(username);
toggleUser(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("hiddenUsers") + "</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("hiddenUsers") + "</div>" +
" </div>" +
" <div class='layout-item layout-item--consume'>" +
" <div class='js-content js-scrollable fit scrollable'>" +
" <div class='js-grid-stats-selector'>" +
" <div id='ra_hiddenusers'>" +
" <ul class='js-list tags-list tags-list--centered'/>" +
" </div>" +
" </div>" +
" </div>" +
" </div>" +
" </div>" +
"</div>");
const ul = $("#ra_hiddenusers > ul");
// Add item for each user.
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 => {
toggleUser(e.target.innerHTML, false);
$(e.target).closest(".tags-list__item").css("display", "none"); // breaks if fully removed?
})
.appendTo(li);
};
})
.appendTo(jNode);
});