Режим «В эфире»¶
Экран владельца и модераторов показывает текущее здоровье бота, безопасные быстрые команды и «Журнал событий» — сквозную ленту работы канала с фильтрами. Журнал — третья колонка панели; на узких экранах он уходит под неё на всю ширину. Высота у него фиксированная.
Статус здоровья строится при каждом обновлении из живой проверки API, ping базы данных и доступности подключённых подсистем. «Задержка» — время серверной проверки, а «Аптайм» показывает долю времени без зафиксированных инцидентов за два скользящих окна: последние 24 часа и последние 7 дней. Это не время работы стрима.
API¶
GET /api/v1/channels/{channel_id}/live— здоровье, первая страница ленты и каталог фильтров.GET /api/v1/channels/{channel_id}/live/health— общий статус, задержка API/БД, аптайм за 24 часа и 7 дней, активные инциденты, последняя активность и состояния сервисов.GET /api/v1/channels/{channel_id}/live/events— лента. Параметры (неизвестные значения игнорируются):category— CSV изstream,system,settings,rules,vip,shoutout,media,games,bot,moderation;severity—info,warning,errorилиproblems(всё, кромеinfo, и незакрытые инциденты);actor—people(действия людей в панели и команды модераторов),auto(бот, правила, система) или числовой id пользователя панели;from,to— период, RFC3339;source,q(поиск по заголовку, тексту и объекту;%и_ищутся буквально, до 100 символов);before— непрозрачный курсор изnext_cursor,limit— до 200. В ответе также приходитviewer_user_id, по нему интерфейс подписывает «Вы».GET /api/v1/channels/{channel_id}/live/events/summary— счётчики за период (from,to,q; по умолчанию последние 30 дней, глубже года не отдаётся):total,people,auto,problems,open_incidents,by_category,by_severityи список авторовactorsс числом событий. Из них строятся числа на вкладках и в разделах журнала.GET /api/v1/channels/{channel_id}/live/actions/game— возвращает «Барабан слов», Text Redact, подготовленные тексты и активный раунд.POST /api/v1/channels/{channel_id}/live/actions/game— запускает или останавливает выбранную игру (operation: start|stop); для Text Redact принимаетsource_id.POST /api/v1/channels/{channel_id}/live/actions/birthdayс{ "nickname": "..." }— запускает поздравление в OBS.GET /api/v1/channels/{channel_id}/live/actions/media— возвращает текущий просмотр, текущую/предыдущую/следующую серию, кандидат на продолжение и каталог для запуска нового медиа.stream_active— к эфиру уже привязано медиа;stream_online— эфир вообще идёт (есть незакрытый стрим), даже если медиа не выбрано.POST /api/v1/channels/{channel_id}/live/actions/media— выполняетstart,resume,previous,next,mark_episode,pause,finishилиdrop; новое медиа задаётся черезentry_idлибоtitle.finish,dropиmark_episodeработают без активного стрима;startиresumeбез эфира отвечают409с понятным текстом. Запуск, продолжение и переключение серии обновляют постер уже опубликованного анонса тем же механизмом, что команда!смотрим.GET /api/v1/admin/incidents— возвращает глобальные инциденты платформы.POST /api/v1/admin/incidents— создаёт глобальный инцидент с описанием, важностью и списком затронутых сервисов.PATCH /api/v1/admin/incidents/{incident_id}/resolve— централизованно закрывает глобальный инцидент.
Доступ имеют владелец канала, его модераторы и администраторы. Действия не обходят существующие сервисы игр и попадают в аудит.
Управление инцидентами находится в глобальной админке, в блоке «Быстрые действия админа». Глобальный инцидент отображается в здоровье всех каналов и учитывается в их аптайме. Автоматические технические ошибки конкретного канала остаются канальными сигналами.
Быстрые действия¶
Блок показывает только то, чем можно воспользоваться сейчас, и сам прячет остальное:
- Таймер — если для него создана и включена ссылка для OBS или пульт Stream Deck, либо он уже идёт или стоит на паузе. Кнопки: старт/пауза и сброс; сброс переспрашивает.
- Предикт и голосование — если есть заготовки и Twitch-аккаунт владельца даёт право управлять, либо предикт или голосование уже идёт (запущенное из панели Twitch тоже подхватывается). Идущие показываются с обратным отсчётом, процентами, лидером и кнопками: закрыть ставки, выбрать победителя, отменить; завершить голосование или убрать без итогов. Итог предикта, отмена и снятие голосования без итогов переспрашивают. Последняя запущенная заготовка запоминается в браузере и в следующий раз выбрана сразу, а итог завершённого предикта или голосования виден ещё пять минут.
- Игра — только если есть незавершённая карточка пройденных игр.
- Медиа — текущий или недосмотренный просмотр, а запуск нового — только во время эфира (
stream_online). - Поздравление с ДР — только если интерактив включён; без ссылки для OBS вместо поля ника показывается подсказка, где её создать.
Раздел, к которому у модератора нет доступа или который выключен для канала, не показывается и не вызывает ошибок. Идущие предикт и голосование обновляются раз в 5 секунд, подключённый таймер — раз в 10, предикт и голосование без активности, неподключённый таймер и медиа — раз в 30. Игра и поздравление перечитываются при открытии страницы, после своих кнопок и при возвращении на вкладку. На скрытой вкладке опрос останавливается, а при возвращении на неё все данные перечитываются сразу.
Источники ленты¶
Лента объединяет channel_activity_events с журналами модулей. Часть событий пишется явно, часть берётся прямо из таблиц, чтобы не дублировать данные:
| Раздел | Откуда берётся |
|---|---|
settings, rules, vip, media, games, shoutout, moderation (изменения людей) |
channel_activity_events: каждое изменение из панели записывается с автором, ролью и разделом (metadata.section). Заголовок и тема берутся из таблицы mutationSubjects (Карточка игры изменена, Титры для OBS изменены…): новый раздел панели нужно добавить туда, иначе он попадёт в запасное «Настройки изменены». Название объекта (target_name) берётся из тела запроса или, для игр, правил и команд, из базы до выполнения запроса, поэтому известно и у удаления. Список полей (fields) — только названия. Безопасные значения (changes) — переключатели «включено/выключено», известные статусы («Брошено») и несколько числовых настроек вроде прогресса; значения остальных полей не пишутся никогда. Если страница отправила целую форму (больше 8 полей), запись говорит «сохранена вся форма», а не перечисляет поля: по такому запросу нельзя понять, что поменялось. Исключение — раздел, который сам сравнивает значения до и после и передаёт их журналу (reportMutationChanges, metadata.diff): сейчас это «Общие настройки» и «Шатауты и рейды». Тогда строка называет до трёх изменений вида «!оракул: вкл → выкл» и «и ещё N», раскрытая запись показывает каждое «было → стало», а сохранение без изменений пишется как «Сохранено без изменений». В diff попадают только переключатели, числа и выбранные варианты; у текстов объявлений шатаутов — только факт изменения («прежний → новый»), сам текст в журнал не пишется |
rules |
срабатывания правил (channel_rule_runs) с именем правила, зрителем, сообщением или командой и результатом |
bot |
результаты действий бота из outbox: тип действия, статус, число попыток и класс ошибки (error_kind: rate_limit, timeout, forbidden, not_found, unknown); начало рассылки о стриме (notification.stream_started, одно событие на стрим с числом получателей Telegram и ВКонтакте); сбой стартового поста в Telegram (notification.telegram_failed). Обычные ответы правил в чат идут напрямую, мимо outbox, поэтому видны как срабатывания правил, а не как действия бота |
moderation |
таймауты и баны бота (moderation.timeout, moderation.ban, при сбое — с суффиксом .failed), сигналы и ручные решения кабинета модератора |
vip |
channel_vip_events без записей «серия» (action=streak): их много, и на странице VIP они вынесены в отдельную ленту |
media |
записи медиатеки и сессии воспроизведения |
games |
раунды «Барабана слов», Словотрона и Text Redact (старт — только если раунд начат из чата, старт из панели уже записан с автором), результаты «Большого дела», выплаты роста |
stream |
начало и конец стрима, предикт и голосование Twitch (последнее состояние), донаты |
system |
глобальные и канальные инциденты; связь с Twitch: проблемы с доступом владельца (twitch.token.*: не подключён, не хватает прав, отклонён, не обновился, чужой аккаунт; при восстановлении — twitch.token.ok), неудачные подписки EventSub (twitch.eventsub.subscribe_failed), переподключение к чату (twitch.chat.reconnected, записывается каждому каналу) и уведомления Twitch, из-за которых бот не может писать в чат (twitch.chat.notice: бот заблокирован, чат приостановлен, режим «только для фолловеров/подписчиков/смайлов», требуется подтверждённая почта или телефон, лимит сообщений) |
Запись из панели (recordLiveEvent) работает только в процессе, где у Server задан activityLog. Он задаётся и в основном процессе, и в автономном ownadmin-api (NewOwnAdminAPIHandler); в новом runtime, который принимает изменения из панели, его нужно подключить так же. Если журнал не подключён, процесс один раз пишет в лог ошибку «activity log is not configured», а события теряются.
Таймауты и баны записываются в одном месте, timeoutViaHelixWithActor; вызывающий код передаёт причину через withModerationCause (модуль-инициатор, понятное название причины, ник зрителя, правило, ник модератора, давшего команду в чате). Новому месту, которое выдаёт таймаут, достаточно добавить такой контекст — иначе запись получит источник «Бот» без пояснения.
Секреты и чувствительные данные¶
Журнал видят владелец и модераторы, а часть событий собирается из текстов ошибок и чужих журналов, поэтому защита многослойная:
- Событие строится из готовых формулировок. Тексты ошибок, ответы Twitch и Telegram и адреса запросов в системные события и события модерации не попадают — только класс причины (
error_kind), код ответа и публичное имя типа подписки. - Чистка при записи.
activitylog.Storeпропускает заголовок, текст, объект и метаданные черезRedactText/ScrubMetadata: скрываются Bearer-токены,oauth:-пароли IRC, токены Telegram-ботов, парыtoken=…/secret=…/password=…, длинные токеноподобные строки, значения ключей с именами вродеtoken,secret,url,link,webhook. Из ссылок остаётся только адрес сайта; путь сохраняется лишь у публичных страницtwitch.tv. - Чистка и белый список при выдаче. Каждое событие перед отправкой клиенту проходит
RedactEvent: метаданные отдаются только по списку известных полей (publicMetadataKeys), всё остальное, включая старые записи и «сырые» поля чужих таблиц, отбрасывается. Новое поле метаданных, которое должен видеть интерфейс, нужно добавить в этот список. - Дубли.
RecordOnceпишет повторяющийся сбой раз за окно (токен — 12 часов, подписка — 6, уведомление чата — 6 или 1, переподключение — 10 минут) и защищает от потока записей; проверка идёт в базе, поэтому работает между процессами и после перезапуска.
Системные события — предупреждения, не инциденты: они не снижают аптайм и не требуют закрытия, а «снова в порядке» пишется отдельным событием, только если до этого была проблема.
channel_activity_events предназначена для пользовательского аудита и инцидентов. В metadata нельзя писать токены, тексты секретов и полные входные payload. Ошибки сохраняются как безопасный класс/статус, а техническая детализация остаётся в серверных логах и трассировке. По той же причине лента не отдаёт сырой payload outbox, текст сообщения донатера и секретное слово игры.
Лента загружается страницами по курсору before — «время|id последнего события», чтобы события с одинаковой меткой не терялись на границе страниц; старый курсор из одного времени по-прежнему принимается.
Журнал событий в интерфейсе¶
- Вкладки: Все события, Изменения (
actor=people), Работа бота (actor=auto), Проблемы (severity=problems) — с числами из сводки. - Фильтры: поиск (с задержкой 350 мс), период (сегодня, 24 часа, 7 и 30 дней, всё время; по умолчанию 7 дней), автор, важность и разделы-чипы в одну прокручиваемую строку. Раздел без событий не прячется: у него просто стоит 0.
- Список прокручивается внутри журнала и догружает более ранние события сам — по 40 за раз, пока они не закончатся; если окно ещё не заполнено, подгрузка идёт без прокрутки. Кнопки «показать ещё» нет, длина блока не растёт.
- События сгруппированы по дням. Строка показывает действие, объект, кто это сделал и время; по нажатию раскрывается карточка: что произошло, кто, когда, почему, подробности, ссылка на раздел панели и поиск по зрителю.
- Новые события появляются раз в 10 секунд, но не вставляются в читаемый список: сверху появляется кнопка «N новых событий — показать». Быстрое действие из панели обновляет журнал сразу.
- «Скачать таблицу» выгружает события по текущим фильтрам в CSV (разделитель
;, BOM для Excel) — до 2000 последних; ячейки, начинающиеся с=,+,-,@, экранируются.
Новые интеграции должны писать событие с устойчивым event_type, источником, сущностью и actor-role. Ошибка, которая требует внимания владельца, помечается is_incident=true; после устранения ей заполняется resolved_at.
Запуск¶
После выкладки применить миграции, затем перезапустить backend и frontend. Для локальной разработки: make run в backend и npm run dev в front-pulse-caster/tg-mini-app.