Skip to content

Hi #1024

@miramur46-ui

Description

@miramur46-ui

I want to update the

  1. Auto Login with Multiple Sessions:
       - Add an “Auto Login” button.
       - Add a box to input how many sessions (browser windows) should be logged in.
       - The script should automatically log in to multiple browser sessions using the same credentials.
       - This will allow faster purchases by using multiple sessions in parallel.

  2. Product URL Input and Live Product Fetch:
       - Add a box to input the product page URL.
       - Add a “Fetch Products” button that opens the product page (without login) and fetches all live products from that URL.
       - Display all available products on the page along with their stock status.

  3. Wait Before Checkout:
       - When the product page opens in the browser, add a 1–2 second delay before clicking the checkout button.
       - This is to avoid accidental purchase of a previously selected product that might still be in the checkout cart.

  4. Product Selection and Quantity Handling:
       - From interface, allow the user to select a product and enter the quantity they want to purchase.

  • Based on the selected quantity, the script should open the product in multiple tabs across available sessions and proceed to checkout for each one.
       - All operations should happen using the already logged-in sessions.

...

from flask import Flask, request, jsonify, render_template_string
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.chrome.options import Options
from webdriver_manager.chrome import ChromeDriverManager
from selenium.webdriver.chrome.service import Service
import pyotp
import time
import threading
import json
from flask_cors import CORS

app = Flask(name)
CORS(app)

--- Global State Management for the Selenium Driver ---

This dictionary will hold the driver instance after a successful login.

driver_state = {
'driver': None,
'wait': None,
'session_active': False,
'totp_key': None,
'base_url': "https://gold.razer.com/global/en/gold/catalog/pubg-mobile-uc-code" # Default
}

--- Constants ---

RAZER_LOGIN_URL = "https://razerid.razer.com/?client_id=63c74d17e027dc11f642146bfeeaee09c3ce23d8&redirect=https%3A%2F%2Fgold.razer.com%2Faccount%2Flogin"

--- Helper Function to Initialize WebDriver ---

def init_webdriver(headless=False):
"""Initializes and returns a Chrome WebDriver instance."""
chrome_options = Options()
chrome_options.add_argument("--disable-extensions")
chrome_options.add_argument("--disable-gpu")
chrome_options.add_argument("--no-sandbox")
chrome_options.add_argument("--window-size=1920,1080")
if headless:
chrome_options.add_argument("--headless")
chrome_options.add_argument("user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36")
else:
chrome_options.add_argument("--start-maximized")

chrome_options.page_load_strategy = 'normal'

try:
    service = Service(ChromeDriverManager().install())
    driver = webdriver.Chrome(service=service, options=chrome_options)
    return driver
except Exception as e:
    print(f"Error initializing WebDriver: {e}")
    return None

--- Helper Function to Fetch Live Product Data (for /get_products endpoint) ---

def fetch_product_denominations(product_url):
"""
Fetches live product denomination and stock status from the Razer Gold page
using a dedicated headless driver instance.
"""
driver = None
MAX_WAIT_TIME = 30

try:
    driver = init_webdriver(headless=True)
    if not driver:
         return {"status": "error", "message": "Failed to initialize Chrome Driver for scraping."}
         
    wait = WebDriverWait(driver, MAX_WAIT_TIME) 
    
    driver.get(product_url)

    PRODUCT_LABEL_XPATH = "//div[@id='webshop_step_sku']//div[contains(@class, 'selection-tile')]/label"
    
    wait.until(EC.presence_of_all_elements_located((By.XPATH, PRODUCT_LABEL_XPATH)))
    labels = driver.find_elements(By.XPATH, PRODUCT_LABEL_XPATH)
    
    product_list = []
    for label in labels:
        try:
            text_div = label.find_element(By.XPATH, ".//div[contains(@class, 'selection-tile__text')]")
            uc_text = text_div.text.replace('PUBG', '').replace('UC', '').strip()
            denomination = int(uc_text)
            
            is_out_of_stock = False
            try:
                label.find_element(By.XPATH, ".//span[contains(text(), 'Out of stock')]")
                is_out_of_stock = True
            except:
                pass
            
            price_text = f"USD {round(denomination / 60, 2)}"
            
            product_list.append({
                "denomination": denomination,
                "price": price_text, 
                "out_of_stock": is_out_of_stock 
            })
        except Exception as e:
            # print(f"Skipping product tile due to error in parsing: {e}")
            continue
    
    valid_products = [p for p in product_list if p['denomination'] > 0]
    print(f"--- Successfully Fetched Products ({len(valid_products)} items) ---: {valid_products}")
    
    return {"status": "success", "products": valid_products}

