#!/usr/bin/env python3 """Self-installing / self-updating entry point — `python update.py`. Run it once (the install one-liner does this for you). From then on a Windows Scheduled Task runs it automatically **at logon and once a day**, so the watchers stay up to date and running with zero further thought. Each run it: 1. makes config.ini on first run (webhook prefilled; gitignored so your edits and OCR snip survive every update), 2. `git pull`s the latest watcher code, 3. installs/upgrades deps only when something changed, 4. (re)starts the watchers ONLY if the code changed or they aren't running — so the daily run never interrupts an active session for nothing, 5. ensures the daily+logon Scheduled Task exists (idempotent). First time on a new PC (or just paste the install one-liner): git clone https://git.armoredarmadillo.com/brockdarnold/eve-watcher.git cd eve-watcher python update.py python eve_orehold_watcher.py --snip # one-time GUI step for OCR hold alerts """ import configparser import os import shutil import subprocess import sys HERE = os.path.dirname(os.path.abspath(__file__)) WIN = os.name == "nt" TASK = "EveWatcher" WATCHER_SCRIPTS = ("eve_combat_watcher.py", "eve_chat_watcher.py", "eve_orehold_watcher.py") def run(cmd, **kw): print(f" $ {' '.join(cmd)}") return subprocess.run(cmd, cwd=HERE, **kw) def ps(script): """Run a PowerShell snippet, return (rc, stdout).""" r = subprocess.run(["powershell", "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", script], cwd=HERE, capture_output=True, text=True) return r.returncode, (r.stdout or "").strip() def ensure_config(): cfg = os.path.join(HERE, "config.ini") first = not os.path.exists(cfg) if first: shutil.copyfile(os.path.join(HERE, "config.ini.example"), cfg) print("• created config.ini from example (webhook prefilled).") else: print("• config.ini exists — your settings + OCR snip left untouched.") # heal a UTF-8 BOM left by the old PowerShell installer (breaks configparser) with open(cfg, "rb") as fh: raw = fh.read() if raw.startswith(b"\xef\xbb\xbf"): with open(cfg, "wb") as fh: fh.write(raw[3:]) print("• stripped a UTF-8 BOM from config.ini (old-installer leftover).") return cfg, first def git_pull(): """Return True if new commits were pulled.""" if not os.path.isdir(os.path.join(HERE, ".git")): print("• not a git clone — can't auto-update. Reinstall via:\n" " git clone https://git.armoredarmadillo.com/brockdarnold/eve-watcher.git") return False r = subprocess.run(["git", "pull", "--ff-only"], cwd=HERE, capture_output=True, text=True) out = (r.stdout + r.stderr).strip() print(f" git: {out.splitlines()[-1] if out else '(no output)'}") if r.returncode != 0: print("! git pull failed (local edits to tracked files?). config.ini is safe.") return False return "Already up to date" not in out and "Already up-to-date" not in out def deps(): req = os.path.join(HERE, "requirements.txt") if os.path.exists(req): run([sys.executable, "-m", "pip", "install", "--quiet", "-r", req]) def watchers_running(): if not WIN: return 0 _, out = ps("(Get-CimInstance Win32_Process -Filter \"Name='pythonw.exe'\" " "| Where-Object { $_.CommandLine -like '*watcher*' }).Count") try: return int(out or "0") except ValueError: return 0 def stop_watchers(): ps("Get-CimInstance Win32_Process -Filter \"Name='pythonw.exe'\" | " "Where-Object { $_.CommandLine -like '*watcher*' } | " "ForEach-Object { Stop-Process -Id $_.ProcessId -Force }") def start_watchers(cfg_path): if not WIN: print("• non-Windows — start watchers manually (this is a Windows tool).") return stop_watchers() run(["powershell", "-ExecutionPolicy", "Bypass", "-File", os.path.join(HERE, "start-all.ps1")]) cp = configparser.ConfigParser() cp.read(cfg_path, encoding="utf-8-sig") if cp.get("watcher", "mode", fallback="ocr") == "ocr" and \ not cp.get("ocr", "region", fallback="").strip(): print("\n >> ONE-TIME (per PC) for hold/compress/stall alerts, run once:\n" " python eve_orehold_watcher.py --snip\n" " (drag a box around the Ore Hold fill bar; saved forever after.)") def ensure_task(): """Register a daily + at-logon Scheduled Task so this runs itself forever. Idempotent: only creates it if missing.""" if not WIN: return rc, _ = ps(f"Get-ScheduledTask -TaskName '{TASK}' -ErrorAction SilentlyContinue") if rc == 0: print(f"• Scheduled Task '{TASK}' already set (auto-update at logon + daily).") return pyw = os.path.join(os.path.dirname(sys.executable), "pythonw.exe") if not os.path.exists(pyw): pyw = "pythonw.exe" here = HERE.replace("'", "''") pyw_e = pyw.replace("'", "''") snippet = ( f"$a = New-ScheduledTaskAction -Execute '{pyw_e}' " f"-Argument '\"{here}\\update.py\"' -WorkingDirectory '{here}';" "$t1 = New-ScheduledTaskTrigger -AtLogOn;" "$t2 = New-ScheduledTaskTrigger -Daily -At 5am;" "$s = New-ScheduledTaskSettingsSet -StartWhenAvailable " "-AllowStartIfOnBatteries -DontStopIfGoingOnBatteries " "-ExecutionTimeLimit (New-TimeSpan -Hours 1);" "$p = New-ScheduledTaskPrincipal -UserId $env:USERNAME " "-LogonType Interactive -RunLevel Limited;" f"Register-ScheduledTask -TaskName '{TASK}' -Action $a -Trigger $t1,$t2 " "-Settings $s -Principal $p -Force | Out-Null") rc, out = ps(snippet) if rc == 0: print(f"• Scheduled Task '{TASK}' created — auto-updates at logon + daily 5am. " "You never have to run this again.") else: print(f"! couldn't register Scheduled Task: {out}") def main(): print("=== Eve watcher setup / auto-update ===") cfg, first = ensure_config() changed = git_pull() if changed or first: deps() if changed or first or watchers_running() == 0: start_watchers(cfg) else: print("• up to date and watchers already running — nothing to restart.") ensure_task() print("\nDone. Hands-off from here: it updates + keeps itself running automatically.") if __name__ == "__main__": main()