# tunnel-handler-th — frps 日志实时回传（sz → atsc web /api/tunnel-log-ingest）
# systemd 常驻: tail /var/log/frps.log, 提取连接/登录/断线事件, 5s 上报一次
# 部署: install-log-shipper.sh（特批执行）; offset 存 /opt/tunnel/.tunnel-log-offset
import json
import re
import time
import urllib.request

LOG = "/var/log/frps.log"
OFFSET_FILE = "/opt/tunnel/.tunnel-log-offset"
URL = "https://atsc.wearchina.com/api/tunnel-log-ingest"
TOKEN = "atsc-84b38282ceb39fbc7de1eb47"

KEYWORDS = [("new proxy", "proxy_new"), ("login to server", "login"),
            ("start error", "start_error"), ("session closed", "closed"),
            ("connection closed", "closed"), ("accept new client", "login")]
def read_tail(offset):
    with open(LOG, "rb") as f:
        f.seek(offset)
        data = f.read()
        return data.decode(errors="replace").splitlines(), f.tell()


def extract_proxy(line):
    # frps 日志行尾是事件主体: "... new proxy [guiping]" / "... login to server success, run id [xx]"
    m = re.search(r"\[([a-zA-Z0-9_-]{1,32})\]\s*$", line.strip())
    if m:
        return m.group(1)
    m2 = re.search(r"(?:new proxy|proxy)\s*\[([a-zA-Z0-9_-]{1,32})\]", line)
    if m2:
        return m2.group(1)
    m3 = re.search(r"run id \[([a-zA-Z0-9_-]{1,32})\]", line)
    if m3:
        return m3.group(1)
    return "?"


def parse(line):
    ev = None
    for kw, tag in KEYWORDS:
        if kw in line:
            ev = tag
            break
    if ev is None:
        return None
    ts = line[:23] if len(line) > 23 else ""
    return {"ts": ts, "proxy": extract_proxy(line), "event": ev,
            "detail": line.strip()[:160]}


def ship(events):
    req = urllib.request.Request(
        URL, data=json.dumps({"events": events}).encode(),
        headers={"Content-Type": "application/json", "X-ATSC-Token": TOKEN})
    urllib.request.urlopen(req, timeout=10)


def main():
    off = 0
    try:
        off = int(open(OFFSET_FILE).read().strip() or 0)
    except Exception:
        off = 0
    while True:
        try:
            lines, new_off = read_tail(off)
            events = [e for e in (parse(l) for l in lines) if e]
            if events:
                try:
                    ship(events[-100:])
                    off = new_off
                    open(OFFSET_FILE, "w").write(str(off))
                except Exception:
                    pass  # 上报失败保留 offset 下次重试
        except Exception:
            pass
        time.sleep(5)


if __name__ == "__main__":
    main()
