Upload files to "Operation Shadow Watcher"
New version applying error fixes (old version could not work).
This commit is contained in:
157
Operation Shadow Watcher/batch.py
Normal file
157
Operation Shadow Watcher/batch.py
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
import time
|
||||||
|
import os
|
||||||
|
import cv2
|
||||||
|
from watchdog.observers import Observer
|
||||||
|
from watchdog.events import FileSystemEventHandler
|
||||||
|
from plyer import notification
|
||||||
|
from datetime import datetime
|
||||||
|
from threading import Thread, Lock
|
||||||
|
|
||||||
|
# ---------------- CONFIG ----------------
|
||||||
|
|
||||||
|
WATCH_DIR = r"C:\Program Files\Renato Software\Senso.Cloud.Client"
|
||||||
|
LOG_FILE = "file_changes_log.txt"
|
||||||
|
|
||||||
|
BATCH_WINDOW = 10 # Seconds between notification flushes
|
||||||
|
LATEST_COUNT = 3 # How many recent changes to show if no priority items
|
||||||
|
MAX_PRIORITY_SHOWN = 10 # Max priority items shown per notification
|
||||||
|
MAX_NOTIFY_LENGTH = 256 # Windows balloon tip max length
|
||||||
|
|
||||||
|
# 🔥 Priority keywords (case-insensitive)
|
||||||
|
PRIORITY_WORDS = ["MOD", "REMOTECONTROL", "PEER2PEER", "REMOTESCREEN"]
|
||||||
|
|
||||||
|
# ---------------- STATE ----------------
|
||||||
|
|
||||||
|
change_buffer = []
|
||||||
|
buffer_lock = Lock()
|
||||||
|
camera_lock = Lock()
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------- CAMERA ----------------
|
||||||
|
|
||||||
|
def blink_camera_light(blink_time=0.7):
|
||||||
|
try:
|
||||||
|
cap = cv2.VideoCapture(0, cv2.CAP_DSHOW)
|
||||||
|
if cap.isOpened():
|
||||||
|
ret, _ = cap.read()
|
||||||
|
if ret:
|
||||||
|
time.sleep(blink_time)
|
||||||
|
cap.release()
|
||||||
|
cv2.destroyAllWindows()
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[❌] Camera blink failed: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
def async_camera_blink():
|
||||||
|
def _blink():
|
||||||
|
with camera_lock: # prevents webcam thread flooding
|
||||||
|
blink_camera_light()
|
||||||
|
Thread(target=_blink, daemon=True).start()
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------- LOGGING ----------------
|
||||||
|
|
||||||
|
def log_change(action, filepath):
|
||||||
|
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||||
|
entry = f"[{timestamp}] {action.upper()}: {filepath}\n"
|
||||||
|
with open(LOG_FILE, "a", encoding="utf-8") as f:
|
||||||
|
f.write(entry)
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------- PRIORITY ----------------
|
||||||
|
|
||||||
|
def priority_score(entry: str) -> int:
|
||||||
|
upper = entry.upper()
|
||||||
|
return sum(1 for word in PRIORITY_WORDS if word in upper)
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------- NOTIFICATION FLUSH ----------------
|
||||||
|
|
||||||
|
def flush_notifications():
|
||||||
|
while True:
|
||||||
|
time.sleep(BATCH_WINDOW)
|
||||||
|
|
||||||
|
with buffer_lock:
|
||||||
|
if not change_buffer:
|
||||||
|
continue
|
||||||
|
|
||||||
|
scored = [(priority_score(c), i, c) for i, c in enumerate(change_buffer)]
|
||||||
|
priority_items = [item for item in scored if item[0] > 0]
|
||||||
|
|
||||||
|
if priority_items:
|
||||||
|
# sort by descending priority then original order
|
||||||
|
priority_items.sort(key=lambda x: (-x[0], x[1]))
|
||||||
|
shown = [c for _, _, c in priority_items[:MAX_PRIORITY_SHOWN]]
|
||||||
|
omitted = len(priority_items) > MAX_PRIORITY_SHOWN
|
||||||
|
else:
|
||||||
|
shown = change_buffer[-LATEST_COUNT:]
|
||||||
|
omitted = len(change_buffer) > len(shown)
|
||||||
|
|
||||||
|
change_buffer.clear()
|
||||||
|
|
||||||
|
message = "\n".join(shown)
|
||||||
|
if omitted:
|
||||||
|
message += "\n..."
|
||||||
|
|
||||||
|
# 🔹 Truncate message safely for Windows balloon notifications
|
||||||
|
if len(message) > MAX_NOTIFY_LENGTH:
|
||||||
|
message = message[:MAX_NOTIFY_LENGTH - 3] + "..."
|
||||||
|
|
||||||
|
notification.notify(
|
||||||
|
title="File Changes Detected",
|
||||||
|
message=message,
|
||||||
|
timeout=5
|
||||||
|
)
|
||||||
|
|
||||||
|
print("🔔 Notification sent:")
|
||||||
|
print(message)
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------- FILE WATCHER ----------------
|
||||||
|
|
||||||
|
class ChangeHandler(FileSystemEventHandler):
|
||||||
|
def on_any_event(self, event):
|
||||||
|
action = event.event_type.capitalize()
|
||||||
|
filepath = event.src_path
|
||||||
|
filename = os.path.basename(filepath)
|
||||||
|
|
||||||
|
# Write log immediately
|
||||||
|
log_change(action, filepath)
|
||||||
|
|
||||||
|
# Flash camera immediately (async, safe)
|
||||||
|
async_camera_blink()
|
||||||
|
|
||||||
|
entry = f"{action}: {filename}"
|
||||||
|
|
||||||
|
with buffer_lock:
|
||||||
|
change_buffer.append(entry)
|
||||||
|
|
||||||
|
print(f"[{action}] {filepath}")
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------- MAIN ----------------
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
print(f"👀 Monitoring: {WATCH_DIR}")
|
||||||
|
print(f"📝 Logging to: {os.path.abspath(LOG_FILE)}")
|
||||||
|
print(f"🔥 Priority words: {', '.join(PRIORITY_WORDS)}")
|
||||||
|
|
||||||
|
# Ensure log file exists
|
||||||
|
if not os.path.exists(LOG_FILE):
|
||||||
|
with open(LOG_FILE, "w", encoding="utf-8") as f:
|
||||||
|
f.write("=== File Change Log ===\n")
|
||||||
|
|
||||||
|
observer = Observer()
|
||||||
|
observer.schedule(ChangeHandler(), WATCH_DIR, recursive=True)
|
||||||
|
observer.start()
|
||||||
|
|
||||||
|
# Start fixed-interval notification flusher
|
||||||
|
Thread(target=flush_notifications, daemon=True).start()
|
||||||
|
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
time.sleep(1)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
observer.stop()
|
||||||
|
|
||||||
|
observer.join()
|
||||||
Reference in New Issue
Block a user