Tag: Gemini

  • Auto-clicker skrevet i Python med Gemini

    Auto-clicker skrevet i Python med Gemini

    Jeg elsker clicker- og idle-spil – spil der ofte involverer en masse klikkeri med musen. Og sådan noget kan automatiseres. Der findes allerede et hav af forskellige småprogrammer, der løser “problemet”, men jeg synes det kunne være sjovt at se om Gemini kunne spytte en autoclicker-app up.

    Første prompt til Gemini 3 i Pro indstilling:

    Jeg vil gerne lave en autoclicker-app til windows 11. Hjælp mig med at gøre det.

    Det resulterede i lidt Python-kode, og med udviklerens vigtigste værktøj (CTRL-C + CTRL-V) fik jeg smidt koden i Visual Studio Code for en testkørsel. Allerede første version er faktisk ganske anstændig:

    Meeeeen, den måde at indstille intervallet mellem klikkene er ikke lige min kop te, så det skal på plads:

    Lad mig indstille intervallet mere nøjagtigt. Timer, minutter, sekunder og millisekunder mellem hvert klik.
    Lav en "start" og "stop" knap.

    Efter den rettelse ser app’en sådan ud:

    Gemini foreslog at lave en “Pick Location”-funktion:

    Would you like me to add the "Pick location" feature next, so you can tell the mouse exactly where to click on the screen?

    Interessant og god idé, men så kan vi ligeså godt gøre det endnu sjovere:

    Ja tak, jeg vil gerne have en pick location, men den skal være mere avanceret.
    Det skal være muligt at tilføje flere locations, som så klikkes i rækkefølge i løkke med det valgte interval.

    Det var faktisk nok til at lave en brugbar app, der gør lige præcis hvad jeg har brug for:

    [autoclicker.py]
    import tkinter as tk
    from tkinter import ttk
    import threading
    import time
    import pyautogui
    import keyboard
    
    class AutoClickerApp:
        def __init__(self, root):
            self.root = root
            self.root.title("Advanced Auto Clicker")
            self.root.geometry("600x350") # Gjort bredere for at gøre plads til listen
            self.root.resizable(False, False)
    
            # State variables
            self.running = False
            self.interval = 0.1
            self.coordinates = [] # Liste over (x, y) punkter
            self.coord_index = 0  # Hvilket punkt er vi nået til?
            
            # Variabel til Radiobuttons (0 = Current Location, 1 = Coordinate List)
            self.click_mode = tk.IntVar(value=0)
    
            # UI Setup
            self.setup_ui()
            
            # Start background thread
            self.click_thread = threading.Thread(target=self.clicker_logic)
            self.click_thread.daemon = True
            self.click_thread.start()
    
            # Hotkeys
            keyboard.add_hotkey('F6', self.toggle_clicking)
            keyboard.add_hotkey('F7', self.add_current_location) # Ny hotkey til at gemme punkt
    
        def setup_ui(self):
            # --- Left Side: Settings ---
            left_frame = ttk.Frame(self.root)
            left_frame.pack(side="left", fill="both", expand=True, padx=10, pady=10)
    
            # 1. Click Interval
            interval_frame = ttk.LabelFrame(left_frame, text="Click interval")
            interval_frame.pack(fill="x", pady=5)
            
            input_container = ttk.Frame(interval_frame)
            input_container.pack(padx=5, pady=10)
    
            self.ent_hours = self.create_time_input(input_container, "hours", "0")
            self.ent_mins = self.create_time_input(input_container, "mins", "0")
            self.ent_secs = self.create_time_input(input_container, "secs", "0")
            self.ent_ms = self.create_time_input(input_container, "ms", "100")
    
            # 2. Click Options / Mode
            mode_frame = ttk.LabelFrame(left_frame, text="Cursor position")
            mode_frame.pack(fill="x", pady=10)
    
            rb_current = ttk.Radiobutton(mode_frame, text="Current location", variable=self.click_mode, value=0)
            rb_current.pack(anchor="w", padx=10, pady=2)
    
            rb_list = ttk.Radiobutton(mode_frame, text="Coordinate List (Sequence)", variable=self.click_mode, value=1)
            rb_list.pack(anchor="w", padx=10, pady=2)
    
            # 3. Status & Start/Stop
            control_frame = ttk.Frame(left_frame)
            control_frame.pack(pady=20)
    
            self.status_label = ttk.Label(control_frame, text="Status: STOPPED", foreground="red", font=("Segoe UI", 10, "bold"))
            self.status_label.pack(pady=5)
    
            btn_frame = ttk.Frame(left_frame)
            btn_frame.pack()
    
            self.btn_start = ttk.Button(btn_frame, text="Start (F6)", command=self.start_clicking)
            self.btn_start.pack(side="left", padx=5, ipadx=5)
    
            self.btn_stop = ttk.Button(btn_frame, text="Stop (F6)", command=self.stop_clicking)
            self.btn_stop.pack(side="left", padx=5, ipadx=5)
            self.btn_stop.state(['disabled'])
    
            # --- Right Side: Coordinate List ---
            right_frame = ttk.LabelFrame(self.root, text="Location List")
            right_frame.pack(side="right", fill="both", padx=10, pady=10, expand=True)
    
            # Listbox with Scrollbar
            list_scroll = ttk.Scrollbar(right_frame)
            list_scroll.pack(side="right", fill="y")
    
            self.lst_coords = tk.Listbox(right_frame, yscrollcommand=list_scroll.set, height=10)
            self.lst_coords.pack(side="left", fill="both", expand=True, padx=5, pady=5)
            list_scroll.config(command=self.lst_coords.yview)
    
            # List Controls
            lbl_instr = ttk.Label(right_frame, text="Hover & Press F7 to add", foreground="gray", font=("Segoe UI", 8))
            lbl_instr.pack(side="bottom", pady=2)
    
            btn_clear = ttk.Button(right_frame, text="Clear List", command=self.clear_locations)
            btn_clear.pack(side="bottom", fill="x", padx=5, pady=5)
    
        def create_time_input(self, parent, label_text, default_val):
            frame = ttk.Frame(parent)
            frame.pack(side="left", padx=2)
            entry = ttk.Entry(frame, width=4, justify='right')
            entry.insert(0, default_val)
            entry.pack(side="left")
            lbl = ttk.Label(frame, text=label_text)
            lbl.pack(side="left")
            return entry
    
        def get_total_seconds(self):
            try:
                h = int(self.ent_hours.get() or 0)
                m = int(self.ent_mins.get() or 0)
                s = int(self.ent_secs.get() or 0)
                ms = int(self.ent_ms.get() or 0)
                return (h * 3600) + (m * 60) + s + (ms / 1000.0)
            except ValueError:
                return 0.1
    
        def add_current_location(self):
            """Adds the current mouse x,y to the list"""
            x, y = pyautogui.position()
            self.coordinates.append((x, y))
            self.lst_coords.insert(tk.END, f"Point {len(self.coordinates)}: X={x}, Y={y}")
            # Auto-select the radio button for list mode if user starts adding points
            self.click_mode.set(1) 
    
        def clear_locations(self):
            self.coordinates = []
            self.lst_coords.delete(0, tk.END)
            self.coord_index = 0
    
        def start_clicking(self):
            if not self.running:
                self.interval = self.get_total_seconds()
                self.running = True
                self.status_label.config(text="Status: RUNNING", foreground="green")
                self.btn_start.state(['disabled'])
                self.btn_stop.state(['!disabled'])
    
        def stop_clicking(self):
            if self.running:
                self.running = False
                self.status_label.config(text="Status: STOPPED", foreground="red")
                self.btn_start.state(['!disabled'])
                self.btn_stop.state(['disabled'])
    
        def toggle_clicking(self):
            if self.running:
                self.stop_clicking()
            else:
                self.start_clicking()
    
        def clicker_logic(self):
            while True:
                if self.running:
                    # Mode 1: Coordinate List
                    if self.click_mode.get() == 1 and self.coordinates:
                        # Get current target
                        target_x, target_y = self.coordinates[self.coord_index]
                        
                        pyautogui.click(target_x, target_y)
                        
                        # Move to next point in list, loop back to 0 if at end
                        self.coord_index = (self.coord_index + 1) % len(self.coordinates)
                    
                    # Mode 0: Current Location (or List is empty)
                    else:
                        pyautogui.click()
                    
                    time.sleep(self.interval)
                else:
                    time.sleep(0.01)
    
    if __name__ == "__main__":
        root = tk.Tk()
        try:
            from ctypes import windll
            windll.shcore.SetProcessDpiAwareness(1)
        except:
            pass
        app = AutoClickerApp(root)
        root.mainloop()
    [### Sådan bruger du den nye "Pick Location" funktion:
    
    Start scriptet.
    
    Bevæg musen hen over det sted på skærmen, du vil klikke på.
    
    Tryk på F7. Du vil se koordinaterne (f.eks. X=500, Y=300) dukke op i listen til højre.
    
    Gentag for så mange punkter, du vil.
    
    Sørg for, at "Coordinate List" er valgt (det sker automatisk, når du tilføjer punkter).
    
    Tryk Start (F6).
    
    Nu vil musen hoppe til Punkt 1, klikke, vente tiden ud, hoppe til Punkt 2, klikke, osv. Når den når bunden af listen, starter den forfra ved Punkt 1.]

    Den “færdige” app (den kan jo stadig forbedres) skrevet på 5 minutter i Google Gemini 3 Pro. Og nu skal jeg lige finde et spil frem, og snyde som en gris!

  • QR-generator i Google AI Studio

    QR-generator i Google AI Studio

    For at lege lidt med Google AI studio besluttede jeg mig for at lave en QR-generator.

    Kravene til generatoren:

    • Mulighed for at lave overlay med runde/firkantede logoer
    • Forskellige former koder: Tekst, links, visitkort, osv, men også en mulighed for at lave MobilePay-koder til erhvervsnumre.
    • Overholde standarderne for QR-koder (bl.a. quiet zone og error correction)
    • Mulighed for at downloade koden som PNG-billede eller SVG-vektor.

    Jeg skriver (for det meste) på dansk, når jeg prompter, så første skud til Gemini i Google AI Studio lød:

    Avanceret QR generator, med fuld implementering af fx bwipjs.
    Mulighed for at lave overlays (både firkantede og runde) på QR-koder.
    Mulighed for at uploade logo til overlay.
    Mulighed for alle "typer" af QR-koder (wifi, contacts, url, tel, og alle de andre).
    Download som SVG eller PNG.

    For at tilføje MobilePay tog jeg linket den “gamle” QR-generator hos MobilePay genererede og bad Gemini lave en kode-generator udfra den:

    Tilføj muligheden for at lave et MobilePay-link (til erhvervsnumre).
    
    Brug denne URL som skabelon:
    https://qr.mobilepay.dk/paymentlink?phone=111111&amount=99.99&comment=Just%20testing&lock=1
    
    phone=6 cifre
    amount=beløb i kroner
    comment=Tekst/Kommentar (valgfrit)
    lock=1/0 (gør det muligt at låse beløb og kommentar)

    Efter lidt sludren frem og tilbage (en hurtig optælling siger 8 prompts totalt) er jeg endt med en generator, der kan hvad jeg bad den om.

    Men det er jo ikke så sjovt, hvis jeg er den eneste, der kan få glæde af den, så for at dele den med andre, skal den på en server, hvor den kan køre. Fra AI Studio kan du deploye din app på Googles servere og lade den leve videre der.

    Apps lavet i Google AI Studio kan køre på Google Cloud Run (kræver en betalingskonto). Min app blev deployet på adressen:

    qr-generator-510735677258.us-west1.run.app

    Hvis det hele skal gøres ekstra lækkert – og det skal det – kan man bruge sit eget domæne, så app’en kan få en lidt pænere URL.

    Det gøres med Domain mappings i Google Cloud Console:

    For at kunne bruge eget domæne eller subdomæner, i mit tilfælde whatever.christianjohannesen.app, skal det være verificeret af Google. Det gøres via Google Search Console.

    Efter domain mapping’en er sat op hos Google, skal subdomænet oprettes som CNAME (i mit tilfælde) hos Cloudflare og peges mod Google-land.

    Efter kort tid er alt på plads, og app’en kører på adressen:

    Her er hvad jeg har brugt for at lave app’en og gøre den offentlig tilgængelig:

    * Relateret til domain mapping – ikke nødvendigt