RomeoEnhancer

Ergänzungen für die Romeo-Website

  1. // ==UserScript==
  2. // @name RomeoEnhancer
  3. // @version 2.0.2
  4. // @author braveguy (Romeo: braveguy / Gruppe RomeoEnhancer)
  5. // @description Ergänzungen für die Romeo-Website
  6. // @require https://code.jquery.com/jquery-3.7.0.min.js
  7. // @match https://*.romeo.com/*
  8. // @match https://83.98.143.20/*
  9. // @match https://www.hunqz.com/*
  10. // @run-at document-body
  11. // @grant none
  12. // @copyright braveguy 12.10.2016 / 27.06.2025
  13. // @namespace https://gf.qytechs.cn/users/139428
  14. // ==/UserScript==
  15.  
  16.  
  17. /**
  18. * Copyright(c) braveguy (Romeo: braveguy / Gruppe RomeoEnhancer)
  19. *
  20. * Änderungen oder die Wiederverwendung von Code von RomeoEnhancer
  21. * erfordern meine ausdrückliche Zustimmung. Es ist nicht gestattet,
  22. * geänderte Versionen von RomeoEnhancer zu veröffentlichen.
  23. *
  24. * Das Skript wurde mit Tampermonkey in aktuellen Versionen von Safari, Chrome
  25. * und Firefox getestet. Dennoch geschieht die Benutzung auf eigenes Risiko.
  26. *
  27. * ** Datenschutz **
  28. * RomeoEnhancer enthält keinerlei Code, um Nutzer zu identifizieren
  29. * oder Daten auszuspähen. Es besteht keinerlei geschäftliches Interesse.
  30. *
  31. * Die aktuelle Version von RomeoEnhancer ist verfügbar unter:
  32. * https://gf.qytechs.cn/scripts/31282-romeoenhancer
  33. *
  34. *
  35. * ****** English version *****
  36. *
  37. * Copyright(c) by braveguy (Romeo: braveguy / group RomeoEnhancer)
  38. *
  39. * Modifications and/or reuse of RomeoEnhancer code require my explicit consent.
  40. * You are NOT allowed to publish any changed version of RomeoEnhancer!
  41. *
  42. * All code has been tested with Tampermonkey in a recent Safari, Chrome,
  43. * and Firefox browser. However, the use of this script is at your own risk.
  44. *
  45. * ** Privacy **
  46. * RomeoEnhancer does NOT and never will include any code to identify you
  47. * or spy on your data. There is no commercial interest.
  48. *
  49. * The latest version of RomeoEnhancer is available on:
  50. * https://gf.qytechs.cn/scripts/31282-romeoenhancer
  51.  
  52. */
  53.  
  54.  
  55. // ***** Use RE_addStyle instead of GM_addStyle to work with both Gear Browser and Userscripts *****
  56. if (typeof RE_addStyle == 'undefined') {
  57. this.RE_addStyle = (aCss) => {
  58. 'use strict';
  59. let head = document.querySelector('head');
  60. if (head) {
  61. let style = document.createElement('style');
  62. style.setAttribute('type', 'text/css');
  63. style.textContent = aCss;
  64. head.appendChild(style);
  65. return style;
  66. }
  67. return null;
  68. };
  69. }
  70.  
  71.  
  72. // ***** CSS *****
  73.  
  74. //background colors
  75. let color = [];
  76. switch (localStorage.getItem('REcolorScheme')) {
  77. case 'dark': // Romeo default - Dark
  78. break;
  79. case 'lighterGrey': // Lighter Grey
  80. color = ['#171717', '#1f1f1f', '#1f1f1f', '#000000', '#101010', '#1a1a1a', '#f0f0f0', '#cc2d2d', '#2a2a2a', '#2a2a2a'];
  81. break;
  82. case 'retroBlue': // Retro Blue
  83. color = ['#102c54', '#102c54', '#102c54', '#000000', '#101010', '#122646', '#f0f0f0', '#f0f0f0', 'rgb(255,255,255,.08)', '#30415d'];
  84. break;
  85. case 'fineGrey': // RE default - Fine Grey
  86. default:
  87. color = ['#000000', '#171717', '#1a1a1a', '#000000', '#101010', '#232323', '#f0f0f0', '#cc2d2d', '#2a2a2a', '#2a2a2a'];
  88. }
  89.  
  90.  
  91. RE_addStyle (
  92.  
  93. //*** COLOR DESIGN ***
  94.  
  95. //navigation, scrollbar
  96. `.layer-left-navigation nav, header.js-header, .locationPicker, .ui-navbar, .ui-navbar--app, .js-main-stage header, #cruise main > div, #cruise main > div > nav, #cruise main main, .js-nav-sidebar, .js-navigation nav, section.js-stream, .layer__container {background-color:${color[0]} !important}` +
  97. `html ::-webkit-scrollbar-track {background-color:rgb(0,0,0,.25)}` +
  98. // `html ::-webkit-scrollbar {display:none}` +
  99.  
  100. //content
  101. `.js-main-stage, .stream__content, .re-stream-toggle-div, .layer__column--background, .js-social-lg > div > div, .layer__column--detail, #group-preview .layer__container {background-color:${color[1]} !important}` +
  102. `.filter-container {background-color:${color[2]} !important}` +
  103. `.listitem--selected > div {background-color:${color[9]} !important}` +
  104. `li[class^="stat-bar__item--"] {background-color:${color[2]}}` +
  105. `.listresult {background-color:${color[2]}}` +
  106. `.listresult:hover, :is(.js-chat, .js-contacts) .reactView > div:hover {background-color:${color[8]} !important}` +
  107. `.listresult .list-stat-bar__item, .icon-circular.icon-u {background-color:transparent}` +
  108.  
  109. //list view
  110. `:is(.search-results--list-style > div, .grouped-tiles-list .refreshable) > div:nth-child(odd) .reactView > div {background-color:${color[5]}}` +
  111. `:is(.search-results--list-style > div, .grouped-tiles-list .refreshable) > div:nth-child(even) .reactView > div {background-color:${color[1]}}` +
  112. `:is(#cruise, #explore-grid, #eyecandy-results) li:nth-child(odd) > div {background-color:${color[5]} !important}` +
  113. `:is(#cruise, #explore-grid, #eyecandy-results) li:nth-child(even) > div {background-color:${color[1]} !important}` +
  114.  
  115. //profile
  116. `.profile .below-fold {background-color:${color[1]} !important}` +
  117. `.profile-section header {background-color:${color[9]} !important}` +
  118. `.profile-section, .profile-section main, .js-profile-contact main div:last-child, .js-report section {background-color:${color[2]} !important}` +
  119. `.js-profile-footprints section {background-color:${color[8]}}` +
  120.  
  121. //messenger
  122. `#messenger header {background-color:${color[9]} !important}` +
  123. `.messages-list__content, .messages-send, #messenger .js-detail main, #messenger .js-detail main div:last-child {background-color:${color[2]}!important}` +
  124. `.messages-send__form-input {background-color:${color[3]}!important}` +
  125.  
  126. //groups
  127. `section.js-main-stage .js-search > div > div > div {background-color:${color[1]} !important}` +
  128. `.js-wrapper section[class^="Section"], .js-wrapper div[class^="slider_wrapper-"]:before, .js-wrapper div[class^="slider_wrapper-"]:after {background-color:${color[1]} !important}` +
  129. `.re-groups-def button {background-color:${color[9]} !important}` +
  130. `:is(.js-post-edit, .js-post-list, #post, #group-post) textarea {background-color:${color[4]} !important}` +
  131. `.js-post-edit button[class*="ActionButton"] {background-color:${color[1]}}` +
  132. `.js-post-edit div[class*="Wrapper-"] {background-color:${color[5]} !important}` +
  133. `.js-post-list div[class^="Container"], :is(#group-post, #post) :is(.js-post, .js-post div[class^="Container"], div.js-comment) {background-color:${color[5]} !important}` +
  134. `section.js-main-stage div[class^="Grid-"] div[class*="Wrapper-"], section.js-main-stage div[class^="Grid-"] + div[class^="Wrapper-"] {background-color:${color[5]} !important}` +
  135.  
  136.  
  137. //*** OTHER ***
  138.  
  139. //bigger text in messages, groups, profiles; wrap long links
  140. ':is(#messages-list div.reactView, :is(.js-post-list, .js-post, .js-profile-text, .js-description) div[class^="TruncateBlock__Content-"]) > p[class^="BaseText-sc-"],' +
  141. '.profile section.profile__stats details > div > p[class^="BaseText-sc-"],' +
  142. ':is(.stream__content, .js-chat, .js-correspondence, .js-contacts, .js-username, .WGgGs, .js-actions, #manage, .js-profile-reviews) [class^="BodyText-sc-"],' +
  143. '[class^="ResponsiveBodyText-"] {font-size:.9375rem !important; letter-spacing:normal !important; line-height:1.375 !important; word-break:break-word !important}' +
  144.  
  145. //avatar icon
  146. ':is(.layer-left-navigation, header li) div[class^="OnlineStatus"] svg[viewBox="0 0 10 12"] {width:.75rem; height:.75rem}' +
  147.  
  148. //icons, links
  149. 'a.re-icon {font-size:.9em}' +
  150. ':is(div.LIST, .js-contacts, .js-username) a.re-icon {font-size:.825em}' +
  151. 'a.re-idle, a.re-idle-no-hover, a.re-idle-no-hover:hover {color:inherit !important}' +
  152. ':is(a.re-icon, a.re-idle):hover {color:rgb(102,215,255,.8) !important}' +
  153. 'a.re-link, a.re-link-no-hover {color:#00bdff}' +
  154. // '[class*="hunqz"] a.re-link {color:#fc193c}' +
  155. 'a.re-link-idle {color:unset !important}' +
  156. ':is(a.re-link, a.re-link-idle):hover {color:#66d7ff !important}' +
  157. '[class*="hunqz"] :is(a.re-link, a.re-link-idle, a.re-icon):hover {color:#fd5871 !important}' +
  158.  
  159. //emoji zoom on hover
  160. 'span[class^="formatEmoji__"]:hover, .listitem .emoji:hover {line-height:1; font-size:2em; margin:-.35em -.15em; display:inline-block}' +
  161. '.listitem .emoji {line-height:1; font-size:1.1em; margin:0; vertical-align:top}' +
  162.  
  163. //tab menu
  164. '.js-main-stage header > nav > a {min-width:unset}' +
  165. '.js-main-stage header > nav {max-width:calc(100% - 18.5rem)}' +
  166. '.is-stream-opened .js-main-stage header > nav {max-width:unset}' +
  167.  
  168. //top right menu
  169. '.js-top-right-navigation ul {scale:.975; width:17.5rem;left:calc(-17.5rem + 100vw); align-items:flex-end; padding-bottom:.33rem}' +
  170. '.js-top-right-navigation li {margin-right:1.25rem}' +
  171. '.js-top-right-navigation div.icon {font-size:1.2rem; line-height:1.5rem}' +
  172.  
  173. //visitor icons
  174. 'a.re-icon-visitor, a.re-icon-visited {font-size:1.1em; color:#fff}' +
  175. 'a.re-icon-visited {transform:scaleX(-1)}' +
  176.  
  177. //discover page
  178. ':is(a.re-eyecandy-toggle, a.re-eyecandy-toggle:hover) span {display:none; margin: 0 .25rem 0 .5rem; font-size:.875rem; color:rgb(255,255,255,1) !important; text-transform:none; font-family:Inter; font-weight:normal}' +
  179. 'a.re-eyecandy-toggle.is-selected span:first-child, a.re-eyecandy-toggle:not(.is-selected) span:nth-child(2) {display:inline}' +
  180. 'a.re-eyecandy-toggle svg {margin-bottom:.125rem}' +
  181. 'a.re-eyecandy-toggle svg path {fill:currentcolor}' +
  182.  
  183. //filter icon, bookmark name
  184. //'.re-filter-bookmark-name {position:absolute; right:0}' +
  185. '.is-filter-collapsed .re-filter-bookmark-name {display:none!important}' +
  186. '.is-filter-opened .re-filter-options-text, .is-filter-opened div.js-filter-button svg + span + span:not(.re-filter-bookmark-name) {display:none!important}' +
  187.  
  188. //hide bookmark name in hunqz filter
  189. '.js-main-stage:has(.js-navigation a[href^="/hunqz"]) div.js-filter-header p {display:none !important}' +
  190.  
  191. //expand truncated location names on hover
  192. '.typo-small.txt-truncate:hover {white-space:normal; word-break:break-all}' +
  193.  
  194. //tiles
  195. ':is(div.BIG, div.SMALL, div.LIST) > div:first-child [class^="SpecialText"] {text-shadow:rgb(0,0,0,.5) 0 1px 1px, rgb(0,0,0,.5) 1px 1px 1px}' +
  196. ':is(div.BIG, div.SMALL) p[class^="SpecialText"] {word-break:break-word}' +
  197. ':is(div.BIG, div.SMALL) p[class^="SpecialText"]:hover {white-space:normal}' +
  198. 'div.BIG span[class^="SpecialText"] {background-color:rgb(38,38,38)}' +
  199. '.re-nsfw-placeholder > div {background-color: rgb(31,31,31,.75) !important; background-image:none !important; backdrop-filter: blur(1px)}' +
  200. 'section:has(.re-selected) :is(.BIG, .SMALL, .LIST) svg + p::before {content:"+"; font-size:.8125rem}' +
  201. // 'div[class^="Dropdown__ClickOutsideContainer"]:has(.re-selected) :is(.BIG, .SMALL, .LIST) svg + p::before {content:"+"; font-size:.8125rem}' +
  202. 'div[class^="List-"]:has(.js-members-header):has(.re-selected) :is(.BIG, .SMALL, .LIST) svg + p::before {content:"+"; font-size:.8125rem}' +
  203.  
  204. //FIX jumping fade in tiles during load
  205. 'div.BIG::before, div.SMALL::before {inset:60% 0 0 !important}' +
  206.  
  207. //tile TEST
  208. // ':is(div.BIG, div.SMALL, div.LIST) > div {display:none}' +
  209. // ':is(div.BIG, div.SMALL, div.LIST):hover > div:first-child {display:flex}' +
  210. // ':is(div.BIG, div.SMALL, div.LIST):hover > div:last-child {display:block}' +
  211.  
  212. //list view
  213. // 'div:has(> .LIST) + div {margin-top:.5rem}' +
  214. // 'button:has(.LIST) {align-items:end}' +
  215.  
  216. //messenger
  217. '.message:not(.message--sent) .message__content {background:hsla(0,0%,100%,.125)}' +
  218. '.message:not(.message--sent) .message__content:before {border-color: transparent transparent hsla(0,0%,100%,.125); left:-9px}' +
  219. '.message:not(.message--sent) .message__attachments {background-color:transparent}' +
  220. '.message__status {color:rgb(255,255,255,.5)!important}' +
  221. ':is(.js-chat, #manage) div[class^="OnlineStatus"] > div > svg {height:13px; width:13px}' +
  222. ':is(.js-chat, #manage) div[class^="OnlineStatus"] > div > svg[viewBox="0 0 10 12"] {height:15px; width:15px}' +
  223. 'p[class*="OnlineStatus__TimeString"] {line-height:1}' +
  224. '.js-chat .js-scrollable {max-height:1270px !important}' +
  225. '.js-chat .typo-headline-small {font-size:inherit}' +
  226. '.fQIYNa {font-weight:550}' +
  227. '.online-favourites__item {margin:0 .475rem !important}' +
  228. '.online-favourites__itemName {width:4rem; margin:0 -.125rem}' +
  229. 'section.emoji-mart button.emoji-mart-anchor:hover {color:rgb(255,255,255,.5)}' +
  230. '.js-chat p[class^="BodyText-sc-"] {opacity:.87; column-gap:.25rem}' +
  231. '.js-chat a[href^="/messenger"] > div > span {opacity:.87}' +
  232. '.js-chat a > span > span svg[viewBox="0 0 9 7"] {height:9px; width:11px; opacity:.66}' +
  233. '.js-chat a > span > span svg[viewBox="0 0 12 12"] {height:15px; width:15px}' +
  234. '#messenger .js-header-region header > h1 {padding-top:.8rem}' +
  235. '#messenger .js-header-region header > h1 a:has(span) {padding-bottom:.85rem}' +
  236. '#messenger .js-header-region .reactView > div > div > :is(div, a) {position:absolute; top:-1.2rem}' +
  237. '#messenger .re-login-location {position:absolute; top:0; width:100%}' +
  238. '#messenger .re-login-location > div {position:absolute; font-size:.75rem; font-weight:500; opacity:.8; background-color:transparent}' +
  239. '#messenger .re-msg-location {top:0; width:100%; padding:.25rem 0 0 3rem}' +
  240. '#messenger .re-msg-location a {padding-top:3px}' +
  241. '#messenger .re-msg-login {top:2.7rem; left:4rem; line-height:.85rem; min-width:10rem; padding-left:3rem; margin-right:5rem}' +
  242. '#messenger .re-msg-offline {top:2.7rem; left:3rem; line-height:.85rem}' +
  243. '.messages-send__form textarea.input--chat {padding-right:.5rem}' +
  244. '.js-contacts div > div > button > svg {height:21px; width:21px}' +
  245. 'ul[class^="TagCloud__UnstyledList"] button {font-size:.875rem; line-height:1.25}' +
  246. ':is(.js-detail, .js-profile-contact) p[class^="ResponsiveBodyText-"] + :is(span, p) {font-size:.875rem !important; padding-bottom:.25rem}' +
  247. '.js-chat .listitem--selected {border-right:5px solid #fff}' +
  248. '.js-chat .refreshable .reactView {min-height:6rem}' +
  249. '.js-contacts .listitem--selected > div {border-left:5px solid #fff}' +
  250.  
  251. //cruise
  252.  
  253. //stream
  254. 'div.stream__content {top:calc(5.25rem + 1px)}' +
  255. 'div.stream__content div.tile {margin:0}' +
  256. 'div.stream__content div.tile div[class^="OnlineStatus"] {position:absolute; left:-8px; top:-8px}' +
  257. 'div.stream__content div.tile div[class^="OnlineStatus"] > div {display:flex; justify-content:center; align-items:center; background-color:#1f1f1f; border-radius:50%; height:20px; width:20px; margin-top:0!important}' +
  258. 'div.stream__content div.tile div[class^="OnlineStatus"] svg {height:12px; width:12px}' +
  259. 'div.stream__content div.tile div[class^="OnlineStatus"] svg:has(path[d^="M7.463"]) {height:14px; width:14px; margin-top:2px}' +
  260. '.stream__content .listitem {min-height:4.8rem}' +
  261. 'div.stream__content .re-group-item:has(path[d^="M8.039"]) {display:none}' +
  262.  
  263. //stream toggle
  264. '.re-stream-toggle-div {position:absolute; top:3.5rem; width:100%; padding:0 1rem; display:flex; justify-content:space-between; align-items:center; border-bottom:1px solid #2e2e2e}' +
  265. ':is(a.re-stream-toggle, a.re-stream-toggle:hover) span {display:none; margin-right:.25rem; font-size:.875rem; color:rgb(255,255,255,.875); font-family:Inter; font-weight:normal}' +
  266. 'a.re-stream-toggle.re-selected span:first-child, a.re-stream-toggle:not(.re-selected) span:nth-child(2) {display:inline}' +
  267. 'a.re-stream-toggle svg {margin-bottom:.125rem}' +
  268. 'a.re-stream-toggle svg path {fill:currentcolor}' +
  269. '.stream__content .re-group-item {display:none}' +
  270. '.stream__content.re-selected .re-group-item {display:flex}' +
  271.  
  272. //profile view
  273. '.re-hide-visit, .re-profile-new {position:absolute !important; top:.5rem; left:3rem; font-size:1rem; font-weight:500; font-family:Inter; color:transparent}' +
  274. '.re-hide-visit {z-index:2}' +
  275. '.re-profile-new span {color:rgb(0 209 0); background-color:rgb(46 46 46); margin-left:1.75rem; padding:0 .25rem; border-radius:.25rem; font-size:.875rem}' +
  276. '.profile__info p {word-break:break-word}' +
  277. '.profile div[aria-label] > div[role="figure"] {border-color:rgb(255,255,255,.33)}' +
  278. '.profile .re-profile-stats {font-size:.825rem; color:rgb(255 255 255/.6)}' +
  279. '.re-img-count {position:absolute; bottom:0; left:0; padding:.5rem}' +
  280. '.re-img-count div {color:rgb(255 255 255/.87); background-color:rgb(18 18 18); border-radius:.125rem; padding:0 .25rem; height:1.125rem; line-height:1rem}' +
  281. '.js-profile-footprints header {grid-template-columns:2.5rem auto 3rem}' +
  282. '.js-profile-footprints h1 a {font-size:1rem !important}' +
  283. '.js-profile-footprints section:last-child {padding-top:7.5rem}' +
  284. '.js-profile-footprints section + section:last-child {padding-top:0}' +
  285. '.js-profile-footprints.re-zoom section:last-child {grid-template-columns:repeat(3,33%)}' +
  286. '.js-profile-footprints.re-zoom section:last-child button {background-size:72px 72px; height:77px; width:72px; border-bottom-width:5px}' +
  287. '.js-profile-footprints.re-zoom section:last-child label p {max-width:14ch}' +
  288.  
  289. //media viewer
  290. '.ReactModal__Content ul:has(img[src^="/img/usr/original/"]) {padding-bottom:2rem}' +
  291. '.ReactModal__Content li:has(img[src^="/img/usr/original/"]) {max-height:85vh; min-height:unset; padding-bottom:2rem !important}' +
  292. '.ReactModal__Content li:has(img[src^="/img/usr/original/"]) svg:has(path[d^="M17.939"], path[d^="m3.911"]) {transform:scale(75%)}' +
  293. // '.ReactModal__Content li:has(img[src^="/img/usr/original/"]) button:has(path[d^="M17.939"], path[d^="m3.911"]) {position:relative; top:.375rem}' +
  294. '.ReactModal__Content li:has(img[src^="/img/usr/original/"]) p {font-size:.875rem}' +
  295. '.re-img-info {position:absolute; bottom:.75rem; right:3rem; width:100%; display:flex; justify-content:end}' +
  296. '.re-img-single {bottom:1rem; right:0; justify-content:center}' +
  297. '.re-img-info a {font-size:.8125rem; color:rgb(255,255,255,.5); text-shadow:rgb(0,0,0,.32) 0 1px 1px, rgb(0,0,0,.42) 1px 1px 1px; cursor:default; z-index:2}' +
  298. '.ReactModal__Content li:has(img[src^="/img/usr/original/"]) > div:has(path[d^="M17.939"], path[d^="m3.911"]) {background-image:linear-gradient(rgb(0,0,0,0) 10%, rgb(0,0,0,.66) 63%, rgb(0,0,0,.88) 90%)}' +
  299. '.ReactModal__Content li img[src^="/img/usr/original/"] {position:relative; object-fit:contain; cursor:pointer}' +
  300.  
  301. //group tiles
  302. 'div[class*="Tile__BaseTile-"] > p + div {position:absolute; bottom:2rem; left:.475rem}' +
  303. 'div[class*="Tile__BaseTile-"] > p + div > p {background-color:#121212 !important}' +
  304. 'div[class*="Tile__BaseTile-"] > p {margin:0 0 -.125rem}' +
  305. '.profile div[class*="Tile__BaseTile-"] > p {margin:0; font-size:.925rem}' +
  306. '.re-common-group p:last-child {color:#6ddc00}' +
  307.  
  308. //groups
  309. 'nav.re-groups-def, nav.re-groups-enh {position:relative; padding:0 1rem 1rem; height:calc(100% - 3rem); overflow-y:auto}' +
  310. 'nav.re-groups-def {display:none}' +
  311. '.js-date .reactView span {font-size:.85em}' +
  312. '.js-post-list button[class*="ShowMoreButton"].is-hidden {display:block!important}' +
  313. '.js-post-list p[class^="BaseText"] > button {margin-top:.25rem}' +
  314. // 'div.js-post-list p.js-detail div[class^="TruncateBlock__Content"] {-webkit-line-clamp:8}' +
  315. 'div[class^="CommentsArea"] div[class^="TruncateBlock__Content"] {-webkit-line-clamp:unset}' +
  316. 'div[class^="CommentsArea"] div[class^="TruncateBlock__Container"] p button {display:none}' +
  317. // '.js-main .js-sidebar {max-height:inherit !important}' +
  318. // '.js-main .js-sidebar .re-groups-def {padding-bottom:1rem}' +
  319. '.js-blurred-image > div {z-index:0}' +
  320. 'div[class^="NameAndOfficialBadgeWrapper-"] {white-space:unset}' +
  321.  
  322. //groups list
  323. 'span.re-posts-view {font-family: "Gibson Bold"; font-weight:500; text-transform:uppercase; background-color:transparent; color:#fff; height:auto; cursor:pointer; padding-left:2rem;}' +
  324. 'span.re-list-head, span.re-list-view, span.re-list-load {font-family:Inter,Helvetica,Arial,"Open Sans",sans-serif; text-transform:uppercase; user-select:none; color:rgb(255,255,255,.5); height:auto}' +
  325. 'span.re-list-view, span.re-list-load {font-family: "Gibson Bold"; font-weight:500; color:rgb(250,250,250); cursor:pointer;}' +
  326. 'span.re-list-load:before {display:inline-block; transform:scaleX(-1)}' +
  327. '.re-groups-listitem {font-family:Inter,Helvetica,Arial,"Open Sans",sans-serif; display:flex; align-items:center; border-bottom: 1px solid #2e2e2e; padding:.66em .5em; width:100%}' +
  328. `.re-groups-listitem:hover {background-color:${color[8]}}` +
  329. '.re-groups-tile {display:flex; flex-shrink:0; margin-right:1em; background-size:cover; border-radius:50%; height:3em; width:3em}' +
  330. '.re-groups-entry {flex-grow:1; flex-shrink:1; min-width:0; display:flex; flex-direction:column}' +
  331. '.re-groups-top-row, .re-groups-bottom-row {display:flex; flex-direction:row; align-items:center; justify-content:space-between}' +
  332. '.re-groups-name {font-size:.9375rem; color:rgb(0,163,228); font-weight:500; display:-webkit-box; -webkit-box-orient:vertical; -webkit-line-clamp:1; overflow:hidden; word-break:break-all}' +
  333. '.re-groups-time {font-size:.825em; color:rgb(250,250,250,.625)}' +
  334. '.re-groups-activity {display:flex; flex-direction:row; position:relative; top:-.1rem}' +
  335. '.re-groups-new {display:flex; align-items:center; margin-left:.5rem; color:rgb(250,250,250,.8); font-size:0.8em}' +
  336. '.re-groups-new svg {display:inline-block; height:1em; width:1.125em; vertical-align:middle}' +
  337. '.re-groups-new svg path {fill:currentcolor}' +
  338. '.re-groups-admin {color:rgb(0,0,0,.8); background-color:rgb(0,189,255); padding:0 .25rem; border-radius:.25rem; font-size:0.625rem}' +
  339. `.re-groups-selected {background-color:${color[9]}}` +
  340. '.re-groups-selected .re-groups-name {color:rgb(255,255,255,.925)}' +
  341. '.re-groups-selected .re-groups-new {color:rgb(250,250,250,.425)}' +
  342. '.re-groups-selected .re-groups-admin {background-color:rgb(255,255,255,.87)}' +
  343. '.re-groups-visited :is(.re-groups-name, .re-groups-new) {color:rgb(250,250,250,.425)}' +
  344. '.re-groups-visited .re-groups-time {color:rgb(250,250,250,.375)}' +
  345. '.re-groups-visited .re-groups-admin {background-color:rgb(250,250,250,.425)}' +
  346.  
  347. //travel
  348. '.re-member-travel, .re-radar-travel, .re-edit-travel {color:#00bdff; font-family:Inter; font-size:.925em; white-space:nowrap; text-transform:initial; vertical-align: middle; line-height:inherit !important; cursor:pointer}' +
  349. 'div[class^="Dropdown__Container"] :is(.re-member-travel, .re-edit-travel) {padding-top:.66rem}' +
  350. '.re-member-travel:hover, .re-radar-travel:hover, .re-edit-travel:hover {color:#66d7ff}' +
  351. '.re-member-travel.re-selected, .re-radar-travel.re-selected {color:#fff}' +
  352. ':is(.js-location-list, .travel-list) label {background-color:#ccc}' +
  353.  
  354. //picture rating
  355. '.re-rating-date {position:absolute; top:2.5rem; font-size:.85em; cursor:default}' +
  356. // '.re-rating-skip:focus {outline-color:transparent !important}' +
  357. // 'button[class^="TertiaryButton__Element-"]:focus {outline-color:transparent !important}' +
  358.  
  359. //relogin
  360. '.re-relogin-frame {background-color:transparent !important}' +
  361. '.re-relogin-frame:before {border:none !important}' +
  362.  
  363. //more big tiles per page
  364. '@media screen and (min-width:55rem) {' +
  365. '.search-results--big-tiles .search-results__item:not(.search-results__banner) {padding-bottom:calc(25% - 1px) !important; width:calc(25% - 1px) !important}' +
  366. ':is(.is-filter-opened, .is-stream-opened) .search-results--big-tiles .search-results__item:not(.search-results__banner) {padding-bottom:calc(33.33333% - 1px) !important; width:calc(33.33333% - 1px) !important}' +
  367. '.is-stream-opened.is-filter-opened #profiles div.search-results--big-tiles div.search-results__item:not(.search-results__banner) {padding-bottom:calc(50% - 1px) !important; width:calc(50% - 1px) !important}' +
  368. '#cruise main > ul, #eyecandy-results > ul {grid-template-columns:repeat(4,1fr)}' +
  369. '.is-stream-opened #cruise main > ul, .is-stream-opened #eyecandy-results > ul, #explore-grid > ul {grid-template-columns:repeat(3,1fr)}' +
  370. '.is-stream-opened #explore-grid > ul {grid-template-columns:repeat(2,1fr)}' +
  371. '}' +
  372. '@media screen and (min-width:75rem) {' +
  373. '.search-results--big-tiles .search-results__item:not(.search-results__banner) {padding-bottom:calc(20% - 1px) !important; width:calc(20% - 1px) !important}' +
  374. ':is(.is-filter-opened, .is-stream-opened) .search-results--big-tiles .search-results__item:not(.search-results__banner) {padding-bottom:calc(25% - 1px) !important; width:calc(25% - 1px) !important}' +
  375. '.is-stream-opened.is-filter-opened #profiles div.search-results--big-tiles div.search-results__item:not(.search-results__banner) {padding-bottom:calc(33.33333% - 1px) !important; width:calc(33.33333% - 1px) !important}' +
  376. '#cruise main > ul, #eyecandy-results > ul {grid-template-columns: repeat(5,1fr)}' +
  377. '.is-stream-opened #cruise main > ul, .is-stream-opened #eyecandy-results > ul, #explore-grid > ul {grid-template-columns:repeat(4,1fr)}' +
  378. '.is-stream-opened #explore-grid > ul {grid-template-columns:repeat(3,1fr)}' +
  379. '}' +
  380. '@media screen and (min-width:95rem) {' +
  381. '.search-results--big-tiles .search-results__item:not(.search-results__banner) {padding-bottom:calc(16.66666% - 1px) !important; width:calc(16.66666% - 1px) !important}' +
  382. ':is(.is-filter-opened, .is-stream-opened) .search-results--big-tiles .search-results__item:not(.search-results__banner) {padding-bottom:calc(20% - 1px) !important; width:calc(20% - 1px) !important}' +
  383. '.is-stream-opened.is-filter-opened #profiles div.search-results--big-tiles div.search-results__item:not(.search-results__banner) {padding-bottom:calc(25% - 1px) !important; width:calc(25% - 1px) !important}' +
  384. '#cruise main > ul, #eyecandy-results > ul {grid-template-columns:repeat(6,1fr)}' +
  385. '.is-stream-opened #cruise main > ul, .is-stream-opened #eyecandy-results > ul, #explore-grid > ul {grid-template-columns:repeat(5,1fr)}' +
  386. '.is-stream-opened #explore-grid > ul {grid-template-columns:repeat(4,1fr)}' +
  387. '}' +
  388. '@media screen and (min-width:120rem) {' +
  389. '.search-results--big-tiles .search-results__item:not(.search-results__banner) {padding-bottom:calc(12.5% - 1px) !important; width:calc(12.5% - 1px) !important}' +
  390. ':is(.is-filter-opened, .is-stream-opened) .search-results--big-tiles .search-results__item:not(.search-results__banner) {padding-bottom:calc(16.66666% - 1px) !important; width:calc(16.66666% - 1px) !important}' +
  391. '.is-stream-opened.is-filter-opened #profiles div.search-results--big-tiles div.search-results__item:not(.search-results__banner) {padding-bottom:calc(20% - 1px) !important; width:calc(20% - 1px) !important}' +
  392. '#cruise main > ul, #eyecandy-results > ul {grid-template-columns:repeat(7,1fr)}' +
  393. '.is-stream-opened #cruise main > ul, .is-stream-opened #eyecandy-results > ul, #explore-grid > ul {grid-template-columns:repeat(6,1fr)}' +
  394. '.is-stream-opened #explore-grid > ul {grid-template-columns:repeat(5,1fr)}' +
  395. '}' +
  396. '@media screen and (min-width:140rem) {' +
  397. '.search-results--big-tiles .search-results__item:not(.search-results__banner) {padding-bottom:calc(12.5% - 1px) !important; width:calc(12.5% - 1px) !important}' +
  398. ':is(.is-filter-opened, .is-stream-opened) .search-results--big-tiles .search-results__item:not(.search-results__banner) {padding-bottom:calc(12.5% - 1px) !important; width:calc(12.5% - 1px) !important}' +
  399. '.is-stream-opened.is-filter-opened #profiles div.search-results--big-tiles div.search-results__item:not(.search-results__banner) {padding-bottom:calc(16.66666% - 1px) !important; width:calc(16.66666% - 1px) !important}' +
  400. '#cruise main > ul, #eyecandy-results > ul {grid-template-columns:repeat(8,1fr)}' +
  401. '.is-stream-opened #cruise main > ul, .is-stream-opened #eyecandy-results > ul, #explore-grid > ul {grid-template-columns:repeat(7,1fr)}' +
  402. '.is-stream-opened #explore-grid > ul {grid-template-columns:repeat(6,1fr)}' +
  403. '}' +
  404.  
  405.  
  406. //*** DESKTOP ***
  407. '@media screen and (min-width:768px) {' +
  408.  
  409. //general
  410. '.re-mobi {display:none!important}' +
  411. '.re-touch {visibility:hidden!important}' +
  412.  
  413. //FIX for group member tiles and posting likes
  414. // '.grouped-tiles-small .tile {width:calc(33.33333% - 1px)}' +
  415. // '.grouped-tiles-big .tile {width:calc(50% - 1px)}' +
  416. 'div.Container--XPRsa .tile {width: calc(25% - 1px)}' +
  417.  
  418. //hunqz
  419. 'div.locationPicker {right:.5rem}' +
  420. '.is-stream-opened div.locationPicker {right:0}' +
  421.  
  422. '}' +
  423.  
  424.  
  425. //*** DESKTOP >= 1024px ***
  426. '@media screen and (min-width:1024px) {' +
  427.  
  428. //stream
  429. '.stream {width:18.5rem!important}'+
  430. '.is-stream-opened .layer--nav-primary {right:18.5rem}' +
  431.  
  432. //hunqz
  433. 'div.locationPicker {right:19rem}' +
  434. '.is-stream-opened div.locationPicker {right:.5rem}' +
  435.  
  436. '}' +
  437.  
  438.  
  439. //*** TABLET ***
  440. '@media screen and (max-width:1023px) {' +
  441.  
  442. //tab menu
  443. '.js-navigation nav > a, #cruise nav > a, .js-main-stage header > nav > a {font-size:1.05rem}' +
  444. '.js-main-stage header > nav {max-width:unset}' +
  445.  
  446. //top right navigation
  447. 'nav.js-top-right-navigation{display:none}' +
  448.  
  449. //FIX for group member tiles and posting likes
  450. '.grouped-tiles-small .tile {width:calc(33.33333% - 1px)}' +
  451. '.grouped-tiles-big .tile {width:calc(50% - 1px)}' +
  452. 'div.Container--XPRsa .tile {width: calc(33.33333% - 1px)}' +
  453.  
  454. '}' +
  455.  
  456.  
  457. //*** MOBILE ***
  458. '@media screen and (max-width:767px) {' +
  459.  
  460. //general
  461. '.re-desk {display:none!important}' +
  462. '.re-touch {visibility:hidden!important}' +
  463.  
  464. //tweak dark design
  465. // `.ui-navbar--app, .ui-navbar.content-nav ul {background-color:#000 !important}` +
  466. 'div.content-nav--animated {height:3rem}' +
  467.  
  468. //main page
  469. // '.header-visible div[class^="MainContainer-"] {bottom: 3.5rem}' +
  470. // 'header.js-header {height:3.75rem}' +
  471.  
  472. //tab menu
  473. '.js-navigation nav > a, #cruise nav > a, .js-main-stage header > nav > a {font-size:.875rem}' +
  474. // '.js-navigation nav {height:3.25rem}' +
  475.  
  476. //stream
  477. '.re-stream-toggle-div {top:calc(3.25rem - 2px); padding:.5rem 1rem 0}' +
  478. 'div.stream__content {top:calc(5.5rem + 1px)}' +
  479. `.layer-header--primary {background-color:${color[2]} !important}` +
  480.  
  481. //messages
  482. '#messenger .js-header-region header > h1 {padding-top:1.3rem}' +
  483. '#messenger .js-header-region header > h1 a:has(span) {padding-bottom:1.2rem}' +
  484. '#messenger .re-msg-location {padding-top:.75rem}' +
  485. '#messenger .re-msg-login {top:3.2rem}' +
  486. '#messenger .re-msg-offline {top:3.2rem}' +
  487. '.messages-send__form textarea.input--chat {font-size:.9375rem; letter-spacing:normal}' +
  488.  
  489. //group tiles
  490. 'div[class*="Tile__BaseTile-"] > p {margin:0; font-size:.925rem}' +
  491.  
  492. //picture rating
  493. '.re-rating-date {top:3rem}' +
  494.  
  495. //hide groups list when group selected, wrap long group names
  496. 'div[class*="withNav-"] > div[class*="ReactContainer-"]:not(:has(.re-groups-def)) {flex:0; width:0}' +
  497. 'div[class*="withNav-"] > div[class*="ReactContainer-"]:not(:has(.re-groups-def)) > :is(div, nav) {display:none}' +
  498. 'div[class*="withNav-"] > div[class*="ReactContainer-"]:has(.re-groups-def) + div.js-main {display:none}' +
  499. // 'div[class^="NameAndOfficialBadgeWrapper-"] {white-space:unset}' +
  500.  
  501. //groups header
  502. '.js-header-container p[class*="HeadlineL"] {font-size:1.375rem; text-wrap-mode:wrap}' +
  503. '.js-header-container a span[class*="ResponsiveBodyText-"] {word-break:normal !important}' +
  504.  
  505. //back arrow in group post
  506. '#group-post .js-post button.js-back {margin:1rem 1rem 0 -.5rem}' +
  507.  
  508. //FIX for flickering top right navigation
  509. // 'nav.js-top-right-navigation {display:none}' +
  510.  
  511. //show distance slide in hunqz nearby filter
  512. '.js-main-stage:has(.js-navigation a[href^="/hunqz"]) div.js-distance-radius.filter__group {display:block !important}' +
  513.  
  514. '}' +
  515.  
  516.  
  517. //*** TOUCH SCREEN ***
  518. '@media screen and (hover:none) {' +
  519.  
  520. //general
  521. '.re-touch {visibility:visible!important}' +
  522.  
  523. //icons, links
  524. 'a.re-icon {padding:.25em 0 .25rem .5em; font-size:1em}' +
  525. 'div.stream__content a.re-icon {padding:.25em .5em .25em 0}' +
  526. 'a.re-eyecandy-toggle {padding:.25em .5em}' +
  527.  
  528. '}'
  529.  
  530. );
  531.  
  532.  
  533. // ***** Clean up old settings *****
  534. localStorage.removeItem('contactsSelection');
  535. localStorage.removeItem('REgroupPostsView');
  536. localStorage.removeItem('reRatingMax');
  537.  
  538.  
  539. // ***** Test if mobile or touch device *****
  540. // usage: if (mobile.matches) ...; if (touch.matches) ...
  541. let mobile = window.matchMedia("(max-width:767px)");
  542. let touch = window.matchMedia("(hover:none)");
  543.  
  544.  
  545. // **************************************************************
  546. // ***** The following function is taken from:
  547. // ***** https://gist.github.com/BrockA
  548. // ***** https://gist.github.com/raw/2625891/waitForKeyElements.js
  549.  
  550.  
  551. /*--- waitForKeyElements(): A utility function, for Greasemonkey scripts,
  552. that detects and handles AJAXed content.
  553.  
  554. Usage example:
  555.  
  556. waitForKeyElements (
  557. "div.comments"
  558. , commentCallbackFunction
  559. );
  560.  
  561. //--- Page-specific function to do what we want when the node is found.
  562. function commentCallbackFunction (jNode) {
  563. jNode.text ("This comment changed by waitForKeyElements().");
  564. }
  565.  
  566. IMPORTANT: This function requires your script to have loaded jQuery.
  567. */
  568. function waitForKeyElements (
  569. selectorTxt, /* Required: The jQuery selector string that
  570. specifies the desired element(s).
  571. */
  572. actionFunction, /* Required: The code to run when elements are
  573. found. It is passed a jNode to the matched
  574. element.
  575. */
  576. bWaitOnce, /* Optional: If false, will continue to scan for
  577. new elements even after the first match is
  578. found.
  579. */
  580. iframeSelector /* Optional: If set, identifies the iframe to
  581. search.
  582. */
  583. ) {
  584. var targetNodes, btargetsFound;
  585.  
  586. if (typeof iframeSelector == "undefined")
  587. targetNodes = $(selectorTxt);
  588. else
  589. targetNodes = $(iframeSelector).contents ()
  590. .find (selectorTxt);
  591.  
  592. if (targetNodes && targetNodes.length > 0) {
  593. btargetsFound = true;
  594. /*--- Found target node(s). Go through each and act if they
  595. are new.
  596. */
  597. targetNodes.each ( function () {
  598. var jThis = $(this);
  599. var alreadyFound = jThis.data ('alreadyFound') || false;
  600.  
  601. if (!alreadyFound) {
  602. //--- Call the payload function.
  603. var cancelFound = actionFunction (jThis);
  604. if (cancelFound)
  605. btargetsFound = false;
  606. else
  607. jThis.data ('alreadyFound', true);
  608. }
  609. } );
  610. }
  611. else {
  612. btargetsFound = false;
  613. }
  614.  
  615. //--- Get the timer-control variable for this selector.
  616. var controlObj = waitForKeyElements.controlObj || {};
  617. var controlKey = selectorTxt.replace (/[^\w]/g, "_");
  618. var timeControl = controlObj [controlKey];
  619. var timerInterval = (document.visibilityState === "visible" ? 750 : 1500);
  620.  
  621. //--- Now set or clear the timer as appropriate.
  622. if (btargetsFound && bWaitOnce && timeControl) {
  623. //--- The only condition where we need to clear the timer.
  624. clearInterval (timeControl);
  625. delete controlObj [controlKey];
  626. }
  627. else {
  628. //--- Set a timer, if needed.
  629. if ( ! timeControl ) {
  630. timeControl = setInterval ( function () {
  631. waitForKeyElements (selectorTxt,
  632. actionFunction,
  633. bWaitOnce,
  634. iframeSelector
  635. );
  636. },
  637. timerInterval
  638. );
  639. controlObj [controlKey] = timeControl;
  640. }
  641. }
  642. waitForKeyElements.controlObj = controlObj;
  643. }
  644.  
  645. // ***** end
  646. // **************************************************************
  647.  
  648.  
  649. // ***** Set AJAX headers *****
  650. let key0 = window.atob('WC1TZXNzaW9uLUlk');
  651. let key1 = window.atob('WC1BcGktS2V5');
  652. let val1 = window.atob('anY4ZXdCRjh6MWw1UzFsaTdhNHBJcWZIcmRmMDRHUU0=');
  653. function ajaxHead(){
  654. return {[key0]: JSON.parse(localStorage.getItem('PR_SETTINGS:SESSION_ID')), [key1]: val1};
  655. }
  656.  
  657.  
  658. // ***** Replace URLs with links *****
  659. // ***** Adopted from https://stackoverflow.com/questions/37684/how-to-replace-plain-urls-with-links/37687#37687 *****
  660. function linkify(text) {
  661.  
  662. //Email addresses
  663. let pattern1 = /(^|\s|<br>)(([-\w\.\+])+@[-\w]+?(\.[A-Z]{2,6})+)/gim;
  664. text = text.replace(pattern1, '$1<a href="mailto:$2" class="plain-text-link">$2</a>');
  665.  
  666. //URLs starting with http:// or https://
  667. let pattern2 = /(^|\s|<br>)(https?:\/\/[-\w+&@#\/\(\)%?=~|!:,.;]*[-\w+&@#\/\(\)%=~|])/gim;
  668. text = text.replace(pattern2, '$1<a target="_blank" href="$2" rel="noreferrer noopener" class="plain-text-link">$2</a>');
  669.  
  670. //URLs without http:// or https://
  671. let pattern3 = /(^|\s|<br>)([-\w]+\.[a-z][-\w+&#\/\(\)%?=~|!:,.;]*[-\w+&#\/\(\)%=~|])/gm;
  672. text = text.replace(pattern3, '$1<a target="_blank" href="http://$2" rel="noreferrer noopener" class="plain-text-link re-link-idle">$2</a>');
  673.  
  674. //keep internal links in same window
  675. let pattern4 = /(target="_blank" )(href="https?:\/\/(www\.)?((planet)?romeo|hunqz)\.com)/gim;
  676. text = text.replace(pattern4, '$2');
  677.  
  678. return text;
  679. }
  680.  
  681.  
  682. // ***** Format date/time *****
  683. function dateTime(timeStamp, dateOnly, noYesterday) {
  684. // 1s...59s; 1min...59min; hh:mm; gestern hh:mm; Mo...So, hh:mm; tt.mm.jjjj, hh:mm
  685. let timeNow = new Date();
  686. let timeNow1 = new Date (timeNow - 1000*60*60*24);
  687. let timeNow7 = new Date (timeNow - 1000*60*60*24*7);
  688. let hhmm = {hour: "numeric", minute: "numeric"};
  689. // console.log('dateTime');
  690. if (timeStamp) {
  691. if (timeStamp.toDateString() == timeNow.toDateString()) { // today
  692. return timeStamp.toLocaleString('de-DE', hhmm);
  693. } else if (!noYesterday && timeStamp.toDateString() == timeNow1.toDateString()) { // yesterday
  694. return `gestern ${timeStamp.toLocaleString('de-DE', hhmm)}`;
  695. } else if (timeStamp > timeNow7 && timeStamp.getDay() != timeNow.getDay()) { // last 6 days of week
  696. return timeStamp.toLocaleString('de-DE', {weekday: 'short', hour: "numeric", minute: "numeric"});
  697. } else {
  698. return `${timeStamp.toLocaleDateString('de-DE')}${dateOnly ? `` : `, ${timeStamp.toLocaleString('de-DE', hhmm)}`}`;
  699. }
  700. }
  701. }
  702.  
  703.  
  704. // ***** Format weekday/time *****
  705. function weekTime(timeStamp, dateOnly) {
  706. // console.log('weekTime');
  707. return dateTime(timeStamp, dateOnly, true);
  708. }
  709.  
  710.  
  711. // ***** Change URL without reloading the page *****
  712. function changeUrl(url) {
  713. history.pushState({}, document.title, url);
  714. history.pushState({}, document.title, url);
  715. history.back();
  716. }
  717.  
  718.  
  719. // ***** Change object property in sessionStorage *****
  720. function setSessionStorageItem(storageItem, key, value) {
  721. let storageValue = JSON.parse(sessionStorage.getItem(storageItem));
  722. (storageValue) ? storageValue[key] = value : storageValue = JSON.parse(`{"${key}":"${value}"}`);
  723. sessionStorage.setItem(storageItem, JSON.stringify(storageValue));
  724. }
  725.  
  726.  
  727. // ***** Change object property in localStorage *****
  728. function setLocalStorageItem(storageItem, key, value) {
  729. let storageValue = JSON.parse(localStorage.getItem(storageItem));
  730. (storageValue) ? storageValue[key] = value : storageValue = JSON.parse(`{"${key}":"${value}"}`);
  731. localStorage.setItem(storageItem, JSON.stringify(storageValue));
  732. }
  733.  
  734.  
  735. // ***** Add title and aria-label *****
  736. jQuery.fn.titleLabel = function(text) {
  737. return $(this).attr({'title': text, 'aria-label': text});
  738. };
  739.  
  740.  
  741. // ***** Open message thread or contact info by profile name *****
  742. // (not working for blocked, blocking, or paused profiles)
  743. function openByName(baseUrl, profileLink, profileName) {
  744. let profileType = (profileLink.match(/^\/hunq\//)) ? 'hunqz/profiles' : 'profiles';
  745. $.ajax({url: `/api/v4/${profileType}?pick=items.*.(id,name)&lang=de&length=1&filter[fulltext_search_mode]=EXACT&filter[fulltext]=@${profileName}`,
  746. // $.ajax({url: `/api/v4/${profileType}?lang=de&length=1&filter[fulltext_search_mode]=EXACT&filter[fulltext]=@${profileName}`,
  747. headers: ajaxHead()
  748. })
  749. .done(function (data) {
  750. if (data.items[0]?.name == profileName) {
  751. directMsg = data.items[0].id;
  752. changeUrl(baseUrl + data.items[0].id);
  753. /* } else {
  754. $.ajax({headers: ajaxHead(), url: `/api/v4/${profileType}/${profileName}/full?lang=de&lookup_type=NAME`})
  755. .done(function (data) {
  756. if (data.id) {
  757. changeUrl(baseUrl + data.id);
  758. }
  759. }); */
  760. }
  761. });
  762. }
  763.  
  764.  
  765. // ***** Hide visit *****
  766. function hideVisit(profileId) {
  767. if (profileId > 0) {
  768. $.ajax({url: `/api/v4/visits/${profileId}`,
  769. headers: ajaxHead(),
  770. method: 'DELETE'
  771. })
  772. .done(function () {
  773. $('ul.profile-nav__list button[data-cta="hide_visit"]').attr('style', 'visibility:hidden');
  774. $('.profile .top-info-header button').attr('style', 'color:rgb(168,168,168)');
  775. })
  776. }
  777. }
  778.  
  779.  
  780. // ***** Search profile id, open profile name directly *****
  781. function handleSearch(jNode) {
  782. $('#search a.re-add').off('click').remove();
  783. $(jNode).attr('autocomplete', 'off').attr('autocorrect', 'off').attr('autocapitalize', 'off').attr('spellcheck', 'false');
  784.  
  785. //workaround for iOS
  786. $(jNode).trigger('blur');
  787. $(jNode).after(`<input class="re-input-tmp" style="width:0; height:0; margin:0; padding:0; border:0; opacity:0" />`).trigger('focus');
  788. setTimeout(() => {
  789. $(jNode).trigger('focus');
  790. $('.re-input-tmp').remove();
  791. }, 0);
  792.  
  793. $(jNode).on('input click', () => {
  794. let inputValue = $(jNode).val().trim();
  795. $('#search a.re-add').off('click').remove();
  796. if (inputValue.match(/^[1-9]\d{2,}$/)) {
  797. $('#search div[class^="InputBase__EndIcon-"]').after(
  798. `<a style="font-size:1rem; white-space:nowrap" class="re-add re-link-no-hover mh--"><span class="re-desk">Profil-ID ${inputValue} anzeigen</span><span class="re-mobi">ID anzeigen</span></a>`
  799. ).next('.re-add').on('click', () => {
  800. openById(inputValue);
  801. return false;
  802. });
  803. } else if (inputValue.match(/^@?[-\w\/\?&=]{3,}$/)) {
  804. inputValue = inputValue.replace(/^@/, '');
  805. /* $('#search div[class^="InputBase__EndIcon-"]').after(
  806. `<a href="${((inputValue.match(/^\//)) ? '' : '/profile/') + inputValue}" style="font-size:1rem; white-space:nowrap" class="re-add re-link-no-hover mh--"><span>${inputValue}</span><span class="re-desk"> anzeigen</span></a>`
  807. ); */
  808. $('#search div[class^="InputBase__EndIcon-"]').after(
  809. `<a style="font-size:1rem; white-space:nowrap" class="re-add re-link-no-hover mh--"><span>${inputValue}</span><span class="re-desk"> anzeigen</span></a>`
  810. ).next('.re-add').on('click', () => {
  811. changeUrl(((inputValue.match(/^\//)) ? '' : '/profile/') + inputValue);
  812. return false;
  813. });
  814. }
  815. });
  816. }
  817.  
  818.  
  819. // ***** Open profile by ID *****
  820. function openById(id) {
  821. $.ajax({headers: ajaxHead(), url: `/api/v4/profiles/${id}?expand=partner&lang=de`})
  822.  
  823. .done(function (data) {
  824. let type = (data.type) ? data.type : '';
  825. let name = (data.name) ? data.name : '';
  826. let profileType = 'profile';
  827. if (type == 'ESCORT') {
  828. profileType = 'hunq';
  829. } else if (type == 'CLUB') {
  830. profileType = 'group';
  831. }
  832. changeUrl(`/${profileType}/${name}`);
  833. })
  834.  
  835. .fail(function (data) {
  836. changeUrl('/profile/-');
  837. });
  838. }
  839.  
  840.  
  841. // ***** Picture upload month/year *****
  842.  
  843. //init
  844. const imgTable = [
  845. 0x00000000,0x00000000,0x000076f1,0x0000c0c2,0x000113ac,0x000193c7,0x00022adb,0x0002e59b,0x0003c274,0x0004bdda,0x0005cb8f,0x0006f792, //2003
  846. 0x00083008,0x0009e166,0x000bc399,0x000dc70d,0x000fcad1,0x001219f5,0x0014e794,0x00180a61,0x001b7a83,0x001eeaaa,0x0022a968,0x0026aaf2, //2004
  847. 0x002b6a0f,0x00313036,0x00367804,0x003c4ae9,0x00421a3c,0x0048a2b6,0x004f0ba9,0x00563a9c,0x005d4ff9,0x00642120,0x006bc702,0x0072df38, //2005
  848. 0x007a2553,0x008262a7,0x0089c5c3,0x0091feff,0x009ae5e4,0x00a3f682,0x00ad0f09,0x00b727f9,0x00c1e7a3,0x00cb8a2a,0x00d5b9fe,0x00df62f9, //2006
  849. 0x00e9061d,0x00f428f1,0x00fe1c56,0x0108f832,0x01141330,0x011ffb48,0x012b9597,0x0137a3d7,0x01436c46,0x014eba63,0x015ae49b,0x01665b6d, //2007
  850. 0x01721df4,0x017ecdd9,0x018a872f,0x0197cf93,0x01a3fca7,0x01b1bb65,0x01bf4e25,0x01ce3307,0x01dd339e,0x01eb4eb3,0x01fa24cc,0x02086e83, //2008
  851. 0x0216e01e,0x0226de08,0x0234eda5,0x02552d86,0x02677f82,0x027aa2a8,0x028d8ba1,0x02a17765,0x02b605b4,0x02c9adb5,0x02dd8ad9,0x02f0dfe7, //2009
  852. 0x0304f671,0x031b4142,0x032e6143,0x0342c960,0x0356cd19,0x036cb4d8,0x0381a103,0x03986cc1,0x03b0829f,0x03c679e3,0x03dd820b,0x03f4381e, //2010
  853. 0x040af127,0x0423f461,0x0439c102,0x045248d4,0x046b187c,0x048570e5,0x04a09a51,0x04bcc8a2,0x04d9b4fa,0x04f52906,0x051201a6,0x052df05d, //2011
  854. 0x054a17b7,0x0568a266,0x0584c8a7,0x05a2542c,0x05c0b41b,0x05df82b9,0x05fe3605,0x061e5f48,0x063f4240,0x065e2c95,0x067e22b7,0x069dbc58, //2012
  855. 0x06c04502,0x06e62970,0x0707d372,0x072dff1a,0x0751ca91,0x077860fb,0x079c0f3e,0x07c1e771,0x07e86366,0x080da8ce,0x0831ee79,0x08558fc5, //2013
  856. 0x087b94b3,0x08a21c54,0x08c4f55c,0x08eac6fe,0x09119caa,0x09397aed,0x0960b60a,0x098a9925,0x09b76f87,0x09e01456,0x0a0906c0,0x0a305fc4, //2014
  857. 0x0a810424,0x0adaf553,0x0b2947ce,0x0b7d01e6,0x0bcf1e76,0x0c24958c,0x0c755b5b,0x0ccd15d3,0x0d23c304,0x0d7376dc,0x0dc56afa,0x0e140d38, //2015
  858. 0x0e6631a2,0x0ebdf2c6,0x0f0ce2ec,0x0f607bf5,0x0fb37151,0x100ae86a,0x105e79ed,0x10b8bbbf,0x1115caa8,0x116ae5ac,0x11beb400,0x1211f832, //2016
  859. 0x1267b822,0x12c42272,0x1315847b,0x136e4be0,0x13c5fdbd,0x141f2328,0x147b20c9,0x14dde3b3,0x1541cb7a,0x159e4a36,0x1601fff2,0x16657140, //2017
  860. 0x16cb8a49,0x1734ece1,0x1792be94,0x17fa7afa,0x185ec78c,0x18c964a2,0x193230d2,0x19a225bd,0x1a12bd1e,0x1a79217a,0x1ae1e760,0x1b4790fd, //2018
  861. 0x1bb4f607,0x1c23ce64,0x1c84b186,0x1cf2f646,0x1d61d7f8,0x1ddb56d8,0x1e59a281,0x1edf03d3,0x1f66d25f,0x1fe26f82,0x205ec456,0x20d2bded, //2019
  862. 0x21484431,0x21c24ba6,0x2230c76f,0x22a6c5d6,0x23131b17,0x23844c83,0x23f45e75,0x246d38c1,0x24eb9338,0x255dc81a,0x25d30c39,0x2643606d, //2020
  863. 0x26b9cb56,0x2730f2c2,0x279824a7,0x28072e59,0x2870b46c,0x28e20cee,0x294ea8da,0x29bdf4f9,0x2a2f2276,0x2a9872c3,0x2b026a37,0x2b6628b7, //2021
  864. 0x2bcd3a33,0x2c3819a3,0x2c95d422,0x2cf66474,0x2d57afa7,0x2dc0df59,0x2e28345d,0x2e94df63,0x2f014662,0x2f63074e,0x2fc3e3be,0x301fada4, //2022
  865. 0x307f54f1,0x30e474f9,0x313bdc54,0x3199d68a,0x31f91ccb,0x32595812,0x32b90f74,0x3320eb2e,0x3386b446,0x33e585da,0x34462941,0x34a0e6fc, //2023
  866. 0x34ffedcc,0x35602515,0x35b6684b,0x36101e12,0x3669ef96,0x36ca6fdd,0x37297144,0x378e7a59,0x37f5221b,0x3851c0a0,0x38ae410d,0x390700f4, //2024
  867. 0x3960ad24,0x39b9d4b0,0x3a02517e,0x3a596510,0x3ab187ce,0x3b0d6b4e,0x3b68c007,0x3bc5407f,0x3c21c0f7,0x3c7b458c,0x3cd7e5d8,0x3d316a6d, //2025
  868. 0x3d8deae5,0x3dea6b5d,0x3e3df82c,0x3e9a58d0,0x3ef3dd65,0x3f505ddd,0x3fa9e272,0x400662ea,0x4062e362,0x40bc67f7,0x41190843,0x41728cd8, //2026
  869. 0xffffffff];
  870.  
  871. function uploadDate(imgName) {
  872. const monthYearIndex = (element) => element >= parseInt(imgName, 16);
  873. let index = imgTable.findIndex(monthYearIndex) - 1;
  874. let year = 2003 + Math.trunc(index / 12);
  875. let month = 1 + index % 12;
  876. if (imgTable[index] == 0x00000000) {
  877. return '';
  878. } else {
  879. return `≈ ${month}.${year}${imgTable[index + 1] == 0xffffffff ? '+' : ''}`;
  880. }
  881. }
  882.  
  883.  
  884. // ***** Copy message thread to clipboard *****
  885. function copyMessageThread(profileId, profileName) {
  886. let loadTime = new Date().toLocaleString('de-DE').slice(0,-3);
  887. let ownName = 'unknown';
  888. let msgThread = '- keine Nachrichten -';
  889. let msgCount = '', header = '', msgTime = '', name = '';
  890. $.ajax({headers: ajaxHead(), url: `/api/v4/session?lang=de`}).done(function (data) {
  891. if (data.username) {
  892. ownName = data.username;
  893. }
  894. let jsonParam = `lang=de&length=10000&filter[folders][]=SENT&filter[folders][]=RECEIVED&filter[partner_id]=${profileId}`;
  895. $.ajax({headers: ajaxHead(), url: `/api/v4/messages?${jsonParam}`})
  896.  
  897. .done(function (data) {
  898.  
  899. //gather data
  900. header = 'Nachrichtenverlauf von ' + ownName + ' mit ' + profileName + ' (Profil-ID ' + profileId + ') vom ' + loadTime + '\n\n\n';
  901. if (data.items_total) {
  902. msgThread = '';
  903. msgCount = data.items_total;
  904. for (let i = msgCount-1; i >= 0; i--) {
  905. msgTime = new Date(data.items[i].date.slice(0,-5)+'.000Z');
  906. name = (data.items[i].folder == 'SENT') ? ownName : profileName;
  907. msgThread += msgCount-i + '. ' + name + ' • ' + msgTime.toLocaleString('de-DE').slice(0,-3);
  908. msgThread += (data.items[i].unread == true) ? ' [ungelesen]' : '';
  909. msgThread += (data.items[i].locked == true) ? ' [gespeichert]' : '';
  910. msgThread += (data.items[i].spam == true) ? ' [Spam]' : '';
  911. msgThread += '\n-------------------------------------------------------------------';
  912. msgThread += '\n' + data.items[i].text.trim() + '\n\n\n';
  913.  
  914. //handle links
  915. if (data.items[i].attachments) {
  916. for (let item of data.items[i].attachments) {
  917. if (item.type == 'COMMAND') {
  918. msgThread += `[${item.index + 1}] ${item.params?.url?.match(/^\//) ? 'https://www.romeo.com' : ''}${item.params?.url}\n`;
  919. msgThread = msgThread.replace(`[[${item.index}]]`, `${item.params?.text} [${item.index + 1}]`);
  920. }
  921. }
  922. msgThread += '\n\n'
  923. }
  924. }
  925. msgThread += '\n====================\n\n';
  926. }
  927.  
  928. //confirm copy
  929. $('div.js-correspondence').after(
  930. `<div class="re-bubble layout layout--h-center">
  931. <div class="ui-bubble ui-bubble--dark" style="width:auto; top:72px">
  932. <div class="ui-bubble__content [ js-content ] [ js-scrollable ] scrollable">
  933. <div class="confirm-box">
  934. <div class="txt-right" style="margin:-.5rem -.5rem -.75rem">
  935. <a class="re-copy-cancel re-link icon icon-larger icon-cross"></a>
  936. </div>
  937. <div class="confirm-box__label" style="margin-bottom:1.125rem">
  938. <p>Nachrichtenverlauf kopieren</p>
  939. </div>
  940. <div class="confirm-box__actions">
  941. <button class="re-copy-email ui-button ui-button--transparent">E-Mail-Entwurf</button>
  942. <button class="re-copy-confirm ui-button ui-button--primary">Zwischenablage</button>
  943. </div>
  944. </div>
  945. </div>
  946. </div>
  947. </div>
  948. <div class="layer ui-bubble__overlay l-fancy"></div>`
  949. );
  950. $('button.re-copy-confirm').focus().on('click', function() {
  951. navigator.clipboard.writeText(header + msgThread);
  952. $('div.re-bubble, div.ui-bubble__overlay').hide();
  953. });
  954. $('button.re-copy-email').on('click', function() {
  955. location.href = `mailto:?subject=${encodeURIComponent(header)}&body=${encodeURIComponent(msgThread)}`;
  956. $('div.re-bubble, div.ui-bubble__overlay').hide();
  957. });
  958. $('a.re-copy-cancel, div.ui-bubble__overlay').on('click', function() {
  959. $('div.re-bubble, div.ui-bubble__overlay').hide();
  960. });
  961. });
  962. });
  963. }
  964.  
  965.  
  966. // ***** Fix scrolling on larger screens for messages list *****
  967. function fixScroll (jNode) {
  968. $(jNode).parents('.js-chat .js-scrollable').attr('style', 'max-height:inherit !important');
  969. }
  970.  
  971.  
  972. // ***** Add double tap to menu items, change behavior for mobile *****
  973. function handleMainMenu (jNode) {
  974. if (!mobile.matches) {
  975. // $(jNode).has('path[d^="M13.493"]').off().has('p[class^="SpecialText"]').on('click', function() { changeUrl('/messenger/chat'); return false; });
  976. // $(jNode).filter('a[href^="/messenger"]').attr('style', 'pointer-events:auto').filter('a[isactive="true"]').off().on('click', function() { $('#messenger').addClass('is-hidden'); return false; });
  977. // $(jNode).has('path[d^="M6.99 10"]').off().on('click', function() { changeUrl('/groups/discover'); return false; });
  978. $(jNode).has('path[d^="M6.99 10"]').find('button').off('click').on('click', function() {
  979. if (lastGroupName && sessionStorage.getItem('PR_SETTINGS:cachedNavTab')?.match('"groups":"member"')) {
  980. changeUrl(`/groups/member/${lastGroupName}`);
  981. return false;
  982. }
  983. });
  984. } else {
  985. $(jNode).has('path[d^="M20.3943"], path[d^="m20.507"]').off('dblclick').on('dblclick', function() { changeUrl('/stream'); return false; });
  986. $(jNode).has('path[d^="M23.102"]').off('dblclick').on('dblclick', function() {
  987. changeUrl('/visitors/me');
  988. setSessionStorageItem('PR_SETTINGS:cachedNavTab', 'visitors', 'me');
  989. return false;
  990. });
  991. $(jNode).has('path[d^="M13.493"]').off('dblclick').on('dblclick', function() {
  992. changeUrl('/messenger/contacts');
  993. setSessionStorageItem('PR_SETTINGS:cachedNavTab', 'messenger', 'contacts');
  994. return false;
  995. });
  996. $(jNode).has('path[d^="M6.99 10"]').off('click').on('click', function() { changeUrl('/groups/member'); return false; });
  997. }
  998. }
  999.  
  1000.  
  1001. // ***** Add travel etc. to tab navigation *****
  1002.  
  1003. //init
  1004. let radarTabMenuLoaded = false;
  1005.  
  1006. function handleTabMenu (jNode) {
  1007.  
  1008. //workaround for tab navigation on NEW page
  1009. if ($('.js-navigation nav').has('a[href^="/radar/"]').length) {
  1010. radarTabMenuLoaded = true;
  1011. }
  1012.  
  1013. //radar
  1014. $('.js-navigation nav').has('a[href^="/radar/"]').not(':has(a.re-add)').prepend(
  1015. '<a class="vBddB re-add" title="Reiseziel"><span class="re-add re-radar-travel icon icon-airplane" title="Reiseziel"><span></span></span></a>'
  1016. );
  1017.  
  1018. //eyecandy
  1019. $('section.js-main-stage').has('#eyecandy-results').not(':has(a.re-add)').find('header nav').prepend(
  1020. '<a class="vBddB re-add" title="Reiseziel"><span class="re-add re-radar-travel icon icon-airplane" title="Reiseziel"><span></span></span></a>'
  1021. );
  1022.  
  1023. //add NEW to discover page menu
  1024. if (testMode) console.log('DISCOVER page');
  1025. $('.js-navigation nav').has('a[href^="/radar/"]').not(':has(a[href="/radar/new"])').append(
  1026. '<a class="vBddB re-add" href="/radar/new"><span class="fdnxUW">Neu</span></a>'
  1027. ).children().last().off('click').on('click', function() {
  1028. setSessionStorageItem('PR_SETTINGS:cachedNavTab', 'radar', mobile.matches ? 'home' : 'new');
  1029. return true;
  1030. });
  1031.  
  1032. //travel
  1033. if ($('main nav.js-navigation ul, section.js-main-stage').has('a[href^="/radar/"], a[href^="/eyecandy/"], #eyecandy-results').length) {
  1034. let viewMode = localStorage.getItem('REradarTravelLocation');
  1035. let toggleTravel = 'span.re-radar-travel';
  1036. let refreshClick = location.pathname.match(/\/eyecandy\//) ? 'header + div > button' : '';
  1037. handleTravelLocation(viewMode, toggleTravel, refreshClick);
  1038. $('span.re-radar-travel').parent().off().on('click', function() {
  1039. let viewMode = localStorage.getItem('REradarTravelLocation');
  1040. let toggleTravel = 'span.re-radar-travel';
  1041. let refreshClick = '.js-navigation a[aria-current="page"], header + div > button';
  1042. viewMode = (viewMode == 'travel') ? 'default' : 'travel';
  1043. localStorage.setItem('REradarTravelLocation', viewMode);
  1044. handleTravelLocation(viewMode, toggleTravel, refreshClick);
  1045. });
  1046. }
  1047.  
  1048. //groups
  1049. $('.js-navigation nav > a[href="/groups/member"]').titleLabel(`${commonGroupsList.length.toLocaleString('de-DE')} Gruppen abonniert`);
  1050. }
  1051.  
  1052.  
  1053. // ***** Add tab navigation to NEW page *****
  1054. function handleTabMenuNew (jNode) {
  1055. if (location.pathname.match(/^\/radar\/new/) && ! mobile.matches && radarTabMenuLoaded) {
  1056. const jNodeNew = $(jNode).parent().parent();
  1057. $(jNode).parent().remove();
  1058. $(jNodeNew).append(
  1059. '<nav class="gHPgrZ">'+
  1060. '<a class="vBddB re-add" title="Reiseziel"><span class="re-add re-radar-travel icon icon-airplane" title="Reiseziel"><span></span></span></a>' +
  1061. '<a class="vBddB re-add" href="/radar/home"><span class="fdnxUW">Entdecken</span></a>' +
  1062. '<a class="vBddB re-add" href="/radar/distance"><span class="fdnxUW">Entfernung</span></a>' +
  1063. '<a class="vBddB re-add" href="/radar/login"><span class="fdnxUW">Aktivität</span></a>' +
  1064. '<a aria-current="page" class="vBddB re-add" href="/radar/new"><span class="fdnxUW">Neu</span></a>' +
  1065. '</nav>'
  1066. ).children('nav').off('click').on('click', 'a[href^="/radar"]', function() {
  1067. let cachedTab = $(this).attr('href').replace(/(^\/radar\/)(\w+)/, '$2');
  1068. setSessionStorageItem('PR_SETTINGS:cachedNavTab', 'radar', cachedTab);
  1069. if (location.pathname.match(/^\/radar\/new/)) $('.layer-left-navigation li button:has(path[d^="m20.507"])')[0].click();
  1070. return true;
  1071. });
  1072. }
  1073. }
  1074.  
  1075.  
  1076. // ***** Add contacts and my groups to top right menu *****
  1077. function handleTopRightMenu (jNode) {
  1078. $(jNode).addClass('re-done');
  1079. if (testMode) console.log('handleTopRightMenu');
  1080.  
  1081. //contacts
  1082. $(jNode).closest('li').clone().insertBefore($(jNode).closest('li')).addClass('re-contacts');
  1083. $('.re-contacts').find('a').attr('href', '/messenger/contacts').off('click').on('click', function() {
  1084. setSessionStorageItem('PR_SETTINGS:cachedNavTab', 'messenger', 'contacts');
  1085. return true;
  1086. });
  1087. $('.re-contacts').find('p').text('Kontakte');
  1088. $('.re-contacts').find('svg').replaceWith('<div class="icon icon-save-contact"></div>');
  1089.  
  1090. //my groups
  1091. if(location.hostname != 'www.hunqz.com') {
  1092. $(jNode).closest('li').clone().insertBefore($(jNode).closest('li')).addClass('re-my-groups');
  1093. $('.re-my-groups').find('a').attr('href', '/groups/member').off('click').on('click', function() {
  1094. (async() => {
  1095. while (!lastGroupName) await new Promise(resolve => setTimeout(resolve, 300));
  1096. changeUrl(`${(lastGroupName) ? `/groups/member/${lastGroupName}` : `/groups/member`}`);
  1097. setSessionStorageItem('PR_SETTINGS:cachedNavTab', 'groups', 'member');
  1098. })();
  1099. return false;
  1100. });
  1101. $('.re-my-groups').find('p').text('Gruppen');
  1102. $('.re-my-groups').find('svg').replaceWith('<div class="icon icon-group-members"></div>');
  1103. }
  1104. }
  1105.  
  1106.  
  1107. // ***** Show versions and users online in main menu *****
  1108. function handleVersion (jNode) {
  1109. $(jNode).addClass('re-done');
  1110. let reVersion = `${(typeof GM_info !== 'undefined') ? `${GM_info.script.name} ${GM_info.script.version}` : 'RomeoEnhancer'}`;
  1111. let onlineAll = '';
  1112. $.ajax({url: `/api/services/landing/online-count`})
  1113. .done((data) => {
  1114. onlineAll = `${data.online_count?.toLocaleString('de-DE')} Profile online`;
  1115. $(jNode).find('.re-add').remove();
  1116. $(jNode).append(`<div class="txt-preserve re-add">${reVersion}\n${onlineAll}</div>`);
  1117. let profileType = $('.js-navigation nav > a[href^="/hunqz"]').length ? '/hunqz/profiles' : '/profiles';
  1118. let users = '', online = '';
  1119. $.ajax({headers: ajaxHead(), url: `/api/v4${profileType}?pick=items,items_total&lang=de&length=1`}).done((data) => {
  1120. users = `${data.items_total?.toLocaleString('de-DE')} User`;
  1121. $.ajax({headers: ajaxHead(), url: `/api/v4${profileType}?pick=items,items_total&lang=de&length=1&filter[online_status][]=ONLINE&filter[online_status][]=DATE&filter[online_status][]=SEX`}).done(function (data) {
  1122. online = `${data.items_total?.toLocaleString('de-DE')} online`;
  1123. $(jNode).off().on('dblclick', () => {
  1124. $(jNode).find('div').replaceWith(`<div class="txt-preserve re-add">${reVersion}\n${onlineAll}\n${users} ${online}</div>`);
  1125. });
  1126. });
  1127. });
  1128. });
  1129. }
  1130.  
  1131.  
  1132. // ***** Hide softcore in tiles *****
  1133. function handleSettingsDisplay (jNode) {
  1134. $(jNode).addClass('re-done');
  1135. $(jNode).closest('div.settings__key').clone().insertAfter($(jNode).closest('div.settings__key')).addClass('re-hide-nsfw-switch');
  1136. $('.re-hide-nsfw-switch').find('span.re-done').text('Softcore ausblenden');
  1137. $('.re-hide-nsfw-switch').find('a.ui-hint').attr('data-hint', 'Softcore-Inhalte in Kacheln werden durch neutrale Platzhalter ersetzt.');
  1138. $('.re-hide-nsfw-switch').find('.js-plus-always, .js-plus').removeClass('js-plus-always js-plus is-disabled-clickable');
  1139. $('.re-hide-nsfw-switch').find('.ui-toggle__label').attr('for', 'uid-re-100');
  1140. $('.re-hide-nsfw-switch').find('.ui-toggle__input').attr('id', 'uid-re-100').prop('checked', hideNSFW).off().on('change', function() {
  1141. hideNSFW = !hideNSFW;
  1142. localStorage.setItem('REhideNSFW', hideNSFW);
  1143. $('.re-hide-nsfw-switch').find('.ui-toggle__input').prop('checked', hideNSFW);
  1144. setTimeout(() => {location.reload()}, 200);
  1145. });
  1146.  
  1147. //text for headline switch
  1148. $('span[data-cta="showHeadline"]').text('Überschrift in großen Kacheln anzeigen');
  1149. }
  1150.  
  1151.  
  1152. // ***** Switch color schemes *****
  1153. function handleColorSwitch (jNode) {
  1154. $(jNode).addClass('re-done');
  1155.  
  1156. //init
  1157. let colorSchemes = ['dark', 'fineGrey', 'lighterGrey', 'retroBlue'];
  1158. let colorSchemeNames = ['Dark (Romeo)', 'Fine Grey (RomeoEnhancer)', 'Lighter Grey', 'Retro Blue'];
  1159. let selectedIndex = colorSchemes.indexOf(localStorage.getItem('REcolorScheme'));
  1160. if (selectedIndex == -1) selectedIndex = 1;
  1161. let newIndex = selectedIndex;
  1162. $('.js-grid-style-selector').clone().insertBefore($('.js-grid-style-selector').siblings().first()).removeClass('js-grid-style-selector').addClass('re-color-scheme-switch');
  1163. $('.re-color-scheme-switch').find('summary > div > span').text('Design').append(`<strong><a class="re-color-accept ml+"></a></strong> `);
  1164. $('.re-color-scheme-switch').find('summary > div + span').text(colorSchemeNames[selectedIndex]);
  1165. $('.re-color-scheme-switch').find('input').prop('checked', false).removeAttr('checked').each(function(index) {
  1166. $(this).attr('name', 'color-scheme').attr('id', `re-color-${index}`).attr('value', colorSchemes[index]);
  1167. $(this).siblings('label').attr('for', `re-color-${index}`).find('span').text(colorSchemeNames[index]);
  1168. if (index == selectedIndex) {
  1169. $(this).siblings('div').removeClass('hidden');
  1170. } else {
  1171. $(this).siblings('div').addClass('hidden');
  1172. }
  1173. });
  1174.  
  1175. //toggle list
  1176. $('.re-color-scheme-switch').find('summary').off().on('click', () => {
  1177. $('.re-color-scheme-switch').find('summary > div + span').toggle();
  1178. localStorage.setItem('REcolorScheme', colorSchemes[newIndex]);
  1179. if (newIndex != selectedIndex) {
  1180. location.reload();
  1181. }
  1182. });
  1183.  
  1184. //change selection
  1185. $('.re-color-scheme-switch').find('fieldset').off().on('change', 'input', function() {
  1186. $('.re-color-scheme-switch').find('input').siblings('div').addClass('hidden');
  1187. $(this).siblings('div').removeClass('hidden');
  1188. newIndex = colorSchemes.indexOf($(this).val());
  1189. $('.re-color-accept').text(newIndex != selectedIndex ? 'Auswahl übernehmen' : '');
  1190. $('.re-color-scheme-switch').find('summary > div + span').text(colorSchemeNames[newIndex]);
  1191. });
  1192. }
  1193.  
  1194.  
  1195. // ***** Show selected bookmark in filter icon title and text *****
  1196. function handleBookmark (jNode) {
  1197. // console.log('handleBookmark');
  1198.  
  1199. let selectedBookmark = $('div[class^="js-bookmarks-list"] div[class*="item__is-selected"] a').text().trim();
  1200. if (selectedBookmark) {
  1201. $('div.js-filter-button button').attr('style', 'color:rgb(250,250,250); background-color:transparent');
  1202. $('div.js-filter-button button title').text(`Lesezeichen: ${selectedBookmark}`);
  1203. $('div.js-filter-button button span').not('.re-add').addClass('re-filter-options-text');
  1204. $('div.js-filter-button button').not(':has(.re-add)').append(`<span class="re-add re-filter-bookmark-name mr">${selectedBookmark}</span>`);
  1205. $('div.js-filter-button button span').attr('title', `Lesezeichen: ${selectedBookmark}`);
  1206. //$('nav.js-navigation li.js-filter-button div.js-filter-button button').attr('title', `Lesezeichen: ${selectedBookmark}`);
  1207. $('button.ui-navbar__button--bookmarks').attr('title', 'Lesezeichen auswählen');
  1208. } else {
  1209. $('div.js-filter-button button span.re-filter-options-text').removeClass('re-filter-options-text');
  1210. $('div.js-filter-button button span.re-add').remove();
  1211. }
  1212. }
  1213.  
  1214.  
  1215. // ***** Preview unread messages in title tag, delete by clicking blue badge *****
  1216. function previewMessage (jNode) {
  1217. $(jNode).addClass('re-done');
  1218. let profileId = $(jNode).closest('a').attr('href').match(/\d{3,}/);
  1219. if (profileId) {
  1220. $(jNode).closest('a').addClass('re-id' + profileId);
  1221. let msgCount = parseInt($(jNode).text());
  1222. let jsonParam = 'lang=de&length=' + msgCount + '&filter[folders][]=RECEIVED&filter[partner_id]=' + profileId;
  1223. let msgText = '';
  1224. let thisId = '.re-id' + profileId;
  1225. $.ajax({headers: ajaxHead(), url: '/api/v4/messages?' + jsonParam}).done(function (data) {
  1226.  
  1227. //preview
  1228. for (let i = msgCount-1; i >= 0; i--) {
  1229. msgText += '\r' + data.items[i].text + '\r';
  1230.  
  1231. //handle links
  1232. if (data.items[i].attachments) {
  1233. for (let item of data.items[i].attachments) {
  1234. if (item.type == 'COMMAND') {
  1235. // console.log('attachments COMMAND');
  1236. msgText = msgText.replace(`[[${item.index}]]`, `[${item.params?.text}]`);
  1237. }
  1238. }
  1239. }
  1240.  
  1241. }
  1242. $(thisId).attr('title', msgText);
  1243. $(thisId).find('img').parent('div').attr('title', msgText);
  1244.  
  1245. //delete unread
  1246. $(jNode).parent('div').wrap(`<div style="margin:-.5rem; padding:.5rem; ${(touch.matches) ? 'margin:0 0 -.5rem -.5rem' : ''}"></div>`).parent().attr('title', 'Löschen').off().on('click', function () {
  1247. if (confirm(msgCount + ' Nachrichten ungelesen löschen?')) {
  1248. for (let i = msgCount-1; i >= 0; i--) {
  1249. $.ajax({url: '/api/v4/messages/' + data.items[i].id + '?expand=from',
  1250. headers: ajaxHead(),
  1251. method: 'DELETE'
  1252. })
  1253. .done(function (data) {
  1254. $(jNode).closest('a[href^="/messenger/chat"]').attr('title', 'Liste aktualisieren').off().on('click', function() {
  1255. $('#messenger .js-navigation a.is-selected')[0].click();
  1256. return false;
  1257. }).children('div').replaceWith(
  1258. `<div style="font-size:.925rem;color:rgb(255,255,255,.8)">– ungelesen gelöscht –</div>`
  1259. );
  1260. })
  1261. }
  1262. }
  1263. return false;
  1264. })
  1265. });
  1266.  
  1267. //highlight profile name
  1268. $(jNode).closest('a').find('p[class^="BodyText"]').attr('style', 'color:rgb(255,255,255,1); opacity:1');
  1269. }
  1270. }
  1271.  
  1272.  
  1273. // ***** Show last login and location in message thread *****
  1274. function showLoginLocation (jNode) {
  1275.  
  1276. //placeholder for login and location
  1277. $('section.js-detail > div').not(':has(.re-login-location)').append(
  1278. `<div class="re-login-location re-add"></div>`
  1279. );
  1280.  
  1281. if ($('.js-correspondence > div').filter('.re-add').length) return;
  1282.  
  1283. let profileId = location.pathname.match(/\d{3,}$/);
  1284. let name = '', country = '', distance = '', sensor = '';
  1285. let loginTime = '', loginLocalTime = '', timeTag = '', lastSince = '', offlineSince = '';
  1286. $.ajax({headers: ajaxHead(), url: `/api/v4/profiles/${profileId}?expand=partner&lang=de`})
  1287.  
  1288. .done(function (data) {
  1289. $('.js-correspondence > div').addClass('re-add');
  1290.  
  1291. //last login
  1292. if (data.last_login && data.online_status) {
  1293. loginTime = new Date(data.last_login.slice(0,-5)+'.000Z');
  1294. loginLocalTime = dateTime(loginTime);
  1295. timeTag = dateTime(loginTime, !mobile.matches);
  1296. lastSince = `${data.online_status == 'OFFLINE' ? 'Zuletzt online:' : 'Online seit:'}`;
  1297.  
  1298. if (($(jNode).closest('div').parent().find('[class*="OnlineStatus"]')).length == 0) {
  1299. offlineSince = `<span class="icon icon-last-login-hours mr-- re-add" style="font-size:.75rem; color:rgb(250,250,250,.75)" title="${lastSince} ${loginLocalTime}"> ${touch.matches ? '' : timeTag}</span>`;
  1300. };
  1301.  
  1302. }
  1303.  
  1304. //location, distance
  1305. if (data.location?.name) {
  1306. name = `<a class="re-link-idle" target="_blank" href="https://google.com/maps/place/${data.location.name}" rel="noreferrer noopener" title="Ort in Google Maps anzeigen">${data.location.name.trim()}</a>`;
  1307. }
  1308. if (data.location?.country && (data.location?.distance > 50000 || data.location?.distance == null)) {
  1309. country = `, ${data.location.country}`;
  1310. }
  1311. if (data.location?.distance >= 0) {
  1312. if (data.location.distance < 1000) {
  1313. distance = ' • ' + data.location.distance + 'm';
  1314. } else if (data.location.distance < 50000) {
  1315. distance = ' • ' + (Math.round(data.location.distance/100)/10).toLocaleString('de-DE') + ' km';
  1316. } else {
  1317. distance = ' • ' + (Math.round(data.location.distance/1000)) + ' km';
  1318. }
  1319. }
  1320. if (data.location?.sensor) {
  1321. sensor = '<span class="icon icon-gps-needle icon-badge mr-" style="font-size:.85em"></span>';
  1322. }
  1323. })
  1324.  
  1325. .always(function (data) {
  1326. $('section.js-detail div.re-login-location').html(`
  1327. <div class="re-msg-location">${sensor}${name}${country}${distance}</div>
  1328. <div class="re-msg-login" title="${lastSince} ${loginLocalTime}">
  1329. <span class="re-touch">${lastSince} ${loginLocalTime}</span></div>
  1330. <div class="re-msg-offline">${offlineSince}</div>`
  1331. );
  1332. });
  1333. }
  1334.  
  1335.  
  1336. // ***** Add copy to message thread options menu *****
  1337. function threadOptionsMenu (jNode) {
  1338. $(jNode).find('li').first().not('.re-done').addClass('re-done').clone().appendTo($(jNode)).on('click', function() {
  1339. let profileId = '', profileName = '';
  1340. if ($(this).closest('#messenger').length) {
  1341. profileId = location.pathname.match(/\d{3,}/);
  1342. profileName = $('#messenger div.js-correspondence').find('a > p[class^="BodyText"]').first().text().trim();
  1343. } else {
  1344. profileId = xhrFull.id;
  1345. profileName = xhrFull.name;
  1346. }
  1347. copyMessageThread(profileId, profileName);
  1348. return false;
  1349. }).attr('id', 'options_copy').find('span').text('Kopieren');
  1350. }
  1351.  
  1352.  
  1353. // ***** Zoom footprints list *****
  1354.  
  1355. //init
  1356. let setFootprintZoom = (localStorage.getItem('REfootprintZoom') === 'true');
  1357.  
  1358. function handleFootprints (jNode) {
  1359. setFootprintZoom ? $('.js-profile-footprints').addClass('re-zoom') : $('.js-profile-footprints').removeClass('re-zoom');
  1360. $('.js-profile-footprints header span').last().not(':has(.re-add)').wrapInner(
  1361. `<a class="re-link mr re-add" style="font-size:1.2rem" title="Vergrößern" href="">- +</a>`
  1362. ).on('click', function() {
  1363. setFootprintZoom = !setFootprintZoom;
  1364. localStorage.setItem('REfootprintZoom', setFootprintZoom);
  1365. $('.js-profile-footprints').toggleClass('re-zoom');
  1366. return false;
  1367. });
  1368. }
  1369.  
  1370.  
  1371. // ***** Filter by "no entry" *****
  1372. function handleFilterNoEntry (jNode) {
  1373.  
  1374. //insert button
  1375. $(jNode).not('.re-add').addClass('re-add').after(`
  1376. <div class="re-add layout mt mh++">
  1377. <a class="re-button-no-entry ui-tag ui-tag--center" href="">+ keine Angabe
  1378. <span class="icon icon-small icon-info ml-" title="Zeigt auch Profile, die zu den ausgewählten Filterkategorien keine Angabe enthalten"
  1379. aria-label="Zeigt auch Profile, die zu den ausgewählten Filterkategorien keine Angabe enthalten"></span>
  1380. </a>
  1381. </div>`
  1382. ).next('div.re-add').off().on('click', function() {
  1383. filterNoEntry = !filterNoEntry;
  1384. localStorage.setItem('REfilterNoEntry', filterNoEntry);
  1385.  
  1386. //toggle button
  1387. $('.re-button-no-entry').toggleClass('ui-tag--selected');
  1388.  
  1389. //reload radar tab
  1390. $('section.js-main-stage div.js-navigation').find('a.is-selected, div.js-nav-item').get(0).click();
  1391. return false;
  1392. });
  1393. if (filterNoEntry) $('.re-button-no-entry').addClass('ui-tag--selected');
  1394. }
  1395.  
  1396.  
  1397. // ***** Link contact icons in messages list entries *****
  1398. function handleMessage (jNode) {
  1399. // console.log('handleMessage');
  1400. let profileId = new Array;
  1401. profileId = $(jNode).attr('href').match(/\d{3,}/);
  1402. if (! profileId) profileId = location.pathname.match(/\d{3,}/);
  1403. $(jNode).not(':has(a.re-add)').find('svg').has('path[d^="M4 8a4"]').wrap(
  1404. `<a href="/messenger/contacts/all/${profileId}" class="re-add"></a>`
  1405. );
  1406. $(jNode).not(':has(a.re-add)').find('svg').has('path[d^="M8.039"]').wrap(
  1407. `<a href="/messenger/contacts/blocked/${profileId}" class="re-add"></a>`
  1408. );
  1409. $(jNode).find('span > div > p[class^="SpecialText"]:contains("Gelesen")').text('Gelesen ✓');
  1410.  
  1411. //only for last item in list
  1412. if (!$(jNode).closest('.reactView').next('.reactView').length) {
  1413. //...
  1414. // console.log('message list complete');
  1415. };
  1416. }
  1417.  
  1418.  
  1419. // ***** Open links in messages without opening flyout menu *****
  1420. function handleMessageLink (jNode) {
  1421. $(jNode).off().on('click', function() {
  1422. (this.target) ? window.open(this.href, this.target, this.rel) : changeUrl(this.href);
  1423. return false;
  1424. });
  1425. }
  1426.  
  1427.  
  1428. // ***** Scroll to last message in thread *****
  1429. function handleMessageScroll (jNode) {
  1430. // console.log('scrollIntoView');
  1431. $(jNode).addClass('re-done').closest('.message__content').addClass('re-done');
  1432. $('#messages-list .message__content').last().not('.re-done')[0]?.scrollIntoView({block: 'start'});
  1433. // $(jNode).addClass('re-done');
  1434. // $('#messages-list .message__content').last()[0]?.scrollIntoView({block: 'start'});
  1435. }
  1436.  
  1437.  
  1438. // ***** Add message icons to contact list entries *****
  1439. function handleContacts (jNode) {
  1440. let profileId = new Array;
  1441. profileId = $(jNode).attr('href').match(/\d{3,}$/);
  1442. if (! profileId) profileId = location.pathname.match(/\d{3,}$/);
  1443. $(jNode).find('p[class^="BodyText-"] > span, span > span').last().not(':has(a)').prepend(
  1444. `<a class="icon icon-chat re-icon re-idle ml-- mr-" title="Messages" href="/messenger/chat/${profileId}"></a>`
  1445. );
  1446. }
  1447.  
  1448.  
  1449. // ***** Prompt for removing contacts tags *****
  1450. function handleTagRemove (jNode) {
  1451. $(jNode).titleLabel('Löschen').off().on('click', function() {
  1452. return confirm(`»${$(this).prev().text().trim()}« löschen? \rAlle Zuweisungen zu Kontakten gehen verloren.`);
  1453. });
  1454. }
  1455.  
  1456.  
  1457. // ***** Click to refresh tab *****
  1458. function handleTabRefresh (jNode) {
  1459. if (testMode) console.log('handleTabRefresh');
  1460. $(jNode).off().on('click', function() {
  1461. if (testMode) console.log('doTabRefresh');
  1462. // $('#messenger .js-scrollable').not('.is-hidden')[0].scrollTo({top:-1000, behavior: "smooth"});
  1463. const e = jQuery.Event( "DOMMouseScroll",{delta: -650} );
  1464. $('#messenger .pulltorefresh').trigger(e);
  1465. $('#messenger .pulltorefresh').show()[0].style.height = '300px';
  1466. });
  1467. }
  1468.  
  1469.  
  1470. // ***** Link user names on discover page to messages *****
  1471. function handleContactStrip (jNode) {
  1472. let replacePattern = /(.*\/(profile|hunq|group)\/)([-\w]*)(.*)/;
  1473. let profileLink = $(jNode).attr('href');
  1474. $(jNode).attr('href', profileLink.replace(/\/group\//, '/profile/'));
  1475. let profileName = profileLink.replace(replacePattern, '$3');
  1476. let baseUrl = '/messenger/chat/';
  1477. $(jNode).find('div[class^="Name-"]').attr('title', profileName)/*.wrapInner(
  1478. `<a class="re-link-idle" title="${profileName}  |  Messages" href="${baseUrl}${profileName}"></a>`
  1479. ).children()*/.off().on('click', function() {
  1480. openByName(baseUrl, profileLink, profileName);
  1481. return false;
  1482. }).attr('style', `${profileLink.match(/^\/hunq\//) ? 'color:#fc193c' : ''}`);
  1483. }
  1484.  
  1485.  
  1486. // ***** Mark common groups *****
  1487. function handleGroupTiles (jNode) {
  1488. $(jNode).addClass('re-done');
  1489. (async() => {
  1490. while (! commonGroupsLoaded) await new Promise(resolve => setTimeout(resolve, 300));
  1491. // console.log('handleGroupTiles');
  1492. let jNodeGroupTiles = $(jNode).closest('a');
  1493. if (jNodeGroupTiles.attr('href')?.match(/^\/group\//)) {
  1494. const profileName = jNodeGroupTiles.attr('href').match(/[-\w]+$/);
  1495. const nameIndex = (item) => item.name == profileName;
  1496. if (commonGroupsList.findIndex(nameIndex) > -1) {
  1497. jNodeGroupTiles.addClass('re-common-group');
  1498. jNodeGroupTiles.find('p').last().attr('title', 'Du bist Mitglied in dieser Gruppe');
  1499. }
  1500.  
  1501. //show full group name in title
  1502. jNodeGroupTiles.find('p').first().attr('title', $(jNode).find('p').first().text().trim());
  1503. }
  1504.  
  1505. //blur nsfw placeholder
  1506. /* if (jNodeGroupTiles.has(`div[style*="${nsfwPlaceholder}"]`).length) {
  1507. let tilePicture = jNodeGroupTiles.children('div').attr('style');
  1508. jNodeGroupTiles.addClass('re-nsfw-placeholder').attr('style', `display:block; ${tilePicture} background-size:cover`).attr('title', 'Softcore-Bild');
  1509. } */
  1510. })();
  1511. }
  1512.  
  1513.  
  1514. // ***** Sort EyeCandy on startpage by Nearby or Activity *****
  1515. function handleDiscoverEyecandy (jNode) {
  1516. $(jNode).not('.re-done').addClass('re-done').append(`
  1517. <a class="re-link re-eyecandy-toggle ml" title="Umschalten" href="">
  1518. <!--<span>${$('a[href^="/radar/distance"] span').first().text().trim()}</span>
  1519. <span>${$('a[href^="/radar/login"] span').first().text().trim()}</span>-->
  1520. <span>Entfernung</span>
  1521. <span>Aktivität</span>
  1522. <svg aria-hidden="true" viewBox="0 0 10 6" height="6" width="10">
  1523. <path fill-rule="evenodd" d="M5.707 5.707a1 1 0 0 1-1.414 0l-4-4A1 1 0 0 1 1.707.293L5 3.586 8.293.293a1 1 0 0 1 1.414 1.414l-4 4Z" clip-rule="evenodd"></path>
  1524. </svg>
  1525. </a>`
  1526. ).children().off().on('click', function() {
  1527. eyecandyActive = !eyecandyActive;
  1528. localStorage.setItem('REeyecandyActive', eyecandyActive);
  1529. $('.re-eyecandy-toggle').toggleClass('is-selected');
  1530. $('.js-navigation a[aria-current="page"]')[0].click();
  1531. return false;
  1532. });
  1533. if (eyecandyActive) $('.re-eyecandy-toggle').addClass('is-selected');
  1534. }
  1535.  
  1536.  
  1537. // ***** Localize link to blogs *****
  1538. function handleBlogLinks (jNode) {
  1539. $(jNode).off().on('click', function(jNode) {
  1540. let lang = $('html').attr('lang');
  1541. let defaultUrl = $(this).attr('href');
  1542. let localeUrl = defaultUrl.replace(/(\/)(\w\w)(\/blog\/.*)/, `$1${lang}$3`);
  1543. if (localeUrl.match(/\/blog\//) && ! localeUrl.endsWith('/')) localeUrl += '/';
  1544. let url = '';
  1545. $.ajax({ headers: {Range: 'bytes=0-0'}, url: localeUrl })
  1546. .done(() => {
  1547. url = localeUrl;
  1548. })
  1549. .fail(() => {
  1550. url = defaultUrl;
  1551. });
  1552. (async() => {
  1553. while (! url) await new Promise(resolve => setTimeout(resolve, 300));
  1554. window.open(url, '_blank');
  1555. })();
  1556. return false;
  1557. });
  1558. }
  1559.  
  1560.  
  1561. // ***** Add edit icon to travel location item *****
  1562. function handleTravelLocationList (jNode) {
  1563. $(jNode).addClass('re-done');
  1564. $(jNode).closest('svg').after(
  1565. `<a href="/explore/edit" class="re-edit-travel icon icon-pen pv-- ph-" style="line-height:1rem !important; font-size:1.1rem" title="Reiseziel ändern"></a>`
  1566. );
  1567. }
  1568.  
  1569.  
  1570. // ***** Add message icons to discover, radar, visitor list, search results, and group member list, link contact icons *****
  1571. function handleTiles (jNode) {
  1572. let jNodeTiles = $(jNode).closest('a, button');
  1573. let profileName = $(jNode).text().trim();
  1574. let profileLink = `/${(jNodeTiles.attr('href')?.match(/^\/hunq\//) ? 'hunq' : 'profile')}/${profileName}`;
  1575.  
  1576. //add visit icons
  1577. let visitReceivedTime = '', visitMadeTime = '', index = -1;
  1578. const nameIndex = (item) => item.name == profileName;
  1579. (async() => {
  1580. while (! (visitorsLoaded && visitsLoaded)) await new Promise(resolve => setTimeout(resolve, 300));
  1581. // console.log('visitors await');
  1582. index = visitorsList.findIndex(nameIndex);
  1583. if (index >= 0) visitReceivedTime = new Date(visitorsList[index].date_visited);
  1584. index = visitsList.findIndex(nameIndex);
  1585. if (index >= 0) visitMadeTime = new Date(visitsList[index].date_visited);
  1586.  
  1587. //visits made page
  1588. if ($(jNode).closest('#visited-grid').length) {
  1589. if (visitReceivedTime) {
  1590. $(jNode).parent().parent().children().first().not(':has(.icon-visitor)').append(
  1591. `<a class="icon icon-visitor re-icon re-icon-visitor ml-" title="Hat mich besucht: ${weekTime(visitReceivedTime)}" href="/visitors"></a>`
  1592. ).children().last().off().on('click', function() {
  1593. changeUrl(`/visitors`);
  1594. return false;
  1595. });
  1596. }
  1597. if ($(jNodeTiles).find('time').length) $(jNodeTiles).find('time').text(weekTime(visitMadeTime)).attr('title', weekTime(visitMadeTime));
  1598.  
  1599. //other
  1600. } else {
  1601. if (visitMadeTime) {
  1602. $(jNode).parent().parent().children().first().not(':has(.icon-visitor)').append(
  1603. `<a class="icon icon-visitor re-icon re-icon-visited ml-" title="Von mir besucht: ${weekTime(visitMadeTime)}" href="/visitors/me"></a>`
  1604. ).children().last().off().on('click', function() {
  1605. changeUrl(`/visitors/me`);
  1606. return false;
  1607. });
  1608. }
  1609. if ($(jNodeTiles).find('time').length) $(jNodeTiles).find('time').text(weekTime(visitReceivedTime)).attr('title', weekTime(visitReceivedTime));
  1610. }
  1611. })();
  1612.  
  1613. //link username in list view
  1614. if ($(jNode).closest('button').length) {
  1615. $(jNode).not(':has(.re-link-idle)').wrapInner(
  1616. `<a class="re-link-idle" title="${profileName}" href="${profileLink}"></a>`
  1617. ).off().on('click', function() {
  1618. changeUrl(`${profileLink}`);
  1619. return false;
  1620. });
  1621. }
  1622.  
  1623. //add message icon
  1624. if (! location.pathname.match(/\/radar\/home|\/hunqz\/home|\/visitors|\/explore|\/eyecandy|\/messenger/)) {
  1625. let baseUrl = '/messenger/chat/';
  1626. $(jNode).parent().not(':has(.icon-chat)').append(
  1627. `<a class="icon icon-chat re-icon re-idle ml-" title="Messages" href="${baseUrl}${profileName}"></a>`
  1628. ).children().last().on('click', function() {
  1629. openByName(baseUrl, profileLink, profileName);
  1630. return false;
  1631. });
  1632. }
  1633.  
  1634. //link contact icon
  1635. jNodeTiles.find('svg').has('path[d^="M4 8a4"]').not('.re-contact').addClass('re-contact').wrap(
  1636. `<a class="re-idle-no-hover re-contact" title="Kontakt bearbeiten" href="/messenger/contacts/all/${profileName}"></a>`
  1637. );
  1638. jNodeTiles.find('a.re-contact').off().on('click', function() {
  1639. openByName('/messenger/contacts/all/', profileLink, profileName);
  1640. // console.log(profileLink, profileName);
  1641. return false;
  1642. });
  1643. jNodeTiles.find('svg').has('path[d^="M8.039"]').not('.re-contact-blocked').addClass('re-contact-blocked').wrap(
  1644. `<a class="re-idle-no-hover re-contact-blocked" title="Kontakt bearbeiten" href="/messenger/contacts/blocked/${profileName}"></a>`
  1645. );
  1646. jNodeTiles.find('a.re-contact-blocked').off().on('click', function() {
  1647. openByName('/messenger/contacts/blocked/', profileLink, profileName);
  1648. return false;
  1649. });
  1650.  
  1651. //blur nsfw placeholder
  1652. if (jNodeTiles.has(`div[style*="${nsfwPlaceholder}"]`).not(':has(.LIST)').length) {
  1653. let tilePicture = jNodeTiles.children('div').attr('style');
  1654. jNodeTiles.addClass('re-nsfw-placeholder').attr('style', `display:block; ${tilePicture} background-size:cover`).attr('title', 'Softcore-Bild');
  1655. }
  1656.  
  1657. //add + to distance if travel (if not made via CSS)
  1658. // if ((travelMode && location.pathname.match(/^\/(radar|groups|eyecandy)\//)) || location.pathname.match(/^\/explore\//) || (location.pathname.match(/^\/hunqz\//) && $('.ui-navbar').has('path[d^="M6 8c-1"]').length)) {
  1659. if (location.pathname.match(/^\/explore\//) || (location.pathname.match(/^\/hunqz\//) && $('.ui-navbar').has('path[d^="M6 8c-1"]').length)) {
  1660. // if ((travelMode && location.pathname.match(/^\/(radar\/(distance|login|new)|groups\/)/)) || location.pathname.match(/^\/explore\//) || (location.pathname.match(/^\/hunqz\//) && $('.ui-navbar').has('path[d^="M6 8c-1"]').length)) {
  1661. if (jNodeTiles.find('svg + p[class^="SpecialText"]').not('.re-add').text().match(/(\d[\d\.,]*\s?(km|m|mi|ft))/)) {
  1662. let distance = `+${jNodeTiles.find('svg + p[class^="SpecialText"]').html()}`;
  1663. jNodeTiles.find('svg + p[class^="SpecialText"]').html(distance).addClass('re-add');
  1664. }
  1665. }
  1666.  
  1667. //only in radar, hunqz, eyecandy
  1668. if ($(jNode).closest('#profiles, #eyecandy-results').length) {
  1669. // console.log('radar');
  1670.  
  1671. //link username to preview
  1672. $(jNode).not(':has(.re-link-idle)').wrapInner(
  1673. `<a class="re-link-idle" title="${profileName} | Vorschau" href="${profileLink}"></a>`
  1674. ).off().on('click', function() {
  1675. changeUrl(`${profileLink}/preview`);
  1676. return false;
  1677. });
  1678. }
  1679.  
  1680. //only for last tile in radar, travel, hunqz, eyecandy
  1681. if ($(jNode).closest('#profiles, #explore-grid, #eyecandy-results').length && !$(jNode).closest('div.search-results__item').next('div.search-results__item').length && !$(jNode).closest('li').next('li').length) {
  1682. if (testMode) console.log('tile stats');
  1683.  
  1684. //show count of profiles, Plus, travelling, and GPS in title tag of selected tab
  1685. let loadTime = new Date().toLocaleTimeString('de-DE') + ' aktualisiert';
  1686. let profiles = $(':is(#profiles, #explore-grid, #eyecandy-results) :is(div.BIG, div.SMALL, div.LIST)').length;
  1687. let profilesPlus = '', percentPlus = '';
  1688. if ($('#profiles .search-results--mixed-tiles, #explore-grid > ul > ul, #eyecandy-results > ul > ul').length) {
  1689. profilesPlus = $(':is(#profiles, #explore-grid, #eyecandy-results) div.BIG').length;
  1690. percentPlus = (profiles > 0 ? ' (' + (Math.round(profilesPlus/profiles*1000)/10).toLocaleString('de-DE') + ' %)' : '');
  1691. profilesPlus = ' • ' + profilesPlus + ' Plus' + percentPlus;
  1692. }
  1693. let profilesTravel = '', percentTravel = '';
  1694. profilesTravel = $(':is(#profiles, #explore-grid, #eyecandy-results) svg > path[d^="M10.0452"]').length;
  1695. percentTravel = (profiles > 0 ? ' (' + (Math.round(profilesTravel/profiles*1000)/10).toLocaleString('de-DE') + ' %)' : '');
  1696. profilesTravel = `${profilesTravel} ${(profilesTravel == 1 ? 'Reisender' : 'Reisende')}${percentTravel}`;
  1697. let profilesGPS = '', percentGPS = '';
  1698. profilesGPS = $(':is(#profiles, #explore-grid, #eyecandy-results) svg > path[d^="M11.94"]').length;
  1699. percentGPS = (profiles > 0 ? ' (' + (Math.round(profilesGPS/profiles*1000)/10).toLocaleString('de-DE') + ' %)' : '');
  1700. profilesGPS = ' • ' + profilesGPS + ' GPS' + percentGPS;
  1701. profiles += ` ${(profiles == 1 ? 'Profil' : 'Profile')} geladen`;
  1702. $('.js-main-stage .js-navigation nav a[aria-current="page"], .js-main-stage header > nav > a[aria-current="page"], div.js-nav-item, .ui-navbar header > h1').attr(
  1703. 'title', `${loadTime}\r${profiles}${profilesPlus}\r${profilesTravel}${profilesGPS}`
  1704. );
  1705. }
  1706. }
  1707.  
  1708.  
  1709. // ***** Add message icons to non-message Activity Stream entries; handle group related entries; add settings icon *****
  1710.  
  1711. //init
  1712. let streamShowAll = (localStorage.getItem('REstreamShowAll') === 'true');
  1713.  
  1714. function handleStream (jNode) {
  1715. let jNodeStream = $(jNode).closest('a');
  1716. let bodyLink = jNodeStream.attr('href');
  1717. let profileLink = '', profileName = '';
  1718. let baseUrl = '/messenger/chat/';
  1719. let itemText = jNodeStream.find('span.listitem__text').not('.thumbnail__info');
  1720. if (bodyLink != undefined) {
  1721. let replacePattern = /(.*\/(profile|hunq|groups)\/)([-\w]*)(.*)/;
  1722. profileLink = jNodeStream.parent().find('a').attr('href');
  1723. profileName = profileLink.replace(replacePattern, '$3');
  1724.  
  1725. //link contact icon
  1726. jNodeStream.find('svg').has('path[d^="M4 8a4"]').not('.re-contact').addClass('re-contact').wrap(
  1727. `<a class="re-idle-no-hover re-contact" title="Kontakt bearbeiten" href="/messenger/contacts/all/${profileName}"></a>`
  1728. );
  1729. jNodeStream.find('a.re-contact').off().on('click', function() {
  1730. openByName('/messenger/contacts/all/', profileLink, profileName);
  1731. return false;
  1732. });
  1733. jNodeStream.find('svg').has('path[d^="M8.039"]').not('.re-contact-blocked').addClass('re-contact-blocked').wrap(
  1734. `<a class="re-idle-no-hover re-contact-blocked" title="Kontakt bearbeiten" href="/messenger/contacts/blocked/${profileName}"></a>`
  1735. );
  1736. jNodeStream.find('a.re-contact-blocked').off().on('click', function() {
  1737. openByName('/messenger/contacts/blocked/', profileLink, profileName);
  1738. return false;
  1739. });
  1740. }
  1741. if (bodyLink != undefined && ! bodyLink.match('/messenger/chat/')) {
  1742.  
  1743. //add message icon
  1744. if (profileName) {
  1745. jNodeStream.find('p[class^="BodyText-"]').not(':has(a,img)').prepend(
  1746. `<a class="icon icon-chat re-icon mr-" title="Messages" href="${baseUrl}${profileName}"></a>`
  1747. ).children().last().on('click', function() {
  1748. openByName(baseUrl, profileLink, profileName);
  1749. return false;
  1750. });
  1751. }
  1752.  
  1753. //italic if system text
  1754. itemText.attr('style', 'font-style:italic');
  1755.  
  1756. //decode html entities in group names
  1757. itemText.html(itemText.text().trim());
  1758.  
  1759. //add icon if group picture, add class for toggle
  1760. let jNodeGroupPicture = jNodeStream.has('img.thumbnail').not(':has(span.icon-heart, span.icon-camera-icon, .re-add)');
  1761. jNodeGroupPicture.find('span.listitem__timestamp').after(
  1762. `<span class="clr-ui-text listitem__highlight--expanded re-add"><span class="icon icon-group-members"></span></span>`
  1763. );
  1764. $(jNodeGroupPicture).parent().addClass('re-group-item');
  1765.  
  1766. //move text to title on non-touch devices if group picture
  1767. if (! touch.matches) {
  1768. // jNodeGroupPicture.attr('title', jNodeGroupPicture.find('span.listitem__text').first().text().trim());
  1769. // jNodeGroupPicture.find('span.listitem__text').first().remove();
  1770. }
  1771.  
  1772. //hide group pictures of blocked users
  1773. // $(jNodeGroupPicture).has('path[d^="M8.039"]').parent().addClass('re-group-item').hide();
  1774. }
  1775.  
  1776. //add toggle and settings
  1777. // $('div.stream__content div.js-list').not(':has(div.re-add)').prepend(`
  1778. $('div.stream__content').not('.re-done').addClass('re-done').before(`
  1779. <div class="re-stream-toggle-div">
  1780. <a class="re-link re-stream-toggle" title="Umschalten" href="">
  1781. <span>MIT Gruppenbildern<em></em></span>
  1782. <span>OHNE Gruppenbilder<em></em></span>
  1783. <svg aria-hidden="true" viewBox="0 0 10 6" height="6" width="10">
  1784. <path fill-rule="evenodd" d="M5.707 5.707a1 1 0 0 1-1.414 0l-4-4A1 1 0 0 1 1.707.293L5 3.586 8.293.293a1 1 0 0 1 1.414 1.414l-4 4Z" clip-rule="evenodd"></path>
  1785. </svg>
  1786. </a>
  1787. <a href="/me/notifications" class="icon icon-settings re-icon re-link p-" title="Activity Stream anpassen"></span></a>
  1788. </div>`
  1789. );
  1790. $('.re-stream-toggle').off().on('click', function() {
  1791. streamShowAll = !streamShowAll;
  1792. localStorage.setItem('REstreamShowAll', streamShowAll);
  1793. $('.re-stream-toggle, .stream__content').toggleClass('re-selected');
  1794. return false;
  1795. });
  1796. if (streamShowAll) $('.re-stream-toggle, .stream__content').addClass('re-selected');
  1797.  
  1798. //insert number of group items
  1799. let groupItems = $('.stream__content .re-group-item').not(':has(path[d^="M8.039"])').length;
  1800. if (groupItems) {
  1801. $('.re-stream-toggle em').html(` (${groupItems})`).parent().attr('title', `Neue Bilder in ${groupItems} Gruppe${(groupItems == 1) ? `` : `n`}`);
  1802. } else {
  1803. $('.re-stream-toggle em').html(` (0)`).parent().attr('title', `Keine neuen Gruppenbilder`);
  1804. }
  1805. }
  1806.  
  1807.  
  1808. // ***** Profiles: show profile id, visits since; link URLs *****
  1809. function handleProfile (jNode) {
  1810. let profileName = $('div.top-info-header p[class^="BodyText"]').first().text().trim();
  1811. let profileType = ($('div.is-profile-loaded').hasClass('profile--hunqz')) ? '/hunqz/profiles' : '/profiles';
  1812. let profilePath = ($('div.is-profile-loaded').hasClass('profile--hunqz')) ? 'hunq' : 'profile';
  1813. let profileId = '', profileIdInfo = '', visits = '', since = '', known = '', known1st = '', known2nd = '', verified = '';
  1814. let albums = 0, profilePictures = 0, albumPictures = 0, quickSharePictures = 0;
  1815. let name = '', country = '', distance = '';
  1816. let loginTime = '', loginLocalTime = '', timeTag = '', lastSince ='';
  1817. let sinceMonthYear = $('section.profile__stats section > p').last().contents().filter(function(){ return this.TEXT_NODE; }).first().text();
  1818. let galleryPath = `/${profilePath}/${profileName}/gallery`;
  1819.  
  1820. let data = xhrFull;
  1821. if (data.id) {
  1822. profileId = data.id;
  1823. profileIdInfo = `Profil-ID: ${data.id}`;
  1824. }
  1825. if (data.visits_count) { // not 0 or undefined
  1826. visits = `<br>Besucher: ${data.visits_count.toLocaleString('de-DE')}`;
  1827. }
  1828. if (data.creation_date) {
  1829. since = new Date(data.creation_date.slice(0,-5)+'.000Z').toLocaleDateString('de-DE');
  1830. since = 'Mitglied seit: ' + since;
  1831. } else {
  1832. since = sinceMonthYear;
  1833. }
  1834.  
  1835. //known by
  1836. if (data.known_by?.first_degree > 0) {
  1837. known1st = data.known_by.first_degree;
  1838. known2nd = data.known_by.second_degree;
  1839. known = `Bekannt bei ${known1st.toLocaleString('de-DE')} ${known1st == 1 ? 'Nutzer (dieser' : 'Nutzern (diese'} bekannt bei ${known2nd.toLocaleString('de-DE')})`;
  1840. } else {
  1841. known = `Noch nicht bei anderen bekannt`;
  1842. }
  1843.  
  1844. //online time
  1845. if (data.last_login && data.online_status) {
  1846. loginTime = new Date(data.last_login.slice(0,-5)+'.000Z');
  1847. loginLocalTime = dateTime(loginTime);
  1848. timeTag = dateTime(loginTime, 'dateOnly');
  1849. lastSince = `${data.online_status == 'OFFLINE' ? 'Zuletzt online:' : 'Online seit:'}`;
  1850. }
  1851.  
  1852. //show pictures count
  1853. if (data.albumsV2.items_total) {
  1854. for (let item of data.albumsV2.items) {
  1855. if (item.id == 'PROFILE') {
  1856. if (item.pictures) {
  1857. profilePictures += item.pictures.items_total;
  1858. }
  1859. } else if (item.access_policy == 'SHARED') {
  1860. quickSharePictures += item.items_total;
  1861. } else {
  1862. if (item.pictures) {
  1863. albumPictures += item.pictures.items_total;
  1864. //albums += (item.items_total) ? 1 : 0;
  1865. }
  1866. }
  1867. }
  1868. if (profilePictures > 1) {
  1869. $('section.profile__image-strip').not(':has(.re-add)').append(
  1870. `<div class="re-add re-img-count"><div><p>${profilePictures}</p></div></div>`
  1871. ).children().last().on('click', function(){
  1872. changeUrl(`${galleryPath}`);
  1873. }).attr('style', 'cursor:pointer').titleLabel('Alle Alben anzeigen');
  1874. }
  1875. if (quickSharePictures) {
  1876. $('section.js-profile-stats section > div > a > div > div').first().has('svg').not(':has(.re-add)').append(
  1877. `<div class="re-add re-img-count"><div><p>${quickSharePictures}</p></div></div>`
  1878. );
  1879. }
  1880. if (quickSharePictures || albumPictures) {
  1881. $(`section.profile__stats section a[href^="${galleryPath}"] span`).not(':has(.re-add)').append(
  1882. `<span class="re-add" style="font-size:.925em; color:rgb(255,255,255,.6)"> ${quickSharePictures + albumPictures} Bilder</span>`
  1883. );
  1884. }
  1885. }
  1886.  
  1887. //link location name to maps
  1888. if (data.location.name) {
  1889. name = `<a target="_blank" href="https://google.com/maps/place/${data.location.name}" rel="noreferrer noopener" class="re-link-idle" title="Ort in Google Maps anzeigen">${data.location.name.trim()}</a>`;
  1890. }
  1891. if (data.location.country && (data.location.distance > 50000 || data.location.distance == null)) {
  1892. country = `, ${data.location.country}`;
  1893. }
  1894. if (data.location.distance != null) {
  1895. if (data.location.distance < 1000) {
  1896. distance = ' • ' + data.location.distance + 'm';
  1897. } else if (data.location.distance < 50000) {
  1898. distance = ' • ' + (Math.round(data.location.distance/100)/10).toLocaleString('de-DE') + ' km';
  1899. } else {
  1900. distance = ' • ' + (Math.round(data.location.distance/1000)) + ' km';
  1901. }
  1902. }
  1903.  
  1904. //location, country, distance
  1905. $('div.profile__info svg + p[class^="BodyText"]').first().html(name + country + distance);
  1906.  
  1907. //since, id, known by, visits, online
  1908. $('section.profile__stats section > p').last().addClass('re-profile-stats mb-').html(`${since}<br>${profileIdInfo}<br>${known}${visits}<br>${lastSince} ${timeTag}`);
  1909.  
  1910. //authenticity
  1911. let title = $('.top-info-header p + div > button').attr('title');
  1912. $('.top-info-header p + div > button').not('.re-add').addClass('re-add').find('title').text(`${title}${(known1st) ? ` (${(known1st <= 30) ? known1st : '30+'})` : ''}\n${sinceMonthYear}\n${lastSince} ${timeTag}`);
  1913.  
  1914. //hide visit
  1915. let hideVisitSpacer = $('.profile .top-info-header p[class^="BodyText"]').first().text().trim();
  1916. $('.profile--romeo .top-info-header').prepend(`<span class="re-hide-visit">${hideVisitSpacer}</span>`);
  1917. $('.re-hide-visit').attr('title', 'Profilbesuch verstecken').off().on('click', () => {
  1918. hideVisit(profileId);
  1919. });
  1920.  
  1921. //new
  1922. if (data.is_new) $('.profile .top-info-header').prepend(`<span class="re-profile-new">${hideVisitSpacer}<span>Neu</span></span>`);
  1923.  
  1924. //complete headline
  1925. if (data.headline?.length > 50) {
  1926. $('div.profile__info p[class^="BodyText"]').last().text(data.headline);
  1927. }
  1928.  
  1929. //URLs in headline and profile text
  1930. $('div.profile__info div.reactView > div > p[class^="BodyText"], section.profile__stats details > div > p[class^="BaseText-sc-"]').each(function() {
  1931. let replacedText = linkify($(this).html());
  1932. $(this).html(replacedText);
  1933. });
  1934.  
  1935. //link travel locations to maps
  1936. $('section.profile__stats details').has('#travel-list').find('div:not([role="heading"]) > p:first-child').not(':has(.re-add)').each(function() {
  1937. $(this).wrapInner(
  1938. `<a target="_blank" href="https://google.com/maps/place/${$(this).text().trim()}" rel="noreferrer noopener" class="re-link-idle re-add" title="Ort in Google Maps anzeigen"></a>`
  1939. )
  1940. });
  1941.  
  1942. //move Travel Date and Looking For to top, keeping Albums and B&B at top
  1943. let sectionContainer = 'section.profile__stats > div > div.reactView:last-child > div';
  1944. $('section.profile__stats details').not('.re-add').has('summary :contains("Ich suche"), summary :contains("Looking For"), summary :contains("Recherche")').prependTo(sectionContainer);
  1945. $('section.profile__stats details').has('#travel-list').not('.re-done').addClass('re-done').prependTo(sectionContainer);
  1946. $('section.profile__stats section').not('.re-done').has('img + div > p').addClass('re-done').prependTo(sectionContainer);
  1947. $('section.profile__stats section').not('.re-done').has(`a[href^="${galleryPath}"]`).addClass('re-done').prependTo(sectionContainer);
  1948. }
  1949.  
  1950.  
  1951. // ***** Handle error page *****
  1952. function linksError (jNode) {
  1953. //...
  1954. $(jNode).append(
  1955. `<div style="font-size:0.9em"><br/>...</div>`
  1956. );
  1957. }
  1958.  
  1959.  
  1960. // ***** Sort groups list by active posts *****
  1961.  
  1962. //init
  1963. let groupsList = [];
  1964. let viewMode = '';
  1965. let counter = 0;
  1966. let groupsCount = 0;
  1967. let groupsRefreshed = false;
  1968. let groupsListloadTime = 0;
  1969. let lastGroupName = '';
  1970. let atoz = false;
  1971. let linkTextActive = 'Aktive Beiträge', linkTextDefault = 'Standard';
  1972.  
  1973. function recentPosts (jNode) {
  1974. // console.log('recentPosts');
  1975.  
  1976. //insert empty list
  1977. if (! $('nav.re-groups-enh').length) {
  1978. $(jNode).not('.re-groups-def').addClass('re-groups-def').after(
  1979. `<nav class="re-groups-enh is-hidden"></nav>`
  1980. );
  1981. }
  1982.  
  1983. //insert menu
  1984. viewMode = localStorage.getItem('REgroupsListView');
  1985. let linkText = (viewMode == 'active') ? linkTextActive : linkTextDefault;
  1986. $('nav.re-groups-def').closest('div[class*="ReactContainer"]').not(':has(span.re-list-head)').prepend(`
  1987. <div class="mh mt- pv-" style="font-size:.85rem; border-bottom:1px solid rgb(255,255,255,.125)">
  1988. <span class="re-list-head">Sortierung</span>
  1989. <span class="re-list-view ml-" title="Umschalten" role="button" aria-label="Sortierung ändern">${linkText}</span>
  1990. <span class="re-list-load icon icon-stathistory pl- pr-" title="Aktualisieren">
  1991. <span class="re-list-head ml--"></span>
  1992. </span>
  1993. </div>`
  1994. );
  1995.  
  1996. //toggle sort mode (recent posts <-> a-z)
  1997. $('.re-list-head').off().on('dblclick', function() {
  1998. if (viewMode == 'active') {
  1999. atoz = !atoz;
  2000. linkText = linkTextActive = (atoz) ? 'A - Z' : 'Aktive Beiträge';
  2001. $('.re-list-view').text(linkText);
  2002. insertPostsList (groupsList);
  2003. }
  2004. });
  2005.  
  2006. if (mobile.matches) {
  2007. $('div.js-header').removeClass('l-hidden-sm');
  2008. $('div.Container--xbtQn').hide();
  2009. }
  2010. if (viewMode !== 'active') {
  2011. $('span.re-list-load').hide();
  2012. }
  2013.  
  2014. //register toggle menu click
  2015. $('span.re-list-view').on('click', function() {
  2016. if (counter == groupsCount) { //not while building groupsList
  2017. let oldViewMode = localStorage.getItem('REgroupsListView');
  2018. if (oldViewMode == 'active') {
  2019. linkText = linkTextDefault;
  2020. viewMode = 'default';
  2021. $('span.re-list-load').hide();
  2022. } else {
  2023. linkText = linkTextActive;
  2024. viewMode = 'active';
  2025. $('span.re-list-load').show();
  2026. }
  2027. localStorage.setItem('REgroupsListView', viewMode);
  2028. $('span.re-list-view').text(linkText);
  2029. handlePostsList(viewMode);
  2030. }
  2031. });
  2032.  
  2033. //register refresh list click
  2034. $('span.re-list-load').on('click', function() {
  2035. if (counter == groupsCount) { //not while building groupsList
  2036. groupsList.length = 0;
  2037. counter = 0;
  2038. groupsRefreshed = true;
  2039. groupsListloadTime = new Date().toLocaleTimeString('de-DE');
  2040. $('nav.re-groups-enh').find('a.re-add, button, h4').remove();
  2041. $('nav.re-groups-def').hide();
  2042. handlePostsList(viewMode);
  2043. }
  2044. });
  2045. handlePostsList(viewMode);
  2046. }
  2047.  
  2048.  
  2049. // ***** Handle posts list *****
  2050. function handlePostsList (viewMode) {
  2051.  
  2052. if (viewMode !== 'active') {
  2053. $('nav.re-groups-enh').addClass('is-hidden');
  2054. $('nav.re-groups-def').show();
  2055. let highlighted = 'nav.re-groups-def button';
  2056. if ($(highlighted).length) {
  2057. $(highlighted)[0].scrollIntoView({block: 'center'});
  2058. }
  2059. } else {
  2060. $('nav.re-groups-def').hide();
  2061. $('nav.re-groups-enh').removeClass('is-hidden');
  2062. if (groupsList.length > 0) {
  2063. insertPostsList(groupsList);
  2064. } else {
  2065. if (commonGroupsList[0]?.name == location.href.replace(/(.*\/groups\/member\/)([-\w]+)$/, '$2')) {
  2066. // groupsRefreshed = true;
  2067. }
  2068. groupsListloadTime = new Date().toLocaleTimeString('de-DE');
  2069. $('nav.re-groups-enh').not(':has(.re-add)').prepend(
  2070. '<div class="spinner-container re-add"><div class="spinner"></div></div>'
  2071. );
  2072.  
  2073. $.ajax({headers: ajaxHead(), url: '/api/v4/profiles/me/groups?expand=items.*.(membership,activity)&lang=de&length=1000&pick=items.*.(id,name,display_name,membership.is_admin,is_forum_enabled,activity.posts.*,activity.photos.*,preview_pic.url_token),items_total'})
  2074.  
  2075. .done(function (data) {
  2076. groupsList = [];
  2077. counter = 0;
  2078. groupsCount = data.items_total;
  2079. let ajaxUrl = '', name = '', displayName = '', isAdmin = false, picToken = '', actPosts = 0, countPosts = 0, actPhotos = 0, countPhotos = 0;
  2080.  
  2081. for (let item of data.items) {
  2082.  
  2083. // loop "posts" for all groups
  2084. if (item) {
  2085. name = item.name;
  2086. displayName = item.display_name;
  2087. isAdmin = item.membership.is_admin;
  2088. picToken = (item.preview_pic) ? item.preview_pic.url_token : '';
  2089. if (item.activity) {
  2090. actPosts = (item.activity.posts) ? item.activity.posts.last_accessed : 0;
  2091. countPosts = (item.activity.posts) ? item.activity.posts.count : 0;
  2092. actPhotos = (item.activity.photos) ? item.activity.photos.last_accessed : 0;
  2093. countPhotos = (item.activity.photos) ? item.activity.photos.count : 0;
  2094. } else {
  2095. actPosts = 0;
  2096. countPosts = 0;
  2097. actPhotos = 0;
  2098. countPhotos = 0;
  2099. }
  2100. if (item.is_forum_enabled) {
  2101. ajaxUrl = `/api/v4/groups/${item.id}/posts?pick=items.*.(date_created,comments.items.*.(date_created))&expand=items.*.(comments)&lang=de&length=5&sort_criteria=COMMENTED_AT_DESC`;
  2102. } else {
  2103. ajaxUrl = `/api/v4/groups/${item.id}/posts?pick=items.*.(date_created)&lang=de&length=5&sort_criteria=COMMENTED_AT_DESC`;
  2104. }
  2105.  
  2106. $.ajax({headers: ajaxHead(),
  2107. url: ajaxUrl,
  2108. custom: {name: name, displayName: displayName, isAdmin: isAdmin, picToken: picToken, actPosts: actPosts, countPosts: countPosts, actPhotos: actPhotos, countPhotos: countPhotos} })
  2109.  
  2110. .done(function (data) {
  2111.  
  2112. //save name, timestamp, etc. to array (most recent post or comment)
  2113. let timeList = [], name = '', displayName = '', time = 0, isComment = false, picToken = '', actPosts = 0, countPosts = 0, actPhotos = 0, countPhotos = 0;
  2114. for (let item of data.items) {
  2115. if (item.comments?.items[0]) {
  2116. time = item.comments.items[0].date_created;
  2117. isComment = true;
  2118. } else {
  2119. time = item.date_created;
  2120. isComment = false;
  2121. }
  2122. if (time != 0) time = new Date(time.slice(0,-2) + ':00');
  2123. timeList.push({time: time, isComment: isComment});
  2124. }
  2125.  
  2126. //sort by most recent post or comment
  2127. timeList.sort(function (a,b) {
  2128. return b.time - a.time;
  2129. });
  2130.  
  2131. if (timeList[0]) time = timeList[0].time;
  2132. if (timeList[0]) isComment = timeList[0].isComment;
  2133. name = this.custom.name;
  2134. displayName = this.custom.displayName.trim();
  2135. isAdmin = this.custom.isAdmin;
  2136. picToken = this.custom.picToken;
  2137. actPosts = this.custom.actPosts;
  2138. countPosts = this.custom.countPosts;
  2139. actPhotos = this.custom.actPhotos;
  2140. countPhotos = this.custom.countPhotos;
  2141. if (actPosts != 0) actPosts = new Date(actPosts);
  2142. if (actPhotos != 0) actPhotos = new Date(actPhotos);
  2143. // console.log(displayName, timeList);
  2144.  
  2145. //add to array
  2146. groupsList.push({time: time, name: name, displayName: displayName, isAdmin: isAdmin, picToken: picToken, isComment: isComment, actPosts: actPosts, countPosts: countPosts, actPhotos: actPhotos, countPhotos: countPhotos, visited: false});
  2147. })
  2148.  
  2149. .always(function (data) {
  2150. counter++;
  2151. if (counter >= groupsCount) {
  2152. insertPostsList(groupsList);
  2153. return;
  2154. }
  2155. });
  2156.  
  2157. } else {
  2158. counter++;
  2159. if (counter >= groupsCount) {
  2160. insertPostsList(groupsList);
  2161. return;
  2162. }
  2163. }
  2164. }
  2165. });
  2166. }
  2167. $('span.re-list-load span').text(groupsListloadTime.slice(0,-3));
  2168. $('ul.Container--1I9Gx button').click();
  2169. }
  2170. }
  2171.  
  2172.  
  2173. // ***** Handle groups list after group is loaded *****
  2174. function refreshPostsList () {
  2175. if (groupsRefreshed == false || lastGroupName != location.pathname.replace(/^(\/groups\/member\/)([-\w]+)(.*)/, '$2')) {
  2176. lastGroupName = location.pathname.replace(/^(\/groups\/member\/)([-\w]+)(.*)/, '$2');
  2177. groupsRefreshed = false;
  2178. } else {
  2179. groupsRefreshed = true;
  2180. }
  2181. if (viewMode == 'active') insertPostsList (groupsList);
  2182. }
  2183.  
  2184.  
  2185. // ***** Insert posts list *****
  2186. function insertPostsList (groupsList) {
  2187.  
  2188. //sort by a-z or most recent
  2189. groupsList.sort(function (a,b) {
  2190. if (atoz) {
  2191. return b.isAdmin - a.isAdmin || a.displayName.localeCompare(b.displayName);
  2192. } else {
  2193. return b.time - a.time;
  2194. }
  2195. });
  2196. // console.log(groupsList);
  2197.  
  2198. //insert list elements on top of MY GROUPS
  2199. $('nav.re-groups-enh').find('a.re-add, button, div, h4').remove();
  2200. $('nav.re-groups-def').hide();
  2201. let thumbnail = '';
  2202. for (let item of groupsList) {
  2203. thumbnail = (item.picToken) ? `/img/usr/squarish/212x212/${item.picToken}.jpg` : '/assets/76d319082f701e66be89.svg';
  2204. $('nav.re-groups-enh').append(`
  2205. <a href="/groups/member/${item.name}" class="re-add re-groups-listitem">
  2206. <div class="re-groups-tile" style="background-image:url(${thumbnail})"></div>
  2207. <div class="re-groups-entry">
  2208. <div class="re-groups-top-row">
  2209. <div class="re-groups-name" title="${item.displayName}">${item.displayName}</div>
  2210. </div>
  2211. <div class="re-groups-bottom-row">
  2212. <div class="re-groups-time" title="${(item.isComment) ? 'Letzter Kommentar' : 'Letzter Beitrag'} vom ${item.time.toLocaleString('de-DE').slice(0,-3)}">${dateTime(item.time, !touch.matches)}</div>
  2213. <div class="re-groups-activity"></div>
  2214. </div>
  2215. </div>
  2216. </a>`
  2217. );
  2218. if (item.isComment) {
  2219. $('nav.re-groups-enh a.re-add .re-groups-activity').last().append(`
  2220. <div class="re-groups-new" style="margin-right:.1em" title="Aktueller Kommentar vom ${item.time.toLocaleString('de-DE').slice(0,-3)}">
  2221. <svg label="Aktueller Kommentar" role="img" aria-hidden="false" viewBox="0 0 1024 1024">
  2222. <path d="M943.3 408.7c-49.9-49.9-118.9-80.7-195-80.7H222.7l.1-183.5L0 367.4l222.8 222.8.1-183.5h525.4c54.4 0 103.6 22.1 139.3 57.7 35.6 35.7 57.6 84.9 57.6 139.3 0 54.4-22 103.6-57.7 139.3-35.7 35.7-84.9 57.7-139.3 57.7H616.3c-14.2 0-25.7 11.5-25.7 25.7v27.5c0 14.2 11.5 25.7 25.7 25.7h131.9c76.2 0 145.1-30.9 195-80.7 49.9-49.9 80.7-118.9 80.7-195 .1-76.3-30.8-145.3-80.6-195.2z"></path>
  2223. </svg>
  2224. </div>`
  2225. );
  2226. }
  2227. if (item.actPosts) {
  2228. $('nav.re-groups-enh a.re-add .re-groups-activity').last().append(`
  2229. <div class="re-groups-new" title="${item.countPosts} ${(item.countPosts == 1) ? 'neuer Beitrag' : 'neue Beiträge'} seit ${item.actPosts.toLocaleString('de-DE').slice(0,-3)}">
  2230. <span style="margin-right:.25em">${item.countPosts}</span>
  2231. <svg label="Neue Beiträge" style="font-size:.675em" role="img" aria-hidden="false" viewBox="0 0 100 100">
  2232. <path d="M50 0a50 50 0 100 100A50 50 0 1050 0z"></path>
  2233. </svg>
  2234. </div>`
  2235. );
  2236. }
  2237. if (item.actPhotos) {
  2238. $('nav.re-groups-enh a.re-add .re-groups-activity').last().append(`
  2239. <div class="re-groups-new" title="${item.countPhotos} ${(item.countPhotos == 1) ? 'neues Bild' : 'neue Bilder'} seit ${item.actPhotos.toLocaleString('de-DE').slice(0,-3)}">
  2240. <span style="margin-right:.25em">${item.countPhotos}</span>
  2241. <svg label="Neue Bilder" role="img" aria-hidden="false" viewBox="0 0 1024 1024">
  2242. <path d="M656 579c0 80-64 145-144 145s-144-65-144-145 64-145 144-145 144 65 144 145zM512 808c-63 0-120-26-161-68s-67-98-67-162c0-63 26-120 67-162s98-67 161-67 120 25 161 67 67 99 67 162c0 64-26 120-67 162s-98 68-161 68zm449-589H762L644 107c-7-7-16-11-27-11H407c-11 0-20 4-27 11L262 219H63c-35 0-63 28-63 63v583c0 35 28 63 63 63h898c35 0 63-28 63-63V282c0-35-28-63-63-63z"></path>
  2243. </svg>
  2244. </div>`
  2245. );
  2246. }
  2247. if (item.isAdmin) {
  2248. $('nav.re-groups-enh a.re-add .re-groups-top-row').last().append(`
  2249. <div class="re-groups-admin ml-" title="Du verwaltest diese Gruppe" aria-label="Du verwaltest diese Gruppe">
  2250. <span>ADMIN</span>
  2251. </div>`
  2252. );
  2253. }
  2254. if (item.visited && !groupsRefreshed) { // dim visited groups
  2255. $('nav.re-groups-enh a.re-add').last().addClass('re-groups-visited');
  2256. }
  2257. }
  2258.  
  2259. //highlight selected item
  2260. $('nav.re-groups-enh a.re-add').each(function(index) {
  2261. if ((!mobile.matches && location.href.match(`${this.href}($|\\/)`)) || (this.href.match(`/groups/member/${lastGroupName}($|\\/)`) && !groupsRefreshed)) {
  2262. if (!groupsRefreshed) $(this)[0].scrollIntoView({block: 'center'});
  2263. $(this).removeClass('re-groups-visited').addClass('re-groups-selected');
  2264. groupsList[index].visited = true;
  2265. }
  2266. });
  2267.  
  2268. //refresh content on re-click
  2269. $('nav.re-groups-enh').off('click').on('click', 'a.re-add', function() {
  2270. groupsRefreshed = false;
  2271. if (location.href.match(`${this.href}($|\\/)`)) {
  2272. $('ul.Container--ZCCAM button').get(0).click();
  2273. if (testMode) console.log ('group refreshed');
  2274. return false;
  2275. }
  2276. });
  2277.  
  2278. //insert separators admin/member if needed
  2279. if (atoz && $('nav.re-groups-enh a.re-add').not(':has(.re-groups-admin)').length) {
  2280. $('nav.re-groups-enh a.re-add').has('.re-groups-admin').first().before(
  2281. `<h4 class="gzwUHp ml- mt- mb--">Admin</h4>`
  2282. );
  2283. $('nav.re-groups-enh a.re-add').has('.re-groups-admin').last().after(
  2284. `<h4 class="gzwUHp ml- mt- mb--">Mitglied</h4>`
  2285. );
  2286. }
  2287. }
  2288.  
  2289.  
  2290. // ***** Toggle group manage mode *****
  2291. function groupManageMode (jNode) {
  2292. if (resigned) $(jNode).not(':has(.re-add)').prepend('<span class="re-add">!</span>');
  2293. $(jNode).off().on('dblclick', function() {
  2294. $(this).find('.re-add').remove();
  2295. resigned = !resigned;
  2296. if (resigned) $(this).prepend('<span class="re-add">!</span>');
  2297. });
  2298. }
  2299.  
  2300.  
  2301. // ***** Compact view for group posts *****
  2302. function groupPostsViewMode (jNode) {
  2303. let viewMode = localStorage.getItem('REgroupPostsView');
  2304. let linkTextCompact = 'Kompakt', linkTextDefault = 'Mit Kommentaren';
  2305. let linkText = (viewMode == 'compact') ? linkTextCompact : linkTextDefault;
  2306. $('div.Container--1cJVr').append(
  2307. '<span class="re-posts-view" title="Umschalten">' + linkText + '</span>' +
  2308. '<span class="layout-item icon icon-dropdown dropdown__arrow"></span>'
  2309. );
  2310. $('span.re-posts-view').on('click', function() {
  2311. let oldViewMode = localStorage.getItem('REgroupPostsView');
  2312. let linkText = linkTextCompact, viewMode = 'compact';
  2313. if (oldViewMode == 'compact') {
  2314. linkText = linkTextDefault;
  2315. viewMode = 'default';
  2316. }
  2317. localStorage.setItem('REgroupPostsView', viewMode);
  2318. $('span.re-posts-view').text(linkText);
  2319. $('span.CommentCount--3uCNR').each(function() {
  2320. handleGroupComment(this);
  2321. });
  2322. });
  2323. }
  2324.  
  2325.  
  2326. // ***** Show/hide group comments *****
  2327. function refreshGroupComments (jNode) {
  2328. let thisNode = $(jNode).find('span.CommentCount--3uCNR'); // span.CommentCount--1Eef2
  2329. handleGroupComment(thisNode);
  2330. }
  2331.  
  2332.  
  2333. // ***** Handle a single group post *****
  2334. function handleGroupComment (thisNode) {
  2335. let viewMode = localStorage.getItem('REgroupPostsView');
  2336. let timestamp = $(thisNode).parent().parent().nextAll().find('span.PostDate--2LVzF').last().text();
  2337. if (viewMode == 'compact') {
  2338. $(thisNode).parent().parent().nextAll().hide();
  2339. if (timestamp == '') {
  2340. $(thisNode).text('Kommentar schreiben');
  2341. } else {
  2342. $(thisNode).after(
  2343. '<span class="ml--"> • ' + timestamp + '</span>'
  2344. );
  2345. }
  2346. $(thisNode).not(':has(a)').wrapInner('<a></a>').off('click').on('click', function() {
  2347. $(thisNode).parent().parent().nextAll().toggle();
  2348. });
  2349. } else {
  2350. $('div.CommentsArea--3iSoQ, div.js-add-comment').show();
  2351. $(thisNode).find('a').contents().unwrap('a');
  2352. $(thisNode).off('click');
  2353. $(thisNode).next('span').remove();
  2354. if (timestamp == '') {
  2355. $(thisNode).text('0 Kommentare');
  2356. }
  2357. }
  2358. }
  2359.  
  2360.  
  2361. // ***** Localize group post date *****
  2362. function handleGroupPostDate (jNode) {
  2363. $(jNode).addClass('re-done');
  2364. let date = $(jNode).text().trim();
  2365. let localeDate = new Date(date).toLocaleDateString('de-DE');
  2366. if (localeDate != 'Invalid Date' && date.length > 4) $(jNode).text(`${localeDate}`).attr('title', `${date}`);
  2367. }
  2368.  
  2369.  
  2370. // ***** Group posts *****
  2371. function handleGroupPosts (jNode) {
  2372. //...
  2373. }
  2374.  
  2375.  
  2376. // ***** Sort group members by distance from travel location, save sorting permanently *****
  2377.  
  2378. //init
  2379. let travelName = '';
  2380. if (localStorage.getItem('PR_SETTINGS:groups:members:sorting')?.match(/GROUP_JOIN_DATE_DESC|LAST_LOGIN_DESC|NEARBY_ASC/)) {
  2381. sessionStorage.setItem('PR_SETTINGS:groups:members:sorting', localStorage.getItem('PR_SETTINGS:groups:members:sorting'));
  2382. } else {
  2383. sessionStorage.setItem('PR_SETTINGS:groups:members:sorting', 'LAST_LOGIN_DESC');
  2384. }
  2385.  
  2386. function groupTravelLocation (jNode) {
  2387. let viewMode = localStorage.getItem('REgroupTravelLocation');
  2388. let toggleTravel = 'span.re-member-travel';
  2389. $('div.js-members-header div.js-dropdown button').not('.re-done').addClass('re-done').has('div:contains("Entf"), div:contains("In der"), div:contains("Nearby"), div:contains("Cercanos"), div:contains("À prox"), div:contains("Vicini"), div:contains("Por perto")').parent().append(
  2390. `<span class="re-add re-member-travel icon icon-airplane ml- pl" title="Reiseziel" role="img" aria-label="Reiseziel"><span class="pl-"></span></span>`
  2391. );
  2392. handleTravelLocation(viewMode, toggleTravel);
  2393. $('span.re-member-travel').off().on('click', function() {
  2394. let viewMode = localStorage.getItem('REgroupTravelLocation');
  2395. let toggleTravel = 'span.re-member-travel';
  2396. let refreshClick = 'ul.Container--ZCCAM button';
  2397. viewMode = (viewMode == 'travel') ? 'default' : 'travel';
  2398. localStorage.setItem('REgroupTravelLocation', viewMode);
  2399. handleTravelLocation(viewMode, toggleTravel, refreshClick);
  2400. });
  2401.  
  2402. //save sorting permanently
  2403. setTimeout(() => {
  2404. if (sessionStorage.getItem('PR_SETTINGS:groups:members:sorting')?.match(/GROUP_JOIN_DATE_DESC|LAST_LOGIN_DESC|NEARBY_ASC/)) {
  2405. localStorage.setItem('PR_SETTINGS:groups:members:sorting', sessionStorage.getItem('PR_SETTINGS:groups:members:sorting'));
  2406. }
  2407. }, 300);
  2408. }
  2409.  
  2410.  
  2411. function handleTravelLocation (viewMode, toggleTravel, refreshClick) {
  2412. if (testMode) console.log('refreshClick');
  2413. if (viewMode == 'travel') {
  2414. setTimeout(() => {
  2415. $(toggleTravel).addClass('re-selected');
  2416. }, 300);
  2417. if (xhrTravelLat && xhrTravelLong) {
  2418. $.ajax({headers: ajaxHead(), url: `/api/geocoder/private/name?lat=${xhrTravelLat}&lon=${xhrTravelLong}&lang=de`})
  2419.  
  2420. .done(function (data) {
  2421. if (data[0].name) {
  2422. travelName = data[0].name;
  2423. }
  2424. let travelEdit = '<a href="/explore/edit" class="re-edit-travel icon icon-pen pl-" style="line-height:inherit" title="Reiseziel ändern"></a>';
  2425. $(toggleTravel).find('span').html(travelName).attr('title', travelName).addClass('pl-');
  2426. $(toggleTravel).next('a.re-edit-travel').remove();
  2427. $(toggleTravel).after(travelEdit);
  2428. travelMode = true;
  2429. if (refreshClick) $(refreshClick).get(0).click();
  2430. });
  2431.  
  2432. } else {
  2433. $.ajax({headers: ajaxHead(), url: `/api/v4/locations/travel`})
  2434.  
  2435. .done(function (data) {
  2436. let travelEdit = '';
  2437. if (data.length) {
  2438. let last = data.length -1;
  2439. xhrTravelLat = data[last].lat;
  2440. xhrTravelLong = data[last].long;
  2441. localStorage.setItem('REtravelLat', xhrTravelLat);
  2442. localStorage.setItem('REtravelLong', xhrTravelLong);
  2443. travelName = data[last].name;
  2444. travelMode = true;
  2445. travelEdit = '<a href="/explore/edit" class="re-edit-travel icon icon-pen pl-" title="Reiseziel ändern"></a>';
  2446. } else {
  2447. travelMode = false;
  2448. travelEdit = '<a href="/explore/new" class="re-edit-travel">Klicken, um ein Reiseziel hinzuzufügen!</a>';
  2449. }
  2450. $(toggleTravel).find('span').html(travelName).attr('title', travelName).addClass('pl-');
  2451. $(toggleTravel).next('a.re-edit-travel').remove();
  2452. $(toggleTravel).after(travelEdit);
  2453. if (refreshClick) $(refreshClick).get(0).click();
  2454. });
  2455.  
  2456. }
  2457.  
  2458. } else {
  2459. setTimeout(() => {
  2460. $(toggleTravel).removeClass('re-selected');
  2461. }, 200);
  2462. $(toggleTravel).find('span').text('').removeAttr('title').removeClass('pl-');
  2463. $(toggleTravel).next('a.re-edit-travel').remove();
  2464. travelMode = false;
  2465. if (refreshClick) $(refreshClick).get(0).click();
  2466. }
  2467. }
  2468.  
  2469.  
  2470. // ***** Show upload date in photo viewer, link URLs *****
  2471. function handleImg (jNode) {
  2472. $(jNode).addClass('re-done');
  2473. // console.log('handleImg');
  2474.  
  2475. //upload date
  2476. let imgName = $(jNode).attr('src');
  2477. let imgNameTxt = uploadDate(`${imgName.substr(imgName.lastIndexOf('/') + 1, 8)}`);
  2478. let info = `<div class="re-add re-img-info"><a title="Upload-Datum" href="${imgName}">${imgNameTxt}</a></div>`;
  2479. $(jNode).closest('li').not(':has(.re-add)').prepend(info);
  2480.  
  2481. //URLs in picture caption
  2482. setTimeout(() => {
  2483. $('.ReactModal__Content li').has('img[src^="/img/usr/original/"]').find('p').not(':has(button)').each(function() {
  2484. let replacedText = linkify($(this).text());
  2485. $(this).html(replacedText);
  2486. });
  2487. }, 0);
  2488. }
  2489.  
  2490.  
  2491. // ***** Show upload date for single image, click to close *****
  2492. function handleSingleImg (jNode) {
  2493. $(jNode).addClass('re-done');
  2494.  
  2495. //upload date
  2496. let imgName = $(jNode).attr('src');
  2497. let imgNameTxt = uploadDate(`${imgName.substr(imgName.lastIndexOf('/') + 1, 8)}`);
  2498. let info = `<div class="re-add re-img-info re-img-single"><a title="Upload-Datum" href="${imgName}">${imgNameTxt}</a></div>`;
  2499. $(jNode).closest('div').after(info);
  2500.  
  2501. //click to close
  2502. $(jNode).closest('div').off().on('click', function() {
  2503. if ($(this).attr('style').match(/scale\(1\)/)) {
  2504. $(this).closest('main').find('button').focus()[0].click();
  2505. return false;
  2506. }
  2507. });
  2508. }
  2509.  
  2510.  
  2511. // ***** Remove refresh for slide show likes list *****
  2512. function handleSlideshowLikes (jNode) {
  2513. $(jNode).closest('div').children('button + div, button').remove();
  2514. }
  2515.  
  2516.  
  2517. // ***** Show picture info in picture rating *****
  2518. function ratingInfo (jNode) {
  2519. $('#picture-rating .re-add').remove();
  2520. $('#picture-rating button').blur();
  2521. let imgName = $('#picture-rating img').attr('src');
  2522. if (imgName) {
  2523. let imgNameTxt = `${imgName.substr(imgName.lastIndexOf('/') + 1, 5)}...`;
  2524. let imgDate = uploadDate(`${imgName.substr(imgName.lastIndexOf('/') + 1, 8)}`);
  2525. let imgNameMax = sessionStorage.getItem('REratingMax');
  2526. imgNameMax = (imgNameMax ? imgNameMax : imgNameTxt);
  2527. if (imgNameTxt >= imgNameMax) {
  2528. sessionStorage.setItem('REratingMax', imgNameTxt);
  2529. }
  2530. let color = (parseInt(imgNameTxt,16) + 1 < parseInt(imgNameMax,16)) ? 'rgb(255,0,0,.8)' : 'rgb(255,255,255,.375)';
  2531. $('#picture-rating button').has('p').after(
  2532. `<a class="re-rating-date re-add" style="color:${color}" title="Upload-Datum" href="${imgName}">${imgDate}</a>`
  2533. );
  2534. }
  2535.  
  2536. //set focus on reload/skip button
  2537. // if (!touch.matches) {
  2538. // $('#picture-rating button[class^="SecondaryButton__Element-"]').focus(); // Aktualisieren
  2539. // $('#picture-rating button[class^="TertiaryButton__Element-"]').addClass('re-rating-skip').focus(); // Überspringen
  2540. // $('#picture-rating button[class^="TertiaryButton__Element-"]').off('keydown').on('keydown', (e) => {
  2541. /* $('#picture-rating').next('div').next('div').trigger('focus').off('keyUp').on('keyUp', (e) => {
  2542. if (testMode) console.log('key: ', e.key);
  2543. if (e.key == '0') {
  2544. // $('#picture-rating button[class^="TertiaryButton__Element-"]')[0].focus().click();
  2545. $('#picture-rating button[class^="TertiaryButton__Element-"]').trigger('focus').trigger('click');
  2546. }
  2547. }); */
  2548. // }
  2549. }
  2550.  
  2551.  
  2552. // ***** Relogin after timeout *****
  2553. function reLogin (jNode) {
  2554. if (testMode) console.log('reLogin');
  2555. if ($(jNode).not('.re-add').addClass('re-add').children('span').filter(':contains("Erneut einloggen"), :contains("Log in again"), :contains("Reconnexion")').length) {
  2556. $(jNode).closest('section').parent('div').addClass('re-relogin-frame');
  2557. $(jNode).closest('section').find('div > button').hide();
  2558. $(jNode).closest('section').find('h1').text('');
  2559. $(jNode).closest('section').find('div > img').replaceWith('<div class="spinner-container"><div class="spinner"></div></div>');
  2560. $(jNode).closest('section').children('p').text('Erneut einloggen ...');
  2561. setTimeout(() => {
  2562. if (testMode) console.log('do reLogin');
  2563. location.reload();
  2564. }, 600);
  2565. } else {
  2566. $(jNode).closest('div[class^="Modal__StyledModal"]').find('section p').off().on('dblclick', function() {
  2567. $(this).closest('div[class^="Modal__StyledModal"]').remove();
  2568. $('body').attr('class', '');
  2569. });
  2570. }
  2571. }
  2572.  
  2573.  
  2574. // ***** Fetch/XHR *****
  2575.  
  2576. //switch test mode
  2577. if (typeof GM_info !== 'undefined') {
  2578. localStorage.setItem('REpostFromDeleted', `${(GM_info?.script?.name.match(/-DEV|-BETA/)) ? true : false}`);
  2579. localStorage.setItem('REtestMode', `${(GM_info?.script?.name.match(/-DEV|-BETA/)) ? true : false}`);
  2580. }
  2581.  
  2582. //init
  2583. let xhrTravelLat = localStorage.getItem('REtravelLat');
  2584. let xhrTravelLong = localStorage.getItem('REtravelLong');
  2585. let travelMode = (localStorage.getItem('REradarTravelLocation') === 'travel');
  2586. let directMsg = 0;
  2587. let resigned = false;
  2588. let xhrFull = '';
  2589. let eyecandyActive = (localStorage.getItem('REeyecandyActive') === 'true');
  2590. let filterNoEntry = (localStorage.getItem('REfilterNoEntry') === 'true');
  2591. let postFromDeleted = (localStorage.getItem('REpostFromDeleted') === 'true');
  2592. let hideNSFW = (localStorage.getItem('REhideNSFW') === 'true');
  2593. let testMode = (localStorage.getItem('REtestMode') === 'true');
  2594. const nsfwPlaceholder = '3646b0e5af396e593c723abdd3'; // '38830464d7d57357ddc67716bc'
  2595. const nsfwPlaceholderBig = '363b566fbda3af7de7f863585e';
  2596.  
  2597.  
  2598. // ***** Requests to modify *****
  2599. const modifyRequest = (url) => {
  2600. // console.log(url);
  2601.  
  2602. //profile view: 32 instead of 4 groups per scroll
  2603. if (url.match(/\/groups\?length=4/)) {
  2604. if (testMode) console.log('groups match');
  2605. url = url.replace('length=4', 'length=32');
  2606. }
  2607.  
  2608. //sort eyecandy
  2609. if (url.match(/v4\/profiles\/popular/)) {
  2610.  
  2611. //radar
  2612. if (travelMode && ! url.match(/&scrollable=false/)) {
  2613. url = url.replace('sort_criteria=LAST_LOGIN_DESC', 'sort_criteria=NEARBY_ASC');
  2614. url = url.replace(/filter%5Blocation%5D%5Blat%5D\=[-0-9\.]+/, `filter[location][lat]=${xhrTravelLat}`);
  2615. url = url.replace(/filter%5Blocation%5D%5Blong%5D\=[-0-9\.]+/, `filter[location][long]=${xhrTravelLong}`);
  2616. }
  2617.  
  2618. //discover
  2619. else if (! eyecandyActive && url.match(/&scrollable=false/)) {
  2620. url = url.replace('sort_criteria=NEARBY_ASC', 'sort_criteria=LAST_LOGIN_DESC');
  2621. }
  2622. }
  2623.  
  2624. //travel mode
  2625. if (travelMode) {
  2626. if (location.pathname.match(/^\/radar\//)) {
  2627. url = url.replace(/filter%5Blocation%5D%5Blat%5D\=[-0-9\.]+/, `filter[location][lat]=${xhrTravelLat}`);
  2628. url = url.replace(/filter%5Blocation%5D%5Blong%5D\=[-0-9\.]+/, `filter[location][long]=${xhrTravelLong}`);
  2629. }
  2630. }
  2631.  
  2632. //get travel coordinates
  2633. if (location.pathname.match(/^\/explore\//) && url.match(/v4\/profiles\?/)) {
  2634. let loc = location.pathname.match(/[-0-9\.]+/g);
  2635. xhrTravelLat = loc[0];
  2636. xhrTravelLong = loc[1];
  2637. if (testMode) console.log(loc);
  2638. localStorage.setItem('REtravelLat', xhrTravelLat);
  2639. localStorage.setItem('REtravelLong', xhrTravelLong);
  2640. }
  2641.  
  2642. //more ...
  2643.  
  2644. return url;
  2645. }
  2646.  
  2647.  
  2648. // ***** Responces to modify *****
  2649. const modifyResponse = (url, data) => {
  2650. // console.log(data);
  2651.  
  2652. //hide nsfw pictures in visitors/visits etc.
  2653. if (hideNSFW) {
  2654. if (url.match(/\/(profiles|popular|visitors|visits|conversations|contacts)\?/) && ! url.match(/v4\/hunqz/)) { // ...|groups|...
  2655. try {
  2656. if (testMode) console.log('check for nsfw');
  2657. // if (testMode) console.log(data);
  2658. // let index = 0;
  2659. for (let item of data.items) {
  2660. if (item?.preview_pic?.rating == 'EROTIC') {
  2661. if (testMode) console.log(`preview pic hidden`);
  2662. item.preview_pic.url_token = nsfwPlaceholder;
  2663. // item.preview_pic.url_token = (item.display?.large_tile) ? nsfwPlaceholderBig : nsfwPlaceholder;
  2664. }
  2665. if (item?.profile?.preview_pic?.rating == 'EROTIC') {
  2666. // if (testMode) console.log(`profile preview pic hidden`);
  2667. item.profile.preview_pic.url_token = nsfwPlaceholder;
  2668. }
  2669. if (item?.chat_partner?.preview_pic?.rating == 'EROTIC') {
  2670. // if (testMode) console.log(`profile preview pic hidden`);
  2671. item.chat_partner.preview_pic.url_token = nsfwPlaceholder;
  2672. }
  2673. // index++;
  2674. // if (testMode) console.log(index);
  2675. }
  2676. data = JSON.stringify(data)
  2677. } catch(e) {
  2678. console.log(`fetch: nsfw filter error (profiles): ${e}`);
  2679. }
  2680. }
  2681. }
  2682.  
  2683. //more ...
  2684.  
  2685. return
  2686. }
  2687.  
  2688.  
  2689. // ***** Handle Fetch *****
  2690.  
  2691. // handle response part
  2692. const realFetch = window.fetch;
  2693. window.fetch = async (url, options) => {
  2694.  
  2695. let newUrl = url;
  2696.  
  2697. // *** modify request ***
  2698. // (not for now; see handle request part below)
  2699.  
  2700. // *** send request & get response ***
  2701. let response = await realFetch(newUrl, options);
  2702. try {
  2703. void await response.clone().json();
  2704. } catch(e) {
  2705. if (testMode) console.log(`not JSON or empty reply: ${e}`);
  2706. return response;
  2707. }
  2708.  
  2709. // *** modify JSON response ***
  2710. let content = await response.text();
  2711. let data = JSON.parse(content);
  2712. modifyResponse(url, data);
  2713. content = JSON.stringify(data)
  2714.  
  2715. return new Response(content, {
  2716. status: response.status,
  2717. statusText: response.statusText,
  2718. headers: response.headers,
  2719. });
  2720. }
  2721.  
  2722. // handle request part
  2723. const firstFetch = window.fetch;
  2724. window.fetch = async (url, options) => {
  2725. let newUrl = url;
  2726. let response = {};
  2727. if (typeof url === 'string') {
  2728. newUrl = modifyRequest(url);
  2729. // if (testMode) console.log('fetch string: ' + newUrl);
  2730. response = await firstFetch(newUrl, options);
  2731. // if (testMode) console.log('response string: ', JSON.parse(JSON.stringify(response)));
  2732. // return response;
  2733. }
  2734. if (typeof url === 'object') {
  2735. let request = url;
  2736. const blob = await request.blob();
  2737. const body = blob.size > 0 ? blob : undefined;
  2738. options = {
  2739. body,
  2740. cache: request.cache,
  2741. credentials: request.credentials,
  2742. headers: request.headers,
  2743. integrity: request.integrity,
  2744. keepalive: request.keepalive,
  2745. method: request.method,
  2746. mode: request.mode,
  2747. redirect: request.redirect,
  2748. referrer: request.referrer,
  2749. referrerPolicy: request.referrerPolicy,
  2750. signal: request.signal,
  2751. }
  2752. newUrl = modifyRequest(request.url);
  2753. // if (testMode) console.log('fetch object: ' + newUrl);
  2754. response = await firstFetch(newUrl, options);
  2755. // return response;
  2756. }
  2757. return response;
  2758. }
  2759.  
  2760.  
  2761. // ***** Handle XHR *****
  2762.  
  2763. (function() {
  2764. let oldXHROpen = window.XMLHttpRequest.prototype.open;
  2765. window.XMLHttpRequest.prototype.open = function(method, url, async, user, password) {
  2766.  
  2767. if (url.match(/v4\/messages\//)) {
  2768. //...
  2769. }
  2770.  
  2771. if (url.match(/v4\/messages\/conversations\?/)) {
  2772. //url = url.replace('&length=15', '&length=120');
  2773. }
  2774.  
  2775. if (url.match(/v4\/contacts\?/)) {
  2776. if ($('#contacts-custom-tags a.ui-tag--selected span.ui-tag__label').text().trim() == '[ A-Z ]') {
  2777. url += '&sort_criteria=NAME_ASC';
  2778. url = url.replace(/&filter%5Btags%5D%5B%5D\=\d+/, ``);
  2779. }
  2780. if ($('#contacts-custom-tags a.ui-tag--selected span.ui-tag__label').text().trim() == '[ Login ]') {
  2781. url += '&sort_criteria=LAST_LOGIN_DESC';
  2782. url = url.replace(/&filter%5Btags%5D%5B%5D\=\d+/, ``);
  2783. }
  2784. if ($('#contacts-custom-tags a.ui-tag--selected span.ui-tag__label').text().trim() == '[ Online ]') {
  2785. url += '&filter[online]=true';
  2786. url = url.replace(/&filter%5Btags%5D%5B%5D\=\d+/, ``);
  2787. }
  2788. //url += '&sort_criteria=LAST_LOGIN_DESC';
  2789. //url += '&sort_criteria=NAME_ASC';
  2790. //url += '&filter[online]=true';
  2791. url = url.replace('/contacts?length=100&pick=items.*.profile&lang=de', '/contacts?length=999&pick=items.*.profile&lang=de');
  2792. }
  2793.  
  2794. if (location.pathname.match(/^\/messenger\/contacts\/name/)) {
  2795. if (url.match(/filter%5Busername%5D\=/)) {
  2796. url += '&sort_criteria=NAME_ASC';
  2797. url = url.replace(/filter%5Busername%5D\=\*/, '');
  2798. }
  2799. }
  2800.  
  2801. if (travelMode) {
  2802. if (location.pathname.match(/^\/radar\//)) {
  2803. url = url.replace(/filter%5Blocation%5D%5Blat%5D\=[-0-9\.]+/, `filter[location][lat]=${xhrTravelLat}`);
  2804. url = url.replace(/filter%5Blocation%5D%5Blong%5D\=[-0-9\.]+/, `filter[location][long]=${xhrTravelLong}`);
  2805. }
  2806. //url = url.replace(/filter%5Btravellers_filter%5D\=EXCLUDED/, '');
  2807. //url = url.replace(/filter%5Btravellers_filter%5D\=INCLUDED/, 'filter%5Btravellers_filter%5D=EXCLUDED');
  2808. //url = url.replace(/sort_criteria\=NEARBY_ASC/, 'sort_criteria=NEARBY_DESC');
  2809. }
  2810.  
  2811. if (location.pathname.match(/^\/(radar|hunqz)\//) && url.match(/filter%5Blocation%5D/)) {
  2812. let radius = $('.js-distance-radius .noUi-handle').attr('aria-valuenow');
  2813. if (radius >= 94500 && radius < 95500) { //95
  2814. url = url.replace(/filter%5Blocation%5D%5Bradius%5D\=[0-9\.]+/, `filter[location][radius]=${250000}`);
  2815. }
  2816. if (radius >= 95500 && radius < 96500) { //96
  2817. url = url.replace(/filter%5Blocation%5D%5Bradius%5D\=[0-9\.]+/, `filter[location][radius]=${500000}`);
  2818. }
  2819. if (radius >= 96500 && radius < 97500) { //97
  2820. url = url.replace(/filter%5Blocation%5D%5Bradius%5D\=[0-9\.]+/, `filter[location][radius]=${1000000}`);
  2821. }
  2822. if (radius >= 97500 && radius < 98500) { //98
  2823. url = url.replace(/filter%5Blocation%5D%5Bradius%5D\=[0-9\.]+/, `filter[location][radius]=${2000000}`);
  2824. }
  2825. if (radius >= 98500 && radius < 99500) { //99
  2826. url = url.replace(/filter%5Blocation%5D%5Bradius%5D\=[0-9\.]+/, `filter[location][radius]=${4000000}`);
  2827. }
  2828. }
  2829.  
  2830. if (url.match(/v4\/groups\//)) {
  2831. if (travelMode) {
  2832. url = url.replace(/filter%5Blocation%5D%5Blat%5D\=[-0-9\.]+/, `filter[location][lat]=${xhrTravelLat}`);
  2833. url = url.replace(/filter%5Blocation%5D%5Blong%5D\=[-0-9\.]+/, `filter[location][long]=${xhrTravelLong}`);
  2834. }
  2835. if (resigned) {
  2836. url = url.replace('statuses[]=REJECTED', 'statuses[]=NONE');
  2837. }
  2838. }
  2839.  
  2840. if (url.match(/v4\/profiles\/popular/)) {
  2841. if (travelMode && !url.match(/&scrollable=false/)) {
  2842. url = url.replace('sort_criteria=LAST_LOGIN_DESC', 'sort_criteria=NEARBY_ASC');
  2843. url = url.replace(/filter%5Blocation%5D%5Blat%5D\=[-0-9\.]+/, `filter[location][lat]=${xhrTravelLat}`);
  2844. url = url.replace(/filter%5Blocation%5D%5Blong%5D\=[-0-9\.]+/, `filter[location][long]=${xhrTravelLong}`);
  2845. }
  2846. else if (!eyecandyActive && url.match(/&scrollable=false/)) {
  2847. url = url.replace('sort_criteria=NEARBY_ASC', 'sort_criteria=LAST_LOGIN_DESC');
  2848. }
  2849. //url = url.replace('sort_criteria=LAST_LOGIN_DESC', 'sort_criteria=SIGNUP_DESC');
  2850. }
  2851.  
  2852. /* if (location.pathname.match(/^\/explore\//) && url.match(/v4\/profiles\?lang/)) {
  2853. let loc = location.pathname.match(/[-0-9\.]+/g);
  2854. xhrTravelLat = loc[0];
  2855. xhrTravelLong = loc[1];
  2856. localStorage.setItem('REtravelLat', xhrTravelLat);
  2857. localStorage.setItem('REtravelLong', xhrTravelLong);
  2858.  
  2859. //let radius = $('.js-distance-radius .noUi-handle').attr('aria-valuenow');
  2860. //url = url.replace('sort_criteria=NEARBY_ASC', `sort_criteria=LAST_LOGIN_DESC&filter[location][radius]=${radius}`);
  2861. //url = url.replace('sort_criteria=NEARBY_ASC', `sort_criteria=SIGNUP_DESC&filter[location][radius]=${radius}`);
  2862. } */
  2863.  
  2864. //FIX for hunqz activity and new
  2865. if (location.pathname.match(/^\/hunqz\/activity/) && url.match(/v4\/hunqz\/profiles\?lang/)) {
  2866. let radiusActivity = $('.js-distance-radius .noUi-handle').attr('aria-valuenow');
  2867. if (radiusActivity == '100000.0') radiusActivity = '100000000';
  2868. url = url.replace('sort_criteria=NEARBY_ASC', `sort_criteria=LAST_LOGIN_DESC&filter[location][radius]=${radiusActivity}`);
  2869. }
  2870. if (location.pathname.match(/^\/hunqz\/new/) && url.match(/v4\/hunqz\/profiles\?lang/)) {
  2871. let radiusNew = $('.js-distance-radius .noUi-handle').attr('aria-valuenow');
  2872. if (radiusNew == '100000.0') radiusNew = '100000000';
  2873. url = url.replace('sort_criteria=NEARBY_ASC', `sort_criteria=SIGNUP_DESC&filter[location][radius]=${radiusNew}`);
  2874. }
  2875.  
  2876. //include "no entry" to filter
  2877. if (filterNoEntry) {
  2878. if (url.match(/\/profiles\?/)) {
  2879. const categories = [
  2880. '&filter%5Bpersonal%5D%5Bgender_orientation%5D%5Bgender%5D%5B%5D=',
  2881. '&filter%5Bpersonal%5D%5Bgender_orientation%5D%5Borientation%5D%5B%5D=',
  2882. '&filter%5Bpersonal%5D%5Blooking_for%5D%5B%5D=',
  2883. '&filter%5Bpersonal%5D%5Bbody_type%5D%5B%5D=',
  2884. '&filter%5Bpersonal%5D%5Bbody_hair%5D%5B%5D=',
  2885. '&filter%5Bpersonal%5D%5Bhair_color%5D%5B%5D=',
  2886. '&filter%5Bpersonal%5D%5Bhair_length%5D%5B%5D=',
  2887. '&filter%5Bpersonal%5D%5Bethnicity%5D%5B%5D=',
  2888. '&filter%5Bsexual%5D%5Banal_position%5D%5B%5D=',
  2889. '&filter%5Bsexual%5D%5Bdick_size%5D%5B%5D=',
  2890. '&filter%5Bsexual%5D%5Bconcision%5D%5B%5D=',
  2891. '&filter%5Bsexual%5D%5Bsafer_sex%5D%5B%5D=',
  2892. '&filter%5Bsexual%5D%5Bfetish%5D%5B%5D=',
  2893. '&filter%5Bsexual%5D%5Bsm%5D%5B%5D=',
  2894. '&filter%5Bsexual%5D%5Bfisting%5D%5B%5D='
  2895. ]
  2896. for (let item of categories) {
  2897. url = url.replace(item, item + 'NO_ENTRY' + item)
  2898. }
  2899. }
  2900. }
  2901.  
  2902. //full profile
  2903. if (url.match(/v4\/(hunqz\/)?profiles\/[-\w]+\/full\?/)) {
  2904. this.addEventListener('load', function() {
  2905. xhrFull = JSON.parse(this.response)
  2906. if (testMode) console.log('xhrFull');
  2907. });
  2908. }
  2909.  
  2910. //show group posts of deleted users
  2911. if (postFromDeleted) {
  2912. if (url.match(/v4\/groups\/\d+\/posts\?/)) {
  2913. this.addEventListener('load', function() {
  2914. try {
  2915. this.xhr = JSON.parse(this.response)
  2916. for (let item of this.xhr.items) {
  2917. if (item.deleted) {
  2918. if (testMode) console.log(`deleted`);
  2919. delete item.deleted;
  2920. //item.edit_status = 'NO_EDIT';
  2921. item.content = `[Gelöschter Beitrag]\n${item.content}`;
  2922. }
  2923. if (item.owner.deletion_date) {
  2924. if (testMode) console.log(`deletion_date`);
  2925. delete item.owner.deletion_date;
  2926. item.content = `[Profil gelöscht]\n\n${item.content}`;
  2927. }
  2928. }
  2929. Object.defineProperty(this, 'responseText', {
  2930. writable: true
  2931. });
  2932. this.responseText = JSON.stringify(this.xhr)
  2933. } catch(e) {
  2934. }
  2935. if (testMode) console.log('postFromDeleted');
  2936. });
  2937. }
  2938. }
  2939.  
  2940. //hide nsfw pictures in radar etc.
  2941. if (hideNSFW) {
  2942. if (url.match(/\/(profiles|popular|conversations|contacts)\?/) && ! url.match(/v4\/hunqz/)) {
  2943. this.addEventListener('load', function() {
  2944. try {
  2945. this.xhr = JSON.parse(this.response);
  2946. // if (testMode) console.log(this.xhr);
  2947. for (let item of this.xhr?.items) {
  2948. if (item?.preview_pic?.rating == 'EROTIC') {
  2949. // console.log(`preview pic hidden`);
  2950. item.preview_pic.url_token = nsfwPlaceholder;
  2951. // console.log(item.preview_pic.url_token);
  2952. // item.personal.age = -item.personal.age;
  2953. // item.preview_pic.url_token = (item.display?.large_tile) ? nsfwPlaceholderBig : nsfwPlaceholder;
  2954. }
  2955. if (item?.profile?.preview_pic?.rating == 'EROTIC') {
  2956. // console.log(`profile preview pic hidden`);
  2957. item.profile.preview_pic.url_token = nsfwPlaceholder;
  2958. }
  2959. if (item?.chat_partner?.preview_pic?.rating == 'EROTIC') {
  2960. // console.log(`chat partner profile preview pic hidden`);
  2961. item.chat_partner.preview_pic.url_token = nsfwPlaceholder;
  2962. }
  2963. }
  2964. Object.defineProperty(this, 'responseText', {
  2965. writable: true
  2966. });
  2967. this.responseText = JSON.stringify(this.xhr);
  2968. if (this.responseText === undefined) console.log('xhr response undefined');
  2969. } catch(e) {
  2970. console.log(`XHR: nsfw filter error (profiles, conversations, contacts): ${e}`);
  2971. }
  2972. if (testMode) console.log('hideNSFW');
  2973. });
  2974. }
  2975. if (url.match(/\/((profiles)\/\d+|activity-stream|list)\?/)) {
  2976. this.addEventListener('load', function() {
  2977. try {
  2978. this.xhr = JSON.parse(this.response);
  2979. // console.log(this.xhr);
  2980. if (this.xhr?.id) {
  2981. // console.log(`1 profile item at top`);
  2982. if (this.xhr?.preview_pic?.rating == 'EROTIC') {
  2983. // console.log(`1 profile preview pic hidden`);
  2984. this.xhr.preview_pic.url_token = nsfwPlaceholder;
  2985. }
  2986. } else {
  2987. for (let item of this.xhr) {
  2988. if (item?.preview_pic?.rating == 'EROTIC') {
  2989. // console.log(`no item preview pic hidden`);
  2990. item.preview_pic.url_token = nsfwPlaceholder;
  2991. }
  2992. if (item?.partner?.preview_pic?.rating == 'EROTIC') {
  2993. // console.log(`no item preview pic hidden`);
  2994. item.partner.preview_pic.url_token = nsfwPlaceholder;
  2995. }
  2996. }
  2997. }
  2998. Object.defineProperty(this, 'responseText', {
  2999. writable: true
  3000. });
  3001. this.responseText = JSON.stringify(this.xhr)
  3002. } catch(e) {
  3003. console.log(`XHR: nsfw filter error (single profile, stream, list): ${e}`);
  3004. }
  3005. });
  3006. }
  3007. }
  3008.  
  3009. return oldXHROpen.apply(this, arguments);
  3010. }
  3011. })();
  3012.  
  3013.  
  3014. // ***** Run at login *****
  3015.  
  3016. //init
  3017. let commonGroupsList = [], commonGroupsLoaded = false, visitorsList = [], visitsList = [], visitorsLoaded = false, visitsLoaded = false;
  3018.  
  3019. function runAtLogin (jNode) {
  3020.  
  3021. //init common groups
  3022. groupsRefreshed = true;
  3023. commonGroupsList = [];
  3024. $.ajax({headers: ajaxHead(), url: '/api/v4/profiles/me/groups?lang=de&length=10000&pick=items.*.(id,name,display_name)'})
  3025. // $.ajax({headers: ajaxHead(), url: '/api/v4/profiles/me/groups?lang=de&length=10000&expand=items.*.(membership.*)'})
  3026. .done(function (data) {
  3027. for (let item of data.items) {
  3028. commonGroupsList.push(item);
  3029. }
  3030. commonGroupsLoaded = true;
  3031. if (testMode) console.log(commonGroupsList);
  3032.  
  3033. //get initial group name
  3034. const nameIndex = (item) => item.name == lastGroupName;
  3035. if (commonGroupsList.findIndex(nameIndex) == -1) {
  3036. if (commonGroupsList.length > 0) {
  3037. lastGroupName = commonGroupsList[0].name;
  3038. } else {
  3039. lastGroupName = '';
  3040. }
  3041. }
  3042. });
  3043.  
  3044. //visitors, visits
  3045. $.ajax({headers: ajaxHead(), url: '/api/v4/visitors?lang=de&length=10000&pick=items_limited,items.*.(name,date_visited)'})
  3046. .done(function (data) {
  3047. for (let item of data.items) {
  3048. if (data.items_limited == undefined || visitorsList.length < data.items_limited) {
  3049. visitorsList.push(item);
  3050. }
  3051. }
  3052. visitorsLoaded = true;
  3053. if (testMode) console.log(visitorsList, data.items_limited);
  3054. });
  3055. $.ajax({headers: ajaxHead(), url: '/api/v4/visits?lang=de&length=10000&pick=items_limited,items.*.(name,date_visited)'})
  3056. .done(function (data) {
  3057. for (let item of data.items) {
  3058. if (data.items_limited == undefined || visitsList.length < data.items_limited) {
  3059. visitsList.push(item);
  3060. }
  3061. }
  3062. visitsLoaded = true;
  3063. if (testMode) console.log(visitsList, data.items_limited);
  3064. });
  3065. }
  3066.  
  3067.  
  3068.  
  3069. // ***** MutationObserver *****
  3070.  
  3071. /*** Functions for accessibility tweaks ***/
  3072.  
  3073. function makeHeading(el, level) {
  3074. el.setAttribute("role", "heading");
  3075. el.setAttribute("aria-level", level);
  3076. }
  3077.  
  3078. function makeRegion(el, label) {
  3079. el.setAttribute("role", "main");
  3080. el.setAttribute("aria-label", label);
  3081. }
  3082.  
  3083. function makeButton(el, label) {
  3084. el.setAttribute("role", "button");
  3085. if (label) el.setAttribute("aria-label", label);
  3086. }
  3087.  
  3088. function makeButtonFromText(el, label) {
  3089. el.setAttribute("role", "button");
  3090. if (!label) label = '';
  3091. el.setAttribute("aria-label", `${label}${el.textContent.trim()}`);
  3092. }
  3093.  
  3094. function makeIcon(el, label) {
  3095. el.setAttribute("role", "img");
  3096. el.setAttribute("aria-label", label);
  3097. }
  3098.  
  3099. function makePresentational(el) {
  3100. el.setAttribute("role", "presentation");
  3101. }
  3102.  
  3103. function setLabel(el, label) {
  3104. el.setAttribute("aria-label", label);
  3105. }
  3106.  
  3107. function setLabelFromTitle(el, label) {
  3108. if (!label) label = '';
  3109. el.setAttribute("aria-label", `${label}${el.getAttribute('title')}`);
  3110. }
  3111.  
  3112. function setLabelFromText(el) {
  3113. el.setAttribute("aria-label", el.textContent.trim());
  3114. }
  3115.  
  3116. function setLabelFromChildText(el) {
  3117. el.parentNode.setAttribute("aria-label", el.textContent.trim());
  3118. }
  3119.  
  3120. function makeRadio(el) {
  3121. el.setAttribute("role", "radio");
  3122. el.setAttribute("aria-checked", "false");
  3123. }
  3124.  
  3125. function makeRadioChecked(el) {
  3126. el.setAttribute("role", "radio");
  3127. el.setAttribute("aria-checked", "true");
  3128. }
  3129.  
  3130.  
  3131. /*** Code to apply the tweaks when appropriate ***/
  3132.  
  3133. function applyTweaks(root, tweaks) {
  3134. for (let tweak of tweaks) {
  3135. for (let el of root.querySelectorAll(tweak.selector)) {
  3136. if (Array.isArray(tweak.tweak)) {
  3137. let [func, ...args] = tweak.tweak;
  3138. func(el, ...args);
  3139. } else {
  3140. tweak.tweak(el);
  3141. }
  3142. }
  3143. }
  3144. }
  3145.  
  3146. function initMutationObserver() {
  3147. applyTweaks(document, LOAD_TWEAKS);
  3148. applyTweaks(document, DYNAMIC_ELEMENT_ADDED_TWEAKS);
  3149. }
  3150.  
  3151. let observer = new MutationObserver(function(mutations) {
  3152. for (let mutation of mutations) {
  3153. try {
  3154. if (mutation.type === "childList") {
  3155. for (let node of mutation.addedNodes) {
  3156. if (node.nodeType != Node.ELEMENT_NODE) {
  3157. continue;
  3158. }
  3159. applyTweaks(node, DYNAMIC_ELEMENT_ADDED_TWEAKS);
  3160. // console.log('MO element node added');
  3161. }
  3162. /*for (let node of mutation.removedNodes) {
  3163. if (node.nodeType != Node.ELEMENT_NODE) {
  3164. continue;
  3165. }
  3166. applyTweaks(node, DYNAMIC_TWEAKS);
  3167. }*/
  3168. /* } else if (mutation.type === "attributes") {
  3169. if (mutation.attributeName == "class") {
  3170. applyTweaks(mutation.target, DYNAMIC_ATTR_ONLY_TWEAKS);
  3171. } */
  3172. }
  3173. } catch (e) {
  3174. // Catch exceptions for individual mutations so other mutations are still handled
  3175. console.log(`Exception while handling mutation: ${e}`);
  3176. }
  3177. }
  3178. });
  3179. observer.observe(document, {childList: true, subtree: true});
  3180.  
  3181. let observerAttr = new MutationObserver(function(mutations) {
  3182. for (let mutation of mutations) {
  3183. try {
  3184. if (mutation.type === "attributes") {
  3185. if (mutation.attributeName == "class") {
  3186. applyTweaks(mutation.target, DYNAMIC_ATTR_ONLY_TWEAKS);
  3187. // console.log('MO attr only');
  3188. }
  3189. }
  3190. if (mutation.type === "childList") {
  3191. applyTweaks(mutation.target, DYNAMIC_ANY_NODE_TWEAKS);
  3192. // console.log('MO any node');
  3193. }
  3194. } catch (e) {
  3195. // Catch exceptions for individual mutations so other mutations are still handled
  3196. console.log(`Exception while handling mutation: ${e}`);
  3197. }
  3198. }
  3199. });
  3200. observerAttr.observe(document, {childList: true, subtree: true, attributes: true, attributeFilter: ["class"]});
  3201.  
  3202.  
  3203. /*** Define the actual tweaks ***/
  3204.  
  3205. // Tweaks to be applied on load
  3206. const LOAD_TWEAKS = [
  3207. ]
  3208.  
  3209. // Tweaks to be applied whenever an element node is added
  3210. const DYNAMIC_ELEMENT_ADDED_TWEAKS = [
  3211.  
  3212. //RomeoEnhancer (see also waitForKeyElements below)
  3213. {selector: '#search input[value]', tweak: handleSearch},
  3214. {selector: ':is(.layer-left-navigation, header) li', tweak: handleMainMenu},
  3215. {selector: 'section.js-main-stage ul[class^="Tabbed-nav-"] li.is-selected, .js-navigation nav > a, #eyecandy-results', tweak: handleTabMenu},
  3216. {selector: '.js-top-right-navigation a:not(.re-done)', tweak: handleTopRightMenu},
  3217. {selector: 'div.js-filter-button button', tweak: handleBookmark},
  3218. {selector: '#offcanvas-nav span[data-cta="hidePlus"]:not(.re-done)', tweak: handleSettingsDisplay},
  3219. {selector: '#offcanvas-nav input[value="TILES_SMALL"]:not(.re-done)', tweak: handleColorSwitch},
  3220. {selector: '#messenger div:is(.js-correspondence, .js-header) div.reactView div > a > p', tweak: showLoginLocation},
  3221. {selector: '.js-profile-footprints h1', tweak: handleFootprints},
  3222. {selector: '.js-quick-filter .js-anal-position > div', tweak: handleFilterNoEntry},
  3223. {selector: '#messenger div.js-chat .reactView a[href^="/messenger/chat"]', tweak: handleMessage},
  3224. {selector: '#messages-list p a', tweak: handleMessageLink},
  3225. {selector: '#messenger div.js-contacts .reactView a[href^="/messenger/contacts"]', tweak: handleContacts},
  3226. {selector: '#contacts-custom-tags .js-remove', tweak: handleTagRemove},
  3227. // {selector: '#messenger a[aria-current="page"]', tweak: handleTabRefresh},
  3228. {selector: '#blog-posts .swiper-slide a:not(.re-done)', tweak: handleBlogLinks},
  3229. {selector: ':is(div.js-wrapper, div.js-admins, #group-preview) a.js-contact', tweak: handleContactStrip},
  3230. {selector: 'a[href^="/group/"] div[class*="Tile__BaseTile-"]:not(.re-done)', tweak: handleGroupTiles},
  3231. {selector: '.js-wrapper a[href^="/eyecandy"] h1', tweak: handleDiscoverEyecandy},
  3232. {selector: ':is(.BIG, .SMALL, .LIST) div > span[class^="sc-"]', tweak: handleTiles},
  3233. // {selector: ':is(.BIG, .SMALL, .LIST) svg + p', tweak: handleTiles},
  3234. {selector: 'div.stream__content a.listitem__body .js-username p, div.stream__content div.js-list', tweak: handleStream},
  3235. {selector: 'section.profile__stats section > p[class^="BaseText"], section.profile__stats ol', tweak: handleProfile},
  3236. {selector: 'div[class*="SidebarContentContainer-"] div[class*="Main-"] > div', tweak: refreshPostsList},
  3237. {selector: '#manage div[class^="FilterBar__Container"] > div > div', tweak: groupManageMode},
  3238. {selector: 'div.js-members-header div.js-dropdown button', tweak: groupTravelLocation},
  3239. {selector: 'label path[d^="M6 8c-1"]:not(.re-done)', tweak: handleTravelLocationList},
  3240. // {selector: '#liked-by-grid', tweak: handleSlideshowLikes},
  3241. // {selector: '#picture-rating img, #picture-rating p[class^="Text-sc-"]', tweak: ratingInfo},
  3242. {selector: '#picture-rating img, #picture-rating button[class^="TertiaryButton__Element-"], #picture-rating p[class^="Text-sc-"]', tweak: ratingInfo},
  3243. {selector: 'section[class^="PopupWindow__Content"] button[class^="PrimaryButton__"]', tweak: reLogin},
  3244.  
  3245. //accessibility: navigation items with badges
  3246. {selector: '.icon-search', tweak: [makeIcon, "Suchen"]},
  3247. {selector: '.icon-visitor', tweak: [makeIcon, "Besucher"]},
  3248. {selector: '.icon-chat', tweak: [makeIcon, "Messages"]},
  3249. {selector: '.icon-notification-bell', tweak: [makeIcon, "Activity Stream"]},
  3250. {selector: '.ui-status--online', tweak: [makeIcon, "Online"]},
  3251. {selector: '.ui-status--date', tweak: [makeIcon, "Date"]},
  3252. {selector: '.ui-status--sex', tweak: [makeIcon, "Now"]},
  3253. {selector: '.icon-airplane', tweak: [makeIcon, "Travel"]},
  3254. {selector: '.icon-save-contact', tweak: [makeIcon, "Kontakte"]},
  3255. {selector: '.js-nav-item .icon-group-members', tweak: [makeIcon, "Meine Gruppen"]},
  3256.  
  3257. //accessibility: profile screen
  3258. {selector: '.profile--romeo', tweak: [makeRegion, "Romeo-Profil"]},
  3259. {selector: '.profile--hunqz', tweak: [makeRegion, "Hunqz-Profil"]},
  3260. {selector: '.icon-add-footprint', tweak: [makeButton, "Fußtaps vergeben"]},
  3261. {selector: '.js-remove-footprint', tweak: [setLabelFromTitle]},
  3262. {selector: '.js-quickshare-trigger', tweak: [setLabel, "QuickShare-Album teilen"]},
  3263. {selector: '.icon-default-contact', tweak: [makeIcon, "Nutzer speichern"]},
  3264. {selector: '[id^="profile-"] .icon-save-contact', tweak: [makeIcon, "Nutzer speichern"]},
  3265. {selector: '#visits > div', tweak: [makeRegion, "Besucher"]},
  3266. {selector: '.icon-back', tweak: [makeIcon, "Zurück"]},
  3267. {selector: '.icon-next', tweak: [makeIcon, "Weiter"]},
  3268. {selector: '.icon-open-menu-ver', tweak: [makeIcon, "Menü öffnen"]},
  3269. {selector: '.icon-open-stats', tweak: [makeIcon, "Profildetails"]},
  3270. {selector: '.js-attach-pictures', tweak: [setLabel, "Bilder anhängen"]},
  3271. {selector: '.js-submit', tweak: [setLabel, "Abschicken"]},
  3272. {selector: 'button[class^="Close--"], button.js-close-button, .js-close-icon button', tweak: [setLabel, "Schließen"]},
  3273. {selector: '.js-close-spotlight', tweak: [makeButton, "Schließen"]},
  3274. {selector: '.js-hide.js-plus', tweak: [setLabel, "Profilbesuch verstecken"]},
  3275. {selector: '.profile-section .reactView p[class^="BodyText"]', tweak: [makeHeading, "3"]},
  3276. {selector: '.top-info-header p[class^="BodyText"]', tweak: [makeHeading, "1"]},
  3277. {selector: 'button[class^="CollapsibleSection"] > p', tweak: [setLabelFromChildText]},
  3278.  
  3279. //accessibility: other
  3280. {selector: '.messages-send__select-button.messages-button-react-view', tweak: [setLabel, "Templates, Standort, Bilder senden"]},
  3281. {selector: ':not(.js-nav-item) > .icon-group-members', tweak: [makeIcon, "Gruppe"]},
  3282. {selector: '.js-settings-privacy div[class*="Radio-"]', tweak: [makeRadio]},
  3283. {selector: '.js-settings-privacy div[class*="Selected-"] div[class*="Radio-"]', tweak: [makeRadioChecked]},
  3284. ]
  3285.  
  3286. // Tweaks to be applied on any node changes
  3287. const DYNAMIC_ANY_NODE_TWEAKS = [
  3288.  
  3289. //RomeoEnhancer
  3290. {selector: '#offcanvas-nav main div > p[class^="MiniText"]:not(.re-done)', tweak: handleVersion},
  3291. {selector: 'nav.js-navigation header', tweak: handleTabMenuNew},
  3292. {selector: '.js-chat div[class^="Box"] + div > p[class^="SpecialText"]:not(.re-done)', tweak: previewMessage},
  3293. {selector: '#messages-list p[class^="SpecialText"]:not(.re-done)', tweak: handleMessageScroll},
  3294. // {selector: '#messenger a[aria-current="page"]', tweak: handleTabRefresh},
  3295. {selector: 'div.js-correspondence div.js-header-region div[class*="ContextMenu__"] ul', tweak: threadOptionsMenu},
  3296. {selector: 'nav:has(#my-groups-list):not(.re-groups-def)', tweak: recentPosts},
  3297. {selector: ':is(.js-post-list, .js-post) .js-date span:not(.re-done)', tweak: handleGroupPostDate},
  3298. {selector: 'main > ul > li > img[src^="/img/usr/original/"]:not(.re-done)', tweak: handleImg},
  3299. {selector: '.ReactModal__Content main div > img[src^="/img/usr/original/"]:not(.re-done)', tweak: handleSingleImg},
  3300. ]
  3301.  
  3302. // Tweaks to be applied on attribute changes
  3303. const DYNAMIC_ATTR_ONLY_TWEAKS = [
  3304.  
  3305. //RomeoEnhancer
  3306. // {selector: 'div.swiper-slide.swiper-slide-active div.swiper-zoom-container, #metadata-bar p', tweak: imgInfo},
  3307. ]
  3308.  
  3309. initMutationObserver();
  3310.  
  3311.  
  3312.  
  3313. // ***** waitForKeyElements (for nodes not handled by MutationObserver) *****
  3314.  
  3315. waitForKeyElements ('div#marionette.is-logged-in', runAtLogin, true);
  3316. waitForKeyElements ('div.js-chat .js-scrollable div[class="js-paging-spinner spinner-container l-fancy"]', fixScroll, true);

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址