# Plan: Telegram Bot для генерации видео

## Цель

Простой Telegram бот, который принимает URL товара с Wildberries и возвращает сгенерированное промо-видео.

---

## Архитектура (максимально простая)

```
┌─────────────────────────────────────────────────────────────┐
│                    LXC Container                             │
│                                                             │
│  ┌─────────────┐    ┌─────────────┐    ┌─────────────┐     │
│  │ Test Bot    │    │ Prod Bot    │    │ Pipeline    │     │
│  │ @test_xxx   │    │ @prod_xxx   │    │ (общий)     │     │
│  └──────┬──────┘    └──────┬──────┘    └──────┬──────┘     │
│         │                  │                  │             │
│         └──────────────────┴──────────────────┘             │
│                            │                                │
│                    ┌───────▼───────┐                       │
│                    │  NFS Storage  │                       │
│                    │  /mnt/video   │                       │
│                    └───────────────┘                       │
└─────────────────────────────────────────────────────────────┘

Внешние API (через proxy-host:1080):
  - LaoZhang.ai → NanoBanana (image-to-image)
  - LaoZhang.ai → Veo3 (video generation)
```

---

## Требования к LXC контейнеру

### Размер
- **RAM**: 4 GB (Playwright + Claude Code)
- **CPU**: 2 cores
- **Disk**: 20 GB (код + venv + playwright browsers)
- **OS**: Ubuntu 22.04 LTS

### Сетевые требования
- Доступ к proxy-host:1080 (SOCKS5 proxy для LaoZhang API)
- Доступ к api.telegram.org
- NFS mount для результатов

### Установка
```bash
# Базовые пакеты
apt update && apt install -y python3.11 python3.11-venv git

# Создание пользователя
useradd -m -s /bin/bash videobot
su - videobot

# Клонирование репозитория
git clone git@git.example.com:user/project.git ~/prod
cp -r ~/prod ~/dev  # Для тестового бота

# Python venv
cd ~/prod && python3.11 -m venv venv
source venv/bin/activate
pip install -r scripts/requirements.txt
pip install python-telegram-bot

# Playwright browsers
playwright install chromium
```

---

## Структура каталогов (NFS)

```
/mnt/video/                              # NFS mount point
├── wb_13374297/                         # По product_id
│   ├── activity.log                     # Лог всех действий
│   ├── 20260116_191907_a3x9/            # Задача 1 (task_id)
│   │   ├── images/
│   │   ├── processed/
│   │   ├── videos/
│   │   └── metadata.json
│   └── 20260117_103022_k7m2/            # Задача 2
│       └── ...
├── wb_7552198/
│   └── ...
└── users/                               # Информация о пользователях
    └── {telegram_user_id}.json
```

### Формат activity.log
```
2026-01-16 19:19:07 | user:123456 | START | 20260116_191907_a3x9
2026-01-16 19:22:13 | user:123456 | DONE  | 20260116_191907_a3x9 | video.mp4
2026-01-16 19:25:00 | user:123456 | VIEW  | 20260116_191907_a3x9
2026-01-16 19:25:30 | user:123456 | RATE  | 20260116_191907_a3x9 | good | "отличное видео"
```

---

## Создание Telegram ботов

### Шаг 1: Создать ботов через @BotFather

```
1. Открыть @BotFather в Telegram
2. /newbot
3. Имя: "SM Video Test Bot" (или "SM Video Bot" для prod)
4. Username: your_test_bot (или your_prod_bot для prod)
5. Сохранить токен: 1234567890:ABCdefGHIjklMNOpqrsTUVwxyz
```

Нужно создать два бота:
- **Test bot**: `@your_test_bot` - для разработки
- **Prod bot**: `@your_prod_bot` - для продакшена

### Шаг 2: Настроить команды бота

В @BotFather:
```
/setcommands
@your_test_bot
start - Начать работу
status - Статус генерации
help - Помощь
```

---

## Идентификатор задачи (task_id)

Формат: `YYYYMMDD_HHMMSS_XXXX` где XXXX - случайные 4 символа