except Exception as e:
    print(f"\n--- CRITICAL ERROR in fetch_product_denominations ---")
    print(f"Error: Failed to fetch products, likely due to timeout or element change. Message: {e}\n")
    return {"status": "error", "message": f"Failed to fetch products: {e}"}

finally:
    try:
        if driver:
            driver.quit()
    except:
        pass

--- Automation Logic: Login Function ---

def start_razer_login(email, password, totp_key, product_url):
"""Performs login and opens the product page, then stores the driver in global state."""

# Clean up previous session if it exists
if driver_state['driver']:
    try:
        driver_state['driver'].quit()
    except:
        pass
    driver_state['driver'] = None
    driver_state['wait'] = None
    driver_state['session_active'] = False
    
driver = init_webdriver(headless=False)
if not driver:
     return {"status": "Error", "message": "Could not start Chrome Driver."}
     
wait = WebDriverWait(driver, 60) 

# Update global state with the new driver and other details
driver_state['driver'] = driver
driver_state['wait'] = wait
driver_state['totp_key'] = totp_key
driver_state['base_url'] = product_url

try:
    # --- A. Login Phase ---
    print("Starting Login...")
    driver.get(RAZER_LOGIN_URL) 

    email_field = wait.until(EC.presence_of_element_located((By.ID, "input-login-email")))
    email_field.send_keys(email)
    driver.find_element(By.ID, "input-login-password").send_keys(password)
    driver.find_element(By.ID, "btn-log-in").click() 

    print("Login button clicked. Waiting 10 seconds for dashboard to load (Increased timeout)...")
    time.sleep(10) # Increased sleep time for stability
    
    # --- B. Navigate to Product Page ---
    driver.get(product_url)
    print("Redirected to product page. Session is now ready for purchase.")
    
    driver_state['session_active'] = True
    
    # NOTE: Returning status here so the Flask thread can send an immediate response.
    return {"status": "Success", "message": "Login successful. Driver is active and on the product page. You can now perform purchases from the web UI."}

except Exception as e:
    print(f"An unexpected error occurred during login: {e}")
    try:
        driver.quit()
    except:
        pass
    driver_state['driver'] = None
    driver_state['wait'] = None
    driver_state['session_active'] = False
    return {"status": "Error", "message": f"Login automation failed. An error occurred: {e}. Check CMD for details."}

--- Automation Logic: Purchase and 2FA (Unchanged) ---

def handle_2fa_input(driver, totp_key):
"""Handles 2FA using JavaScript input and a FRESH TOTP code."""
wait = WebDriverWait(driver, 30)
IFRAME_XPATH = "//iframe[contains(@title, 'Razer OTP') or contains(@src, 'otp?') or @id='otp-iframe-1']"

try:
    # 1. Generate FRESH TOTP CODE
    otp_code = None
    try:
        totp = pyotp.TOTP(totp_key)
        otp_code = totp.now()
    except:
        return False

    if not otp_code or len(otp_code) != 6:
        return False

    # 2. Wait for and switch to the specific IFRAME
    iframe_element = wait.until(EC.presence_of_element_located((By.XPATH, IFRAME_XPATH)))
    driver.switch_to.frame(iframe_element)
    
    # 3. Wait for the first NEW input box
    NEW_INPUT_ID = "otp-input-0"
    wait.until(EC.presence_of_element_located((By.ID, NEW_INPUT_ID)))
    
    # 4. Input each digit using JavaScript
    for i, digit in enumerate(otp_code):
        input_id = f"otp-input-{i}"
        try:
            input_element = driver.find_element(By.ID, input_id)
            driver.execute_script("arguments[0].value = arguments[1];", input_element, digit)
            driver.execute_script("arguments[0].dispatchEvent(new Event('input', { bubbles: true }));", input_element)
        except:
            return False
    
    return True

except Exception as e:
    print(f"2FA Input Critical Failure: {e}")
    return False
    
finally:
    try:
        driver.switch_to.default_content()
    except:
        pass

