#!/usr/bin/env python3
"""
fb_group_rss.py
----------------
RSS feedet generál egy nyilvános Facebook csoport posztjaiból.

MŰKÖDÉS:
  1. Playwright-tel, egy korábban elmentett bejelentkezett munkamenettel
     (storage_state.json) betölti a csoport oldalát.
  2. Legörgeti a feedet N posztig, kiolvassa a szöveget, szerzőt, linket,
     időpontot.
  3. A már látott posztokat egy helyi JSON állapotfájlban tartja nyilván
     (nem generál duplikátumot minden futáskor).
  4. Legenerálja/frissíti a rss.xml fájlt, amit egy sima webszerverrel
     (pl. nginx/lighttpd) ki lehet szolgálni, vagy statikusan olvasni.

ELSŐ HASZNÁLAT ELŐTT — munkamenet mentése:
  Mivel a script nem kezel jelszót/2FA-t (ez direkt döntés, hogy ne
  üsse fel magát a bot-védelem), egyszer manuálisan be kell jelentkezni
  egy valódi böngészőben, és exportálni a cookie-kat storage_state.json-ba:

    python3 -c "
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
    browser = p.chromium.launch(headless=False)
    page = browser.new_page()
    page.goto('https://www.facebook.com/login')
    input('Jelentkezz be a böngészőben, majd nyomj Entert...')
    page.context.storage_state(path='storage_state.json')
    browser.close()
"

  Ezt olyan gépen érdemes futtatni, ahol van grafikus felület
  (headless=False), utána a storage_state.json fájlt átmásolhatod
  az Armbian dobozra.

FÜGGŐSÉGEK (Armbian / Debian aarch64-en is működik):
    pip install playwright feedgen --break-system-packages
    playwright install chromium --with-deps

FUTTATÁS (mivel headless=False módban indul, virtuális kijelző kell — xvfb):
    xvfb-run -a python3 fb_group_rss.py

MEGJEGYZÉS: a Facebook rendszeresen változtatja az oldal HTML
szerkezetét, és tiltja az automatizált hozzáférést a ToS-ában.
Ez a script bármikor működésképtelenné válhat, és a fiók
korlátozásának/tiltásának is van (kis) kockázata. Csak saját
felelősségre, saját fiókkal, mértékkel (ne pörgesd túl gyakran).
"""

import json
import re
import sys
import time
from datetime import datetime, timezone
from hashlib import sha1
from pathlib import Path

from feedgen.feed import FeedGenerator
from playwright.sync_api import sync_playwright

# ---------------------------------------------------------------------------
# KONFIG — ezt igazítsd a saját csoportodra
# ---------------------------------------------------------------------------
GROUP_URL = "https://www.facebook.com/groups/1928282657363211/"
STATE_FILE = Path("storage_state.json")      # mentett bejelentkezés
SEEN_FILE = Path("seen_posts.json")          # már feldolgozott posztok
OUTPUT_RSS = Path("rss.xml")                 # ide íródik a feed
MAX_POSTS = 25                               # ennyi legutóbbi posztot tartsunk meg a feedben
SCROLL_ROUNDS = 6                            # hányszor görgessünk lejjebb betöltéskor
SCROLL_PAUSE_MS = 1500                       # görgetések közti várakozás


def load_seen() -> dict:
    if SEEN_FILE.exists():
        return json.loads(SEEN_FILE.read_text(encoding="utf-8"))
    return {}


def save_seen(seen: dict) -> None:
    SEEN_FILE.write_text(json.dumps(seen, ensure_ascii=False, indent=2), encoding="utf-8")


def post_id_for(text: str, author: str) -> str:
    """Stabil azonosító poszthoz, ha nincs közvetlen permalink."""
    return sha1(f"{author}|{text[:200]}".encode("utf-8")).hexdigest()


