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
+35
View File
@@ -23,6 +23,11 @@ class FeedIn(BaseModel):
filter_exclude: str = ""
interval: int = 0
enabled: bool = True
send_full_content: bool = False
fetch_full_article: bool = False
digest_enabled: bool = False
digest_period_hours: int = 24
category_id: Optional[int] = None
@field_validator("url")
@classmethod
@@ -63,6 +68,13 @@ class SettingsIn(BaseModel):
alerts_enabled: bool = False
alert_topic: str = ""
alert_threshold: int = 3
# Default feed values
default_priority: int = 3
default_tags: str = ""
default_attach_image: bool = True
default_interval: int = 0
notification_template: str = "**{title}**\n\n{body}\n\n[Открыть →]({link})"
proxy_url: str = ""
@field_validator("check_interval")
@classmethod
@@ -74,6 +86,16 @@ class SettingsIn(BaseModel):
def _threshold_min(cls, v: int) -> int:
return max(1, v)
@field_validator("default_priority")
@classmethod
def _def_priority_range(cls, v: int) -> int:
return min(5, max(1, v))
@field_validator("default_interval")
@classmethod
def _def_interval_nonneg(cls, v: int) -> int:
return max(0, v)
class TestIn(BaseModel):
server: str = ""
@@ -98,6 +120,19 @@ class PreviewIn(BaseModel):
return v
class CategoryIn(BaseModel):
name: str
sort_order: int = 0
@field_validator("name")
@classmethod
def _name_required(cls, v: str) -> str:
v = v.strip()
if not v:
raise ValueError("Название категории обязательно")
return v
class UserIn(BaseModel):
username: str
password: str = "" # empty on edit = keep existing