Guest User

Untitled

a guest
Jul 30th, 2026
31
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 8.38 KB | None | 0 0
  1. // ==UserScript==
  2. // @name EyeWire – Clickable Cube Player Names
  3. // @namespace eyewire-clickable-player-names
  4. // @version 0.4.0
  5. // @description Opens profiles from the Cube Players panel and keeps the logged-in profile compact.
  6. // @match https://eyewire.org/*
  7. // @run-at document-idle
  8. // @grant none
  9. // ==/UserScript==
  10.  
  11. (() => {
  12. 'use strict';
  13.  
  14. const PLAYER_SELECTOR = '.panel.player > li';
  15.  
  16. function getUsername(element) {
  17. return String(element?.textContent ?? '')
  18. .replace(/\s+/g, ' ')
  19. .trim();
  20. }
  21.  
  22. function getJQuery() {
  23. return window.jQuery || window.$;
  24. }
  25.  
  26. function isVisible(element) {
  27. if (!(element instanceof Element)) {
  28. return false;
  29. }
  30.  
  31. const rect = element.getBoundingClientRect();
  32. const style = getComputedStyle(element);
  33.  
  34. return (
  35. rect.width > 0 &&
  36. rect.height > 0 &&
  37. style.display !== 'none' &&
  38. style.visibility !== 'hidden'
  39. );
  40. }
  41.  
  42. function findLeaderboardRow() {
  43. const rows = [...document.querySelectorAll('.leaderRow')];
  44.  
  45. return (
  46. rows.find(isVisible) ??
  47. rows[0] ??
  48. null
  49. );
  50. }
  51.  
  52. function replaceUsernameFields(value, username, depth = 0) {
  53. if (
  54. !value ||
  55. typeof value !== 'object' ||
  56. depth > 5
  57. ) {
  58. return;
  59. }
  60.  
  61. for (const key of Object.keys(value)) {
  62. const item = value[key];
  63.  
  64. if (
  65. typeof item === 'string' &&
  66. /^(username|user_name|name)$/i.test(key)
  67. ) {
  68. value[key] = username;
  69. } else if (item && typeof item === 'object') {
  70. replaceUsernameFields(item, username, depth + 1);
  71. }
  72. }
  73. }
  74.  
  75. function cloneData(value, seen = new WeakMap()) {
  76. if (!value || typeof value !== 'object') {
  77. return value;
  78. }
  79.  
  80. if (seen.has(value)) {
  81. return seen.get(value);
  82. }
  83.  
  84. const copy = Array.isArray(value) ? [] : {};
  85. seen.set(value, copy);
  86.  
  87. for (const key of Object.keys(value)) {
  88. copy[key] = cloneData(value[key], seen);
  89. }
  90.  
  91. return copy;
  92. }
  93.  
  94. function restoreObject(target, backup) {
  95. for (const key of Object.keys(target)) {
  96. if (!(key in backup)) {
  97. delete target[key];
  98. }
  99. }
  100.  
  101. for (const key of Object.keys(backup)) {
  102. target[key] = backup[key];
  103. }
  104. }
  105.  
  106. function findUsernameElement(row) {
  107. const selectors = [
  108. '.username',
  109. '.userName',
  110. '[class*="username"]',
  111. '[class*="userName"]',
  112. 'span'
  113. ];
  114.  
  115. for (const selector of selectors) {
  116. const element = row.querySelector(selector);
  117.  
  118. if (element) {
  119. return element;
  120. }
  121. }
  122.  
  123. return null;
  124. }
  125.  
  126. function openProfile(username) {
  127. const $ = getJQuery();
  128.  
  129. if (!$ || typeof $.data !== 'function') {
  130. console.error(
  131. '[EyeWire clickable names] EyeWire jQuery was not found.'
  132. );
  133. return false;
  134. }
  135.  
  136. const row = findLeaderboardRow();
  137.  
  138. if (!row) {
  139. console.error(
  140. '[EyeWire clickable names] No leaderboard row was found.'
  141. );
  142. return false;
  143. }
  144.  
  145. const rowData = $.data(row);
  146.  
  147. if (!rowData || typeof rowData !== 'object') {
  148. console.error(
  149. '[EyeWire clickable names] The leaderboard row contains no player data.'
  150. );
  151. return false;
  152. }
  153.  
  154. const dataBackup = cloneData(rowData);
  155. const usernameElement = findUsernameElement(row);
  156. const originalVisibleName = usernameElement?.textContent ?? null;
  157.  
  158. try {
  159. replaceUsernameFields(rowData, username);
  160.  
  161. // Fields confirmed by EyeWire's leaderboard data.
  162. rowData.username = username;
  163.  
  164. if (rowData.user && typeof rowData.user === 'object') {
  165. rowData.user.username = username;
  166.  
  167. if ('name' in rowData.user) {
  168. rowData.user.name = username;
  169. }
  170. }
  171.  
  172. // Some versions of EyeWire may read the visible text.
  173. if (usernameElement) {
  174. usernameElement.textContent = username;
  175. }
  176.  
  177. /*
  178. * Invoke EyeWire's existing profile-opening handler.
  179. * triggerHandler avoids simulating an unrelated browser action.
  180. */
  181. $(row).triggerHandler('click');
  182.  
  183. return true;
  184. } catch (error) {
  185. console.error(
  186. `[EyeWire clickable names] Could not open "${username}".`,
  187. error
  188. );
  189.  
  190. return false;
  191. } finally {
  192. restoreObject(rowData, dataBackup);
  193.  
  194. if (usernameElement && originalVisibleName !== null) {
  195. usernameElement.textContent = originalVisibleName;
  196. }
  197. }
  198. }
  199.  
  200. function showError(username, element) {
  201. document
  202. .querySelectorAll('.ew-profile-click-error')
  203. .forEach(message => message.remove());
  204.  
  205. const message = document.createElement('div');
  206. message.className = 'ew-profile-click-error';
  207. message.textContent = `Could not open ${username}'s profile.`;
  208.  
  209. const rect = element.getBoundingClientRect();
  210.  
  211. Object.assign(message.style, {
  212. position: 'fixed',
  213. left: `${Math.max(
  214. 8,
  215. Math.min(rect.left, window.innerWidth - 260)
  216. )}px`,
  217. top: `${Math.max(8, rect.top - 38)}px`,
  218. zIndex: '2147483647',
  219. padding: '7px 10px',
  220. border: '1px solid #555',
  221. borderRadius: '4px',
  222. background: 'rgba(25, 25, 28, 0.96)',
  223. color: '#ddd',
  224. font: '12px/1.4 sans-serif',
  225. pointerEvents: 'none'
  226. });
  227.  
  228. document.body.appendChild(message);
  229. setTimeout(() => message.remove(), 2500);
  230. }
  231.  
  232. /*
  233. * EyeWire gives the logged-in user's own profile a much larger layout.
  234. * Detect the Settings tab and scale only that version down.
  235. */
  236. function updateOwnProfileSize() {
  237. const profile = document.querySelector('#profileContainer');
  238.  
  239. if (!profile) {
  240. return;
  241. }
  242.  
  243. const hasSettingsTab = [...profile.querySelectorAll(
  244. 'a, button, li, span, div'
  245. )].some(element => (
  246. element.children.length === 0 &&
  247. element.textContent.trim() === 'Settings'
  248. ));
  249.  
  250. profile.classList.toggle(
  251. 'ew-compact-own-profile',
  252. hasSettingsTab
  253. );
  254. }
  255.  
  256. const style = document.createElement('style');
  257.  
  258. style.textContent = `
  259. ${PLAYER_SELECTOR} {
  260. cursor: pointer;
  261. }
  262.  
  263. #profileContainer.ew-compact-own-profile {
  264. zoom: 0.98 !important;
  265. }
  266. `;
  267.  
  268. document.head.appendChild(style);
  269.  
  270. document.addEventListener(
  271. 'click',
  272. event => {
  273. if (event.button !== 0) {
  274. return;
  275. }
  276.  
  277. const nameElement = event.target.closest(PLAYER_SELECTOR);
  278.  
  279. if (!nameElement) {
  280. return;
  281. }
  282.  
  283. const username = getUsername(nameElement);
  284.  
  285. if (!username) {
  286. return;
  287. }
  288.  
  289. event.preventDefault();
  290. event.stopPropagation();
  291. event.stopImmediatePropagation();
  292.  
  293. if (!openProfile(username)) {
  294. showError(username, nameElement);
  295. }
  296. },
  297. true
  298. );
  299.  
  300. const observer = new MutationObserver(updateOwnProfileSize);
  301.  
  302. observer.observe(document.body, {
  303. childList: true,
  304. subtree: true,
  305. characterData: true
  306. });
  307.  
  308. updateOwnProfileSize();
  309.  
  310. console.info(
  311. '[EyeWire clickable names] Version 0.4.0 loaded.'
  312. );
  313. })();
Advertisement
Add Comment
Please, Sign In to add comment