publish eve_local_watcher.py
This commit is contained in:
parent
3f7d717685
commit
ef950fdccd
1 changed files with 161 additions and 0 deletions
161
eve_local_watcher.py
Normal file
161
eve_local_watcher.py
Normal 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()
|
||||
Loading…
Add table
Add a link
Reference in a new issue