def perform_purchase_action(driver, wait, product_link, denomination, totp_key):
"""Performs the single purchase action."""
try:
driver.get(product_link)

    # --- STEP 1: Select Product (UC Denomination) ---
    full_uc_text = f"PUBG {denomination} UC"
    uc_label_xpath = f"//div[@id='webshop_step_sku']//div[contains(text(),'{full_uc_text}')]/ancestor::label[1]"
    label_element = wait.until(EC.element_to_be_clickable((By.XPATH, uc_label_xpath)))
    driver.execute_script("arguments[0].click();", label_element)
    
    # --- STEP 2: Select Payment Method (Razer Gold) ---
    razer_gold_xpath = "//div[contains(@class, 'payment-channels-list')]//div[contains(text(), 'Razer Gold')]/ancestor::label[1]"
    razer_gold_label = wait.until(EC.element_to_be_clickable((By.XPATH, razer_gold_xpath)))
    driver.execute_script("arguments[0].click();", razer_gold_label) 
    
    # --- STEP 3: Click Checkout ---
    checkout_button_xpath = "//button[@data-cs-override-id='purchase-webshop-checkout-btn']" 
    checkout_button = wait.until(EC.element_to_be_clickable((By.XPATH, checkout_button_xpath)))
    checkout_button.click()
    
    # --- STEP 4: HANDLE 2FA / FINAL STEP (Pass TOTP key) ---
    handle_2fa_input(driver, totp_key)
    
    return True
    
except Exception as e:
    print(f"ERROR processing {denomination} UC: Purchase automation failed. Message: {e}")
    return False

def _process_purchases_threaded(products_to_buy, product_link):
"""Handles the purchase logic in a separate thread."""
driver = driver_state['driver']
wait = driver_state['wait']
totp_key = driver_state['totp_key']

product_count = 0
total_items = sum(p['quantity'] for p in products_to_buy)

if not driver or not wait or not totp_key:
    print("Thread Error: Driver or essential data missing. Purchase aborted.")
    return 
    
try:
    driver.switch_to.window(driver.window_handles[0])
    
    for product in products_to_buy:
        denomination = str(product['denomination']) 
        quantity = product['quantity']
        
        for i in range(quantity):
            if product_count > 0:
                driver.execute_script("window.open('');")
                driver.switch_to.window(driver.window_handles[-1])
            
            print(f"Processing: {denomination} UC (Item {product_count+1}/{total_items})")
            
            if perform_purchase_action(driver, wait, product_link, denomination, totp_key):
                product_count += 1
            else:
                try:
                    driver.close()
                    driver.switch_to.window(driver.window_handles[0])
                except:
                    pass
    
    if product_count > 0:
        driver.switch_to.window(driver.window_handles[0]) 
        
    print(f"--- Purchase Phase Complete ({product_count} successful purchases) ---")
    
except Exception as e:
    print(f"CRITICAL ERROR during purchase phase: {e}")
    driver_state['session_active'] = False

print(f"Purchase Thread Finished. Total successful: {product_count}")

--- Flask Server Setup (Updated HTML Template) ---

