apiVersion: apps/v1
kind: Deployment
metadata:
  name: feishu-forwarder
  namespace: falco
  labels:
    app: feishu-forwarder
spec:
  replicas: 1
  selector:
    matchLabels:
      app: feishu-forwarder
  template:
    metadata:
      labels:
        app: feishu-forwarder
    spec:
      containers:
        - name: forwarder
          image: 380838443567.dkr.ecr.us-west-2.amazonaws.com/third-party/baseimage/python:3.12-alpine
          command: ["python3", "-c"]
          args:
            - |
              from http.server import ThreadingHTTPServer, BaseHTTPRequestHandler
              from datetime import datetime, timezone, timedelta
              import json, threading, time, urllib.request

              FEISHU = "https://open.feishu.cn/open-apis/bot/v2/hook/5f9bb8ce-8ce8-4f91-a966-abfba53e6552"
              WINDOW = 600
              CST = timezone(timedelta(hours=8))
              active = {}
              lock = threading.Lock()

              def value(body, fields, *names):
                  for name in names:
                      if name in fields:
                          return str(fields[name])
                      if name in body:
                          return str(body[name])
                  return "?"

              def event_data(body):
                  fields = body.get("output_fields") or {}
                  return {
                      "cluster": value(body, fields, "cluster", "Cluster"),
                      "host": value(body, fields, "hostname", "host", "Host"),
                      "pod": value(body, fields, "k8s.pod.name"),
                      "ns": value(body, fields, "k8s.ns.name"),
                      "container": value(body, fields, "container.name"),
                      "user": value(body, fields, "user.name"),
                      "process": value(body, fields, "proc.name"),
                      "command": value(body, fields, "proc.cmdline"),
                      "image": value(body, fields, "container.image.repository"),
                  }

              def format_time(value):
                  try:
                      return datetime.strptime(value[:19], "%Y-%m-%dT%H:%M:%S").replace(
                          tzinfo=timezone.utc).astimezone(CST).strftime("%Y-%m-%d %H:%M:%S (+08)")
                  except Exception:
                      return value or "?"

              def format_event(body, summary=None):
                  fields = event_data(body)
                  prefix = "[Falco grouped alert]" if summary else "🚨"
                  text = (f"{prefix} [{body.get('priority', '?')}] {body.get('rule', '?')}\n"
                          f"Cluster: {fields['cluster']}\n"
                          f"Node: {fields['host']}\n"
                          f"Pod: {fields['pod']}  |  NS: {fields['ns']}\n"
                          f"Container: {fields['container']}  |  User: {fields['user']}\n"
                          f"Time: {format_time(body.get('time', ''))}\n"
                          f"Details: {body.get('output', '?')}")
                  if summary:
                      text += (f"\nOccurrences: {summary['count']}\n"
                               f"First seen: {format_time(summary['first_time'])}\n"
                               f"Last seen: {format_time(summary['last_time'])}")
                  return text

              def event_key(body):
                  fields = event_data(body)
                  return tuple([body.get("rule", "?")] + [fields[name] for name in (
                      "cluster", "ns", "pod", "container", "process", "image")])

              def send(text):
                  data = json.dumps({"msg_type":"text","content":{"text":text}}).encode()
                  req = urllib.request.Request(FEISHU, data=data, headers={"Content-Type":"application/json"})
                  urllib.request.urlopen(req, timeout=10).read()

              def flush_expired():
                  while True:
                      time.sleep(5)
                      now = time.time()
                      expired = []
                      with lock:
                          for key, item in list(active.items()):
                              if now - item["first_seen"] >= WINDOW:
                                  expired.append((key, active.pop(key)))
                      for _, item in expired:
                          if item["count"] > 1:
                              try:
                                  send(format_event(item["body"], item))
                              except Exception:
                                  pass

              threading.Thread(target=flush_expired, daemon=True).start()

              class H(BaseHTTPRequestHandler):
                  def do_POST(self):
                      try:
                          body = json.loads(self.rfile.read(int(self.headers["Content-Length"])))
                          key = event_key(body)
                          now = time.time()
                          expired = None
                          with lock:
                              item = active.get(key)
                              if item is None or now - item["first_seen"] >= WINDOW:
                                  if item is not None and item["count"] > 1:
                                      expired = item
                                  active[key] = {"body": body, "count": 1, "first_seen": now,
                                                 "first_time": body.get("time", ""),
                                                 "last_time": body.get("time", "")}
                                  text = format_event(body)
                                  send_now = True
                              else:
                                  item["count"] += 1
                                  item["last_time"] = body.get("time", "")
                                  text = ""
                                  send_now = False
                          if expired is not None:
                              try:
                                  send(format_event(expired["body"], expired))
                              except Exception:
                                  pass
                          if send_now:
                              try:
                                  send(text)
                              except Exception:
                                  with lock:
                                      active.pop(key, None)
                                  raise
                          self.send_response(200)
                      except Exception as e:
                          self.send_response(500)
                      self.end_headers()
                      self.wfile.write(b"OK")
                  def log_message(self, *a): pass

              ThreadingHTTPServer(("0.0.0.0", 8080), H).serve_forever()
          ports:
            - containerPort: 8080
          resources:
            requests:
              cpu: 10m
              memory: 16Mi
            limits:
              cpu: 50m
              memory: 32Mi
