mirror of
https://github.com/FerraSoft/bottohelp.git
synced 2026-08-06 21:55:03 +00:00
943 lines
43 KiB
Python
943 lines
43 KiB
Python
import sqlite3
|
|
import os
|
|
from datetime import datetime
|
|
import csv
|
|
from messages import TECH_MESSAGES
|
|
|
|
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(TECH_MESSAGES['db_connected'])
|
|
except sqlite3.Error as error:
|
|
print(TECH_MESSAGES['db_connection_error'].format(error=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)
|
|
)
|
|
""")
|
|
|
|
# Таблица достижений
|
|
cursor.execute("""
|
|
CREATE TABLE IF NOT EXISTS achievements (
|
|
achievement_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
user_id INTEGER,
|
|
achievement_type TEXT NOT NULL,
|
|
unlocked_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
FOREIGN KEY (user_id) REFERENCES users (user_id)
|
|
)
|
|
""")
|
|
|
|
# Таблица донатов
|
|
cursor.execute("""
|
|
CREATE TABLE IF NOT EXISTS donations (
|
|
donation_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
user_id INTEGER,
|
|
amount REAL NOT NULL,
|
|
currency TEXT DEFAULT 'RUB',
|
|
donated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
FOREIGN KEY (user_id) REFERENCES users (user_id)
|
|
)
|
|
""")
|
|
|
|
# Таблица ошибок и отчетов
|
|
cursor.execute("""
|
|
CREATE TABLE IF NOT EXISTS errors (
|
|
error_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
admin_id INTEGER,
|
|
error_type TEXT NOT NULL,
|
|
title TEXT NOT NULL,
|
|
description TEXT NOT NULL,
|
|
status TEXT DEFAULT 'new',
|
|
priority TEXT DEFAULT 'medium',
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
ai_analysis TEXT,
|
|
todo_added BOOLEAN DEFAULT 0,
|
|
resolved_at TIMESTAMP,
|
|
FOREIGN KEY (admin_id) REFERENCES users (user_id)
|
|
)
|
|
""")
|
|
|
|
self.connection.commit()
|
|
print(TECH_MESSAGES['db_tables_created'])
|
|
except sqlite3.Error as error:
|
|
print(TECH_MESSAGES['db_tables_error'].format(error=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(TECH_MESSAGES['user_added_error'].format(error=error))
|
|
|
|
def update_score(self, user_id, points=1):
|
|
"""Обновление очков пользователя"""
|
|
try:
|
|
# Валидация входных данных
|
|
if not isinstance(user_id, int) or user_id <= 0:
|
|
print(f"Ошибка валидации: некорректный user_id = {user_id}")
|
|
return False
|
|
|
|
if not isinstance(points, (int, float)) or points < 0:
|
|
print(f"Ошибка валидации: некорректное количество очков = {points}")
|
|
return False
|
|
|
|
# Проверка соединения с базой данных
|
|
if not self.connection:
|
|
print("Ошибка: соединение с базой данных не установлено")
|
|
return False
|
|
|
|
cursor = self.connection.cursor()
|
|
cursor.execute("""
|
|
UPDATE users SET score = score + ?, reputation = reputation + ?, message_count = message_count + 1, last_message = CURRENT_TIMESTAMP WHERE user_id = ?
|
|
""", (points, points, user_id))
|
|
|
|
if cursor.rowcount == 0:
|
|
print(f"Предупреждение: пользователь с ID {user_id} не найден для обновления очков")
|
|
return False
|
|
|
|
self.connection.commit()
|
|
# Обновление ранга на основе репутации (которая теперь синхронизирована с очками)
|
|
self.update_rank(user_id)
|
|
return True
|
|
except sqlite3.Error as error:
|
|
print(f"Ошибка базы данных при обновлении очков: {error}")
|
|
if self.connection:
|
|
self.connection.rollback()
|
|
return False
|
|
except Exception as error:
|
|
print(f"Неожиданная ошибка при обновлении очков: {error}")
|
|
return False
|
|
|
|
def update_reputation(self, user_id, rep_points=1):
|
|
"""Обновление репутации пользователя"""
|
|
try:
|
|
# Валидация входных данных
|
|
if not isinstance(user_id, int) or user_id <= 0:
|
|
print(f"Ошибка валидации: некорректный user_id = {user_id}")
|
|
return False
|
|
|
|
if not isinstance(rep_points, (int, float)):
|
|
print(f"Ошибка валидации: некорректное количество репутации = {rep_points}")
|
|
return False
|
|
|
|
# Проверка соединения с базой данных
|
|
if not self.connection:
|
|
print("Ошибка: соединение с базой данных не установлено")
|
|
return False
|
|
|
|
cursor = self.connection.cursor()
|
|
cursor.execute("""
|
|
UPDATE users SET reputation = reputation + ? WHERE user_id = ?
|
|
""", (rep_points, user_id))
|
|
|
|
if cursor.rowcount == 0:
|
|
print(f"Предупреждение: пользователь с ID {user_id} не найден для обновления репутации")
|
|
return False
|
|
|
|
self.connection.commit()
|
|
# Обновление ранга на основе репутации (теперь вызывается только здесь)
|
|
self.update_rank(user_id)
|
|
return True
|
|
except sqlite3.Error as error:
|
|
print(f"Ошибка базы данных при обновлении репутации: {error}")
|
|
if self.connection:
|
|
self.connection.rollback()
|
|
return False
|
|
except Exception as error:
|
|
print(f"Неожиданная ошибка при обновлении репутации: {error}")
|
|
return False
|
|
|
|
def update_rank(self, user_id, chat_id=None, first_name=None):
|
|
"""Обновление ранга пользователя на основе очков (score)"""
|
|
try:
|
|
cursor = self.connection.cursor()
|
|
cursor.execute("SELECT score, rank FROM users WHERE user_id = ?", (user_id,))
|
|
result = cursor.fetchone()
|
|
if result:
|
|
score = result[0]
|
|
old_rank = result[1]
|
|
new_rank = self.calculate_rank(score)
|
|
|
|
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(TECH_MESSAGES['rank_update_error'].format(error=error))
|
|
return None
|
|
|
|
def calculate_rank(self, reputation):
|
|
"""Расчет ранга на основе репутации"""
|
|
# Используем константы из messages.py
|
|
from messages import RANK_THRESHOLDS
|
|
|
|
for threshold, rank_name in reversed(RANK_THRESHOLDS):
|
|
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(TECH_MESSAGES['top_users_error'].format(error=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(TECH_MESSAGES['warning_add_error'].format(error=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(TECH_MESSAGES['warnings_get_error'].format(error=error))
|
|
return 0
|
|
|
|
def get_user_info(self, user_id):
|
|
"""Получение информации о пользователе"""
|
|
try:
|
|
# Валидация входных данных
|
|
if not isinstance(user_id, int) or user_id <= 0:
|
|
print(f"Ошибка валидации: некорректный user_id = {user_id}")
|
|
return None
|
|
|
|
# Проверка соединения с базой данных
|
|
if not self.connection:
|
|
print("Ошибка: соединение с базой данных не установлено")
|
|
return None
|
|
|
|
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:
|
|
try:
|
|
return {
|
|
'ID': result[0],
|
|
'Имя': result[1] or 'Неизвестно',
|
|
'Имя пользователя': result[2] or 'не указано',
|
|
'Репутация': result[3] or 0,
|
|
'Ранг': result[4] or 'Рядовой',
|
|
'Количество сообщений': result[5] or 0,
|
|
'Активные дни': result[6] or 0,
|
|
'Дней с присоединения': result[7] or 0,
|
|
'Последнее сообщение': result[8],
|
|
'Присоединился': result[9],
|
|
'Покинул': result[10],
|
|
'Язык': result[11] or 'ru',
|
|
'Действия': result[12] or '[]',
|
|
'Очки': result[13] or 0,
|
|
'Предупреждений': result[14] or 0,
|
|
'Роль': result[15] or 'user'
|
|
}
|
|
except (IndexError, TypeError) as e:
|
|
print(f"Ошибка обработки данных пользователя {user_id}: {e}")
|
|
return None
|
|
|
|
return None
|
|
except sqlite3.Error as error:
|
|
print(f"Ошибка базы данных при получении информации о пользователе {user_id}: {error}")
|
|
return None
|
|
except Exception as error:
|
|
print(f"Неожиданная ошибка при получении информации о пользователе {user_id}: {error}")
|
|
return None
|
|
|
|
def import_users_from_csv(self, csv_file_path):
|
|
"""Импорт пользователей из CSV файла"""
|
|
try:
|
|
if not os.path.exists(csv_file_path):
|
|
print(TECH_MESSAGES['csv_file_not_found'].format(file=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 = ?, rank = ?
|
|
WHERE user_id = ?
|
|
""", (username, first_name, last_name, xp, rep, xp, db.calculate_rank(xp), user_id))
|
|
updated_count += 1
|
|
else:
|
|
# Добавляем нового пользователя
|
|
cursor.execute("""
|
|
INSERT INTO users (user_id, username, first_name, last_name, score, warnings, reputation, rank)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
""", (user_id, username, first_name, last_name, xp, rep, xp, db.calculate_rank(xp)))
|
|
imported_count += 1
|
|
|
|
self.connection.commit()
|
|
|
|
except (ValueError, KeyError) as e:
|
|
print(TECH_MESSAGES['csv_row_error'].format(row=row, error=e))
|
|
continue
|
|
|
|
print(f"Импорт завершен. Добавлено: {imported_count}, Обновлено: {updated_count}")
|
|
return True
|
|
|
|
except sqlite3.Error as error:
|
|
print(TECH_MESSAGES['csv_import_error'].format(error=error))
|
|
return False
|
|
|
|
def add_scheduled_post(self, chat_id, text, schedule_time, created_by, image_path=None):
|
|
"""Добавление запланированного поста"""
|
|
try:
|
|
# Валидация входных данных
|
|
if not isinstance(chat_id, int):
|
|
print(f"Ошибка валидации: некорректный chat_id = {chat_id}")
|
|
return None
|
|
|
|
if not text or not text.strip():
|
|
print("Ошибка валидации: текст поста пустой")
|
|
return None
|
|
|
|
if not isinstance(created_by, int) or created_by <= 0:
|
|
print(f"Ошибка валидации: некорректный created_by = {created_by}")
|
|
return None
|
|
|
|
# Ограничение длины текста
|
|
if len(text) > 4000:
|
|
print("Ошибка валидации: текст поста слишком длинный")
|
|
return None
|
|
|
|
# Валидация пути к изображению
|
|
if image_path and not isinstance(image_path, str):
|
|
print(f"Ошибка валидации: некорректный путь к изображению = {image_path}")
|
|
return None
|
|
|
|
# Проверка соединения с базой данных
|
|
if not self.connection:
|
|
print("Ошибка: соединение с базой данных не установлено")
|
|
return None
|
|
|
|
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}")
|
|
if self.connection:
|
|
self.connection.rollback()
|
|
return None
|
|
except Exception 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 unlock_achievement(self, user_id, achievement_type):
|
|
"""Разблокировать достижение для пользователя"""
|
|
try:
|
|
cursor = self.connection.cursor()
|
|
cursor.execute("""
|
|
INSERT OR IGNORE INTO achievements (user_id, achievement_type)
|
|
VALUES (?, ?)
|
|
""", (user_id, achievement_type))
|
|
self.connection.commit()
|
|
return cursor.rowcount > 0 # True если достижение было разблокировано впервые
|
|
except sqlite3.Error as error:
|
|
print(f"Ошибка при разблокировке достижения: {error}")
|
|
return False
|
|
|
|
def get_user_achievements(self, user_id):
|
|
"""Получить достижения пользователя"""
|
|
try:
|
|
cursor = self.connection.cursor()
|
|
cursor.execute("""
|
|
SELECT achievement_type, unlocked_at
|
|
FROM achievements
|
|
WHERE user_id = ?
|
|
ORDER BY unlocked_at DESC
|
|
""", (user_id,))
|
|
return cursor.fetchall()
|
|
except sqlite3.Error as error:
|
|
print(f"Ошибка при получении достижений: {error}")
|
|
return []
|
|
|
|
def add_donation(self, user_id, amount, currency='RUB'):
|
|
"""Добавить донат пользователя"""
|
|
try:
|
|
# Валидация входных данных
|
|
if not isinstance(user_id, int) or user_id <= 0:
|
|
print(f"Ошибка валидации: некорректный user_id = {user_id}")
|
|
return False
|
|
|
|
if not isinstance(amount, (int, float)) or amount <= 0:
|
|
print(f"Ошибка валидации: некорректная сумма доната = {amount}")
|
|
return False
|
|
|
|
if not isinstance(currency, str) or not currency.strip():
|
|
print(f"Ошибка валидации: некорректная валюта = {currency}")
|
|
return False
|
|
|
|
# Ограничение максимальной суммы доната для предотвращения злоупотреблений
|
|
if amount > 1000000: # 1 миллион рублей максимум
|
|
print(f"Ошибка валидации: сумма доната слишком большая = {amount}")
|
|
return False
|
|
|
|
# Проверка соединения с базой данных
|
|
if not self.connection:
|
|
print("Ошибка: соединение с базой данных не установлено")
|
|
return False
|
|
|
|
cursor = self.connection.cursor()
|
|
|
|
# Вставляем запись о донате
|
|
cursor.execute("""
|
|
INSERT INTO donations (user_id, amount, currency)
|
|
VALUES (?, ?, ?)
|
|
""", (user_id, amount, currency))
|
|
|
|
# Начисляем очки за донат (1 очко за каждые 100 рублей)
|
|
points = int(amount // 100)
|
|
if points > 0:
|
|
# Обновляем score и reputation одновременно, и вызываем update_rank только один раз
|
|
cursor.execute("""
|
|
UPDATE users SET score = score + ?, reputation = reputation + ?, last_message = CURRENT_TIMESTAMP WHERE user_id = ?
|
|
""", (points, points, user_id))
|
|
|
|
if cursor.rowcount == 0:
|
|
print(f"Предупреждение: пользователь с ID {user_id} не найден при начислении очков за донат")
|
|
self.connection.rollback()
|
|
return False
|
|
|
|
self.update_rank(user_id)
|
|
|
|
self.connection.commit()
|
|
return True
|
|
except sqlite3.Error as error:
|
|
print(f"Ошибка базы данных при добавлении доната: {error}")
|
|
if self.connection:
|
|
self.connection.rollback()
|
|
return False
|
|
except Exception as error:
|
|
print(f"Неожиданная ошибка при добавлении доната: {error}")
|
|
return False
|
|
|
|
def get_total_donations(self, user_id, year=None):
|
|
"""Получить общую сумму донатов пользователя"""
|
|
try:
|
|
cursor = self.connection.cursor()
|
|
if year:
|
|
cursor.execute("""
|
|
SELECT SUM(amount) FROM donations
|
|
WHERE user_id = ? AND strftime('%Y', donated_at) = ?
|
|
""", (user_id, str(year)))
|
|
else:
|
|
cursor.execute("""
|
|
SELECT SUM(amount) FROM donations
|
|
WHERE user_id = ?
|
|
""", (user_id,))
|
|
result = cursor.fetchone()
|
|
return result[0] if result[0] else 0
|
|
except sqlite3.Error as error:
|
|
print(f"Ошибка при получении донатов: {error}")
|
|
return 0
|
|
|
|
def check_and_unlock_achievements(self, user_id, message_count=None, games_won=None, donations=None):
|
|
"""Проверяет и разблокирует достижения"""
|
|
achievements_unlocked = []
|
|
|
|
# Достижения за сообщения
|
|
if message_count is None:
|
|
user_info = self.get_user_info(user_id)
|
|
if user_info:
|
|
message_count = user_info['Количество сообщений']
|
|
|
|
if message_count >= 1:
|
|
# Проверяем, было ли уже разблокировано достижение первого сообщения
|
|
existing_achievements = self.get_user_achievements(user_id)
|
|
has_first_message = any(achievement[0] == 'first_message' for achievement in existing_achievements)
|
|
|
|
if not has_first_message:
|
|
if self.unlock_achievement(user_id, 'first_message'):
|
|
achievements_unlocked.append('first_message')
|
|
if message_count >= 100:
|
|
existing_achievements = self.get_user_achievements(user_id)
|
|
has_100_messages = any(achievement[0] == '100_messages' for achievement in existing_achievements)
|
|
if not has_100_messages:
|
|
if self.unlock_achievement(user_id, '100_messages'):
|
|
achievements_unlocked.append('100_messages')
|
|
|
|
if message_count >= 1000:
|
|
existing_achievements = self.get_user_achievements(user_id)
|
|
has_1000_messages = any(achievement[0] == '1000_messages' for achievement in existing_achievements)
|
|
if not has_1000_messages:
|
|
if self.unlock_achievement(user_id, '1000_messages'):
|
|
achievements_unlocked.append('1000_messages')
|
|
|
|
# Достижения за игры
|
|
if games_won is None:
|
|
# Здесь можно добавить логику подсчета побед в играх
|
|
pass
|
|
|
|
if games_won and games_won >= 10:
|
|
existing_achievements = self.get_user_achievements(user_id)
|
|
has_games_master = any(achievement[0] == 'games_master' for achievement in existing_achievements)
|
|
if not has_games_master:
|
|
if self.unlock_achievement(user_id, 'games_master'):
|
|
achievements_unlocked.append('games_master')
|
|
|
|
# Достижения за донаты
|
|
if donations is None:
|
|
current_year = datetime.now().year
|
|
donations = self.get_total_donations(user_id, current_year)
|
|
|
|
if donations >= 100000: # 100k рублей в год
|
|
existing_achievements = self.get_user_achievements(user_id)
|
|
has_sponsor_year = any(achievement[0] == 'sponsor_year' for achievement in existing_achievements)
|
|
if not has_sponsor_year:
|
|
if self.unlock_achievement(user_id, 'sponsor_year'):
|
|
achievements_unlocked.append('sponsor_year')
|
|
|
|
return achievements_unlocked
|
|
|
|
def add_error(self, admin_id, error_type, title, description, priority='medium'):
|
|
"""Добавление новой ошибки в систему"""
|
|
try:
|
|
# Валидация входных данных
|
|
if not isinstance(admin_id, int) or admin_id <= 0:
|
|
print(f"Ошибка валидации: некорректный admin_id = {admin_id}")
|
|
return None
|
|
|
|
if not error_type or not isinstance(error_type, str):
|
|
print(f"Ошибка валидации: некорректный тип ошибки = {error_type}")
|
|
return None
|
|
|
|
if not title or not isinstance(title, str) or len(title.strip()) == 0:
|
|
print("Ошибка валидации: заголовок ошибки пустой")
|
|
return None
|
|
|
|
if not description or not isinstance(description, str) or len(description.strip()) == 0:
|
|
print("Ошибка валидации: описание ошибки пустое")
|
|
return None
|
|
|
|
valid_priorities = ['low', 'medium', 'high', 'critical']
|
|
if priority not in valid_priorities:
|
|
print(f"Ошибка валидации: некорректный приоритет = {priority}")
|
|
return None
|
|
|
|
# Проверка соединения с базой данных
|
|
if not self.connection:
|
|
print("Ошибка: соединение с базой данных не установлено")
|
|
return None
|
|
|
|
cursor = self.connection.cursor()
|
|
current_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
|
|
|
cursor.execute("""
|
|
INSERT INTO errors (admin_id, error_type, title, description, priority, created_at, updated_at)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
""", (admin_id, error_type, title.strip(), description.strip(), priority, current_time, current_time))
|
|
|
|
self.connection.commit()
|
|
return cursor.lastrowid
|
|
except sqlite3.Error as error:
|
|
print(f"Ошибка базы данных при добавлении ошибки: {error}")
|
|
if self.connection:
|
|
self.connection.rollback()
|
|
return None
|
|
except Exception as error:
|
|
print(f"Неожиданная ошибка при добавлении ошибки: {error}")
|
|
return None
|
|
|
|
def get_errors(self, status=None, limit=50):
|
|
"""Получение списка ошибок с фильтрацией по статусу"""
|
|
try:
|
|
cursor = self.connection.cursor()
|
|
if status:
|
|
cursor.execute("""
|
|
SELECT e.error_id, e.admin_id, e.error_type, e.title, e.description,
|
|
e.status, e.priority, e.created_at, e.updated_at, e.ai_analysis,
|
|
e.todo_added, e.resolved_at, u.first_name, u.username
|
|
FROM errors e
|
|
LEFT JOIN users u ON e.admin_id = u.user_id
|
|
WHERE e.status = ?
|
|
ORDER BY
|
|
CASE e.priority
|
|
WHEN 'critical' THEN 1
|
|
WHEN 'high' THEN 2
|
|
WHEN 'medium' THEN 3
|
|
WHEN 'low' THEN 4
|
|
END,
|
|
e.created_at DESC
|
|
LIMIT ?
|
|
""", (status, limit))
|
|
else:
|
|
cursor.execute("""
|
|
SELECT e.error_id, e.admin_id, e.error_type, e.title, e.description,
|
|
e.status, e.priority, e.created_at, e.updated_at, e.ai_analysis,
|
|
e.todo_added, e.resolved_at, u.first_name, u.username
|
|
FROM errors e
|
|
LEFT JOIN users u ON e.admin_id = u.user_id
|
|
ORDER BY
|
|
CASE e.priority
|
|
WHEN 'critical' THEN 1
|
|
WHEN 'high' THEN 2
|
|
WHEN 'medium' THEN 3
|
|
WHEN 'low' THEN 4
|
|
END,
|
|
e.created_at DESC
|
|
LIMIT ?
|
|
""", (limit,))
|
|
|
|
return cursor.fetchall()
|
|
except sqlite3.Error as error:
|
|
print(f"Ошибка при получении ошибок: {error}")
|
|
return []
|
|
|
|
def update_error_status(self, error_id, status, admin_id=None):
|
|
"""Обновление статуса ошибки"""
|
|
try:
|
|
# Валидация входных данных
|
|
if not isinstance(error_id, int) or error_id <= 0:
|
|
print(f"Ошибка валидации: некорректный error_id = {error_id}")
|
|
return False
|
|
|
|
valid_statuses = ['new', 'in_progress', 'resolved', 'rejected']
|
|
if status not in valid_statuses:
|
|
print(f"Ошибка валидации: некорректный статус = {status}")
|
|
return False
|
|
|
|
# Проверка соединения с базой данных
|
|
if not self.connection:
|
|
print("Ошибка: соединение с базой данных не установлено")
|
|
return False
|
|
|
|
cursor = self.connection.cursor()
|
|
current_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
|
|
|
if status == 'resolved':
|
|
cursor.execute("""
|
|
UPDATE errors
|
|
SET status = ?, updated_at = ?, resolved_at = ?
|
|
WHERE error_id = ?
|
|
""", (status, current_time, current_time, error_id))
|
|
else:
|
|
cursor.execute("""
|
|
UPDATE errors
|
|
SET status = ?, updated_at = ?
|
|
WHERE error_id = ?
|
|
""", (status, current_time, error_id))
|
|
|
|
self.connection.commit()
|
|
return cursor.rowcount > 0
|
|
except sqlite3.Error as error:
|
|
print(f"Ошибка базы данных при обновлении статуса ошибки: {error}")
|
|
if self.connection:
|
|
self.connection.rollback()
|
|
return False
|
|
|
|
def update_error_ai_analysis(self, error_id, ai_analysis):
|
|
"""Обновление анализа ИИ для ошибки"""
|
|
try:
|
|
# Валидация входных данных
|
|
if not isinstance(error_id, int) or error_id <= 0:
|
|
print(f"Ошибка валидации: некорректный error_id = {error_id}")
|
|
return False
|
|
|
|
if not ai_analysis or not isinstance(ai_analysis, str):
|
|
print(f"Ошибка валидации: некорректный анализ ИИ = {ai_analysis}")
|
|
return False
|
|
|
|
# Проверка соединения с базой данных
|
|
if not self.connection:
|
|
print("Ошибка: соединение с базой данных не установлено")
|
|
return False
|
|
|
|
cursor = self.connection.cursor()
|
|
current_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
|
|
|
cursor.execute("""
|
|
UPDATE errors
|
|
SET ai_analysis = ?, updated_at = ?
|
|
WHERE error_id = ?
|
|
""", (ai_analysis, current_time, error_id))
|
|
|
|
self.connection.commit()
|
|
return cursor.rowcount > 0
|
|
except sqlite3.Error as error:
|
|
print(f"Ошибка базы данных при обновлении анализа ИИ: {error}")
|
|
if self.connection:
|
|
self.connection.rollback()
|
|
return False
|
|
|
|
def mark_error_todo_added(self, error_id):
|
|
"""Отметить ошибку как добавленную в TODO список"""
|
|
try:
|
|
# Валидация входных данных
|
|
if not isinstance(error_id, int) or error_id <= 0:
|
|
print(f"Ошибка валидации: некорректный error_id = {error_id}")
|
|
return False
|
|
|
|
# Проверка соединения с базой данных
|
|
if not self.connection:
|
|
print("Ошибка: соединение с базой данных не установлено")
|
|
return False
|
|
|
|
cursor = self.connection.cursor()
|
|
current_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
|
|
|
cursor.execute("""
|
|
UPDATE errors
|
|
SET todo_added = 1, updated_at = ?
|
|
WHERE error_id = ?
|
|
""", (current_time, error_id))
|
|
|
|
self.connection.commit()
|
|
return cursor.rowcount > 0
|
|
except sqlite3.Error as error:
|
|
print(f"Ошибка базы данных при отметке ошибки в TODO: {error}")
|
|
if self.connection:
|
|
self.connection.rollback()
|
|
return False
|
|
|
|
def get_error_by_id(self, error_id):
|
|
"""Получение конкретной ошибки по ID"""
|
|
try:
|
|
# Валидация входных данных
|
|
if not isinstance(error_id, int) or error_id <= 0:
|
|
print(f"Ошибка валидации: некорректный error_id = {error_id}")
|
|
return None
|
|
|
|
cursor = self.connection.cursor()
|
|
cursor.execute("""
|
|
SELECT e.error_id, e.admin_id, e.error_type, e.title, e.description,
|
|
e.status, e.priority, e.created_at, e.updated_at, e.ai_analysis,
|
|
e.todo_added, e.resolved_at, u.first_name, u.username
|
|
FROM errors e
|
|
LEFT JOIN users u ON e.admin_id = u.user_id
|
|
WHERE e.error_id = ?
|
|
""", (error_id,))
|
|
|
|
result = cursor.fetchone()
|
|
return result
|
|
except sqlite3.Error as error:
|
|
print(f"Ошибка при получении ошибки: {error}")
|
|
return None
|
|
|
|
def close(self):
|
|
"""Закрытие соединения с базой данных"""
|
|
if self.connection:
|
|
self.connection.close()
|
|
print(TECH_MESSAGES['db_closed']) |