```python
import random
import string
from datetime import datetime

def generate_task_id() -> str:
    """Генерация уникального ID задачи, сортируемого по времени."""
    timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
    suffix = ''.join(random.choices(string.ascii_lowercase + string.digits, k=4))
    return f"{timestamp}_{suffix}"

# Примеры:
# 20260116_191907_a3x9
# 20260116_192015_k7m2
# 20260117_103022_p1qw
```

Преимущества:
- Сортируется по времени (ls покажет в хронологическом порядке)
- Уникальность за счёт случайного суффикса
- Человекочитаемый

---

## Telegram Bot - Минимальный функционал

### Команды
- `/start` - Приветствие, инструкция
- `/generate <url>` или просто URL - Запустить генерацию
- `/status` - Статус текущей генерации (если есть)

### Сценарий использования
```
Пользователь: https://www.wildberries.ru/catalog/13374297/detail.aspx

Бот: ⏳ Начинаю генерацию видео для товара 13374297...
     Это займёт 2-3 минуты.

Бот: ✅ Видео готово!
     [video.mp4]

     Оцените результат:
     [👍 Хорошо] [👎 Плохо] [⚠️ Есть артефакты]

Пользователь: [нажимает 👍 Хорошо]

Бот: Спасибо за оценку! Хотите добавить комментарий?
     (отправьте текст или /skip)

Пользователь: классное видео, но можно чуть короче

Бот: ✅ Записал. Спасибо за обратную связь!
```

---

## Файлы для создания

### 1. `scripts/telegram_bot.py` (~200 строк)

