mirror of
https://github.com/driftywinds/rssrise.git
synced 2025-12-19 03:23:32 +00:00
Add files via upload
This commit is contained in:
5
.env
Normal file
5
.env
Normal file
@@ -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
|
||||
12
Dockerfile
Normal file
12
Dockerfile
Normal file
@@ -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"]
|
||||
8
compose.yml
Normal file
8
compose.yml
Normal file
@@ -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
|
||||
137
main.py
Normal file
137
main.py
Normal file
@@ -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()
|
||||
3
requirements.txt
Normal file
3
requirements.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
requests
|
||||
apprise
|
||||
python-dotenv
|
||||
Reference in New Issue
Block a user