mirror of
https://github.com/FerraSoft/bottohelp.git
synced 2026-08-06 21:55:03 +00:00
feat: добавлен планировщик постов по расписанию
This commit is contained in:
@@ -0,0 +1,169 @@
|
|||||||
|
# Telegram Bot
|
||||||
|
|
||||||
|
Многофункциональный Telegram бот на Python с системой ранжирования пользователей, играми и интеграцией с внешними API.
|
||||||
|
|
||||||
|
## Функции
|
||||||
|
|
||||||
|
### Основные возможности
|
||||||
|
- ✅ Отвечать на сообщения пользователей предопределенными ответами
|
||||||
|
- ✅ Обрабатывать инлайновые запросы и команды
|
||||||
|
- ✅ Система ранжирования пользователей за участие в чате (хранение в PostgreSQL)
|
||||||
|
- ✅ Приветствие новых пользователей при добавлении в группу
|
||||||
|
|
||||||
|
### Игровые элементы
|
||||||
|
- 🎮 Мини-игры: Угадай число, Камень-ножницы-бумага
|
||||||
|
- 🧠 Викторина с вопросами
|
||||||
|
- Начисление дополнительных очков за участие в играх
|
||||||
|
|
||||||
|
### Администрирование чата
|
||||||
|
- ⚠️ Выдача предупреждений пользователям
|
||||||
|
- 🔇 Временное заглушение (mute)
|
||||||
|
- 🚫 Блокировка (ban) и кик пользователей
|
||||||
|
- 📊 Просмотр рейтинга и информации о пользователях
|
||||||
|
|
||||||
|
### Посты по расписанию
|
||||||
|
- ⏰ Планирование постов для автоматической публикации
|
||||||
|
- 📅 Гибкие форматы времени (абсолютное и относительное)
|
||||||
|
- 🖼 Поддержка изображений в постах
|
||||||
|
- 👥 Управление постами (только администраторы)
|
||||||
|
- 💾 Хранение всех постов в базе данных
|
||||||
|
|
||||||
|
### Интеграция с API
|
||||||
|
- 🌤️ Получение погоды (OpenWeatherMap)
|
||||||
|
- 📰 Последние новости (NewsAPI)
|
||||||
|
- 🌐 Перевод текста (Google Translate) - базовая реализация
|
||||||
|
|
||||||
|
## Установка и настройка
|
||||||
|
|
||||||
|
### 1. Клонирование репозитория
|
||||||
|
```bash
|
||||||
|
git clone <repository-url>
|
||||||
|
cd telegram_bot
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Установка зависимостей
|
||||||
|
```bash
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Настройка базы данных
|
||||||
|
База данных SQLite настраивается автоматически при первом запуске бота. Файл базы данных `telegram_bot.db` создается в корневой директории проекта.
|
||||||
|
|
||||||
|
### 4. Получение токенов API
|
||||||
|
- **Telegram Bot Token**: Получите от [@BotFather](https://t.me/botfather)
|
||||||
|
- **OpenWeatherMap API Key**: Зарегистрируйтесь на [openweathermap.org](https://openweathermap.org/api)
|
||||||
|
- **NewsAPI Key**: Зарегистрируйтесь на [newsapi.org](https://newsapi.org/)
|
||||||
|
|
||||||
|
Обновите `config.py` с вашими ключами:
|
||||||
|
|
||||||
|
```python
|
||||||
|
BOT_TOKEN = "your_bot_token_here"
|
||||||
|
OPENWEATHER_API_KEY = "your_weather_api_key_here"
|
||||||
|
NEWS_API_KEY = "your_news_api_key_here"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. Запуск бота
|
||||||
|
```bash
|
||||||
|
python bot.py
|
||||||
|
```
|
||||||
|
|
||||||
|
## Использование
|
||||||
|
|
||||||
|
### Команды бота
|
||||||
|
|
||||||
|
#### Основные команды
|
||||||
|
- `/start` - Начать работу с ботом
|
||||||
|
- `/help` - Показать справку
|
||||||
|
- `/rank` - Ваш текущий рейтинг
|
||||||
|
- `/ranks_info` - Информация о системе рангов
|
||||||
|
- `/leaderboard` - Топ-10 участников
|
||||||
|
- `/info` - Информация о вас
|
||||||
|
|
||||||
|
#### Информация и сервисы
|
||||||
|
- `/weather [город]` - Погода в городе
|
||||||
|
- `/news` - Последние новости
|
||||||
|
- `/translate [текст] [язык]` - Перевод текста
|
||||||
|
|
||||||
|
#### Игры
|
||||||
|
- `/play_game` - Запустить мини-игру
|
||||||
|
|
||||||
|
#### Посты по расписанию (только админы)
|
||||||
|
- `/schedule_post [время] [текст]` - Запланировать пост
|
||||||
|
- `/list_posts` - Показать запланированные посты
|
||||||
|
- `/delete_post [ID]` - Удалить пост по расписанию
|
||||||
|
- `/publish_now [ID]` - Опубликовать пост немедленно
|
||||||
|
|
||||||
|
#### Модерация (только админы)
|
||||||
|
- `/warn [пользователь] [причина]` - Выдать предупреждение
|
||||||
|
- `/mute [пользователь] [время]` - Заглушить пользователя
|
||||||
|
- `/unmute [пользователь]` - Снять заглушку
|
||||||
|
- `/ban [пользователь] [причина]` - Забанить пользователя
|
||||||
|
- `/unban [пользователь]` - Разбанить пользователя
|
||||||
|
- `/kick [пользователь] [причина]` - Кикнуть пользователя
|
||||||
|
- `/promote [пользователь]` - Повысить до модератора
|
||||||
|
- `/demote [пользователь]` - Понизить с модератора
|
||||||
|
|
||||||
|
### Система ранжирования
|
||||||
|
- За каждое сообщение начисляется 1 очко
|
||||||
|
- За выигрыш в мини-игре начисляются дополнительные очки
|
||||||
|
- Военная иерархия званий от Рядового до Маршала
|
||||||
|
- Рейтинг сохраняется в SQLite базе данных
|
||||||
|
|
||||||
|
### Посты по расписанию
|
||||||
|
- Планирование публикации постов в указанное время
|
||||||
|
- Гибкие форматы времени: абсолютное (2024-01-15 14:30) и относительное (+2h, +30m)
|
||||||
|
- Поддержка изображений в постах
|
||||||
|
- Автоматическая публикация через планировщик
|
||||||
|
- Управление постами только администраторами чата
|
||||||
|
- Все посты сохраняются в базе данных с историей публикаций
|
||||||
|
|
||||||
|
#### Примеры использования планировщика:
|
||||||
|
```
|
||||||
|
/schedule_post +1h Добро пожаловать в наш чат!
|
||||||
|
/schedule_post 2024-01-15 09:00 Ежедневное утреннее приветствие
|
||||||
|
/schedule_post +30m Важное объявление для всех участников
|
||||||
|
```
|
||||||
|
|
||||||
|
## Структура проекта
|
||||||
|
|
||||||
|
```
|
||||||
|
telegram_bot/
|
||||||
|
├── bot.py # Основной файл бота
|
||||||
|
├── config.py # Конфигурация (токены, настройки БД)
|
||||||
|
├── database_sqlite.py # Работа с SQLite базой данных
|
||||||
|
├── scheduler.py # Планировщик постов по расписанию
|
||||||
|
├── migrate_ranks.py # Миграция системы рангов
|
||||||
|
├── requirements.txt # Зависимости Python
|
||||||
|
└── README.md # Эта документация
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. Запуск планировщика постов
|
||||||
|
Для работы постов по расписанию запустите планировщик в отдельном терминале:
|
||||||
|
```bash
|
||||||
|
python scheduler.py
|
||||||
|
```
|
||||||
|
|
||||||
|
Рекомендуется запускать бота и планировщик одновременно для полной функциональности.
|
||||||
|
|
||||||
|
## Технологии
|
||||||
|
|
||||||
|
- **Python 3.10+**
|
||||||
|
- **python-telegram-bot** - библиотека для работы с Telegram API
|
||||||
|
- **sqlite3** - встроенная поддержка SQLite базы данных
|
||||||
|
- **requests** - HTTP запросы для API
|
||||||
|
- **asyncio** - асинхронное программирование
|
||||||
|
- **SQLite** - база данных для хранения рейтинга и постов
|
||||||
|
|
||||||
|
## Разработка
|
||||||
|
|
||||||
|
### Добавление новых функций
|
||||||
|
1. Добавьте логику в соответствующие методы класса `TelegramBot`
|
||||||
|
2. Обновите базу данных при необходимости (методы в `database.py`)
|
||||||
|
3. Добавьте новые команды в `setup_handlers()`
|
||||||
|
|
||||||
|
### Тестирование
|
||||||
|
Запустите бота локально и протестируйте все функции в тестовом чате.
|
||||||
|
|
||||||
|
## Лицензия
|
||||||
|
|
||||||
|
MIT License - см. файл LICENSE для подробностей.
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
# 📅 Руководство по планировщику постов
|
||||||
|
|
||||||
|
## Описание
|
||||||
|
Планировщик постов позволяет администраторам чата планировать публикацию сообщений в указанное время автоматически.
|
||||||
|
|
||||||
|
## 🚀 Быстрый старт
|
||||||
|
|
||||||
|
1. **Запустите планировщик:**
|
||||||
|
```bash
|
||||||
|
python scheduler.py
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Используйте команды в Telegram:**
|
||||||
|
- `/schedule_post +1h Привет всем!` - запланировать пост через час
|
||||||
|
- `/list_posts` - посмотреть все запланированные посты
|
||||||
|
- `/delete_post 1` - удалить пост с ID 1
|
||||||
|
|
||||||
|
## 📋 Доступные команды
|
||||||
|
|
||||||
|
### `/schedule_post [время] [текст]`
|
||||||
|
Планирует публикацию поста в указанное время.
|
||||||
|
|
||||||
|
**Форматы времени:**
|
||||||
|
- **Относительное время:**
|
||||||
|
- `+30m` - через 30 минут
|
||||||
|
- `+2h` - через 2 часа
|
||||||
|
- `+1d` - через 1 день
|
||||||
|
|
||||||
|
- **Абсолютное время:**
|
||||||
|
- `2024-01-15 14:30:00` - 15 января 2024 года в 14:30
|
||||||
|
- `2024-01-15 14:30` - то же самое без секунд
|
||||||
|
|
||||||
|
**Примеры:**
|
||||||
|
```
|
||||||
|
/schedule_post +1h Ежедневное приветствие!
|
||||||
|
/schedule_post +30m Напоминание о встрече
|
||||||
|
/schedule_post 2024-01-15 09:00 Доброе утро!
|
||||||
|
```
|
||||||
|
|
||||||
|
### `/list_posts`
|
||||||
|
Показывает список всех запланированных постов с информацией:
|
||||||
|
- ID поста
|
||||||
|
- Время публикации
|
||||||
|
- Автор поста
|
||||||
|
- Текст поста (первые 100 символов)
|
||||||
|
|
||||||
|
### `/delete_post [ID]`
|
||||||
|
Удаляет запланированный пост по его ID.
|
||||||
|
|
||||||
|
**Пример:**
|
||||||
|
```
|
||||||
|
/delete_post 5
|
||||||
|
```
|
||||||
|
|
||||||
|
### `/publish_now [ID]`
|
||||||
|
Немедленно публикует пост и удаляет его из планировщика.
|
||||||
|
|
||||||
|
**Пример:**
|
||||||
|
```
|
||||||
|
/publish_now 3
|
||||||
|
```
|
||||||
|
|
||||||
|
## ⚙️ Как это работает
|
||||||
|
|
||||||
|
1. **База данных** - все посты хранятся в таблице `scheduled_posts`
|
||||||
|
2. **Планировщик** (`scheduler.py`) работает в фоне и проверяет посты каждые 30 секунд
|
||||||
|
3. **Автопубликация** - посты публикуются автоматически в указанное время
|
||||||
|
4. **Логирование** - все действия записываются в логи планировщика
|
||||||
|
|
||||||
|
## 🔧 Структура базы данных
|
||||||
|
|
||||||
|
Таблица `scheduled_posts` содержит:
|
||||||
|
- `post_id` - уникальный идентификатор поста
|
||||||
|
- `chat_id` - ID чата для публикации
|
||||||
|
- `text` - текст поста
|
||||||
|
- `image_path` - путь к изображению (если есть)
|
||||||
|
- `schedule_time` - время публикации
|
||||||
|
- `created_by` - ID пользователя, создавшего пост
|
||||||
|
- `status` - статус поста (`scheduled`/`published`)
|
||||||
|
- `published_at` - время фактической публикации
|
||||||
|
- `created_at` - время создания поста
|
||||||
|
|
||||||
|
## 📝 Примечания
|
||||||
|
|
||||||
|
- Только администраторы чата могут управлять постами по расписанию
|
||||||
|
- Планировщик должен работать постоянно для автоматической публикации
|
||||||
|
- Рекомендуется запускать планировщик в отдельном терминале или как сервис
|
||||||
|
- Все посты сохраняются в истории даже после публикации
|
||||||
|
|
||||||
|
## 🔍 Мониторинг
|
||||||
|
|
||||||
|
Для мониторинга работы планировщика проверяйте:
|
||||||
|
1. Логи планировщика в терминале
|
||||||
|
2. Статус постов командой `/list_posts`
|
||||||
|
3. Лог публикаций в основном боте
|
||||||
|
|
||||||
|
## 🛠️ Устранение неисправностей
|
||||||
|
|
||||||
|
**Планировщик не публикует посты:**
|
||||||
|
- Убедитесь, что планировщик запущен (`python scheduler.py`)
|
||||||
|
- Проверьте права бота в чате
|
||||||
|
- Убедитесь, что время на сервере правильное
|
||||||
|
|
||||||
|
**Посты не сохраняются:**
|
||||||
|
- Проверьте права доступа к базе данных
|
||||||
|
- Убедитесь, что пользователь является администратором чата
|
||||||
|
- Проверьте формат команды и времени
|
||||||
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,6 @@
|
|||||||
|
# Конфигурация бота
|
||||||
|
BOT_TOKEN = "5601825044:AAHr7-VMgD34vPsyZ03QTRWSN73D-YPGQcE" # Токен от @BotFather
|
||||||
|
|
||||||
|
# API ключи для внешних сервисов (опционально)
|
||||||
|
OPENWEATHER_API_KEY = "ВСТАВЬТЕ_ВАШ_API_КЛЮЧ_ЗДЕСЬ" # Для погоды
|
||||||
|
NEWS_API_KEY = "ВСТАВЬТЕ_ВАШ_API_КЛЮЧ_ЗДЕСЬ" # Для новостей
|
||||||
+209
@@ -0,0 +1,209 @@
|
|||||||
|
import psycopg2
|
||||||
|
from psycopg2 import Error
|
||||||
|
from config import DB_CONFIG
|
||||||
|
import csv
|
||||||
|
import os
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
class Database:
|
||||||
|
def __init__(self):
|
||||||
|
self.connection = None
|
||||||
|
self.connect()
|
||||||
|
|
||||||
|
def connect(self):
|
||||||
|
"""Установка соединения с базой данных"""
|
||||||
|
try:
|
||||||
|
self.connection = psycopg2.connect(**DB_CONFIG)
|
||||||
|
self.connection.autocommit = True
|
||||||
|
print("Успешное подключение к базе данных PostgreSQL")
|
||||||
|
self.create_tables()
|
||||||
|
except (Exception, Error) as error:
|
||||||
|
print(f"Ошибка при подключении к PostgreSQL: {error}")
|
||||||
|
self.connection = None
|
||||||
|
|
||||||
|
def create_tables(self):
|
||||||
|
"""Создание таблиц в базе данных"""
|
||||||
|
try:
|
||||||
|
cursor = self.connection.cursor()
|
||||||
|
|
||||||
|
# Таблица пользователей
|
||||||
|
cursor.execute("""
|
||||||
|
CREATE TABLE IF NOT EXISTS users (
|
||||||
|
user_id BIGINT PRIMARY KEY,
|
||||||
|
username TEXT,
|
||||||
|
first_name TEXT,
|
||||||
|
last_name TEXT,
|
||||||
|
score INTEGER DEFAULT 0,
|
||||||
|
warnings INTEGER DEFAULT 0,
|
||||||
|
role TEXT DEFAULT 'user',
|
||||||
|
joined_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
)
|
||||||
|
""")
|
||||||
|
|
||||||
|
# Таблица предупреждений
|
||||||
|
cursor.execute("""
|
||||||
|
CREATE TABLE IF NOT EXISTS warnings (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
user_id BIGINT REFERENCES users(user_id),
|
||||||
|
reason TEXT,
|
||||||
|
issued_by BIGINT,
|
||||||
|
issued_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
)
|
||||||
|
""")
|
||||||
|
|
||||||
|
# Таблица игр
|
||||||
|
cursor.execute("""
|
||||||
|
CREATE TABLE IF NOT EXISTS games (
|
||||||
|
game_id SERIAL PRIMARY KEY,
|
||||||
|
type TEXT,
|
||||||
|
status TEXT DEFAULT 'active',
|
||||||
|
participants TEXT,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
)
|
||||||
|
""")
|
||||||
|
|
||||||
|
print("Таблицы созданы успешно")
|
||||||
|
except (Exception, Error) as error:
|
||||||
|
print(f"Ошибка при создании таблиц: {error}")
|
||||||
|
|
||||||
|
def add_user(self, user_id, username, first_name, last_name):
|
||||||
|
"""Добавление нового пользователя"""
|
||||||
|
try:
|
||||||
|
cursor = self.connection.cursor()
|
||||||
|
cursor.execute("""
|
||||||
|
INSERT INTO users (user_id, username, first_name, last_name)
|
||||||
|
VALUES (%s, %s, %s, %s)
|
||||||
|
ON CONFLICT (user_id) DO NOTHING
|
||||||
|
""", (user_id, username, first_name, last_name))
|
||||||
|
except (Exception, Error) as error:
|
||||||
|
print(f"Ошибка при добавлении пользователя: {error}")
|
||||||
|
|
||||||
|
def update_score(self, user_id, points=1):
|
||||||
|
"""Обновление очков пользователя"""
|
||||||
|
try:
|
||||||
|
cursor = self.connection.cursor()
|
||||||
|
cursor.execute("""
|
||||||
|
UPDATE users SET score = score + %s WHERE user_id = %s
|
||||||
|
""", (points, user_id))
|
||||||
|
except (Exception, Error) as error:
|
||||||
|
print(f"Ошибка при обновлении очков: {error}")
|
||||||
|
|
||||||
|
def get_top_users(self, limit=10):
|
||||||
|
"""Получение топ пользователей по очкам"""
|
||||||
|
try:
|
||||||
|
cursor = self.connection.cursor()
|
||||||
|
cursor.execute("""
|
||||||
|
SELECT user_id, username, first_name, score
|
||||||
|
FROM users
|
||||||
|
ORDER BY score DESC
|
||||||
|
LIMIT %s
|
||||||
|
""", (limit,))
|
||||||
|
return cursor.fetchall()
|
||||||
|
except (Exception, Error) as error:
|
||||||
|
print(f"Ошибка при получении топ пользователей: {error}")
|
||||||
|
return []
|
||||||
|
|
||||||
|
def add_warning(self, user_id, reason, issued_by):
|
||||||
|
"""Добавление предупреждения пользователю"""
|
||||||
|
try:
|
||||||
|
cursor = self.connection.cursor()
|
||||||
|
cursor.execute("""
|
||||||
|
INSERT INTO warnings (user_id, reason, issued_by)
|
||||||
|
VALUES (%s, %s, %s)
|
||||||
|
""", (user_id, reason, issued_by))
|
||||||
|
|
||||||
|
cursor.execute("""
|
||||||
|
UPDATE users SET warnings = warnings + 1 WHERE user_id = %s
|
||||||
|
""", (user_id,))
|
||||||
|
except (Exception, Error) as error:
|
||||||
|
print(f"Ошибка при добавлении предупреждения: {error}")
|
||||||
|
|
||||||
|
def get_user_warnings(self, user_id):
|
||||||
|
"""Получение количества предупреждений пользователя"""
|
||||||
|
try:
|
||||||
|
cursor = self.connection.cursor()
|
||||||
|
cursor.execute("""
|
||||||
|
SELECT warnings FROM users WHERE user_id = %s
|
||||||
|
""", (user_id,))
|
||||||
|
result = cursor.fetchone()
|
||||||
|
return result[0] if result else 0
|
||||||
|
except (Exception, Error) as error:
|
||||||
|
print(f"Ошибка при получении предупреждений: {error}")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def get_user_info(self, user_id):
|
||||||
|
"""Получение информации о пользователе"""
|
||||||
|
try:
|
||||||
|
cursor = self.connection.cursor()
|
||||||
|
cursor.execute("""
|
||||||
|
SELECT user_id, username, first_name, last_name, score, warnings, role
|
||||||
|
FROM users WHERE user_id = %s
|
||||||
|
""", (user_id,))
|
||||||
|
return cursor.fetchone()
|
||||||
|
except (Exception, Error) as error:
|
||||||
|
print(f"Ошибка при получении информации о пользователе: {error}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def import_users_from_csv(self, csv_file_path):
|
||||||
|
"""Импорт пользователей из CSV файла"""
|
||||||
|
try:
|
||||||
|
if not os.path.exists(csv_file_path):
|
||||||
|
print(f"Файл {csv_file_path} не найден")
|
||||||
|
return False
|
||||||
|
|
||||||
|
imported_count = 0
|
||||||
|
skipped_count = 0
|
||||||
|
|
||||||
|
with open(csv_file_path, 'r', encoding='utf-8') as file:
|
||||||
|
csv_reader = csv.DictReader(file)
|
||||||
|
|
||||||
|
for row in csv_reader:
|
||||||
|
try:
|
||||||
|
user_id = int(row['User ID'])
|
||||||
|
name = row['Name'].strip()
|
||||||
|
username = row['Username'].strip() if row['Username'] != 'N/A' else None
|
||||||
|
xp = int(row['XP']) if row['XP'] else 0
|
||||||
|
rep = int(row['REP']) if row['REP'] else 0
|
||||||
|
|
||||||
|
# Разделяем имя на first_name и last_name
|
||||||
|
name_parts = name.split(' ', 1)
|
||||||
|
first_name = name_parts[0] if name_parts else name
|
||||||
|
last_name = name_parts[1] if len(name_parts) > 1 else None
|
||||||
|
|
||||||
|
# Проверяем, существует ли пользователь
|
||||||
|
cursor = self.connection.cursor()
|
||||||
|
cursor.execute("SELECT user_id FROM users WHERE user_id = %s", (user_id,))
|
||||||
|
existing_user = cursor.fetchone()
|
||||||
|
|
||||||
|
if existing_user:
|
||||||
|
# Обновляем существующего пользователя
|
||||||
|
cursor.execute("""
|
||||||
|
UPDATE users
|
||||||
|
SET username = %s, first_name = %s, last_name = %s, score = %s, warnings = %s
|
||||||
|
WHERE user_id = %s
|
||||||
|
""", (username, first_name, last_name, xp, rep, user_id))
|
||||||
|
skipped_count += 1
|
||||||
|
else:
|
||||||
|
# Добавляем нового пользователя
|
||||||
|
cursor.execute("""
|
||||||
|
INSERT INTO users (user_id, username, first_name, last_name, score, warnings)
|
||||||
|
VALUES (%s, %s, %s, %s, %s, %s)
|
||||||
|
""", (user_id, username, first_name, last_name, xp, rep))
|
||||||
|
imported_count += 1
|
||||||
|
|
||||||
|
except (ValueError, KeyError) as e:
|
||||||
|
print(f"Ошибка обработки строки: {row}. Ошибка: {e}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
print(f"Импорт завершен. Добавлено: {imported_count}, Обновлено: {skipped_count}")
|
||||||
|
return True
|
||||||
|
|
||||||
|
except (Exception, Error) as error:
|
||||||
|
print(f"Ошибка при импорте пользователей из CSV: {error}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
"""Закрытие соединения с базой данных"""
|
||||||
|
if self.connection:
|
||||||
|
self.connection.close()
|
||||||
|
print("Соединение с PostgreSQL закрыто")
|
||||||
@@ -0,0 +1,422 @@
|
|||||||
|
import sqlite3
|
||||||
|
import os
|
||||||
|
from datetime import datetime
|
||||||
|
import csv
|
||||||
|
|
||||||
|
class Database:
|
||||||
|
def __init__(self, db_file='telegram_bot.db'):
|
||||||
|
self.db_file = db_file
|
||||||
|
self.connection = None
|
||||||
|
self.connect()
|
||||||
|
self.create_tables()
|
||||||
|
|
||||||
|
def connect(self):
|
||||||
|
"""Установка соединения с SQLite базой данных"""
|
||||||
|
try:
|
||||||
|
self.connection = sqlite3.connect(self.db_file)
|
||||||
|
print("Успешное подключение к SQLite базе данных")
|
||||||
|
except sqlite3.Error as error:
|
||||||
|
print(f"Ошибка при подключении к SQLite: {error}")
|
||||||
|
self.connection = None
|
||||||
|
|
||||||
|
def create_tables(self):
|
||||||
|
"""Создание таблиц в базе данных"""
|
||||||
|
try:
|
||||||
|
cursor = self.connection.cursor()
|
||||||
|
|
||||||
|
# Таблица пользователей
|
||||||
|
cursor.execute("""
|
||||||
|
CREATE TABLE IF NOT EXISTS users (
|
||||||
|
user_id INTEGER PRIMARY KEY,
|
||||||
|
username TEXT,
|
||||||
|
first_name TEXT,
|
||||||
|
last_name TEXT,
|
||||||
|
reputation INTEGER DEFAULT 0,
|
||||||
|
rank TEXT DEFAULT 'Рядовой',
|
||||||
|
message_count INTEGER DEFAULT 0,
|
||||||
|
active_days INTEGER DEFAULT 0,
|
||||||
|
days_since_join INTEGER DEFAULT 0,
|
||||||
|
last_message TIMESTAMP,
|
||||||
|
joined_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
left_at TIMESTAMP,
|
||||||
|
language TEXT DEFAULT 'ru',
|
||||||
|
actions TEXT DEFAULT '[]',
|
||||||
|
score INTEGER DEFAULT 0,
|
||||||
|
warnings INTEGER DEFAULT 0,
|
||||||
|
role TEXT DEFAULT 'user'
|
||||||
|
)
|
||||||
|
""")
|
||||||
|
|
||||||
|
# Таблица предупреждений
|
||||||
|
cursor.execute("""
|
||||||
|
CREATE TABLE IF NOT EXISTS warnings (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
user_id INTEGER,
|
||||||
|
reason TEXT,
|
||||||
|
issued_by INTEGER,
|
||||||
|
issued_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (user_id) REFERENCES users (user_id)
|
||||||
|
)
|
||||||
|
""")
|
||||||
|
|
||||||
|
# Таблица игр
|
||||||
|
cursor.execute("""
|
||||||
|
CREATE TABLE IF NOT EXISTS games (
|
||||||
|
game_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
type TEXT,
|
||||||
|
status TEXT DEFAULT 'active',
|
||||||
|
participants TEXT,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
)
|
||||||
|
""")
|
||||||
|
|
||||||
|
# Таблица запланированных постов
|
||||||
|
cursor.execute("""
|
||||||
|
CREATE TABLE IF NOT EXISTS scheduled_posts (
|
||||||
|
post_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
chat_id INTEGER,
|
||||||
|
text TEXT NOT NULL,
|
||||||
|
image_path TEXT,
|
||||||
|
schedule_time TIMESTAMP NOT NULL,
|
||||||
|
created_by INTEGER,
|
||||||
|
status TEXT DEFAULT 'scheduled',
|
||||||
|
published_at TIMESTAMP,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (created_by) REFERENCES users (user_id)
|
||||||
|
)
|
||||||
|
""")
|
||||||
|
|
||||||
|
self.connection.commit()
|
||||||
|
print("Таблицы созданы успешно")
|
||||||
|
except sqlite3.Error as error:
|
||||||
|
print(f"Ошибка при создании таблиц: {error}")
|
||||||
|
|
||||||
|
def add_user(self, user_id, username, first_name, last_name):
|
||||||
|
"""Добавление нового пользователя"""
|
||||||
|
try:
|
||||||
|
cursor = self.connection.cursor()
|
||||||
|
cursor.execute("""
|
||||||
|
INSERT OR IGNORE INTO users (user_id, username, first_name, last_name, joined_at, message_count, active_days, days_since_join)
|
||||||
|
VALUES (?, ?, ?, ?, CURRENT_TIMESTAMP, 0, 0, 0)
|
||||||
|
""", (user_id, username, first_name, last_name))
|
||||||
|
self.connection.commit()
|
||||||
|
except sqlite3.Error as error:
|
||||||
|
print(f"Ошибка при добавлении пользователя: {error}")
|
||||||
|
|
||||||
|
def update_score(self, user_id, points=1):
|
||||||
|
"""Обновление очков пользователя"""
|
||||||
|
try:
|
||||||
|
cursor = self.connection.cursor()
|
||||||
|
cursor.execute("""
|
||||||
|
UPDATE users SET score = score + ?, message_count = message_count + 1, last_message = CURRENT_TIMESTAMP WHERE user_id = ?
|
||||||
|
""", (points, user_id))
|
||||||
|
self.connection.commit()
|
||||||
|
except sqlite3.Error as error:
|
||||||
|
print(f"Ошибка при обновлении очков: {error}")
|
||||||
|
|
||||||
|
def update_reputation(self, user_id, rep_points=1):
|
||||||
|
"""Обновление репутации пользователя"""
|
||||||
|
try:
|
||||||
|
cursor = self.connection.cursor()
|
||||||
|
cursor.execute("""
|
||||||
|
UPDATE users SET reputation = reputation + ? WHERE user_id = ?
|
||||||
|
""", (rep_points, user_id))
|
||||||
|
self.connection.commit()
|
||||||
|
# Обновление ранга на основе репутации
|
||||||
|
self.update_rank(user_id)
|
||||||
|
except sqlite3.Error as error:
|
||||||
|
print(f"Ошибка при обновлении репутации: {error}")
|
||||||
|
|
||||||
|
def update_rank(self, user_id, chat_id=None, first_name=None):
|
||||||
|
"""Обновление ранга пользователя на основе репутации"""
|
||||||
|
try:
|
||||||
|
cursor = self.connection.cursor()
|
||||||
|
cursor.execute("SELECT reputation, rank FROM users WHERE user_id = ?", (user_id,))
|
||||||
|
result = cursor.fetchone()
|
||||||
|
if result:
|
||||||
|
reputation = result[0]
|
||||||
|
old_rank = result[1]
|
||||||
|
new_rank = self.calculate_rank(reputation)
|
||||||
|
|
||||||
|
if new_rank != old_rank:
|
||||||
|
cursor.execute("""
|
||||||
|
UPDATE users SET rank = ? WHERE user_id = ?
|
||||||
|
""", (new_rank, user_id))
|
||||||
|
self.connection.commit()
|
||||||
|
|
||||||
|
# Если передан chat_id, объявить о повышении в чате
|
||||||
|
if chat_id and first_name:
|
||||||
|
return {"promoted": True, "new_rank": new_rank, "old_rank": old_rank, "name": first_name}
|
||||||
|
else:
|
||||||
|
self.connection.commit()
|
||||||
|
except sqlite3.Error as error:
|
||||||
|
print(f"Ошибка при обновлении ранга: {error}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def calculate_rank(self, reputation):
|
||||||
|
"""Расчет ранга на основе репутации"""
|
||||||
|
ranks = [
|
||||||
|
(0, "Рядовой"),
|
||||||
|
(100, "Ефрейтор"),
|
||||||
|
(235, "Младший сержант"),
|
||||||
|
(505, "Сержант"),
|
||||||
|
(810, "Старший сержант"),
|
||||||
|
(1250, "Старшина"),
|
||||||
|
(1725, "Прапорщик"),
|
||||||
|
(2335, "Старший прапорщик"),
|
||||||
|
(2980, "Младший лейтенант"),
|
||||||
|
(3760, "Лейтенант"),
|
||||||
|
(4575, "Старший лейтенант"),
|
||||||
|
(5525, "Капитан"),
|
||||||
|
(6510, "Майор"),
|
||||||
|
(7630, "Подполковник"),
|
||||||
|
(8785, "Полковник"),
|
||||||
|
(16075, "Генерал майор"),
|
||||||
|
(32150, "Генерал лейтенант"),
|
||||||
|
(64300, "Генерал полковник"),
|
||||||
|
(128600, "Генерал армии"),
|
||||||
|
(256000, "Маршал")
|
||||||
|
]
|
||||||
|
|
||||||
|
for threshold, rank_name in reversed(ranks):
|
||||||
|
if reputation >= threshold:
|
||||||
|
return rank_name
|
||||||
|
|
||||||
|
return "Рядовой"
|
||||||
|
|
||||||
|
def get_top_users(self, limit=10):
|
||||||
|
"""Получение топ пользователей по очкам"""
|
||||||
|
try:
|
||||||
|
cursor = self.connection.cursor()
|
||||||
|
cursor.execute("""
|
||||||
|
SELECT user_id, username, first_name, score
|
||||||
|
FROM users
|
||||||
|
ORDER BY score DESC
|
||||||
|
LIMIT ?
|
||||||
|
""", (limit,))
|
||||||
|
return cursor.fetchall()
|
||||||
|
except sqlite3.Error as error:
|
||||||
|
print(f"Ошибка при получении топ пользователей: {error}")
|
||||||
|
return []
|
||||||
|
|
||||||
|
def add_warning(self, user_id, reason, issued_by):
|
||||||
|
"""Добавление предупреждения пользователю"""
|
||||||
|
try:
|
||||||
|
cursor = self.connection.cursor()
|
||||||
|
cursor.execute("""
|
||||||
|
INSERT INTO warnings (user_id, reason, issued_by)
|
||||||
|
VALUES (?, ?, ?)
|
||||||
|
""", (user_id, reason, issued_by))
|
||||||
|
|
||||||
|
cursor.execute("""
|
||||||
|
UPDATE users SET warnings = warnings + 1 WHERE user_id = ?
|
||||||
|
""", (user_id,))
|
||||||
|
|
||||||
|
self.connection.commit()
|
||||||
|
except sqlite3.Error as error:
|
||||||
|
print(f"Ошибка при добавлении предупреждения: {error}")
|
||||||
|
|
||||||
|
def get_user_warnings(self, user_id):
|
||||||
|
"""Получение количества предупреждений пользователя"""
|
||||||
|
try:
|
||||||
|
cursor = self.connection.cursor()
|
||||||
|
cursor.execute("""
|
||||||
|
SELECT warnings FROM users WHERE user_id = ?
|
||||||
|
""", (user_id,))
|
||||||
|
result = cursor.fetchone()
|
||||||
|
return result[0] if result else 0
|
||||||
|
except sqlite3.Error as error:
|
||||||
|
print(f"Ошибка при получении предупреждений: {error}")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def get_user_info(self, user_id):
|
||||||
|
"""Получение информации о пользователе"""
|
||||||
|
try:
|
||||||
|
cursor = self.connection.cursor()
|
||||||
|
cursor.execute("""
|
||||||
|
SELECT user_id, first_name, username, reputation, rank, message_count, active_days, days_since_join, last_message, joined_at, left_at, language, actions, score, warnings, role
|
||||||
|
FROM users WHERE user_id = ?
|
||||||
|
""", (user_id,))
|
||||||
|
result = cursor.fetchone()
|
||||||
|
if result:
|
||||||
|
return {
|
||||||
|
'ID': result[0],
|
||||||
|
'Имя': result[1],
|
||||||
|
'Имя пользователя': result[2] or 'не указано',
|
||||||
|
'Репутация': result[3],
|
||||||
|
'Ранг': result[4],
|
||||||
|
'Количество сообщений': result[5],
|
||||||
|
'Активные дни': result[6],
|
||||||
|
'Дней с присоединения': result[7],
|
||||||
|
'Последнее сообщение': result[8],
|
||||||
|
'Присоединился': result[9],
|
||||||
|
'Покинул': result[10],
|
||||||
|
'Язык': result[11],
|
||||||
|
'Действия': result[12],
|
||||||
|
'Очки': result[13],
|
||||||
|
'Предупреждений': result[14],
|
||||||
|
'Роль': result[15]
|
||||||
|
}
|
||||||
|
return None
|
||||||
|
except sqlite3.Error as error:
|
||||||
|
print(f"Ошибка при получении информации о пользователе: {error}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def import_users_from_csv(self, csv_file_path):
|
||||||
|
"""Импорт пользователей из CSV файла"""
|
||||||
|
try:
|
||||||
|
if not os.path.exists(csv_file_path):
|
||||||
|
print(f"Файл {csv_file_path} не найден")
|
||||||
|
return False
|
||||||
|
|
||||||
|
imported_count = 0
|
||||||
|
updated_count = 0
|
||||||
|
|
||||||
|
with open(csv_file_path, 'r', encoding='utf-8') as file:
|
||||||
|
csv_reader = csv.DictReader(file)
|
||||||
|
|
||||||
|
for row in csv_reader:
|
||||||
|
try:
|
||||||
|
user_id = int(row['User ID'])
|
||||||
|
name = row['Name'].strip()
|
||||||
|
username = row['Username'].strip() if row['Username'] != 'N/A' else None
|
||||||
|
xp = int(row['XP']) if row['XP'] else 0
|
||||||
|
rep = int(row['REP']) if row['REP'] else 0
|
||||||
|
|
||||||
|
# Разделяем имя на first_name и last_name
|
||||||
|
name_parts = name.split(' ', 1)
|
||||||
|
first_name = name_parts[0] if name_parts else name
|
||||||
|
last_name = name_parts[1] if len(name_parts) > 1 else None
|
||||||
|
|
||||||
|
# Проверяем, существует ли пользователь
|
||||||
|
cursor = self.connection.cursor()
|
||||||
|
cursor.execute("SELECT user_id FROM users WHERE user_id = ?", (user_id,))
|
||||||
|
existing_user = cursor.fetchone()
|
||||||
|
|
||||||
|
if existing_user:
|
||||||
|
# Обновляем существующего пользователя
|
||||||
|
cursor.execute("""
|
||||||
|
UPDATE users
|
||||||
|
SET username = ?, first_name = ?, last_name = ?, score = ?, warnings = ?, reputation = ?
|
||||||
|
WHERE user_id = ?
|
||||||
|
""", (username, first_name, last_name, xp, rep, rep, user_id))
|
||||||
|
updated_count += 1
|
||||||
|
else:
|
||||||
|
# Добавляем нового пользователя
|
||||||
|
cursor.execute("""
|
||||||
|
INSERT INTO users (user_id, username, first_name, last_name, score, warnings, reputation)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||||
|
""", (user_id, username, first_name, last_name, xp, rep, rep))
|
||||||
|
imported_count += 1
|
||||||
|
|
||||||
|
self.connection.commit()
|
||||||
|
|
||||||
|
except (ValueError, KeyError) as e:
|
||||||
|
print(f"Ошибка обработки строки: {row}. Ошибка: {e}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
print(f"Импорт завершен. Добавлено: {imported_count}, Обновлено: {updated_count}")
|
||||||
|
return True
|
||||||
|
|
||||||
|
except sqlite3.Error as error:
|
||||||
|
print(f"Ошибка при импорте пользователей из CSV: {error}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def add_scheduled_post(self, chat_id, text, schedule_time, created_by, image_path=None):
|
||||||
|
"""Добавление запланированного поста"""
|
||||||
|
try:
|
||||||
|
cursor = self.connection.cursor()
|
||||||
|
cursor.execute("""
|
||||||
|
INSERT INTO scheduled_posts (chat_id, text, image_path, schedule_time, created_by)
|
||||||
|
VALUES (?, ?, ?, ?, ?)
|
||||||
|
""", (chat_id, text, image_path, schedule_time, created_by))
|
||||||
|
self.connection.commit()
|
||||||
|
return cursor.lastrowid
|
||||||
|
except sqlite3.Error as error:
|
||||||
|
print(f"Ошибка при добавлении поста: {error}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_scheduled_posts(self, chat_id=None, limit=50):
|
||||||
|
"""Получение списка запланированных постов"""
|
||||||
|
try:
|
||||||
|
cursor = self.connection.cursor()
|
||||||
|
if chat_id:
|
||||||
|
cursor.execute("""
|
||||||
|
SELECT sp.post_id, sp.chat_id, sp.text, sp.image_path, sp.schedule_time,
|
||||||
|
sp.created_by, sp.status, sp.published_at, sp.created_at, u.first_name
|
||||||
|
FROM scheduled_posts sp
|
||||||
|
LEFT JOIN users u ON sp.created_by = u.user_id
|
||||||
|
WHERE sp.chat_id = ? AND sp.status = 'scheduled'
|
||||||
|
ORDER BY sp.schedule_time ASC
|
||||||
|
LIMIT ?
|
||||||
|
""", (chat_id, limit))
|
||||||
|
else:
|
||||||
|
cursor.execute("""
|
||||||
|
SELECT sp.post_id, sp.chat_id, sp.text, sp.image_path, sp.schedule_time,
|
||||||
|
sp.created_by, sp.status, sp.published_at, sp.created_at, u.first_name
|
||||||
|
FROM scheduled_posts sp
|
||||||
|
LEFT JOIN users u ON sp.created_by = u.user_id
|
||||||
|
WHERE sp.status = 'scheduled'
|
||||||
|
ORDER BY sp.schedule_time ASC
|
||||||
|
LIMIT ?
|
||||||
|
""", (limit,))
|
||||||
|
|
||||||
|
return cursor.fetchall()
|
||||||
|
except sqlite3.Error as error:
|
||||||
|
print(f"Ошибка при получении постов: {error}")
|
||||||
|
return []
|
||||||
|
|
||||||
|
def get_pending_posts(self):
|
||||||
|
"""Получение постов, готовых к публикации"""
|
||||||
|
try:
|
||||||
|
cursor = self.connection.cursor()
|
||||||
|
current_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||||
|
cursor.execute("""
|
||||||
|
SELECT post_id, chat_id, text, image_path
|
||||||
|
FROM scheduled_posts
|
||||||
|
WHERE schedule_time <= ? AND status = 'scheduled'
|
||||||
|
ORDER BY schedule_time ASC
|
||||||
|
""", (current_time,))
|
||||||
|
|
||||||
|
return cursor.fetchall()
|
||||||
|
except sqlite3.Error as error:
|
||||||
|
print(f"Ошибка при получении ожидающих постов: {error}")
|
||||||
|
return []
|
||||||
|
|
||||||
|
def mark_post_published(self, post_id):
|
||||||
|
"""Отметить пост как опубликованный"""
|
||||||
|
try:
|
||||||
|
cursor = self.connection.cursor()
|
||||||
|
current_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||||
|
cursor.execute("""
|
||||||
|
UPDATE scheduled_posts
|
||||||
|
SET status = 'published', published_at = ?
|
||||||
|
WHERE post_id = ?
|
||||||
|
""", (current_time, post_id))
|
||||||
|
self.connection.commit()
|
||||||
|
return True
|
||||||
|
except sqlite3.Error as error:
|
||||||
|
print(f"Ошибка при обновлении статуса поста: {error}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def delete_scheduled_post(self, post_id, user_id):
|
||||||
|
"""Удалить запланированный пост (только создатель или админ)"""
|
||||||
|
try:
|
||||||
|
cursor = self.connection.cursor()
|
||||||
|
cursor.execute("""
|
||||||
|
DELETE FROM scheduled_posts
|
||||||
|
WHERE post_id = ? AND (created_by = ? OR ? IN (
|
||||||
|
SELECT user_id FROM users WHERE role = 'admin'
|
||||||
|
))
|
||||||
|
""", (post_id, user_id, user_id))
|
||||||
|
self.connection.commit()
|
||||||
|
return cursor.rowcount > 0
|
||||||
|
except sqlite3.Error as error:
|
||||||
|
print(f"Ошибка при удалении поста: {error}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
"""Закрытие соединения с базой данных"""
|
||||||
|
if self.connection:
|
||||||
|
self.connection.close()
|
||||||
|
print("Соединение с SQLite закрыто")
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
Миграция рангов пользователей к новой военной системе
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sqlite3
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
|
||||||
|
# Добавляем текущую директорию в путь для импорта
|
||||||
|
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
|
||||||
|
from database_sqlite import Database
|
||||||
|
|
||||||
|
def migrate_ranks():
|
||||||
|
"""Миграция рангов пользователей к новой системе"""
|
||||||
|
db = Database()
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Получаем всех пользователей
|
||||||
|
cursor = db.connection.cursor()
|
||||||
|
cursor.execute("SELECT user_id, reputation, rank FROM users")
|
||||||
|
users = cursor.fetchall()
|
||||||
|
|
||||||
|
print(f"Найдено {len(users)} пользователей для миграции")
|
||||||
|
|
||||||
|
migrated_count = 0
|
||||||
|
|
||||||
|
for user_id, reputation, old_rank in users:
|
||||||
|
# Рассчитываем новый ранг по новой системе
|
||||||
|
new_rank = db.calculate_rank(reputation)
|
||||||
|
|
||||||
|
# Обновляем ранг, если он изменился
|
||||||
|
if new_rank != old_rank:
|
||||||
|
cursor.execute("UPDATE users SET rank = ? WHERE user_id = ?", (new_rank, user_id))
|
||||||
|
print(f"Пользователь {user_id}: '{old_rank}' -> '{new_rank}' (репутация: {reputation})")
|
||||||
|
migrated_count += 1
|
||||||
|
|
||||||
|
db.connection.commit()
|
||||||
|
print(f"\nМиграция завершена! Обновлено рангов: {migrated_count}")
|
||||||
|
|
||||||
|
except sqlite3.Error as error:
|
||||||
|
print(f"Ошибка при миграции: {error}")
|
||||||
|
finally:
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
print("Запуск миграции рангов к военной системе...")
|
||||||
|
migrate_ranks()
|
||||||
|
print("Миграция завершена!")
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
python-telegram-bot==20.7
|
||||||
|
requests==2.31.0
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
Планировщик публикации постов по расписанию
|
||||||
|
"""
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import logging
|
||||||
|
import time
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from datetime import datetime
|
||||||
|
from telegram import Bot
|
||||||
|
from database_sqlite import Database
|
||||||
|
from config import BOT_TOKEN
|
||||||
|
|
||||||
|
# Настройка логирования
|
||||||
|
logging.basicConfig(
|
||||||
|
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
||||||
|
level=logging.INFO
|
||||||
|
)
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
class PostScheduler:
|
||||||
|
def __init__(self):
|
||||||
|
self.db = Database()
|
||||||
|
self.bot = Bot(token=BOT_TOKEN)
|
||||||
|
self.running = False
|
||||||
|
|
||||||
|
async def start(self):
|
||||||
|
"""Запуск планировщика"""
|
||||||
|
self.running = True
|
||||||
|
logger.info("Планировщик постов запущен")
|
||||||
|
|
||||||
|
while self.running:
|
||||||
|
try:
|
||||||
|
await self.check_and_publish_posts()
|
||||||
|
await asyncio.sleep(30) # Проверка каждые 30 секунд
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Ошибка в планировщике: {e}")
|
||||||
|
await asyncio.sleep(60) # При ошибке ждем минуту
|
||||||
|
|
||||||
|
async def stop(self):
|
||||||
|
"""Остановка планировщика"""
|
||||||
|
self.running = False
|
||||||
|
logger.info("Планировщик постов остановлен")
|
||||||
|
|
||||||
|
async def check_and_publish_posts(self):
|
||||||
|
"""Проверка и публикация постов по расписанию"""
|
||||||
|
try:
|
||||||
|
# Получаем посты, готовые к публикации
|
||||||
|
pending_posts = self.db.get_pending_posts()
|
||||||
|
|
||||||
|
for post in pending_posts:
|
||||||
|
post_id, chat_id, text, image_path = post
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Публикуем пост
|
||||||
|
if image_path and os.path.exists(image_path):
|
||||||
|
with open(image_path, 'rb') as photo:
|
||||||
|
await self.bot.send_photo(chat_id, photo, caption=text)
|
||||||
|
else:
|
||||||
|
await self.bot.send_message(chat_id, text)
|
||||||
|
|
||||||
|
# Отмечаем пост как опубликованный
|
||||||
|
self.db.mark_post_published(post_id)
|
||||||
|
|
||||||
|
logger.info(f"Пост {post_id} опубликован в чат {chat_id}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Ошибка при публикации поста {post_id}: {e}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Ошибка при проверке постов: {e}")
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
"""Главная функция"""
|
||||||
|
scheduler = PostScheduler()
|
||||||
|
|
||||||
|
try:
|
||||||
|
await scheduler.start()
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
logger.info("Получен сигнал остановки")
|
||||||
|
finally:
|
||||||
|
await scheduler.stop()
|
||||||
|
scheduler.db.close()
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
asyncio.run(main())
|
||||||
Binary file not shown.
Reference in New Issue
Block a user