mirror of
https://gitverse.ru/kpa39l/vesti.git
synced 2026-09-29 09:55:03 +00:00
Initial import: vesti.nixg.ru — новостной апрув-проект (web, crawler, classifier, publisher, openspec)
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-09-12
|
||||
@@ -0,0 +1,69 @@
|
||||
## Дизайн
|
||||
|
||||
### 1. Формат карточки (publisher/card.py)
|
||||
|
||||
`make_card(post, comment=None)` теперь собирает текст репоста:
|
||||
|
||||
```
|
||||
[комментарий модератора (если задан)]
|
||||
[пустая строка]
|
||||
[полный текст исходного поста]
|
||||
[пустая строка]
|
||||
🔗 Оригинал: <url> (или атрибуция «Дед в АйТи (@dedinit) · t.me/dedinit/<id>» для своих)
|
||||
```
|
||||
|
||||
- Полный текст поста (post["text"]) вместо сниппета/summary; обрезка до Telegram-лимита
|
||||
(MAX_TEXT=4096) с учётом ссылки и комментария.
|
||||
- Служебная информация (📁 направление/язык, 📰 источник, 👁 просмотры) убирается из тела
|
||||
поста — это был «агрегаторный» вид. Просмотры/реакции остаются только в бандле и вебе.
|
||||
- Ссылка на оригинал — всегда последней строкой (для внешних: `🔗 Оригинал: <url>`;
|
||||
для своих: атрибуция + ссылка `t.me/dedinit/<id>` как раньше).
|
||||
- HTML-экранирование текста поста и комментария (вход — непроверенный текст, правило XSS).
|
||||
|
||||
### 2. Медиа (publisher/card.py + services/publisher)
|
||||
|
||||
- Медиа отправляется ПЕРВЫМ сообщением (фото или видео), затем — текст.
|
||||
- Разрешить video: `card.py` фильтр `.mp4` больше не отбрасывает (content_type=video).
|
||||
- `services/publisher/app/telegram.py` — новый `send_video(chat_id, path, caption="")`
|
||||
(Bot API sendVideo), общий `send_media` по расширению.
|
||||
- Caption у медиа: короткий (`<b>⬇️ Пост ниже</b>` или комментарий), НЕ дублировать весь
|
||||
текст (раньше photo caption обрезался до 1024 и текст всё равно слался вторым).
|
||||
|
||||
### 3. Отключение предпросмотра ссылки (services/publisher/app/telegram.py)
|
||||
|
||||
`send_message` → добавить параметр:
|
||||
|
||||
```python
|
||||
link_preview_options={"is_disabled": True}
|
||||
```
|
||||
|
||||
чтобы Telegram НЕ добавлял предпросмотр оригинальной ссылки в конце поста.
|
||||
|
||||
### 4. Веб: поле «Комментарий» (web/app.py + templates/candidates.html)
|
||||
|
||||
- В форме approve (для всех постов — и свои, и внешние) текстовое поле
|
||||
`<textarea name="comment" ...>Комментарий (по желанию)</textarea>`.
|
||||
- `approve`: `comment = (await request.form()).get("comment", "").strip()` → в card.
|
||||
- Комментарий уходит в published? Нет (не меняем схему БД) — только в текст поста и в
|
||||
метаданные бандла? Для простоты: комментарий включается в текст карточки (и, если
|
||||
хочется, в frontmatter бандла `comment:` — опционально, без схемы БД).
|
||||
|
||||
### 5. Полный текст в бандле
|
||||
|
||||
- Бандл (web/store.py) УЖЕ хранит полный текст поста — не меняется.
|
||||
|
||||
### 6. Обратная совместимость
|
||||
|
||||
- `make_card(post)` без comment работает (comment=None).
|
||||
- Старый `send_photo`/`send_message` остаются; main.py выбирает: если media → send_media,
|
||||
потом send_message (link_preview disabled).
|
||||
- Если текст пустой (пост только медиа) — текст-сообщение можно пропустить? Нет:
|
||||
ссылка на оригинал должна быть → текст генерируется всегда (минимум ссылка).
|
||||
|
||||
### 7. Проверка
|
||||
|
||||
- Юнит: `make_card` с comment → комментарий первым, полный текст, ссылка, без служебки.
|
||||
- Карточка ≤4096 (обрезается).
|
||||
- sendVideo работает для реального mp4 (в Docker медиа-маунт уже есть).
|
||||
- В канале: медиа + текст + ссылка, предпросмотра ссылки нет.
|
||||
- Веб: поле комментария видно, approve с комментарием → в канале комментарий первым.
|
||||
@@ -0,0 +1,50 @@
|
||||
## Why
|
||||
|
||||
Пользователь опубликовал несколько постов через веб-approve в канал @dedinit_vesti
|
||||
и увидел, что в канале пост выглядит «как простой агрегатор»:
|
||||
|
||||
- только ссылка на чужой канал (заголовок-сниппет из первых 120 символов текста),
|
||||
- служебная информация по направлениям (📁 направление/язык, 📰 источник, 👁 просмотры),
|
||||
- в конце Telegram добавляет предпросмотр ссылки (link preview).
|
||||
|
||||
Такой репост никому не интересен: не видно ни медиа из оригинального поста, ни
|
||||
полного текста, ни комментария модератора — только «голая» ссылка и служебка.
|
||||
|
||||
## What Changes
|
||||
|
||||
Формат публикуемого поста (карточка) меняется на «богатый репост»:
|
||||
|
||||
1. **Медиа из оригинального поста** — если у поста есть media_path (фото/видео),
|
||||
оно отправляется **первым сообщением** (sendPhoto/sendVideo), а текст — следующим.
|
||||
Сейчас sendPhoto есть, но caption обрезается до 1024 и текст дублируется вторым
|
||||
сообщением; видео (content_type=video) вообще не отправляется как медиа (фильтр в card.py).
|
||||
|
||||
2. **Полный текст поста** — вместо сниппета из первых 120 символов (make_card) или
|
||||
summary отправляется полный текст исходного поста (обрезанный до Telegram-лимита 4096).
|
||||
|
||||
3. **Комментарий модератора** — в веб-форме approve добавляется поле «Комментарий
|
||||
(по желанию)»; комментарий публикуется первым (или в начале текста), чтобы в канале
|
||||
было видно мнение/контекст редактора, а не только пересказ.
|
||||
|
||||
4. **Ссылка на оригинал БЕЗ предпросмотра** — ссылка на исходный пост остаётся в тексте,
|
||||
но Telegram-предпросмотр ссылки отключается (sendMessage параметр
|
||||
`link_preview_options={"is_disabled": True}`), чтобы канал не выглядел как агрегатор
|
||||
ссылок с превью.
|
||||
|
||||
5. **Служебная информация** — частично убирается/переформатируется: направление/язык,
|
||||
источник, просмотры больше НЕ обязательны в теле поста (внешняя ссылка на оригинал
|
||||
уже даёт контекст). Остаётся атрибуция для своего контента («Дед в АйТи»).
|
||||
|
||||
## Impact
|
||||
|
||||
- `publisher/card.py` — построение текста карточки (полный текст + комментарий + ссылка
|
||||
+ атрибуция, служебка по минимуму).
|
||||
- `services/publisher/app/telegram.py` — поддержка видео (sendVideo), параметр
|
||||
`link_preview_options` для sendMessage; send_photo с полным caption без обрезания
|
||||
(или без дублирования текста).
|
||||
- `services/publisher/app/main.py` — Card модель: +`comment`; передача link_preview_options.
|
||||
- `web/app.py` (approve) — чтение комментария из формы, проброс в card.
|
||||
- `web/templates/candidates.html` — поле ввода комментария в форме approve.
|
||||
- `publisher/card.py` — фильтр медиа: разрешить video (mp4), не только картинки.
|
||||
- Rollback: вернуть прежний make_card и вызовы; карточки станут как раньше.
|
||||
- Безопасность: комментарий — пользовательский ввод → HTML-экранирование как текст поста.
|
||||
@@ -0,0 +1,120 @@
|
||||
# TG Publisher — формат «богатого репоста»
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Карточка репоста — полный текст
|
||||
|
||||
При публикации внешнего поста карточка MUST содержать полный текст
|
||||
исходного поста (а не сниппет из первых 120 символов), обрезанный до лимита Telegram
|
||||
(4096 символов).
|
||||
|
||||
#### Scenario: публикация поста с длинным текстом
|
||||
|
||||
- Given пост с текстом длиной 3000 символов и ссылкой на оригинал
|
||||
- When модератор подтверждает пост в вебе
|
||||
- Then в канале отправляется текст с полным содержимым поста (обрезанный до 4096)
|
||||
- And заголовок-сниппет из 120 символов НЕ используется
|
||||
|
||||
### Requirement: Карточка репоста — комментарий модератора
|
||||
|
||||
При подтверждении поста модератор МОЖЕТ указать комментарий; комментарий
|
||||
MUST публиковаться первым блоком в тексте поста.
|
||||
|
||||
#### Scenario: approve с комментарием
|
||||
|
||||
- Given пост, подтверждаемый через веб
|
||||
- When модератор вводит комментарий «Отличный материал по Linux» и нажимает «Опубликовать»
|
||||
- Then текст карточки начинается с комментария «Отличный материал по Linux»
|
||||
- And полный текст исходного поста следует после комментария
|
||||
|
||||
#### Scenario: approve без комментария
|
||||
|
||||
- Given пост, подтверждаемый через веб
|
||||
- When модератор оставляет поле комментария пустым
|
||||
- Then текст карточки начинается с полного текста исходного поста
|
||||
- And лишних пустых блоков нет
|
||||
|
||||
### Requirement: Карточка репоста — ссылка на оригинал без предпросмотра
|
||||
|
||||
Ссылка на оригинальный пост MUST присутствовать в тексте карточки, и
|
||||
Telegram-предпросмотр этой ссылки MUST быть отключён (link_preview_options is_disabled).
|
||||
|
||||
#### Scenario: публикация внешнего поста
|
||||
|
||||
- Given внешний пост с url на оригинал
|
||||
- When карточка отправляется в канал
|
||||
- Then в тексте есть строка со ссылкой на оригинал
|
||||
- And Telegram НЕ добавляет предпросмотр ссылки внизу поста
|
||||
|
||||
### Requirement: Карточка репоста — медиа из оригинала
|
||||
|
||||
Если у исходного поста есть медиа (photo/video), публикация MUST
|
||||
отправлять медиа первым сообщением, а текст — следующим; видео (.mp4) MUST
|
||||
поддерживаться.
|
||||
|
||||
#### Scenario: пост с фото
|
||||
|
||||
- Given пост с media_path=media/xyz.jpg
|
||||
- When модератор подтверждает пост
|
||||
- Then в канал отправляется фото (sendPhoto) первым сообщением
|
||||
- And текст карточки отправляется следующим сообщением
|
||||
|
||||
#### Scenario: пост с видео
|
||||
|
||||
- Given пост с content_type=video и media_path=media/xyz.mp4
|
||||
- When модератор подтверждает пост
|
||||
- Then в канал отправляется видео (sendVideo) первым сообщением
|
||||
- And текст карточки отправляется следующим сообщением
|
||||
|
||||
### Requirement: Карточка репоста — без служебной информации
|
||||
|
||||
Служебная информация (направление/язык, источник, просмотры/реакции)
|
||||
MAY НЕ выводиться в теле поста; основное содержимое — текст, комментарий, ссылка на
|
||||
оригинал.
|
||||
|
||||
#### Scenario: пост без служебки
|
||||
|
||||
- Given внешний пост с направлением и просмотрами
|
||||
- When карточка формируется
|
||||
- Then в тексте карточки НЕТ строк «📁 linux / ru», «📰 источник», «👁 123 💬 4»
|
||||
- And текст содержит полный текст поста и ссылку на оригинал
|
||||
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: Формат карточки (publisher/card.py)
|
||||
|
||||
Функция make_card MUST принимать необязательный параметр `comment` и
|
||||
строить текст из: комментария (если задан), полного текста поста, ссылки на оригинал
|
||||
(атрибуция для своих).
|
||||
|
||||
#### Scenario: make_card с comment
|
||||
|
||||
- Given post с текстом «Важная новость» и comment «Мой комментарий»
|
||||
- When вызывается make_card(post, comment)
|
||||
- Then результат содержит «Мой комментарий» перед «Важная новость»
|
||||
- And ссылка на оригинал присутствует
|
||||
|
||||
#### Scenario: make_card без comment
|
||||
|
||||
- Given post с текстом «Важная новость» и comment=None
|
||||
- When вызывается make_card(post)
|
||||
- Then результат НЕ содержит пустого первого блока
|
||||
- And результат содержит полный текст «Важная новость» и ссылку на оригинал
|
||||
|
||||
### Requirement: Отправка медиа в publisher-service
|
||||
|
||||
Сервис публикации MUST отправлять медиа первым и текст вторым; для
|
||||
sendMessage параметр link_preview_options MUST отключать предпросмотр ссылки.
|
||||
|
||||
#### Scenario: sendMessage без предпросмотра
|
||||
|
||||
- Given канал @dedinit_vesti и текст со ссылкой на оригинал
|
||||
- When вызывается sendMessage
|
||||
- Then параметр link_preview_options={"is_disabled": True} передаётся в Bot API
|
||||
|
||||
#### Scenario: sendVideo для mp4
|
||||
|
||||
- Given файл media/xyz.mp4 существует в медиа-маунте
|
||||
- When публикуется пост с этим видео
|
||||
- Then вызывается sendVideo (а не sendPhoto)
|
||||
- And текст отправляется следом
|
||||
@@ -0,0 +1,24 @@
|
||||
## 1. Карточка (publisher/card.py)
|
||||
|
||||
- [ ] 1.1 `make_card(post, comment=None)`: полный текст поста вместо сниппета; служебка (📁/📰/👁) убрана; ссылка на оригинал последней строкой; comment первым
|
||||
- [ ] 1.2 HTML-экранирование comment (XSS-safe как текст)
|
||||
- [ ] 1.3 Медиа: разрешить video (.mp4), фильтр не отбрасывает
|
||||
- [ ] 1.4 Проверка: карточка ≤4096, эмодзи/ссылки на месте
|
||||
|
||||
## 2. Publisher-сервис (services/publisher/)
|
||||
|
||||
- [ ] 2.1 `telegram.py`: `send_media` (photo/video по расширению) + `send_video`; sendMessage с `link_preview_options={"is_disabled": True}`
|
||||
- [ ] 2.2 `main.py`: Card + `comment`; если media → send_media первым, затем send_message
|
||||
- [ ] 2.3 healthz/publish не ломаются; Docker пересобран (медиа-маунт уже есть)
|
||||
|
||||
## 3. Веб (web/)
|
||||
|
||||
- [ ] 3.1 `candidates.html`: поле «Комментарий (по желанию)» в форме approve (все посты)
|
||||
- [ ] 3.2 `app.py` approve: читать `comment` из формы, передать в card
|
||||
- [ ] 3.3 Проверка: approve с комментарием → в канале комментарий первым, текст полный, ссылка без превью
|
||||
|
||||
## 4. Документация и регресс
|
||||
|
||||
- [ ] 4.1 requirements без изменений (media/video — Bot API)
|
||||
- [ ] 4.2 STATUS.md / PRD.md / TODO.md обновлены
|
||||
- [ ] 4.3 Регресс: старые посты (текст) публикуются как раньше, тест в канал реальный (1 пост)
|
||||
Reference in New Issue
Block a user