uNews — единая система публикации новостей, патчноутов и отчётов разработки по проектам Антона.
Текущая версия: 0.3.7. Она проверяет реальные bytes и внутреннюю структуру каждого изображения до Telegram. Стабильная версия до автоматизации сохранена в ветке stable/manual-publishing-v0.1.0.
Главная идея: каждый проект хранит свои новости в папке news/, а uNews забирает эти патчноуты и публикует их в Telegram-канал через бота.
- публиковать текстовые сообщения в Telegram;
- публиковать пост с одной картинкой;
- публиковать альбом из нескольких картинок через
sendMediaGroup; - брать порядок картинок из YAML-поля
images; - использовать
imageкак запасной вариант для одной картинки; - проверять публикацию без отправки в Telegram;
- ограничивать подпись под Telegram-альбомом без изменения исходного
.md; - публиковать до 20 готовых записей за один запуск с паузой не менее 61 секунды;
- фиксировать
data/published.jsonсразу после каждого успешного Telegram-поста; - сохранять безопасную причину фатального сбоя в
data/health.jsonиdata/errors.json; - коммитить диагностическое состояние до окончательного завершения workflow с ошибкой;
- автоматически закрывать служебный Issue после восстановления очереди;
- скачивать каждое pending-изображение через GET вместо одной проверки HEAD;
- проверять сигнатуру, формат, размеры, PNG CRC и zlib-декодирование;
- повторно проверять файл непосредственно перед Telegram;
- передавать Telegram уже проверенные bytes как multipart Blob.
500 Tower DefenseuSugaruDreamuChurchGOARTTime Rift- другие проекты автора
- В любом публичном репозитории
sunpoleсоздаётся папкаnews/. - В неё добавляются Markdown-патчноут и изображение с полями
versionиqueued_at. - uNews просыпается раз в четыре часа или вручную.
- Каждый pending-файл скачивается через GET и проходит byte-level image integrity проверку.
- Внутри проекта более ранняя версия всегда идёт первой.
- Среди проектов выбирается самая старая запись по
queued_at. - Непосредственно перед Telegram выбранное изображение скачивается и проверяется повторно.
- Telegram получает проверенный Blob, а не исходный raw URL.
- За запуск публикуется до 20 готовых Telegram-постов в строгом FIFO-порядке с паузой 61 секунду.
- Каждый успешный результат немедленно записывается и отправляется в GitHub через
data/published.json. - После завершения запуска обновляются
data/health.jsonиdata/errors.json; при фатальном сбое state коммитится до возврата ошибки.
Приватные репозитории не сканируются. Полная схема: docs/QUEUE_ARCHITECTURE.md. Точный контракт изображений: docs/IMAGE_INTEGRITY.md.
news/
├── 2026-06-14-500td-v1-0-2-pages-preview.md
├── 2026-06-14-500td-v1-0-2-pages-preview.png
└── 2026-06-14-500td-v1-0-2-pages-preview_2.png
type: patch
project: 500 Tower Defense
series: 500td
title: Версия 1.0.2 подготовлена для веб-запуска
version: 1.0.2
queued_at: 2026-07-18T15:40:00Z
repo_url: https://github.com/sunpole/500
web_url: https://sunpole.github.io/500/previews/500td/1.0.2/
image: 2026-06-14-500td-v1-0-2-pages-preview.png
images:
- 2026-06-14-500td-v1-0-2-pages-preview_2.png
- 2026-06-14-500td-v1-0-2-pages-preview.pngtype— тип публикации:intro,patch,report,note.project— название проекта.series— короткий ключ серии, например500td,usugar,udream.title— заголовок публикации.version— версия, если она есть.queued_at— точное UTC-время постановки в очередь в ISO 8601.repo_url— ссылка на GitHub-репозиторий.web_url— ссылка на рабочую веб-версию или preview.image— одна картинка или fallback.images— список картинок для Telegram-альбома.
Если указано images, порядок картинок берётся строго из YAML. Первая картинка получает подпись, остальные отправляются без подписи.
Внутри патчноута можно добавить блок:
Короткий текст для Telegram:
Текст после этого блока будет использован как короткая подпись к Telegram-публикации. Это удобно, если полный патчноут длинный.
Проверка без отправки:
npm run publish:projects:check -- "../500_td_game/news/2026-06-14-500td-v1-0-2-pages-preview.md"
npm run publish:all:check
npm run diagnose:telegram
npm run check:images
npm testОжидаемый результат для альбома:
{
"method": "sendMediaGroup",
"captionWasTruncated": false
}Основной путь реальной публикации — только GitHub Actions. Проект-источник кладёт патчноут и изображение в публичную папку news/, после чего workflow Publish all project news в uNews находит новый файл, проверяет правила публикации, отправляет пост в @uNewsLog и обновляет data/published.json.
Локально разрешены только безопасные команды:
npm run publish:projects:check -- "../500_td_game/news/2026-06-14-500td-v1-0-2-pages-preview.md"
npm run publish:all:check
npm run diagnose:telegram
npm run check:fixtures
npm run check:images
npm testКоманды npm run publish:projects и npm run publish:all по умолчанию блокируют реальную отправку с локального компьютера. Они должны отправлять Telegram-посты только внутри GitHub Actions, где GITHUB_ACTIONS=true.
Локальные секреты должны храниться только в .env. Этот файл нельзя добавлять в GitHub.
Финальная подпись Telegram собирается автоматически через policy-слой. Текст из блока Короткий текст для Telegram не публикуется “как есть”: к нему добавляются обязательные ссылка и хештеги.
Правило ссылки:
- если есть
web_url, используется он; - если
web_urlнет, используетсяrepo_url; - если указан
branchи нетweb_url, формируется ссылка на GitHub-ветку; - если нет ни
web_url, ниrepo_url, публикация блокируется.
Обязательные хештеги:
uSugar→#uSugar #тыСахар #uNews #SunpoleuNews→#uNews #тыНовости #SunpoleuDream→#uDream #тыСон #uNews #SunpoleuChurch→#uChurch #тыЦерковь #uNews #Sunpole500 Tower Defense→#500TD #500ТД #uNews #Sunpole
Если для проекта нет mapping, check падает и mapping нужно добавить до публикации.
Для type: patch, docs, feature, bugfix и release финальная подпись обязательно содержит слово “патч”, “обновление”, “релиз” или “документационное обновление”. Если автор забыл это в коротком тексте, policy добавляет компактную вводную фразу автоматически.
Публикация блокируется, если в патчноуте есть подозрение на секреты, .env, token-like строки, TELEGRAM_BOT_TOKEN, DEEPSEEK_API_KEY. Для uSugar дополнительно блокируются приватные Telegram identifiers, ngrok-ссылки и явные glucose-like медицинские значения.
Перед реальной публикацией можно безопасно проверить Telegram-настройки:
npm run diagnose:telegramДиагностика показывает только наличие переменных, результат getMe, целевой канал и результат getChat. Она не печатает токен целиком или частично.
Ожидаемые локальные переменные:
TELEGRAM_BOT_TOKEN=...
TELEGRAM_CHANNEL_ID=@uNewsLog
BOT_USERNAME=@uNewsDev_botЕсли публикация падает с Telegram sendPhoto failed: Unauthorized или диагностика показывает bot getMe: FAILED (401), проблема в TELEGRAM_BOT_TOKEN: токен отсутствует, отозван, введён неверно или не относится к нужному Telegram-боту. В этом случае нужно заменить TELEGRAM_BOT_TOKEN в локальном .env на актуальный токен @uNewsDev_bot.
Если локально публикация работает, а GitHub Actions падает, проверьте repository secrets для sunpole/uNews:
TELEGRAM_BOT_TOKENTELEGRAM_CHANNEL_ID
Значения секретов нельзя публиковать в README, логах, issue, pull request или патчноутах.
- Канал: @uNewsLog
- Бот:
@uNewsDev_bot
Главный workflow публикации — .github/workflows/publish-all-news.yml.
workflow_dispatchсdry_run=trueзапускаетnpm run publish:all:check;workflow_dispatchсdry_run=false, trigger-файл и расписание запускают реальную очередь;- тесты исходников и очереди выполняются перед каждым запуском, включая расписание;
- preflight безопасно проверяет Telegram bot token через
getMeи доступ к каналу черезgetChat; - queue audit скачивает все pending-изображения через GET и проверяет реальные bytes;
- непосредственно перед постом файл проверяется повторно и отправляется как проверенный Blob;
- до
publish:allworkflow настраивает Git identitygithub-actions[bot], необходимую для каждого per-post checkpoint; publish:allвыполняется черезscripts/run-publish-all-news.js;- после каждой успешной реальной публикации
data/published.jsonнемедленно коммитится; - при фатальном сбое runner записывает безопасную причину в
data/health.jsonиdata/errors.json; - workflow коммитит изменённый
data/до шага, который окончательно возвращает ошибку; - ошибка одного проекта записывается в
data/errors.jsonи не останавливает остальные проекты; - служебный Issue создаётся один раз при ошибке и автоматически закрывается после успешного восстановления;
data/published.jsonхранитmessage_ids,post_url,method,queued_atиpublished_atдля новых публикаций.
Workflow Diagnose public project news проверяет ту же очередь всех публичных проектов без публикации. Отдельный Quality checks запускает синтаксические и поведенческие тесты при изменениях кода.
data/health.json хранит:
- время последней успешной проверки;
- время и статус последней попытки;
- число ожидающих и заблокированных записей;
- следующий ключ очереди;
- безопасную последнюю ошибку без секретов.
data/errors.json хранит структурированный список ошибок текущей попытки. При успешном запуске список очищается.
scripts/lib/github-client.js— только обнаружение публичных репозиториев и чтение GitHub;scripts/lib/image-integrity.js— GET, сигнатуры, размеры, PNG CRC и декодирование;scripts/lib/telegram-client.js— только безопасная отправка в Telegram;scripts/lib/front-matter.js— единый разбор патчноутов;scripts/lib/queue.js— порядок версий, FIFO и пауза;scripts/lib/state.js— строгая проверка, атомарная запись и успешный health-state;scripts/lib/run-state.js— безопасная фиксация фатального состояния;scripts/patchnote-policy.js— правила содержимого и безопасности;scripts/publish-all-news.js— координация одного запуска;scripts/run-publish-all-news.js— защитный runner с сохранением fatal-state;scripts/record-run-failure.js— запись ошибки workflow preflight.
Пост https://t.me/uNewsLog/8 был опубликован до обязательного footer-rule, а затем исправлен maintenance-командой editMessageCaption: подпись обновлена ссылкой и хештегами без создания дубля.
uNews работает как GitHub-first очередь для публичных проектов. Версия 0.3.7 проверяет реальные bytes каждого изображения до FIFO и повторно перед Telegram; версия 0.3.6 сохраняет Git identity до publisher-step, а recovery-state 0.3.5, FIFO, безопасность и пауза остаются действующими.
Проверено на практике:
- бот публикует текст, одну картинку и Telegram-альбом;
- патчноут
500TD v1.0.2опубликован с двумя изображениями; - полный dry-run
0.3.7проверил 35 публичных проектов: 9 pending, 9 ready, 0 errors; - dry-run подтвердил пять исправленных uDream-изображений и отсутствие повторной публикации message
54; - Telegram preflight подтверждает
@uNewsDev_botи канал@uNewsLog; - после каждого успешного поста состояние фиксируется в GitHub;
- при ошибке state сохраняется до завершения workflow.
Правила работы с credentials находятся в SECURITY.md, последний аудит — в docs/SECURITY_AUDIT_2026-07-18.md.
MIT
Новости проектов Антона по умолчанию публикуются на русском языке. Английские технические слова допустимы как короткие термины (OCR, WebApp, runtime, Settings, Food Log), но основной текст Telegram-поста должен быть понятным русским описанием обновления.
Для project: uSugar policy дополнительно требует:
- поле
version; - поле
image_textс машинно-проверяемым описанием видимого текста карточки; - русский caption/body;
- отсутствие
????,???,�и типичных mojibake-фрагментов; - footer
#uSugar #тыСахар #uNews #Sunpole; - ссылку через
web_urlилиrepo_url; - отсутствие приватных Telegram identifiers, медицинских значений,
.env, токенов и ngrok-ссылок.
Карточка Telegram для uSugar должна быть на русском или почти без текста. Английская карточка для русского uSugar-поста считается ошибкой.
Старые опубликованные посты нельзя чинить повторной публикацией: это создаёт дубли. Если у поста известен message_id в data/published.json, используйте maintenance-команды:
npm run edit:media -- -- --message-id 14 --patchnote "../002_usugar/news/example.md" --key "published-key" --record-state
npm run edit:caption -- -- --message-id 14 --patchnote "../002_usugar/news/example.md" --key "published-key" --record-stateedit:media заменяет картинку и caption через Telegram editMessageMedia. Если Telegram отказывает в замене изображения, используйте edit:caption и честно зафиксируйте, что старая картинка осталась исторической.