Does this python script look good to you?
16:51 30 Mar 2026

Wondering if there's any room for improvement.. does it look ok to you? It's supposed to check the webpage every second or so and alert me if there's change. Trying to make it as human-like as possible and not to stressful on the webpage it's checking. Any advice appreciated.

ATM it works but every minute or so there's a few seconds of ERROR status.

import requests, tkinter as tk, os, random, time, json, subprocess
from datetime import datetime
from threading import Thread, Lock
from tkinter import ttk

# -------------------------
# CONFIGURATION
# -------------------------

URL = "https://formdt1.com/products/t1titanium.js"  # <-- CHANGE THIS
DEBUG = True
FLASH_DURATION = 500  # milliseconds
STATE_FILE = "last_state.json"  # persistent state file

# -------------------------
# USER AGENTS
# -------------------------

USER_AGENTS = [
    "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120 Safari/537.36",
    "Mozilla/5.0 (Macintosh; Intel Mac OS X 13_0) AppleWebKit/537.36 Chrome/119 Safari/537.36",
    "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 Chrome/118 Safari/537.36"
]

# -------------------------
# SESSION
# -------------------------

session = requests.Session()

# -------------------------
# STATE VARIABLES
# -------------------------

data_state = {}         # Current availability
last_state = {}         # Previous availability
newly_available = set() # Track newly available variants
state_lock = Lock()

status_text = "Starting..."
system_status = "STARTING"
last_update = "N/A"
last_check_time = None
etag = None
error_count = 0

# -------------------------
# ALERT FUNCTION (non-blocking)
# -------------------------

def alert(msg="Test alert"):
    """
    Sends a system notification + sound (non-blocking)
    """
    subprocess.Popen(['notify-send', f'⚠️ {msg}'])
    subprocess.Popen(['paplay', '/usr/share/sounds/freedesktop/stereo/alarm-clock-elapsed.oga'])
    if DEBUG:
        print(f"[ALERT] {msg}")

# -------------------------
# VARIANT EXTRACTION
# -------------------------

def extract_variants(data):
    """
    Converts JSON into: { "Variant Name": True/False }
    """
    return {v["title"]: v["available"] for v in data.get("variants", [])}

# -------------------------
# LOAD/SAVE STATE
# -------------------------

def load_state():
    global last_state
    if os.path.exists(STATE_FILE):
        try:
            with open(STATE_FILE, "r") as f:
                last_state.update(json.load(f))
        except Exception as e:
            print(f"[WARN] Failed to load state: {e}")

def save_state():
    with state_lock:
        try:
            with open(STATE_FILE, "w") as f:
                json.dump(data_state, f)
        except Exception as e:
            print(f"[WARN] Failed to save state: {e}")

load_state()

# -------------------------
# MONITOR THREAD
# -------------------------

def monitor():
    global data_state, last_state, newly_available
    global status_text, system_status, last_update, last_check_time, etag, error_count

    while True:
        try:
            headers = {
                "User-Agent": random.choice(USER_AGENTS),
                "Accept": random.choice([
                    "application/json, text/javascript, */*; q=0.01",
                    "*/*"
                ]),
                "Accept-Language": random.choice(["en-US,en;q=0.9", "en-GB,en;q=0.8"]),
                "Referer": URL,
                "Cache-Control": random.choice(["no-cache", "max-age=0"]),
                "Connection": "keep-alive"
            }

            if etag:
                headers["If-None-Match"] = etag

            if DEBUG:
                print(f"[{datetime.now()}] Checking server...")

            r = session.get(URL, headers=headers, timeout=3)
            last_check_time = datetime.now()

            # -------------------------
            # HANDLE RATE LIMIT / BLOCK
            # -------------------------
            if r.status_code == 429:
                system_status = "RATE LIMITED"
                status_text = "Too many requests"
                error_count += 1
                time.sleep(min(60, 2 ** error_count))
                continue
            elif r.status_code == 403:
                system_status = "BLOCKED"
                status_text = "Access denied"
                error_count += 1
                time.sleep(min(60, 2 ** error_count))
                continue

            # -------------------------
            # SUCCESS CASES
            # -------------------------
            elif r.status_code == 304:
                system_status = "OK"
                status_text = "NO CHANGE (cached)"
                last_update = str(datetime.now())
                error_count = 0

            elif r.status_code == 200:
                system_status = "OK"
                error_count = 0
                etag = r.headers.get("ETag")

                try:
                    json_data = r.json()
                except Exception:
                    system_status = "ERROR"
                    status_text = "INVALID JSON"
                    time.sleep(2)
                    continue

                current = extract_variants(json_data)

                with state_lock:
                    newly_available.clear()
                    if not last_state:
                        last_state = current.copy()
                        data_state = current
                        status_text = "INIT"
                    else:
                        changes = []
                        for name, available in current.items():
                            if name in last_state and available != last_state[name]:
                                if available:
                                    changes.append(f"{name} AVAILABLE")
                                    newly_available.add(name)
                        if changes:
                            msg = " | ".join(changes)
                            status_text = msg
                            alert(msg)
                        data_state = current
                        last_state = current.copy()
                        save_state()

                last_update = str(datetime.now())

            else:
                system_status = "ERROR"
                status_text = f"HTTP {r.status_code}"
                error_count += 1
                time.sleep(2)
                continue

        except Exception as e:
            system_status = "ERROR"
            status_text = f"ERROR: {e}"
            error_count += 1
            if DEBUG:
                print(f"[ERROR] {e}")
            time.sleep(2)
            continue

        # -------------------------
        # HUMAN-LIKE DELAY
        # -------------------------
        delay = random.uniform(0.8, 2.2)
        time.sleep(delay)