```python
#!/usr/bin/env python3
"""
Простой Telegram бот для генерации видео.
"""
import os
import logging
from pathlib import Path
from datetime import datetime
from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup
from telegram.ext import Application, CommandHandler, MessageHandler, CallbackQueryHandler, filters

from full_pipeline import run_pipeline
from wb_to_video_prompts import extract_product_id

# Конфигурация
BOT_TOKEN = os.environ["TELEGRAM_BOT_TOKEN"]
NFS_BASE = Path(os.environ.get("NFS_BASE", "/mnt/video"))
LAOZHANG_KEY = os.environ["LAOZHANG_KEY"]

def generate_task_id() -> str:
    """Генерация уникального ID задачи, сортируемого по времени."""
    import random
    import string
    timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
    suffix = ''.join(random.choices(string.ascii_lowercase + string.digits, k=4))
    return f"{timestamp}_{suffix}"

def log_activity(product_id: str, user_id: int, action: str, details: str = ""):
    """Записать в activity.log."""
    log_path = NFS_BASE / f"wb_{product_id}" / "activity.log"
    log_path.parent.mkdir(parents=True, exist_ok=True)
    timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    with open(log_path, "a") as f:
        f.write(f"{timestamp} | user:{user_id} | {action} | {details}\n")

async def handle_url(update: Update, context):
    """Обработка URL от пользователя."""
    url = update.message.text.strip()
    user_id = update.effective_user.id

    # Извлекаем product_id
    product_id = extract_product_id(url)
    if not product_id:
        await update.message.reply_text("❌ Не удалось распознать URL товара")
        return

    # Создаём каталог для генерации
    task_id = generate_task_id()
    output_dir = NFS_BASE / f"wb_{product_id}" / task_id

    await update.message.reply_text(
        f"⏳ Начинаю генерацию видео для товара {product_id}...\n"
        f"Это займёт 2-3 минуты."
    )

    log_activity(product_id, user_id, "START", task_id)

    try:
        # Запускаем пайплайн
        result = run_pipeline(
            product_url=url,
            ssh_host="127.0.0.1",  # Dummy, не используется с LaoZhang
            ssh_port=22,
            api_key=os.environ.get("GEMINI_API_KEY", "dummy"),
            output_base=str(NFS_BASE),
            task_id=task_id,  # Новый формат каталогов
            use_laozhang=True,
            laozhang_key=LAOZHANG_KEY
        )

        # Находим видео
        video_path = output_dir / "videos" / "video.mp4"
        if video_path.exists():
            log_activity(product_id, user_id, "DONE", f"{task_id} | video.mp4")

            # Отправляем видео с кнопками оценки
            keyboard = [
                [
                    InlineKeyboardButton("👍 Хорошо", callback_data=f"rate:{product_id}:{task_id}:good"),
                    InlineKeyboardButton("👎 Плохо", callback_data=f"rate:{product_id}:{task_id}:bad"),
                ],
                [
                    InlineKeyboardButton("⚠️ Артефакты", callback_data=f"rate:{product_id}:{task_id}:artifacts"),
                ]
            ]
            reply_markup = InlineKeyboardMarkup(keyboard)

            await update.message.reply_video(
                video=open(video_path, 'rb'),
                caption="✅ Видео готово!\n\nОцените результат:",
                reply_markup=reply_markup
            )
        else:
            log_activity(product_id, user_id, "ERROR", f"{task_id} | no video")
            await update.message.reply_text(
                f"❌ Ошибка: видео не сгенерировано\n"
                f"Ошибки: {result.get('errors', [])}"
            )

    except Exception as e:
        log_activity(product_id, user_id, "ERROR", f"{task_id} | {str(e)}")
        await update.message.reply_text(f"❌ Ошибка генерации: {e}")

async def handle_rating(update: Update, context):
    """Обработка оценки."""
    query = update.callback_query
    await query.answer()

    _, product_id, task_id, rating = query.data.split(":")
    user_id = update.effective_user.id

    log_activity(product_id, user_id, "RATE", f"{task_id} | {rating}")

    # Сохраняем состояние для ожидания комментария
    context.user_data["pending_comment"] = {
        "product_id": product_id,
        "task_id": task_id,
        "rating": rating
    }

    await query.edit_message_caption(
        caption=f"✅ Видео готово!\n\nОценка: {rating}\n\n"
                f"Хотите добавить комментарий? (отправьте текст или /skip)"
    )

async def handle_comment(update: Update, context):
    """Обработка комментария."""
    pending = context.user_data.get("pending_comment")
    if not pending:
        return

    comment = update.message.text.strip()
    user_id = update.effective_user.id

    log_activity(
        pending["product_id"],
        user_id,
        "COMMENT",
        f"{pending['task_id']} | {comment}"
    )

    context.user_data.pop("pending_comment", None)
    await update.message.reply_text("✅ Записал. Спасибо за обратную связь!")

def main():
    app = Application.builder().token(BOT_TOKEN).build()

    # Handlers
    app.add_handler(CommandHandler("start", start_command))
    app.add_handler(CommandHandler("skip", skip_comment))
    app.add_handler(CallbackQueryHandler(handle_rating, pattern="^rate:"))
    app.add_handler(MessageHandler(filters.Regex(r"wildberries\.ru"), handle_url))
    app.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, handle_comment))

    app.run_polling()

if __name__ == "__main__":
    main()
```

### 2. `scripts/bot_config.py` (~30 строк)

```python
"""Конфигурация ботов."""
import os
from pathlib import Path

# Определяем режим по переменной окружения
BOT_MODE = os.environ.get("BOT_MODE", "test")  # test или prod

if BOT_MODE == "test":
    BOT_TOKEN = os.environ["TEST_BOT_TOKEN"]
    CODE_DIR = Path.home() / "dev"
else:
    BOT_TOKEN = os.environ["PROD_BOT_TOKEN"]
    CODE_DIR = Path.home() / "prod"

# Общие настройки
NFS_BASE = Path("/mnt/video")
LAOZHANG_KEY = os.environ["LAOZHANG_KEY"]
PROXY_HOST = "PROXY_HOST"
PROXY_PORT = 1080
```

### 3. `systemd/videobot-test.service`

```ini
[Unit]
Description=Video Generation Telegram Bot (Test)
After=network.target

[Service]
Type=simple
User=videobot
Environment=BOT_MODE=test
Environment=TEST_BOT_TOKEN=xxx
Environment=LAOZHANG_KEY=xxx
WorkingDirectory=/home/videobot/dev/scripts
ExecStart=/home/videobot/dev/venv/bin/python telegram_bot.py
Restart=always
RestartSec=10

[Install]
WantedBy=multi-user.target
```

