20 KiB
MpakoImageAIbot
Telegram-бот для генерации и редактирования изображений через удалённый ComfyUI API.
Что умеет
- Text-to-image: текст → изображение (Z-Image Turbo, 1024×1024).
- Image-to-image: фото + подпись → редактирование (Krea2 edit).
- Reply-to-image: ответ текстом на готовую картинку бота → редактирование этой картинки.
- Веб-история: просмотр запросов с фильтрами по
user_id, статусу и режиму, просмотр изображений. - Улучшение промптов: перевод и переписывание запроса на английский через
TextGenerate+qwen3vl_4bв ComfyUI. - Фильтрация промптов: блокировка CSAM, зоофилии, NSFW с публичными людьми и обходных вариантов для не-админов.
- Оплата: 5 Telegram Stars за обычную очередь, 15 за премиум; админам бесплатно, возврат при ошибке.
- Премиум-очередь: чередуется с обычной (одна премиум, одна обычная); если одна пуста — работает другая.
- Баланс и рефералы: 3 стартовые генерации, +1 ежедневно в 00:00 МСК, +3 за первую оплату друга.
- Управление пользователями: в веб-интерфейсе можно выдавать админку, менять баланс и блокировать.
- Подтверждение 18+: без подтверждения возраста бот не принимает запросы.
- Контекст: следующий текстовый запрос продолжает предыдущую сцену;
/restartначинает новую. - Настройки Krea2:
/settingsпереключает стандартную (Q4) / улучшенную (Q6) модель и 8/10/12 шагов. - Глобальная очередь с одной активной задачей на пользователя.
- Позиция в очереди и прогресс генерации через WebSocket ComfyUI.
- PostgreSQL: запросы, статусы, seed, пути к файлам.
Требования
- Python 3.10+ (проверено на 3.13).
- ComfyUI с доступом по сети, например:
python main.py --listen 0.0.0.0 --port 8188 - Установленные кастомные ноды для текущих workflow:
Krea2ModelLoader,TextEncodeQwenImageEdit,UnetLoaderGGUF,CLIPLoaderGGUF. - Модели, указанные в
workflows/txt2img.jsonиworkflows/img2img.json.
Настройка
Copy-Item .env.example .env
Заполнить .env:
BOT_TOKEN— токен от@BotFather.TELEGRAM_PROXY— прокси для Telegram API (пусто — напрямую).COMFY_BASE_URL— напримерhttp://192.168.31.113:8188.DATABASE_URL— строка подключения PostgreSQL. В Docker хостdb, при локальном запуске —localhost.- Остальные параметры можно оставить по умолчанию.
Прокси для Telegram
Если сервер не имеет прямого доступа к Telegram, укажи прокси V2RayA/HTTP/SOCKS5:
# Docker на Linux, рекомендуется SOCKS5
TELEGRAM_PROXY=socks5h://host.docker.internal:20170
# HTTP-вариант
# TELEGRAM_PROXY=http://host.docker.internal:20171
# Запуск без Docker, на том же хосте
# TELEGRAM_PROXY=socks5h://127.0.0.1:20170
SOCKS5 стабильнее держит long polling; зависимость socksio уже включена в requirements.txt.
Запуск
py -3 -m venv .venv
.\.venv\Scripts\Activate.ps1
pip install -r requirements.txt
python bot.py
Бот работает в приватных чатах. Пользователь отправляет текст или фото с подписью.
Для локального запуска нужен PostgreSQL и DATABASE_URL с хостом localhost.
Docker
docker compose up -d --build
Compose поднимает PostgreSQL, бота и веб. Каталог ./storage монтируется в контейнеры, данные PostgreSQL лежат в ./data/postgres. Для ComfyUI на другой машине используйте её LAN-IP, а не localhost.
PostgreSQL
POSTGRES_DB=mpakoimageaibot
POSTGRES_USER=mpako
POSTGRES_PASSWORD=change-me
DATABASE_URL=postgresql://mpako:change-me@db:5432/mpakoimageaibot
DB_POOL_MIN_SIZE=1
DB_POOL_MAX_SIZE=10
Пароль в DATABASE_URL должен совпадать с POSTGRES_PASSWORD; спецсимволы в пароле нужно URL-кодировать.
Перенос старой SQLite-базы
python scripts/migrate_sqlite_to_postgres.py `
--sqlite data/bot.db `
--database-url "postgresql://mpako:change-me@localhost:5432/mpakoimageaibot"
Перед миграцией останови старый бот, чтобы SQLite закрылась и данные были консистентны. Скрипт создаёт схему, переносит данные и пропускает уже существующие записи.
CI
При push в main или master Gitea Actions сначала запускает тесты на PostgreSQL, затем собирает образ на сервере, где запущен runner, и пушит его в Gitea Registry:
git.mpakosrv.ru/mpakobecex/mpakoimageaibot:latestgit.mpakosrv.ru/mpakobecex/mpakoimageaibot:<sha>- локально:
mpakoimageaibot:latest
Workflow: .gitea/workflows/docker-build.yml. Runner использует label ubuntu-latest и смонтированный Docker socket. Для входа в registry нужны секреты REGISTRY_USER и REGISTRY_TOKEN.
WORKFLOW_PROMPT— workflow улучшения промптов (по умолчаниюworkflows/prompt_enhance.json).PROMPT_ENHANCE_ENABLED— включить улучшение промптов.PROMPT_ENHANCE_TIMEOUT— таймаут генерации промпта в секундах.
Улучшение промптов
Перед генерацией исходный текст проходит через ComfyUI TextGenerate с уже установленной моделью qwen3vl_4b_fp8_scaled.safetensors:
- T2I: перевод и переписывание в английский промпт для генерации.
- I2I: перевод и переписывание инструкции для редактирования.
- Исходный и улучшенный промпты сохраняются в БД и видны в веб-истории.
- При ошибке или таймауте используется исходный промпт.
Отдельную модель скачивать не нужно, используется существующий CLIP-файл ComfyUI.
Фильтрация промптов
Фильтр работает для всех, кроме пользователей из ADMIN_USER_IDS:
MODERATION_ENABLED=true
ADMIN_USER_IDS=7467236616
BLOCKED_PUBLIC_FIGURES=
BLOCKED_PUBLIC_FIGURES добавляет имена к встроенному списку (через запятую).
Что блокируется:
- CSAM: child/kid/minor/teen/young/loli/shota/school/underage + NSFW;
- зоофилия: sex/nude + animal/dog/cat/horse/beast;
- публичные люди + nude/naked/sex/undress/deepfake;
- undress/nude/naked + photo/real/person/woman/man;
- насилие + NSFW;
- экстремизм + инструкции/символика;
- обходные написания (
n_u_d_e,l0li,ch1ldи т.п.).
Для i2i: если установлен OpenCV, проверяется наличие лица/человека. В базовом образе OpenCV нет, поэтому NSFW-промпты в i2i блокируются консервативно — как если бы на фото был человек.
При блокировке пользователь получает «Запрос нарушает правила. Звёзды не списаны.», а запись с user_id, временем, промптом и причиной сохраняется в таблицу blocked_requests.
Управление пользователями
В админ-панели есть раздел «Пользователи» (/admin/users):
- поиск по ID или username;
- установка точного баланса генераций;
- назначение/снятие админа;
- блокировка/разблокировка.
Заблокированный пользователь не может отправлять запросы, получать счета и оплачивать. Админ-флаг из веб-интерфейса действует сразу.
ADMIN_USER_IDS используется только для первичной инициализации админов; после первого запуска админы хранятся в БД и управляются через веб.
Надёжность Telegram
TELEGRAM_RETRY_ATTEMPTS=0
TELEGRAM_RETRY_MAX_DELAY=30
TELEGRAM_HEALTH_INTERVAL=60
TELEGRAM_RETRY_ATTEMPTS=0— повторять операции до успеха.- Backoff растёт до
TELEGRAM_RETRY_MAX_DELAY. - Критичные отправки (результат, счёт, подтверждение) повторяются бесконечно; обновления статуса — ограниченно, чтобы не плодить зависшие задачи.
- Раз в
TELEGRAM_HEALTH_INTERVALвыполняетсяgetMe; при сбое проверка повторяется с backoff, пока Telegram не ответит. - Watchdog проверяет pending updates: если они не обрабатываются 5 минут, polling перезапускается, а при неудаче контейнер перезапускается автоматически.
Подтверждение возраста
Перед использованием бота нужно подтвердить 18+:
- в
/startпоказываются кнопки «Мне 18+» и «Мне меньше 18»; - без подтверждения текстовые и фото-запросы отклоняются;
- отказ «мне меньше 18» сохраняется в БД и блокирует доступ;
- статус 18+ виден в веб-разделе «Пользователи».
Существующие пользователи проходят подтверждение один раз после обновления.
Баланс и рефералы
- Новому пользователю начисляется 3 генерации.
- Каждый день в 00:00 МСК — ещё 1 генерация.
- Обычная генерация может быть оплачена 1 генерацией с баланса или 5 звёздами.
- Премиум-очередь всегда оплачивается 15 звёздами; баланс при этом не расходуется.
/balance— текущий баланс./invite— реферальная ссылка./restart— сбросить контекст и начать новую сцену (алиас/reset)./context— показать текущий контекст: промпт, базовую сцену и последний запрос./settings— выбор модели Krea2 (Q4/Q6) и количества шагов (8/10/12).- При старте бот проверяет
setMyCommandsи включает кнопку меню с командами. - За первую оплату приглашённого друга начисляется +3 генерации, реферер получает уведомление.
Баланс и рефералы хранятся в таблице users. Пропущенные дни начисляются при следующем запуске/полуночи.
Контекст
- Первый текстовый запрос создаёт контекст: базовую сцену и текущий улучшенный промпт.
- Следующий текст обрабатывается вместе с предыдущим промптом: бот сохраняет сцену и применяет только запрошенное изменение.
/restartочищает контекст; следующий запрос начнёт новую сцену./contextпоказывает текущий промпт, базовую сцену и последний запрос.- Новое фото с подписью автоматически начинает новую цепочку.
- Для точного редактирования конкретной картинки ответь на сообщение бота с изображением.
/restartне отменяет уже запущенную генерацию: она завершится со своим снимком контекста.
Контекст хранится в таблице user_contexts и обновляется только после успешной отправки результата.
Оплата
Генерация стоит 5 Telegram Stars в обычной очереди и 15 звёзд в премиум-очереди:
PAYMENTS_ENABLED=true
STARS_PRICE=5
PREMIUM_STARS_PRICE=15
- Перед оплатой бот показывает количество людей в обычной и премиум-очередях.
- Пользователь выбирает очередь кнопкой, после чего получает счёт.
- Премиум- и обычные задачи чередуются: одна премиум, одна обычная; если одна очередь пуста, берётся из другой.
- Админы из
ADMIN_USER_IDSгенерируют бесплатно в обычной очереди. - Не-админу выставляется счёт через Telegram Stars (
XTR). - Фильтрация промпта выполняется до выставления счёта.
- Если поставить задачу в очередь не удалось, звёзды возвращаются.
- При ошибке генерации звёзды также возвращаются.
- Оплата и
telegram_payment_charge_idсохраняются вpending_paymentsиrequests.
Веб-история
После docker compose up -d доступна страница:
http://192.168.31.158:8099
Возможности:
Публичная страница / показывает только историю:
- фильтр по Telegram
user_id(с подсказкой известных пользователей); - в списке показывается
@username, если он известен, иначе Telegram ID; - фильтр по статусу и режиму (
t2i/i2i); - пагинация;
- превью результата в списке (кэшируются в
storage/.thumbnails); - карточка запроса с полным промптом, ошибкой, seed и изображениями.
Админ-панель находится на /admin и защищена HTTP Basic:
/admin— история, включая скрытые запросы;/admin/users— управление пользователями;- скрытие/показ запроса из публичной истории;
- скрытые запросы не видны в публичном списке, их карточки и медиа отдают
404.
WEB_BIND_IP=192.168.31.158
WEB_PAGE_SIZE=50
WEB_ADMIN_USERNAME=admin
WEB_ADMIN_PASSWORD=change-me
Если WEB_ADMIN_PASSWORD пустой, админ-панель отключена. Basic Auth передаёт пароль без шифрования, поэтому используй только LAN или VPN; не публикуй порт 8099 наружу.
После успешной сборки обновление контейнера:
docker compose up -d
Без --build: Compose возьмёт готовый образ mpakoimageaibot:latest.
Workflow
workflows/txt2img.json— Z-Image Turbo:EmptySD3LatentImage → KSampler → VAEDecode → SaveImage.workflows/img2img.json— Krea2 edit:LoadImage → TextEncodeQwenImageEdit → KSamplerAdvanced → VAEDecode → SaveImage.workflows/mapping.json— ID узлов, в которые подставляются prompt, negative, seed, входное изображение и размер латента.workflows/Krea2.jsonиworkflows/ZImage.json— исходные экспорты.- Krea2 edit: VAE подключён к
TextEncodeQwenImageEdit, латент подгоняется под пропорции входного изображения; 8/10/12 шагов,euler/simple. /settingsпереключает модель Krea2 (Q4_K_M/Q6_K) и шаги (8/10/12). Остальные настройки сэмплера и латента одинаковые.- Файлы моделей должны быть в
models/diffusion_models; имена задаютсяKREA2_Q4_MODELиKREA2_Q6_MODEL.
Если меняются workflow, обновите mapping.json. При старте бот проверяет, что все узлы и поля существуют.
Очистка ComfyUI
После успешного скачивания результата бот удаляет запись истории ComfyUI по prompt_id:
POST /history
{"delete": ["<prompt_id>"]}
Это убирает запрос и его ассеты из истории ComfyUI, но не удаляет файлы с диска. Для файлов output/ и temp/ есть скрипт для машины с ComfyUI:
.\scripts\cleanup_comfy_assets.ps1 -ComfyRoot "C:\ComfyUI" -RetentionHours 6
Запускай его по расписанию, например раз в час. Сначала можно проверить без удаления:
.\scripts\cleanup_comfy_assets.ps1 -ComfyRoot "C:\ComfyUI" -RetentionHours 6 -WhatIf
Тесты
pip install -r requirements-dev.txt
pytest
Ограничения текущей версии
- Один воркер и одна активная задача на пользователя.
- Нет
/cancelи удаления задачи из очереди. - Нет автоочистки
storage/иdata/. - Прогресс в процентах зависит от WebSocket; при его недоступности используется polling без процентов.
- При рестарте задача в статусе
runningпомечаетсяfailed.