8 major features: trafilatura, digest, ntfy actions, templates, FTS5 search, backup/restore, proxy, RSS reader
build-and-push / docker (push) Has been cancelled

- Full article extraction via trafilatura (fetch_full_article)
- Digest mode with configurable period (digest_enabled, digest_period_hours)
- ntfy Actions buttons (Open article, Open feed)
- Notification templates with {title}, {body}, {link}, {source}, {image_url}
- FTS5 full-text search in notification history
- Database backup/restore (download/upload .db)
- HTTP/SOCKS proxy for RSS feed fetching (proxy_url setting)
- Built-in RSS reader tab with categories, unread counts, article detail view
- Auto-category 'Общее' for feeds without a category
- Article storage (Article table) for reader
- DigestEntry model for pending digest entries

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
dimon
2026-06-03 20:47:46 +08:00
parent f8d2c31658
commit 834092a3ec
13 changed files with 1414 additions and 44 deletions
+60
View File
@@ -44,6 +44,14 @@ class Feed(SQLModel, table=True):
interval: int = 0
enabled: bool = True
send_full_content: bool = False # send entire article: all text, images, videos
fetch_full_article: bool = False # trafilatura: extract full page content from link
digest_enabled: bool = False # accumulate entries instead of real-time dispatch
digest_period_hours: int = 24 # how often to send the digest (1=hourly, 24=daily, 168=weekly)
last_digest_at: Optional[datetime] = None
# --- category ---
category_id: Optional[int] = Field(default=None, foreign_key="category.id")
# --- state ---
last_checked: Optional[datetime] = None
@@ -52,6 +60,46 @@ class Feed(SQLModel, table=True):
created_at: datetime = Field(default_factory=_utcnow)
class Category(SQLModel, table=True):
"""Admin-defined category for organizing feeds."""
id: Optional[int] = Field(default=None, primary_key=True)
name: str = Field(index=True, unique=True)
sort_order: int = 0
class Article(SQLModel, table=True):
"""Stored feed entries for the built-in RSS reader."""
id: Optional[int] = Field(default=None, primary_key=True)
feed_id: int = Field(index=True, foreign_key="feed.id")
feed_title: str = ""
title: str = ""
body: str = ""
full_html: str = ""
link: str = ""
image: str = ""
published_at: Optional[datetime] = None
is_read: bool = False
created_at: datetime = Field(default_factory=_utcnow, index=True)
class DigestEntry(SQLModel, table=True):
"""Pending entries for digest-enabled feeds."""
id: Optional[int] = Field(default=None, primary_key=True)
feed_id: int = Field(index=True, foreign_key="feed.id")
title: str = ""
link: str = ""
body: str = ""
image: str = ""
full_html: str = ""
images: str = "" # JSON array
videos: str = "" # JSON array
full_content: bool = False
created_at: datetime = Field(default_factory=_utcnow)
class SeenEntry(SQLModel, table=True):
"""Tracks which feed entries have already been pushed to avoid duplicates."""
@@ -113,3 +161,15 @@ class Settings(SQLModel, table=True):
alerts_enabled: bool = False
alert_topic: str = "" # ntfy topic to notify when a feed keeps failing
alert_threshold: int = 3 # consecutive failures before alerting
# --- Default values for new feeds (pre-fill the "Add feed" form) ---
default_priority: int = 3
default_tags: str = ""
default_attach_image: bool = True
default_interval: int = 0
# --- Notification template ---
notification_template: str = "**{title}**\n\n{body}\n\n[Открыть \u2192]({link})"
# --- Proxy for RSS fetching ---
proxy_url: str = ""