Torn Keno Random Selector (TornPDA Version)

For TornPDA: Places a Torn-styled block with random selection buttons fixed over the interface so that it is not hidden by the matched numbers panel.

  1. // ==UserScript==
  2. // @name Torn Keno Random Selector (TornPDA Version)
  3. // @namespace https://greasyfork.org/en/scripts/531078-torn-keno-random-selector
  4. // @version 03.28.2025.20.00
  5. // @description For TornPDA: Places a Torn-styled block with random selection buttons fixed over the interface so that it is not hidden by the matched numbers panel.
  6. // @author KillerCleat
  7. // @match https://www.torn.com/page.php?sid=keno*
  8. // @grant none
  9. // @license MIT License
  10. // ==/UserScript==
  11.  
  12. (function() {
  13. 'use strict';
  14.  
  15. // Utility: Randomly select 'count' unique elements from an array.
  16. function getRandomElements(arr, count) {
  17. const arrCopy = [...arr];
  18. const result = [];
  19. for (let i = 0; i < count; i++) {
  20. if (arrCopy.length === 0) break;
  21. const index = Math.floor(Math.random() * arrCopy.length);
  22. result.push(arrCopy[index]);
  23. arrCopy.splice(index, 1);
  24. }
  25. return result;
  26. }
  27.  
  28. // Inject CSS for the TornPDA version – fixed positioning with a high z-index.
  29. function injectStyles() {
  30. const style = document.createElement('style');
  31. style.innerHTML = `
  32. /* Our container is fixed, centered horizontally, with high z-index to overlay the win sheet */
  33. #randomButtonsContainer {
  34. position: fixed !important;
  35. top: 250px !important; /* Adjust this value to push the block down further */
  36. left: 50% !important;
  37. transform: translateX(-50%) !important;
  38. z-index: 999999 !important;
  39. width: auto;
  40. text-align: center;
  41. }
  42. /* Torn-styled text */
  43. #randomButtonsContainer .desc p {
  44. margin: 0;
  45. padding: 0;
  46. font-size: 14px;
  47. font-weight: bold;
  48. }
  49. /* Force the buttons to be in one horizontal row */
  50. #randomButtonsContainer .cancel-btn-wrap {
  51. display: inline-block;
  52. white-space: nowrap;
  53. margin-top: 5px;
  54. }
  55. /* Minor spacing for each button */
  56. .btn-wrap.orange {
  57. display: inline-block;
  58. margin: 0 5px 5px 0;
  59. }
  60. `;
  61. document.head.appendChild(style);
  62. }
  63.  
  64. // Create the Torn-styled block (text + row of buttons).
  65. function createRandomButtons() {
  66. if (document.getElementById('randomButtonsContainer')) return;
  67.  
  68. // Outer container using Torn's "cancel" class.
  69. const container = document.createElement('div');
  70. container.id = 'randomButtonsContainer';
  71. container.className = 'cancel';
  72.  
  73. // Description text (using Torn's "desc" class).
  74. const desc = document.createElement('div');
  75. desc.className = 'desc';
  76. desc.tabIndex = 0;
  77. desc.innerHTML = '<p>Select how many random numbers to pick:</p>';
  78. container.appendChild(desc);
  79.  
  80. // Wrapper for the buttons.
  81. const cancelBtnWrap = document.createElement('div');
  82. cancelBtnWrap.className = 'cancel-btn-wrap';
  83. container.appendChild(cancelBtnWrap);
  84.  
  85. // Create 10 Torn-styled buttons (1–10) in one row.
  86. for (let i = 1; i <= 10; i++) {
  87. const btnWrap = document.createElement('div');
  88. btnWrap.className = 'btn-wrap orange';
  89.  
  90. const btnDiv = document.createElement('div');
  91. btnDiv.className = 'btn';
  92.  
  93. const button = document.createElement('button');
  94. button.className = 'torn-btn orange';
  95. button.setAttribute('aria-label', `Select ${i}`);
  96. button.setAttribute('data-count', i);
  97. button.textContent = i;
  98.  
  99. btnDiv.appendChild(button);
  100. btnWrap.appendChild(btnDiv);
  101. cancelBtnWrap.appendChild(btnWrap);
  102. }
  103.  
  104. // Append the container directly to the body.
  105. document.body.appendChild(container);
  106.  
  107. // Attach click handlers: simulate click on board numbers so Torn marks them.
  108. const boardSpans = document.querySelectorAll('#boardContainer span');
  109. const allButtons = container.querySelectorAll('button');
  110. allButtons.forEach(btn => {
  111. btn.addEventListener('click', function() {
  112. const count = parseInt(this.getAttribute('data-count'), 10);
  113. const unmarkedSpans = Array.from(boardSpans).filter(
  114. span => !span.classList.contains('marked')
  115. );
  116.  
  117. if (unmarkedSpans.length < count) {
  118. alert(`Not enough unmarked numbers available. Only ${unmarkedSpans.length} left.`);
  119. return;
  120. }
  121.  
  122. const selectedSpans = getRandomElements(unmarkedSpans, count);
  123. selectedSpans.forEach(span => span.click());
  124. });
  125. });
  126. }
  127.  
  128. // Poll for the Keno board to be available, then create our container.
  129. function waitForKenoElements() {
  130. const boardContainer = document.getElementById('boardContainer');
  131. if (boardContainer) {
  132. createRandomButtons();
  133. clearInterval(checkInterval);
  134. }
  135. }
  136.  
  137. injectStyles();
  138. const checkInterval = setInterval(waitForKenoElements, 500);
  139. })();