From 84eeff3da3036d7ec2202f572a24030935a0527e Mon Sep 17 00:00:00 2001 From: drifty Date: Fri, 8 Aug 2025 17:26:25 +0530 Subject: [PATCH] Add files via upload --- .env | 5 ++ Dockerfile | 12 +++++ compose.yml | 8 +++ main.py | 137 +++++++++++++++++++++++++++++++++++++++++++++++ requirements.txt | 3 ++ 5 files changed, 165 insertions(+) create mode 100644 .env create mode 100644 Dockerfile create mode 100644 compose.yml create mode 100644 main.py create mode 100644 requirements.txt diff --git a/.env b/.env new file mode 100644 index 0000000..a50eae3 --- /dev/null +++ b/.env @@ -0,0 +1,5 @@ +FRESHRSS_URL=https://frss.example.com/api/greader.php +FRESHRSS_USERNAME=YourUsername +FRESHRSS_PASSWORD=YourPassword +POLL_INTERVAL=60 +APPRISE_URLS=discord://webhook_id/webhook_token,gotifys://gotify.example.com/token,tgram://bot_token/chat_id1/chat_id2/chat_idN diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..b530d30 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,12 @@ +FROM python:3.11-slim + +WORKDIR /app + +ENV PYTHONUNBUFFERED=1 + +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +COPY main.py . + +CMD ["python3", "main.py"] diff --git a/compose.yml b/compose.yml new file mode 100644 index 0000000..c52b8d3 --- /dev/null +++ b/compose.yml @@ -0,0 +1,8 @@ +version: "3.9" +services: + rssrise: + image: ghcr.io/driftywinds/rssrise:latest + container_name: rssrise + restart: unless-stopped + volumes: + - ./.env:/app/.env diff --git a/main.py b/main.py new file mode 100644 index 0000000..b662bd1 --- /dev/null +++ b/main.py @@ -0,0 +1,137 @@ +import os +import time +import requests +from dotenv import load_dotenv +import subprocess +import re +import datetime + +load_dotenv() + +FRESHRSS_URL = os.getenv("FRESHRSS_URL") # e.g. https://freshrss.example.net/api/greader.php +USERNAME = os.getenv("FRESHRSS_USERNAME") +PASSWORD = os.getenv("FRESHRSS_PASSWORD") +POLL_INTERVAL = int(os.getenv("POLL_INTERVAL", "60")) # seconds + +APPRISE_CONFIG = os.getenv("APPRISE_CONFIG") # apprise URLs or config file, optional + +session = requests.Session() +auth_token = None + +def login(): + global auth_token + login_url = f"{FRESHRSS_URL}/accounts/ClientLogin" + params = { + "Email": USERNAME, + "Passwd": PASSWORD + } + resp = session.get(login_url, params=params) + if resp.status_code != 200: + print(f"Login failed: {resp.status_code} {resp.text}") + return False + + # Response is like: + # SID=alice/8e6845e089457af25303abc6f53356eb60bdb5f8 + # Auth=alice/8e6845e089457af25303abc6f53356eb60bdb5f8 + # We need the Auth line + for line in resp.text.splitlines(): + if line.startswith("Auth="): + auth_token = line[len("Auth="):].strip() + break + if not auth_token: + print("Auth token not found in login response") + return False + + print(f"Logged in, got auth token") + return True + +def get_headers(): + return { + "Authorization": f"GoogleLogin auth={auth_token}" + } + +def fetch_unread_items(): + url = f"{FRESHRSS_URL}/reader/api/0/stream/contents/reading-list?output=json&n=20" + resp = session.get(url, headers=get_headers()) + if resp.status_code != 200: + print(f"Failed to fetch unread items: {resp.status_code} {resp.text}") + return [] + + data = resp.json() + items = data.get("items", []) + unread_items = [] + for item in items: + categories = item.get("categories", []) + # Skip items marked as read + if "user/-/state/com.google/read" not in categories: + unread_items.append(item) + return unread_items + +def clean_html(raw_html): + cleanr = re.compile('<.*?>') + cleantext = re.sub(cleanr, '', raw_html) + return cleantext.strip() + +def format_message(item): + title = item.get("title", "No title").strip() + url = item.get("alternate", [{}])[0].get("href", "").strip() + categories = item.get("categories", []) + fresh_categories = [c for c in categories if not c.startswith("user/-/state/com.google")] + category = fresh_categories[0] if fresh_categories else "Uncategorized" + + message_body = ( + f"---------\n" + f"URL: {url}\n\n" + f"Category: {category}\n" + f"---------" + ) + + return title, message_body + + +def send_notification(title, body): + # Compose apprise command + cmd = ["apprise"] + if APPRISE_CONFIG: + cmd += ["-q", "-c", APPRISE_CONFIG] # quiet mode, config file + cmd += ["-t", title, "-b", body] + print(f"Sending notification: {title}") + result = subprocess.run(cmd, capture_output=True, text=True) + if result.returncode != 0: + print(f"Failed to send notification: {result.stderr}") + else: + print("Notification sent.") + +def main(): + if not login(): + return + + sent_ids = set() + + # On first run, send all unread items + unread_items = fetch_unread_items() + print(f"[{datetime.datetime.now().isoformat()}] Fetched {len(unread_items)} unread items on startup") + for item in unread_items: + item_id = item.get("id") + if item_id in sent_ids: + continue + title, body = format_message(item) + send_notification(title, body) + sent_ids.add(item_id) + + while True: + time.sleep(POLL_INTERVAL) + now = datetime.datetime.now().isoformat() + unread_items = fetch_unread_items() + print(f"[{now}] Polled and found {len(unread_items)} unread items") + + for item in unread_items: + item_id = item.get("id") + if item_id in sent_ids: + continue + title, body = format_message(item) + send_notification(title, body) + sent_ids.add(item_id) + +if __name__ == "__main__": + main() diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..cd4683e --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +requests +apprise +python-dotenv