feat: добавлен планировщик постов по расписанию

This commit is contained in:
2025-10-14 23:56:28 +03:00
commit c14d53b3db
13 changed files with 6021 additions and 0 deletions
+169
View File
@@ -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 для подробностей.
+107
View File
@@ -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.
+1272
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+6
View File
@@ -0,0 +1,6 @@
# Конфигурация бота
BOT_TOKEN = "5601825044:AAHr7-VMgD34vPsyZ03QTRWSN73D-YPGQcE" # Токен от @BotFather
# API ключи для внешних сервисов (опционально)
OPENWEATHER_API_KEY = "ВСТАВЬТЕ_ВАШ_API_КЛЮЧ_ЗДЕСЬ" # Для погоды
NEWS_API_KEY = "ВСТАВЬТЕ_ВАШ_API_КЛЮЧ_ЗДЕСЬ" # Для новостей
+209
View File
@@ -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 закрыто")
+422
View File
@@ -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 закрыто")
+51
View File
@@ -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("Миграция завершена!")
+2
View File
@@ -0,0 +1,2 @@
python-telegram-bot==20.7
requests==2.31.0
+89
View File
@@ -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())
BIN
View File
Binary file not shown.