Advertisement
Kartom

Romeo Additions userscript.js

Aug 22nd, 2021
1,423
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. // ==UserScript==
  2. // @name           Romeo Additions
  3. // @namespace      https://greasyfork.org/en/users/723211-ray/
  4. // @version        2.2
  5. // @description    Allows to hide users, display their information on tiles, and enhances the Radar.
  6. // @description:de Ermöglicht das Verstecken von Benutzern, die Anzeige ihrer Details auf Kacheln, und verbessert den Radar.
  7. // @author         -Ray-, Djamana
  8. // @include        *://*.romeo.com/*
  9. // @grant          GM_addStyle
  10. // @require        https://code.jquery.com/git/jquery-git.slim.min.js
  11. // @_require        https://code.jquery.com/jquery-3.6.0.slim.min.js
  12. // ==/UserScript==
  13.  
  14. // ==== Dependencies ====
  15.  
  16. /*! waitForKeyElements | https://gist.github.com/BrockA/2625891 */
  17. /*--- waitForKeyElements():  A utility function, for Greasemonkey scripts,
  18.     that detects and handles AJAXed content.
  19.     Usage example:
  20.         waitForKeyElements (
  21.             "div.comments"
  22.             , commentCallbackFunction
  23.         );
  24.         //--- Page-specific function to do what we want when the node is found.
  25.         function commentCallbackFunction (jNode) {
  26.             jNode.text ("This comment changed by waitForKeyElements().");
  27.         }
  28.     IMPORTANT: This function requires your script to have loaded jQuery.
  29. */
  30. function waitForKeyElements(
  31.     selectorTxt,    /* Required: The jQuery selector string that
  32.                         specifies the desired element(s).
  33.                     */
  34.     actionFunction, /* Required: The code to run when elements are
  35.                         found. It is passed a jNode to the matched
  36.                         element.
  37.                     */
  38.     bWaitOnce,      /* Optional: If false, will continue to scan for
  39.                         new elements even after the first match is
  40.                         found.
  41.                     */
  42.     iframeSelector  /* Optional: If set, identifies the iframe to
  43.                         search.
  44.                     */
  45. ) {
  46.     var targetNodes, btargetsFound;
  47.  
  48.     if (typeof iframeSelector == "undefined")
  49.         targetNodes = $(selectorTxt);
  50.     else
  51.         targetNodes = $(iframeSelector).contents()
  52.             .find(selectorTxt);
  53.  
  54.     if (targetNodes && targetNodes.length > 0) {
  55.         btargetsFound = true;
  56.         /*--- Found target node(s).  Go through each and act if they
  57.             are new.
  58.         */
  59.         targetNodes.each(function () {
  60.             var jThis = $(this);
  61.             var alreadyFound = jThis.data('alreadyFound') || false;
  62.  
  63.             if (!alreadyFound) {
  64.                 //--- Call the payload function.
  65.                 var cancelFound = actionFunction(jThis);
  66.                 if (cancelFound)
  67.                     btargetsFound = false;
  68.                 else
  69.                     jThis.data('alreadyFound', true);
  70.             }
  71.         });
  72.     }
  73.     else {
  74.         btargetsFound = false;
  75.     }
  76.  
  77.     //--- Get the timer-control variable for this selector.
  78.     var controlObj = waitForKeyElements.controlObj || {};
  79.     var controlKey = selectorTxt.replace(/[^\w]/g, "_");
  80.     var timeControl = controlObj[controlKey];
  81.  
  82.     //--- Now set or clear the timer as appropriate.
  83.     if (btargetsFound && bWaitOnce && timeControl) {
  84.         //--- The only condition where we need to clear the timer.
  85.         clearInterval(timeControl);
  86.         delete controlObj[controlKey]
  87.     }
  88.     else {
  89.         //--- Set a timer, if needed.
  90.         if (!timeControl) {
  91.             timeControl = setInterval(function () {
  92.                 waitForKeyElements(selectorTxt,
  93.                     actionFunction,
  94.                     bWaitOnce,
  95.                     iframeSelector
  96.                 );
  97.             },
  98.                 300
  99.             );
  100.             controlObj[controlKey] = timeControl;
  101.         }
  102.     }
  103.     waitForKeyElements.controlObj = controlObj;
  104. }
  105.  
  106. // ==== CSS ====
  107.  
  108. GM_addStyle(`
  109. #visits > .layer__container--wider { width:unset; max-width:1227px; }
  110. div[class*='tile--loading--'] .tile__image { background-image:url(/assets/05c2dc53b86dcd7abdb1d8a50346876b.svg); }
  111.  
  112. .tile__bar { position:absolute; bottom:0; right:0; visibility:hidden; }
  113. .tile__bar_action { background:rgba(0,0,0,0.4); backdrop-filter: blur(3px); display: inline-block; color:white; margin-left: 1px; padding: 0.25rem 0.45rem; }
  114. .tile__bar_action:hover { background-color:#00A3E4; }
  115. .tile__bar_action:active { background-color:#06648B; }
  116. .tile__link:hover .tile__bar { visibility:visible; }
  117. `);
  118.  
  119. // ==== Script ====
  120.  
  121. (function () {
  122.     'use strict';
  123.     proxyXhr();
  124. })();
  125.  
  126. // ---- Language ----
  127.  
  128. const _strings = {
  129.     "display": {
  130.         "de": "Anzeige",
  131.         "en": "Display"
  132.     },
  133.     "enhancedTiles": {
  134.         "de": "Erweiterte Kacheln",
  135.         "en": "Enhanced tiles"
  136.     },
  137.     "enhancedTilesDesc": {
  138.         "de": "Zeige alle Benutzerdetails auf den Kacheln. Im Radar wird dies Benutzer als 'Plus'-Abonnenten mit großen Kacheln darstellen.",
  139.         "en": "Shows all user details on tiles. The radar will claim users to be 'Plus' subscribers in large tiles."
  140.     },
  141.     "extensionTitle": {
  142.         "en": "Romeo Additions"
  143.     },
  144.     "hiddenUsers": {
  145.         "de": "Ausgeblendete Benutzer",
  146.         "en": "Hidden users"
  147.     },
  148.     "hideUser": {
  149.         "de": "Benutzer ausblenden",
  150.         "en": "Hide user"
  151.     },
  152.     "maxAge": {
  153.         "de": "Maximales Alter",
  154.         "en": "Maximal age"
  155.     },
  156.     "minAge": {
  157.         "de": "Minimales Alter",
  158.         "en": "Minimal age"
  159.     },
  160.     "viewFullImage": {
  161.         "de": "Bild vergrößern",
  162.         "en": "View full image"
  163.     },
  164. }
  165.  
  166. function getString(key) {
  167.     const lang = document.documentElement.getAttribute("lang") || "en";
  168.     const translations = _strings[key];
  169.     if (translations)
  170.         return translations[lang] || translations["en"] || "%" + key + "%";
  171.     return "%" + key + "%";
  172. }
  173.  
  174. // ---- Settings ----
  175.  
  176. const settingNs = "RA_SETTINGS:";
  177.  
  178. function getEnhancedTiles() {
  179.     return localStorage.getItem(settingNs + "enhancedTiles") == "true";
  180. }
  181.  
  182. function getHiddenMaxAge() {
  183.     return parseInt(localStorage.getItem(settingNs + "hiddenMaxAge")) || 99;
  184. }
  185.  
  186. function getHiddenMinAge() {
  187.     return parseInt(localStorage.getItem(settingNs + "hiddenMinAge")) || 18;
  188. }
  189.  
  190. function getHiddenUsers() {
  191.     return JSON.parse(localStorage.getItem(settingNs + "hiddenUsers")) || [];
  192. }
  193.  
  194. function setEnhancedTiles(value) {
  195.     localStorage.setItem(settingNs + "enhancedTiles", value);
  196. }
  197.  
  198. function setHiddenMaxAge(value) {
  199.     localStorage.setItem(settingNs + "hiddenMaxAge", value);
  200. }
  201.  
  202. function setHiddenMinAge(value) {
  203.     localStorage.setItem(settingNs + "hiddenMinAge", value);
  204. }
  205.  
  206. function setUserHidden(username, hide) {
  207.     let hiddenUsers = getHiddenUsers();
  208.     if (hide) {
  209.         if (hiddenUsers.length < hiddenUsers.push(username)) {
  210.             hiddenUsers.sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()));
  211.             localStorage.setItem(settingNs + "hiddenUsers", JSON.stringify(hiddenUsers));
  212.         }
  213.     } else {
  214.         const prevLength = hiddenUsers.length;
  215.         hiddenUsers = hiddenUsers.filter(e => e != username);
  216.         if (prevLength > hiddenUsers.length)
  217.             localStorage.setItem(settingNs + "hiddenUsers", JSON.stringify(hiddenUsers));
  218.     }
  219. }
  220.  
  221. // ---- XHR ----
  222.  
  223. function ReMaskDash(RE_WithDashes) {
  224.     return RE_WithDashes
  225.         .replaceAll("/","\\/")
  226. }
  227.  
  228. function isApiRequest(url, verb) {
  229.     // Request must start with "/api/v?/" or "/api/+/" followed by the given verb.
  230.     //const matches = url.match(/\/api\/(v[0-9]|\+)\//);
  231.     const matches = url.match(ReMaskDash("/api/(v[0-9]|\\+)/"));
  232.     if (matches && matches.length)
  233.         return url.startsWith(verb, matches[0].length);
  234.     return false;
  235. }
  236.  
  237. function filterUser(user, hiddenMaxAge, hiddenMinAge, hiddenNames) {
  238.     return user.personal.age >= hiddenMinAge
  239.         && user.personal.age <= hiddenMaxAge
  240.         && !hiddenNames.includes(user.name)
  241. }
  242.  
  243. function proxyXhr() {
  244.     // Intercept XHR queries and replies by hooking the XHR open method.
  245.     const realOpen = window.XMLHttpRequest.prototype.open;
  246.     window.XMLHttpRequest.prototype.open = function (method, url, async, user, password) {
  247.         this.addEventListener("load", () => {
  248.             //console.log("[RA] XHR reply: method=" + method + ", url=" + url);
  249.             try {
  250.                 // Parse data.
  251.                 const isString = typeof this.response === "string";
  252.                 reply = isString ?
  253.                     JSON.parse(this.response) :
  254.                                this.response;
  255.  
  256.                 // Modify interesting data.
  257.                 if ( isApiRequest(url, "notifications") ) {
  258.                     reply = xhrHideNotifications(reply);
  259.                 }
  260.                 if (
  261.                      isApiRequest(url, "profiles") ||
  262.                      isApiRequest(url, "visitors") ||
  263.                      isApiRequest(url, "visits")
  264.                    ) {
  265.                     reply = xhrRestorePlusVisit( reply );
  266.                     reply = xhrEnhanceUsers    ( reply );
  267.  
  268.                 }
  269.                 if ( isApiRequest(url, "session") ) {
  270.                     reply = xhrPoorMensPlus( reply );
  271.                 }
  272.  
  273.  
  274.                 // Write back possibly modified data.
  275.                 Object.defineProperty(this, "responseText", { writable: true });
  276.                 this.responseText = isString ? JSON.stringify(reply) : reply;
  277.             } catch (e) {
  278.                 //console.log("[RA] XHR handler failed: " + e)
  279.             }
  280.         });
  281.  
  282.         // Forward to client.
  283.         return realOpen.apply(this, arguments);
  284.     }
  285. }
  286.  
  287. function xhrHideNotifications(reply) {
  288.     // Remove manually hidden users.
  289.     const hiddenMaxAge = getHiddenMaxAge();
  290.     const hiddenMinAge = getHiddenMinAge();
  291.     const hiddenNames = getHiddenUsers();
  292.     return reply.filter(x => filterUser(x.partner, hiddenMaxAge, hiddenMinAge, hiddenNames));
  293. }
  294.  
  295. function xhrEnhanceUsers(reply) {
  296.     // Remove manually hidden users.
  297.     const enhancedTiles = getEnhancedTiles();
  298.     const hiddenMaxAge = getHiddenMaxAge();
  299.     const hiddenMinAge = getHiddenMinAge();
  300.     const hiddenNames = getHiddenUsers();
  301.     let newItems = [];
  302.     for (let item of reply.items) {
  303.         if (filterUser(item, hiddenMaxAge, hiddenMinAge, hiddenNames)) {
  304.             // Show as "large tiles" to display user details everywhere.
  305.             if (enhancedTiles)
  306.                 item.display.large_tile = true;
  307.             newItems.push(item);
  308.         }
  309.     }
  310.     reply.items = newItems;
  311.     return reply;
  312. }
  313.  
  314.  
  315. function xhrPoorMensPlus(reply) {
  316.     // Cosmetic patch #1
  317.     if (!reply.is_plus) {
  318.         reply.is_plus = true
  319.         reply.is_free_plus = true // maybe not needed
  320.         reply.payment_group = "PLUS"
  321.     }
  322.     // Cosmetic patch #2
  323.     if (reply.inferface) {
  324.         reply.show_plus_badge = true // maybe not needed
  325.         reply.show_ads = false  // maybe not needed
  326.     }
  327.     // Cosmetic patch #3
  328.     if (reply.show_plus_badge) {
  329.         reply.show_plus_badge = true // maybe not needed
  330.     }
  331.     return reply;
  332. }
  333. function xhrRestorePlusVisit(reply) {
  334.     // Restore PLUS-visible visitors.
  335.     reply.items_limited = 0;
  336.     return reply;
  337. }
  338.  
  339. // ---- Tile UI ----
  340.  
  341. waitForKeyElements(
  342.     "a.tile__link, " +
  343.     "div.js-profiles a.listresult, " +
  344.     "div.js-wrapper a.tile__link > div.tile__image, " +
  345.     "#visits a.listresult", jNode => {
  346.         // Determine tile properties.
  347.         const tile = jNode.parent(".tile");
  348.         const tileLink = tile.children(".tile__link").first();
  349.         if (!tileLink) // ignore placeholders
  350.             return;
  351.         // Add full headline as tooltip.
  352.         const tileInfo = tileLink.children(".tile__info").first();
  353.         const tileHeadline = tileInfo.children(".tile__headline").first();
  354.         tileHeadline.attr("title", tileHeadline.text());
  355.         // Add action bar.
  356.         const tileImage = tileLink.children(".tile__image").first();
  357.         const username = tileImage.attr("aria-label");
  358.         const tileBar = $("<div class='tile__bar'></div>").appendTo(tileLink);
  359.         addShowImageAction(tileBar, tileImage);
  360.         addHideUserAction(tileBar, tile, username);
  361.     });
  362.  
  363. function addShowImageAction(tileBar, tileImage) {
  364.     const style = tileImage.attr("style");
  365.     if (!style)
  366.         return;
  367.     const url = style.substring(style.lastIndexOf("/") + 1, style.lastIndexOf(")"));
  368.     if (url.endsWith(".svg")) // ignore "no photo" placeholders
  369.         return;
  370.     const origUrl = "/img/usr/original/0x0/" + url;
  371.     $("<a class='tile__bar_action' href='" + origUrl + "' title='" + getString("viewFullImage") + "'><span class='icon icon-picture'></a>")
  372.         .on("click", e => {
  373.             e.preventDefault();
  374.             window.open(origUrl, "_blank");
  375.         })
  376.         .appendTo(tileBar);
  377. }
  378.  
  379. function addHideUserAction(tileBar, tile, username) {
  380.     $("<a class='tile__bar_action' href='#' title='" + getString("hideUser") + "'><span class='icon icon-hide-visit'></a>")
  381.         .on("click", e => {
  382.             e.preventDefault();
  383.             setUserHidden(username, true);
  384.             tile.css("display", "none");
  385.         })
  386.         .appendTo(tileBar);
  387. }
  388.  
  389. // ---- Settings UI ----
  390.  
  391. waitForKeyElements("li.js-settings > div.accordion > ul", jNode => {
  392.     let itemClass = jNode.find("a").attr("class");
  393.     $("<li><div><a class='" + itemClass + "'>" + getString("extensionTitle") + "</a></div></li>")
  394.         .on("click", e => {
  395.             // Force open the setting pane and clear any existing contents.
  396.             $("#offcanvas-nav > .js-layer-content").addClass("is-open");
  397.             const pane = $(".js-side-content");
  398.             pane.empty();
  399.             // Add pane and list.
  400.             pane.append(`
  401. <div class='layout layout--vertical layout--consume'>
  402.     <div class='layout-item layout-item--consume layout layout--vertical'>
  403.  
  404.         <div class='layout-item settings__navigation p l-hidden-sm'>
  405.             <div class='js-title typo-section-navigation'>` + getString("extensionTitle") + `</div>
  406.         </div>
  407.  
  408.         <div class='layout-item layout-item--consume'>
  409.             <div class='js-content js-scrollable fit scrollable'>
  410.                 <div class="p">
  411.                     <div class="settings__key">
  412.                         <div class="layout layout--v-center">
  413.                             <div class="layout-item [ 6/12--sm ]">
  414.                                 <span>` + getString("enhancedTiles") + `</span>
  415.                             </div>
  416.                             <div class="layout-item [ 6/12--sm ]">
  417.                                 <div class="js-toggle-show-headlines pull-right">
  418.                                     <div>
  419.                                         <span class="ui-toggle ui-toggle--default ui-toggle--right">
  420.                                             <input class="ui-toggle__input" type="checkbox" id="ra_enhancedTiles">
  421.                                             <label class="ui-toggle__label" for="ra_enhancedTiles" style="touch-action: pan-y; user-select: none; -webkit-user-drag: none; -webkit-tap-highlight-color: rgba(0, 0, 0, 0);"></label>
  422.                                         </span>
  423.                                     </div>
  424.                                 </div>
  425.                             </div>
  426.                         </div>
  427.                         <div>
  428.                             <div class="settings__description">` + getString("enhancedTilesDesc") + `</div>
  429.                         </div>
  430.                     </div>
  431.                     <div class="settings__key">
  432.                         <div>
  433.                             <span>` + getString("hiddenUsers") + `</span>
  434.                         </div>
  435.                         <div class="separator separator--alt separator--narrow [ mb ] "></div>
  436.                         <div class="settings__key">
  437.                             <div class="layout layout--v-center">
  438.                                 <div class="layout-item [ 6/12--sm ]">
  439.                                     <span>` + getString("minAge") + `</span>
  440.                                 </div>
  441.                                 <div class="layout-item [ 6/12--sm ]">
  442.                                     <input class="input input--block" id="ra_hiddenMinAge" type="number" min="18" max="99"/>
  443.                                 </div>
  444.                             </div>
  445.                         </div>
  446.                         <div class="settings__key">
  447.                             <div class="layout layout--v-center">
  448.                                 <div class="layout-item [ 6/12--sm ]">
  449.                                     <span>` + getString("maxAge") + `</span>
  450.                                 </div>
  451.                                 <div class="layout-item [ 6/12--sm ]">
  452.                                     <input class="input input--block" id="ra_hiddenMaxAge" type="number" min="18" max="99"/>
  453.                                 </div>
  454.                             </div>
  455.                         </div>
  456.                         <div class="settings__key">
  457.                             <div class="js-grid-stats-selector">
  458.                                 <div>
  459.                                     <ul class="js-list tags-list tags-list--centered" id="ra_hiddenUsers"/>
  460.                                 </div>
  461.                             </div>
  462.                         </div>
  463.                     </div>
  464.                 </div>
  465.             </div>
  466.         </div>
  467.     </div>
  468. </div>`);
  469.             // Handle enhanced user tiles.
  470.             let inEnhancedTiles = $("#ra_enhancedTiles");
  471.             inEnhancedTiles.prop("checked", getEnhancedTiles());
  472.             inEnhancedTiles.on("change", e => {
  473.                 setEnhancedTiles(e.target.checked);
  474.             });
  475.             // Handle hidden age.
  476.             let minAge = getHiddenMinAge();
  477.             let maxAge = getHiddenMaxAge();
  478.             let inMinAge = $("#ra_hiddenMinAge");
  479.             let inMaxAge = $("#ra_hiddenMaxAge");
  480.             inMinAge.val(minAge);
  481.             inMaxAge.val(maxAge);
  482.             inMinAge.on("change", e => {
  483.                 minAge = parseInt(e.target.value);
  484.                 setHiddenMinAge(minAge);
  485.                 if (minAge > maxAge) {
  486.                     maxAge = minAge;
  487.                     setHiddenMaxAge(maxAge);
  488.                     inMaxAge.val(maxAge);
  489.                 }
  490.             });
  491.             inMaxAge.on("change", e => {
  492.                 maxAge = parseInt(e.target.value);
  493.                 setHiddenMaxAge(maxAge);
  494.                 if (maxAge < minAge) {
  495.                     minAge = maxAge;
  496.                     setHiddenMinAge(minAge);
  497.                     inMinAge.val(minAge);
  498.                 }
  499.             });
  500.             // Handle hidden user list.
  501.             const ul = $("#ra_hiddenUsers");
  502.             for (const item of getHiddenUsers()) {
  503.                 const li = $("<li class='tags-list__item'/>").appendTo(ul);
  504.                 $("<a class='js-tag ui-tag ui-tag--removable ui-tag--selected' href='#'><span class='ui-tag__label'>" + item + "</span></a>")
  505.                     .on("click", e => {
  506.                         setUserHidden(e.target.innerHTML, false);
  507.                         $(e.target).closest(".tags-list__item").css("display", "none");
  508.                     })
  509.                     .appendTo(li);
  510.             };
  511.         })
  512.         .appendTo(jNode);
  513. });
  514.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement