您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Allows to hide users, display their information on tiles, and enhances the Radar.
当前为
// ==UserScript== // @name Romeo Additions // @name:de Romeo Additions // @namespace https://greasyfork.org/en/users/723211-ray/ // @version 2.12 // @description Allows to hide users, display their information on tiles, and enhances the Radar. // @description:de Ermöglicht das Verstecken von Benutzern, die Anzeige ihrer Details auf Kacheln, und verbessert den Radar. // @author -Ray-, Djamana // @include *://*.romeo.com/* // @grant GM_addStyle // @require https://code.jquery.com/jquery-3.6.0.slim.min.js // @require https://greasyfork.org/scripts/383527-wait-for-key-elements/code/Wait_for_key_elements.js // @license MIT // ==/UserScript== // ==== CSS ==== GM_addStyle(` #visits > .layer__container--wider { width:unset; max-width:1227px; } .tile__bar { position:absolute; left:0; top:0; visibility:hidden; } .tile__bar_action { background:rgba(0,0,0,0.4); backdrop-filter:blur(4px); display:inline-block; color:white; margin-right:1px; margin-bottom:1px; padding:0.5rem; } .tile__bar_action:hover { background-color:#00A3E4; } .tile__bar_action:active { background-color:#06648B; } .tile:hover .tile__bar { visibility:visible; } .js-romeo-badge { display:none; } /* hide Plus user icon (it is faked for enhanced tiles */ #messenger div[class*='TruncateBlock__Content-sc-'] { -webkit-line-clamp: unset; } /* show full messages */ `); // ==== Script ==== (function () { 'use strict'; proxyXhr(); })(); // ---- Language ---- const _strings = { "display": { "de": "Anzeige", "en": "Display" }, "enhancedTiles": { "de": "Erweiterte Kacheln", "en": "Enhanced tiles" }, "enhancedTilesDesc": { "de": "Zeige alle Benutzerdetails auf den Kacheln. Im Radar wird dies Benutzer mit großen Kacheln darstellen.", "en": "Shows all user details on tiles. The radar will display users with large tiles." }, "extensionTitle": { "en": "Romeo Additions" }, "hiddenUsers": { "de": "Ausgeblendete Benutzer", "en": "Hidden users" }, "hideActivities": { "de": "Auch Activities verstecken", "en": "Also hide activities" }, "hideActivitiesDesc": { "de": "Versteckt ausgeblendete Benutzer auch im Activity Stream.", "en": "Removes hidden users even in the activity stream." }, "hideMessages": { "de": "Auch Nachrichten verstecken", "en": "Also hide messages" }, "hideMessagesDesc": { "de": "Versteckt ausgeblendete Benutzer auch in der Nachrichtenliste.", "en": "Removes hidden users even in the message list." }, "hideUser": { "de": "Benutzer ausblenden", "en": "Hide user" }, "maxAge": { "de": "Maximales Alter", "en": "Maximal age" }, "minAge": { "de": "Minimales Alter", "en": "Minimal age" }, "viewFullImage": { "de": "Bild vergrößern", "en": "View full image" }, } function getString(key) { const lang = document.documentElement.getAttribute("lang") || "en"; const translations = _strings[key]; if (translations) { return translations[lang] || translations.en || "%" + key + "%"; } return "%" + key + "%"; } // ---- Settings ---- const settingNs = "RA_SETTINGS:"; function getEnhancedTiles() { return localStorage.getItem(settingNs + "enhancedTiles") == "true"; } function getHiddenMaxAge() { return parseInt(localStorage.getItem(settingNs + "hiddenMaxAge")) || 99; } function getHiddenMinAge() { return parseInt(localStorage.getItem(settingNs + "hiddenMinAge")) || 18; } function getHiddenUsers() { return JSON.parse(localStorage.getItem(settingNs + "hiddenUsers")) || []; } function getHideActivities() { return localStorage.getItem(settingNs + "hideActivities") == "true"; } function getHideMessages() { return localStorage.getItem(settingNs + "hideMessages") == "true"; } function setEnhancedTiles(value) { localStorage.setItem(settingNs + "enhancedTiles", value); } function setHiddenMaxAge(value) { localStorage.setItem(settingNs + "hiddenMaxAge", value); } function setHiddenMinAge(value) { localStorage.setItem(settingNs + "hiddenMinAge", value); } function setHideActivities(value) { localStorage.setItem(settingNs + "hideActivities", value); } function setHideMessages(value) { localStorage.setItem(settingNs + "hideMessages", value); } function setUserHidden(username, hide) { let hiddenUsers = getHiddenUsers(); if (hide) { if (hiddenUsers.length < hiddenUsers.push(username)) { hiddenUsers.sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase())); localStorage.setItem(settingNs + "hiddenUsers", JSON.stringify(hiddenUsers)); } } else { const prevLength = hiddenUsers.length; hiddenUsers = hiddenUsers.filter(e => e != username); if (prevLength > hiddenUsers.length) { localStorage.setItem(settingNs + "hiddenUsers", JSON.stringify(hiddenUsers)); } } } // ---- XHR ---- function filterUser(user, hiddenMaxAge, hiddenMinAge, hiddenNames) { return user.personal.age >= hiddenMinAge && user.personal.age <= hiddenMaxAge && !hiddenNames.includes(user.name) } function getApiVerb(url) { if (url.includes("/api/stream")) { return "stream"; } // Extract verb in "/api/v#/verb?" or "/api/+/verb?". const matches = url.match("/api/(v[0-9]|\\+)/(.*)\\?".replaceAll("/", "\\/")); if (matches && matches.length == 3) { return matches.at(-1); } return undefined; } function proxyXhr() { // Intercept XHR queries and replies by hooking the XHR open method. const realOpen = window.XMLHttpRequest.prototype.open; window.XMLHttpRequest.prototype.open = function (method, url, async, user, password) { this.addEventListener("load", () => { //console.log("[RA] XHR reply: method=" + method + ", url=" + url); try { // Parse data. const verb = getApiVerb(url); const isJson = verb !== "stream" && typeof this.response === "string"; let reply = isJson ? JSON.parse(this.response) : this.response; //console.log("[RA] XHR reply " + verb + "\n", reply); // Modify interesting data. switch (verb) { case "messages/conversations": reply = xhrHideMessages(reply); break; case "notifications/activity-stream": reply = xhrHideActivities(reply); break; case "profiles": case "visitors": case "visits": reply = xhrRestorePlusVisit(reply); reply = xhrEnhanceUsers(reply); break; } // Write back possibly modified data. Object.defineProperty(this, "responseText", { writable: true }); this.responseText = isJson ? JSON.stringify(reply) : reply; } catch (e) { console.log("[RA] XHR handler failed: " + e) } }); // Forward to client. return realOpen.apply(this, arguments); } } function xhrHideActivities(reply) { if (!getHideActivities()) { return reply; } // Remove hidden users. const hiddenMaxAge = getHiddenMaxAge(); const hiddenMinAge = getHiddenMinAge(); const hiddenNames = getHiddenUsers(); return reply.filter(x => filterUser(x.partner, hiddenMaxAge, hiddenMinAge, hiddenNames)); } function xhrHideMessages(reply) { if (!getHideMessages()) { return reply; } // Remove hidden users. const hiddenMaxAge = getHiddenMaxAge(); const hiddenMinAge = getHiddenMinAge(); const hiddenNames = getHiddenUsers(); reply.items = reply.items.filter(x => filterUser(x.chat_partner, hiddenMaxAge, hiddenMinAge, hiddenNames)); return reply; } function xhrEnhanceUsers(reply) { // Remove hidden users. const enhancedTiles = getEnhancedTiles(); const hiddenMaxAge = getHiddenMaxAge(); const hiddenMinAge = getHiddenMinAge(); const hiddenNames = getHiddenUsers(); let newItems = []; for (let item of reply.items) { if (filterUser(item, hiddenMaxAge, hiddenMinAge, hiddenNames)) { // Show as "large tiles" to display user details everywhere. if (enhancedTiles) { item.display.large_tile = true; } newItems.push(item); } } reply.items = newItems; return reply; } function xhrRestorePlusVisit(reply) { // Restore PLUS-visible visitors. reply.items_limited = 0; return reply; } // ---- Tile UI ---- waitForKeyElements(".tile > .reactView", jNode => { const tile = jNode.parent(".tile")[0]; // Ignore placeholder tiles. for (const cls of tile.classList) { if (cls.startsWith("tile--loading--")) { return; } } // Extract user name from link. const a = tile.querySelector("a"); const username = a.href.substring(a.href.indexOf("profile/")).split("/")[1]; // Extract user avatar from link div. const div = a.firstChild; const divImg = window.getComputedStyle(div).getPropertyValue("background-image"); const imgUrl = divImg.substring(divImg.indexOf('"') + 1, divImg.lastIndexOf('"')); // Add action bar. const tileBar = $("<div class='tile__bar'></div>").appendTo(tile); addHideUserAction(tileBar, tile, username); addShowImageAction(tileBar, imgUrl); }); function addShowImageAction(tileBar, url) { if (url.endsWith(".svg")) { // ignore "no photo" placeholders return; } const fileName = url.substring(url.lastIndexOf("/") + 1); const origUrl = "/img/usr/original/0x0/" + fileName; $("<a class='tile__bar_action' href='" + origUrl + "' title='" + getString("viewFullImage") + "'><span class='icon icon-picture'></a>") .on("click", e => { e.preventDefault(); const body = $("#spotlight-container"); const layer = $(`<div class='layer layer--spotlight' style='top:0;z-index:100;'> <img src='` + origUrl + `'></img> </div>`); layer.on("click", e => layer.remove()); layer.appendTo(body); }) .appendTo(tileBar); } function addHideUserAction(tileBar, tile, username) { $("<a class='tile__bar_action' href='#' title='" + getString("hideUser") + "'><span class='icon icon-hide-visit'></a>") .on("click", e => { e.preventDefault(); setUserHidden(username, true); tile.style.display = "none"; }) .appendTo(tileBar); } // ---- Settings UI ---- waitForKeyElements("li.js-settings > div.accordion > ul", jNode => { let itemClass = jNode.find("a").attr("class"); $("<li><div><a class='" + itemClass + "'>" + getString("extensionTitle") + "</a></div></li>") .on("click", e => { // Force open the setting pane and clear any existing contents. $("#offcanvas-nav > .js-layer-content").addClass("is-open"); const pane = $(".js-side-content"); pane.empty(); // Add pane and list. pane.append(` <div class='layout layout--vertical layout--consume'> <div class='layout-item layout-item--consume layout layout--vertical'> <div class='layout-item settings__navigation p l-hidden-sm'> <div class='js-title typo-section-navigation'>` + getString("extensionTitle") + `</div> </div> <div class='layout-item layout-item--consume'> <div class='js-content js-scrollable fit scrollable'> <div class="p"> <div class="settings__key"> <div class="layout layout--v-center"> <div class="layout-item [ 6/12--sm ]"> <span>` + getString("enhancedTiles") + `</span> </div> <div class="layout-item [ 6/12--sm ]"> <div class="js-toggle-show-headlines pull-right"> <div> <span class="ui-toggle ui-toggle--default ui-toggle--right"> <input class="ui-toggle__input" type="checkbox" id="ra_enhancedTiles"> <label class="ui-toggle__label" for="ra_enhancedTiles" style="touch-action: pan-y; user-select: none; -webkit-user-drag: none; -webkit-tap-highlight-color: rgba(0, 0, 0, 0);"></label> </span> </div> </div> </div> </div> <div> <div class="settings__description">` + getString("enhancedTilesDesc") + `</div> </div> </div> <div class="settings__key"> <div> <span>` + getString("hiddenUsers") + `</span> </div> <div class="separator separator--alt separator--narrow [ mb ] "></div> <div class="layout layout--v-center"> <div class="layout-item [ 6/12--sm ]"> <span>` + getString("hideMessages") + `</span> </div> <div class="layout-item [ 6/12--sm ]"> <div class="js-toggle-show-headlines pull-right"> <div> <span class="ui-toggle ui-toggle--default ui-toggle--right"> <input class="ui-toggle__input" type="checkbox" id="ra_hideMessages"> <label class="ui-toggle__label" for="ra_hideMessages" style="touch-action: pan-y; user-select: none; -webkit-user-drag: none; -webkit-tap-highlight-color: rgba(0, 0, 0, 0);"></label> </span> </div> </div> </div> </div> <div> <div class="settings__description">` + getString("hideMessagesDesc") + `</div> </div> <div class="layout layout--v-center"> <div class="layout-item [ 6/12--sm ]"> <span>` + getString("hideActivities") + `</span> </div> <div class="layout-item [ 6/12--sm ]"> <div class="js-toggle-show-headlines pull-right"> <div> <span class="ui-toggle ui-toggle--default ui-toggle--right"> <input class="ui-toggle__input" type="checkbox" id="ra_hideActivities"> <label class="ui-toggle__label" for="ra_hideActivities" style="touch-action: pan-y; user-select: none; -webkit-user-drag: none; -webkit-tap-highlight-color: rgba(0, 0, 0, 0);"></label> </span> </div> </div> </div> </div> <div> <div class="settings__description">` + getString("hideActivitiesDesc") + `</div> </div> <div class="settings__key"> <div class="layout layout--v-center"> <div class="layout-item [ 6/12--sm ]"> <span>` + getString("minAge") + `</span> </div> <div class="layout-item [ 6/12--sm ]"> <input class="input input--block" id="ra_hiddenMinAge" type="number" min="18" max="99"/> </div> </div> </div> <div class="settings__key"> <div class="layout layout--v-center"> <div class="layout-item [ 6/12--sm ]"> <span>` + getString("maxAge") + `</span> </div> <div class="layout-item [ 6/12--sm ]"> <input class="input input--block" id="ra_hiddenMaxAge" type="number" min="18" max="99"/> </div> </div> </div> <div class="settings__key"> <div class="js-grid-stats-selector"> <div> <ul class="js-list tags-list tags-list--centered" id="ra_hiddenUsers"/> </div> </div> </div> </div> </div> </div> </div> </div> </div>`); // Handle enhanced user tiles. let inEnhancedTiles = $("#ra_enhancedTiles"); inEnhancedTiles.prop("checked", getEnhancedTiles()); inEnhancedTiles.on("change", e => { setEnhancedTiles(e.target.checked); }); // Handle hidden interactions. let inHideMessages = $("#ra_hideMessages"); inHideMessages.prop("checked", getHideMessages()); inHideMessages.on("change", e => { setHideMessages(e.target.checked); }); let inHideActivities = $("#ra_hideActivities"); inHideActivities.prop("checked", getHideActivities()); inHideActivities.on("change", e => { setHideActivities(e.target.checked); }); // Handle hidden age. let minAge = getHiddenMinAge(); let maxAge = getHiddenMaxAge(); let inMinAge = $("#ra_hiddenMinAge"); let inMaxAge = $("#ra_hiddenMaxAge"); inMinAge.val(minAge); inMaxAge.val(maxAge); inMinAge.on("change", e => { minAge = parseInt(e.target.value); setHiddenMinAge(minAge); if (minAge > maxAge) { maxAge = minAge; setHiddenMaxAge(maxAge); inMaxAge.val(maxAge); } }); inMaxAge.on("change", e => { maxAge = parseInt(e.target.value); setHiddenMaxAge(maxAge); if (maxAge < minAge) { minAge = maxAge; setHiddenMinAge(minAge); inMinAge.val(minAge); } }); // Handle hidden user list. const ul = $("#ra_hiddenUsers"); for (const item of getHiddenUsers()) { const li = $("<li class='tags-list__item'/>").appendTo(ul); $("<a class='js-tag ui-tag ui-tag--removable ui-tag--selected' href='#'><span class='ui-tag__label'>" + item + "</span></a>") .on("click", e => { setUserHidden(e.target.innerHTML, false); $(e.target).closest(".tags-list__item").css("display", "none"); }) .appendTo(li); }; }) .appendTo(jNode); });