您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Automatically enter giveaways on extrasforamazon.com
// ==UserScript== // @name Giveaway Auto Enter // @namespace http://tampermonkey.net/ // @version 1.1 // @description Automatically enter giveaways on extrasforamazon.com // @author sacrosaunt // @match https://extrasforamazon.com/app/giveaways* // @grant none // ==/UserScript== (function() { 'use strict'; /** * Creates and injects the auto-enter button into the navigation bar * This function handles the UI setup for the giveaway automation tool */ function createButton() { // Check if button already exists to avoid duplicates let existingButton = document.getElementById('giveaway-auto-enter-btn'); if (existingButton) { return; // Button already exists, don't recreate } // Find the navigation container on the right side of the page // Try multiple selectors for compatibility across different page layouts const navRightContainer = document.querySelector('.nav_top_inner-wrap-right') || document.querySelector('.nav_top-content > div:last-child'); // If navigation container found, create button immediately if (navRightContainer) { createButtonElement(navRightContainer); return; } // If navigation container not found, keep checking until it appears // This ensures the button is added as soon as the container element is available const checkInterval = setInterval(() => { const container = document.querySelector('.nav_top_inner-wrap-right') || document.querySelector('.nav_top-content > div:last-child'); if (container) { clearInterval(checkInterval); // Stop checking createButtonElement(container); } }, 100); // Check every 100ms for better responsiveness // Add a timeout to prevent infinite checking (10 seconds max) setTimeout(() => { clearInterval(checkInterval); // If still no container found after timeout, create fallback button if (!document.getElementById('giveaway-auto-enter-btn')) { const fallbackContainer = document.createElement('div'); fallbackContainer.style.cssText = ` position: fixed; top: 20px; right: 20px; z-index: 10000; `; document.body.appendChild(fallbackContainer); createButtonElement(fallbackContainer); } }, 10000); } /** * Creates the actual button element and adds it to the specified container * @param {Element} container - The container to add the button to */ function createButtonElement(container) { // Create the auto-enter button element const button = document.createElement('button'); button.id = 'giveaway-auto-enter-btn'; button.className = 'themeButton primaryLinkButton'; button.title = 'Enter All Giveaways'; button.disabled = true; // Initially disabled until DOM is ready // Apply custom styling to match the site's design button.style.cssText = ` color: rgb(34, 51, 68); border: none; background: transparent; padding: 12px 16px; border-radius: 4px; cursor: not-allowed; font-size: 18px; margin-right: 10px; display: inline-flex; align-items: center; justify-content: center; opacity: 0.6; `; // Add gift icon to the button button.innerHTML = '<i class="fal fa-gift" style="font-size: 2.8rem;"></i>'; // Add hover effects for better user experience (only when enabled) button.addEventListener('mouseenter', () => { if (!button.disabled) { button.style.opacity = '0.8'; } }); button.addEventListener('mouseleave', () => { if (!button.disabled) { button.style.opacity = '1'; } }); // Insert button at the beginning of the container container.insertBefore(button, container.firstChild); // Check if button should be enabled immediately checkAndEnableButton(); } /** * Checks if the button should be enabled and enables it if conditions are met * This ensures buttons are enabled regardless of when they're created */ function checkAndEnableButton() { const button = document.getElementById('giveaway-auto-enter-btn'); if (button && document.readyState !== 'loading') { enableGiveawayAutomation(); } } /** * Main function that automates entering all eligible giveaways * This is the core functionality that processes giveaways one by one */ function autoEnterGiveaways() { /** * Creates a toast notification system for user feedback * Shows success, info, and error messages during the automation process * @returns {Object} Toast system with show method */ function createToastSystem() { // Check if toast container already exists let toastContainer = document.getElementById('giveaway-extension-toasts'); if (!toastContainer) { // Create toast container for notifications toastContainer = document.createElement('div'); toastContainer.id = 'giveaway-extension-toasts'; toastContainer.style.cssText = ` position: fixed; bottom: 20px; right: 20px; z-index: 10000; display: flex; flex-direction: column; align-items: flex-end; gap: 10px; `; document.body.appendChild(toastContainer); } // Add CSS styles for toast notifications const style = document.createElement('style'); style.textContent = ` .giveaway-toast { background-color: rgba(0, 0, 0, 0.8); color: white; padding: 12px 20px; border-radius: 4px; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); font-family: Arial, sans-serif; font-size: 14px; max-width: 300px; opacity: 0; transform: translateY(20px); transition: opacity 0.3s, transform 0.3s; } .giveaway-toast.show { opacity: 1; transform: translateY(0); } .giveaway-toast.success { border-left: 4px solid #4CAF50; } .giveaway-toast.info { border-left: 4px solid #2196F3; } .giveaway-toast.error { border-left: 4px solid #F44336; } `; document.head.appendChild(style); // Return toast system with show method return { show: function(message, type = 'info', duration = 3000) { const toast = document.createElement('div'); toast.className = `giveaway-toast ${type}`; toast.textContent = message; toastContainer.appendChild(toast); // Animate toast in setTimeout(() => toast.classList.add('show'), 10); // Auto-remove toast after duration setTimeout(() => { toast.classList.remove('show'); setTimeout(() => toastContainer.removeChild(toast), 300); }, duration); } }; } /** * Waits for an element to appear in the DOM * Uses MutationObserver for efficient element detection * @param {string} selector - CSS selector for the target element * @param {number} timeout - Maximum time to wait in milliseconds * @returns {Promise<Element>} Promise that resolves with the found element */ function waitForElement(selector, timeout = 10000) { return new Promise((resolve, reject) => { // Check if element already exists const element = document.querySelector(selector); if (element) { return resolve(element); } // Set up observer to watch for DOM changes const observer = new MutationObserver((mutations, obs) => { const element = document.querySelector(selector); if (element) { obs.disconnect(); resolve(element); } }); // Start observing DOM changes observer.observe(document.body, { childList: true, subtree: true, attributes: true }); // Set timeout to prevent infinite waiting setTimeout(() => { observer.disconnect(); reject(new Error(`Timeout waiting for ${selector}`)); }, timeout); }); } /** * Waits for the page to fully load * @returns {Promise} Promise that resolves when page is loaded */ function waitForPageLoad() { return new Promise(resolve => { if (document.readyState === 'complete') { return resolve(); } window.addEventListener('load', resolve, { once: true }); }); } /** * Waits for the DOM to become stable (no more rapid changes) * This prevents issues with elements being modified while we're trying to interact with them * @param {number} timeoutMs - Time to wait for stability in milliseconds * @returns {Promise} Promise that resolves when DOM is stable */ function waitForDomStable(timeoutMs = 100) { return new Promise(resolve => { let timeout; // Watch for DOM mutations and reset timer const observer = new MutationObserver(() => { clearTimeout(timeout); timeout = setTimeout(() => { observer.disconnect(); resolve(); }, timeoutMs); }); // Start observing DOM changes observer.observe(document.body, { childList: true, subtree: true, attributes: true }); // Fallback timeout if no mutations occur timeout = setTimeout(() => { observer.disconnect(); resolve(); }, timeoutMs); }); } /** * Main processing loop that enters giveaways one by one * Handles the entire workflow from finding eligible giveaways to completing entries */ async function processGiveaways() { const toast = createToastSystem(); // Show initial status message toast.show('Starting...', 'info', 2000); let continueProcessing = true; // Main processing loop while (continueProcessing) { // Wait for page to be ready before processing await waitForPageLoad(); await waitForDomStable(); // Find all eligible giveaways (those that can be entered) const eligibleGiveaways = document.querySelectorAll('button.giveaway-tile.eligible'); // If no eligible giveaways found, we're done if (eligibleGiveaways.length === 0) { toast.show('All giveaways have been entered!', 'success', 5000); continueProcessing = false; break; } try { // Process the first eligible giveaway const giveaway = eligibleGiveaways[0]; // Extract giveaway name for user feedback const nameElement = giveaway.querySelector('.giveaway-tile_name'); const giveawayName = nameElement?.textContent?.trim() || "giveaway"; // Click on the giveaway to open it giveaway.click(); // Wait for and click the enter button const enterButton = await waitForElement('div.giveaway-enter-form button[type="submit"]'); // Small delay to ensure form is ready await new Promise(r => setTimeout(r, 100)); enterButton.click(); toast.show(`Successfully entered ${giveawayName}!`, 'success'); // Wait for DOM to stabilize after entry await waitForDomStable(); // Find and click back button to return to giveaway list const backButton = await waitForElement('div.giveaway-overlay_header button'); backButton.click(); // Wait for page to stabilize before continuing await waitForDomStable(); } catch (error) { // Handle errors during giveaway processing toast.show(`Error: ${error.message}`, 'error', 5000); // Try to recover by clicking back button if possible try { const backButton = document.querySelector('div.giveaway-overlay_header button'); if (backButton) backButton.click(); await waitForDomStable(); } catch (e) { toast.show("Couldn't find back button, trying to continue...", 'info'); } // Implement retry logic (one retry) if (continueProcessing === true) { continueProcessing = "last_try"; toast.show("Encountered an error, will try once more", 'info'); } else { continueProcessing = false; toast.show("Too many errors, stopping process", 'error', 5000); } } } } // Start the giveaway processing processGiveaways(); } // Initialize the button immediately for better user experience // The button will be visible early, but giveaway automation will wait for DOM to be ready createButton(); // Wait for DOM content to be fully loaded before allowing giveaway automation to start if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => { // Enable the button functionality once DOM is ready enableGiveawayAutomation(); }); } else { // DOM is already loaded, enable immediately enableGiveawayAutomation(); } /** * Enables the giveaway automation functionality once the DOM is ready * This ensures all necessary elements are available before processing begins */ function enableGiveawayAutomation() { const button = document.getElementById('giveaway-auto-enter-btn'); if (button) { // Remove any existing listeners and add the main one button.removeEventListener('click', autoEnterGiveaways); button.addEventListener('click', autoEnterGiveaways); // Enable the button (remove any disabled state) button.disabled = false; button.style.opacity = '1'; button.style.cursor = 'pointer'; } } })();