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
+112 -3
View File
@@ -5,7 +5,9 @@
<div class="brand"><span class="logo">📡</span> RSS&nbsp;&nbsp;ntfy</div>
<nav class="tabs">
<button class="tab active" data-tab="feeds" data-i18n="nav.feeds">Ленты</button>
<button class="tab" data-tab="reader" data-i18n="nav.reader">Чтение</button>
<button class="tab" data-tab="history" data-i18n="nav.history">История</button>
<button class="tab admin-only" data-tab="categories" data-i18n="nav.categories">Категории</button>
<button class="tab admin-only" data-tab="users" data-i18n="nav.users">Пользователи</button>
<button class="tab admin-only" data-tab="settings" data-i18n="nav.settings">Настройки</button>
</nav>
@@ -39,6 +41,9 @@
<div class="panel-head-actions">
<button class="btn ghost" id="check-all" data-i18n="feeds.checkAll">↻ Проверить все</button>
<button class="btn ghost admin-only" id="import-btn" data-i18n="feeds.import">⬆ Импорт OPML</button>
<button class="btn ghost admin-only" id="backup-btn" data-i18n="feeds.backup">💾 Бэкап</button>
<button class="btn ghost admin-only" id="restore-btn" data-i18n="feeds.restore">📥 Восстановить</button>
<input type="file" id="restore-file" accept=".db" hidden>
<button class="btn ghost" id="export-btn" data-i18n="feeds.export">⬇ Экспорт OPML</button>
<button class="btn primary admin-only" id="add-feed" data-i18n="feeds.add">+ Добавить ленту</button>
<input type="file" id="opml-file" accept=".opml,.xml,text/xml" hidden>
@@ -51,7 +56,30 @@
</div>
</section>
<!-- ===================== HISTORY ===================== -->
<!-- ===================== READER ===================== -->
<section id="tab-reader" class="tab-panel">
<div class="reader-layout">
<aside class="reader-sidebar">
<div class="reader-cats" id="reader-cat-list">
<div class="reader-cat active" data-cat="all" data-i18n="reader.all">Все</div>
</div>
</aside>
<div class="reader-main">
<div class="panel-head">
<h2 data-i18n="nav.reader">Чтение</h2>
<button class="btn ghost" id="reader-mark-all" data-i18n="reader.markAll">Отметить все прочитанными</button>
</div>
<div id="reader-list"></div>
<div id="reader-detail" class="hidden"></div>
<div id="reader-empty" class="empty hidden">
<div class="empty-icon">📖</div>
<p data-i18n="reader.empty">Статей пока нет. Добавьте ленты, чтобы начать читать.</p>
</div>
</div>
</div>
</section>
<!-- ===================== HISTORY ===================== -->
<section id="tab-history" class="tab-panel">
<div class="panel-head">
<h2 data-i18n="history.heading">История уведомлений</h2>
@@ -72,7 +100,20 @@
</div>
</section>
<!-- ===================== USERS ===================== -->
<!-- ===================== CATEGORIES ===================== -->
<section id="tab-categories" class="tab-panel">
<div class="panel-head">
<h2 data-i18n="categories.heading">Категории</h2>
<button class="btn primary" id="add-category" data-i18n="categories.add">+ Добавить категорию</button>
</div>
<div id="categories-list" class="cards"></div>
<div id="categories-empty" class="empty hidden">
<div class="empty-icon">🏷️</div>
<p data-i18n="categories.empty">Категорий пока нет.</p>
</div>
</section>
<!-- ===================== USERS ===================== -->
<section id="tab-users" class="tab-panel">
<div class="panel-head">
<h2 data-i18n="users.heading">Пользователи</h2>
@@ -108,13 +149,40 @@
<button type="button" class="btn ghost" id="test-btn" data-i18n="settings.testBtn">Отправить тест</button>
</div>
<h3 data-i18n="settings.check">Проверка</h3>
<label><span data-i18n="settings.proxyUrl">URL прокси</span> <small class="muted" data-i18n="settings.proxyHint"></small>
<input type="text" name="proxy_url" placeholder="http://proxy:8080"></label>
<h3 data-i18n="settings.template">Шаблон уведомлений</h3>
<label><span data-i18n="settings.templateHint">Переменные: {title}, {body}, {link}, {source}, {image_url}</span>
<textarea name="notification_template" rows="3" style="width:100%;margin-top:4px;padding:8px;background:var(--bg-soft);border:1px solid var(--border);border-radius:8px;color:var(--text);font-family:monospace;font-size:13px;resize:vertical"></textarea></label>
<h3 data-i18n="settings.check">Проверка</h3>
<label><span data-i18n="settings.interval">Интервал проверки по умолчанию (минуты)</span>
<input type="number" name="check_interval" min="1" value="5">
<small class="muted" data-i18n="settings.intervalHint"></small>
</label>
<h3 data-i18n="settings.telegram">Telegram</h3>
<h3 data-i18n="settings.feedDefaults">Значения по умолчанию для новых лент</h3>
<div class="grid-2">
<label><span data-i18n="feed.priority">Приоритет</span>
<select name="default_priority">
<option value="1" data-i18n="feed.p1"></option>
<option value="2" data-i18n="feed.p2"></option>
<option value="3" selected data-i18n="feed.p3"></option>
<option value="4" data-i18n="feed.p4"></option>
<option value="5" data-i18n="feed.p5"></option>
</select>
</label>
<label><span data-i18n="feed.intervalMin">Интервал, мин</span> <small class="muted" data-i18n="feed.intervalHint"></small>
<input type="number" name="default_interval" min="0" value="0"></label>
</div>
<label><span data-i18n="feed.tags">Теги / эмодзи</span> <small class="muted" data-i18n="feed.commaHint"></small>
<input type="text" name="default_tags" placeholder="newspaper,fire"></label>
<label class="switch-row"><span data-i18n="feed.attach">Прикреплять картинку</span>
<input type="checkbox" name="default_attach_image" class="switch" checked></label>
<h3 data-i18n="settings.telegram">Telegram</h3>
<label class="switch-row"><span data-i18n="settings.tgEnable">Включить доставку в Telegram</span>
<input type="checkbox" name="telegram_enabled" class="switch"></label>
<div class="grid-2">
@@ -169,6 +237,11 @@
<label><span data-i18n="feed.title">Название</span> <small class="muted" data-i18n="feed.titleOpt"></small>
<input type="text" name="title">
</label>
<label><span data-i18n="feed.category">Категория</span>
<select name="category_id">
<option value="" data-i18n="feed.categoryNone">— без категории —</option>
</select>
</label>
<div class="grid-2">
<label><span data-i18n="feed.server">Сервер ntfy</span> <small class="muted" data-i18n="feed.serverHint"></small>
<input type="text" name="ntfy_server" placeholder="https://ntfy.sh"></label>
@@ -201,6 +274,15 @@
<label><span data-i18n="feed.intervalMin">Интервал, мин</span> <small class="muted" data-i18n="feed.intervalHint"></small>
<input type="number" name="interval" min="0" value="0"></label>
</div>
<details class="adv">
<summary data-i18n="feed.digest">Дайджест</summary>
<label class="switch-row"><span data-i18n="feed.digestEnable">Накапливать записи (дайджест)</span>
<input type="checkbox" name="digest_enabled" class="switch"></label>
<label><span data-i18n="feed.digestPeriod">Период дайджеста (часы)</span>
<input type="number" name="digest_period_hours" min="1" value="24"></label>
</details>
<label><span data-i18n="feed.tags">Теги / эмодзи</span> <small class="muted" data-i18n="feed.commaHint"></small>
<input type="text" name="tags" placeholder="newspaper,fire"></label>
@@ -214,6 +296,12 @@
<div class="switch-grid">
<label class="switch-row"><span data-i18n="feed.attach">Прикреплять картинку</span>
<input type="checkbox" name="attach_image" class="switch" checked></label>
<label class="switch-row"><span data-i18n="feed.fetchArticle">Загружать полную статью (trafilatura)</span>
<input type="checkbox" name="fetch_full_article" class="switch"></label>
<small class="muted" data-i18n="feed.fetchArticleHint" style="margin-top:-8px;display:block">Загружает страницу статьи и извлекает основной текст.</small>
<label class="switch-row"><span data-i18n="feed.fullContent">Отправлять полный контент</span>
<input type="checkbox" name="send_full_content" class="switch"></label>
<small class="muted" data-i18n="feed.fullContentHint" style="margin-top:-8px;display:block">Весь текст, все картинки и видео. Для ntfy — Markdown.</small>
<label class="switch-row"><span data-i18n="feed.dupTg">Дублировать в Telegram</span>
<input type="checkbox" name="to_telegram" class="switch"></label>
<label class="switch-row"><span data-i18n="feed.toWebhook">Отправлять в webhook</span>
@@ -234,6 +322,27 @@
</form>
</div>
<!-- ===================== CATEGORY MODAL ===================== -->
<div id="cat-modal" class="modal-backdrop hidden">
<form class="modal" id="cat-form">
<div class="modal-head">
<h3 id="cat-modal-title" data-i18n="cat.addTitle">Добавить категорию</h3>
<button type="button" class="icon-btn" id="cat-modal-close"></button>
</div>
<div class="modal-body">
<input type="hidden" name="id">
<label><span data-i18n="cat.name">Название *</span>
<input type="text" name="name" required></label>
<label><span data-i18n="cat.sortOrder">Порядок сортировки</span>
<input type="number" name="sort_order" min="0" value="0"></label>
</div>
<div class="modal-foot">
<button type="button" class="btn ghost" id="cat-modal-cancel" data-i18n="modal.cancel">Отмена</button>
<button type="submit" class="btn primary" data-i18n="modal.save">Сохранить</button>
</div>
</form>
</div>
<!-- ===================== USER MODAL ===================== -->
<div id="user-modal" class="modal-backdrop hidden">
<form class="modal" id="user-form">