def clean_post_text(text: str) -> str:
    """
    A Facebook a poszt-konténerbe (aria-posinset) sokszor betölti a
    szomszédos posztok galéria-képeinek "Facebook" alt-szövegét is,
    amíg azok még töltődnek. Ez levágja ezt a zajt:
      - a "Comment as <név>" komment-mező előtti rész ér véget maga
        a poszt (minden azutáni tartalom már a következő elemé)
      - a poszt eleje előtti/utáni önálló "Facebook" sorokat (kép
        placeholder alt-szöveg) eldobja
    """
    lines = text.split("\n")

    # Vágjuk le a "Comment as ..." mezőnél és minden utána jövőt.
    for idx, ln in enumerate(lines):
        if ln.strip().startswith("Comment as "):
            lines = lines[:idx]
            break

    def is_noise(ln: str) -> bool:
        s = ln.strip()
        return s == "" or s == "Facebook"

    while lines and is_noise(lines[0]):
        lines.pop(0)
    while lines and is_noise(lines[-1]):
        lines.pop()

    return "\n".join(lines).strip()


def scrape_group_posts(page) -> list[dict]:
    """
    Legörgeti a csoport feedjét, és kigyűjti a posztokat.

    FIGYELEM: a Facebook DOM-ja folyamatosan változik, ezért a lenti
    szelektorok idővel elavulhatnak — ha a script egyszer csak üres
    listát ad vissza, először ezeket kell frissíteni (böngésző
    Fejlesztői eszközök -> Inspect a posztokon).
    """
    page.goto(GROUP_URL, wait_until="networkidle")
    time.sleep(3)

    # FONTOS: a Facebook virtualizálja a listát — a görgetés SORÁN el
    # kell menteni ami éppen a DOM-ban van, mert a végén már csak a
    # legutolsó pozíció közelében lévő elemek maradnak (a korábbiakat
    # a böngésző eltávolítja a memóriából). Ezért minden görgetési
    # körben azonnal begyűjtünk mindent, nem csak a legvégén egyszer.
    collected: dict[str, dict] = {}

    for round_no in range(SCROLL_ROUNDS + 1):  # +1: az induló (0.) állapotot is nézzük
        articles = page.locator('div[aria-posinset]')
        count = articles.count()

        for i in range(count):
            art = articles.nth(i)
            try:
                text = art.inner_text(timeout=2000).strip()
            except Exception:
                continue
            if not text:
                continue

            # A "Facebook"-zajt (töltődő galéria-képek alt-szövege) és a
            # "Comment as ..." utáni felesleget levágjuk.
            text = clean_post_text(text)
            if not text:
                continue

            # Kommentek kiszűrése: a posztoknál "Comment"/"Share" gombsor
            # van, a kommenteknél "Reply"/"Share" — ez utóbbit dobjuk.
            tail = text[-150:]
            if "Reply" in tail or "Válasz" in tail:
                continue

            # Szerző: a megtisztított szöveg jellemzően a szerző nevével
            # kezdődik (az első nem üres sor). Ezt a sort a törzsszövegből
            # kivesszük, hogy a cím/leírás ne duplikálja a nevet.
            lines_all = text.split("\n")
            author = "Ismeretlen szerző"
            body_start = 0
            for idx, ln in enumerate(lines_all):
                if ln.strip():
                    author = ln.strip()
                    body_start = idx + 1
                    break
            body_text = "\n".join(lines_all[body_start:]).strip()

            # További UI-zaj eltávolítása a törzs elejéről ("·" elválasztó
            # pötty, "Follow" gomb felirata), amíg ilyesmi marad ott.
            noise_exact = {"", "·", "Follow", "Kövesd"}
            b_lines = body_text.split("\n")
            while b_lines and b_lines[0].strip() in noise_exact:
                b_lines.pop(0)
            body_text = "\n".join(b_lines).strip()

            if not body_text:
                body_text = text  # biztonsági tartalék, ha csak a név maradt

            # Permalink keresése. A Facebook a poszt-konténerben ritkán ad
            # közvetlen "/posts/<id>/" linket, DE minden galéria-kép/videó
            # linkje tartalmazza a "set=pcb.<POSZT_ID>" paramétert — ez a
            # poszt tényleges azonosítója, amiből felépíthető a permalink.
            link = GROUP_URL
            try:
                all_hrefs = art.locator("a").evaluate_all(
                    "els => els.map(e => e.getAttribute('href') || '')"
                )
            except Exception:
                all_hrefs = []

            pcb_id = None
            for href in all_hrefs:
                m = re.search(r"pcb\.(\d+)", href)
                if m:
                    pcb_id = m.group(1)
                    break

            if pcb_id:
                gm = re.search(r"/groups/(\d+)", GROUP_URL)
                if gm:
                    link = f"https://www.facebook.com/groups/{gm.group(1)}/posts/{pcb_id}/"
            else:
                # Tartalék: hátha mégis van közvetlen "/posts/" jellegű link.
                try:
                    perma = art.locator(
                        'a[href*="/posts/"], a[href*="/permalink/"], '
                        'a[href*="permalink.php"], a[href*="story_fbid"], '
                        'a[href*="multi_permalinks"]'
                    ).first
                    href = perma.get_attribute("href", timeout=1000)
                    if href:
                        link = href.split("?")[0] if "story_fbid" not in href else href
                        if link.startswith("/"):
                            link = "https://www.facebook.com" + link
                except Exception:
                    pass

            pid = post_id_for(body_text, author)
            if pid not in collected:
                collected[pid] = {
                    "id": pid,
                    "author": author,
                    "text": body_text,
                    "link": link,
                    "fetched_at": datetime.now(timezone.utc).isoformat(),
                }

        if round_no < SCROLL_ROUNDS:
            page.mouse.wheel(0, 3000)
            page.wait_for_timeout(SCROLL_PAUSE_MS)

    return list(collected.values())