# -------------------------
# GUI SETUP
# -------------------------

root = tk.Tk()
root.title("Variant Monitor")
root.geometry("1000x520")
root.configure(bg="#f0f0f0")

canvas = tk.Canvas(root, bg="#f0f0f0", highlightthickness=0)
scrollbar = ttk.Scrollbar(root, orient="vertical", command=canvas.yview)
frame = tk.Frame(canvas, bg="#f0f0f0")

frame.bind("", lambda e: canvas.configure(scrollregion=canvas.bbox("all")))
canvas.create_window((0, 0), window=frame, anchor="nw")
canvas.configure(yscrollcommand=scrollbar.set)
canvas.pack(side="left", fill="both", expand=True)
scrollbar.pack(side="right", fill="y")

FONT_TITLE = ("Arial", 12, "bold")
FONT_TEXT = ("Arial", 11)

# -------------------------
# STATUS COLOR
# -------------------------

def status_color():
    if system_status == "OK":
        return "#28a745"
    elif system_status == "RATE LIMITED":
        return "#ff9900"
    elif system_status == "BLOCKED":
        return "#ff0000"
    else:
        return "#666666"

# -------------------------
# TEST ALERT BUTTON
# -------------------------

btn = tk.Button(root, text="Test Alert",
                command=lambda: alert("Test alert"),
                bg="#ff9500", fg="black",
                font=("Arial", 12, "bold"))
btn.place(x=850, y=10)

# -------------------------
# GUI UPDATE LOOP
# -------------------------

def update_ui():
    with state_lock:
        local_data = data_state.copy()
        local_new = newly_available.copy()

    for widget in frame.winfo_children():
        widget.destroy()

    tk.Label(frame, text=f"System Status: {system_status}",
             fg=status_color(), bg="#f0f0f0", font=FONT_TITLE).pack(anchor="w")

    # Legend
    legend = tk.Frame(frame, bg="#f0f0f0")
    legend.pack(anchor="w", pady=(5,10))
    tk.Label(legend, text="Legend:", bg="#f0f0f0", font=FONT_TITLE).pack(side="left")
    tk.Label(legend, text=" ● OK", fg="#28a745", bg="#f0f0f0").pack(side="left")
    tk.Label(legend, text=" ● RATE LIMITED", fg="#ff9900", bg="#f0f0f0").pack(side="left")
    tk.Label(legend, text=" ● BLOCKED", fg="#ff0000", bg="#f0f0f0").pack(side="left")
    tk.Label(legend, text=" ● ERROR", fg="#666666", bg="#f0f0f0").pack(side="left")

    tk.Label(frame, text=f"Last update: {last_update}", bg="#f0f0f0").pack(anchor="w")
    tk.Label(frame, text=f"Status: {status_text}", bg="#f0f0f0").pack(anchor="w")

    if last_check_time:
        seconds = (datetime.now() - last_check_time).total_seconds()
        tk.Label(frame, text=f"Time since last check: {seconds:.1f}s", bg="#f0f0f0").pack(anchor="w")

    tk.Label(frame, text="", bg="#f0f0f0").pack()

    for idx, (name, available) in enumerate(sorted(local_data.items(), key=lambda x: not x[1])):
        bg = "#e8e8e8" if idx % 2 == 0 else "#dcdcdc"
        color = "#28a745" if available else "#ff4d4d"
        text = "AVAILABLE" if available else "NOT AVAILABLE"

        row = tk.Frame(frame, bg=bg)
        row.pack(fill="x", padx=5, pady=1)

        tk.Label(row, text=name, bg=bg, fg="black",
                 font=FONT_TITLE, anchor="w", width=70).pack(side="left")

        status_lbl = tk.Label(row, text=text, bg=bg, fg=color, font=FONT_TITLE)
        status_lbl.pack(side="right")

        # Flash if newly available
        if name in local_new:
            def flash(lbl=status_lbl, original=bg):
                lbl.config(bg="#28a745")
                root.after(FLASH_DURATION, lambda: lbl.config(bg=original))
            flash()

    root.after(300, update_ui)

# -------------------------
# START PROGRAM
# -------------------------

Thread(target=monitor, daemon=True).start()
update_ui()
root.mainloop()
javascript python html