NextUp.Game Enhancer

Shows the names of the streamers for each clip on NextUp Game voting page

  1. // ==UserScript==
  2. // @name NextUp.Game Enhancer
  3. // @namespace PrimeMinister
  4. // @version 2.0
  5. // @description Shows the names of the streamers for each clip on NextUp Game voting page
  6. // @author Kier
  7. // @match https://nextup.game/*
  8. // @grant none
  9. // ==/UserScript==
  10.  
  11. (function () {
  12. 'use strict';
  13. function getStreamerInfo() {
  14. const scriptTag = document.querySelector('#__NEXT_DATA__');
  15. if (!scriptTag) return {};
  16.  
  17. const jsonData = JSON.parse(scriptTag.textContent);
  18. const streamers = jsonData?.props?.pageProps?.event?.streamers;
  19. if (!streamers) return {};
  20.  
  21. return streamers.reduce((acc, streamer) => {
  22. acc[streamer.id] = streamer.kick_username;
  23. return acc;
  24. }, {});
  25. }
  26.  
  27. function addStreamerNames(streamer1, streamer2, elo1, elo2) {
  28. const targetDiv = document.querySelector('.space-y-4');
  29. if (!targetDiv) return;
  30. if (document.querySelector('.streamer-info')) return;
  31. const streamerInfoDiv = document.createElement('div');
  32. streamerInfoDiv.className = 'text-center text-lg font-bold mt-2 streamer-info';
  33. streamerInfoDiv.textContent = `${streamer1} (${elo1}) vs ${streamer2} (${elo2})`;
  34. streamerInfoDiv.addEventListener('click', () => {
  35. const textToCopy = `${streamer1}: ${elo1}\n${streamer2}: ${elo2}`;
  36. navigator.clipboard.writeText(textToCopy).then(() => {
  37. showPopup('Streamer ELO ratings copied to clipboard');
  38. }).catch(err => {
  39. console.error('Error copying text: ', err);
  40. });
  41. });
  42.  
  43. targetDiv.appendChild(streamerInfoDiv);
  44. }
  45.  
  46. function showPopup(message) {
  47. const popup = document.createElement('div');
  48. popup.className = 'clipboard-popup';
  49. popup.textContent = message;
  50. document.body.appendChild(popup);
  51. Object.assign(popup.style, {
  52. position: 'fixed',
  53. top: '50%',
  54. left: '50%',
  55. transform: 'translate(-50%, -50%)',
  56. backgroundColor: '#333',
  57. color: '#fff',
  58. padding: '10px 20px',
  59. borderRadius: '5px',
  60. boxShadow: '0 2px 10px rgba(0, 0, 0, 0.2)',
  61. zIndex: '1000',
  62. opacity: '0',
  63. transition: 'opacity 0.3s ease'
  64. });
  65.  
  66. setTimeout(() => {
  67. popup.style.opacity = '1';
  68. }, 10);
  69.  
  70. setTimeout(() => {
  71. popup.style.opacity = '0';
  72. setTimeout(() => {
  73. document.body.removeChild(popup);
  74. }, 300);
  75. }, 2000);
  76. }
  77.  
  78. async function fetchClipData(streamerInfo) {
  79. try {
  80. const response = await fetch('https://api.nextup.game/v1/votes/', {
  81. method: 'POST',
  82. headers: {
  83. 'Content-Type': 'application/json',
  84. },
  85. credentials: 'include'
  86. });
  87. const data = await response.json();
  88. if (data.success && data.data && data.data.clips) {
  89. const clipA = data.data.clips.a;
  90. const clipB = data.data.clips.b;
  91.  
  92. const streamer1 = streamerInfo[clipA.streamer_id] || 'Unknown Streamer 1';
  93. const streamer2 = streamerInfo[clipB.streamer_id] || 'Unknown Streamer 2';
  94. const elo1 = clipA.elo_rating;
  95. const elo2 = clipB.elo_rating;
  96.  
  97. addStreamerNames(streamer1, streamer2, elo1, elo2);
  98. }
  99. } catch (error) {
  100. console.error('Error fetching clip data:', error);
  101. }
  102. }
  103.  
  104. function main() {
  105. const streamerInfo = getStreamerInfo();
  106. if (Object.keys(streamerInfo).length > 0) {
  107. fetchClipData(streamerInfo);
  108. }
  109. }
  110.  
  111. function observePageChanges() {
  112. const targetNode = document.querySelector('body');
  113. const config = { childList: true, subtree: true };
  114.  
  115. const callback = function (mutationsList) {
  116. for (const mutation of mutationsList) {
  117. if (mutation.type === 'childList') {
  118. if (!document.querySelector('.streamer-info') && window.location.pathname === '/vote') {
  119. setTimeout(main, 1000);
  120. }
  121. }
  122. }
  123. };
  124.  
  125. const observer = new MutationObserver(callback);
  126. if (targetNode) {
  127. observer.observe(targetNode, config);
  128. }
  129. }
  130.  
  131. function observeUrlChanges() {
  132. let lastPathname = window.location.pathname;
  133. setInterval(() => {
  134. if (window.location.pathname !== lastPathname) {
  135. lastPathname = window.location.pathname;
  136. if (lastPathname === '/vote') {
  137. main();
  138. observePageChanges();
  139. }
  140. }
  141. }, 500);
  142. }
  143.  
  144. setTimeout(() => {
  145. if (window.location.pathname === '/vote') {
  146. main();
  147. observePageChanges();
  148. }
  149. observeUrlChanges();
  150. }, 1500);
  151. })();