Обзор
Система конкурсов состоит из двух подсистем:
- Ежедневные игры — мини-игры с мгновенными призами (дни подписки, баланс)
- Реферальные конкурсы — соревнования по привлечению рефералов с рейтингами
Обе подсистемы управляются общим флагом CONTESTS_ENABLED, но работают на разных моделях данных.
CONTESTS_ENABLED=true
CONTESTS_BUTTON_VISIBLE=true
Участие в конкурсах доступно только пользователям с активной подпиской (платной или триальной).
Ежедневные игры
Архитектура
ContestTemplate → ContestRound → ContestAttempt
(шаблон) (раунд) (попытка)
- Шаблон — определяет тип игры, расписание, призы
- Раунд — конкретный экземпляр игры с временным окном
- Попытка — одна попытка пользователя (уникальна: один раунд — одна попытка)
7 типов игр
| Игра | Механика | Ввод | Описание |
|---|
| Quest Buttons | Сетка 3x3 | Кнопка | Найти скрытую ноду в сетке |
| Lock Hack | 20 замков | Кнопка | Найти единственный открытый замок |
| Server Lottery | 10 флагов стран | Кнопка | Угадать правильный сервер |
| Blitz Reaction | Одна кнопка | Кнопка | Кто первый нажмёт — тот победил |
| Letter Cipher | Числовой шифр | Текст | Расшифровать слово (A=1, B=2…) |
| Emoji Guess | Перемешанные эмодзи | Текст | Угадать название сервиса по подсказкам |
| Anagram | Перемешанные буквы | Текст | Составить слово из букв |
Расписание и ротация
Каждый шаблон имеет:
schedule_times — список времён запуска (формат HH:MM, через запятую)
cooldown_hours — длительность раунда (по умолчанию 24ч)
is_enabled — вкл/выкл для конкретной игры
Фоновый сервис ContestRotationService проверяет расписание каждые 60 секунд. При наступлении времени создаёт новый раунд и:
- Анонсирует в канале подписки (
CHANNEL_SUB_ID)
- Рассылает всем пользователям с активной подпиской
Призы
| Тип приза | Описание |
|---|
days | Продление подписки на N дней |
balance | Начисление на баланс (в копейках) |
custom | Произвольный текст (ручное выполнение) |
Определение победителя
Используется атомарная проверка с блокировкой строки (SELECT FOR UPDATE):
- Пользователь отправляет ответ
- Стратегия игры проверяет правильность
- Если верно — блокируется строка раунда, проверяется
winners_count < max_winners
- При победе приз выдаётся автоматически
Настройки шаблонов по умолчанию
| Игра | Расписание | Макс. победителей | Приз |
|---|
| Quest Buttons | 10:00, 18:00 | 3 | 1 день |
| Lock Hack | 09:00, 19:00 | 1 | 5 дней |
| Letter Cipher | 12:00, 20:00 | 1 | 1 день |
| Server Lottery | 15:00 | 1 | 7 дней |
| Blitz Reaction | 11:00, 21:00 | 1 | 1 день |
| Emoji Guess | 13:00 | 1 | 1 день |
| Anagram | 17:00 | 1 | 1 день |
Все 7 шаблонов создаются автоматически при первом запуске, но отключены по умолчанию. Включайте их по мере необходимости.
Реферальные конкурсы
Как работают
Реферальные конкурсы — это соревнования с ограниченным временем, где пользователи зарабатывают очки за привлечение рефералов.
Типы конкурсов
| Тип | Что считается очком |
|---|
referral_paid | Реферал зарегистрировался и купил подписку |
referral_registered | Реферал просто зарегистрировался |
Создание конкурса (FSM)
- Выбор режима:
referral_paid или referral_registered
- Название конкурса
- Описание (опционально,
- для пропуска)
- Текст призов (опционально)
- Дата начала (
дд.мм.гггг ЧЧ:ММ)
- Дата окончания
- Время ежедневной сводки (
ЧЧ:ММ или несколько через запятую)
Ежедневные сводки
- Отправляются в канал подписки и чат администраторов
- Поддержка нескольких времён в день (
daily_summary_times)
- По завершении конкурса — финальная сводка (отправляется один раз)
- Содержание: название, топ-5, общее количество рефералов, призы, оставшееся время
Виртуальные участники
Администраторы могут добавлять “призраков” для создания видимости конкуренции:
- Индивидуально — имя + количество рефералов
- Массово (“Массовка”) — 1-50 призраков с одинаковым количеством рефералов и случайными именами
- В админке отмечены иконкой призрака
- В публичном канале выглядят как обычные участники
Лидерборд
Лидерборд объединяет реальных участников (из referral_contest_events) и виртуальных. Доступен из админки (топ-10) и в ежедневных сводках (топ-5).
Управление (админ-панель)
Ежедневные игры
| Действие | Описание |
|---|
| Список шаблонов | Все 7 игр с состоянием вкл/выкл |
| Вкл/Выкл | Переключение конкретной игры |
| Запуск раунда | Немедленный запуск (ручной или с автовключением) |
| Редактирование | Тип приза, значение, макс. победителей, расписание, cooldown |
| Сброс попыток | Сбросить попытки активного раунда |
| Закрытие раунда | Принудительно завершить текущий раунд |
| Массовые операции | Закрыть все раунды, сбросить все попытки, запустить все |
Реферальные конкурсы
| Действие | Описание |
|---|
| Список конкурсов | С пагинацией (5 на страницу) |
| Детали | Статус, период, призы, топ-5, всего событий |
| Вкл/Выкл | Переключение активности |
| Статистика | Участники, рефералы, платные/бесплатные, суммы |
| Синхронизация | Синхронизация событий с реальными платежами |
| Очистка | Удаление невалидных событий |
| Виртуальные участники | Добавление, редактирование, удаление, массовое создание |
Видимость
Конкурсы имеют многоуровневую систему видимости:
CONTESTS_ENABLED — глобальный переключатель (фоновые сервисы, обработчики)
CONTESTS_BUTTON_VISIBLE — видимость кнопки “Конкурсы” в главном меню
- Вкл/выкл шаблона — каждая ежедневная игра переключается отдельно
- Активность конкурса — каждый реферальный конкурс переключается отдельно
- Требование подписки — только пользователи с активной/триальной подпиской
Настройка
| Переменная | По умолчанию | Описание |
|---|
CONTESTS_ENABLED | false | Глобальный переключатель системы конкурсов |
CONTESTS_BUTTON_VISIBLE | false | Показывать кнопку “Конкурсы” в меню |
TIMEZONE | UTC | Часовой пояс для расписания |
CHANNEL_SUB_ID | — | ID канала для анонсов |
Cabinet и Web API
Cabinet (для пользователей)
GET /contests/count — количество доступных (неотыгранных) игр
GET /contests — список доступных игр со статусом
GET /contests/{round_id} — данные игры
POST /contests/{round_id}/answer — отправка ответа
Web API (для администраторов)
Полный CRUD для шаблонов, раундов, попыток (ежедневные игры) и реферальных конкурсов с аутентификацией по API-токену.