### 4. `systemd/videobot-prod.service`

```ini
[Unit]
Description=Video Generation Telegram Bot (Prod)
After=network.target

[Service]
Type=simple
User=videobot
Environment=BOT_MODE=prod
Environment=PROD_BOT_TOKEN=xxx
Environment=LAOZHANG_KEY=xxx
WorkingDirectory=/home/videobot/prod/scripts
ExecStart=/home/videobot/prod/venv/bin/python telegram_bot.py
Restart=always
RestartSec=10

[Install]
WantedBy=multi-user.target
```

---

## Задача для DevOps

### Создать LXC контейнер

```yaml
Name: sm-videobot
Template: ubuntu-22.04
Resources:
  RAM: 4GB
  CPU: 2 cores
  Disk: 20GB

Network:
  - Доступ к PROXY_HOST:1080 (SOCKS5 proxy)
  - Доступ к api.telegram.org

Mounts:
  - NFS: /mnt/video → video-storage:/sm-video-gen/

Users:
  - videobot (для запуска ботов)

Packages:
  - python3.11, python3.11-venv
  - git
```

### Настроить NFS

```bash
# На NFS сервере
mkdir -p /exports/sm-video-gen
chown videobot:videobot /exports/sm-video-gen
echo "/exports/sm-video-gen *(rw,sync,no_subtree_check)" >> /etc/exports
exportfs -ra

# В контейнере
echo "nfs-server:/exports/sm-video-gen /mnt/video nfs defaults 0 0" >> /etc/fstab
mount -a
```

---

## Изменения в существующем коде

### `scripts/full_pipeline.py`

Минимальные изменения:
1. Добавить параметр `task_id` для указания идентификатора задачи
2. Изменить формат каталога: `wb_{product_id}/{task_id}/` вместо `{product_id}_{timestamp}/`

```python
import random
import string

def generate_task_id() -> str:
    """Генерация уникального ID задачи, сортируемого по времени."""
    timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
    suffix = ''.join(random.choices(string.ascii_lowercase + string.digits, k=4))
    return f"{timestamp}_{suffix}"

def create_output_directory(output_base: str, product_id: str, task_id: str = None) -> Path:
    """Create output directory structure."""
    if task_id:
        # Новый формат для бота: wb_<id>/<task_id>/
        output_dir = Path(output_base) / f"wb_{product_id}" / task_id
    else:
        # Старый формат для CLI: <id>_<timestamp>/
        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        output_dir = Path(output_base) / f"{product_id}_{timestamp}"

    output_dir.mkdir(parents=True, exist_ok=True)
    (output_dir / "images").mkdir(exist_ok=True)
    (output_dir / "processed").mkdir(exist_ok=True)
    (output_dir / "videos").mkdir(exist_ok=True)

    return output_dir
```

### `run_pipeline()` - новый параметр

```python
def run_pipeline(
    product_url: str,
    ...
    task_id: str = None,  # Если указан - используется новый формат каталогов
    ...
) -> Dict:
```

---

## Тестирование

### 1. Локальный тест пайплайна
```bash
cd ~/dev/scripts
source ../venv/bin/activate
python full_pipeline.py \
    --url "https://www.wildberries.ru/catalog/13374297/detail.aspx" \
    --ssh-host 127.0.0.1 --ssh-port 22 \
    --use-laozhang \
    --output /mnt/video
```

### 2. Тест бота
```bash
BOT_MODE=test TEST_BOT_TOKEN=xxx LAOZHANG_KEY=xxx python telegram_bot.py
# В Telegram: отправить URL товара тестовому боту
```

### 3. Проверка NFS
```bash
ls -la /mnt/video/wb_13374297/
cat /mnt/video/wb_13374297/activity.log
```

---

## Будущие улучшения (НЕ сейчас)

- [ ] Показ существующих генераций по URL
- [ ] Перегенерация с изменёнными промптами
- [ ] Очередь генераций (если много запросов)
- [ ] Админ-панель для просмотра статистики
- [ ] Поддержка Ozon
