# 🎲 GM-Relay: TTRPG Session Scheduling Bot & Web Dashboard **GM-Relay** — это комплексное решение для Мастеров Подземелий (ГМов), состоящее из высокопроизводительного Telegram-бота, Discord worker и удобного веб-интерфейса. Предназначено для автоматизации записи игроков на сессии, управления расписанием и проведения игр. Проект разработан с упором на производительность, архитектуру Vertical Slice, Native AOT (для бота) и удобство развертывания с использованием .NET Aspire. **Текущая версия:** `v3.5.0`. --- ## ✨ Key Features ### 🤖 Telegram Bot - **📅 Создание расписаний (Batch Sessions)**: Создавайте сразу несколько игр одним сообщением изменения (на недельный месяц в перед). - **🖼 Обложки расписаний**: И batch-посту можно прикрепить фото к `/newsession` или указать строку `Картинка: https://...`; бот отправит обложку перед сообщением записи. - **⚡ Быстрые повторы расписания**: Для регулярной кампании можно указать одну дату, количество игр и интервал, а бот сам развернёт повторяющийся batch. - **✋ Интерактивная запись и выход**: Игроки записываются на конкретные даты и самостоятельно снимают запись нажатием одной кнопки. - **👥 Лимит мест и лист ожидания**: ГМ задаёт максимальный состав, бот не переполняет сессию, автоматически ведёт очередь ожидания и освобождённое место отдаёт первому ожидающему. - **📁 Поддержка Форумов (Telegram Topics)**: Если `/newsession` запущен в теме форума Telegram, расписание и групповые уведомления остаются в этой теме; при запуске из корня форума бот создает отдельную тему и сообщает о необходимости прав admin/Manage Topics, если их не хватает. - **❌ Управление сессиями**: Owner и назначенные co-GM могут создавать, отменять, удалять и переносить игры из Telegram через `/listsessions`; публичный пост записи показывает только кнопки игроков. - **🔄 Голосование за перенос**: Быстрый поиск свободного места с через свободное недель и кнопками новых времени и дедлайном. - **🔔 Уведомления**: Игрок получают за 24 часа, напоминание за 1 час, ссылку перед игрой, отмены и переносы; групповые уведомления при этом остаются. - **🕐 Режим уведомлений batch**: Для каждой пачки можно выбрать `В группе и в личку` или `Только в группе`. - **⬆️ Управление очередью**: Веб-интерфейс показывает заполненность, лист ожидания и позволяет ГМу поднять первого игрока из очереди. - **🔄 Автоматическая синхронизация**: Любые изменения в веб-интерфейсе мгновенно обновляют сообщения с расписанием в подключенных Telegram- и Discord-каналах. ### Discord Bot - **Slash-команды `/newsession` и `/listsessions`**: GM создаёт сессии и публикует актуальное расписание прямо в Discord-канале. - **Кнопки Join/Leave с ephemeral-ответами**: игроки нажимают Join/Leave в Discord-сообщении; бот отвечает ephemeral-сообщением и обновляет schedule message. - **RSVP (подтверждения) за 24ч до сессии**: scheduler публикует запрос подтверждения в Discord-канале, игроки отвечают кнопками, а GM получает итоги RSVP. - **DM-напоминания за 1ч и ссылки перед игрой**: one-hour reminders и join-link notifications отправляются в Discord DM при включённых личных уведомлениях; сбои DM логируются без публичного fallback. - **Reschedule voting (голосование за перенос)**: deadline-сервис обновляет Discord vote message и schedule message через `IPlatformMessenger`. - **Лимиты и waitlist**: при заполненном составе игрок попадает в waitlist, а при выходе участника первый ожидающий автоматически продвигается в основной состав. ### 🌐 Web Dashboard (Blazor Server) - **🔐 Авторизация через Telegram**: Telegram Login Widget с HMAC-SHA256 валидацией. - **📱 Telegram Mini App Dashboard**: Мобильная панель открывается из Telegram, проверяет `initData` на сервере, учитывает safe-area телефона и верхнюю панель Telegram. - **✏️ Редактирование**: Детальное изменение дат, названий и статусов сессий. - **🤝 Co-GM и делегирование**: Owner назначает помощников по Telegram ID; co-GM управляет расписанием, но **не может назначать других co-GM**. - **🌍 Публичные страницы клубов**: Owner и co-GM включают read-only страницу `/club/{slug}` и отдельные ссылки `/s/{sessionId}` только для опубликованных сессий; состав игроков и приватные join-ссылки не показываются. - **🧑‍🏫 Публичные профили мастеров**: мастер управляет профилем из `/profile`, публикует описание на `/gm/{slug}`, а публичные клубы, игры и каталог ссылаются на профиль без раскрытия platform identifiers. - **📋 Шаблоны кампаний**: Вкладка `Шаблоны` отдельно от страницы группы: сохранение типовых параметров и запуск нового batch из шаблона. - **📦 Bulk-операции для Batch Sessions**: - обновить общий `title`/`link` у всей пачки; - перенести пачку на фиксированный шаг в днях; - клонировать batch на следующую неделю или месяц. - **⬆️ Управление очередью**: Заполненность, лист ожидания и ручное повышение игрока из очереди. - **📜 История изменений сессий**: Страница `/session/{id}/history` показывает аудит-лог всех значимых изменений (время, ссылка, название, участники, статус) с указанием акторов и дат. - **📊 Статистика посещаемости**: Страница `/group/{id}/stats` показывает долю присутствия, количество пропусков и среднюю явку по каждому игроку группы. - **🔄 Автосинхронизация**: Изменения в вебе мгновенно перерисовывают platform message расписания через `IPlatformMessenger`. --- ## 🛠 Технологический стек | Компонент | Технология | |---|---| | Язык | C# 14 (.NET 10) | | Архитектура | Vertical Slice + общая библиотека `GmRelay.Shared` | | Боты | Telegram.Bot (**Native AOT**), NetCord Gateway (Discord worker внутри `GmRelay.Bot`) | | Веб | Blazor Server | | Оркестрация | .NET Aspire (`GmRelay.AppHost`) | | БД | PostgreSQL | | ORM | Dapper + **Dapper.AOT** (source generators) | | Миграции | DbUp | | Развёртывание | Docker Compose, Multi-arch (**AMD64/ARM64**) | > [!NOTE] > При использовании Dapper в режиме Native AOT все SQL-запросы используют строго типизированные DTO; динамические типы (`dynamic`) не поддерживаются. --- ## 🚀 Быстрый старт (Docker Compose) **Требования:** Docker и Docker Compose. ### 1. Настройка окружения ```bash cp .env.example .env ``` **Ключевые переменные `.env`:** ```env # Токен от @BotFather (используется ботом и как секретный ключ веб-авторизации) TELEGRAM_BOT_TOKEN=ваш_токен_здесь # Токен Discord application bot DISCORD_BOT_TOKEN=ваш_discord_токен_здесь # Discord OAuth (для Web Dashboard) DISCORD_CLIENT_ID=ваш_discord_client_id_здесь DISCORD_CLIENT_SECRET=ваш_discord_client_secret_здесь DISCORD_REDIRECT_URI=https://your-domain.example/auth/discord/callback # Имя бота без @ (для Telegram Login Widget) TELEGRAM_BOT_USERNAME=ваше_имя_бота_здесь # HTTPS URL Mini App, например https://your-domain.example/miniapp TELEGRAM_MINI_APP_URL=https://your-domain.example/miniapp POSTGRES_PASSWORD=ваш_надежный_пароль GMRELAY_WEB_PORT=8080 ``` **Настройка в @BotFather:** - Команда `/setdomain` для работы виджета авторизации на вашем домене. - Для Mini App настройте домен Web Dashboard и menu button на URL из `TELEGRAM_MINI_APP_URL`. - Начиная с **v1.9.3** дополнительных действий для фикса входа не требуется: fallback выполняется внутри активного Telegram WebView по тому же HTTPS-адресу `/miniapp`. ### 2. Запуск ```bash docker compose up -d ``` **Автоматически выполняется:** - создание Docker-сети и volume PostgreSQL; - подъём PostgreSQL (`db:5432`); - запуск бота с плавной миграцией (DbUp); - запуск Discord Gateway worker на NetCord (healthcheck на `:8082`); - запуск веб-приложения с подключением к БД и Telegram API. ### 3. Первоначальная настройка 1. Напишите боту `/start`. 2. Создайте группу через `/newgroup`. 3. Откройте Mini App или Web Dashboard для расширенного управления. 4. Для Discord пригласите application bot на сервер с правами `bot` и `applications.commands`. Скопируйте `DISCORD_BOT_TOKEN` в `.env`; `DISCORD_CLIENT_ID`, `DISCORD_CLIENT_SECRET` и `DISCORD_REDIRECT_URI` нужны только для входа в Web Dashboard через Discord. 5. Перезапустите Docker Compose (`docker compose up -d`), а затем в Discord создайте сессию через `/newsession` или опубликуйте расписание через `/listsessions`; игроки записываются и выходят кнопками в опубликованном сообщении. ## 💾 Backup и восстановление Проект включает автоматический ежедневный backup PostgreSQL через сервис `db-backup` в Docker Compose. ### Как это работает - **Каждый день в 03:00** выполняется `pg_dump` базы `gmrelay_db`. - Дампы сжимаются (`gzip`) и сохраняются в volume `pgbackups` (`/backups`). - Формат имени: `gmrelay_db_YYYYMMDD_HHMMSS.sql.gz`. - Ротация: по умолчанию хранятся последние **7 дней** (настраивается через `BACKUP_RETENTION_DAYS`). ### Проверка бэкапов ```bash docker compose exec db-backup ls -la /backups ``` ### Ручное создание дампа ```bash docker compose exec db-backup sh -c "pg_dump -h db -U gmrelay -d gmrelay_db | gzip > /backups/gmrelay_db_manual.sql.gz" ``` ### Восстановление из бэкапа ```bash # Использовать последний автоматический бэкап ./scripts/restore.sh # Или указать конкретный файл ./scripts/restore.sh backups/gmrelay_db_20260512_030000.sql.gz ``` > [!WARNING] > Восстановление **перезаписывает текущую базу данных**. Убедитесь, что вы понимаете последствия, прежде чем запускать `restore.sh`. ### Переменные окружения (опциональные) ```env BACKUP_RETENTION_DAYS=7 BACKUP_VOLUME_NAME=game_pgbackups ``` --- ## 🗂 Структура репозитория ``` ├── src/ │ ├── GmRelay.AppHost/ # .NET Aspire orchestrator │ ├── GmRelay.Bot/ # Telegram- и Discord-бот (Native AOT + NetCord Gateway worker) │ ├── GmRelay.ServiceDefaults/ # Aspire service defaults │ ├── GmRelay.Shared/ # Общие доменные модели │ └── GmRelay.Web/ # Blazor Server dashboard ├── tests/ │ └── GmRelay.Bot.Tests/ # xUnit + NSubstitute ├── compose.yaml # Docker Compose (AMD64 + ARM64) └── .env.example # Шаблон переменных окружения ``` --- ## 👨‍💻 Для разработчиков - **Архитектура**: проект следует Vertical Slice с явным DI. Подробности — в [ADR-001](docs/adr/0001-use-vertical-slice-native-aot-and-aspire.md) и [ADR-002](docs/adr/002-platform-neutral-batch-rendering.md). - **Добавление обработчика**: из-за Native AOT все DI-регистрации выполняются вручную в `src/GmRelay.Bot/Program.cs` (assembly scanning не используется). - **Миграции**: SQL-скрипты добавляются как embedded resources в `src/GmRelay.Bot/Migrations/` и применяются автоматически при старте бота через DbUp. - **Тесты**: `dotnet test tests/GmRelay.Bot.Tests/GmRelay.Bot.Tests.csproj --verbosity normal` - **Сборка**: `dotnet build` --- ## 📜 Лицензия MIT License. См. [LICENSE](./LICENSE). --- *Построено с ❤️ для TTRPG-сообщества.*