// ==UserScript==
// @name Romeo Additions
// @name:de Romeo Additions
// @namespace https://greasyfork.org/en/users/723211-ray/
// @version 4.3.1
// @description Allows to hide users, display their information on tiles, and enhances the Radar.
// @description:de Ermöglicht das Verstecken von Benutzern, die Anzeige ihrer Details auf Kacheln, und verbessert den Radar.
// @author -Ray-, Djamana
// @match *://*.romeo.com/*
// @license MIT
// ==/UserScript==
// ==== Dependencies ====
// ---- https://github.com/CoeJoder/GM_wrench ----
function addCss(css) {
let style = document.createElement('style');
style.type = 'text/css';
if (style.styleSheet) {
style.styleSheet.cssText = css;
} else {
style.appendChild(document.createTextNode(css));
}
document.head.appendChild(style);
};
function waitForKeyElements(selectorOrFunction, callback, waitOnce, interval, maxIntervals) {
if (typeof waitOnce === 'undefined') {
waitOnce = true;
}
if (typeof interval === 'undefined') {
interval = 300;
}
if (typeof maxIntervals === 'undefined') {
maxIntervals = -1;
}
var targetNodes = (typeof selectorOrFunction === 'function')
? selectorOrFunction()
: document.querySelectorAll(selectorOrFunction);
var targetsFound = targetNodes && targetNodes.length > 0;
if (targetsFound) {
targetNodes.forEach(function (targetNode) {
var attrAlreadyFound = 'data-userscript-alreadyFound';
var alreadyFound = targetNode.getAttribute(attrAlreadyFound) || false;
if (!alreadyFound) {
var cancelFound = callback(targetNode);
if (cancelFound) {
targetsFound = false;
}
else {
targetNode.setAttribute(attrAlreadyFound, true);
}
}
});
}
if (maxIntervals !== 0 && !(targetsFound && waitOnce)) {
maxIntervals -= 1;
setTimeout(function () {
waitForKeyElements(selectorOrFunction, callback, waitOnce, interval, maxIntervals);
}, interval);
}
};
// ==== CSS ====
addCss(`
:root {
--message-line-clamp: 2;
--tile-headline-white-space: nowrap;
--tile-size-factor: 0;
--tile-size-group-factor: 0;
--tile-size-xxlarge: calc(100% / max(1, var(--tile-size-factor) + 1));
--tile-size-xlarge: calc(100% / max(1, var(--tile-size-factor) + 2));
--tile-size-large: calc(100% / max(1, var(--tile-size-factor) + 3));
--tile-size-medium: calc(100% / max(1, var(--tile-size-factor) + 4));
--tile-size-small: calc(100% / max(1, var(--tile-size-factor) + 5));
--tile-size-xsmall: calc(100% / max(1, var(--tile-size-factor) + 6));
--tile-size-xxsmall: calc(100% / max(1, var(--tile-size-factor) + 7));
--tile-size-group-large: calc(100% / max(1, var(--tile-size-group-factor) + 3));
--tile-size-group-medium: calc(100% / max(1, var(--tile-size-group-factor) + 4));
--tile-size-group-small: calc(100% / max(1, var(--tile-size-group-factor) + 5));
}
/* responsive tile size overrides (visitors) */
.grouped-tiles-small .tile {
width: var(--tile-size-group-large);
}
@media screen and (min-width: 768px)and (max-width:1023px) {
.grouped-tiles-small .tile {
width: var(--tile-size-group-medium);
}
}
@media screen and (min-width: 1024px) {
.grouped-tiles-small .tile {
width: var(--tile-size-group-small);
}
}
@media screen and (min-width: 35rem)and (max-width:48rem) {
.grouped-tiles-small .tile {
width: var(--tile-size-group-medium);
}
}
/* responsive tile size overrides (radar) */
@media screen and (min-width: 48rem) {
.search-results--big-tiles .search-results__item,
.search-results--mixed-tiles .search-results__item {
padding-bottom: var(--tile-size-xlarge) !important;
width: var(--tile-size-xlarge) !important;
}
.is-stream-opened .search-results--big-tiles .search-results__item,
.is-stream-opened .search-results--mixed-tiles .search-results__item {
padding-bottom: var(--tile-size-xlarge) !important;
width: var(--tile-size-xlarge) !important;
}
.is-filter-opened .search-results--big-tiles .search-results__item,
.is-filter-opened .search-results--mixed-tiles .search-results__item {
padding-bottom: var(--tile-size-xxlarge) !important;
width: var(--tile-size-xxlarge) !important;
}
.is-stream-opened .is-filter-opened .search-results--big-tiles .search-results__item,
.is-stream-opened .is-filter-opened .search-results--mixed-tiles .search-results__item {
padding-bottom: var(--tile-size-xxlarge) !important;
width: var(--tile-size-xxlarge) !important;
}
}
@media screen and (min-width: 60rem) {
.search-results--big-tiles .search-results__item,
.search-results--mixed-tiles .search-results__item {
padding-bottom: var(--tile-size-large) !important;
width: var(--tile-size-large) !important;
}
.is-filter-opened .search-results--big-tiles .search-results__item,
.is-filter-opened .search-results--mixed-tiles .search-results__item,
.is-stream-opened .search-results--big-tiles .search-results__item,
.is-stream-opened .search-results--mixed-tiles .search-results__item {
padding-bottom: var(--tile-size-xlarge) !important;
width: var(--tile-size-xlarge) !important;
}
.is-stream-opened .is-filter-opened .search-results--big-tiles .search-results__item,
.is-stream-opened .is-filter-opened .search-results--mixed-tiles .search-results__item {
padding-bottom: var(--tile-size-xlarge) !important;
width: var(--tile-size-xlarge) !important;
}
}
@media screen and (min-width: 80rem) {
.search-results--big-tiles .search-results__item,
.search-results--mixed-tiles .search-results__item {
padding-bottom: var(--tile-size-medium) !important;
width: var(--tile-size-medium) !important;
}
.is-filter-opened .search-results--big-tiles .search-results__item,
.is-filter-opened .search-results--mixed-tiles .search-results__item,
.is-stream-opened .search-results--big-tiles .search-results__item,
.is-stream-opened .search-results--mixed-tiles .search-results__item {
padding-bottom: var(--tile-size-large) !important;
width:var(--tile-size-large) !important;
}
.is-stream-opened .is-filter-opened .search-results--big-tiles .search-results__item,
.is-stream-opened .is-filter-opened .search-results--mixed-tiles .search-results__item {
padding-bottom: var(--tile-size-xlarge) !important;
width: var(--tile-size-xlarge) !important;
}
}
@media screen and (min-width: 100rem) {
.search-results--big-tiles .search-results__item,
.search-results--mixed-tiles .search-results__item {
padding-bottom: var(--tile-size-small) !important;
width: var(--tile-size-small) !important;
}
.is-filter-opened .search-results--big-tiles .search-results__item,
.is-filter-opened .search-results--mixed-tiles .search-results__item,
.is-stream-opened .search-results--big-tiles .search-results__item,
.is-stream-opened .search-results--mixed-tiles .search-results__item {
padding-bottom: var(--tile-size-medium) !important;
width: var(--tile-size-medium) !important;
}
.is-stream-opened .is-filter-opened .search-results--big-tiles .search-results__item,
.is-stream-opened .is-filter-opened .search-results--mixed-tiles .search-results__item {
padding-bottom: var(--tile-size-large) !important;
width: var(--tile-size-large) !important;
}
}
@media screen and (min-width: 120rem) {
.search-results--big-tiles .search-results__item,
.search-results--mixed-tiles .search-results__item {
padding-bottom: var(--tile-size-xsmall) !important;
width: var(--tile-size-xsmall) !important;
}
.is-filter-opened .search-results--big-tiles .search-results__item,
.is-filter-opened .search-results--mixed-tiles .search-results__item,
.is-stream-opened .search-results--big-tiles .search-results__item,
.is-stream-opened .search-results--mixed-tiles .search-results__item {
padding-bottom: var(--tile-size-small) !important;
width: var(--tile-size-small) !important;
}
.is-stream-opened .is-filter-opened .search-results--big-tiles .search-results__item,
.is-stream-opened .is-filter-opened .search-results--mixed-tiles .search-results__item {
padding-bottom: var(--tile-size-medium) !important;
width: var(--tile-size-medium) !important;
}
}
@media screen and (min-width: 140rem) {
.search-results--big-tiles .search-results__item,
.search-results--mixed-tiles .search-results__item {
padding-bottom: var(--tile-size-xxsmall) !important;
width: var(--tile-size-xxsmall) !important;
}
.is-filter-opened .search-results--big-tiles .search-results__item,
.is-filter-opened .search-results--mixed-tiles .search-results__item,
.is-stream-opened .search-results--big-tiles .search-results__item,
.is-stream-opened .search-results--mixed-tiles .search-results__item {
padding-bottom: var(--tile-size-xsmall) !important;
width: var(--tile-size-xsmall) !important;
}
.is-stream-opened .is-filter-opened .search-results--big-tiles .search-results__item,
.is-stream-opened .is-filter-opened .search-results--mixed-tiles .search-results__item {
padding-bottom: var(--tile-size-small) !important;
width: var(--tile-size-small) !important;
}
}
/* enhanced radar filter */
.js-quick-filter {
overflow-y: scroll;
}
/* tile description truncation */
.tile p[class^="SpecialText-"] {
white-space: var(--tile-headline-white-space);
}
/* message list truncation */
#messenger div[class^="TruncateBlock__Content-sc-"] {
-webkit-line-clamp: var(--message-line-clamp);
}
/* hide PLUS icon as PLUS is faked to enhance tiles */
.js-romeo-badge {
display: none;
}
/* wider visitor list */
#visits>.layer__container--wider {
max-width: 1227px;
width: unset;
}
/* hide PLUS message at bottom of visitor grid */
#visits div[class^="UnlockMoreVisitorsGrid"] {
display: none;
}
/* hide models on login page */
div[data-testid="desktop-image"] {
background-image: none;
}
/* allow clicking tile actions above stamps */
.listitem__highlight--footprint {
pointer-events: none;
}
/* tile action bar */
.tile__bar {
background: #121212;
border-radius: 5px 0 0 5px;
padding: 2px 1px 2px 2px;
box-shadow: 0 0 8px 0 #0000007F;
right: 0;
opacity: 0;
position: absolute;
top: 50%;
transform: translate(100%, -50%);
transition: opacity 0.2s ease-out, transform 0.2s ease-out;
z-index: 1;
}
@media screen and (hover:none) {
.tile__bar {
opacity: 0.5;
transform: translate(0%, -50%);
}
}
.tile__bar_action {
border-radius: 3px;
color: white;
display: block;
padding: 6px;
}
.tile__bar_action:hover {
background-color: #00A3E4;
}
.tile__bar_action:active {
background-color: #06648B;
}
.tile:hover .tile__bar {
opacity: 0.5;
transform: translate(0, -50%);
}
.tile:hover .tile__bar:hover {
opacity: 1;
}
/* profile preview */
#ra_profile_wrapper {
background-color: black;
display: grid;
grid-template-rows: auto auto;
height: 100%;
}
#ra_profile_content {
display: grid;
font-family: Inter, Helvetica, Arial, "Open Sans", sans-serif;
grid-template-columns: auto 352px;
overflow-y: scroll;
word-break: break-word;
}
#ra_profile_left {
background: #121212;
overflow-y: scroll;
padding: 16px;
}
#ra_profile_right {
overflow-y: scroll;
padding: 16px;
}
.ra_profile_details:not(:first-child) {
border-top: 1px solid rgb(46, 46, 46);
margin-top: 1rem;
}
.ra_profile_summary {
padding: 1rem 0;
}
.ra_profile_keyvalue {
display: grid;
gap: 16px;
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
}
.ra_profile_keyvalue > :first-child {
color: rgba(255, 255, 255, 0.6);
text-align: right;
}
#ra_profile_text {
white-space: pre-line;
}
@media screen and (max-width: 767px) {
#ra_profile_content {
grid-template-columns: initial;
grid-template-rows: auto auto;
}
#ra_profile_left {
overflow-y: initial;
}
#ra_profile_right {
overflow-y: initial;
}
#ra_profile_pic {
width: 100%;
}
}
#version {
color: white;
}
`);
// ==== Script ====
function addElement(parent, html) {
parent.insertAdjacentHTML("beforeend", html);
return parent.lastChild;
}
function onElement(selector, callback) {
waitForKeyElements(selector, callback, false);
}
// ---- Language ----
const _strings = {
aboutMe: {
de: "Über mich",
en: "About Me"
},
age: {
de: "Alter",
en: "Age"
},
ageRange: {
de: "Altersspanne",
en: "Age range"
},
ageRangeValue: {
de: "Zwischen $from und $to",
en: "Between $from and $to"
},
analPosition: {
en: "Position"
},
analPosition_TOP_ONLY: {
de: "Nur Aktiv",
en: "Top only",
},
analPosition_MORE_TOP: {
de: "Eher Aktiv",
en: "More top",
},
analPosition_VERSATILE: {
de: "Flexibel",
en: "Versatile",
},
analPosition_MORE_BOTTOM: {
de: "Eher Passiv",
en: "More bottom",
},
analPosition_BOTTOM_ONLY: {
de: "Nur Passiv",
en: "Bottom only",
},
analPosition_NO: {
de: "Kein Anal",
en: "No anal",
},
beard: {
de: "Bart",
en: "Beard"
},
beard_DESIGNER_STUBBLE: {
de: "3-Tage-Bart",
en: "Designer stubble"
},
beard_FULL_BEARD: {
de: "Vollbart",
en: "Full beard"
},
beard_GOATEE: {
en: "Goatee"
},
beard_MOUSTACHE: {
de: "Schnauzer",
en: "Moustache"
},
beard_NO_BEARD: {
de: "Kein Bart",
en: "No beard"
},
bedAndBreakfast: {
en: "Bed & Breakfast"
},
bmi: {
en: "BMI"
},
bmiMildThin: {
de: "Leichtes Untergewicht",
en: "Mildly Thin"
},
bmiModerateThin: {
de: "Mäßiges Untergewicht",
en: "Moderately Thin"
},
bmiNormal: {
de: "Normal",
en: "Normal"
},
bmiObese1: {
de: "Adipositas I",
en: "Obese Class I",
},
bmiObese2: {
de: "Adipositas II",
en: "Obese Class II",
},
bmiObese3: {
de: "Adipositas III",
en: "Obese Class III"
},
bmiPreObese: {
de: "Präadipositas",
en: "Pre-Obese"
},
bmiSevereThin: {
de: "Starkes Untergewicht",
en: "Severely Thin"
},
bodyType: {
de: "Statur",
en: "Body Type"
},
bodyType_ATHLETIC: {
de: "Athletisch",
en: "Athletic",
},
bodyType_AVERAGE: {
de: "Normal",
en: "Average"
},
bodyType_BELLY: {
de: "Bauch",
en: "Belly"
},
bodyType_MUSCULAR: {
de: "Muskulös",
en: "Muscular"
},
bodyType_SLIM: {
de: "Schlank",
en: "Slim",
},
bodyType_STOCKY: {
de: "Stämmig",
en: "Stocky"
},
bodyHair: {
de: "Körperbehaarung",
en: "Body Hair"
},
bodyHair_AVERAGE: {
de: "Mittel behaart",
en: "Hairy"
},
bodyHair_LITTLE: {
de: "Wenig behaart",
en: "Not very hairy"
},
bodyHair_SHAVED: {
de: "Rasiert",
en: "Shaved"
},
bodyHair_SMOOTH: {
de: "Unbehaart",
en: "Smooth"
},
bodyHair_VERY_HAIRY: {
de: "Stark behaart",
en: "Very hairy"
},
concision: {
de: "Beschneidung",
en: "Concision"
},
concision_CUT: {
de: "Beschnitten",
en: "Cut"
},
concision_UNCUT: {
de: "Unbeschnitten",
en: "Uncut"
},
dick: {
de: "Schwanz",
en: "Dick"
},
dick_S: {
en: "S"
},
dick_M: {
en: "M"
},
dick_L: {
en: "L"
},
dick_XL: {
en: "XL"
},
dick_XXL: {
en: "XXL"
},
dirty: {
en: "Dirty"
},
dirty_NO: {
de: "Kein Dirty",
en: "No dirty"
},
dirty_WS_ONLY: {
de: "Ja, aber nur NS",
en: "WS only"
},
dirty_YES: {
en: "Dirty"
},
display: {
de: "Anzeige",
en: "Display"
},
distance: {
de: "Entfernung",
en: "Distance"
},
enhancedFilter: {
de: "Erweiterter Filter",
en: "Enhanced filter"
},
enhancedFilterDesc: {
de: "Erlaubt die Filterung von Radar-Ergebnissen ohne PLUS und fügt neue Filter hinzu.",
en: "Allows filtering of radar results without having PLUS adds new filters."
},
enhancedTiles: {
de: "Erweiterte Kacheln",
en: "Enhanced tiles"
},
enhancedTilesDesc: {
de: "Zeigt alle Details auf den Kacheln. Im Radar zeigt dies Benutzer mit großen Kacheln.",
en: "Shows all user details on tiles. The radar will display users with large tiles."
},
ethnicity: {
de: "Typ",
en: "Ethnicity"
},
ethnicity_ARAB: {
de: "Araber",
en: "Arab"
},
ethnicity_ASIAN: {
de: "Asiate",
en: "Asian"
},
ethnicity_BLACK: {
de: "Schwarz",
en: "Black"
},
ethnicity_CAUCASIAN: {
de: "Europäer",
en: "Caucasian"
},
ethnicity_INDIAN: {
de: "Inder",
en: "Indian"
},
ethnicity_LATIN: {
de: "Latino",
en: "Latin"
},
ethnicity_MEDITERRANEAN: {
de: "Südländer",
en: "Mediterranean"
},
ethnicity_MIXED: {
en: "Mixed"
},
eyeColor: {
de: "Augenfarbe",
en: "Eye Colour"
},
eyeColor_BLUE: {
de: "Blau",
en: "Blue"
},
eyeColor_BROWN: {
de: "Braun",
en: "Brown"
},
eyeColor_GREEN: {
de: "Grün",
en: "Green"
},
eyeColor_GREY: {
de: "Grau",
en: "Grey"
},
eyeColor_OTHER: {
de: "Sonstige",
en: "Other"
},
fetish: {
de: "Fetisch",
en: "Fetish"
},
fetish_BOOTS: {
en: "Boots"
},
fetish_CROSSDRESSING: {
de: "Cross-Dressing",
en: "Cross-dressing"
},
fetish_DRAG: {
de: "Dessous",
en: "Lingerie"
},
fetish_FORMAL: {
de: "Anzug",
en: "Formal dress"
},
fetish_JEANS: {
en: "Jeans"
},
fetish_LEATHER: {
de: "Leder",
en: "Leather"
},
fetish_LYCRA: {
en: "Lycra"
},
fetish_RUBBER: {
en: "Rubber"
},
fetish_SKATER: {
en: "Skater"
},
fetish_SKINS: {
en: "Skins & Punks"
},
fetish_SNEAKERS: {
en: "Sneakers & Socks"
},
fetish_SPORTS: {
de: "Sportsgear",
en: "Sports gear"
},
fetish_TECHNO: {
en: "Raver",
},
fetish_UNDERWEAR: {
de: "Unterwäsche",
en: "Underwear"
},
fetish_UNIFORM: {
en: "Uniform"
},
fetish_WORKER: {
de: "Handwerker",
en: "Worker"
},
fisting: {
de: "Fisten",
en: "Fisting"
},
fisting_ACTIVE: {
de: "FF Aktiv",
en: "FF Active",
},
fisting_ACTIVE_PASSIVE: {
de: "FF Flexibel",
en: "FF Versatile",
},
fisting_NO: {
de: "Kein FF",
en: "No FF"
},
fisting_PASSIVE: {
de: "FF Passiv",
en: "FF Passive",
},
fullHeadlines: {
de: "Vollständige Überschriften anzeigen",
en: "Show full headlines"
},
fullHeadlinesDesc: {
de: "Zeigt auch lange Profilüberschriften ungekürzt auf Kacheln.",
en: "Shows even long profile headlines completely on tiles."
},
fullMessages: {
de: "Vollständige Nachrichten anzeigen",
en: "Show full messages"
},
fullMessagesDesc: {
de: "Zeigt Nachrichten ungekürzt in der Nachrichtenliste.",
en: "Shows messages without truncation in the message list."
},
gender: {
de: "Geschlecht",
en: "Gender"
},
gender_MAN: {
de: "Mann",
en: "Man"
},
gender_TRANS_MAN: {
de: "Transmann",
en: "Trans man"
},
gender_TRANS_WOMAN: {
de: "Transfrau",
en: "Trans woman"
},
gender_NON_BINARY: {
de: "Nicht binär",
en: "Non-binary"
},
gender_OTHER: {
de: "Anderes",
en: "Other"
},
genderOrientation: {
de: "Ich bin",
en: "I am"
},
general: {
de: "Allgemein",
en: "General"
},
hairColor: {
de: "Haarfarbe",
en: "Hair Colour"
},
hairColor_BLACK: {
de: "Schwarz",
en: "Black"
},
hairColor_BLOND: {
en: "Blond"
},
hairColor_BROWN: {
de: "Braune Haare",
en: "Brown"
},
hairColor_GREY: {
de: "Grau",
en: "Grey"
},
hairColor_LIGHT_BROWN: {
de: "Dunkelblond",
en: "Light brown"
},
hairColor_OTHER: {
de: "Sonstige",
en: "Other"
},
hairColor_RED: {
de: "Rot",
en: "Red"
},
hairLength: {
de: "Haarlänge",
en: "Hair Length"
},
hairLength_AVERAGE: {
de: "Normal",
en: "Average"
},
hairLength_LONG: {
de: "Lang",
en: "Long"
},
hairLength_PUNK: {
en: "Punk"
},
hairLength_SHAVED: {
de: "Rasiert",
en: "Shaved"
},
hairLength_SHORT: {
de: "Kurz",
en: "Short"
},
height: {
de: "Größe",
en: "Height"
},
hiddenUsers: {
de: "Ausgeblendete Benutzer",
en: "Hidden users"
},
hiddenUsersList: {
de: "Liste ausgeblendeter Benutzernamen",
en: "List of hidden user names"
},
hideActivities: {
de: "Auch Activities verstecken",
en: "Also hide activities"
},
hideActivitiesDesc: {
de: "Versteckt ausgeblendete Benutzer auch im Activity Stream.",
en: "Removes hidden users even in the activity stream."
},
hideLikes: {
de: "Auch Likes auf Bilder verstecken",
en: "Also hide likes on pictures"
},
hideLikesDesc: {
de: "Versteckt ausgeblendete Benutzer auch in der Liste an Likes von Bildern.",
en: "Removes hidden users even in the list of likes on pictures."
},
hideMessages: {
de: "Auch Nachrichten verstecken",
en: "Also hide messages"
},
hideMessagesDesc: {
de: "Versteckt ausgeblendete Benutzer auch in der Nachrichtenliste.",
en: "Removes hidden users even in the message list."
},
hideUser: {
de: "Benutzer ausblenden",
en: "Hide user"
},
interests: {
de: "Interessen",
en: "Interests"
},
interests_ART: {
de: "Kunst",
en: "Art"
},
interests_BOARDGAME: {
de: "Brettspiele",
en: "Board games"
},
interests_CAR: {
de: "Autos",
en: "Cars"
},
interests_COLLECT: {
de: "Sammeln",
en: "Collecting"
},
interests_COMPUTER: {
de: "Computer",
en: "Computers"
},
interests_COOK: {
de: "Kochen",
en: "Cooking"
},
interests_DANCE: {
en: "Dance"
},
interests_FILM: {
en: "Film & Video"
},
interests_FOTO: {
de: "Fotografie",
en: "Photography"
},
interests_GAME: {
de: "Computerspiele",
en: "Gaming"
},
interests_LITERATURE: {
de: "Literatur",
en: "Literature"
},
interests_MODELING: {
de: "Modellbau",
en: "Model building"
},
interests_MOTORBIKE: {
de: "Motorrad",
en: "Motorbikes"
},
interests_MUSIC: {
de: "Musik",
en: "Music"
},
interests_NATURE: {
de: "Natur",
en: "Nature"
},
interests_POLITICS: {
de: "Politik",
en: "Politics"
},
interests_TV: {
en: "TV"
},
languages: {
de: "Sprachen",
en: "Languages"
},
languages_af: {
de: "Afrikaans",
en: "Afrikaans"
},
languages_ar: {
de: "Arabisch",
en: "Arabic"
},
languages_arm: {
de: "Armenisch",
en: "Armenian"
},
languages_az: {
de: "Aserbaidschanisch",
en: "Azerbaijani"
},
languages_be: {
de: "Belarussisch",
en: "Belarusian"
},
languages_bg: {
de: "Bulgarisch",
en: "Bulgarian"
},
languages_bn: {
de: "Bengali",
en: "Bengali"
},
languages_bs: {
de: "Bosnisch",
en: "Bosnian"
},
languages_bur: {
de: "Burmesisch",
en: "Burmese"
},
languages_ca: {
de: "Katalanisch",
en: "Catalan"
},
languages_ceb: {
de: "Cebuano",
en: "Cebuano"
},
languages_cs: {
de: "Tschechisch",
en: "Czech"
},
languages_da: {
de: "Dänisch",
en: "Danish"
},
languages_de: {
de: "Deutsch",
en: "German"
},
languages_el: {
de: "Griechisch",
en: "Greek"
},
languages_en: {
de: "Englisch",
en: "English"
},
languages_eo: {
de: "Esperanto",
en: "Esperanto"
},
languages_es: {
de: "Spanisch",
en: "Spanish"
},
languages_et: {
de: "Estnisch",
en: "Estonian"
},
languages_eu: {
de: "Baskisch",
en: "Basque"
},
languages_fa: {
de: "Persisch",
en: "Persian"
},
languages_fi: {
de: "Finnisch",
en: "Finnish"
},
languages_fr: {
de: "Französisch",
en: "French"
},
languages_frc: {
de: "Kanadisches Französisch",
en: "Canadian French"
},
languages_gd: {
de: "Schottisch-Gälisch",
en: "Scottish Gaelic"
},
languages_gl: {
de: "Galician",
en: "Galician"
},
languages_gsw: {
de: "Schwyzerdütsch",
en: "Swiss-German"
},
languages_hi: {
de: "Hindi",
en: "Hindi"
},
languages_hr: {
de: "Kroatisch",
en: "Croatian"
},
languages_hu: {
de: "Ungarisch",
en: "Hungarian"
},
languages_id: {
de: "Indonesisch",
en: "Indonesian"
},
languages_is: {
de: "Isländisch",
en: "Icelandic"
},
languages_it: {
de: "Italienisch",
en: "Italian"
},
languages_iw: {
de: "Hebräisch",
en: "Hebrew"
},
languages_ja: {
de: "Japanisch",
en: "Japanese"
},
languages_ka: {
de: "Georgisch",
en: "Georgian"
},
languages_kl: {
de: "Grönländisch",
en: "Greenlandic (Kalaallisut)"
},
languages_km: {
de: "Kambodschanisch",
en: "Cambodian"
},
languages_kn: {
de: "Kannada",
en: "Kannada"
},
languages_ko: {
de: "Koreanisch",
en: "Korean"
},
languages_ku: {
de: "Kurdisch",
en: "Kurdish"
},
languages_la: {
de: "Latein",
en: "Latin"
},
languages_lb: {
de: "Luxemburgisch",
en: "Luxembourgish"
},
languages_lo: {
de: "Laotisch",
en: "Lao"
},
languages_lt: {
de: "Litauisch",
en: "Lithuanian"
},
languages_lv: {
de: "Lettisch",
en: "Latvian"
},
languages_mk: {
de: "Mazedonisch",
en: "Macedonian"
},
languages_ml: {
de: "Malayalam",
en: "Malayalam"
},
languages_mr: {
de: "Marathi",
en: "Marathi"
},
languages_ms: {
de: "Malaiisch",
en: "Malay"
},
languages_mt: {
de: "Maltesisch",
en: "Maltese"
},
languages_nl: {
de: "Niederländisch",
en: "Dutch"
},
languages_no: {
de: "Norwegisch",
en: "Norwegian"
},
languages_oc: {
de: "Okzitanisch",
en: "Occitan"
},
languages_pl: {
de: "Polnisch",
en: "Polish"
},
languages_ps: {
de: "Paschtunisch",
en: "Pashto"
},
languages_pt: {
de: "Portugiesisch",
en: "Portuguese"
},
languages_ro: {
de: "Rumänisch",
en: "Romanian"
},
languages_roh: {
de: "Rätoromanisch",
en: "Romansch"
},
languages_ru: {
de: "Russisch",
en: "Russian"
},
languages_sgn: {
de: "Gebärdensprache",
en: "Sign language"
},
languages_sh: {
de: "Serbo-Croatian",
en: "Serbo-Croatian"
},
languages_sk: {
de: "Slowakisch",
en: "Slovak"
},
languages_sl: {
de: "Slowenisch",
en: "Slovenian"
},
languages_sq: {
de: "Albanisch",
en: "Albanian"
},
languages_sr: {
de: "Serbisch",
en: "Serbian"
},
languages_sv: {
de: "Schwedisch",
en: "Swedish"
},
languages_ta: {
de: "Tamil",
en: "Tamil"
},
languages_te: {
de: "Telugu",
en: "Telugu"
},
languages_th: {
de: "Thailändisch",
en: "Thai"
},
languages_tl: {
de: "Tagalog",
en: "Tagalog"
},
languages_tr: {
de: "Türkisch",
en: "Turkish"
},
languages_uk: {
de: "Ukrainisch",
en: "Ukrainian"
},
languages_us: {
de: "US-Englisch",
en: "US English"
},
languages_vi: {
de: "Vietnamesisch",
en: "Vietnamese"
},
languages_wel: {
de: "Walisisch",
en: "Welsh"
},
languages_wen: {
de: "Sorbisch",
en: "Sorbian"
},
languages_zgh: {
de: "Tamazight",
en: "Tamazight"
},
languages_zh: {
de: "Chinesisch",
en: "Chinese"
},
lastLogin: {
de: "Letzter Login",
en: "Last Login"
},
location: {
de: "Ort",
en: "Location"
},
latLong: {
de: "Breitengrad, Längengrad",
en: "Latitude, Longitude"
},
lookingFor: {
de: "Ich suche",
en: "Looking For"
},
lookingForOther: {
de: "Sucht nach",
en: "They're Looking For"
},
maxAge: {
de: "Maximales Alter",
en: "Maximal age"
},
messages: {
de: "Nachrichten",
en: "Messages"
},
metadata: {
de: "Metadaten",
en: "Metadata"
},
minAge: {
de: "Minimales Alter",
en: "Minimal age"
},
myAge: {
de: "Mein Alter",
en: "My Age"
},
myGender: {
de: "Mein Geschlecht",
en: "My gender"
},
myOrientation: {
de: "Meine Orientierung",
en: "My orientation"
},
noEntry: {
de: "Keine Angabe",
en: "No entry"
},
onlineStatus: {
en: "Status"
},
onlineStatus_DATE: {
en: "Date",
},
onlineStatus_OFFLINE: {
en: "Offline"
},
onlineStatus_ONLINE: {
en: "Online"
},
onlineStatus_SEX: {
en: "Now"
},
travelersOnly: {
de: "Nur Reisende",
en: "Travelers only"
},
openTo: {
de: "Offen für",
en: "Open to"
},
openTo_FRIENDSHIP: {
de: "Freunde",
en: "Friends"
},
openTo_RELATIONSHIP: {
de: "Beziehung",
en: "Relationship"
},
openTo_SEXDATES: {
en: "Sex"
},
orientation: {
de: "Orientierung",
en: "Orientation"
},
orientation_BISEXUAL: {
de: "Bisexuell",
en: "Bisexual"
},
orientation_GAY: {
en: "Gay"
},
orientation_QUEER: {
en: "Queer"
},
orientation_OTHER: {
de: "Andere",
en: "Other"
},
orientation_STRAIGHT: {
de: "Hetero",
en: "Straight"
},
other: {
de: "Sonstige",
en: "Other"
},
piercings: {
en: "Piercings"
},
piercings_A_FEW: {
de: "Wenige",
en: "A few"
},
piercings_A_LOT: {
de: "Viele",
en: "A lot"
},
piercings_NO: {
de: "Keine Piercings",
en: "No piercings"
},
profileId: {
de: "Profil-ID",
en: "Profile ID"
},
relationship: {
de: "Beziehung",
en: "Relationship"
},
relationship_MARRIED: {
de: "Verheiratet",
en: "Married"
},
relationship_OPEN: {
de: "Offene Partnerschaft",
en: "Open"
},
relationship_PARTNER: {
de: "Ich habe einen Partner",
en: "Partner"
},
relationship_SINGLE: {
en: "Single"
},
saferSex: {
de: "Safer Sex",
en: "Safer sex"
},
saferSex_ALWAYS: {
en: "Safe"
},
saferSex_CONDOM: {
de: "Kondom",
en: "Condom"
},
saferSex_NEEDS_DISCUSSION: {
de: "Nach Absprache",
en: "Let's talk"
},
saferSex_PREP: {
en: "PrEP"
},
saferSex_PREP_AND_CONDOM: {
de: "PrEP und Kondom",
en: "PrEP and condom"
},
saferSex_TASP: {
en: "TasP"
},
searchOptions: {
de: "Suchoptionen",
en: "Search options"
},
sendEnter: {
de: "Enter sendet Nachricht",
en: "Enter sends message"
},
sendEnterDesc: {
de: "Wenn deaktiviert, erzeugt Enter einen Absatz und Strg+Enter versendet die Nachricht.",
en: "If disabled, Enter creates a new line instead, and Ctrl+Enter sends the message."
},
sexual: {
de: "Sexuelles",
en: "Sexual"
},
sm: {
de: "SM",
en: "S&M"
},
sm_NO: {
de: "Kein SM",
en: "No SM"
},
sm_SOFT: {
en: "Soft SM",
},
sm_YES: {
en: "SM"
},
smoker: {
de: "Raucher",
en: "Smoker"
},
smoker_NO: {
de: "Nein",
en: "No"
},
smoker_SOCIALLY: {
de: "Selten",
en: "Socially"
},
smoker_YES: {
de: "Ja",
en: "Yes"
},
speakingMyLanguage: {
de: "Spricht meine Sprache",
en: "Speaking my language"
},
tattoos: {
en: "Tattoos"
},
tattoos_A_FEW: {
de: "Wenige",
en: "A few"
},
tattoos_A_LOT: {
de: "Viele",
en: "A lot"
},
tattoos_NO: {
de: "Keine Tattoos",
en: "No tattoos"
},
tileDetailsList: {
de: "Profildetails auf Kacheln anzeigen",
en: "Grid Stats"
},
tiles: {
de: "Benutzerkacheln",
en: "User tiles"
},
tileSizeFactor: {
de: "Extra-Kacheln pro Zeile (Radar)",
en: "Extra tiles per row (Radar)"
},
tileSizeGroupFactor: {
de: "Extra-Kacheln pro Zeile (Besucher)",
en: "Extra tiles per row (Visitors)"
},
typingNotifications: {
de: "Tippbenachrichtigungen",
en: "Typing notifications"
},
typingNotificationsDesc: {
de: "Wenn deaktiviert, können Empfänger nicht mehr sehen, dass eine Nachricht verfasst wird.",
en: "If disabled, receivers can no longer see that a message is being composed."
},
viewFullImage: {
de: "Bild vergrößern",
en: "View full image"
},
viewProfile: {
de: "Profil ohne Besuch anzeigen",
en: "View profile without visiting"
},
weight: {
de: "Gewicht",
en: "Weight"
}
};
function translate(key) {
const lang = document.documentElement.getAttribute("lang") || "en";
const translations = _strings[key];
return translations
? translations[lang] || translations.en || "%" + key + "%"
: "%" + key + "%";
}
function translateEnum(name, key) {
return translate(`${name}_${key}`);
}
// ---- Settings ----
const settingsNs = "RA_SETTINGS:";
let measurementSystem = "METRIC";
let radarFilter = {};
let tileDetails = new Set();
function load(name, fallback) {
const value = localStorage.getItem(settingsNs + name);
return value === "false" ? false
: value ? value
: fallback;
}
function save(name, value) {
localStorage.setItem(settingsNs + name, value);
}
function setStyleProp(name, value) {
document.documentElement.style.setProperty(name, value);
}
function getEnhancedFilter() {
return load("enhancedFilter", true);
}
function getEnhancedTiles() {
return load("enhancedTiles", true);
}
function getFullHeadlines() {
return load("fullHeadlines", true);
}
function getFullMessages() {
return load("fullMessages", true);
}
function getHiddenMaxAge() {
return load("hiddenMaxAge", 99);
}
function getHiddenMinAge() {
return load("hiddenMinAge", 18);
}
function getHiddenUsers() {
return new Set(JSON.parse(load("hiddenUsers", `[]`)));
}
function getHideActivities() {
return load("hideActivities", true);
}
function getHideLikes() {
return load("hideLikes", true);
}
function getHideMessages() {
return load("hideMessages", false);
}
function getRadarFilter() {
return JSON.parse(load("radarFilter", `{}`));
}
function getSendEnter() {
return load("sendEnter", true);
}
function getTileDetails() {
return new Set(JSON.parse(load("tileDetails", `[ "age", "height", "bodyHair", "bodyType", "relationship", "analPosition" ]`)));
}
function getTileSizeFactor() {
return load("tileSizeFactor", 0);
}
function getTileSizeGroupFactor() {
return load("tileSizeGroupFactor", 0);
}
function getTypingNotifications() {
return load("typingNotifications", true);
}
function setEnhancedFilter(value) {
save("enhancedFilter", value);
}
function setEnhancedTiles(value) {
save("enhancedTiles", value);
}
function setFullHeadlines(value) {
setStyleProp("--tile-headline-white-space", value ? "unset" : "nowrap");
save("fullHeadlines", value);
}
function setFullMessages(value) {
setStyleProp("--message-line-clamp", value ? "unset" : "2");
save("fullMessages", value);
}
function setHiddenMaxAge(value) {
save("hiddenMaxAge", value);
}
function setHiddenMinAge(value) {
save("hiddenMinAge", value);
}
function setHideActivities(value) {
save("hideActivities", value);
}
function setHideLikes(value) {
save("hideLikes", value);
}
function setHideMessages(value) {
save("hideMessages", value);
}
function setRadarFilter() {
save("radarFilter", JSON.stringify(radarFilter));
}
function setSendEnter(value) {
save("sendEnter", value);
}
function setTileDetail(key, visible) {
if (visible) {
tileDetails.add(key);
} else {
tileDetails.delete(key);
}
save("tileDetails", JSON.stringify(Array.from(tileDetails)));
}
function setTileSizeFactor(value) {
setStyleProp("--tile-size-factor", value);
save("tileSizeFactor", value);
}
function setTileSizeGroupFactor(value) {
setStyleProp("--tile-size-group-factor", value);
save("tileSizeGroupFactor", value);
}
function setTypingNotifications(value) {
save("typingNotifications", value);
}
function setUserHidden(username, hide) {
let hiddenUsers = getHiddenUsers();
if (hide) {
hiddenUsers.add(username);
} else {
hiddenUsers.delete(username);
}
save("hiddenUsers", JSON.stringify(Array.from(hiddenUsers)));
}
// ---- XHR ----
function getApiVerb(url) {
if (url.includes("/api/stream")) {
return "stream";
}
// Extract verb in "/api/v#/verb?" or "/api/+/verb?".
const matches = url.match("/api/(v[0-9]|\\+)/(.*)\\?".replaceAll("/", "\\/"));
if (matches && matches.length === 3) {
return matches.at(-1);
}
}
function proxyXhr() {
const realOpen = window.XMLHttpRequest.prototype.open;
window.XMLHttpRequest.prototype.open = function (method, url, async, user, password) {
// Manipulate request.
const verb = getApiVerb(url);
switch (verb) {
case "hunqz/profiles":
case "profiles":
url = xhrApplyRadarFilter(url);
break;
}
// Manipulate reply.
this.addEventListener("load", () => {
//console.log(`[RA] ${method} ${url}`);
if (method !== "GET") {
return;
}
const verb = getApiVerb(url);
const isJson = verb !== "stream" && typeof this.response === "string";
let reply = isJson ? JSON.parse(this.response) : this.response;
//console.log(`[RA] ${verb}`, reply);
// Modify interesting data.
switch (verb) {
case "messages/conversations":
reply.items = xhrProcessUserItems(reply.items, x => x.chat_partner, getHideMessages());
break;
case "notifications/activity-stream":
reply = xhrProcessUserItems(reply, x => x.partner, getHideActivities());
break;
case "hunqz/profiles":
case "profiles":
reply.items = xhrProcessUserItems(reply.items, x => x, true);
reply = xhrEnhanceProfilesAndVisits(reply);
break;
case "session":
xhrHandleSession(reply);
break;
case "visitors":
case "visits":
reply.items = xhrProcessUserItems(reply.items, x => x, true);
reply = xhrEnhanceProfilesAndVisits(reply);
break;
case "reactions/pictures/basic":
reply.items = xhrProcessUserItems(reply.items, x => x.user_id, getHideLikes());
break;
}
// Write back possibly modified data.
Object.defineProperty(this, "responseText", { writable: true });
this.responseText = isJson ? JSON.stringify(reply) : reply;
});
// Forward to client.
return realOpen.apply(this, arguments);
}
}
function xhrEnhanceProfilesAndVisits(reply) {
// Restore PLUS-visible visitors.
reply.items_limited = reply.items_total;
// Show as "large tiles" to display user details everywhere.
if (getEnhancedTiles()) {
for (let item of reply.items ?? []) {
if (item.display) {
item.display.large_tile = true;
}
}
}
return reply;
}
function xhrHandleSession(reply) {
measurementSystem = reply.bb_settings?.interface?.measurement_system ?? measurementSystem;
}
function xhrProcessUserItems(items, userSelector, filter) {
let newItems = [];
const hiddenMaxAge = getHiddenMaxAge();
const hiddenMinAge = getHiddenMinAge();
const hiddenNames = getHiddenUsers();
for (const item of items ?? []) {
const profile = cacheProfile(userSelector(item));
if (!filter || filterProfile(profile, hiddenMaxAge, hiddenMinAge, hiddenNames)) {
newItems.push(item);
}
}
return newItems;
}
// ---- Radar ----
function addRadarFilter(filter, key, value) {
if (!isMultiRadarFilter(key)) {
filter[key] = value;
} else if (key in filter) {
filter[key].push(value);
} else {
filter[key] = [value];
}
}
function hasRadarFilter(filter, key, value) {
return key in filter
&& (value === undefined || (isMultiRadarFilter(key)
? filter[key].includes(value)
: filter[key] === value));
}
function isMultiRadarFilter(key) {
return key.endsWith("[]");
}
function removeRadarFilter(filter, key, value) {
if (!hasRadarFilter(filter, key, value)) {
return;
}
if (isMultiRadarFilter(key)) {
filter[key] = filter[key].filter(x => x !== value);
if (!filter[key].length) {
delete filter[key];
}
} else {
delete filter[key];
}
}
function refreshFilter() {
// Save filter, enable reset filter button, and reload.
setRadarFilter();
document.querySelector(".js-clear-all").classList.remove("is-disabled");
document.querySelector("section.js-main-stage div.js-navigation a.is-selected, div.js-nav-item").click();
}
function packRadarFilter(keyValues) {
let filter = {};
for (const [key, value] of keyValues) {
addRadarFilter(filter, key, value);
}
return filter;
}
function unpackRadarFilter(filter) {
let keyValues = [];
for (const key in filter) {
if (isMultiRadarFilter(key)) {
for (const value of filter[key]) {
keyValues.push([key, value]);
}
} else {
keyValues.push([key, filter[key]]);
}
}
return keyValues;
}
function replaceFilterContainer(el) {
if (!getEnhancedFilter()) {
return;
}
// Remove bookmark action if not available.
const save = el.querySelector(".js-filter-actions .js-save");
if (save && save.classList.contains("is-plus")) {
save.parentNode.remove();
}
// Remove all filters on reset.
const clearAll = el.querySelector(".js-filter-actions .js-clear-all");
if (Object.keys(radarFilter).length) {
clearAll.classList.remove("is-disabled");
}
clearAll.addEventListener("click", e => {
radarFilter = {};
setRadarFilter();
// Filter panel is recreated by default handler, recreate selections.
setTimeout(() => replaceFilterContainer(el));
});
filter = el.querySelector(".filter");
// Remove PLUS-Filter ad if no original filters are selected.
if (filter.querySelector(".js-quick-filter .js-add-params-button.plain-text-link")) {
filter.querySelector(".js-quick-filter .filter__group-more-options").remove();
}
// Add custom filters.
function addSection(text) {
return addElement(filter, `<div class="filter__params-tags js-tags-list">
<h3 class="typo mb-">${translate(text)}</h3>
</div>`);
}
function addSectionList(text) {
const section = addSection(text);
return addElement(section, `<ul class="js-list tags-list"></ul>`);
}
function addSectionListMulti(text, prefix, filterKey, filterValues, hasNoEntry = true) {
const section = addSectionList(text);
for (const filterValue of filterValues) {
addListTagFilter(section, `${prefix}_${filterValue}`, filterKey, filterValue);
}
if (hasNoEntry) {
addListTagFilter(section, "noEntry", filterKey, "NO_ENTRY");
}
return section;
};
function addListTag(ul, text, selected, change) {
const li = addElement(ul, `
<li class="tags-list__item">
<a class="js-tag ui-tag ui-tag--removable txt-truncate">
<span class="ui-tag__label">${translate(text)}</span>
</a>
</li>`);
const a = li.querySelector("a");
if (selected) {
a.classList.add("ui-tag--selected");
}
li.addEventListener("click", e => {
e.preventDefault();
if (a.classList.contains("ui-tag--selected")) {
change(false);
a.classList.remove("ui-tag--selected");
} else {
change(true);
a.classList.add("ui-tag--selected");
}
});
}
function addListTagFilter(ul, text, filterKey, filterValue) {
let selected = hasRadarFilter(radarFilter, filterKey, filterValue);
return addListTag(ul, text, selected, checked => {
if (checked) {
addRadarFilter(radarFilter, filterKey, filterValue);
} else {
removeRadarFilter(radarFilter, filterKey, filterValue);
}
refreshFilter();
});
}
function addInput(ul) {
const el = addElement(ul, `
<div class="filter__group">
<div class="js-fulltext-input filter__group--fulltext">
<div class="Container--f_nVe layout layout--v-center">
<div class="layout-item layout-item--consume">
<input class="js-input Input--9tGCI input" autocorrect="off" autocapitalize="off" spellcheck="false">
</div>
</div>
</div>
</div>`);
return el.querySelector("input");
}
addSectionListMulti("lookingForOther", "openTo", "filter[personal][looking_for][]",
["SEXDATES", "FRIENDSHIP", "RELATIONSHIP"]);
addSectionListMulti("bodyType", "bodyType", "filter[personal][body_type][]",
["SLIM", "AVERAGE", "ATHLETIC", "MUSCULAR", "BELLY", "STOCKY"]);
addSectionListMulti("ethnicity", "ethnicity", "filter[personal][ethnicity][]",
["CAUCASIAN", "ASIAN", "LATIN", "MEDITERRANEAN", "BLACK", "MIXED", "ARAB", "INDIAN"]);
addSectionListMulti("hairLength", "hairLength", "filter[personal][hair_length][]",
["SHAVED", "SHORT", "AVERAGE", "LONG", "PUNK"]);
addSectionListMulti("hairColor", "hairColor", "filter[personal][hair_color][]",
["BLOND", "LIGHT_BROWN", "BROWN", "BLACK", "GREY", "OTHER", "RED"]);
addSectionListMulti("beard", "beard", "filter[personal][beard][]",
["DESIGNER_STUBBLE", "MOUSTACHE", "GOATEE", "FULL_BEARD", "NO_BEARD"]);
addSectionListMulti("eyeColor", "eyeColor", "filter[personal][eye_color][]",
["BLUE", "BROWN", "GREY", "GREEN", "OTHER"]);
addSectionListMulti("bodyHair", "bodyHair", "filter[personal][body_hair][]",
["SMOOTH", "SHAVED", "LITTLE", "AVERAGE", "VERY_HAIRY"]);
addSectionListMulti("gender", "gender", "filter[personal][gender_orientation][gender][]",
["MAN", "TRANS_MAN", "TRANS_WOMAN", "NON_BINARY", "OTHER"]);
addSectionListMulti("orientation", "orientation", "filter[personal][gender_orientation][orientation][]",
["GAY", "BISEXUAL", "QUEER", "STRAIGHT", "OTHER"]);
addSectionListMulti("smoker", "smoker", "filter[personal][smoker][]",
["NO", "SOCIALLY", "YES"]);
addSectionListMulti("tattoos", "tattoos", "filter[personal][tattoo][]",
["A_FEW", "A_LOT", "NO"]);
addSectionListMulti("piercings", "piercings", "filter[personal][piercing][]",
["A_FEW", "A_LOT", "NO"]);
addSectionListMulti("relationship", "relationship", "filter[personal][relationship][]",
["SINGLE", "PARTNER", "OPEN", "MARRIED"]);
addSectionListMulti("analPosition", "analPosition", "filter[sexual][anal_position][]",
["TOP_ONLY", "MORE_TOP", "VERSATILE", "MORE_BOTTOM", "BOTTOM_ONLY", "NO"]);
addSectionListMulti("dick", "dick", "filter[sexual][dick_size][]",
["S", "M", "L", "XL", "XXL"]);
addSectionListMulti("concision", "concision", "filter[sexual][concision][]",
["CUT", "UNCUT"]);
addSectionListMulti("fetish", "fetish", "filter[sexual][fetish][]",
["LEATHER", "SPORTS", "SKATER", "RUBBER", "UNDERWEAR", "SKINS", "BOOTS", "LYCRA", "UNIFORM", "FORMAL",
"TECHNO", "SNEAKERS", "JEANS", "DRAG", "WORKER", "CROSSDRESSING"]);
addSectionListMulti("dirty", "dirty", "filter[sexual][dirty_sex][]",
["YES", "NO", "WS_ONLY"]);
addSectionListMulti("fisting", "fisting", "filter[sexual][fisting][]",
["ACTIVE", "ACTIVE_PASSIVE", "PASSIVE", "NO"]);
addSectionListMulti("sm", "sm", "filter[sexual][sm][]",
["YES", "SOFT", "NO"]);
addSectionListMulti("saferSex", "saferSex", "filter[sexual][safer_sex][]",
["ALWAYS", "NEEDS_DISCUSSION", "CONDOM", "PREP", "TASP"]);
addSectionListMulti("interests", "interests", "filter[hobby][interests][]",
["ART", "BOARDGAME", "CAR", "COLLECT", "COMPUTER", "COOK", "DANCE", "FILM", "FOTO", "GAME", "LITERATURE",
"MODELING", "MOTORBIKE", "MUSIC", "NATURE", "POLITICS", "TV"], false);
const section = addSectionList("other");
const coordInput = addInput(section);
coordInput.type = "text";
coordInput.placeholder = translate("latLong");
if ("filter[location][lat]" in radarFilter && "filter[location][long]" in radarFilter) {
coordInput.value = `${radarFilter["filter[location][lat]"]}, ${radarFilter["filter[location][long]"]}`;
}
coordInput.addEventListener("change", e => {
removeRadarFilter(radarFilter, "filter[location][lat]");
removeRadarFilter(radarFilter, "filter[location][long]");
const sep = e.target.value.indexOf(", ");
if (sep != -1) {
const lat = parseFloat(e.target.value);
const long = parseFloat(e.target.value.substring(sep + 2));
if (!isNaN(lat) && !isNaN(long)) {
addRadarFilter(radarFilter, "filter[location][lat]", lat.toString());
addRadarFilter(radarFilter, "filter[location][long]", long.toString());
}
}
refreshFilter();
});
addListTagFilter(section, "bedAndBreakfast", "filter[bed_and_breakfast_filter]", "ONLY");
addListTagFilter(section, "travelersOnly", "filter[travellers_filter]", "TRAVELLERS_ONLY");
addListTagFilter(section, "speakingMyLanguage", "filter[personal][speaks_my_languages]", "true");
}
function xhrApplyRadarFilter(url) {
if (!getEnhancedFilter()) {
return url;
}
// Decode existing parameters.
const sep = url.indexOf("?");
const baseUrl = url.substring(0, sep);
const query = url.substring(sep + 1);
let keyValues = [];
for (const param of query.split("&")) {
let [key, value] = param.split("=");
key = decodeURI(key);
// Ignore pagination and "Explore" views as filters don't fully work in them.
if (key == "cursor" || key == "length" && value == 48) {
return url;
}
keyValues.push([key, value]);
}
// Overwrite with custom parameters.
let filter = packRadarFilter(keyValues);
filter = { ...filter, ...radarFilter };
// Encode new parameters.
url = `${baseUrl}?`;
for (const [key, value] of unpackRadarFilter(filter)) {
url += `${encodeURI(key)}=${value}&`;
}
return url.slice(0, -1);
}
onElement(".js-quick-filter", el => {
replaceFilterContainer(el.parentNode);
});
// ---- Profiles ----
let profileCache = {};
function cacheProfile(user) {
// For activities, user is the activity partner.
if (user.partner) {
user = user.partner;
}
const existing = profileCache[user.name];
const profile = {
id: user.id,
name: user.name,
headline: user.headline ?? existing?.headline,
last_login: user.last_login ?? existing?.last_login,
location: user.location ?? existing?.location,
online_status: user.online_status ?? existing?.online_status,
pic: user.preview_pic?.url_token ?? existing?.pic, // not available if no picture
personal: user.profile?.personal ?? user.personal ?? existing?.personal, // not available in activities
sexual: user.profile?.sexual ?? user.sexual ?? existing?.sexual // not available in activities
}
profileCache[profile.name] = profile;
return profile;
}
function filterProfile(profile, hiddenMaxAge, hiddenMinAge, hiddenNames) {
// Return whether to display the profile.
return (!profile.personal || profile.personal.age >= hiddenMinAge && profile.personal.age <= hiddenMaxAge)
&& !hiddenNames.has(profile.name);
}
function getProfileAgeRange(range, short) {
if (range) {
return short
? `${range.min ?? 18}-${range.max ?? 99}`
: translate("ageRangeValue").replace("$from", range.min ?? 18).replace("$to", range.max ?? 99);
}
}
function getProfileBmi(height, weight, withName) {
if (height && weight) {
const bmi = weight / Math.pow(height / 100, 2);
let result = `${(Math.round(bmi * 10) / 10).toFixed(1)}`;
if (withName) {
const name
= bmi < 16 ? translate("bmiSevereThin")
: bmi < 17 ? translate("bmiModerateThin")
: bmi < 18.5 ? translate("bmiMildThin")
: bmi < 25 ? translate("bmiNormal")
: bmi < 30 ? translate("bmiPreObese")
: bmi < 35 ? translate("bmiObese1")
: bmi < 40 ? translate("bmiObese2")
: translate("bmiObese3");
result += ` / ${name}`;
}
return result;
}
}
function getProfileDick(size, concision) {
let values = [];
if (size && size !== "NO_ENTRY") {
values.push(translateEnum("dick", size));
}
if (concision && concision !== "NO_ENTRY") {
values.push(translateEnum("concision", concision));
}
if (values.length) {
return values.join(" - ");
}
}
function getProfileEnum(key, value) {
if (value && value !== "NO_ENTRY") {
return translateEnum(key, value);
}
}
function getProfileHeight(height) {
if (height) {
return measurementSystem === "METRIC"
? `${height}cm`
: `${Math.round(height * 3.280839895) / 100} ft`;
}
}
function getProfileWeight(weight) {
if (weight) {
return measurementSystem === "METRIC"
? `${weight}kg`
: `${Math.round(weight * 2.20462262185)}lbs`;
}
}
function showProfile(layer, profile) {
function isEntry(value) {
return value && value !== "NO_ENTRY";
}
function addSection(el, key) {
return addElement(el, `<details class="ra_profile_details" open>
<summary class="ra_profile_summary">${translate(key)}</summary>
</details>`);
}
function add(section, key, value) {
if (value) {
addElement(section, `<div class="ra_profile_keyvalue">
<div>${translate(key)}</div>
<div>${value}</div>
</div>`);
}
}
function addAgeRange(section, range) {
if (range) {
add(section, "ageRange", getProfileAgeRange(range));
}
}
function addArray(section, key, array) {
if (!array) {
return;
}
let values = [];
for (let i = 0; i < array.length; i++) {
values.push(translate(array[i]));
}
if (values.length) {
add(section, key, values.join(", "));
}
}
function addArrayEnum(section, key, array) {
if (!array) {
return;
}
let values = [];
for (let i = 0; i < array.length; i++) {
if (isEntry(array[i])) {
values.push(translateEnum(key, array[i]));
}
}
if (values.length) {
add(section, key, values.join(", "));
}
}
function addDistance(section, distance, sensor) {
let text = measurementSystem === "METRIC"
? `${distance / 1000} km`
: `${Math.round(distance * 0.006213712) / 10}mi`;
if (sensor) {
text += " (GPS)"
}
add(section, "distance", text);
}
function addEnum(section, key, value) {
if (isEntry(value)) {
add(section, key, translateEnum(key, value));
}
}
function addGender(section, genderOrientation) {
let values = [];
if (isEntry(genderOrientation?.orientation)) {
values.push(translateEnum("orientation", genderOrientation.orientation));
}
if (isEntry(genderOrientation?.gender)) {
values.push(translateEnum("gender", genderOrientation.gender));
}
if (values.length) {
add(section, "genderOrientation", values.join(" / "));
}
}
// Create preview popup.
const pane = addElement(layer, `<div id="ra_profile_wrapper">
<div class="js-header layout-item l-hidden-md-lg">
<div class="layer-header layer-header--primary">
<a class="back-button l-hidden-md-lg l-tappable js-back marionette" href="#">
<span class="js-back-icon icon icon-back icon-large"></span>
</a>
<div class="layer-header__title">
<h2>${profile.name}</h2>
</div>
</div>
</div>
<div class="layout-item p l-hidden-sm">
<div class="js-title typo-section-navigation">${profile.name}</div>
</div>
<div id="ra_profile_content">
<div id="ra_profile_left">
<div>${profile.headline}</div>
<img id="ra_profile_pic" src="/assets/3e6f78fd4e864c6071e6fa65d3d0c679c48b84a95dee9f232a2e86303c4ed5a1.svg"></img>
</div>
<div id="ra_profile_right"></div>
</div>
</div>`);
const right = pane.querySelector("#ra_profile_right");
// Set image.
if (profile.pic) {
const img = pane.querySelector("#ra_profile_pic");
img.src = `/img/usr/${profile.pic}.jpg`;
}
// Set profile metadata.
{
const section = addSection(right, "metadata");
addEnum(section, "onlineStatus", profile.online_status);
add(section, "lastLogin", profile.last_login);
add(section, "location", `${profile.location.name}, ${profile.location.country}`);
addDistance(section, profile.location.distance, profile.location.sensor);
add(section, "profileId", profile.id);
}
// Set general details.
const personal = profile.personal;
if (personal) {
const section = addSection(right, "general");
add(section, "age", personal.age);
add(section, "height", getProfileHeight(personal.height));
add(section, "weight", getProfileWeight(personal.weight));
add(section, "bmi", getProfileBmi(personal.height, personal.weight, true));
add(section, "bodyType", getProfileEnum("bodyType", personal.body_type));
add(section, "ethnicity", getProfileEnum("ethnicity", personal.ethnicity));
addEnum(section, "hairLength", personal.hair_length);
addEnum(section, "hairColor", personal.hair_color);
addEnum(section, "beard", personal.beard);
addEnum(section, "eyeColor", personal.eye_color);
add(section, "bodyHair", getProfileEnum("bodyHair", personal.body_hair));
addGender(section, personal?.gender_orientation);
addEnum(section, "smoker", personal.smoker);
addEnum(section, "tattoos", personal.tattoo);
addEnum(section, "piercings", personal.piercing);
addArrayEnum(section, "languages", personal.spoken_languages);
add(section, "relationship", getProfileEnum("relationship", personal.relationship));
}
// Set sexual details.
const sexual = profile.sexual;
if (sexual) {
const section = addSection(right, "sexual");
add(section, "analPosition", getProfileEnum("analPosition", sexual.anal_position));
add(section, "dick", getProfileDick(sexual.dick_size, sexual.concision));
addArrayEnum(section, "fetish", sexual.fetish);
add(section, "dirty", getProfileEnum("dirty", sexual.dirty_sex));
addEnum(section, "fisting", sexual.fisting);
addEnum(section, "sm", sexual.sm);
add(section, "saferSex", getProfileEnum("saferSex", sexual.safer_sex));
if (!section.querySelectorAll(".ra_profile_keyvalue").length) {
section.remove();
}
}
// Set looking for details.
if (personal) {
const section = addSection(right, "lookingFor");
addArrayEnum(section, "openTo", personal.looking_for);
addAgeRange(section, personal.target_age);
addArrayEnum(section, "gender", personal.gender_orientation?.looking_for_gender);
addArrayEnum(section, "orientation", personal.gender_orientation?.looking_for_orientation);
if (!section.querySelectorAll(".ra_profile_keyvalue").length) {
section.remove();
}
}
// Set profile text.
if (profile.personal?.profile_text) {
const section = addSection(right, "aboutMe");
addElement(section, `<div id="ra_profile_text">${profile.personal.profile_text}</div>`);
}
}
// ---- Tile UI ----
onElement(`.tile:not(div[class*="tile--loading--"]) > .reactView`, el => {
const tile = el.closest(".tile");
if (!tile) {
return;
}
// Extract user name.
const a = tile.querySelector("a");
let start = a.href.indexOf("profile/");
if (start === -1) {
start = a.href.indexOf("hunq/");
}
const username = a.href.substring(start).split("/")[1];
// Modify tile.
const tileBar = addElement(tile, `<div class="tile__bar"></div>`);
addHideUserAction(tileBar, tile, username);
addShowProfileAction(tileBar, username);
addUserTags(tile, username);
});
function addHideUserAction(tileBar, tile, username) {
const action = addElement(tileBar, `<a class="tile__bar_action" href="#" title="${translate("hideUser")}">
<span class="icon icon-hide-visit">
</a>`);
action.addEventListener("click", e => {
e.preventDefault();
setUserHidden(username, true);
tile.style.display = "none";
});
}
function addShowProfileAction(tileBar, username) {
const action = addElement(tileBar, `<a class="tile__bar_action" title="${translate("viewProfile")}">
<span class="icon icon-info">
</a>`);
action.addEventListener("click", e => {
e.preventDefault();
const spotlightContainer = document.querySelector("#spotlight-container");
const layer = addElement(spotlightContainer, `<div class="layer layer--spotlight" style="top:0;z-index:100;"></div>`);
showProfile(layer, profileCache[username]);
layer.querySelector(".js-back").addEventListener("click", e => layer.remove());
layer.addEventListener("click", e => {
if (e.target === layer) {
layer.remove()
}
});
});
}
function addUserTags(tile, username) {
const tag = tile.querySelector(`span[class^="SpecialText-"]:last-child`);
if (!tag) {
return;
}
// Remove all existing tags.
const tags = tag.parentNode;
tags.replaceChildren();
// Add user selected tags.
function addTag(text) {
if (text) {
addElement(tags, `<span class="${tag.classList}">${text}</span>`);
}
}
const profile = profileCache[username];
// Add general details.
const personal = profile.personal;
if (personal) {
if (tileDetails.has("age")) addTag(personal.age);
if (tileDetails.has("bodyHair")) addTag(getProfileEnum("bodyHair", personal.body_hair));
if (tileDetails.has("height")) addTag(getProfileHeight(personal.height));
if (tileDetails.has("weight")) addTag(getProfileWeight(personal.weight));
if (tileDetails.has("bmi")) addTag(getProfileBmi(personal.height, personal.weight));
if (tileDetails.has("ageRange")) addTag(getProfileAgeRange(personal.target_age, true));
if (tileDetails.has("bodyType")) addTag(getProfileEnum("bodyType", personal.body_type));
if (tileDetails.has("ethnicity")) addTag(getProfileEnum("ethnicity", personal.ethnicity));
if (tileDetails.has("relationship")) addTag(getProfileEnum("relationship", personal.relationship));
}
// Add sexual details.
const sexual = profile.sexual;
if (sexual) {
if (tileDetails.has("analPosition")) addTag(getProfileEnum("analPosition", sexual.anal_position));
if (tileDetails.has("dick")) addTag(getProfileDick(sexual.dick_size, sexual.concision));
if (tileDetails.has("saferSex")) addTag(getProfileEnum("saferSex", sexual.safer_sex));
if (tileDetails.has("dirty")) addTag(getProfileEnum("dirty", sexual.dirty_sex));
if (tileDetails.has("sm")) addTag(getProfileEnum("sm", sexual.sm));
if (tileDetails.has("fisting")) addTag(getProfileEnum("fisting", sexual.fisting));
}
}
// ---- Messaging UI ----
onElement(".js-send-region.layout-item > div", el => {
el.addEventListener("keydown", e => {
// Prevent site event handler from sending message or typing notifications.
const enter = e.key === "Enter";
const send = enter && (getSendEnter() || e.ctrlKey);
const allow = send || getTypingNotifications() && !enter;
if (!allow) {
e.stopPropagation();
}
}, true);
});
// ---- Sidebar UI ----
function openPane(ul, link) {
// Open pane.
const layerContent = document.querySelector("#offcanvas-nav > .js-layer-content");
layerContent.classList.add("is-open");
// Replace pane contents.
const pane = layerContent.querySelector(".js-side-content");
pane.replaceChildren();
addElement(pane, `
<div class="layout layout--vertical layout--consume">
<div class="layout-item layout-item--consume layout layout--vertical">
<div class="js-header layout-item l-hidden-md-lg">
<div class="layer-header layer-header--primary">
<a class="back-button l-hidden-md-lg l-tappable js-back marionette" href="/me">
<span class="js-back-icon icon icon-back icon-large"></span>
</a>
<div class="layer-header__title">
<h2>${GM_info.script.name}</h2>
</div>
</div>
</div>
<div class="layout-item settings__navigation p l-hidden-sm">
<div class="js-title typo-section-navigation">${GM_info.script.name}</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>${translate("searchOptions")}</span>
</div>
<div class="separator separator--alt separator--narrow [ mb ] "></div>
<div class="layout layout--v-center">
<div class="layout-item [ 6/12--sm ]">
<span>${translate("enhancedFilter")}</span>
</div>
<div class="layout-item [ 6/12--sm ]">
<div class="js-toggle-show-headlines pull-right">
<div>
<span class="ui-toggle ui-toggle--default ui-toggle--right">
<input class="ui-toggle__input" type="checkbox" id="ra_enhancedFilter">
<label class="ui-toggle__label" for="ra_enhancedFilter" style="touch-action: pan-y; user-select: none; -webkit-user-drag: none; -webkit-tap-highlight-color: rgba(0, 0, 0, 0);"></label>
</span>
</div>
</div>
</div>
</div>
<div>
<div class="settings__description">${translate("enhancedFilterDesc")}</div>
</div>
</div>
<div class="settings__key">
<div>
<span>${translate("tiles")}</span>
</div>
<div class="separator separator--alt separator--narrow [ mb ] "></div>
<div class="layout layout--v-center">
<div class="layout-item [ 6/12--sm ]">
<span>${translate("enhancedTiles")}</span>
</div>
<div class="layout-item [ 6/12--sm ]">
<div class="js-toggle-show-headlines pull-right">
<div>
<span class="ui-toggle ui-toggle--default ui-toggle--right">
<input class="ui-toggle__input" type="checkbox" id="ra_enhancedTiles">
<label class="ui-toggle__label" for="ra_enhancedTiles" style="touch-action: pan-y; user-select: none; -webkit-user-drag: none; -webkit-tap-highlight-color: rgba(0, 0, 0, 0);"></label>
</span>
</div>
</div>
</div>
</div>
<div>
<div class="settings__description">${translate("enhancedTilesDesc")}</div>
</div>
<div class="layout layout--v-center">
<div class="layout-item [ 6/12--sm ]">
<span>${translate("fullHeadlines")}</span>
</div>
<div class="layout-item [ 6/12--sm ]">
<div class="js-toggle-show-headlines pull-right">
<div>
<span class="ui-toggle ui-toggle--default ui-toggle--right">
<input class="ui-toggle__input" type="checkbox" id="ra_fullHeadlines">
<label class="ui-toggle__label" for="ra_fullHeadlines" style="touch-action: pan-y; user-select: none; -webkit-user-drag: none; -webkit-tap-highlight-color: rgba(0, 0, 0, 0);"></label>
</span>
</div>
</div>
</div>
</div>
<div>
<div class="settings__description">${translate("fullHeadlinesDesc")}</div>
</div>
<div class="settings__key">
<div class="layout layout--v-center">
<div class="layout-item [ 6/12--sm ]">
<span>${translate("tileSizeFactor")}</span>
</div>
<div class="layout-item [ 6/12--sm ]">
<input class="input input--block" id="ra_tileSizeFactor" type="number" min="-5" max="5"/>
</div>
</div>
</div>
<div class="settings__key">
<div class="layout layout--v-center">
<div class="layout-item [ 6/12--sm ]">
<span>${translate("tileSizeGroupFactor")}</span>
</div>
<div class="layout-item [ 6/12--sm ]">
<input class="input input--block" id="ra_tileSizeGroupFactor" type="number" min="-5" max="5"/>
</div>
</div>
</div>
<div class="settings__key">
<div class="mb">
<span>${translate("tileDetailsList")}</span>
</div>
<div class="js-grid-stats-selector">
<div>
<ul class="js-list tags-list tags-list--centered" id="ra_tileDetails"/>
</div>
</div>
</div>
</div>
<div class="settings__key">
<div>
<span>${translate("messages")}</span>
</div>
<div class="separator separator--alt separator--narrow [ mb ] "></div>
<div class="layout layout--v-center">
<div class="layout-item [ 6/12--sm ]">
<span>${translate("fullMessages")}</span>
</div>
<div class="layout-item [ 6/12--sm ]">
<div class="js-toggle-show-headlines pull-right">
<div>
<span class="ui-toggle ui-toggle--default ui-toggle--right">
<input class="ui-toggle__input" type="checkbox" id="ra_fullMessages">
<label class="ui-toggle__label" for="ra_fullMessages" style="touch-action: pan-y; user-select: none; -webkit-user-drag: none; -webkit-tap-highlight-color: rgba(0, 0, 0, 0);"></label>
</span>
</div>
</div>
</div>
</div>
<div>
<div class="settings__description">${translate("fullMessagesDesc")}</div>
</div>
<div class="layout layout--v-center">
<div class="layout-item [ 6/12--sm ]">
<span>${translate("typingNotifications")}</span>
</div>
<div class="layout-item [ 6/12--sm ]">
<div class="js-toggle-show-headlines pull-right">
<div>
<span class="ui-toggle ui-toggle--default ui-toggle--right">
<input class="ui-toggle__input" type="checkbox" id="ra_typingNotifications">
<label class="ui-toggle__label" for="ra_typingNotifications" style="touch-action: pan-y; user-select: none; -webkit-user-drag: none; -webkit-tap-highlight-color: rgba(0, 0, 0, 0);"></label>
</span>
</div>
</div>
</div>
</div>
<div>
<div class="settings__description">${translate("typingNotificationsDesc")}</div>
</div>
<div class="layout layout--v-center">
<div class="layout-item [ 6/12--sm ]">
<span>${translate("sendEnter")}</span>
</div>
<div class="layout-item [ 6/12--sm ]">
<div class="js-toggle-show-headlines pull-right">
<div>
<span class="ui-toggle ui-toggle--default ui-toggle--right">
<input class="ui-toggle__input" type="checkbox" id="ra_sendEnter">
<label class="ui-toggle__label" for="ra_sendEnter" style="touch-action: pan-y; user-select: none; -webkit-user-drag: none; -webkit-tap-highlight-color: rgba(0, 0, 0, 0);"></label>
</span>
</div>
</div>
</div>
</div>
<div>
<div class="settings__description">${translate("sendEnterDesc")}</div>
</div>
</div>
<div class="settings__key">
<div>
<span>${translate("hiddenUsers")}</span>
</div>
<div class="separator separator--alt separator--narrow [ mb ] "></div>
<div class="layout layout--v-center">
<div class="layout-item [ 6/12--sm ]">
<span>${translate("hideMessages")}</span>
</div>
<div class="layout-item [ 6/12--sm ]">
<div class="js-toggle-show-headlines pull-right">
<div>
<span class="ui-toggle ui-toggle--default ui-toggle--right">
<input class="ui-toggle__input" type="checkbox" id="ra_hideMessages">
<label class="ui-toggle__label" for="ra_hideMessages" style="touch-action: pan-y; user-select: none; -webkit-user-drag: none; -webkit-tap-highlight-color: rgba(0, 0, 0, 0);"></label>
</span>
</div>
</div>
</div>
</div>
<div>
<div class="settings__description">${translate("hideMessagesDesc")}</div>
</div>
<div class="layout layout--v-center">
<div class="layout-item [ 6/12--sm ]">
<span>${translate("hideActivities")}</span>
</div>
<div class="layout-item [ 6/12--sm ]">
<div class="js-toggle-show-headlines pull-right">
<div>
<span class="ui-toggle ui-toggle--default ui-toggle--right">
<input class="ui-toggle__input" type="checkbox" id="ra_hideActivities">
<label class="ui-toggle__label" for="ra_hideActivities" style="touch-action: pan-y; user-select: none; -webkit-user-drag: none; -webkit-tap-highlight-color: rgba(0, 0, 0, 0);"></label>
</span>
</div>
</div>
</div>
</div>
<div>
<div class="settings__description">${translate("hideActivitiesDesc")}</div>
</div>
<div class="layout layout--v-center">
<div class="layout-item [ 6/12--sm ]">
<span>${translate("hideLikes")}</span>
</div>
<div class="layout-item [ 6/12--sm ]">
<div class="js-toggle-show-headlines pull-right">
<div>
<span class="ui-toggle ui-toggle--default ui-toggle--right">
<input class="ui-toggle__input" type="checkbox" id="ra_hideLikes">
<label class="ui-toggle__label" for="ra_hideLikes" style="touch-action: pan-y; user-select: none; -webkit-user-drag: none; -webkit-tap-highlight-color: rgba(0, 0, 0, 0);"></label>
</span>
</div>
</div>
</div>
</div>
<div>
<div class="settings__description">${translate("hideLikesDesc")}</div>
</div>
<div class="settings__key">
<div class="layout layout--v-center">
<div class="layout-item [ 6/12--sm ]">
<span>${translate("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>${translate("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="mb">
<span>${translate("hiddenUsersList")}</span>
</div>
<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>`);
function addTag(ul, tag, text, selected, change) {
const li = addElement(ul, `<li class="tags-list__item">
<a class="js-tag ui-tag ui-tag--removable" href="#">
<span class="ui-tag__label">${text}</span>
</a>
</li>`);
const a = li.querySelector("a");
if (selected) {
a.classList.add("ui-tag--selected");
}
li.addEventListener("click", e => {
e.preventDefault();
if (a.classList.contains("ui-tag--selected")) {
change({ tag: tag, checked: false });
a.classList.remove("ui-tag--selected");
} else {
change({ tag: tag, checked: true });
a.classList.add("ui-tag--selected");
}
});
}
// Handle tile settings.
const inEnhancedTiles = pane.querySelector("#ra_enhancedTiles");
inEnhancedTiles.checked = getEnhancedTiles();
inEnhancedTiles.addEventListener("change", e => setEnhancedTiles(e.target.checked));
const inFullHeadlines = pane.querySelector("#ra_fullHeadlines");
inFullHeadlines.checked = getFullHeadlines();
inFullHeadlines.addEventListener("change", e => setFullHeadlines(e.target.checked));
const inEnhancedFilter = pane.querySelector("#ra_enhancedFilter");
inEnhancedFilter.checked = getEnhancedFilter();
inEnhancedFilter.addEventListener("change", e => setEnhancedFilter(e.target.checked));
const inTileSizeFactor = pane.querySelector("#ra_tileSizeFactor");
inTileSizeFactor.value = getTileSizeFactor();
inTileSizeFactor.addEventListener("change", e => setTileSizeFactor(parseInt(e.target.value)));
const inTileSizeGroupFactor = pane.querySelector("#ra_tileSizeGroupFactor");
inTileSizeGroupFactor.value = getTileSizeGroupFactor();
inTileSizeGroupFactor.addEventListener("change", e => setTileSizeGroupFactor(parseInt(e.target.value)));
const inTileDetails = pane.querySelector("#ra_tileDetails");
for (const tileDetail of ["age", "height", "weight", "bmi", "ageRange",
"bodyHair", "bodyType", "ethnicity", "relationship", "analPosition",
"dick", "saferSex", "dirty", "sm", "fisting"]) {
addTag(inTileDetails, tileDetail, translate(tileDetail), tileDetails.has(tileDetail), e => setTileDetail(e.tag, e.checked));
}
// Handle message settings.
const inFullMessages = pane.querySelector("#ra_fullMessages");
inFullMessages.checked = getFullMessages();
inFullMessages.addEventListener("change", e => setFullMessages(e.target.checked));
const inTypingNotifications = pane.querySelector("#ra_typingNotifications");
inTypingNotifications.checked = getTypingNotifications();
inTypingNotifications.addEventListener("change", e => setTypingNotifications(e.target.checked));
const inSendEnter = pane.querySelector("#ra_sendEnter");
inSendEnter.checked = getSendEnter();
inSendEnter.addEventListener("change", e => setSendEnter(e.target.checked));
// Handle hidden interacctions.
const inHideMessages = pane.querySelector("#ra_hideMessages");
inHideMessages.checked = getHideMessages();
inHideMessages.addEventListener("change", e => setHideMessages(e.target.checked));
const inHideActivities = pane.querySelector("#ra_hideActivities");
inHideActivities.checked = getHideActivities();
inHideActivities.addEventListener("change", e => setHideActivities(e.target.checked));
const inHideLikes = pane.querySelector("#ra_hideLikes");
inHideLikes.checked = getHideLikes();
inHideLikes.addEventListener("change", e => setHideLikes(e.target.checked));
// Handle hidden age.
const inMinAge = pane.querySelector("#ra_hiddenMinAge");
const inMaxAge = pane.querySelector("#ra_hiddenMaxAge");
let minAge = getHiddenMinAge();
let maxAge = getHiddenMaxAge();
inMinAge.value = minAge;
inMaxAge.value = maxAge;
inMinAge.addEventListener("change", e => {
minAge = parseInt(e.target.value);
setHiddenMinAge(minAge);
if (minAge > maxAge) {
maxAge = minAge;
setHiddenMaxAge(maxAge);
inMaxAge.val(maxAge);
}
});
inMaxAge.addEventListener("change", e => {
maxAge = parseInt(e.target.value);
setHiddenMaxAge(maxAge);
if (maxAge < minAge) {
minAge = maxAge;
setHiddenMinAge(minAge);
inMinAge.val(minAge);
}
});
// Handle hidden user list.
const hiddenUsers = pane.querySelector("#ra_hiddenUsers");
const users = Array.from(getHiddenUsers()).sort(Intl.Collator().compare);
for (const user of users) {
addTag(hiddenUsers, user, user, true, e => setUserHidden(e.tag, e.checked));
};
}
onElement("li.js-settings > div.accordion > ul", el => {
// Add extension menu item.
const linkClass = el.querySelector("a").className;
const link = addElement(el, `<li>
<div>
<a class="${linkClass}" href="/me/romeoadditions">${GM_info.script.name}</a>
</div>
</li>`);
link.addEventListener("click", e => {
if (link.classList.contains("is-selected")) {
link.classList.remove("is-selected");
} else {
link.classList.add("is-selected");
setTimeout(() => openPane(el, link)); // delayed execution to force open panel
}
});
// Deselect menu item if others are clicked.
for (const linkOther of el.querySelectorAll("li")) {
if (linkOther !== link) {
linkOther.addEventListener("click", e => link.classList.remove("is-selected"));
}
}
});
onElement(`div[class^="Version--"]`, el => {
el.innerHTML += `<br>
<a id="version" href="https://greasyfork.org/en/scripts/419514" target="blank">${GM_info.script.name} ${GM_info.script.version}</a>`;
});
// ---- On Load ----
(function () {
"use strict";
setFullHeadlines(getFullHeadlines());
setTileSizeFactor(getTileSizeFactor());
setFullMessages(getFullMessages());
radarFilter = getRadarFilter();
tileDetails = getTileDetails();
proxyXhr();
})();