Перейти к содержанию

Режим «В эфире»

Экран владельца и модераторов показывает текущее здоровье бота, безопасные быстрые команды и «Журнал событий» — сквозную ленту работы канала с фильтрами. Журнал — третья колонка панели; на узких экранах он уходит под неё на всю ширину. Высота у него фиксированная.

Статус здоровья строится при каждом обновлении из живой проверки 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 (модуль-инициатор, понятное название причины, ник зрителя, правило, ник модератора, давшего команду в чате). Новому месту, которое выдаёт таймаут, достаточно добавить такой контекст — иначе запись получит источник «Бот» без пояснения.

Секреты и чувствительные данные

Журнал видят владелец и модераторы, а часть событий собирается из текстов ошибок и чужих журналов, поэтому защита многослойная:

  1. Событие строится из готовых формулировок. Тексты ошибок, ответы Twitch и Telegram и адреса запросов в системные события и события модерации не попадают — только класс причины (error_kind), код ответа и публичное имя типа подписки.
  2. Чистка при записи. activitylog.Store пропускает заголовок, текст, объект и метаданные через RedactText/ScrubMetadata: скрываются Bearer-токены, oauth:-пароли IRC, токены Telegram-ботов, пары token=…/secret=…/password=…, длинные токеноподобные строки, значения ключей с именами вроде token, secret, url, link, webhook. Из ссылок остаётся только адрес сайта; путь сохраняется лишь у публичных страниц twitch.tv.
  3. Чистка и белый список при выдаче. Каждое событие перед отправкой клиенту проходит RedactEvent: метаданные отдаются только по списку известных полей (publicMetadataKeys), всё остальное, включая старые записи и «сырые» поля чужих таблиц, отбрасывается. Новое поле метаданных, которое должен видеть интерфейс, нужно добавить в этот список.
  4. Дубли. 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.