publish eve_local_watcher.py

This commit is contained in:
brockdarnold 2026-06-14 16:08:36 +00:00
parent 3f7d717685
commit ef950fdccd

161
eve_local_watcher.py Normal file
View file

@ -0,0 +1,161 @@
#!/usr/bin/env python3
"""Local watcher — 'someone just entered Local' alerts for isolated systems.
EVE has no API for who's in Local, and the chat logs only record *messages*, not who
jumps in/out. So this reads your **Local member list** off the screen and pings you
when the pilot count rises exactly what matters when you're mining somewhere quiet
like Solitude and a stranger lands in system. Best in low-population systems (the whole
member list is visible); in a busy hub it just tracks what's on screen.
Only runs during a mining session (`!mining on` in Discord). Point it at your Local
window once: python eve_local_watcher.py --snip (or send Brock a screenshot and he
sets [local] region in config.ini). Auto-found if the window shows the word 'Local'.
python eve_local_watcher.py # normal (waits for !mining on)
python eve_local_watcher.py --snip # box your Local member-list window
python eve_local_watcher.py --test # read once and print the pilot count
"""
import os
import re
import sys
import time
HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, HERE)
import eve_orehold_watcher as w # load_config, notify, grab_region, heartbeat, bot_mining, snip_region
def count_pilots(text):
"""Count name-like lines in the OCR'd member list (a row per pilot)."""
n = 0
for line in text.splitlines():
s = line.strip()
if len(s) < 3:
continue
if s.lower().startswith("local"): # channel header, not a pilot
continue
if sum(c.isalpha() for c in s) >= 2: # looks like a name
n += 1
return n
def save_local_region(cp, l, t, wd, ht):
if not cp.has_section("local"):
cp.add_section("local")
cp["local"]["region"] = f"{l},{t},{wd},{ht}"
with open(w.CONFIG_PATH, "w") as f:
cp.write(f)
def detect_local_region(cp):
"""Best-effort: find the Local window by locating the 'Local' label, take the
column beneath it as the member list. Falls back to None (use --snip)."""
import mss
import pytesseract
from PIL import Image
tcmd = cp.get("ocr", "tesseract_cmd", fallback="").strip()
if tcmd:
pytesseract.pytesseract.tesseract_cmd = tcmd
try:
with mss.mss() as sct:
mon = sct.monitors[0]
raw = sct.grab(mon)
img = Image.frombytes("RGB", raw.size, raw.bgra, "raw", "BGRX")
data = pytesseract.image_to_data(img, output_type=pytesseract.Output.DICT)
except Exception as e:
print(f"[local] detect error: {e}")
return None
for i in range(len(data["text"])):
if data["text"][i].strip().lower() == "local":
x = mon["left"] + data["left"][i] - 10
y = mon["top"] + data["top"][i] - 6
wd = max(220, data["width"][i] + 220)
ht = 600 # capture the list beneath the label
save_local_region(cp, x, y, wd, ht)
print(f"[local] auto-located Local window near {x},{y}")
return [x, y, wd, ht]
return None
def get_region(cp):
rs = cp.get("local", "region", fallback="").strip() if cp.has_section("local") else ""
if rs:
return [int(v) for v in rs.split(",")]
print("[local] no saved region — trying to auto-locate the Local window...")
return detect_local_region(cp)
def main():
cp = w.load_config()
if "--snip" in sys.argv:
r = w.snip_region("Drag a box around your whole Local member-list window. Esc to cancel.")
if r:
save_local_region(cp, *r)
print(f"Saved Local region {r} to config.ini")
else:
print("Cancelled.")
return
import pytesseract
tcmd = cp.get("ocr", "tesseract_cmd", fallback="").strip()
if tcmd:
pytesseract.pytesseract.tesseract_cmd = tcmd
poll = cp.getint("local", "poll_secs", fallback=8) if cp.has_section("local") else 8
cooldown = cp.getint("local", "cooldown_secs", fallback=30) if cp.has_section("local") else 30
if "--test" in sys.argv:
region = get_region(cp)
if not region:
print("No Local region. Run --snip (or send a screenshot).")
return
print("pilots:", count_pilots(pytesseract.image_to_string(w.grab_region(region))))
return
print(f"[local] started; poll {poll}s (waits for !mining on)")
region = None
prev = None
pending = None
pend_n = 0
last_alert = 0.0
while True:
if not w.bot_mining(cp):
prev = None
time.sleep(15)
continue
if region is None:
region = get_region(cp)
if region is None:
print("[local] Local window not found — open it / run --snip. Retrying...")
time.sleep(max(poll, 20))
continue
w.heartbeat(cp, "local")
try:
count = count_pilots(pytesseract.image_to_string(w.grab_region(region)))
now = time.time()
if prev is None:
prev = count
print(f"[local] baseline {count} pilots")
elif count > prev:
# confirm over 2 reads to shrug off OCR jitter
pend_n = pend_n + 1 if pending == count else 1
pending = count
if pend_n >= 2 and now - last_alert > cooldown:
w.notify(cp, "Someone entered Local",
f"+{count - prev} in Local — {count} pilots now (was {prev}). "
"Check d-scan / watch for a visitor on your field.",
priority="high", tags="eyes,warning")
last_alert = now
prev = count
pending, pend_n = None, 0
else:
if count < prev:
prev = count # someone left; rebaseline down
pending, pend_n = None, 0
except Exception as e:
print(f"[local] error: {e}")
time.sleep(poll)
if __name__ == "__main__":
main()