def build_rss(posts: list[dict]) -> None:
    fg = FeedGenerator()
    fg.title("Facebook csoport RSS")
    fg.link(href=GROUP_URL, rel="alternate")
    fg.description("Automatikusan generált feed egy nyilvános Facebook csoport posztjaiból")
    fg.language("hu")

    # Legfrissebb elöl
    for post in posts[:MAX_POSTS]:
        fe = fg.add_entry()
        fe.id(post["id"])
        fe.title(f'{post["author"]}: {post["text"][:80]}')
        fe.link(href=post["link"])
        fe.description(post["text"])
        fe.author(name=post["author"])

    fg.rss_file(str(OUTPUT_RSS))


def main() -> None:
    if not STATE_FILE.exists():
        print(f"HIBA: nincs {STATE_FILE} — előbb mentsd el a bejelentkezett "
              f"munkamenetet (lásd a script tetején lévő útmutatót).")
        sys.exit(1)

    seen = load_seen()

    with sync_playwright() as p:
        browser = p.chromium.launch(
            headless=False,
            args=["--disable-blink-features=AutomationControlled"],
        )
        context = browser.new_context(
            storage_state=str(STATE_FILE),
            viewport={"width": 1366, "height": 900},
            user_agent=(
                "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
                "(KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36"
            ),
        )
        page = context.new_page()

        scraped = scrape_group_posts(page)
        browser.close()

    new_count = 0
    for post in scraped:
        if post["id"] not in seen:
            seen[post["id"]] = post
            new_count += 1

    # Csak az utolsó MAX_POSTS*4-et tartsuk meg a state fájlban, hogy ne
    # nőjön a végtelenségig.
    all_posts = sorted(seen.values(), key=lambda p: p["fetched_at"], reverse=True)
    seen = {p["id"]: p for p in all_posts[: MAX_POSTS * 4]}
    save_seen(seen)

    build_rss(all_posts)
    print(f"Kész. {new_count} új poszt, összesen {len(all_posts)} tárolva, "
          f"{OUTPUT_RSS} frissítve.")


if __name__ == "__main__":
    main()
