213 lines
19 KiB
Markdown
213 lines
19 KiB
Markdown
# 🎲 GM-Relay: TTRPG Session Scheduling Bot & Web Dashboard
|
||
|
||
**GM-Relay** — это комплексное решение для Мастеров Подземелий (ГМов), состоящее из высокопроизводительного Telegram-бота и удобного веб-интерфейса. Предназначено для автоматизации записи игроков на сессии, управления расписанием и проведения игр.
|
||
|
||
Проект разработан с упором на производительность, архитектуру Vertical Slice, Native AOT (для бота) и удобство развертывания с использованием .NET Aspire.
|
||
|
||
**Текущая версия:** `v1.9.3`.
|
||
|
||
---
|
||
|
||
## ✨ Ключевые возможности
|
||
|
||
### 🤖 Telegram Бот
|
||
- **📅 Создание расписаний (Batch Sessions)**: Создавайте сразу несколько игр одним сообщением (на неделю или месяц вперед).
|
||
- **⚡ Быстрые повторы расписания**: Для регулярной кампании можно указать одну дату, количество игр и интервал, а бот сам развернёт повторяющийся batch.
|
||
- **✋ Интерактивная запись и выход**: Игроки записываются на конкретные даты и самостоятельно снимают запись нажатием одной кнопки.
|
||
- **👥 Лимит мест и лист ожидания**: ГМ задаёт максимальный состав, бот не переполняет сессию, автоматически ведёт очередь ожидания и освобождённое место отдаёт первому ожидающему.
|
||
- **📁 Поддержка Форумов (Telegram Topics)**: Бот автоматически создает тему во вложенных чатах Telegram под каждую новую пачку игр.
|
||
- **❌ Управление сессиями**: Owner и назначенные co-GM могут создавать, отменять, удалять и переносить игры прямо из Telegram.
|
||
- **🔄 Голосование за перенос**: При переносе сессии GM предлагает 2-3 новых времени и дедлайн, игроки голосуют кнопками, а бот показывает текущие результаты и применяет победивший вариант.
|
||
- **🔔 Персональные уведомления**: Игроки получают DM о RSVP за 24 часа, напоминание за 1 час, ссылку перед игрой, отмены и переносы; групповые уведомления при этом остаются.
|
||
- **🗓 Экспорт в Календарь**: Генерация файла `.ics` для добавления всех игр в Google, Apple или Яндекс Календарь одной командой.
|
||
- **🚀 Native AOT**: Скомпилирован в нативный бинарный файл. Мгновенный запуск и минимальное потребление памяти. Идеально для **Raspberry Pi**.
|
||
|
||
### 🌐 Web Dashboard (Blazor Server)
|
||
- **🔐 Авторизация через Telegram**: Безопасный вход с использованием Telegram Login Widget (HMAC-SHA256 валидация).
|
||
- **📱 Telegram Mini App Dashboard**: Мобильная версия dashboard открывается прямо из Telegram, проверяет WebApp `initData` на сервере и использует те же права owner/co-GM, что и обычный Web Dashboard. Если Mini App попадает в fallback-вход, Telegram Login Widget авторизует пользователя callback-запросом внутри текущего WebView, а интерфейс учитывает safe-area телефона и верхнюю панель Telegram.
|
||
- **📝 Удобное редактирование**: Веб-интерфейс для детального редактирования сессий, изменения дат, названий и статусов.
|
||
- **🤝 Co-GM и делегирование**: Owner группы назначает помощников по Telegram ID, а co-GM получает доступ к управлению расписанием в Telegram и Web Dashboard.
|
||
- **📋 Шаблоны кампаний**: Owner и co-GM управляют типовыми параметрами кампаний в отдельной вкладке `Шаблоны`, а на странице группы запускают новый повторяющийся batch из выбранного шаблона.
|
||
- **🧩 Bulk-операции для Batch Sessions**: ГМ может обновить общий title/link, перенести всю пачку на фиксированный шаг и клонировать batch на следующую неделю или месяц.
|
||
- **🔕 Режим уведомлений batch**: Для каждой пачки можно выбрать `В группе и в личку` или `Только в группе`.
|
||
- **⬆️ Управление очередью**: Веб-интерфейс показывает заполненность, лист ожидания и позволяет ГМу поднять первого игрока из очереди.
|
||
- **🔄 Автоматическая синхронизация**: Любые изменения в веб-интерфейсе мгновенно обновляют сообщения с расписанием в Telegram-чатах игроков.
|
||
- **🕒 Управление временем**: UI адаптирован под московское время (UTC+3), в то время как база данных работает в UTC.
|
||
|
||
---
|
||
|
||
## 🛠 Технологический стек
|
||
|
||
- **Язык**: C# 14 (.NET 10)
|
||
- **Архитектура**: Vertical Slice Architecture, общая библиотека (`GmRelay.Shared`) для доменной логики.
|
||
- **Бот**: Telegram.Bot, Native AOT.
|
||
- **Веб-интерфейс**: Blazor Server.
|
||
- **Оркестрация**: .NET Aspire (`GmRelay.AppHost`).
|
||
- **База данных**: PostgreSQL
|
||
- **ORM**: Dapper (с использованием Dapper.AOT для source generators).
|
||
- **Миграции**: DbUp.
|
||
- **Развертывание**: Docker Compose + Multi-arch (AMD64/ARM64).
|
||
|
||
---
|
||
|
||
## 🚀 Быстрый старт (Docker Compose)
|
||
|
||
Проект использует Docker Compose для одновременного запуска базы данных, бота и веб-интерфейса.
|
||
|
||
### 1. Подготовка
|
||
Убедитесь, что у вас установлены **Docker** и **Docker Compose**.
|
||
|
||
### 2. Настройка окружения
|
||
Скопируйте файл-шаблон и заполните его значениями:
|
||
|
||
```bash
|
||
cp .env.example .env
|
||
```
|
||
|
||
Отредактируйте `.env`:
|
||
|
||
```env
|
||
# Токен вашего бота от @BotFather (используется и для бота, и как секретный ключ для веб-авторизации)
|
||
TELEGRAM_BOT_TOKEN=ваш_токен_здесь
|
||
|
||
# Имя вашего бота в Telegram (без @), например: GmRelayBot.
|
||
# Найти его можно в информации о боте у @BotFather.
|
||
# Используется для работы виджета авторизации (Telegram Login Widget).
|
||
TELEGRAM_BOT_USERNAME=ваше_имя_бота_здесь
|
||
|
||
# HTTPS URL Mini App dashboard, например: https://your-domain.example/miniapp.
|
||
# Используется кнопкой меню Telegram и кнопкой /start.
|
||
TELEGRAM_MINI_APP_URL=https://your-domain.example/miniapp
|
||
|
||
# Пароль для базы данных PostgreSQL
|
||
POSTGRES_PASSWORD=ваш_надежный_пароль
|
||
|
||
# Локальный порт веб-интерфейса GM-Relay
|
||
GMRELAY_WEB_PORT=8080
|
||
```
|
||
|
||
*(Опционально)* Настройте домен Telegram бота в @BotFather командой `/setdomain` для работы виджета авторизации на вашем сайте.
|
||
|
||
Для Telegram Mini App настройте в @BotFather домен Web Dashboard и menu button на URL из `TELEGRAM_MINI_APP_URL`. Бот также показывает кнопку `Открыть dashboard` в ответе на `/start`, если переменная задана. Начиная с v1.9.3 дополнительных действий в BotFather для фикса входа не требуется: URL остаётся тем же HTTPS-адресом `/miniapp`, а fallback-вход выполняется внутри активного Telegram WebView.
|
||
|
||
### 3. Запуск
|
||
Выполните команду:
|
||
```bash
|
||
docker compose up -d
|
||
```
|
||
Инфраструктура автоматически:
|
||
- Создаст локальную Docker-сеть и volume PostgreSQL, если их ещё нет.
|
||
- Поднимет PostgreSQL, доступный для контейнеров как `db:5432`.
|
||
- Запустит бота (применив миграции БД).
|
||
- Запустит веб-интерфейс на `http://localhost:8080` или другом порту из `GMRELAY_WEB_PORT`.
|
||
|
||
---
|
||
|
||
## ⚙️ Настройка бота в Telegram
|
||
|
||
Чтобы бот работал корректно:
|
||
1. **Добавьте бота в группу** (или Супергруппу/Форум).
|
||
2. **Назначьте бота Администратором**.
|
||
3. **Необходимые права**:
|
||
* `Выбор тем` (Managed Topics) — **обязательно** для Форумов.
|
||
* `Отправка сообщений`.
|
||
* `Закрепление сообщений` — рекомендуется.
|
||
|
||
> [!TIP]
|
||
> Owner группы определяется по первому человеку, который создал сессию в этой группе. Owner может назначать co-GM в Web Dashboard; owner и co-GM могут управлять сессиями через кнопки бота и веб-интерфейс.
|
||
|
||
---
|
||
|
||
## 📝 Инструкция для Мастера
|
||
|
||
### Создание расписания игр
|
||
Используйте команду `/newsession` с описанием в следующем формате:
|
||
|
||
```text
|
||
/newsession
|
||
Название: Легенды Берега Мечей (D&D 5e)
|
||
Время: 15.05.2024 19:30
|
||
Время: 22.05.2024 19:00
|
||
Мест: 4
|
||
Ссылка: https://discord.gg/invite-link
|
||
```
|
||
|
||
Строка `Мест:` необязательна. Если она указана, игроки сверх лимита попадут в лист ожидания, а ГМ сможет повысить первого ожидающего через кнопку в Telegram или Web Dashboard.
|
||
|
||
Для регулярной кампании можно не перечислять все даты вручную. Укажите одну строку `Время:`, количество игр и интервал в днях:
|
||
|
||
```text
|
||
/newsession
|
||
Название: Kingmaker
|
||
Время: 30.04.2026 19:30
|
||
Игр: 6
|
||
Интервал: 7
|
||
Мест: 5
|
||
Ссылка: https://discord.gg/invite-link
|
||
```
|
||
|
||
Бот создаст 6 игр с недельным шагом. Вместо `Игр:` также принимается `Сессий:` или `Повторов:`, вместо `Интервал:` — `Шаг:`.
|
||
|
||
Игрок может самостоятельно снять запись кнопкой `🚪 Выйти` в сообщении расписания. Если он был в основном составе и в листе ожидания есть игроки, бот автоматически переводит первого ожидающего в основной состав и обновляет сообщение пачки.
|
||
|
||
### Делегирование управления
|
||
На странице группы Web Dashboard показывает owner и список co-GM. Owner может добавить помощника по Telegram ID, имени и username, а также снять роль co-GM. Назначенный co-GM видит группу в панели управления и может редактировать сессии, управлять batch-операциями, очередью, переносами и удалением игр, но не может назначать других co-GM.
|
||
|
||
### Перенос сессии голосованием
|
||
Owner или co-GM нажимает кнопку `⏰ Перенести` у нужной сессии и отправляет в чат 2-3 варианта нового времени вместе с дедлайном:
|
||
|
||
```text
|
||
25.04.2026 19:30
|
||
26.04.2026 18:00
|
||
Дедлайн: 25.04.2026 12:00
|
||
```
|
||
|
||
Дедлайн должен быть в будущем и раньше первого предложенного времени. Участники выбирают один вариант кнопкой в Telegram, могут изменить голос до дедлайна и видят текущие результаты в сообщении голосования. По дедлайну бот выбирает вариант с наибольшим числом голосов, переносит сессию, сбрасывает RSVP и обновляет batch-сообщение. Если голосов нет или есть ничья, перенос отклоняется, а время сессии остаётся прежним.
|
||
|
||
### Шаблоны и bulk-операции в Web Dashboard
|
||
Вкладка `Шаблоны` в левом меню вынесена отдельно от страницы группы. Owner и co-GM выбирают группу, сохраняют шаблон кампании с названием, ссылкой, количеством игр, интервалом, лимитом мест и режимом уведомлений, а также удаляют устаревшие шаблоны.
|
||
|
||
На странице группы Web Dashboard показывает только применение сохранённых шаблонов и отдельный блок для каждой пачки игр. Owner и co-GM могут:
|
||
- создать новый batch из шаблона, выбрав только первую дату расписания;
|
||
- обновить общий `title` и `link` сразу у всех сессий batch;
|
||
- выбрать режим уведомлений: дублировать важные сообщения игрокам в личку или оставить только групповые уведомления;
|
||
- перенести пачку, задав новую первую дату и фиксированный шаг между играми в днях;
|
||
- клонировать batch на следующую неделю или следующий календарный месяц.
|
||
|
||
После создания из шаблона или клонирования появляется новая пачка с новым Telegram-сообщением и пустым составом игроков. После редактирования или переноса исходное Telegram-сообщение расписания перерисовывается.
|
||
|
||
Если включён режим `В группе и в личку`, бот дополнительно отправляет игрокам персональные сообщения о RSVP за 24 часа, напоминание за 1 час, ссылку перед стартом, отмену и перенос. Если Telegram не позволяет написать игроку в ЛС, бот логирует ошибку и продолжает отправку остальным участникам.
|
||
|
||
### Telegram Mini App Dashboard
|
||
Owner и co-GM могут открыть мобильный dashboard прямо из Telegram через кнопку меню бота или кнопку `Открыть dashboard` после `/start`. Страница `/miniapp` получает `Telegram.WebApp.initData`, отправляет его на серверный endpoint `/auth/telegram-webapp`, проходит HMAC-проверку токеном бота и выдаёт обычную cookie-сессию dashboard.
|
||
|
||
После входа Mini App использует те же страницы, что и Web Dashboard: список групп, карточки сессий, редактирование игры, повышение игрока из листа ожидания, применение шаблонов и bulk-операции batch. Доступ к чужим группам не появляется: все данные по-прежнему фильтруются через `AuthorizedSessionService` по роли owner/co-GM.
|
||
|
||
Если `Telegram.WebApp.initData` недоступен или серверная проверка Mini App не прошла, `/miniapp` показывает диагностичное состояние и fallback-кнопку входа. Fallback больше не зависит от внешнего redirect: Telegram Login Widget вызывает callback на странице, отправляет payload на `/auth/telegram-login`, получает cookie в текущем WebView и сразу возвращает пользователя в dashboard.
|
||
|
||
### Другие команды
|
||
- `/listsessions` — Показать список всех актуальных игр в этой группе.
|
||
- `⏰ Перенести` в сообщении расписания — Запустить голосование по 2-3 вариантам нового времени.
|
||
- `/deletesession` — Удалить сессию.
|
||
- `/exportcalendar` — Получить `.ics` файл с играми.
|
||
- `/help` — Справка по формату.
|
||
|
||
---
|
||
|
||
## 🏗 Разработка и запуск локально (.NET Aspire)
|
||
|
||
Для локальной разработки проще всего использовать .NET Aspire:
|
||
|
||
1. Установите [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0) и workload Aspire.
|
||
2. Откройте решение `GM-Relay.slnx`.
|
||
3. Установите переменные окружения (или user secrets) для `GmRelay.AppHost`.
|
||
4. Запустите проект `GmRelay.AppHost`. Aspire Dashboard запустится автоматически, предоставляя удобный мониторинг БД, бота и веб-интерфейса.
|
||
|
||
> [!NOTE]
|
||
> При использовании **Dapper** в режиме Native AOT, все SQL-запросы используют строго типизированные DTO. Динамические типы (`dynamic`) не поддерживаются.
|
||
|
||
---
|
||
|
||
## 📜 Лицензия
|
||
Проект распространяется под лицензией MIT. Использование в некоммерческих целях приветствуется.
|