Final release: Multi-session comment bot with filtering
Features: - Multi-account support (session files) - AI comments via Ollama - Telegram bot moderation - Filter by sessions and groups - Docker support - Auto-join groups - Log notifications - DB migration script Bug fixes: - Fixed comment_to for proper post targeting - Fixed entity lookup with multiple ID formats - Fixed callback handlers for filtering - Added auto-join before entity lookup
This commit is contained in:
5
.gitignore
vendored
5
.gitignore
vendored
@@ -55,6 +55,8 @@ logs/
|
|||||||
*.swp
|
*.swp
|
||||||
*.swo
|
*.swo
|
||||||
*~
|
*~
|
||||||
|
|
||||||
|
# macOS
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
||||||
# Testing
|
# Testing
|
||||||
@@ -69,3 +71,6 @@ docs/_build/
|
|||||||
tmp/
|
tmp/
|
||||||
temp/
|
temp/
|
||||||
*.tmp
|
*.tmp
|
||||||
|
|
||||||
|
# Migration scripts (optional)
|
||||||
|
migrate_db.py
|
||||||
|
|||||||
110
DEPLOYMENT_CHECKLIST.md
Normal file
110
DEPLOYMENT_CHECKLIST.md
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
# 🚀 Чеклист для отправки в Gitea
|
||||||
|
|
||||||
|
## ✅ Проверено перед отправкой:
|
||||||
|
|
||||||
|
### Файлы проекта:
|
||||||
|
- [x] `README.md` — обновлённая документация
|
||||||
|
- [x] `.env.example` — пример конфигурации
|
||||||
|
- [x] `.gitignore` — игнорирование секретов
|
||||||
|
- [x] `docker-compose.yml` — Docker конфигурация
|
||||||
|
- [x] `Dockerfile` — образ контейнера
|
||||||
|
- [x] `requirements.txt` — Python зависимости
|
||||||
|
- [x] `auth.py` — автономная авторизация
|
||||||
|
- [x] `migrate_db.py` — скрипт миграции БД
|
||||||
|
- [x] `prompt.txt` — шаблон для LLM
|
||||||
|
|
||||||
|
### Исходный код:
|
||||||
|
- [x] `bot/controller.py` — бот для модерации
|
||||||
|
- [x] `bot/worker.py` — воркер для отправки
|
||||||
|
- [x] `bot/db.py` — база данных
|
||||||
|
- [x] `bot/config.py` — конфигурация
|
||||||
|
- [x] `bot/keyboard.py` — inline-клавиатуры
|
||||||
|
- [x] `bot/ollama.py` — Ollama API
|
||||||
|
- [x] `bot/session_manager.py` — управление сессиями
|
||||||
|
- [x] `bot/__init__.py` — инициализация пакета
|
||||||
|
|
||||||
|
### НЕ попадает в репозиторий:
|
||||||
|
- [x] `.env` — секреты
|
||||||
|
- [x] `sessions/*.session` — сессии
|
||||||
|
- [x] `data/comments.db` — база данных
|
||||||
|
- [x] `logs/` — логи
|
||||||
|
- [x] `__pycache__/` — кэш Python
|
||||||
|
- [x] `.DS_Store` — системные файлы
|
||||||
|
|
||||||
|
## 📋 Функционал:
|
||||||
|
|
||||||
|
### Основные функции:
|
||||||
|
- [x] Мультиаккаунт (несколько сессий)
|
||||||
|
- [x] AI генерация комментариев (Ollama)
|
||||||
|
- [x] Модерация (approve/reject/regenerate/edit)
|
||||||
|
- [x] Фильтрация по сессиям
|
||||||
|
- [x] Фильтрация по группам
|
||||||
|
- [x] Авто-вступление в группы
|
||||||
|
- [x] Уведомления в лог-группу
|
||||||
|
- [x] Удаление групп (выход + сброс)
|
||||||
|
- [x] Перегенерация при повторном добавлении
|
||||||
|
|
||||||
|
### Docker:
|
||||||
|
- [x] Controller сервис
|
||||||
|
- [x] Worker сервис
|
||||||
|
- [x] Тома для данных
|
||||||
|
- [x] Сетевая конфигурация
|
||||||
|
|
||||||
|
### Документация:
|
||||||
|
- [x] README.md (полная)
|
||||||
|
- [x] .env.example (с комментариями)
|
||||||
|
- [x] DOCKER.md (Docker инструкция)
|
||||||
|
- [x] QUICKSTART.md (быстрый старт)
|
||||||
|
- [x] GIT_INSTRUCTIONS.md (инструкция по Git)
|
||||||
|
|
||||||
|
## 🚀 Команды для отправки:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /Users/bilal/Documents/code/batch-bot
|
||||||
|
|
||||||
|
# 1. Добавить изменения
|
||||||
|
git add .
|
||||||
|
|
||||||
|
# 2. Проверить что будет закоммичено
|
||||||
|
git status
|
||||||
|
|
||||||
|
# 3. Сделать коммит
|
||||||
|
git commit -m "Final release: Multi-session comment bot with filtering
|
||||||
|
|
||||||
|
Features:
|
||||||
|
- Multi-account support (session files)
|
||||||
|
- AI comments via Ollama
|
||||||
|
- Telegram bot moderation
|
||||||
|
- Filter by sessions and groups
|
||||||
|
- Docker support
|
||||||
|
- Auto-join groups
|
||||||
|
- Log notifications
|
||||||
|
- DB migration script
|
||||||
|
|
||||||
|
Bug fixes:
|
||||||
|
- Fixed comment_to for proper post targeting
|
||||||
|
- Fixed entity lookup with multiple ID formats
|
||||||
|
- Fixed callback handlers for filtering
|
||||||
|
- Added auto-join before entity lookup"
|
||||||
|
|
||||||
|
# 4. Отправить в Gitea
|
||||||
|
git push origin main
|
||||||
|
```
|
||||||
|
|
||||||
|
## ✅ После отправки:
|
||||||
|
|
||||||
|
1. Проверьте репозиторий: https://git.core.com.ru/bilal/batch-bot
|
||||||
|
2. Убедитесь что все файлы на месте
|
||||||
|
3. Проверьте что .env и сессии НЕ в репозитории
|
||||||
|
|
||||||
|
## 📊 Статистика проекта:
|
||||||
|
|
||||||
|
- Файлов: ~15
|
||||||
|
- Строк кода: ~2500
|
||||||
|
- Функций: ~50
|
||||||
|
- Callback обработчиков: ~20
|
||||||
|
- Таблиц БД: 4
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Готово к отправке!** 🎉
|
||||||
36
README.md
36
README.md
@@ -1,6 +1,6 @@
|
|||||||
# Batch Bot - Telegram Comment Bot
|
# Batch Bot - Telegram Comment Bot
|
||||||
|
|
||||||
Автоматический бот для генерации и публикации комментариев в Telegram от имени нескольких пользователей.
|
Автоматический бот для генерации и публикации комментариев в Telegram от имени нескольких пользователей с использованием локальной LLM (Ollama).
|
||||||
|
|
||||||
## 🏗 Архитектура
|
## 🏗 Архитектура
|
||||||
|
|
||||||
@@ -30,8 +30,9 @@
|
|||||||
- ✅ **Модерация** — inline-кнопки для одобрения/отклонения
|
- ✅ **Модерация** — inline-кнопки для одобрения/отклонения
|
||||||
- ✅ **Редактирование** — возможность изменить текст перед отправкой
|
- ✅ **Редактирование** — возможность изменить текст перед отправкой
|
||||||
- ✅ **Статистика** — учёт сгенерированных/отправленных комментариев
|
- ✅ **Статистика** — учёт сгенерированных/отправленных комментариев
|
||||||
- ✅ **Безопасность** — разделение контроллера и воркеров
|
- ✅ **Фильтрация** — просмотр комментариев по сессиям и группам
|
||||||
- ✅ **Docker** — полная контейнеризация
|
- ✅ **Docker** — полная контейнеризация
|
||||||
|
- ✅ **Уведомления** — уведомления о новых постах в лог-группу
|
||||||
|
|
||||||
## 🚀 Быстрый старт
|
## 🚀 Быстрый старт
|
||||||
|
|
||||||
@@ -66,13 +67,19 @@ pip install -r requirements.txt
|
|||||||
python auth.py
|
python auth.py
|
||||||
```
|
```
|
||||||
|
|
||||||
Введите номер телефона и код из Telegram.
|
Введите API credentials, номер телефона и код из Telegram.
|
||||||
|
|
||||||
**Для нескольких аккаунтов:**
|
**Для нескольких аккаунтов:**
|
||||||
- Запустите `python auth.py` несколько раз
|
- Запустите `python auth.py` несколько раз
|
||||||
- Или скопируйте `.session` файлы в `sessions/`
|
- Или скопируйте `.session` файлы в `sessions/`
|
||||||
|
|
||||||
### 3. Запуск Docker
|
### 3. Миграция БД (если обновляетесь)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python migrate_db.py
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Запуск Docker
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Сборка и запуск
|
# Сборка и запуск
|
||||||
@@ -98,12 +105,24 @@ docker-compose down
|
|||||||
|---------|----------|
|
|---------|----------|
|
||||||
| `/start` | Главное меню |
|
| `/start` | Главное меню |
|
||||||
| `/stats` | Статистика |
|
| `/stats` | Статистика |
|
||||||
| `/pending` | Ожидающие комментарии |
|
| `/pending` | Ожидающие комментарии (по группам) |
|
||||||
| `/sessions` | Сессии |
|
| `/sessions` | Сессии (по сессиям) |
|
||||||
| `/groups` | Управление группами |
|
| `/groups` | Управление группами |
|
||||||
| `/add_group ID` | Добавить группу |
|
| `/add_group ID` | Добавить группу |
|
||||||
| `/help` | Справка |
|
| `/help` | Справка |
|
||||||
|
|
||||||
|
### Фильтрация комментариев
|
||||||
|
|
||||||
|
**По сессиям:**
|
||||||
|
1. Нажмите "👥 Сессии"
|
||||||
|
2. Выберите сессию
|
||||||
|
3. Просмотрите комментарии этой сессии
|
||||||
|
|
||||||
|
**По группам:**
|
||||||
|
1. Нажмите "📝 Ожидающие"
|
||||||
|
2. Выберите группу
|
||||||
|
3. Просмотрите комментарии этой группы
|
||||||
|
|
||||||
### Добавление группы
|
### Добавление группы
|
||||||
|
|
||||||
**Через команду:**
|
**Через команду:**
|
||||||
@@ -158,6 +177,7 @@ batch-bot/
|
|||||||
├── Dockerfile # Образ для controller/worker
|
├── Dockerfile # Образ для controller/worker
|
||||||
├── requirements.txt # Python зависимости
|
├── requirements.txt # Python зависимости
|
||||||
├── auth.py # Скрипт авторизации
|
├── auth.py # Скрипт авторизации
|
||||||
|
├── migrate_db.py # Скрипт миграции БД
|
||||||
├── prompt.txt # Шаблон для LLM
|
├── prompt.txt # Шаблон для LLM
|
||||||
├── bot/
|
├── bot/
|
||||||
│ ├── config.py # Конфигурация
|
│ ├── config.py # Конфигурация
|
||||||
@@ -257,6 +277,10 @@ OLLAMA_URL=http://host.docker.internal:11434
|
|||||||
- Проверьте что аккаунт вступил в группу комментариев
|
- Проверьте что аккаунт вступил в группу комментариев
|
||||||
- Worker автоматически вступает при отправке
|
- Worker автоматически вступает при отправке
|
||||||
|
|
||||||
|
**"Could not find the input entity":**
|
||||||
|
- Аккаунт должен быть участником группы
|
||||||
|
- Worker автоматически вступает при сканировании
|
||||||
|
|
||||||
## 📝 Лицензия
|
## 📝 Лицензия
|
||||||
|
|
||||||
MIT
|
MIT
|
||||||
|
|||||||
130
auth.py
130
auth.py
@@ -1,14 +1,13 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""
|
"""
|
||||||
Скрипт для создания сессии Telegram
|
Telegram Session Creator
|
||||||
Запустите этот скрипт для авторизации и создания файла сессии
|
Создаёт сессию для бота через интерактивную авторизацию
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from dotenv import load_dotenv
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
from telethon import TelegramClient
|
from telethon import TelegramClient
|
||||||
from telethon.sessions import StringSession
|
from telethon.sessions import StringSession
|
||||||
@@ -27,80 +26,114 @@ logger.add(
|
|||||||
level="DEBUG"
|
level="DEBUG"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Загрузка переменных окружения
|
# Пути
|
||||||
load_dotenv()
|
BASE_DIR = Path(__file__).parent
|
||||||
|
SESSIONS_DIR = BASE_DIR / "sessions"
|
||||||
# Конфигурация
|
SESSIONS_DIR.mkdir(exist_ok=True)
|
||||||
API_ID = int(os.getenv("TELEGRAM_API_ID", "0"))
|
|
||||||
API_HASH = os.getenv("TELEGRAM_API_HASH", "")
|
|
||||||
PHONE = os.getenv("TELEGRAM_PHONE", "")
|
|
||||||
|
|
||||||
SESSIONS_DIR = Path("sessions")
|
|
||||||
|
|
||||||
|
|
||||||
async def main():
|
async def main():
|
||||||
"""Создание сессии"""
|
"""Создание сессии"""
|
||||||
try:
|
try:
|
||||||
# Проверка конфигурации
|
logger.info("=" * 50)
|
||||||
if not all([API_ID, API_HASH]):
|
logger.info("🔐 Telegram Session Creator")
|
||||||
logger.error("❌ TELEGRAM_API_ID и TELEGRAM_API_HASH должны быть заданы в .env")
|
logger.info("=" * 50)
|
||||||
|
logger.info("")
|
||||||
|
|
||||||
|
# Запрос API credentials
|
||||||
|
logger.info("📋 Для работы нужны API credentials")
|
||||||
|
logger.info("Получить можно здесь: https://my.telegram.org/apps")
|
||||||
|
logger.info("")
|
||||||
|
|
||||||
|
api_id = input("Введите API ID: ").strip()
|
||||||
|
api_hash = input("Введите API Hash: ").strip()
|
||||||
|
|
||||||
|
if not api_id or not api_hash:
|
||||||
|
logger.error("❌ API ID и API Hash обязательны")
|
||||||
return
|
return
|
||||||
|
|
||||||
# Создаём директорию для сессий
|
logger.info("")
|
||||||
SESSIONS_DIR.mkdir(exist_ok=True)
|
logger.info("📱 Введите номер телефона")
|
||||||
|
logger.info("Формат: +79991234567 (с кодом страны)")
|
||||||
|
logger.info("")
|
||||||
|
|
||||||
logger.info("🔐 Telegram Session Creator")
|
phone = input("Номер телефона: ").strip()
|
||||||
logger.info("=" * 40)
|
|
||||||
logger.info(f"API ID: {API_ID}")
|
|
||||||
logger.info(f"API Hash: {'*' * len(API_HASH) if API_HASH else 'Не задан'}")
|
|
||||||
logger.info(f"Phone: {PHONE or 'Будет запрошен'}")
|
|
||||||
logger.info("=" * 40)
|
|
||||||
|
|
||||||
# Запрос телефона если не задан
|
|
||||||
phone = PHONE
|
|
||||||
if not phone:
|
if not phone:
|
||||||
phone = input("📱 Введите номер телефона (с +7): ").strip()
|
logger.error("❌ Номер телефона обязателен")
|
||||||
|
return
|
||||||
|
|
||||||
# Создаём клиента
|
# Создаём клиента
|
||||||
client = TelegramClient(
|
client = TelegramClient(
|
||||||
StringSession(),
|
StringSession(),
|
||||||
API_ID,
|
int(api_id),
|
||||||
API_HASH,
|
api_hash,
|
||||||
device_model="comment_bot",
|
device_model="Desktop (X64)",
|
||||||
system_version="Linux",
|
system_version="Windows 11",
|
||||||
app_version="1.0",
|
app_version="3.2.2",
|
||||||
lang_code="ru"
|
lang_code="en",
|
||||||
|
system_lang_code="en-US"
|
||||||
)
|
)
|
||||||
|
|
||||||
logger.info("Подключение к Telegram...")
|
logger.info("")
|
||||||
|
logger.info("⏳ Подключение к Telegram...")
|
||||||
await client.connect()
|
await client.connect()
|
||||||
|
|
||||||
if not await client.is_user_authorized():
|
if not await client.is_user_authorized():
|
||||||
logger.info("Отправка кода подтверждения...")
|
logger.info("📲 Отправка кода подтверждения...")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await client.send_code_request(phone)
|
await client.send_code_request(phone)
|
||||||
|
logger.info(f"✅ Код отправлен на {phone}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Ошибка отправки кода: {e}")
|
logger.error(f"❌ Ошибка отправки кода: {e}")
|
||||||
|
await client.disconnect()
|
||||||
return
|
return
|
||||||
|
|
||||||
# Ввод кода
|
# Ввод кода
|
||||||
code = input("📲 Введите код из Telegram: ").strip()
|
logger.info("")
|
||||||
|
code = input("Введите код из Telegram: ").strip()
|
||||||
|
|
||||||
|
if not code:
|
||||||
|
logger.error("❌ Код обязателен")
|
||||||
|
await client.disconnect()
|
||||||
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await client.sign_in(phone, code)
|
await client.sign_in(phone, code)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
if "PASSWORD" in str(e):
|
error_msg = str(e)
|
||||||
|
|
||||||
|
if "SESSION_PASSWORD_NEEDED" in error_msg or "2FA" in error_msg.upper():
|
||||||
# Запрос 2FA пароля
|
# Запрос 2FA пароля
|
||||||
password = input("🔒 Введите 2FA пароль: ").strip()
|
logger.info("")
|
||||||
|
logger.info("🔒 Требуется 2FA пароль")
|
||||||
|
password = input("Введите пароль: ").strip()
|
||||||
|
|
||||||
|
try:
|
||||||
await client.sign_in(password=password)
|
await client.sign_in(password=password)
|
||||||
|
except Exception as e2:
|
||||||
|
logger.error(f"❌ Ошибка 2FA: {e2}")
|
||||||
|
await client.disconnect()
|
||||||
|
return
|
||||||
|
elif "PHONE_CODE_INVALID" in error_msg:
|
||||||
|
logger.error("❌ Неверный код")
|
||||||
|
await client.disconnect()
|
||||||
|
return
|
||||||
else:
|
else:
|
||||||
logger.error(f"Ошибка входа: {e}")
|
logger.error(f"❌ Ошибка входа: {e}")
|
||||||
|
await client.disconnect()
|
||||||
return
|
return
|
||||||
|
|
||||||
# Получаем информацию о пользователе
|
# Получаем информацию о пользователе
|
||||||
me = await client.get_me()
|
me = await client.get_me()
|
||||||
logger.info(f"✅ Успешная авторизация: {me.first_name} @{me.username or 'no_username'}")
|
logger.info("")
|
||||||
|
logger.info("✅ Успешная авторизация!")
|
||||||
|
logger.info(f"👤 {me.first_name} {me.last_name or ''}")
|
||||||
|
logger.info(f"📱 @{me.username or 'нет username'}")
|
||||||
|
logger.info(f"🆔 ID: {me.id}")
|
||||||
|
logger.info(f"📞 {me.phone}")
|
||||||
|
logger.info("")
|
||||||
|
|
||||||
# Сохраняем сессию
|
# Сохраняем сессию
|
||||||
session_string = client.session.save()
|
session_string = client.session.save()
|
||||||
@@ -110,19 +143,24 @@ async def main():
|
|||||||
with open(session_path, 'w', encoding='utf-8') as f:
|
with open(session_path, 'w', encoding='utf-8') as f:
|
||||||
f.write(session_string)
|
f.write(session_string)
|
||||||
|
|
||||||
logger.info(f"💾 Сессия сохранена: {session_path}")
|
logger.info("💾 Сессия сохранена:")
|
||||||
|
logger.info(f"📁 {session_path}")
|
||||||
logger.info("")
|
logger.info("")
|
||||||
logger.info("Следующие шаги:")
|
logger.info("📋 Следующие шаги:")
|
||||||
logger.info("1. Скопируйте файл сессии в папку sessions/")
|
logger.info("1. Файл сессии уже в папке sessions/")
|
||||||
logger.info("2. Запустите бота: python bot/controller.py")
|
logger.info("2. Перезапустите worker: docker-compose restart worker")
|
||||||
logger.info("3. Запустите воркера: python bot/worker.py")
|
logger.info("3. Проверьте логи: docker-compose logs -f worker")
|
||||||
|
logger.info("")
|
||||||
|
logger.info("=" * 50)
|
||||||
|
|
||||||
await client.disconnect()
|
await client.disconnect()
|
||||||
|
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
logger.info("\n❌ Отменено пользователем")
|
logger.info("")
|
||||||
|
logger.info("❌ Отменено пользователем")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"❌ Ошибка: {e}")
|
logger.error(f"❌ Ошибка: {e}")
|
||||||
|
logger.exception("Полный стек:")
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,8 @@ from bot.db import (
|
|||||||
increment_regeneration, get_all_sessions, get_active_sessions,
|
increment_regeneration, get_all_sessions, get_active_sessions,
|
||||||
toggle_session, delete_session, get_summary_stats, get_stats,
|
toggle_session, delete_session, get_summary_stats, get_stats,
|
||||||
update_stats, add_target_group, get_target_groups, get_all_target_groups,
|
update_stats, add_target_group, get_target_groups, get_all_target_groups,
|
||||||
remove_target_group, toggle_target_group, is_target_group
|
remove_target_group, toggle_target_group, is_target_group,
|
||||||
|
get_pending_comments_by_session, get_pending_comments_by_group
|
||||||
)
|
)
|
||||||
from bot.ollama import generate_comment
|
from bot.ollama import generate_comment
|
||||||
from bot.keyboard import (
|
from bot.keyboard import (
|
||||||
@@ -381,15 +382,27 @@ class CommentBot:
|
|||||||
if data == "stats":
|
if data == "stats":
|
||||||
await self._callback_stats(callback)
|
await self._callback_stats(callback)
|
||||||
elif data == "sessions":
|
elif data == "sessions":
|
||||||
await self._callback_sessions(callback)
|
await self._callback_sessions_list(callback)
|
||||||
|
elif data.startswith("session_select:"):
|
||||||
|
session_file = data.split(":")[1]
|
||||||
|
await self._callback_session_pending(callback, session_file)
|
||||||
elif data == "pending":
|
elif data == "pending":
|
||||||
await self._callback_pending(callback)
|
await self._callback_pending_groups(callback)
|
||||||
|
elif data.startswith("group_pending:"):
|
||||||
|
group_id = data.split(":")[1]
|
||||||
|
await self._callback_group_pending(callback, group_id)
|
||||||
elif data == "groups":
|
elif data == "groups":
|
||||||
await self._callback_groups(callback)
|
await self._callback_groups(callback)
|
||||||
elif data == "group_add":
|
elif data == "group_add":
|
||||||
await self._callback_group_add(callback)
|
await self._callback_group_add(callback)
|
||||||
elif data.startswith("group_"):
|
elif data.startswith("group_info:"):
|
||||||
await self._callback_group_action(callback)
|
await self._callback_group_info(callback, data)
|
||||||
|
elif data.startswith("group_pause:"):
|
||||||
|
await self._callback_group_action(callback, "pause", data)
|
||||||
|
elif data.startswith("group_resume:"):
|
||||||
|
await self._callback_group_action(callback, "resume", data)
|
||||||
|
elif data.startswith("group_delete:"):
|
||||||
|
await self._callback_group_action(callback, "delete", data)
|
||||||
elif data.startswith("approve:"):
|
elif data.startswith("approve:"):
|
||||||
await self._callback_approve(callback)
|
await self._callback_approve(callback)
|
||||||
elif data.startswith("reject:"):
|
elif data.startswith("reject:"):
|
||||||
@@ -400,8 +413,16 @@ class CommentBot:
|
|||||||
await self._callback_edit(callback)
|
await self._callback_edit(callback)
|
||||||
elif data.startswith("edit_cancel:"):
|
elif data.startswith("edit_cancel:"):
|
||||||
await self._callback_edit_cancel(callback)
|
await self._callback_edit_cancel(callback)
|
||||||
elif data.startswith("session_"):
|
elif data.startswith("session_status:"):
|
||||||
await self._callback_session_action(callback)
|
await self._callback_session_action(callback, "status", data)
|
||||||
|
elif data.startswith("session_pause:"):
|
||||||
|
await self._callback_session_action(callback, "pause", data)
|
||||||
|
elif data.startswith("session_resume:"):
|
||||||
|
await self._callback_session_action(callback, "resume", data)
|
||||||
|
elif data.startswith("session_delete:"):
|
||||||
|
await self._callback_session_action(callback, "delete", data)
|
||||||
|
elif data == "main_menu":
|
||||||
|
await self._callback_main_menu(callback)
|
||||||
else:
|
else:
|
||||||
await callback.answer("Неизвестная команда")
|
await callback.answer("Неизвестная команда")
|
||||||
|
|
||||||
@@ -462,6 +483,99 @@ class CommentBot:
|
|||||||
|
|
||||||
await callback.answer()
|
await callback.answer()
|
||||||
|
|
||||||
|
|
||||||
|
async def _callback_sessions_list(self, callback: CallbackQuery):
|
||||||
|
"""Callback для списка сессий"""
|
||||||
|
sessions = get_all_sessions()
|
||||||
|
|
||||||
|
if not sessions:
|
||||||
|
text = "❌ Нет сессий\n\nДобавьте файлы сессий в `sessions/`"
|
||||||
|
await callback.message.edit_text(text, reply_markup=create_back_keyboard())
|
||||||
|
else:
|
||||||
|
text = "👥 **Сессии**\n\nВыберите сессию для просмотра комментариев:\n\n"
|
||||||
|
for session in sessions:
|
||||||
|
status = "🟢" if session['is_active'] else "🔴"
|
||||||
|
username = f"@{session['username']}" if session.get('username') else ""
|
||||||
|
text += f"{status} `{session['session_file']}` {username}\n"
|
||||||
|
|
||||||
|
await callback.message.edit_text(
|
||||||
|
text,
|
||||||
|
parse_mode="Markdown",
|
||||||
|
reply_markup=create_sessions_list_keyboard(sessions)
|
||||||
|
)
|
||||||
|
|
||||||
|
await callback.answer()
|
||||||
|
|
||||||
|
async def _callback_session_pending(self, callback: CallbackQuery, session_file: str):
|
||||||
|
"""Callback для просмотра pending комментариев сессии"""
|
||||||
|
pending = get_pending_comments_by_session(session_file)
|
||||||
|
|
||||||
|
if not pending:
|
||||||
|
await callback.message.edit_text(
|
||||||
|
f"✅ Нет ожидающих комментариев для `{session_file}`",
|
||||||
|
reply_markup=create_back_keyboard()
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
await callback.message.edit_text(
|
||||||
|
f"📝 Ожидают модерации ({len(pending)}):\n\nСессия: `{session_file}`",
|
||||||
|
reply_markup=create_back_keyboard()
|
||||||
|
)
|
||||||
|
for comment in pending[:10]:
|
||||||
|
await self._send_moderation_message(
|
||||||
|
chat_id=callback.message.chat.id,
|
||||||
|
comment=comment
|
||||||
|
)
|
||||||
|
|
||||||
|
await callback.answer()
|
||||||
|
|
||||||
|
async def _callback_pending_groups(self, callback: CallbackQuery):
|
||||||
|
"""Callback для выбора группы из pending"""
|
||||||
|
groups = get_all_target_groups()
|
||||||
|
|
||||||
|
if not groups:
|
||||||
|
text = "📋 **Нет групп**\n\nДобавьте группу через /add_group"
|
||||||
|
await callback.message.edit_text(text, parse_mode="Markdown", reply_markup=create_back_keyboard())
|
||||||
|
else:
|
||||||
|
text = "📋 **Группы**\n\nВыберите группу для просмотра комментариев:\n\n"
|
||||||
|
for group in groups:
|
||||||
|
name = group['group_name'] or f"Группа {group['group_id']}"
|
||||||
|
text += f"📢 `{name}`\n"
|
||||||
|
|
||||||
|
await callback.message.edit_text(
|
||||||
|
text,
|
||||||
|
parse_mode="Markdown",
|
||||||
|
reply_markup=create_groups_list_for_pending_keyboard(groups)
|
||||||
|
)
|
||||||
|
|
||||||
|
await callback.answer()
|
||||||
|
|
||||||
|
async def _callback_group_pending(self, callback: CallbackQuery, group_id: str):
|
||||||
|
"""Callback для просмотра pending комментариев группы"""
|
||||||
|
pending = get_pending_comments_by_group(group_id)
|
||||||
|
|
||||||
|
if not pending:
|
||||||
|
await callback.message.edit_text(
|
||||||
|
f"✅ Нет ожидающих комментариев для этой группы",
|
||||||
|
reply_markup=create_back_keyboard()
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
await callback.message.edit_text(
|
||||||
|
f"📝 Ожидают модерации ({len(pending)}):\n\nГруппа: `{group_id}`",
|
||||||
|
reply_markup=create_back_keyboard()
|
||||||
|
)
|
||||||
|
for comment in pending[:10]:
|
||||||
|
await self._send_moderation_message(
|
||||||
|
chat_id=callback.message.chat.id,
|
||||||
|
comment=comment
|
||||||
|
)
|
||||||
|
|
||||||
|
await callback.answer()
|
||||||
|
|
||||||
|
async def _callback_main_menu(self, callback: CallbackQuery):
|
||||||
|
"""Callback для возврата в главное меню"""
|
||||||
|
await self.cmd_start(callback.message)
|
||||||
|
await callback.answer()
|
||||||
|
|
||||||
async def _callback_settings(self, callback: CallbackQuery):
|
async def _callback_settings(self, callback: CallbackQuery):
|
||||||
"""Callback для настроек"""
|
"""Callback для настроек"""
|
||||||
text = (
|
text = (
|
||||||
@@ -622,6 +736,27 @@ class CommentBot:
|
|||||||
channel_id = comment.get('channel_id') or comment['chat_id']
|
channel_id = comment.get('channel_id') or comment['chat_id']
|
||||||
post_url = f"https://t.me/c/{channel_id}/{comment['message_id']}"
|
post_url = f"https://t.me/c/{channel_id}/{comment['message_id']}"
|
||||||
|
|
||||||
|
# Получаем название группы/канала из БД
|
||||||
|
# Ищем по chat_id (группа комментариев) или по channel_id (канал)
|
||||||
|
groups = get_all_target_groups()
|
||||||
|
group = None
|
||||||
|
|
||||||
|
# Сначала ищем по chat_id (группа комментариев)
|
||||||
|
for g in groups:
|
||||||
|
if str(g['group_id']) == str(comment['chat_id']):
|
||||||
|
group = g
|
||||||
|
break
|
||||||
|
# Или по channel_id (канал)
|
||||||
|
if str(g.get('group_id')) == str(channel_id):
|
||||||
|
group = g
|
||||||
|
break
|
||||||
|
# Или по comments_group_id
|
||||||
|
if str(g.get('comments_group_id')) == str(comment['chat_id']):
|
||||||
|
group = g
|
||||||
|
break
|
||||||
|
|
||||||
|
group_name = group['group_name'] if group and group.get('group_name') else f"Канал {channel_id}"
|
||||||
|
|
||||||
session_info = ""
|
session_info = ""
|
||||||
if comment.get('session_file'):
|
if comment.get('session_file'):
|
||||||
session_info = f"👤 Сессия: `{comment['session_file']}`\n\n"
|
session_info = f"👤 Сессия: `{comment['session_file']}`\n\n"
|
||||||
@@ -631,7 +766,8 @@ class CommentBot:
|
|||||||
post_text = post_text[:300] + "..."
|
post_text = post_text[:300] + "..."
|
||||||
|
|
||||||
text = (
|
text = (
|
||||||
f"📝 **Новый комментарий**\n\n"
|
f"📝 Новый комментарий\n\n"
|
||||||
|
f"📢 Канал: **{group_name}**\n"
|
||||||
f"🔗 Пост: {post_url}\n"
|
f"🔗 Пост: {post_url}\n"
|
||||||
f"{session_info}"
|
f"{session_info}"
|
||||||
f"📄 Текст поста:\n"
|
f"📄 Текст поста:\n"
|
||||||
|
|||||||
42
bot/db.py
42
bot/db.py
@@ -264,6 +264,48 @@ def get_pending_comments() -> list:
|
|||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
|
|
||||||
|
def get_pending_comments_by_session(session_file: str) -> list:
|
||||||
|
"""Получение ожидающих комментариев для конкретной сессии"""
|
||||||
|
try:
|
||||||
|
conn = get_connection()
|
||||||
|
cursor = conn.cursor()
|
||||||
|
|
||||||
|
cursor.execute('''
|
||||||
|
SELECT * FROM comments
|
||||||
|
WHERE status = 'pending' AND session_file = ?
|
||||||
|
ORDER BY created_at DESC
|
||||||
|
''', (session_file,))
|
||||||
|
|
||||||
|
return [dict(row) for row in cursor.fetchall()]
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Ошибка при получении ожидающих комментариев для сессии: {e}")
|
||||||
|
return []
|
||||||
|
finally:
|
||||||
|
if 'conn' in locals():
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
|
||||||
|
def get_pending_comments_by_group(group_id: str) -> list:
|
||||||
|
"""Получение ожидающих комментариев для конкретной группы"""
|
||||||
|
try:
|
||||||
|
conn = get_connection()
|
||||||
|
cursor = conn.cursor()
|
||||||
|
|
||||||
|
cursor.execute('''
|
||||||
|
SELECT * FROM comments
|
||||||
|
WHERE status = 'pending' AND chat_id = ?
|
||||||
|
ORDER BY created_at DESC
|
||||||
|
''', (group_id,))
|
||||||
|
|
||||||
|
return [dict(row) for row in cursor.fetchall()]
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Ошибка при получении ожидающих комментариев для группы: {e}")
|
||||||
|
return []
|
||||||
|
finally:
|
||||||
|
if 'conn' in locals():
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
|
||||||
# === Сессии ===
|
# === Сессии ===
|
||||||
|
|
||||||
def save_session(session_file: str, user_info: dict) -> bool:
|
def save_session(session_file: str, user_info: dict) -> bool:
|
||||||
|
|||||||
@@ -111,10 +111,37 @@ def create_sessions_list_keyboard(sessions: list) -> InlineKeyboardMarkup:
|
|||||||
for session in sessions:
|
for session in sessions:
|
||||||
session_file = session['session_file']
|
session_file = session['session_file']
|
||||||
status = "🟢" if session['is_active'] else "🔴"
|
status = "🟢" if session['is_active'] else "🔴"
|
||||||
|
username = f"@{session['username']}" if session.get('username') else ""
|
||||||
builder.button(
|
builder.button(
|
||||||
text=f"{status} {session_file}",
|
text=f"{status} {session_file} {username}",
|
||||||
callback_data=f"session_info:{session_file}"
|
callback_data=f"session_select:{session_file}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
builder.adjust(1)
|
||||||
|
builder.button(text="🔙 Назад", callback_data="main_menu")
|
||||||
|
return builder.as_markup()
|
||||||
|
|
||||||
|
|
||||||
|
def create_groups_list_for_pending_keyboard(groups: list) -> InlineKeyboardMarkup:
|
||||||
|
"""Клавиатура со списком групп для выбора pending комментариев"""
|
||||||
|
builder = InlineKeyboardBuilder()
|
||||||
|
|
||||||
|
for group in groups:
|
||||||
|
group_id = group['group_id']
|
||||||
|
name = group['group_name'] or f"Группа {group_id}"
|
||||||
|
builder.button(
|
||||||
|
text=f"📢 {name}",
|
||||||
|
callback_data=f"group_pending:{group_id}"
|
||||||
|
)
|
||||||
|
|
||||||
|
builder.adjust(1)
|
||||||
|
builder.button(text="🔙 Назад", callback_data="main_menu")
|
||||||
|
return builder.as_markup()
|
||||||
|
|
||||||
|
|
||||||
|
def create_back_keyboard() -> InlineKeyboardMarkup:
|
||||||
|
"""Клавиатура с кнопкой Назад"""
|
||||||
|
builder = InlineKeyboardBuilder()
|
||||||
|
builder.button(text="🔙 Назад", callback_data="main_menu")
|
||||||
builder.adjust(1)
|
builder.adjust(1)
|
||||||
return builder.as_markup()
|
return builder.as_markup()
|
||||||
|
|||||||
112
bot/worker.py
112
bot/worker.py
@@ -29,6 +29,7 @@ class CommentWorker:
|
|||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.clients: dict[str, TelegramClient] = {}
|
self.clients: dict[str, TelegramClient] = {}
|
||||||
|
self.log_client: TelegramClient = None # Клиент для отправки логов
|
||||||
self.running = False
|
self.running = False
|
||||||
|
|
||||||
async def start(self):
|
async def start(self):
|
||||||
@@ -46,14 +47,37 @@ class CommentWorker:
|
|||||||
logger.warning("Нет активных сессий. Добавьте файлы в sessions/")
|
logger.warning("Нет активных сессий. Добавьте файлы в sessions/")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# Создаём клиента для отправки логов (используем первую сессию)
|
||||||
|
if self.clients:
|
||||||
|
first_session = list(self.clients.keys())[0]
|
||||||
|
self.log_client = self.clients[first_session]
|
||||||
|
logger.info(f"Лог-клиент: {first_session}")
|
||||||
|
|
||||||
self.running = True
|
self.running = True
|
||||||
|
|
||||||
|
# Отправляем уведомление о запуске
|
||||||
|
await self.send_log_message("🚀 Worker запущен")
|
||||||
|
|
||||||
# Запускаем задачи параллельно
|
# Запускаем задачи параллельно
|
||||||
await asyncio.gather(
|
await asyncio.gather(
|
||||||
self.listen_for_new_posts(),
|
self.listen_for_new_posts(),
|
||||||
self.monitor_approved_comments()
|
self.monitor_approved_comments()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
async def send_log_message(self, message: str):
|
||||||
|
"""Отправка сообщения в лог-группу"""
|
||||||
|
if not self.log_client or not LOG_GROUP_ID:
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
await self.log_client.send_message(
|
||||||
|
int(LOG_GROUP_ID),
|
||||||
|
message,
|
||||||
|
parse_mode='HTML'
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
logger.debug(f"Не удалось отправить лог: {e}")
|
||||||
|
|
||||||
async def stop(self):
|
async def stop(self):
|
||||||
"""Остановка воркера"""
|
"""Остановка воркера"""
|
||||||
logger.info("Остановка Comment Worker...")
|
logger.info("Остановка Comment Worker...")
|
||||||
@@ -164,45 +188,59 @@ class CommentWorker:
|
|||||||
|
|
||||||
while self.running:
|
while self.running:
|
||||||
current_time = datetime.now().timestamp()
|
current_time = datetime.now().timestamp()
|
||||||
|
|
||||||
|
# Проверяем новые группы каждые 10 секунд
|
||||||
if current_time - last_groups_check > 10:
|
if current_time - last_groups_check > 10:
|
||||||
target_groups = get_target_groups()
|
target_groups = get_target_groups()
|
||||||
new_group_ids = [str(g['group_id']) for g in target_groups]
|
new_group_ids = [str(g['group_id']) for g in target_groups]
|
||||||
|
|
||||||
# Проверяем, есть ли группы которые были удалены и добавлены заново
|
# Добавляем обработчики для новых групп
|
||||||
for group_id in new_group_ids:
|
for group_id in new_group_ids:
|
||||||
if group_id not in group_handlers:
|
if group_id not in group_handlers:
|
||||||
logger.info(f"Добавлена группа для мониторинга: {group_id}")
|
logger.info(f"➕ Добавлена группа для мониторинга: {group_id}")
|
||||||
|
|
||||||
# Сканируем последние 10 сообщений новой группы
|
# Сканируем последние 10 сообщений новой группы
|
||||||
# Всегда сканируем при добавлении, даже если уже было
|
|
||||||
await self.scan_group(group_id, limit=10)
|
await self.scan_group(group_id, limit=10)
|
||||||
scanned_groups.add(group_id)
|
scanned_groups.add(group_id)
|
||||||
|
|
||||||
|
# Регистрируем обработчик для каждой сессии
|
||||||
for session_file, client in self.clients.items():
|
for session_file, client in self.clients.items():
|
||||||
|
# Проверяем что клиент подключён
|
||||||
|
if not client.is_connected():
|
||||||
|
logger.warning(f"Клиент {session_file} не подключён")
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Регистрируем обработчик
|
||||||
@client.on(events.NewMessage(chats=group_id))
|
@client.on(events.NewMessage(chats=group_id))
|
||||||
async def handle_new_post(event):
|
async def handle_new_post(event):
|
||||||
message = event.message
|
message = event.message
|
||||||
logger.info(f"Новый пост в группе {group_id}: {message.id}")
|
logger.info(f"📬 Новый пост в группе {group_id}: {message.id}")
|
||||||
|
|
||||||
|
# Проверяем что это не бот
|
||||||
|
if message.from_id and message.from_id.user_id == (await client.get_me()).id:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Отправляем уведомление в лог-группу
|
||||||
|
post_preview = message.text[:100] + "..." if len(message.text or "") > 100 else (message.text or "Без текста")
|
||||||
|
log_message = (
|
||||||
|
f"📬 <b>Новый пост</b>\n\n"
|
||||||
|
f"📢 Группа: <code>{group_id}</code>\n"
|
||||||
|
f"🔗 Пост: <a href='https://t.me/c/{str(group_id).lstrip('-')}/{message.id}'>{message.id}</a>\n"
|
||||||
|
f"📄 Текст: <i>{post_preview}</i>"
|
||||||
|
)
|
||||||
|
await self.send_log_message(log_message)
|
||||||
|
|
||||||
await self.process_message(message, session_file, client, group_id)
|
await self.process_message(message, session_file, client, group_id)
|
||||||
|
|
||||||
group_handlers[group_id] = True
|
group_handlers[group_id] = True
|
||||||
else:
|
logger.info(f"✅ Обработчик зарегистрирован для {group_id}")
|
||||||
# Группа уже обрабатывается, но проверяем не была ли она удалена и добавлена снова
|
|
||||||
# Если была - очищаем scanned_groups для этой группы
|
|
||||||
if group_id in scanned_groups:
|
|
||||||
# Проверяем что группа всё ещё в БД
|
|
||||||
group_exists = any(str(g['group_id']) == group_id for g in target_groups)
|
|
||||||
if not group_exists:
|
|
||||||
scanned_groups.discard(group_id)
|
|
||||||
del group_handlers[group_id]
|
|
||||||
logger.info(f"Группа {group_id} удалена, сброс кэша")
|
|
||||||
|
|
||||||
# Удаляем обработчики для удалённых групп
|
# Удаляем обработчики для удалённых групп
|
||||||
for group_id in list(group_handlers.keys()):
|
for group_id in list(group_handlers.keys()):
|
||||||
if group_id not in new_group_ids:
|
if group_id not in new_group_ids:
|
||||||
|
logger.info(f"❌ Группа {group_id} удалена из мониторинга")
|
||||||
del group_handlers[group_id]
|
del group_handlers[group_id]
|
||||||
scanned_groups.discard(group_id)
|
scanned_groups.discard(group_id)
|
||||||
logger.info(f"Группа {group_id} удалена из мониторинга")
|
|
||||||
|
|
||||||
last_groups_check = current_time
|
last_groups_check = current_time
|
||||||
|
|
||||||
@@ -332,8 +370,27 @@ class CommentWorker:
|
|||||||
|
|
||||||
logger.info(f"Отправка комментария {comment['id']}: message_id={message_id_in_channel}, comments_group={comments_group_id}, channel={channel_id}")
|
logger.info(f"Отправка комментария {comment['id']}: message_id={message_id_in_channel}, comments_group={comments_group_id}, channel={channel_id}")
|
||||||
|
|
||||||
comments_group = await client.get_entity(PeerChannel(comments_group_id))
|
# Вступаем в группу комментариев ПЕРЕД тем как получать сущность
|
||||||
logger.info(f"Группа комментариев: {comments_group.title} (ID: {comments_group_id})")
|
try:
|
||||||
|
await session_manager.join_channel(session_file, comments_group_id)
|
||||||
|
logger.info(f"✅ Вступил в группу комментариев: {comments_group_id}")
|
||||||
|
except Exception as e:
|
||||||
|
logger.debug(f"Уже в группе: {e}")
|
||||||
|
|
||||||
|
# Теперь получаем сущность группы комментариев
|
||||||
|
comments_group = None
|
||||||
|
for try_id in [comments_group_id, abs(int(comments_group_id)), -abs(int(comments_group_id))]:
|
||||||
|
try:
|
||||||
|
comments_group = await client.get_entity(PeerChannel(abs(int(try_id))))
|
||||||
|
logger.info(f"Группа комментариев: {comments_group.title} (ID: {try_id})")
|
||||||
|
break
|
||||||
|
except Exception as e:
|
||||||
|
logger.debug(f"Не удалось получить сущность {try_id}: {e}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
if not comments_group:
|
||||||
|
logger.error(f"Не удалось найти группу комментариев {comments_group_id}")
|
||||||
|
return
|
||||||
|
|
||||||
from telethon.tl.functions.channels import GetFullChannelRequest
|
from telethon.tl.functions.channels import GetFullChannelRequest
|
||||||
channel_full = await client(GetFullChannelRequest(comments_group))
|
channel_full = await client(GetFullChannelRequest(comments_group))
|
||||||
@@ -346,25 +403,24 @@ class CommentWorker:
|
|||||||
logger.info(f"Linked chat ID: {linked_chat_id}")
|
logger.info(f"Linked chat ID: {linked_chat_id}")
|
||||||
|
|
||||||
# Получаем сообщение в КАНАЛЕ
|
# Получаем сообщение в КАНАЛЕ
|
||||||
|
target_message_in_channel = None
|
||||||
|
for try_id in [linked_chat_id, abs(int(linked_chat_id)), -abs(int(linked_chat_id))]:
|
||||||
|
try:
|
||||||
target_message_in_channel = await client.get_messages(
|
target_message_in_channel = await client.get_messages(
|
||||||
PeerChannel(abs(int(linked_chat_id))),
|
PeerChannel(abs(int(try_id))),
|
||||||
ids=message_id_in_channel
|
ids=message_id_in_channel
|
||||||
)
|
)
|
||||||
|
if target_message_in_channel:
|
||||||
|
logger.info(f"Найдено сообщение в канале: {target_message_in_channel.id}")
|
||||||
|
break
|
||||||
|
except Exception as e:
|
||||||
|
logger.debug(f"Не удалось получить сообщение {try_id}/{message_id_in_channel}: {e}")
|
||||||
|
continue
|
||||||
|
|
||||||
if not target_message_in_channel:
|
if not target_message_in_channel:
|
||||||
logger.error(f"Сообщение {message_id_in_channel} не найдено в канале {linked_chat_id}")
|
logger.error(f"Сообщение {message_id_in_channel} не найдено в канале {linked_chat_id}")
|
||||||
return
|
return
|
||||||
|
|
||||||
logger.info(f"Найдено сообщение в канале: {target_message_in_channel.id}")
|
|
||||||
|
|
||||||
# Вступаем в группу комментариев (если ещё не вступили)
|
|
||||||
# Это требуется для некоторых каналов
|
|
||||||
try:
|
|
||||||
await session_manager.join_channel(session_file, comments_group_id)
|
|
||||||
logger.info(f"Вступил в группу комментариев: {comments_group_id}")
|
|
||||||
except Exception as e:
|
|
||||||
logger.warning(f"Не удалось вступить в группу комментариев: {e}")
|
|
||||||
|
|
||||||
# ОТПРАВЛЯЕМ В КАНАЛ с comment_to= (как в старом проекте)
|
# ОТПРАВЛЯЕМ В КАНАЛ с comment_to= (как в старом проекте)
|
||||||
# Telegram автоматически направит комментарий в группу комментариев
|
# Telegram автоматически направит комментарий в группу комментариев
|
||||||
delay = random.uniform(COMMENT_DELAY_MIN, COMMENT_DELAY_MAX)
|
delay = random.uniform(COMMENT_DELAY_MIN, COMMENT_DELAY_MAX)
|
||||||
|
|||||||
Reference in New Issue
Block a user