HTML_FORM_TEMPLATE = """
<!doctype html>

<title>Razer Gold Automation</title> <style> body { font-family: Arial, sans-serif; padding: 20px; background-color: #f4f4f4; } .container { background-color: #fff; padding: 20px; border-radius: 8px; box-shadow: 0 0 10px rgba(0,0,0,0.1); max-width: 800px; margin: auto; } h1 { color: #008000; } label { display: block; margin-top: 10px; font-weight: bold; } input[type="text"], input[type="password"], textarea { width: 100%; padding: 8px; margin-top: 5px; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box; } button { background-color: #008000; color: white; padding: 10px 15px; border: none; border-radius: 4px; cursor: pointer; margin-top: 20px; margin-right: 10px; } button:hover { background-color: #006400; } #message { margin-top: 20px; padding: 10px; border-radius: 4px; } .error { background-color: #fdd; border: 1px solid #f00; color: #f00; } .success { background-color: #dfd; border: 1px solid #0f0; color: #008000; } .info { background-color: #e0f2f7; border: 1px solid #b3e5fc; color: #0288d1; } .note { margin-top: 15px; padding: 10px; background-color: #ffffe0; border: 1px solid #cccc00; border-radius: 4px;} .product-list-container { margin-top: 20px; border: 1px solid #ccc; padding: 15px; border-radius: 4px; background-color: #fafafa; } .product-item { display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px; padding-bottom: 5px; border-bottom: 1px dashed #eee; } .product-item:last-child { border-bottom: none; margin-bottom: 0; } .product-info { font-weight: 500; } .product-input input { width: 60px; text-align: center; margin-left: 10px; } .out-of-stock { color: #ff0000; font-weight: bold; } .disabled-item { background-color: #f8f8f8; opacity: 0.6; } .button-group { display: flex; gap: 10px; margin-top: 10px; } .purchase-group { margin-top: 25px; border-top: 2px solid #ccc; padding-top: 15px; } </style>

Razer Gold Automation Control

<div id="status-display" class="info">
    Driver Status: Not Active.
</div>

<form id="controlForm">
    <label for="product_url">Product Page URL:</label>
    <input type="text" id="product_url" name="product_url" value="https://gold.razer.com/global/en/gold/catalog/pubg-mobile-uc-code" required>
    <button type="button" onclick="fetchProducts()">Refresh / Fetch Products</button>
    
    <div class="note">
        <strong>STEP 1: LOGIN (Required for Purchase)</strong>
    </div>

    <label for="email">Razer ID Email:</label>
    <input type="text" id="email" name="email" required>

    <label for="password">Razer ID Password:</label>
    <input type="password" id="password" name="password" required>

    <label for="totp_key">TOTP Secret Key (Base32):</label>
    <input type="text" id="totp_key" name="totp_key" required>
    
    <button type="button" onclick="startLogin()">Start Auto Login</button>
    
    <div class="purchase-group">
        <div class="note">
            <strong>STEP 2: PURCHASE (Requires successful login)</strong><br>
            Items marked <span class="out-of-stock">RED</span> are Out of Stock and will be skipped by the script.
        </div>

        <label>Select Quantities to Purchase:</label>
        <div class="product-list-container">
            <div id="product-list-area">Click 'Refresh / Fetch Products' to load data.</div>
        </div>
        
        <button type="button" onclick="startPurchase()">Perform Purchases</button>
    </div>
</form>

<div id="message"></div>
<script> let availableProducts = []; function updateStatus(status) { const statusDiv = document.getElementById('status-display'); statusDiv.className = status.includes('Active') ? 'success' : 'info'; if (status.includes('Error')) { statusDiv.className = 'error'; } statusDiv.innerHTML = 'Driver Status: ' + status; } // Function to fetch product list from the new backend endpoint function fetchProducts() { const listArea = document.getElementById('product-list-area'); const productUrl = document.getElementById('product_url').value; listArea.innerHTML = 'Fetching products... This may take a few seconds as a headless browser is launched.'; document.getElementById('message').textContent = ''; fetch('/get_products', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ product_url: productUrl }) }) .then(response => response.json()) .then(data => { if (data.status === 'success' && data.products.length > 0) { availableProducts = data.products; renderProductList(data.products); document.getElementById('message').textContent = 'Product list successfully refreshed.'; document.getElementById('message').className = 'success'; } else { const errorMessage = data.message || 'Please check the backend console for the exact error.'; listArea.innerHTML = `Error: Could not fetch products. ${errorMessage}`; document.getElementById('message').textContent = `Product fetch failed: ${errorMessage}`; document.getElementById('message').className = 'error'; } }) .catch(error => { listArea.innerHTML = `Network Error: ${error}. Ensure Python server is running.`; document.getElementById('message').textContent = `Network Error during product fetch: ${error}`; document.getElementById('message').className = 'error'; }); } // Function to dynamically create the list of products on the HTML function renderProductList(products) { const listArea = document.getElementById('product-list-area'); listArea.innerHTML = ''; products.forEach((product, index) => { const itemDiv = document.createElement('div'); itemDiv.className = 'product-item' + (product.out_of_stock ? '' : ''); const infoDiv = document.createElement('div'); infoDiv.className = 'product-info'; let infoText = `PUBG ${product.denomination} UC (${product.price})`; if (product.out_of_stock) { infoText += ` [OUT OF STOCK]`; } infoDiv.innerHTML = infoText; const inputDiv = document.createElement('div'); inputDiv.className = 'product-input'; const inputField = document.createElement('input'); inputField.type = 'number'; inputField.min = '0'; inputField.value = '0'; inputField.id = `qty-${index}`; inputField.setAttribute('data-denomination', product.denomination); inputDiv.appendChild(inputField); itemDiv.appendChild(infoDiv); itemDiv.appendChild(inputDiv); listArea.appendChild(itemDiv); }); } // Function for Auto Login button function startLogin() { const messageDiv = document.getElementById('message'); if (!document.getElementById('email').value || !document.getElementById('password').value || !document.getElementById('totp_key').value || !document.getElementById('product_url').value) { messageDiv.textContent = 'Error: All login fields and Product URL are required for Auto Login.'; messageDiv.className = 'error'; return; } messageDiv.textContent = 'Starting Auto Login... Please wait for a new Chrome window to open...'; messageDiv.className = 'info'; updateStatus('Connecting...'); const loginData = { email: document.getElementById('email').value, password: document.getElementById('password').value, totp_key: document.getElementById('totp_key').value, product_url: document.getElementById('product_url').value }; fetch('/start_login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(loginData) }) .then(response => response.json()) .then(result => { messageDiv.textContent = result.message; messageDiv.className = result.status === 'Error' ? 'error' : 'success'; // Driver Status update for login is now simpler, relying on the success message. // If the console says success, but the front-end says error, the user can still proceed. updateStatus(result.status === 'Success' ? 'Active and Logged In' : 'Error / Check Console'); }) .catch(error => { messageDiv.textContent = 'Network Error during login: ' + error; messageDiv.className = 'error'; updateStatus('Error / Not Active'); }); } // Function for Perform Purchase button function startPurchase() { const messageDiv = document.getElementById('message'); // *** IMPORTANT FIX: Removed the front-end status check *** // We trust the Python console output you provided. Backend check remains. const productsToBuy = []; let totalQuantity = 0; // Iterate through all product quantity inputs availableProducts.forEach((product, index) => { const inputField = document.getElementById(`qty-${index}`); const quantity = parseInt(inputField.value); // Only include in-stock items with quantity > 0 if (quantity > 0 && !product.out_of_stock) { productsToBuy.push({ denomination: product.denomination, quantity: quantity }); totalQuantity += quantity; } }); if (totalQuantity === 0) { messageDiv.textContent = 'Error: Please select at least one IN-STOCK item to purchase.'; messageDiv.className = 'error'; return; } messageDiv.textContent = `Starting purchase for ${totalQuantity} items...`; messageDiv.className = 'info'; updateStatus('Active - Running Purchase...'); const purchaseData = { products: productsToBuy, product_link: document.getElementById('product_url').value }; fetch('/perform_purchase', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(purchaseData) }) .then(response => response.json()) .then(result => { messageDiv.textContent = result.message; messageDiv.className = result.status === 'Error' ? 'error' : 'success'; // Revert status to logged-in or error based on the purchase result updateStatus(result.status === 'Success' ? 'Active and Logged In (Finished Purchase)' : 'Error / Check Console'); }) .catch(error => { messageDiv.textContent = 'Network Error during purchase: ' + error; messageDiv.className = 'error'; updateStatus('Error / Not Active'); }); } window.onload = function() { updateStatus('Not Active'); fetchProducts(); }; </script> """

