Automatically claims Aviator rain rewards with toggle control and auto-scroll
Size
15.5 KB
Version
1.0.1
Created
Mar 16, 2026
Updated
6 days ago
1// ==UserScript==
2// @name Supabets Aviator Rain Auto-Claimer
3// @description Automatically claims Aviator rain rewards with toggle control and auto-scroll
4// @version 1.0.1
5// @match https://*.gaming.supabets.co.za/*
6// @match https://games-interface-supabets.bitville-api.com/*
7// @icon https://gaming.supabets.co.za/images/gaming.supabets.co.za/favicon/favicon.ico
8// @grant GM.getValue
9// @grant GM.setValue
10// @grant GM.addStyle
11// @run-at document-start
12// ==/UserScript==
13(function() {
14 'use strict';
15
16 // Debounce function to prevent excessive calls
17 function debounce(func, wait) {
18 let timeout;
19 return function executedFunction(...args) {
20 const later = () => {
21 clearTimeout(timeout);
22 func(...args);
23 };
24 clearTimeout(timeout);
25 timeout = setTimeout(later, wait);
26 };
27 }
28
29 // Add styles for the toggle button
30 GM.addStyle(`
31 #rain-auto-claim-toggle {
32 position: fixed;
33 top: 10px;
34 right: 10px;
35 z-index: 999999;
36 background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
37 color: white;
38 border: none;
39 padding: 12px 24px;
40 border-radius: 25px;
41 cursor: pointer;
42 font-weight: bold;
43 font-size: 14px;
44 box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3);
45 transition: all 0.3s ease;
46 font-family: Arial, sans-serif;
47 display: flex;
48 align-items: center;
49 gap: 8px;
50 }
51
52 #rain-auto-claim-toggle:hover {
53 transform: translateY(-2px);
54 box-shadow: 0 6px 20px rgba(0, 0, 0, 0.4);
55 }
56
57 #rain-auto-claim-toggle.active {
58 background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%);
59 }
60
61 #rain-auto-claim-toggle.inactive {
62 background: linear-gradient(135deg, #ee0979 0%, #ff6a00 100%);
63 }
64
65 #rain-auto-claim-toggle .status-indicator {
66 width: 10px;
67 height: 10px;
68 border-radius: 50%;
69 background: white;
70 animation: pulse 2s infinite;
71 }
72
73 @keyframes pulse {
74 0%, 100% { opacity: 1; }
75 50% { opacity: 0.5; }
76 }
77
78 #rain-claim-log {
79 position: fixed;
80 bottom: 10px;
81 right: 10px;
82 z-index: 999998;
83 background: rgba(0, 0, 0, 0.85);
84 color: #00ff00;
85 padding: 10px 15px;
86 border-radius: 8px;
87 font-family: 'Courier New', monospace;
88 font-size: 12px;
89 max-width: 300px;
90 max-height: 150px;
91 overflow-y: auto;
92 display: none;
93 box-shadow: 0 4px 15px rgba(0, 0, 0, 0.5);
94 }
95
96 #rain-claim-log.show {
97 display: block;
98 }
99
100 #rain-claim-log .log-entry {
101 margin: 3px 0;
102 padding: 3px 0;
103 border-bottom: 1px solid rgba(0, 255, 0, 0.2);
104 }
105
106 #rain-claim-log .log-entry:last-child {
107 border-bottom: none;
108 }
109 `);
110
111 // State management
112 let isEnabled = false;
113 let claimCount = 0;
114 let observer = null;
115 let processedElements = new Set();
116
117 // Initialize the extension
118 async function init() {
119 console.log('[Rain Auto-Claimer] Initializing...');
120
121 // Load saved state
122 isEnabled = await GM.getValue('rainAutoClaimEnabled', false);
123 claimCount = await GM.getValue('rainClaimCount', 0);
124
125 // Wait for body to be ready
126 if (document.body) {
127 setupUI();
128 if (isEnabled) {
129 startMonitoring();
130 }
131 } else {
132 const bodyObserver = new MutationObserver((mutations, obs) => {
133 if (document.body) {
134 obs.disconnect();
135 setupUI();
136 if (isEnabled) {
137 startMonitoring();
138 }
139 }
140 });
141 bodyObserver.observe(document.documentElement, { childList: true, subtree: true });
142 }
143 }
144
145 // Setup UI elements
146 function setupUI() {
147 // Create toggle button
148 const toggleButton = document.createElement('button');
149 toggleButton.id = 'rain-auto-claim-toggle';
150 toggleButton.className = isEnabled ? 'active' : 'inactive';
151 toggleButton.innerHTML = `
152 <span class="status-indicator"></span>
153 <span class="status-text">${isEnabled ? 'Auto-Claim ON' : 'Auto-Claim OFF'}</span>
154 `;
155
156 toggleButton.addEventListener('click', toggleAutoClaim);
157 document.body.appendChild(toggleButton);
158
159 // Create log display
160 const logDisplay = document.createElement('div');
161 logDisplay.id = 'rain-claim-log';
162 document.body.appendChild(logDisplay);
163
164 console.log('[Rain Auto-Claimer] UI setup complete');
165 }
166
167 // Toggle auto-claim functionality
168 async function toggleAutoClaim() {
169 isEnabled = !isEnabled;
170 await GM.setValue('rainAutoClaimEnabled', isEnabled);
171
172 const button = document.getElementById('rain-auto-claim-toggle');
173 button.className = isEnabled ? 'active' : 'inactive';
174 button.querySelector('.status-text').textContent = isEnabled ? 'Auto-Claim ON' : 'Auto-Claim OFF';
175
176 if (isEnabled) {
177 startMonitoring();
178 addLog('Auto-claim activated! 🎯');
179 } else {
180 stopMonitoring();
181 addLog('Auto-claim deactivated');
182 }
183
184 console.log(`[Rain Auto-Claimer] ${isEnabled ? 'Enabled' : 'Disabled'}`);
185 }
186
187 // Start monitoring for rain notifications
188 function startMonitoring() {
189 if (observer) {
190 observer.disconnect();
191 }
192
193 console.log('[Rain Auto-Claimer] Starting monitoring...');
194
195 // Monitor the entire document for rain-related elements
196 observer = new MutationObserver(debounce((mutations) => {
197 if (!isEnabled) return;
198
199 // Look for rain notifications and claim buttons
200 scanForRainElements();
201 }, 100));
202
203 observer.observe(document.body, {
204 childList: true,
205 subtree: true,
206 attributes: true,
207 attributeFilter: ['class', 'style']
208 });
209
210 // Initial scan
211 scanForRainElements();
212
213 // Also monitor iframes
214 monitorIframes();
215 }
216
217 // Monitor iframes for rain elements
218 function monitorIframes() {
219 const iframes = document.querySelectorAll('iframe');
220 iframes.forEach(iframe => {
221 try {
222 if (iframe.contentDocument && iframe.contentDocument.body) {
223 const iframeObserver = new MutationObserver(debounce(() => {
224 if (!isEnabled) return;
225 scanForRainElementsInContext(iframe.contentDocument);
226 }, 100));
227
228 iframeObserver.observe(iframe.contentDocument.body, {
229 childList: true,
230 subtree: true,
231 attributes: true
232 });
233 }
234 } catch (e) {
235 // Cross-origin iframe, can't access
236 console.log('[Rain Auto-Claimer] Cannot access iframe:', e.message);
237 }
238 });
239 }
240
241 // Scan for rain elements in a specific context
242 function scanForRainElementsInContext(context = document) {
243 // Look for various rain-related selectors
244 const selectors = [
245 '[class*="rain" i][class*="claim" i]',
246 '[class*="rain" i] button',
247 'button[class*="claim" i]',
248 '[id*="rain" i][id*="claim" i]',
249 '[data-testid*="rain" i]',
250 '[aria-label*="claim" i]',
251 'button:not([disabled])[class*="button" i]',
252 '.claim-button',
253 '.rain-claim',
254 '[onclick*="claim" i]'
255 ];
256
257 selectors.forEach(selector => {
258 try {
259 const elements = context.querySelectorAll(selector);
260 elements.forEach(element => {
261 const elementId = generateElementId(element);
262 if (!processedElements.has(elementId) && isRainClaimButton(element)) {
263 processedElements.add(elementId);
264 claimRain(element);
265 }
266 });
267 } catch (e) {
268 // Invalid selector or other error
269 }
270 });
271
272 // Also check for text content containing "rain" and "claim"
273 const allButtons = context.querySelectorAll('button:not([disabled]), [role="button"]:not([disabled])');
274 allButtons.forEach(button => {
275 const text = button.textContent.toLowerCase();
276 const elementId = generateElementId(button);
277
278 if (!processedElements.has(elementId) &&
279 (text.includes('claim') || text.includes('rain')) &&
280 isVisible(button)) {
281 processedElements.add(elementId);
282 claimRain(button);
283 }
284 });
285
286 // Auto-scroll chat if new messages appear
287 autoScrollChat(context);
288 }
289
290 // Scan for rain elements (wrapper for default context)
291 function scanForRainElements() {
292 scanForRainElementsInContext(document);
293 }
294
295 // Check if element is a rain claim button
296 function isRainClaimButton(element) {
297 const text = element.textContent.toLowerCase();
298 const className = element.className.toLowerCase();
299 const id = element.id.toLowerCase();
300
301 const hasRainKeyword = text.includes('rain') || className.includes('rain') || id.includes('rain');
302 const hasClaimKeyword = text.includes('claim') || className.includes('claim') || id.includes('claim');
303
304 return (hasRainKeyword || hasClaimKeyword) && isVisible(element);
305 }
306
307 // Check if element is visible
308 function isVisible(element) {
309 if (!element) return false;
310 const style = window.getComputedStyle(element);
311 return style.display !== 'none' &&
312 style.visibility !== 'hidden' &&
313 style.opacity !== '0' &&
314 element.offsetWidth > 0 &&
315 element.offsetHeight > 0;
316 }
317
318 // Generate unique ID for element
319 function generateElementId(element) {
320 return `${element.tagName}_${element.className}_${element.textContent.substring(0, 50)}_${element.getBoundingClientRect().top}`;
321 }
322
323 // Claim rain reward with human-like behavior
324 async function claimRain(button) {
325 if (!isEnabled) return;
326
327 try {
328 console.log('[Rain Auto-Claimer] Attempting to claim rain...');
329
330 // Add random delay to appear more human-like (50-200ms)
331 const delay = Math.floor(Math.random() * 150) + 50;
332 await new Promise(resolve => setTimeout(resolve, delay));
333
334 // Simulate mouse movement and click
335 const rect = button.getBoundingClientRect();
336 const x = rect.left + rect.width / 2;
337 const y = rect.top + rect.height / 2;
338
339 // Dispatch mouse events for more realistic interaction
340 const mouseOverEvent = new MouseEvent('mouseover', {
341 bubbles: true,
342 cancelable: true,
343 view: window,
344 clientX: x,
345 clientY: y
346 });
347 button.dispatchEvent(mouseOverEvent);
348
349 await new Promise(resolve => setTimeout(resolve, 20));
350
351 const mouseDownEvent = new MouseEvent('mousedown', {
352 bubbles: true,
353 cancelable: true,
354 view: window,
355 clientX: x,
356 clientY: y
357 });
358 button.dispatchEvent(mouseDownEvent);
359
360 await new Promise(resolve => setTimeout(resolve, 30));
361
362 const mouseUpEvent = new MouseEvent('mouseup', {
363 bubbles: true,
364 cancelable: true,
365 view: window,
366 clientX: x,
367 clientY: y
368 });
369 button.dispatchEvent(mouseUpEvent);
370
371 // Click the button
372 button.click();
373
374 // Update claim count
375 claimCount++;
376 await GM.setValue('rainClaimCount', claimCount);
377
378 addLog(`✅ Claimed rain #${claimCount}!`);
379 console.log(`[Rain Auto-Claimer] Successfully claimed rain #${claimCount}`);
380
381 // Remove from processed after some time to allow re-claiming if needed
382 setTimeout(() => {
383 const elementId = generateElementId(button);
384 processedElements.delete(elementId);
385 }, 30000); // 30 seconds
386
387 } catch (error) {
388 console.error('[Rain Auto-Claimer] Error claiming rain:', error);
389 addLog('❌ Claim failed');
390 }
391 }
392
393 // Auto-scroll chat when new messages appear
394 function autoScrollChat(context = document) {
395 const chatSelectors = [
396 '[class*="chat" i][class*="container" i]',
397 '[class*="chat" i][class*="messages" i]',
398 '[class*="message" i][class*="list" i]',
399 '[id*="chat" i]',
400 '.chat-container',
401 '.messages-container',
402 '.chat-messages'
403 ];
404
405 chatSelectors.forEach(selector => {
406 try {
407 const chatContainers = context.querySelectorAll(selector);
408 chatContainers.forEach(container => {
409 if (container.scrollHeight > container.clientHeight) {
410 // Smooth scroll to bottom
411 container.scrollTo({
412 top: container.scrollHeight,
413 behavior: 'smooth'
414 });
415 }
416 });
417 } catch (e) {
418 // Selector error
419 }
420 });
421 }
422
423 // Stop monitoring
424 function stopMonitoring() {
425 if (observer) {
426 observer.disconnect();
427 observer = null;
428 }
429 console.log('[Rain Auto-Claimer] Monitoring stopped');
430 }
431
432 // Add log entry
433 function addLog(message) {
434 const logDisplay = document.getElementById('rain-claim-log');
435 if (!logDisplay) return;
436
437 const timestamp = new Date().toLocaleTimeString();
438 const logEntry = document.createElement('div');
439 logEntry.className = 'log-entry';
440 logEntry.textContent = `[${timestamp}] ${message}`;
441
442 logDisplay.appendChild(logEntry);
443 logDisplay.classList.add('show');
444
445 // Keep only last 5 entries
446 while (logDisplay.children.length > 5) {
447 logDisplay.removeChild(logDisplay.firstChild);
448 }
449
450 // Auto-hide after 5 seconds
451 setTimeout(() => {
452 if (logDisplay.children.length === 0) {
453 logDisplay.classList.remove('show');
454 }
455 }, 5000);
456 }
457
458 // Start the extension
459 if (document.readyState === 'loading') {
460 document.addEventListener('DOMContentLoaded', init);
461 } else {
462 init();
463 }
464
465})();