init: TTS Telegram Bot (edge-tts, Dmitry voice, admin panel)
This commit is contained in:
commit
ed3c1cf0ce
9 changed files with 498 additions and 0 deletions
3
.env
Normal file
3
.env
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
USER_BOT_TOKEN=8567080481:AAHDn6D-JQf2tuLD_S3Md9w0j34REktVsWE
|
||||||
|
ADMIN_BOT_TOKEN=8808539314:AAFydKdcal6HJpgirkFHO2fUdetECTwgxxI
|
||||||
|
TTS_VOICE=ru-RU-DmitryNeural
|
||||||
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
.venv/
|
||||||
|
venv/
|
||||||
149
ARCHITECTURE.md
Normal file
149
ARCHITECTURE.md
Normal file
|
|
@ -0,0 +1,149 @@
|
||||||
|
# Архитектура сервиса T2S Telegram Bot
|
||||||
|
|
||||||
|
## Описание
|
||||||
|
|
||||||
|
Сервис реализует двухбота — один для пользователей (озвучка текста в голос), второй для администратора (логи и копии озвучек). Оба бота запускаются в одном Docker-контейнере в рамках одного asyncio-процесса.
|
||||||
|
|
||||||
|
## Компоненты
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────┐
|
||||||
|
│ Docker Container │
|
||||||
|
│ ┌──────────────────────────────────────────────┐ │
|
||||||
|
│ │ Python 3.12 процесс │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ ┌─────────────────┐ ┌─────────────────┐ │ │
|
||||||
|
│ │ │ User Bot App │ │ Admin Bot App │ │ │
|
||||||
|
│ │ │ (Application) │ │ (Application) │ │ │
|
||||||
|
│ │ │ │ │ │ │ │
|
||||||
|
│ │ │ Token: │ │ Token: │ │ │
|
||||||
|
│ │ │ USER_BOT_TOKEN │ │ ADMIN_BOT_TOKEN│ │ │
|
||||||
|
│ │ └───────┬─────────┘ └────────┬────────┘ │ │
|
||||||
|
│ │ │ │ │ │
|
||||||
|
│ │ │ Admin Bot │ │ │
|
||||||
|
│ │ │ Instance │ │ │
|
||||||
|
│ │ └──────┬───────────────┘ │ │
|
||||||
|
│ │ │ (Bot token=ADMIN_BOT_TOKEN) │ │
|
||||||
|
│ │ ▼ │ │
|
||||||
|
│ │ ┌────────────────────────────┐ │ │
|
||||||
|
│ │ │ edge-tts (TTS) │ │ │
|
||||||
|
│ │ │ Microsoft TTS Service │ │ │
|
||||||
|
│ │ │ via websocket/HTTP │ │ │
|
||||||
|
│ │ │ Voice: ru-RU-DmitryNeural │ │ │
|
||||||
|
│ │ └────────────────────────────┘ │ │
|
||||||
|
│ └──────────────────────────────────────────────┘ │
|
||||||
|
└─────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## Потоки данных
|
||||||
|
|
||||||
|
### Пользовательский поток
|
||||||
|
|
||||||
|
```
|
||||||
|
Пользователь ──текст──▶ User Bot App ──TTS──▶ edge-tts
|
||||||
|
│
|
||||||
|
User Bot App ◀──audio.ogg────────────┘
|
||||||
|
│
|
||||||
|
голосовое сообщение
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Пользователь
|
||||||
|
```
|
||||||
|
|
||||||
|
### Администраторский поток
|
||||||
|
|
||||||
|
```
|
||||||
|
User Bot App ──текст + user info──▶ Admin Bot (прямой Bot-клиент)
|
||||||
|
│
|
||||||
|
голосовое + подпись
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Администратор
|
||||||
|
```
|
||||||
|
|
||||||
|
## Детали реализации
|
||||||
|
|
||||||
|
### `bot.py` — точка входа
|
||||||
|
|
||||||
|
```python
|
||||||
|
async def main():
|
||||||
|
admin_app = Application.builder().token(ADMIN_TOKEN).build()
|
||||||
|
user_app = Application.builder().token(USER_TOKEN).build()
|
||||||
|
# Оба запускаются внутри вложенных async with
|
||||||
|
```
|
||||||
|
|
||||||
|
Ключевые решения:
|
||||||
|
|
||||||
|
1. **Один процесс** — два `Application` внутри вложенных `async with`. Это позволяет разделять память (переменная `admin_chat_id`) и не усложнять инфраструктуру.
|
||||||
|
|
||||||
|
2. **Отдельный Bot-клиент для админа** — `admin_bot = Bot(token=ADMIN_TOKEN)` создаётся как экземпляр `telegram.Bot`, а не `Application`. Это лёгкий HTTP-клиент для отправки сообщений без полного цикла поллинга.
|
||||||
|
|
||||||
|
3. **Временные файлы** — TTS генерируется в `NamedTemporaryFile(suffix='.ogg')`, файл открывается для чтения, отправляется, затем удаляется в `finally`. Никакого накопления мусора.
|
||||||
|
|
||||||
|
### Озвучка (edge-tts)
|
||||||
|
|
||||||
|
- Библиотека `edge-tts` эмулирует запрос к Microsoft Edge TTS API
|
||||||
|
- Не требует API-ключа, бесплатно
|
||||||
|
- Голос кешируется при первом вызове (скачивается в `~/.cache/edge-tts/`)
|
||||||
|
- Формат вывода — Ogg Opus (нативно поддерживается Telegram как голосовое сообщение)
|
||||||
|
- Асинхронный вызов: `await edge_tts.Communicate(text, voice).save(path)`
|
||||||
|
|
||||||
|
### Telegram Bot API
|
||||||
|
|
||||||
|
- Используется `python-telegram-bot` v21+ (синтаксис `Application`, `async with`)
|
||||||
|
- Long-polling (без webhook — не требует публичного HTTPS-адреса)
|
||||||
|
- `send_action(action='record_voice')` показывает индикатор "запись голоса"
|
||||||
|
|
||||||
|
## Обработка ошибок
|
||||||
|
|
||||||
|
| Сценарий | Реакция |
|
||||||
|
|---|---|
|
||||||
|
| edge-tts недоступен | Исключение в `communicate.save()` → ошибка в лог |
|
||||||
|
| Админ не запустил `/start` | `admin_chat_id is None` → лог `warning` + без уведомления |
|
||||||
|
| Ошибка отправки админу | `try/except` → лог `error`, пользователь всё равно получает ответ |
|
||||||
|
| Временный файл не удалился | `try/unlink` в `finally` — silent ignore |
|
||||||
|
|
||||||
|
## Сетевая модель
|
||||||
|
|
||||||
|
```
|
||||||
|
Container ──443/tcp──▶ api.telegram.org (боты)
|
||||||
|
Container ──443/tcp──▶ speech.microsoft.com (edge-tts)
|
||||||
|
```
|
||||||
|
|
||||||
|
Исходящие HTTPS-соединения. Входящих нет — только long-polling к Telegram API.
|
||||||
|
|
||||||
|
## Зависимости
|
||||||
|
|
||||||
|
- `python:3.12-slim` — base image (~130 MB)
|
||||||
|
- `python-telegram-bot >=21, <22` — Telegram API
|
||||||
|
- `edge-tts >=6, <7` — Microsoft TTS
|
||||||
|
- `httpx` — HTTP-клиент PTB
|
||||||
|
- `aiohttp` — HTTP-клиент edge-tts
|
||||||
|
|
||||||
|
## Конфигурация
|
||||||
|
|
||||||
|
Вся конфигурация через переменные окружения (файл `.env`):
|
||||||
|
|
||||||
|
```env
|
||||||
|
USER_BOT_TOKEN=*** # Токен пользовательского бота (Telegram BotFather)
|
||||||
|
ADMIN_BOT_TOKEN=*** # Токен админского бота
|
||||||
|
TTS_VOICE=ru-RU-DmitryNeural # Голос edge-tts (опционально)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Docker
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# docker-compose.yml
|
||||||
|
services:
|
||||||
|
t2s:
|
||||||
|
build: ./app
|
||||||
|
container_name: t2s-telegram-bot
|
||||||
|
restart: always
|
||||||
|
env_file: .env
|
||||||
|
```
|
||||||
|
|
||||||
|
Сборка при заблокированном Docker Hub:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
DOCKER_BUILDKIT=0 docker build --network=host -t t2s-telegram-bot ./app
|
||||||
|
```
|
||||||
101
README.md
Normal file
101
README.md
Normal file
|
|
@ -0,0 +1,101 @@
|
||||||
|
# T2S Telegram Bot — Text-to-Speech
|
||||||
|
|
||||||
|
Сервис озвучивания текста через Telegram. Пользователь отправляет текст — получает голосовое сообщение голосом Дмитрия (Microsoft edge-tts). Администратор получает копию каждой озвучки с информацией об отправителе.
|
||||||
|
|
||||||
|
## Быстрый старт
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Запустить сервис
|
||||||
|
docker compose up -d
|
||||||
|
|
||||||
|
# Посмотреть логи
|
||||||
|
docker compose logs -f
|
||||||
|
|
||||||
|
# Остановить
|
||||||
|
docker compose down
|
||||||
|
```
|
||||||
|
|
||||||
|
### Первичная настройка
|
||||||
|
|
||||||
|
1. Напиши `/start` **админ-боту** (токен `ADMIN_BOT_TOKEN`) — он запомнит твой `chat_id`
|
||||||
|
2. После этого все сообщения пользователей будут дублироваться тебе с аудио + информацией
|
||||||
|
3. Любой пользователь может писать текст **пользовательскому боту** (токен `USER_BOT_TOKEN`) и получать озвучку
|
||||||
|
|
||||||
|
## Архитектура
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
graph LR
|
||||||
|
User[Пользователь Telegram] -->|текст| UBot[Пользовательский бот]
|
||||||
|
UBot -->|TTS запрос| edge[edge-tts<br/>Microsoft TTS]
|
||||||
|
edge -->|audio.ogg| UBot
|
||||||
|
UBot -->|голосовое сообщение| User
|
||||||
|
UBot -->|копия + информация| ABot[Админ-бот]
|
||||||
|
ABot -->|голосовое + информация| Admin[Администратор]
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Один процесс** запускает два экземпляра `python-telegram-bot` Application
|
||||||
|
- **Озвучка** — Microsoft edge-tts, голос `ru-RU-DmitryNeural` (бесплатно, без API-ключа)
|
||||||
|
- **Аудио** — временные `.ogg` файлы, удаляются сразу после отправки
|
||||||
|
|
||||||
|
## Структура проекта
|
||||||
|
|
||||||
|
```
|
||||||
|
.
|
||||||
|
├── .env # Токены ботов + настройки
|
||||||
|
├── .gitignore
|
||||||
|
├── README.md
|
||||||
|
├── docker-compose.yml # Оркестрация
|
||||||
|
└── app/
|
||||||
|
├── Dockerfile
|
||||||
|
├── requirements.txt
|
||||||
|
└── bot.py # Логика ботов
|
||||||
|
```
|
||||||
|
|
||||||
|
## Конфигурация (.env)
|
||||||
|
|
||||||
|
| Переменная | Описание | Пример |
|
||||||
|
|---|---|---|
|
||||||
|
| `USER_BOT_TOKEN` | Токен пользовательского бота | `856708...` |
|
||||||
|
| `ADMIN_BOT_TOKEN` | Токен админского бота | `880853...` |
|
||||||
|
| `TTS_VOICE` | Голос edge-tts (опционально) | `ru-RU-DmitryNeural` |
|
||||||
|
|
||||||
|
### Доступные русские голоса
|
||||||
|
|
||||||
|
| Имя | Пол |
|
||||||
|
|---|---|
|
||||||
|
| `ru-RU-DmitryNeural` | Мужской (по умолчанию) |
|
||||||
|
| `ru-RU-SvetlanaNeural` | Женский |
|
||||||
|
| `ru-RU-DariyaNeural` | Женский |
|
||||||
|
|
||||||
|
## Использование
|
||||||
|
|
||||||
|
Пользователь пишет любое текстовое сообщение пользовательскому боту:
|
||||||
|
|
||||||
|
```
|
||||||
|
User: Привет, как дела?
|
||||||
|
Bot: 🎵 (голосовое сообщение с озвучкой)
|
||||||
|
```
|
||||||
|
|
||||||
|
Администратору приходит:
|
||||||
|
|
||||||
|
```
|
||||||
|
👤 Иван Иванов
|
||||||
|
🆔 123456789
|
||||||
|
📝 Привет, как дела?
|
||||||
|
⏰ 2026-06-03 18:09:00
|
||||||
|
🎵 (то же голосовое сообщение)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Требования
|
||||||
|
|
||||||
|
- Docker + Docker Compose
|
||||||
|
- Доступ к api.telegram.org (боты)
|
||||||
|
- Доступ к Microsoft edge-tts CDN (первый запрос кеширует голос)
|
||||||
|
|
||||||
|
## Особенности сети
|
||||||
|
|
||||||
|
При использовании с роутером GLiNet (DPI, блокировка Docker Hub) используйте для сборки:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
DOCKER_BUILDKIT=0 docker build --network=host -t t2s-telegram-bot ./app
|
||||||
|
```
|
||||||
10
app/Dockerfile
Normal file
10
app/Dockerfile
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
FROM python:3.12-slim
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY requirements.txt .
|
||||||
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
|
COPY bot.py .
|
||||||
|
|
||||||
|
CMD ["python", "bot.py"]
|
||||||
216
app/bot.py
Normal file
216
app/bot.py
Normal file
|
|
@ -0,0 +1,216 @@
|
||||||
|
import asyncio
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import tempfile
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
import edge_tts
|
||||||
|
from mutagen.mp3 import MP3
|
||||||
|
from telegram import Bot, Update
|
||||||
|
from telegram.ext import Application, CommandHandler, MessageHandler, filters
|
||||||
|
|
||||||
|
logging.basicConfig(
|
||||||
|
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
||||||
|
level=logging.INFO
|
||||||
|
)
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
USER_TOKEN = os.environ['USER_BOT_TOKEN']
|
||||||
|
ADMIN_TOKEN = os.environ['ADMIN_BOT_TOKEN']
|
||||||
|
TTS_VOICE = os.environ.get('TTS_VOICE', 'ru-RU-DmitryNeural')
|
||||||
|
DATA_DIR = os.environ.get('DATA_DIR', '/app/data')
|
||||||
|
os.makedirs(DATA_DIR, exist_ok=True)
|
||||||
|
|
||||||
|
ADMIN_CHAT_ID_FILE = os.path.join(DATA_DIR, 'admin_chat_id.txt')
|
||||||
|
STATS_FILE = os.path.join(DATA_DIR, 'stats.json')
|
||||||
|
|
||||||
|
|
||||||
|
def load_admin_chat_id():
|
||||||
|
try:
|
||||||
|
with open(ADMIN_CHAT_ID_FILE) as f:
|
||||||
|
return int(f.read().strip())
|
||||||
|
except (FileNotFoundError, ValueError):
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def save_admin_chat_id(chat_id):
|
||||||
|
with open(ADMIN_CHAT_ID_FILE, 'w') as f:
|
||||||
|
f.write(str(chat_id))
|
||||||
|
|
||||||
|
|
||||||
|
def load_stats():
|
||||||
|
try:
|
||||||
|
with open(STATS_FILE) as f:
|
||||||
|
return json.load(f)
|
||||||
|
except (FileNotFoundError, json.JSONDecodeError):
|
||||||
|
return {"users": [], "total_messages": 0}
|
||||||
|
|
||||||
|
|
||||||
|
def save_stats(stats):
|
||||||
|
with open(STATS_FILE, 'w') as f:
|
||||||
|
json.dump(stats, f)
|
||||||
|
|
||||||
|
|
||||||
|
def get_ogg_duration(path: str) -> int:
|
||||||
|
"""Read audio duration in seconds (MP3 from edge-tts v7)."""
|
||||||
|
try:
|
||||||
|
audio = MP3(path)
|
||||||
|
return int(audio.info.length)
|
||||||
|
except Exception:
|
||||||
|
logger.warning(f"Could not read duration from {path}")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
admin_chat_id = load_admin_chat_id()
|
||||||
|
|
||||||
|
# Separate Bot instance for sending admin notifications
|
||||||
|
admin_bot = Bot(token=ADMIN_TOKEN)
|
||||||
|
|
||||||
|
# ────────────────────────── ADMIN BOT ──────────────────────────
|
||||||
|
|
||||||
|
async def admin_start(update: Update, context):
|
||||||
|
"""Capture admin chat ID on /start and persist."""
|
||||||
|
global admin_chat_id
|
||||||
|
admin_chat_id = update.effective_chat.id
|
||||||
|
save_admin_chat_id(admin_chat_id)
|
||||||
|
await update.message.reply_text(
|
||||||
|
f'✅ Админ-панель готова.\n'
|
||||||
|
f'Твой chat_id: <code>{admin_chat_id}</code>\n\n'
|
||||||
|
f'Сюда будут приходить все озвучки пользователей.\n'
|
||||||
|
f'Используй /stat для статистики.',
|
||||||
|
parse_mode='HTML'
|
||||||
|
)
|
||||||
|
logger.info(f"Admin registered: chat_id={admin_chat_id}")
|
||||||
|
|
||||||
|
|
||||||
|
async def admin_stat(update: Update, context):
|
||||||
|
"""Show bot usage statistics."""
|
||||||
|
s = load_stats()
|
||||||
|
unique = len(s['users'])
|
||||||
|
total = s['total_messages']
|
||||||
|
await update.message.reply_text(
|
||||||
|
f'📊 <b>Статистика бота</b>\n\n'
|
||||||
|
f'👥 Уникальных пользователей: <b>{unique}</b>\n'
|
||||||
|
f'🎤 Озвучено сообщений: <b>{total}</b>',
|
||||||
|
parse_mode='HTML'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# ────────────────────────── USER BOT ───────────────────────────
|
||||||
|
|
||||||
|
async def user_start(update: Update, context):
|
||||||
|
await update.message.reply_text(
|
||||||
|
'👋 Привет! Отправь мне текст, и я озвучу его '
|
||||||
|
'голосом <b>Дмитрия</b> (ru-RU).\n\n'
|
||||||
|
'Просто пришли любое текстовое сообщение.',
|
||||||
|
parse_mode='HTML'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def handle_user_message(update: Update, context):
|
||||||
|
"""Receive text → TTS → reply voice → forward to admin."""
|
||||||
|
global admin_chat_id
|
||||||
|
user = update.effective_user
|
||||||
|
text = update.message.text
|
||||||
|
|
||||||
|
logger.info(f"User {user.full_name} (id={user.id}): {text[:80]}")
|
||||||
|
|
||||||
|
# Track stats
|
||||||
|
stats = load_stats()
|
||||||
|
if user.id not in stats['users']:
|
||||||
|
stats['users'].append(user.id)
|
||||||
|
stats['total_messages'] = stats.get('total_messages', 0) + 1
|
||||||
|
save_stats(stats)
|
||||||
|
|
||||||
|
# Tell Telegram we're recording audio
|
||||||
|
await update.message.chat.send_action(action='record_voice')
|
||||||
|
|
||||||
|
# Generate TTS with edge-tts (Microsoft, free, offline-capable)
|
||||||
|
tmp = tempfile.NamedTemporaryFile(suffix='.ogg', delete=False)
|
||||||
|
tmp_path = tmp.name
|
||||||
|
tmp.close()
|
||||||
|
|
||||||
|
try:
|
||||||
|
communicate = edge_tts.Communicate(text, voice=TTS_VOICE)
|
||||||
|
await communicate.save(tmp_path)
|
||||||
|
|
||||||
|
# ── 1. Send voice back to user ──
|
||||||
|
duration = get_ogg_duration(tmp_path)
|
||||||
|
with open(tmp_path, 'rb') as f:
|
||||||
|
await update.message.reply_voice(voice=f, duration=duration)
|
||||||
|
|
||||||
|
# ── 2. Forward to admin ──
|
||||||
|
if admin_chat_id:
|
||||||
|
caption = (
|
||||||
|
f'👤 <b>{user.full_name}</b>\n'
|
||||||
|
f'🆔 <code>{user.id}</code>\n'
|
||||||
|
f'📝 {text}\n'
|
||||||
|
f'⏰ {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}\n'
|
||||||
|
f'🔊 Длительность: {duration // 60}:{duration % 60:02d}'
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
with open(tmp_path, 'rb') as f:
|
||||||
|
await admin_bot.send_voice(
|
||||||
|
chat_id=admin_chat_id,
|
||||||
|
voice=f,
|
||||||
|
duration=duration,
|
||||||
|
caption=caption,
|
||||||
|
parse_mode='HTML',
|
||||||
|
)
|
||||||
|
logger.info(f"Admin notified: {user.full_name} → {text[:40]}")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to notify admin: {e}")
|
||||||
|
else:
|
||||||
|
logger.warning("Admin not registered — run /start in admin bot")
|
||||||
|
|
||||||
|
finally:
|
||||||
|
try:
|
||||||
|
os.unlink(tmp_path)
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
# ────────────────────────── SETUP ──────────────────────────────
|
||||||
|
|
||||||
|
def build_admin_app() -> Application:
|
||||||
|
app = Application.builder().token(ADMIN_TOKEN).build()
|
||||||
|
app.add_handler(CommandHandler('start', admin_start))
|
||||||
|
app.add_handler(CommandHandler('stat', admin_stat))
|
||||||
|
return app
|
||||||
|
|
||||||
|
|
||||||
|
def build_user_app() -> Application:
|
||||||
|
app = Application.builder().token(USER_TOKEN).build()
|
||||||
|
app.add_handler(CommandHandler('start', user_start))
|
||||||
|
app.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, handle_user_message))
|
||||||
|
return app
|
||||||
|
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
admin_app = build_admin_app()
|
||||||
|
user_app = build_user_app()
|
||||||
|
|
||||||
|
# Run both bots concurrently
|
||||||
|
async with admin_app:
|
||||||
|
await admin_app.initialize()
|
||||||
|
await admin_app.start()
|
||||||
|
await admin_app.updater.start_polling()
|
||||||
|
logger.info("Admin bot polling started")
|
||||||
|
|
||||||
|
async with user_app:
|
||||||
|
await user_app.initialize()
|
||||||
|
await user_app.start()
|
||||||
|
await user_app.updater.start_polling()
|
||||||
|
logger.info("User bot polling started")
|
||||||
|
|
||||||
|
# Keep alive
|
||||||
|
while True:
|
||||||
|
await asyncio.sleep(3600)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
try:
|
||||||
|
asyncio.run(main())
|
||||||
|
except (KeyboardInterrupt, SystemExit):
|
||||||
|
logger.info("Shutting down gracefully...")
|
||||||
3
app/requirements.txt
Normal file
3
app/requirements.txt
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
python-telegram-bot>=21,<22
|
||||||
|
edge-tts>=7,<8
|
||||||
|
mutagen>=1.47,<2
|
||||||
1
data/stats.json
Normal file
1
data/stats.json
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
{"users": [20814732, 639276829, 1594297548], "total_messages": 8}
|
||||||
11
docker-compose.yml
Normal file
11
docker-compose.yml
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
version: "3.9"
|
||||||
|
|
||||||
|
services:
|
||||||
|
t2s:
|
||||||
|
build: ./app
|
||||||
|
container_name: t2s-telegram-bot
|
||||||
|
restart: always
|
||||||
|
network_mode: "host"
|
||||||
|
env_file: .env
|
||||||
|
volumes:
|
||||||
|
- ./data:/app/data
|
||||||
Loading…
Add table
Add a link
Reference in a new issue