Version 1.3.0 - Bug fixes and donation system improvements

This commit is contained in:
2025-10-16 15:17:58 +03:00
parent 653cda9430
commit de13bc3e31
8 changed files with 1196 additions and 19 deletions
+51 -4
View File
@@ -2,17 +2,45 @@
Многофункциональный Telegram бот на Python с системой ранжирования пользователей, играми и интеграцией с внешними API. Многофункциональный Telegram бот на Python с системой ранжирования пользователей, играми и интеграцией с внешними API.
## 📋 История версий
### Версия 1.3.0 (16 октября 2025)
**Дата релиза:** 16.10.2025
#### 🐛 Исправления ошибок
- ✅ Исправлена ошибка с повторяющимся сообщением о "первом сообщении" при вводе суммы доната
- ✅ Исправлена проблема с кодировкой Unicode символов при запуске бота на Windows
- ✅ Исправлена система донатов - теперь корректно работает кнопка "Другая сумма"
#### ✨ Новые функции
- ✅ Добавлена команда `/donate` для поддержки проекта
- ✅ Добавлена информация о донатах в справку `/help`
- ✅ Улучшена система достижений - достижения разблокируются только один раз
#### 🔧 Технические улучшения
- ✅ Оптимизирована логика проверки достижений в базе данных
- ✅ Добавлена проверка существующих достижений перед разблокировкой
- ✅ Улучшена обработка ошибок в системе донатов
### Версия 1.2.0 (Предыдущая версия)
- 🚀 Добавлены расширенные игры: 2048, Тетрис, Змейка
- 🎯 Улучшена система достижений
- 📅 Расширенная система планировщика постов
## Функции ## Функции
### Основные возможности ### Основные возможности
- ✅ Отвечать на сообщения пользователей предопределенными ответами - ✅ Отвечать на сообщения пользователей предопределенными ответами
- ✅ Обрабатывать инлайновые запросы и команды - ✅ Обрабатывать инлайновые запросы и команды
- ✅ Система ранжирования пользователей за участие в чате (хранение в PostgreSQL) - ✅ Система ранжирования пользователей за участие в чате (хранение в SQLite)
- ✅ Приветствие новых пользователей при добавлении в группу - ✅ Приветствие новых пользователей при добавлении в группу с правилами группы
- ✅ Автоматическое удаление приветственных сообщений через 2 минуты
- ✅ Интеграция донатов в приветственные сообщения
### Игровые элементы ### Игровые элементы
- 🎮 Мини-игры: Угадай число, Камень-ножницы-бумага - 🎮 Мини-игры: Камень-ножницы-бумага, Крестики-нолики, Викторина, Морской бой
- 🧠 Викторина с вопросами - 🎯 Расширенные игры: 2048, Тетрис, Змейка
- 🧠 Система достижений и начисления очков
- Начисление дополнительных очков за участие в играх - Начисление дополнительных очков за участие в играх
### Администрирование чата ### Администрирование чата
@@ -33,6 +61,13 @@
- 📰 Последние новости (NewsAPI) - 📰 Последние новости (NewsAPI)
- 🌐 Перевод текста (Google Translate) - базовая реализация - 🌐 Перевод текста (Google Translate) - базовая реализация
### Система приветствий и донатов
- 🎉 Автоматическое приветствие новых пользователей с правилами группы
- ⏰ Автоматическое удаление приветственных сообщений через 2 минуты
- 👥 Обработка нескольких пользователей (удаление старого, создание нового приветствия)
- 💰 Интеграция кнопок донатов прямо в приветственные сообщения
- 🎯 Быстрый доступ к помощи через приветственные кнопки
## Установка и настройка ## Установка и настройка
### 1. Клонирование репозитория ### 1. Клонирование репозитория
@@ -86,6 +121,7 @@ python bot.py
#### Игры #### Игры
- `/play_game` - Запустить мини-игру - `/play_game` - Запустить мини-игру
- Доступные игры: Камень-ножницы-бумага, Крестики-нолики, Викторина, Морской бой, 2048, Тетрис, Змейка
#### Посты по расписанию (только админы) #### Посты по расписанию (только админы)
- `/schedule_post [время] [текст]` - Запланировать пост - `/schedule_post [время] [текст]` - Запланировать пост
@@ -108,6 +144,8 @@ python bot.py
- За выигрыш в мини-игре начисляются дополнительные очки - За выигрыш в мини-игре начисляются дополнительные очки
- Военная иерархия званий от Рядового до Маршала - Военная иерархия званий от Рядового до Маршала
- Рейтинг сохраняется в SQLite базе данных - Рейтинг сохраняется в SQLite базе данных
- Начисление очков за вступление в группу
- Система достижений с разблокировкой значков
### Посты по расписанию ### Посты по расписанию
- Планирование публикации постов в указанное время - Планирование публикации постов в указанное время
@@ -117,6 +155,12 @@ python bot.py
- Управление постами только администраторами чата - Управление постами только администраторами чата
- Все посты сохраняются в базе данных с историей публикаций - Все посты сохраняются в базе данных с историей публикаций
### Автоматическая система приветствий
- Приветствие новых пользователей с подробными правилами группы
- Автоматическое удаление приветствий через 2 минуты для чистоты чата
- Интеллектуальная обработка нескольких пользователей одновременно
- Интеграция донатов и быстрого доступа к помощи в приветственных сообщениях
#### Примеры использования планировщика: #### Примеры использования планировщика:
``` ```
/schedule_post +1h Добро пожаловать в наш чат! /schedule_post +1h Добро пожаловать в наш чат!
@@ -130,9 +174,12 @@ python bot.py
telegram_bot/ telegram_bot/
├── bot.py # Основной файл бота ├── bot.py # Основной файл бота
├── config.py # Конфигурация (токены, настройки БД) ├── config.py # Конфигурация (токены, настройки БД)
├── config_local.py # Локальная конфигурация с токенами
├── database_sqlite.py # Работа с SQLite базой данных ├── database_sqlite.py # Работа с SQLite базой данных
├── database.py # Базовые функции работы с БД
├── scheduler.py # Планировщик постов по расписанию ├── scheduler.py # Планировщик постов по расписанию
├── migrate_ranks.py # Миграция системы рангов ├── migrate_ranks.py # Миграция системы рангов
├── messages.py # Текстовые сообщения и константы бота
├── requirements.txt # Зависимости Python ├── requirements.txt # Зависимости Python
└── README.md # Эта документация └── README.md # Эта документация
``` ```
+92
View File
@@ -0,0 +1,92 @@
# 📋 TODO - Список задач разработки
## 🚀 Приоритетные задачи (High Priority)
### 🔧 Критические исправления
- [ ] Исправить обработку ошибок при недоступности внешних API (OpenWeatherMap, NewsAPI)
- [ ] Добавить валидацию входящих данных для всех команд
- [ ] Улучшить обработку исключений в базе данных
### ✨ Новые функции
- [ ] Добавить поддержку медиа файлов в планировщик постов (видео, документы)
- [ ] Реализовать многоязычность (i18n) для бота
- [ ] Добавить экспорт статистики в CSV/Excel формат
- [ ] Создать веб-интерфейс для управления ботом
## 🎯 Средний приоритет (Medium Priority)
### 🎮 Игры
- [ ] Добавить новые мини-игры (Шахматы, Судоку, Словесные игры)
- [ ] Улучшить ИИ для игр (особенно крестики-нолики)
- [ ] Добавить турнирную систему для игр
### 💰 Монетизация
- [ ] Интегрировать платежные системы (ЮMoney, QIWI, банковские карты)
- [ ] Добавить премиум функции для донатеров
- [ ] Создать систему подписок
### 📊 Аналитика
- [ ] Добавить подробную статистику использования команд
- [ ] Создать графики активности пользователей
- [ ] Добавить мониторинг производительности бота
## 🔮 Низкий приоритет (Low Priority)
### 🎨 Интерфейс
- [ ] Добавить кастомные клавиатуры для всех команд
- [ ] Улучшить форматирование сообщений (больше эмодзи, форматирование)
- [ ] Добавить интерактивные меню
### 🔒 Безопасность
- [ ] Добавить rate limiting для команд
- [ ] Реализовать CAPTCHA для новых пользователей
- [ ] Добавить аудит лог для действий админов
### 🔧 Техническое
- [ ] Миграция с SQLite на PostgreSQL для больших нагрузок
- [ ] Добавить Redis для кеширования
- [ ] Создать Docker контейнер для развертывания
## ✅ Завершенные задачи
### Версия 1.3.0 (16 октября 2025)
- [x] ✅ Исправлена ошибка с повторяющимся "первым сообщением" при донатах
- [x] ✅ Исправлена кодировка Unicode символов при запуске на Windows
- [x] ✅ Исправлена система донатов - кнопка "Другая сумма"
- [x] ✅ Добавлена команда /donate для поддержки проекта
- [x] ✅ Добавлена информация о донатах в справку /help
- [x] ✅ Улучшена система достижений (разблокировка только один раз)
- [x] ✅ Обновлена документация с историей версий
### Версия 1.2.0
- [x] ✅ Добавлены расширенные игры: 2048, Тетрис, Змейка
- [x] ✅ Улучшена система достижений
- [x] ✅ Расширенная система планировщика постов
## 📅 Планирование релизов
### Следующий релиз (v1.4.0) - Декабрь 2025
- Цель: Улучшение стабильности и производительности
- Основной фокус: Исправление ошибок и оптимизация
### Релиз Q1 2026 (v2.0.0) - Март 2026
- Цель: Значительное расширение функциональности
- Основной фокус: Новые игры и улучшенный интерфейс
## 🛠 Инструкции для contributors
1. **Создание задач:** Добавляйте новые задачи в соответствующий раздел с приоритетом
2. **Форматирование:** Используйте формат [ ] для незавершенных и [x] для завершенных задач
3. **Описание:** Добавляйте подробное описание для сложных задач
4. **Присвоение:** Присваивайте задачи себе с помощью @username
5. **Тестирование:** Все изменения должны быть протестированы перед коммитом
## 📞 Контакты команды разработки
- **Tech Lead:** [Имя Фамилия] - email@example.com
- **Backend Developer:** [Имя Фамилия] - email@example.com
- **QA Engineer:** [Имя Фамилия] - email@example.com
---
*Последнее обновление: 16 октября 2025*
Binary file not shown.
+839 -13
View File
@@ -2,13 +2,35 @@ import logging
import random import random
import requests import requests
import os import os
from datetime import datetime import asyncio
import tempfile
import subprocess
from datetime import datetime, timedelta
from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup, InlineQueryResultArticle, InputTextMessageContent from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup, InlineQueryResultArticle, InputTextMessageContent
from telegram.ext import ApplicationBuilder, ContextTypes, CommandHandler, MessageHandler, filters, InlineQueryHandler, ChatMemberHandler from telegram.ext import ApplicationBuilder, ContextTypes, CommandHandler, MessageHandler, filters, InlineQueryHandler, ChatMemberHandler
from database_sqlite import Database from database_sqlite import Database
from config_local import BOT_TOKEN, OPENWEATHER_API_KEY, NEWS_API_KEY, ADMIN_IDS from config_local import BOT_TOKEN, OPENWEATHER_API_KEY, NEWS_API_KEY, ADMIN_IDS
from messages import * from messages import *
try:
import speech_recognition as sr
SPEECH_RECOGNITION_AVAILABLE = True
print("[+] Speech recognition library loaded successfully")
except ImportError:
SPEECH_RECOGNITION_AVAILABLE = False
print("[-] Speech recognition library not available")
print(" To install: pip install SpeechRecognition")
print(" Alternative: pip install speech_recognition")
try:
from pydub import AudioSegment
PYDUB_AVAILABLE = True
print("[+] Pydub library loaded successfully")
except ImportError:
PYDUB_AVAILABLE = False
print("[-] Pydub library not available")
print(" To install: pip install pydub")
# Настройка логирования # Настройка логирования
logging.basicConfig( logging.basicConfig(
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
@@ -22,6 +44,10 @@ db = Database()
class TelegramBot: class TelegramBot:
def __init__(self): def __init__(self):
# Система отслеживания приветственных сообщений
self.welcome_messages = {} # {chat_id: {'message_id': int, 'timestamp': datetime}}
self.cleanup_task = None
self.application = ApplicationBuilder().token(BOT_TOKEN).build() self.application = ApplicationBuilder().token(BOT_TOKEN).build()
self.setup_handlers() self.setup_handlers()
@@ -39,6 +65,7 @@ class TelegramBot:
self.application.add_handler(CommandHandler("news", self.news)) self.application.add_handler(CommandHandler("news", self.news))
self.application.add_handler(CommandHandler("translate", self.translate)) self.application.add_handler(CommandHandler("translate", self.translate))
self.application.add_handler(CommandHandler("play_game", self.play_game)) self.application.add_handler(CommandHandler("play_game", self.play_game))
self.application.add_handler(CommandHandler("donate", self.donate))
self.application.add_handler(CommandHandler("ban", self.ban_user)) self.application.add_handler(CommandHandler("ban", self.ban_user))
self.application.add_handler(CommandHandler("mute", self.mute_user)) self.application.add_handler(CommandHandler("mute", self.mute_user))
self.application.add_handler(CommandHandler("warn", self.warn_user)) self.application.add_handler(CommandHandler("warn", self.warn_user))
@@ -55,6 +82,7 @@ class TelegramBot:
# Обработчики сообщений # Обработчики сообщений
self.application.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, self.handle_message)) self.application.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, self.handle_message))
self.application.add_handler(MessageHandler(filters.VOICE, self.handle_voice_message))
self.application.add_handler(ChatMemberHandler(self.handle_new_chat_members, ChatMemberHandler.CHAT_MEMBER)) self.application.add_handler(ChatMemberHandler(self.handle_new_chat_members, ChatMemberHandler.CHAT_MEMBER))
# Инлайновые запросы # Инлайновые запросы
@@ -64,8 +92,65 @@ class TelegramBot:
from telegram.ext import CallbackQueryHandler from telegram.ext import CallbackQueryHandler
self.application.add_handler(CallbackQueryHandler(self.handle_callback)) self.application.add_handler(CallbackQueryHandler(self.handle_callback))
def start_cleanup_task(self):
"""Запуск задачи очистки приветственных сообщений"""
if self.cleanup_task is None or self.cleanup_task.done():
self.cleanup_task = asyncio.create_task(self.cleanup_welcome_messages())
async def cleanup_welcome_messages(self):
"""Периодическая очистка устаревших приветственных сообщений"""
while True:
try:
await asyncio.sleep(30) # Проверяем каждые 30 секунд
await self.delete_expired_welcome_messages()
except asyncio.CancelledError:
break
except Exception as e:
print(f"Ошибка при очистке приветственных сообщений: {e}")
async def delete_expired_welcome_messages(self):
"""Удаление просроченных приветственных сообщений"""
current_time = datetime.now()
expired_chats = []
for chat_id, message_info in self.welcome_messages.items():
message_time = message_info['timestamp']
if current_time - message_time > timedelta(minutes=2):
try:
await self.application.bot.delete_message(chat_id, message_info['message_id'])
expired_chats.append(chat_id)
except Exception as e:
print(f"Не удалось удалить приветственное сообщение в чате {chat_id}: {e}")
expired_chats.append(chat_id)
# Удаляем записи о просроченных сообщениях
for chat_id in expired_chats:
self.welcome_messages.pop(chat_id, None)
async def delete_welcome_message(self, chat_id):
"""Удаление приветственного сообщения для конкретного чата"""
if chat_id in self.welcome_messages:
try:
message_info = self.welcome_messages[chat_id]
await self.application.bot.delete_message(chat_id, message_info['message_id'])
except Exception as e:
print(f"Не удалось удалить приветственное сообщение в чате {chat_id}: {e}")
finally:
self.welcome_messages.pop(chat_id, None)
async def set_welcome_message(self, chat_id, message_id):
"""Сохранение информации о приветственном сообщении"""
self.welcome_messages[chat_id] = {
'message_id': message_id,
'timestamp': datetime.now()
}
async def start(self, update: Update, context: ContextTypes.DEFAULT_TYPE): async def start(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
"""Обработчик команды /start""" """Обработчик команды /start"""
# Запускаем задачу очистки приветственных сообщений при первом использовании
if self.cleanup_task is None or self.cleanup_task.done():
self.start_cleanup_task()
user = update.effective_user user = update.effective_user
db.add_user(user.id, user.username, user.first_name, user.last_name) db.add_user(user.id, user.username, user.first_name, user.last_name)
@@ -135,7 +220,15 @@ class TelegramBot:
try: try:
target_user_id = int(target_user) target_user_id = int(target_user)
user_info = db.get_user_info(target_user_id) user_info = db.get_user_info(target_user_id)
achievements = db.get_user_achievements(target_user_id)
total_donations = db.get_total_donations(target_user_id)
if user_info: if user_info:
# Формируем строку значков
badge_str = ""
for achievement_type, _ in achievements:
if achievement_type in ACHIEVEMENT_BADGES:
badge_str += ACHIEVEMENT_BADGES[achievement_type] + " "
info_text = f""" info_text = f"""
👤 <b>Информация о пользователе:</b> 👤 <b>Информация о пользователе:</b>
@@ -155,6 +248,8 @@ ID: {user_info['ID']}
Очки: {user_info['Очки']} Очки: {user_info['Очки']}
Предупреждений: {user_info['Предупреждений']} Предупреждений: {user_info['Предупреждений']}
Роль: {user_info['Роль']} Роль: {user_info['Роль']}
{f"🏆 Достижения: {badge_str.strip()}" if badge_str else ""}
{f"💰 Всего донатов: {total_donations} RUB" if total_donations > 0 else ""}
""" """
else: else:
info_text = f"Пользователь с ID {target_user_id} не найден." info_text = f"Пользователь с ID {target_user_id} не найден."
@@ -164,8 +259,23 @@ ID: {user_info['ID']}
# Обычный пользователь запрашивает свою информацию # Обычный пользователь запрашивает свою информацию
db.add_user(user.id, user.username, user.first_name, user.last_name) db.add_user(user.id, user.username, user.first_name, user.last_name)
user_info = db.get_user_info(user.id) user_info = db.get_user_info(user.id)
achievements = db.get_user_achievements(user.id)
total_donations = db.get_total_donations(user.id)
if user_info: if user_info:
# Формируем строку значков
badge_str = ""
achievement_list = []
for achievement_type, unlocked_at in achievements:
if achievement_type in ACHIEVEMENT_BADGES:
badge_str += ACHIEVEMENT_BADGES[achievement_type] + " "
achievement_list.append(f"{ACHIEVEMENT_BADGES[achievement_type]} {achievement_type.replace('_', ' ').title()}")
# Создаем список достижений отдельно
achievements_text = ""
if achievement_list:
achievements_text = f"\n📋 Список достижений:\n" + '\n'.join(achievement_list)
info_text = f""" info_text = f"""
👤 <b>Информация о вас:</b> 👤 <b>Информация о вас:</b>
@@ -185,6 +295,9 @@ ID: {user_info['ID']}
Очки: {user_info['Очки']} Очки: {user_info['Очки']}
Предупреждений: {user_info['Предупреждений']} Предупреждений: {user_info['Предупреждений']}
Роль: {user_info['Роль']} Роль: {user_info['Роль']}
{f"🏆 Достижения: {badge_str.strip()}" if badge_str else ""}
{f"💰 Всего донатов: {total_donations} RUB" if total_donations > 0 else ""}
{achievements_text}
""" """
else: else:
info_text = USER_MESSAGES['info_not_found'] info_text = USER_MESSAGES['info_not_found']
@@ -259,19 +372,14 @@ ID: {user_info['ID']}
async def play_game(self, update: Update, context: ContextTypes.DEFAULT_TYPE): async def play_game(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
"""Запустить мини-игру""" """Запустить мини-игру"""
keyboard = [
[InlineKeyboardButton("Камень-ножницы-бумага", callback_data='game_rps')],
[InlineKeyboardButton("Крестики-нолики", callback_data='game_tic_tac_toe')],
[InlineKeyboardButton("Викторина", callback_data='game_quiz')],
[InlineKeyboardButton("⬅️ Назад", callback_data='cmd_start')]
]
reply_markup = InlineKeyboardMarkup(keyboard)
keyboard = [ keyboard = [
[InlineKeyboardButton("Камень-ножницы-бумага", callback_data='game_rps')], [InlineKeyboardButton("Камень-ножницы-бумага", callback_data='game_rps')],
[InlineKeyboardButton("Крестики-нолики", callback_data='game_tic_tac_toe')], [InlineKeyboardButton("Крестики-нолики", callback_data='game_tic_tac_toe')],
[InlineKeyboardButton("Викторина", callback_data='game_quiz')], [InlineKeyboardButton("Викторина", callback_data='game_quiz')],
[InlineKeyboardButton("Морской бой", callback_data='game_battleship')], [InlineKeyboardButton("Морской бой", callback_data='game_battleship')],
[InlineKeyboardButton("2048", callback_data='game_2048')],
[InlineKeyboardButton("Тетрис", callback_data='game_tetris')],
[InlineKeyboardButton("Змейка", callback_data='game_snake')],
[InlineKeyboardButton("⬅️ Назад", callback_data='cmd_start')] [InlineKeyboardButton("⬅️ Назад", callback_data='cmd_start')]
] ]
reply_markup = InlineKeyboardMarkup(keyboard) reply_markup = InlineKeyboardMarkup(keyboard)
@@ -297,8 +405,20 @@ ID: {user_info['ID']}
await self.start_quiz_game(query, context) await self.start_quiz_game(query, context)
elif query.data == 'game_battleship': elif query.data == 'game_battleship':
await self.start_battleship_game(query, context) await self.start_battleship_game(query, context)
elif query.data == 'game_2048':
await self.start_2048_game(query, context)
elif query.data == 'game_tetris':
await self.start_tetris_game(query, context)
elif query.data == 'game_snake':
await self.start_snake_game(query, context)
elif query.data.startswith('bs_'): elif query.data.startswith('bs_'):
await self.handle_battleship_shot(query, context) await self.handle_battleship_shot(query, context)
elif query.data.startswith('2048_'):
await self.handle_2048_move(query, context)
elif query.data.startswith('tetris_'):
await self.handle_tetris_move(query, context)
elif query.data.startswith('snake_'):
await self.handle_snake_move(query, context)
elif query.data.startswith('rps_'): elif query.data.startswith('rps_'):
await self.handle_rps(query, context) await self.handle_rps(query, context)
elif query.data.startswith('tictactoe_'): elif query.data.startswith('tictactoe_'):
@@ -313,6 +433,8 @@ ID: {user_info['ID']}
await self.add_schedule_image(query, context) await self.add_schedule_image(query, context)
elif query.data == 'cancel_schedule': elif query.data == 'cancel_schedule':
await self.cancel_schedule_post(query, context) await self.cancel_schedule_post(query, context)
elif query.data.startswith('donate_'):
await self.handle_donation_callback(query, context)
async def start_rps_game(self, query, context): async def start_rps_game(self, query, context):
@@ -1176,8 +1298,18 @@ ID: {user_info['ID']}
await self.handle_text_edit(update, context) await self.handle_text_edit(update, context)
return return
# Проверяем, ждет ли пользователь ввода суммы доната
if context.user_data.get('waiting_for_donation_amount'):
await self.handle_donation_amount_input(update, context)
return
message_text = update.message.text.lower() message_text = update.message.text.lower()
# Автофильтр нецензурной лексики
if self.check_profanity(message_text):
await self.handle_profanity_violation(update, user)
return
# Добавление пользователя в базу данных # Добавление пользователя в базу данных
db.add_user(user.id, user.username, user.first_name, user.last_name) db.add_user(user.id, user.username, user.first_name, user.last_name)
@@ -1261,14 +1393,103 @@ ID: {user_info['ID']}
context.user_data.pop('waiting_for_text_edit', None) context.user_data.pop('waiting_for_text_edit', None)
async def handle_donation_amount_input(self, update, context):
"""Обработка ввода суммы доната"""
try:
amount = float(update.message.text.strip())
if amount <= 0:
raise ValueError()
user = update.effective_user
# Сохраняем донат в базу данных
if db.add_donation(user.id, amount):
points = int(amount // 100)
await update.message.reply_text(
DONATION_MESSAGES['donate_success'].format(
amount=amount,
currency='RUB',
points=points
),
parse_mode='HTML'
)
# Проверяем достижения
current_year = datetime.now().year
total_yearly = db.get_total_donations(user.id, current_year)
achievements = db.check_and_unlock_achievements(user.id, donations=total_yearly)
# Сообщаем о новых достижениях
for achievement in achievements:
if achievement in ACHIEVEMENT_MESSAGES:
await update.effective_chat.send_message(
ACHIEVEMENT_MESSAGES[achievement].format(name=user.first_name),
parse_mode='HTML'
)
else:
await update.message.reply_text(DONATION_MESSAGES['donate_error'])
except ValueError:
await update.message.reply_text("❌ Неверная сумма! Введите положительное число.")
context.user_data.pop('waiting_for_donation_amount', None)
async def handle_new_chat_members(self, update: Update, context: ContextTypes.DEFAULT_TYPE): async def handle_new_chat_members(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
"""Обработка новых участников чата""" """Обработка новых участников чата"""
for member in update.chat_member.new_chat_members: chat_id = update.effective_chat.id
db.add_user(member.id, member.username, member.first_name, member.last_name)
welcome_text = USER_MESSAGES['welcome_new_user'].format(name=member.first_name) # Удаляем предыдущее приветственное сообщение, если оно есть
await self.delete_welcome_message(chat_id)
await update.effective_chat.send_message(welcome_text) # Создаем новое приветственное сообщение с правилами и кнопками донатов
welcome_text = self.get_welcome_message_with_rules()
# Создаем клавиатуру с кнопками донатов
donation_keyboard = [
[InlineKeyboardButton("💰 100 ₽", callback_data='donate_100')],
[InlineKeyboardButton("💰 500 ₽", callback_data='donate_500')],
[InlineKeyboardButton("💰 1000 ₽", callback_data='donate_1000')],
[InlineKeyboardButton("🎯 Помощь", callback_data='cmd_help')]
]
reply_markup = InlineKeyboardMarkup(donation_keyboard)
try:
message = await update.effective_chat.send_message(welcome_text, parse_mode='HTML', reply_markup=reply_markup)
# Сохраняем информацию о сообщении для автодаления
await self.set_welcome_message(chat_id, message.message_id)
# Обрабатываем каждого нового участника
for member in update.chat_member.new_chat_members:
db.add_user(member.id, member.username, member.first_name, member.last_name)
# Начисление очков за вступление в чат
db.update_score(member.id, SCORE_VALUES['join_chat'])
# Обновление репутации за вступление в чат
db.update_reputation(member.id, SCORE_VALUES['reputation_per_message'])
except Exception as e:
print(f"Ошибка при отправке приветственного сообщения: {e}")
def get_welcome_message_with_rules(self):
"""Генерация приветственного сообщения с правилами группы"""
welcome_text = """🎉 <b>Добро пожаловать в нашу группу!</b>
👋 Приветствуем всех новых участников!
📋 <b>Правила нашей группы:</b>
• Соблюдайте уважительное общение
• Не используйте нецензурную лексику
• Спам и реклама запрещены
• Уважайте мнение других участников
• Используйте /help для получения справки о командах бота
🎮 В группе работает система рейтингов и игр!
🏆 Используйте /rank для просмотра вашего рейтинга
💰 <b>Поддержите проект:</b>
Если вам нравится бот, вы можете поддержать его развитие!"""
return welcome_text
async def handle_inline_query(self, update: Update, context: ContextTypes.DEFAULT_TYPE): async def handle_inline_query(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
"""Обработка инлайновых запросов""" """Обработка инлайновых запросов"""
@@ -1357,6 +1578,9 @@ ID: {user_info['ID']}
[InlineKeyboardButton("Крестики-нолики", callback_data='game_tic_tac_toe')], [InlineKeyboardButton("Крестики-нолики", callback_data='game_tic_tac_toe')],
[InlineKeyboardButton("Викторина", callback_data='game_quiz')], [InlineKeyboardButton("Викторина", callback_data='game_quiz')],
[InlineKeyboardButton("Морской бой", callback_data='game_battleship')], [InlineKeyboardButton("Морской бой", callback_data='game_battleship')],
[InlineKeyboardButton("2048", callback_data='game_2048')],
[InlineKeyboardButton("Тетрис", callback_data='game_tetris')],
[InlineKeyboardButton("Змейка", callback_data='game_snake')],
[InlineKeyboardButton("⬅️ Назад", callback_data='cmd_start')] [InlineKeyboardButton("⬅️ Назад", callback_data='cmd_start')]
] ]
reply_markup = InlineKeyboardMarkup(keyboard) reply_markup = InlineKeyboardMarkup(keyboard)
@@ -1430,6 +1654,608 @@ ID: {user_info['ID']}
await query.edit_message_text("❌ Планирование поста отменено.") await query.edit_message_text("❌ Планирование поста отменено.")
# ===== НОВЫЕ ИГРЫ =====
async def start_2048_game(self, query, context):
"""Запуск игры 2048"""
# Инициализация поля 4x4
board = [[0 for _ in range(4)] for _ in range(4)]
# Добавляем два начальных числа
self.add_random_tile(board)
self.add_random_tile(board)
context.user_data['2048_board'] = board
context.user_data['2048_score'] = 0
keyboard = self.create_2048_keyboard(board, 0)
reply_markup = InlineKeyboardMarkup(keyboard)
await query.edit_message_text(GAME_MESSAGES['2048_start'], reply_markup=reply_markup, parse_mode='HTML')
async def start_tetris_game(self, query, context):
"""Запуск игры Тетрис"""
# Инициализация поля 10x20 (ширина x высота)
board = [[0 for _ in range(10)] for _ in range(20)]
current_piece = self.get_random_tetris_piece()
context.user_data['tetris_board'] = board
context.user_data['tetris_current_piece'] = current_piece
context.user_data['tetris_score'] = 0
context.user_data['tetris_lines'] = 0
keyboard = self.create_tetris_keyboard(board, current_piece, 0, 0)
reply_markup = InlineKeyboardMarkup(keyboard)
await query.edit_message_text(GAME_MESSAGES['tetris_start'], reply_markup=reply_markup, parse_mode='HTML')
async def start_snake_game(self, query, context):
"""Запуск игры Змейка"""
# Инициализация поля 10x10
board = [[0 for _ in range(10)] for _ in range(10)]
snake = [(5, 5)] # Начальная позиция змейки
food = self.place_food(board, snake)
direction = 'right'
context.user_data['snake_board'] = board
context.user_data['snake_body'] = snake
context.user_data['snake_food'] = food
context.user_data['snake_direction'] = direction
context.user_data['snake_score'] = 0
keyboard = self.create_snake_keyboard(board, snake, food, 0)
reply_markup = InlineKeyboardMarkup(keyboard)
await query.edit_message_text(GAME_MESSAGES['snake_start'], reply_markup=reply_markup, parse_mode='HTML')
async def handle_2048_move(self, query, context):
"""Обработка ходов в игре 2048"""
move = query.data.split('_')[1]
board = context.user_data.get('2048_board')
score = context.user_data.get('2048_score', 0)
if not board:
await query.answer("Игра не найдена")
return
moved, new_score = self.move_2048(board, move)
score += new_score
if moved:
self.add_random_tile(board)
context.user_data['2048_score'] = score
# Проверяем победу
if self.check_2048_win(board):
db.update_score(query.from_user.id, 20)
keyboard = self.create_2048_keyboard(board, score, game_over=True)
reply_markup = InlineKeyboardMarkup(keyboard)
await query.edit_message_text(GAME_MESSAGES['2048_win'], reply_markup=reply_markup)
return
# Проверяем окончание игры
if not self.can_move_2048(board):
keyboard = self.create_2048_keyboard(board, score, game_over=True)
reply_markup = InlineKeyboardMarkup(keyboard)
await query.edit_message_text(GAME_MESSAGES['2048_game_over'], reply_markup=reply_markup)
return
keyboard = self.create_2048_keyboard(board, score)
reply_markup = InlineKeyboardMarkup(keyboard)
await query.edit_message_text(GAME_MESSAGES['2048_start'], reply_markup=reply_markup, parse_mode='HTML')
else:
await query.answer("Невозможный ход!")
async def handle_tetris_move(self, query, context):
"""Обработка ходов в игре Тетрис"""
move = query.data.split('_')[1]
board = context.user_data.get('tetris_board')
piece = context.user_data.get('tetris_current_piece')
score = context.user_data.get('tetris_score', 0)
lines = context.user_data.get('tetris_lines', 0)
if not board or not piece:
await query.answer("Игра не найдена")
return
# Обработка движений
if move == 'left':
piece['x'] -= 1
if not self.is_valid_position(board, piece):
piece['x'] += 1
elif move == 'right':
piece['x'] += 1
if not self.is_valid_position(board, piece):
piece['x'] -= 1
elif move == 'down':
piece['y'] += 1
if not self.is_valid_position(board, piece):
piece['y'] -= 1
# Фиксируем фигуру и создаем новую
self.place_piece(board, piece)
lines_cleared = self.clear_lines(board)
score += lines_cleared * 10
lines += lines_cleared
new_piece = self.get_random_tetris_piece()
if not self.is_valid_position(board, new_piece):
# Игра окончена
keyboard = self.create_tetris_keyboard(board, piece, score, lines, game_over=True)
reply_markup = InlineKeyboardMarkup(keyboard)
await query.edit_message_text(GAME_MESSAGES['tetris_game_over'].format(score=score), reply_markup=reply_markup)
return
piece = new_piece
elif move == 'rotate':
old_shape = piece['shape'][:]
piece['shape'] = list(zip(*piece['shape'][::-1]))
if not self.is_valid_position(board, piece):
piece['shape'] = old_shape
context.user_data['tetris_current_piece'] = piece
context.user_data['tetris_score'] = score
context.user_data['tetris_lines'] = lines
keyboard = self.create_tetris_keyboard(board, piece, score, lines)
reply_markup = InlineKeyboardMarkup(keyboard)
await query.edit_message_text(GAME_MESSAGES['tetris_start'], reply_markup=reply_markup, parse_mode='HTML')
async def handle_snake_move(self, query, context):
"""Обработка движений в игре Змейка"""
new_direction = query.data.split('_')[1]
board = context.user_data.get('snake_board')
snake = context.user_data.get('snake_body')
food = context.user_data.get('snake_food')
direction = context.user_data.get('snake_direction', 'right')
score = context.user_data.get('snake_score', 0)
if not board or not snake:
await query.answer("Игра не найдена")
return
# Не позволяем двигаться в обратном направлении
opposites = {'up': 'down', 'down': 'up', 'left': 'right', 'right': 'left'}
if new_direction == opposites.get(direction):
await query.answer("Невозможное направление!")
return
# Двигаем змейку
head = snake[0]
if new_direction == 'up':
new_head = (head[0] - 1, head[1])
elif new_direction == 'down':
new_head = (head[0] + 1, head[1])
elif new_direction == 'left':
new_head = (head[0], head[1] - 1)
elif new_direction == 'right':
new_head = (head[0], head[1] + 1)
# Проверяем столкновения
if (new_head[0] < 0 or new_head[0] >= 10 or
new_head[1] < 0 or new_head[1] >= 10 or
new_head in snake):
# Игра окончена
db.update_score(query.from_user.id, score)
keyboard = self.create_snake_keyboard(board, snake, food, score, game_over=True)
reply_markup = InlineKeyboardMarkup(keyboard)
await query.edit_message_text(GAME_MESSAGES['snake_game_over'].format(length=len(snake), score=score), reply_markup=reply_markup)
return
snake.insert(0, new_head)
# Проверяем еду
if new_head == food:
score += 10
food = self.place_food(board, snake)
else:
snake.pop() # Удаляем хвост
context.user_data['snake_body'] = snake
context.user_data['snake_food'] = food
context.user_data['snake_direction'] = new_direction
context.user_data['snake_score'] = score
keyboard = self.create_snake_keyboard(board, snake, food, score)
reply_markup = InlineKeyboardMarkup(keyboard)
await query.edit_message_text(GAME_MESSAGES['snake_start'], reply_markup=reply_markup, parse_mode='HTML')
# ===== ДОНАТЫ И ДОСТИЖЕНИЯ =====
async def donate(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
"""Команда доната"""
keyboard = [
[InlineKeyboardButton("100 ₽", callback_data='donate_100')],
[InlineKeyboardButton("500 ₽", callback_data='donate_500')],
[InlineKeyboardButton("1000 ₽", callback_data='donate_1000')],
[InlineKeyboardButton("2500 ₽", callback_data='donate_2500')],
[InlineKeyboardButton("5000 ₽", callback_data='donate_5000')],
[InlineKeyboardButton("Другая сумма", callback_data='donate_custom')]
]
reply_markup = InlineKeyboardMarkup(keyboard)
await update.message.reply_text(
DONATION_MESSAGES['donate_welcome'],
reply_markup=reply_markup,
parse_mode='HTML'
)
async def handle_voice_message(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
"""Обработка голосовых сообщений"""
user = update.effective_user
voice = update.message.voice
# Добавление пользователя в базу данных
db.add_user(user.id, user.username, user.first_name, user.last_name)
# Начисление очков за голосовое сообщение
db.update_score(user.id, SCORE_VALUES['message'])
duration = voice.duration
# Попытка транскрибации голосового сообщения
transcription = await self.transcribe_voice_message(voice, update)
await update.message.reply_text(
f"🎤 Голосовое сообщение от {user.first_name} ({duration} сек)\n\n"
f"🔄 Транскрибация: {transcription}\n\n"
f"💡 +1 очко за активность!",
reply_to_message_id=update.message.message_id
)
async def transcribe_voice_message(self, voice, update):
"""Транскрибация голосового сообщения с использованием speech recognition"""
if not SPEECH_RECOGNITION_AVAILABLE:
return "[Speech Recognition не установлен. Установите: pip install SpeechRecognition]"
try:
# Создаем временный файл для загрузки аудио
with tempfile.NamedTemporaryFile(suffix='.ogg', delete=False) as temp_ogg:
temp_ogg_path = temp_ogg.name
# Скачиваем голосовое сообщение
voice_file = await voice.get_file()
await voice_file.download_to_drive(temp_ogg_path)
# Конвертируем OGG в WAV для speech_recognition
temp_wav_path = temp_ogg_path.replace('.ogg', '.wav')
if PYDUB_AVAILABLE:
# Используем pydub для конвертации
audio = AudioSegment.from_ogg(temp_ogg_path)
audio.export(temp_wav_path, format='wav')
else:
# Используем ffmpeg для конвертации (если доступен)
try:
subprocess.run([
'ffmpeg', '-i', temp_ogg_path, '-acodec', 'pcm_s16le',
'-ar', '16000', '-ac', '1', temp_wav_path
], check=True, capture_output=True)
except (subprocess.CalledProcessError, FileNotFoundError):
# Очищаем временные файлы и возвращаем ошибку
os.unlink(temp_ogg_path)
return "[невозможно конвертировать аудио - установите ffmpeg или pydub]\n💡 Для полной работы: pip install SpeechRecognition pydub ffmpeg-python"
# Распознавание речи
recognizer = sr.Recognizer()
with sr.AudioFile(temp_wav_path) as source:
audio_data = recognizer.record(source)
# Пробуем разные API для распознавания
try:
# Сначала пробуем Google Speech Recognition (бесплатно)
text = recognizer.recognize_google(audio_data, language='ru-RU')
return text
except sr.UnknownValueError:
return "[не удалось распознать речь]"
except sr.RequestError:
# Если Google API недоступен, пробуем Sphinx (локальный, но менее точный)
try:
text = recognizer.recognize_sphinx(audio_data, language='ru-RU')
return text
except sr.UnknownValueError:
return "[не удалось распознать речь (локально)]"
except sr.RequestError:
return "[сервисы распознавания речи недоступны]"
except Exception as e:
print(f"Ошибка при транскрибации: {e}")
return "[ошибка при обработке голосового сообщения]"
finally:
# Очищаем временные файлы
try:
if 'temp_ogg_path' in locals():
os.unlink(temp_ogg_path)
if 'temp_wav_path' in locals():
os.unlink(temp_wav_path)
except:
pass
# ===== ВСПОМОГАТЕЛЬНЫЕ МЕТОДЫ ДЛЯ ИГР =====
def add_random_tile(self, board):
"""Добавляет случайную плитку (2 или 4) в пустую клетку"""
empty_cells = [(i, j) for i in range(4) for j in range(4) if board[i][j] == 0]
if empty_cells:
i, j = random.choice(empty_cells)
board[i][j] = 2 if random.random() < 0.9 else 4
def move_2048(self, board, direction):
"""Двигает плитки в указанном направлении"""
def move_row_left(row):
"""Двигает строку влево"""
new_row = [i for i in row if i != 0]
for i in range(len(new_row) - 1):
if new_row[i] == new_row[i + 1]:
new_row[i] *= 2
new_row[i + 1] = 0
new_row = [i for i in new_row if i != 0]
return new_row + [0] * (4 - len(new_row))
moved = False
score = 0
if direction == 'left':
for i in range(4):
old_row = board[i][:]
board[i] = move_row_left(board[i])
if board[i] != old_row:
moved = True
elif direction == 'right':
for i in range(4):
old_row = board[i][:]
board[i] = move_row_left(board[i][::-1])[::-1]
if board[i] != old_row:
moved = True
elif direction == 'up':
for j in range(4):
col = [board[i][j] for i in range(4)]
old_col = col[:]
new_col = move_row_left(col)
for i in range(4):
board[i][j] = new_col[i]
if new_col != old_col:
moved = True
elif direction == 'down':
for j in range(4):
col = [board[i][j] for i in range(4)]
old_col = col[:]
new_col = move_row_left(col[::-1])[::-1]
for i in range(4):
board[i][j] = new_col[i]
if new_col != old_col:
moved = True
return moved, score
def can_move_2048(self, board):
"""Проверяет, возможны ли ходы"""
for i in range(4):
for j in range(4):
if board[i][j] == 0:
return True
if i > 0 and board[i][j] == board[i-1][j]:
return True
if j > 0 and board[i][j] == board[i][j-1]:
return True
return False
def check_2048_win(self, board):
"""Проверяет победу (наличие 2048)"""
return any(2048 in row for row in board)
def create_2048_keyboard(self, board, score, game_over=False):
"""Создает клавиатуру для игры 2048"""
keyboard = []
for i in range(4):
row = []
for j in range(4):
cell = board[i][j]
text = str(cell) if cell != 0 else ' '
row.append(InlineKeyboardButton(text, callback_data=f'2048_cell_{i}_{j}'))
keyboard.append(row)
# Кнопки управления
keyboard.append([
InlineKeyboardButton("⬅️", callback_data='2048_left'),
InlineKeyboardButton("⬆️", callback_data='2048_up'),
InlineKeyboardButton("⬇️", callback_data='2048_down'),
InlineKeyboardButton("➡️", callback_data='2048_right')
])
keyboard.append([
InlineKeyboardButton("🔄 Новая игра", callback_data='game_2048'),
InlineKeyboardButton("⬅️ Назад к играм", callback_data='cmd_play_game')
])
return keyboard
def get_random_tetris_piece(self):
"""Возвращает случайную тетрис-фигуру"""
pieces = [
{'shape': [[1, 1, 1, 1]], 'x': 3, 'y': 0}, # I
{'shape': [[1, 1], [1, 1]], 'x': 4, 'y': 0}, # O
{'shape': [[1, 0, 0], [1, 1, 1]], 'x': 3, 'y': 0}, # J
{'shape': [[0, 0, 1], [1, 1, 1]], 'x': 3, 'y': 0}, # L
{'shape': [[0, 1, 1], [1, 1, 0]], 'x': 3, 'y': 0}, # S
{'shape': [[1, 1, 0], [0, 1, 1]], 'x': 3, 'y': 0}, # Z
{'shape': [[0, 1, 0], [1, 1, 1]], 'x': 3, 'y': 0}, # T
]
return random.choice(pieces)
def is_valid_position(self, board, piece):
"""Проверяет, можно ли разместить фигуру в данной позиции"""
for i, row in enumerate(piece['shape']):
for j, cell in enumerate(row):
if cell:
x, y = piece['x'] + j, piece['y'] + i
if (x < 0 or x >= 10 or y >= 20 or
(y >= 0 and board[y][x] != 0)):
return False
return True
def place_piece(self, board, piece):
"""Размещает фигуру на поле"""
for i, row in enumerate(piece['shape']):
for j, cell in enumerate(row):
if cell:
x, y = piece['x'] + j, piece['y'] + i
if 0 <= y < 20 and 0 <= x < 10:
board[y][x] = 1
def clear_lines(self, board):
"""Очищает заполненные линии"""
lines_cleared = 0
y = 19
while y >= 0:
if all(board[y]):
del board[y]
board.insert(0, [0] * 10)
lines_cleared += 1
else:
y -= 1
return lines_cleared
def create_tetris_keyboard(self, board, piece, score, lines, game_over=False):
"""Создает клавиатуру для игры Тетрис"""
# Упрощенная визуализация - показываем только нижнюю часть поля
display_board = board[-10:] if len(board) > 10 else board
keyboard = []
for row in display_board:
display_row = []
for cell in row:
text = '' if cell else ''
display_row.append(InlineKeyboardButton(text, callback_data='tetris_display'))
keyboard.append(display_row)
# Кнопки управления
keyboard.append([
InlineKeyboardButton("⬅️", callback_data='tetris_left'),
InlineKeyboardButton("🔄", callback_data='tetris_rotate'),
InlineKeyboardButton("➡️", callback_data='tetris_right')
])
keyboard.append([
InlineKeyboardButton("⬇️", callback_data='tetris_down'),
InlineKeyboardButton("🔄 Новая игра", callback_data='game_tetris'),
InlineKeyboardButton("⬅️ Назад к играм", callback_data='cmd_play_game')
])
return keyboard
def place_food(self, board, snake):
"""Размещает еду в случайной свободной клетке"""
empty_cells = [(i, j) for i in range(10) for j in range(10) if (i, j) not in snake]
if empty_cells:
return random.choice(empty_cells)
return None
def create_snake_keyboard(self, board, snake, food, score, game_over=False):
"""Создает клавиатуру для игры Змейка"""
keyboard = []
for i in range(10):
row = []
for j in range(10):
if (i, j) == food:
text = '🍎'
elif (i, j) in snake:
text = '🟢' if (i, j) == snake[0] else '🟩'
else:
text = ''
row.append(InlineKeyboardButton(text, callback_data=f'snake_cell_{i}_{j}'))
keyboard.append(row)
# Кнопки управления
keyboard.append([
InlineKeyboardButton("⬅️", callback_data='snake_left'),
InlineKeyboardButton("⬆️", callback_data='snake_up'),
InlineKeyboardButton("⬇️", callback_data='snake_down'),
InlineKeyboardButton("➡️", callback_data='snake_right')
])
keyboard.append([
InlineKeyboardButton("🔄 Новая игра", callback_data='game_snake'),
InlineKeyboardButton("⬅️ Назад к играм", callback_data='cmd_play_game')
])
return keyboard
def check_profanity(self, text):
"""Проверка текста на наличие нецензурной лексики"""
text_lower = text.lower()
for word in PROFANITY_WORDS:
if word in text_lower:
return True
return False
async def handle_profanity_violation(self, update, user):
"""Обработка нарушения с нецензурной лексикой"""
# Удаление сообщения с матом
try:
await update.message.delete()
except Exception as e:
print(f"Не удалось удалить сообщение: {e}")
# Отправка предупреждения в чат
username = user.username if user.username else user.first_name
await update.effective_chat.send_message(
MODERATION_MESSAGES['profanity_detected'].format(username=username),
parse_mode='HTML'
)
# Начисление предупреждения пользователю
db.add_warning(user.id, "Нецензурная лексика", 0) # 0 - системное предупреждение
async def handle_donation_callback(self, query, context):
"""Обработка callback'ов донатов"""
await query.answer()
amount_str = query.data.split('_')[1]
if amount_str == 'custom':
# Запрос ввода суммы
await query.edit_message_text(
"💰 Введите сумму доната в рублях:",
reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton("❌ Отмена", callback_data='donate_cancel')]])
)
context.user_data['waiting_for_donation_amount'] = True
return
try:
amount = float(amount_str)
user = query.from_user
# Сохраняем донат в базу данных
if db.add_donation(user.id, amount):
points = int(amount // 100)
await query.edit_message_text(
DONATION_MESSAGES['donate_success'].format(
amount=amount,
currency='RUB',
points=points
),
parse_mode='HTML'
)
# Проверяем достижения
current_year = datetime.now().year
total_yearly = db.get_total_donations(user.id, current_year)
achievements = db.check_and_unlock_achievements(user.id, donations=total_yearly)
# Сообщаем о новых достижениях
for achievement in achievements:
if achievement in ACHIEVEMENT_MESSAGES:
await query.message.chat.send_message(
ACHIEVEMENT_MESSAGES[achievement].format(name=user.first_name),
parse_mode='HTML'
)
else:
await query.edit_message_text(DONATION_MESSAGES['donate_error'])
except ValueError:
await query.edit_message_text("❌ Неверная сумма!")
def run(self): def run(self):
"""Запуск бота""" """Запуск бота"""
print("Bot started!") print("Bot started!")
+150
View File
@@ -87,6 +87,29 @@ class Database:
) )
""") """)
# Таблица достижений
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)
)
""")
self.connection.commit() self.connection.commit()
print(TECH_MESSAGES['db_tables_created']) print(TECH_MESSAGES['db_tables_created'])
except sqlite3.Error as error: except sqlite3.Error as error:
@@ -396,6 +419,133 @@ class Database:
print(f"Ошибка при удалении поста: {error}") print(f"Ошибка при удалении поста: {error}")
return False 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:
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:
self.update_score(user_id, points)
self.connection.commit()
return True
except sqlite3.Error 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 close(self): def close(self):
"""Закрытие соединения с базой данных""" """Закрытие соединения с базой данных"""
if self.connection: if self.connection:
+60
View File
@@ -34,6 +34,9 @@ HELP_TEXT = """
<b>Прочее:</b> <b>Прочее:</b>
Введите "реквизиты" - Получить реквизиты для оплаты Введите "реквизиты" - Получить реквизиты для оплаты
<b>Поддержка проекта:</b>
/donate - Поддержать проект донатом (начисление очков за поддержку)
<b>Игры:</b> <b>Игры:</b>
/play_game - Запустить мини-игру /play_game - Запустить мини-игру
@@ -158,6 +161,33 @@ MODERATION_MESSAGES = {
'kick_error': '❌ Ошибка при кике пользователя: {error}', 'kick_error': '❌ Ошибка при кике пользователя: {error}',
'promote_error': '❌ Ошибка при повышении пользователя: {error}', 'promote_error': '❌ Ошибка при повышении пользователя: {error}',
'demote_error': '❌ Ошибка при понижении пользователя: {error}', 'demote_error': '❌ Ошибка при понижении пользователя: {error}',
'profanity_detected': '🚫 <b>Обнаружена нецензурная лексика!</b>\n\nСообщение пользователя @{username} удалено.\nПричина: использование запрещенных выражений.',
}
# Сообщения достижений
ACHIEVEMENT_MESSAGES = {
'first_message': '🎉 <b>Новое достижение!</b>\n\n🏆 <b>Первое сообщение!</b>\nВы отправили первое сообщение в чате!',
'100_messages': '🎊 <b>Новое достижение!</b>\n\n💯 <b>100 сообщений!</b>\nВы отправили 100 сообщений в чате!',
'1000_messages': '🏅 <b>Новое достижение!</b>\n\n🎖 <b>1000 сообщений!</b>\nВы - настоящий участник чата!',
'games_master': '🎮 <b>Новое достижение!</b>\n\n🏆 <b>Мастер игр!</b>\nВы выиграли 10 игр!',
'sponsor_year': '💰 <b>Новое достижение!</b>\n\n👑 <b>Спонсор года!</b>\nВы задонатили более 100 000 рублей в этом году!',
}
# Значки достижений
ACHIEVEMENT_BADGES = {
'first_message': '💬',
'100_messages': '💯',
'1000_messages': '🎖',
'games_master': '🎮',
'sponsor_year': '👑',
}
# Сообщения донатов
DONATION_MESSAGES = {
'donate_welcome': '💰 <b>Поддержите проект!</b>\n\nВаша поддержка помогает развивать бота и добавлять новые функции.\n\nВыберите сумму пожертвования:',
'donate_success': '✅ <b>Спасибо за поддержку!</b>\n\n💰 Сумма: {amount} {currency}\n🎁 Начислено очков: {points}',
'donate_error': '❌ Ошибка при обработке доната. Попробуйте позже.',
'donate_cancel': '❌ Донат отменен.',
} }
# Сообщения игр # Сообщения игр
@@ -185,6 +215,17 @@ GAME_MESSAGES = {
'battleship_lose': '😞 Вы проиграли! Все корабли потоплены.', 'battleship_lose': '😞 Вы проиграли! Все корабли потоплены.',
'battleship_already_shot': 'Вы уже стреляли в эту клетку!', 'battleship_already_shot': 'Вы уже стреляли в эту клетку!',
'battleship_invalid_coord': 'Неверные координаты!', 'battleship_invalid_coord': 'Неверные координаты!',
# Новые игры
'2048_start': '🧩 <b>2048</b>\n\nЦель: соединяйте одинаковые числа, чтобы получить 2048!\n\nИспользуйте стрелки для движения плиток.',
'2048_win': '🎉 Поздравляем! Вы достигли 2048! +20 очков',
'2048_game_over': '😞 Игра окончена! Попробуйте снова.',
'2048_score': 'Текущий счет: {score}',
'tetris_start': '🧱 <b>Тетрис</b>\n\nИспользуйте стрелки для управления фигурами.\nСобирайте полные линии!',
'tetris_line_cleared': '✨ Линия очищена! +{points} очков',
'tetris_game_over': '😞 Игра окончена! Счет: {score}',
'snake_start': '🐍 <b>Змейка</b>\n\nСобирайте 🍎, чтобы расти!\nИспользуйте стрелки для движения.',
'snake_game_over': '😞 Игра окончена! Длина: {length}, Счет: {score}',
'snake_score': '🍎 Съедено: {score}, Длина: {length}',
} }
# Сообщения погоды # Сообщения погоды
@@ -365,8 +406,27 @@ SCORE_VALUES = {
'bonus_min': 1, 'bonus_min': 1,
'bonus_max': 5, 'bonus_max': 5,
'reputation_per_message': 1, 'reputation_per_message': 1,
'join_chat': 5,
'reputation_per_join': 2,
} }
# Список запрещенных слов для автофильтра
PROFANITY_WORDS = [
# Русская нецензурная лексика (основные вариации)
'блядь', 'бля', 'блять', 'блядский', 'блядство', 'блядник',
'хуй', 'хуйня', 'хуйло', 'хуесос', 'хуеглот', 'хуяк',
'пизда', 'пиздец', 'пиздеть', 'пиздилка', 'пиздобол',
'ебать', 'ебаный', 'ебанат', 'еблан', 'ебло', 'ебануться',
'сука', 'сукин', 'сучий', 'сучка', 'суко',
'мудак', 'мудацкий', 'мудило', 'мудизм',
'говно', 'говнос', 'говнюк', 'говнище',
'дрочить', 'дрочер', 'дрочила',
'залупа', 'залупный',
'жопа', 'жопный', 'жопник',
# Английская нецензурная лексика
'fuck', 'shit', 'damn', 'asshole', 'bastard', 'cunt', 'dick', 'pussy', 'cock', 'bitch'
]
# Константы планировщика # Константы планировщика
SCHEDULER_CONFIG = { SCHEDULER_CONFIG = {
'check_interval': 30, # секунд 'check_interval': 30, # секунд
+2
View File
@@ -1,2 +1,4 @@
python-telegram-bot==20.7 python-telegram-bot==20.7
requests==2.31.0 requests==2.31.0
speech_recognition==3.10.0
pydub==0.25.1
+1 -1
View File
@@ -12,7 +12,7 @@ import sys
from datetime import datetime from datetime import datetime
from telegram import Bot from telegram import Bot
from database_sqlite import Database from database_sqlite import Database
from config import BOT_TOKEN from config_local import BOT_TOKEN
from messages import TECH_MESSAGES, SCHEDULER_CONFIG from messages import TECH_MESSAGES, SCHEDULER_CONFIG
# Настройка логирования # Настройка логирования