Files
AWatch-rus/aw-server/aw-worktime-ui-bridge.py
T

139 lines
4.1 KiB
Python

#!/usr/bin/env python3
import json
import os
import urllib.error
import urllib.request
from datetime import datetime, timezone
AW_URL = os.environ.get("AW_SERVER_URL", "http://127.0.0.1:5600")
HOST = os.environ.get("AW_WORKTIME_HOST", "SHARKON2025")
STATE_PATH = os.environ.get(
"AW_WORKTIME_UI_BRIDGE_STATE",
"/var/lib/activitywatch/aw-worktime-ui-bridge-state.json",
)
TIMEOUT = float(os.environ.get("AW_WORKTIME_UI_BRIDGE_TIMEOUT", "20"))
SESSIONS_BUCKET = f"aw-worktime-sessions_{HOST}"
AFK_BUCKET = f"aw-watcher-afk_{HOST}"
WINDOW_BUCKET = f"aw-watcher-window_{HOST}"
def _req(method: str, path: str, payload=None):
data = None
headers = {}
if payload is not None:
data = json.dumps(payload, ensure_ascii=False).encode("utf-8")
headers["Content-Type"] = "application/json"
req = urllib.request.Request(AW_URL + path, data=data, headers=headers, method=method)
with urllib.request.urlopen(req, timeout=TIMEOUT) as r:
raw = r.read()
if not raw:
return None
return json.loads(raw.decode("utf-8"))
def ensure_bucket(bucket_id: str, event_type: str, client: str):
payload = {"client": client, "type": event_type, "hostname": HOST}
try:
_req("POST", f"/api/0/buckets/{bucket_id}", payload)
except urllib.error.HTTPError as e:
if e.code != 304:
raise
def load_state():
try:
with open(STATE_PATH, "r", encoding="utf-8") as f:
data = json.load(f)
if isinstance(data, dict) and "last_ts" in data:
return data
except FileNotFoundError:
pass
except json.JSONDecodeError:
pass
return {"last_ts": "1970-01-01T00:00:00Z"}
def save_state(state):
os.makedirs(os.path.dirname(STATE_PATH), exist_ok=True)
tmp = STATE_PATH + ".tmp"
with open(tmp, "w", encoding="utf-8") as f:
json.dump(state, f, ensure_ascii=False)
os.replace(tmp, STATE_PATH)
def to_iso_utc(ts):
if ts.endswith("Z"):
return ts
return ts.replace("+00:00", "Z")
def build_window_title(users, active_count):
if not users:
return "RDP idle"
return f"RDP active ({active_count}): " + ", ".join(users)
def transform(events):
out_afk = []
out_win = []
last_ts = None
for e in events:
ts = e.get("timestamp")
if not ts:
continue
duration = float(e.get("duration", 0.0))
data = e.get("data") or {}
active_users = data.get("activeUsers") or []
active_count = int(data.get("activeCount", len(active_users)))
is_active = active_count > 0
afk_data = {"status": "not-afk" if is_active else "afk", "source": "aw-worktime-ui-bridge"}
out_afk.append({"timestamp": ts, "duration": duration, "data": afk_data})
win_data = {
"app": "RDP",
"title": build_window_title(active_users, active_count),
"source": "aw-worktime-ui-bridge",
}
out_win.append({"timestamp": ts, "duration": duration, "data": win_data})
last_ts = ts
return out_afk, out_win, last_ts
def main():
state = load_state()
last_ts = state.get("last_ts", "1970-01-01T00:00:00Z")
ensure_bucket(AFK_BUCKET, "afkstatus", "aw-worktime-ui-bridge")
ensure_bucket(WINDOW_BUCKET, "currentwindow", "aw-worktime-ui-bridge")
query = {
"query": [
"events = query_bucket(find_bucket($bid));",
"RETURN = sort_by_timestamp(events);",
],
"timeperiods": [[last_ts, to_iso_utc(datetime.now(timezone.utc).isoformat())]],
}
rows = _req("POST", f"/api/0/query/?bid={SESSIONS_BUCKET}", query) or []
if not rows or not rows[0]:
return
events = rows[0]
afk_events, win_events, new_last_ts = transform(events)
if not afk_events or not win_events or not new_last_ts:
return
_req("POST", f"/api/0/buckets/{AFK_BUCKET}/events", afk_events)
_req("POST", f"/api/0/buckets/{WINDOW_BUCKET}/events", win_events)
save_state({"last_ts": new_last_ts})
print(f"posted_afk={len(afk_events)} posted_win={len(win_events)} last_ts={new_last_ts}")
if __name__ == "__main__":
main()