您需要先安装一个扩展,例如 篡改猴、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 3.0.0 // @description Allows to hide users, display their information on tiles, and enhances the Radar. // @description:de Ermöglicht das Verstecken von Benutzern, die Anzeige ihrer Details auf Kacheln, und verbessert den Radar. // @author -Ray-, Djamana // @match *://*.romeo.com/* // @license MIT // ==/UserScript== // ==== Dependencies ==== // ---- https://github.com/CoeJoder/GM_wrench ---- function addCss(css) { var style = document.createElement('style'); style.type = 'text/css'; // @ts-ignore if (style.styleSheet) { // @ts-ignore style.styleSheet.cssText = css; } else { style.appendChild(document.createTextNode(css)); } document.getElementsByTagName('head')[0].appendChild(style); }; function waitForKeyElements(selectorOrFunction, callback, waitOnce, interval, maxIntervals) { if (typeof waitOnce === 'undefined') { waitOnce = true; } if (typeof interval === 'undefined') { interval = 300; } if (typeof maxIntervals === 'undefined') { maxIntervals = -1; } var targetNodes = (typeof selectorOrFunction === 'function') ? selectorOrFunction() : document.querySelectorAll(selectorOrFunction); var targetsFound = targetNodes && targetNodes.length > 0; if (targetsFound) { targetNodes.forEach(function (targetNode) { var attrAlreadyFound = 'data-userscript-alreadyFound'; var alreadyFound = targetNode.getAttribute(attrAlreadyFound) || false; if (!alreadyFound) { var cancelFound = callback(targetNode); if (cancelFound) { targetsFound = false; } else { targetNode.setAttribute(attrAlreadyFound, true); } } }); } if (maxIntervals !== 0 && !(targetsFound && waitOnce)) { maxIntervals -= 1; setTimeout(function () { waitForKeyElements(selectorOrFunction, callback, waitOnce, interval, maxIntervals); }, interval); } }; // ==== CSS ==== addCss(` .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 */ #visits > .layer__container--wider { width:unset; max-width:1227px; } /* wider visitor list */ #visits div[class^="UnlockMoreVisitorsGrid"] { display: none; } /* hide PLUS message at bottom of visitor grid */ div[data-testid="desktop-image"] { background-image: none; } /* hide models on login page */ .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; } #version { color: white; } `); // ==== Script ==== (function () { "use strict"; proxyXhr(); })(); function addElement(parent, html) { parent.insertAdjacentHTML("beforeend", html); return parent.lastChild; } function onElement(selector, callback) { waitForKeyElements(selector, callback, false, 500); } // ---- Language ---- const _strings = { "display": { "de": "Anzeige", "en": "Display" }, "enhancedTiles": { "de": "Erweiterte Kacheln", "en": "Enhanced tiles" }, "enhancedTilesDesc": { "de": "Zeigt 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" }, "sendEnter": { "de": "Enter sendet Nachricht", "en": "Enter sends message" }, "sendEnterDesc": { "de": "Wenn deaktiviert, erzeugt Enter einen Absatz und Strg+Enter versendet die Nachricht.", "en": "If disabled, Enter creates a new line instead, and Ctrl+Enter sends the message." }, "typingNotifications": { "de": "Tippbenachrichtigungen", "en": "Typing notifications" }, "typingNotificationsDesc": { "de": "Wenn deaktiviert, können Empfänger nicht mehr sehen, dass eine Nachricht verfasst wird.", "en": "If disabled, receivers can no longer see that a message is being composed." }, "viewFullImage": { "de": "Bild vergrößern", "en": "View full image" }, } function translate(key) { const lang = document.documentElement.getAttribute("lang") || "en"; const translations = _strings[key]; return translations ? translations[lang] || translations.en || "%" + key + "%" : "%" + key + "%"; } // ---- Settings ---- const settingsNs = "RA_SETTINGS:"; function load(name, fallback) { const value = localStorage.getItem(settingsNs + name); return value === "false" ? false : value ? value : fallback; } function save(name, value) { localStorage.setItem(settingsNs + name, value); } function getEnhancedTiles() { return load("enhancedTiles", true); } function getHiddenMaxAge() { return load("hiddenMaxAge", 99); } function getHiddenMinAge() { return load("hiddenMinAge", 18); } function getHiddenUsers() { return new Set(JSON.parse(load("hiddenUsers", "[]"))); } function getHideActivities() { return load("hideActivities", false); } function getHideMessages() { return load("hideMessages", false); } function getSendEnter() { return load("sendEnter", true); } function getTypingNotifications() { return load("typingNotifications", true); } function setEnhancedTiles(value) { save("enhancedTiles", value); } function setHiddenMaxAge(value) { save("hiddenMaxAge", value); } function setHiddenMinAge(value) { save("hiddenMinAge", value); } function setHideActivities(value) { save( "hideActivities", value); } function setHideMessages(value) { save("hideMessages", value); } function setSendEnter(value) { save("sendEnter", value); } function setTypingNotifications(value) { save("typingNotifications", value); } function setUserHidden(username, hide) { let hiddenUsers = getHiddenUsers(); if (hide) { hiddenUsers.add(username); } else { hiddenUsers.delete(username); } save("hiddenUsers", JSON.stringify(Array.from(hiddenUsers))); } // ---- XHR ---- function filterUser(user, hiddenMaxAge, hiddenMinAge, hiddenNames) { return (!user.personal || user.personal.age >= hiddenMinAge && user.personal.age <= hiddenMaxAge) && !hiddenNames.has(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() { 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); 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; }); // Forward to client. return realOpen.apply(this, arguments); } } function xhrHideActivities(reply) { if (!getHideActivities()) { return reply; } // Remove hidden users. return reply.filter(x => filterUser(x.partner, getHiddenMaxAge(), getHiddenMinAge(), getHiddenUsers())); } function xhrHideMessages(reply) { if (!getHideMessages()) { return reply; } // Remove hidden users. reply.items = reply.items.filter(x => filterUser(x.chat_partner, getHiddenMaxAge(), getHiddenMinAge(), getHiddenUsers())); return reply; } function xhrEnhanceUsers(reply) { if (!reply.items) { return; } // 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) { item.display.large_tile = true; } newItems.push(item); } } reply.items = newItems; return reply; } function xhrRestorePlusVisit(reply) { // Restore PLUS-visible visitors. reply.items_limited = reply.items_total; return reply; } // ---- Tile UI ---- onElement(`.tile:not(div[class*="tile--loading--"]) > .reactView`, el => { const tile = el.closest(".tile"); if (!tile) { 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 = addElement(tile, `<div class="tile__bar"></div>`); 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; const action = addElement(tileBar, `<a class="tile__bar_action" href="${origUrl}" title="${translate("viewFullImage")}"> <span class="icon icon-picture"> </a>`); action.addEventListener("click", e => { e.preventDefault(); const spotlightContainer = document.querySelector("#spotlight-container"); const layer = addElement(spotlightContainer, `<div class="layer layer--spotlight" style="top:0;z-index:100;"> <img src="${origUrl}"></img> </div>`); layer.addEventListener("click", e => layer.remove()); }); } function addHideUserAction(tileBar, tile, username) { const action = addElement(tileBar, `<a class="tile__bar_action" href="#" title="${translate("hideUser")}"> <span class="icon icon-hide-visit"> </a>`); action.addEventListener("click", e => { e.preventDefault(); setUserHidden(username, true); tile.style.display = "none"; }); } // ---- Messaging UI ---- onElement(".js-send-region.layout-item > div", el => { el.addEventListener("keydown", e => { // Prevent site event handler from sending message or typing notifications. const enter = e.key === "Enter"; const send = enter && (getSendEnter() || e.ctrlKey); const allow = send || getTypingNotifications() && !enter; if (!allow) { e.stopPropagation(); } }, true); }); // ---- Sidebar UI ---- onElement(`div[class^="Version--"]`, el => { el.innerHTML += `<br><a id="version" href="https://greasyfork.org/en/scripts/419514" target="blank">Romeo Additions 3.0.0</a>`; }); onElement("li.js-settings > div.accordion > ul", el => { const linkClass = el.querySelector("a").className; const link = addElement(el, `<li><div><a class="${linkClass}">${translate("extensionTitle")}</a></div></li>`); link.addEventListener("click", e => { // Force open the setting pane and clear any existing contents. document.querySelector("#offcanvas-nav > .js-layer-content").classList.add("is-open"); const pane = document.querySelector(".js-side-content"); pane.replaceChildren(); // Add pane and list. addElement(pane, ` <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">${translate("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>${translate("enhancedTiles")}</span> </div> <div class="layout-item [ 6/12--sm ]"> <div class="js-toggle-show-headlines pull-right"> <div> <span class="ui-toggle ui-toggle--default ui-toggle--right"> <input class="ui-toggle__input" type="checkbox" id="ra_enhancedTiles"> <label class="ui-toggle__label" for="ra_enhancedTiles" style="touch-action: pan-y; user-select: none; -webkit-user-drag: none; -webkit-tap-highlight-color: rgba(0, 0, 0, 0);"></label> </span> </div> </div> </div> </div> <div> <div class="settings__description">${translate("enhancedTilesDesc")}</div> </div> <div class="layout layout--v-center"> <div class="layout-item [ 6/12--sm ]"> <span>${translate("typingNotifications")}</span> </div> <div class="layout-item [ 6/12--sm ]"> <div class="js-toggle-show-headlines pull-right"> <div> <span class="ui-toggle ui-toggle--default ui-toggle--right"> <input class="ui-toggle__input" type="checkbox" id="ra_typingNotifications"> <label class="ui-toggle__label" for="ra_typingNotifications" style="touch-action: pan-y; user-select: none; -webkit-user-drag: none; -webkit-tap-highlight-color: rgba(0, 0, 0, 0);"></label> </span> </div> </div> </div> </div> <div> <div class="settings__description">${translate("typingNotificationsDesc")}</div> </div> <div class="layout layout--v-center"> <div class="layout-item [ 6/12--sm ]"> <span>${translate("sendEnter")}</span> </div> <div class="layout-item [ 6/12--sm ]"> <div class="js-toggle-show-headlines pull-right"> <div> <span class="ui-toggle ui-toggle--default ui-toggle--right"> <input class="ui-toggle__input" type="checkbox" id="ra_sendEnter"> <label class="ui-toggle__label" for="ra_sendEnter" style="touch-action: pan-y; user-select: none; -webkit-user-drag: none; -webkit-tap-highlight-color: rgba(0, 0, 0, 0);"></label> </span> </div> </div> </div> </div> <div> <div class="settings__description">${translate("sendEnterDesc")}</div> </div> </div> <div class="settings__key"> <div> <span>${translate("hiddenUsers")}</span> </div> <div class="separator separator--alt separator--narrow [ mb ] "></div> <div class="layout layout--v-center"> <div class="layout-item [ 6/12--sm ]"> <span>${translate("hideMessages")}</span> </div> <div class="layout-item [ 6/12--sm ]"> <div class="js-toggle-show-headlines pull-right"> <div> <span class="ui-toggle ui-toggle--default ui-toggle--right"> <input class="ui-toggle__input" type="checkbox" id="ra_hideMessages"> <label class="ui-toggle__label" for="ra_hideMessages" style="touch-action: pan-y; user-select: none; -webkit-user-drag: none; -webkit-tap-highlight-color: rgba(0, 0, 0, 0);"></label> </span> </div> </div> </div> </div> <div> <div class="settings__description">${translate("hideMessagesDesc")}</div> </div> <div class="layout layout--v-center"> <div class="layout-item [ 6/12--sm ]"> <span>${translate("hideActivities")}</span> </div> <div class="layout-item [ 6/12--sm ]"> <div class="js-toggle-show-headlines pull-right"> <div> <span class="ui-toggle ui-toggle--default ui-toggle--right"> <input class="ui-toggle__input" type="checkbox" id="ra_hideActivities"> <label class="ui-toggle__label" for="ra_hideActivities" style="touch-action: pan-y; user-select: none; -webkit-user-drag: none; -webkit-tap-highlight-color: rgba(0, 0, 0, 0);"></label> </span> </div> </div> </div> </div> <div> <div class="settings__description">${translate("hideActivitiesDesc")}</div> </div> <div class="settings__key"> <div class="layout layout--v-center"> <div class="layout-item [ 6/12--sm ]"> <span>${translate("minAge")}</span> </div> <div class="layout-item [ 6/12--sm ]"> <input class="input input--block" id="ra_hiddenMinAge" type="number" min="18" max="99"/> </div> </div> </div> <div class="settings__key"> <div class="layout layout--v-center"> <div class="layout-item [ 6/12--sm ]"> <span>${translate("maxAge")}</span> </div> <div class="layout-item [ 6/12--sm ]"> <input class="input input--block" id="ra_hiddenMaxAge" type="number" min="18" max="99"/> </div> </div> </div> <div class="settings__key"> <div class="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. const inEnhancedTiles = pane.querySelector("#ra_enhancedTiles"); inEnhancedTiles.checked = getEnhancedTiles(); inEnhancedTiles.addEventListener("change", e => setEnhancedTiles(e.target.checked)); // Handle typing notifications. const inTypingNotifications = pane.querySelector("#ra_typingNotifications"); inTypingNotifications.checked = getTypingNotifications(); inTypingNotifications.addEventListener("change", e => setTypingNotifications(e.target.checked)); // Handle send enter. const inSendEnter = pane.querySelector("#ra_sendEnter"); inSendEnter.checked = getSendEnter(); inSendEnter.addEventListener("change", e => setSendEnter(e.target.checked)); // Handle hidden interactions. const inHideMessages = pane.querySelector("#ra_hideMessages"); inHideMessages.checked = getHideMessages(); inHideMessages.addEventListener("change", e => setHideMessages(e.target.checked)); const inHideActivities = pane.querySelector("#ra_hideActivities"); inHideActivities.checked = getHideActivities(); inHideActivities.addEventListener("change", e => setHideActivities(e.target.checked)); // Handle hidden age. const inMinAge = pane.querySelector("#ra_hiddenMinAge"); const inMaxAge = pane.querySelector("#ra_hiddenMaxAge"); let minAge = getHiddenMinAge(); let maxAge = getHiddenMaxAge(); inMinAge.value = minAge; inMaxAge.value = maxAge; inMinAge.addEventListener("change", e => { minAge = parseInt(e.target.value); setHiddenMinAge(minAge); if (minAge > maxAge) { maxAge = minAge; setHiddenMaxAge(maxAge); inMaxAge.val(maxAge); } }); inMaxAge.addEventListener("change", e => { maxAge = parseInt(e.target.value); setHiddenMaxAge(maxAge); if (maxAge < minAge) { minAge = maxAge; setHiddenMinAge(minAge); inMinAge.val(minAge); } }); // Handle hidden user list. const ul = pane.querySelector("#ra_hiddenUsers"); const users = Array.from(getHiddenUsers()).sort(Intl.Collator().compare); for (const user of users) { const li = addElement(ul, `<li class="tags-list__item"> <a class="js-tag ui-tag ui-tag--removable ui-tag--selected" href="#"> <span class="ui-tag__label">${user}</span> </a> </li>`); li.querySelector("a").addEventListener("click", e => { setUserHidden(e.target.innerHTML, false); e.target.closest(".tags-list__item").style.display = "none"; }); }; }); });