@app.route('/', methods=['GET'])
def index():
return render_template_string(HTML_FORM_TEMPLATE)

@app.route('/get_products', methods=['POST'])
def get_products():
"""Endpoint to fetch and return live product list from the provided URL."""
data = request.json
product_url = data.get('product_url', driver_state['base_url'])
result = fetch_product_denominations(product_url)
return jsonify(result)

@app.route('/start_login', methods=['POST'])
def handle_login_request():
"""Endpoint to start the login process and keep the driver open."""
data = request.json
email = data.get('email')
password = data.get('password')
totp_key = data.get('totp_key')
product_url = data.get('product_url', driver_state['base_url'])

if not all([email, password, totp_key, product_url]):
    return jsonify({"status": "Error", "message": "Missing email, password, TOTP key, or product URL."}), 400

# Start the login in a separate thread
# We create a function to manage the thread and return its result for the Flask response
def run_login_and_report():
    result = start_razer_login(email, password, totp_key, product_url)
    # In a real app, this would use websockets to push status to the front-end.
    # Here, we just rely on the main function to start the thread.
    print(f"Login Thread Status Report: {result['status']} - {result['message']}")

threading.Thread(target=run_login_and_report).start()

return jsonify({
    "status": "Initiated", 
    "message": "Login script started in the background. Please wait for the Chrome window to open. The Backend session is ready if the console shows success."
})

@app.route('/perform_purchase', methods=['POST'])
def handle_purchase_request():
"""Endpoint to perform the purchase using the existing, logged-in driver."""
data = request.json
products_to_buy = data.get('products')
product_link = data.get('product_link')

# This is the critical BACKEND check.
if not driver_state['session_active'] or not driver_state['driver']:
     return jsonify({"status": "Error", "message": "No active session found in the backend. Please restart 'Start Auto Login'."}), 400
     
if not products_to_buy or not product_link:
    return jsonify({"status": "Error", "message": "Missing product data or product link."}), 400
    
threading.Thread(target=_process_purchases_threaded, args=(products_to_buy, product_link)).start()

return jsonify({
    "status": "Initiated",
    "message": "Purchase script started in the background. New tabs will open in the existing Chrome window. Monitor the console for 2FA messages."
})

if name == 'main':
driver_state['base_url'] = "https://gold.razer.com/global/en/gold/catalog/pubg-mobile-uc-code"

print("Starting Flask Server... Access http://127.0.0.1:5000 in your browser.")
app.run(host='127.0.0.1', port=5000)

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions