639 lines
27 KiB
Python
639 lines
27 KiB
Python
import logging
|
||
import asyncio
|
||
import json
|
||
import os
|
||
from datetime import datetime, date, time, timedelta
|
||
from telegram import Update
|
||
from telegram.ext import ApplicationBuilder, CommandHandler, ContextTypes
|
||
from dotenv import load_dotenv
|
||
import calendar
|
||
import sys
|
||
|
||
# Настройка путей
|
||
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
||
DATA_DIR = os.path.join(BASE_DIR, 'data')
|
||
LOG_DIR = os.path.join(BASE_DIR, 'logs')
|
||
|
||
# Создаем директории если их нет
|
||
os.makedirs(DATA_DIR, exist_ok=True)
|
||
os.makedirs(LOG_DIR, exist_ok=True)
|
||
|
||
# Пути к файлам
|
||
DATA_FILE = os.path.join(DATA_DIR, 'love_dates.json')
|
||
LOG_FILE = os.path.join(LOG_DIR, 'bot.log')
|
||
|
||
# Настройка логирования
|
||
logging.basicConfig(
|
||
level=logging.INFO,
|
||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
||
handlers=[
|
||
logging.FileHandler(LOG_FILE),
|
||
logging.StreamHandler(sys.stdout)
|
||
]
|
||
)
|
||
logger = logging.getLogger(__name__)
|
||
|
||
# Загрузка переменных окружения
|
||
load_dotenv()
|
||
TOKEN = os.getenv('TELEGRAM_BOT_TOKEN')
|
||
if not TOKEN:
|
||
logger.error("Токен бота не найден! Проверьте файл .env")
|
||
sys.exit(1)
|
||
|
||
# Чтение настроек из переменных окружения
|
||
NOTIFY_HOUR = int(os.getenv('NOTIFY_HOUR', '9'))
|
||
NOTIFY_MINUTE = int(os.getenv('NOTIFY_MINUTE', '0'))
|
||
NOTIFY_TIME = time(NOTIFY_HOUR, NOTIFY_MINUTE)
|
||
MONTHLY_REPORT_DAY = int(os.getenv('MONTHLY_REPORT_DAY', '1'))
|
||
|
||
logger.info(f"Настройки бота: уведомления в {NOTIFY_HOUR}:{NOTIFY_MINUTE:02d}, отчет 1 числа")
|
||
|
||
# === ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ ===
|
||
|
||
def load_data():
|
||
"""Загружает данные из JSON файла"""
|
||
try:
|
||
if os.path.exists(DATA_FILE):
|
||
with open(DATA_FILE, "r", encoding="utf-8") as f:
|
||
data = json.load(f)
|
||
logger.info(f"Данные загружены из {DATA_FILE}")
|
||
return data
|
||
except FileNotFoundError:
|
||
logger.info(f"Файл {DATA_FILE} не найден, создан новый")
|
||
return {}
|
||
except json.JSONDecodeError as e:
|
||
logger.error(f"Ошибка чтения JSON: {e}")
|
||
return {}
|
||
except Exception as e:
|
||
logger.error(f"Ошибка загрузки данных: {e}")
|
||
return {}
|
||
return {}
|
||
|
||
def save_data(data):
|
||
"""Сохраняет данные в JSON файл"""
|
||
try:
|
||
with open(DATA_FILE, "w", encoding="utf-8") as f:
|
||
json.dump(data, f, ensure_ascii=False, indent=2)
|
||
logger.debug(f"Данные сохранены в {DATA_FILE}")
|
||
except Exception as e:
|
||
logger.error(f"Ошибка сохранения данных: {e}")
|
||
|
||
def is_leap_year(year):
|
||
"""Проверка на високосный год"""
|
||
return calendar.isleap(year)
|
||
|
||
def calculate_precise_difference(start_date, end_date):
|
||
"""
|
||
Рассчитывает точную разницу между датами с учётом високосных годов
|
||
Возвращает: годы, месяцы, дни, общее количество дней
|
||
"""
|
||
if start_date > end_date:
|
||
start_date, end_date = end_date, start_date
|
||
|
||
total_days = (end_date - start_date).days
|
||
|
||
# Рассчитываем годы
|
||
years = end_date.year - start_date.year
|
||
|
||
# Рассчитываем месяцы
|
||
if end_date.month < start_date.month:
|
||
years -= 1
|
||
months = 12 - start_date.month + end_date.month
|
||
else:
|
||
months = end_date.month - start_date.month
|
||
|
||
# Рассчитываем дни
|
||
if end_date.day < start_date.day:
|
||
months -= 1
|
||
# Получаем количество дней в предыдущем месяце
|
||
if end_date.month == 1:
|
||
prev_month = 12
|
||
prev_year = end_date.year - 1
|
||
else:
|
||
prev_month = end_date.month - 1
|
||
prev_year = end_date.year
|
||
|
||
days_in_prev_month = calendar.monthrange(prev_year, prev_month)[1]
|
||
days = days_in_prev_month - start_date.day + end_date.day
|
||
else:
|
||
days = end_date.day - start_date.day
|
||
|
||
# Корректировка если месяцы стали отрицательными
|
||
if months < 0:
|
||
years -= 1
|
||
months += 12
|
||
|
||
return years, months, days, total_days
|
||
|
||
def calculate_full_months(start_date, end_date):
|
||
"""Рассчитывает полные месяцы между датами"""
|
||
if start_date > end_date:
|
||
return 0
|
||
|
||
full_months = (end_date.year - start_date.year) * 12 + (end_date.month - start_date.month)
|
||
|
||
# Если текущий день меньше начального, вычитаем месяц
|
||
if end_date.day < start_date.day:
|
||
full_months -= 1
|
||
|
||
return max(0, full_months)
|
||
|
||
def format_month_count(months):
|
||
"""Форматирует количество месяцев с правильным окончанием"""
|
||
if months % 10 == 1 and months % 100 != 11:
|
||
return f"{months} месяц"
|
||
elif 2 <= months % 10 <= 4 and (months % 100 < 10 or months % 100 >= 20):
|
||
return f"{months} месяца"
|
||
else:
|
||
return f"{months} месяцев"
|
||
|
||
def format_year_count(years):
|
||
"""Форматирует количество лет с правильным окончанием"""
|
||
if years % 10 == 1 and years % 100 != 11:
|
||
return f"{years} год"
|
||
elif 2 <= years % 10 <= 4 and (years % 100 < 10 or years % 100 >= 20):
|
||
return f"{years} года"
|
||
else:
|
||
return f"{years} лет"
|
||
|
||
def format_day_count(days):
|
||
"""Форматирует количество дней с правильным окончанием"""
|
||
if days % 10 == 1 and days % 100 != 11:
|
||
return f"{days} день"
|
||
elif 2 <= days % 10 <= 4 and (days % 100 < 10 or days % 100 >= 20):
|
||
return f"{days} дня"
|
||
else:
|
||
return f"{days} дней"
|
||
|
||
# === ОБРАБОТЧИКИ КОМАНД ===
|
||
|
||
async def start(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||
"""Обработчик команды /start"""
|
||
logger.info(f"Команда /start от пользователя {update.effective_chat.id}")
|
||
await update.message.reply_text(
|
||
"💖 Привет! Я бот для отслеживания особых дат 💖\n\n"
|
||
"📌 Добавить дату: /adddate <название> <ГГГГ-ММ-ДД>\n"
|
||
"🔎 Посмотреть все даты: /mydates\n"
|
||
"📊 Статистика по дате: /stats <название>\n"
|
||
"🗑 Удалить дату: /remove <название>\n"
|
||
"ℹ️ Помощь: /help\n\n"
|
||
"⏰ Ежедневные уведомления в 09:00\n"
|
||
"📅 Ежемесячные отчеты 1 числа"
|
||
)
|
||
|
||
async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||
"""Обработчик команды /help"""
|
||
await update.message.reply_text(
|
||
"📋 Доступные команды:\n\n"
|
||
"/start - Начать работу с ботом\n"
|
||
"/help - Показать это сообщение\n"
|
||
"/adddate <название> <ГГГГ-ММ-ДД> - Добавить новую дату\n"
|
||
"/mydates - Показать все ваши даты\n"
|
||
"/stats <название> - Подробная статистика по дате\n"
|
||
"/remove <название> - Удалить дату\n\n"
|
||
"Пример:\n"
|
||
"/adddate Знакомство 2020-02-29\n"
|
||
"/stats Знакомство\n\n"
|
||
"📅 Бот учитывает високосные годы и корректно считает месяцы!"
|
||
)
|
||
|
||
async def add_date(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||
"""Обработчик команды /adddate"""
|
||
user_id = str(update.effective_chat.id)
|
||
|
||
if len(context.args) < 2:
|
||
await update.message.reply_text("❌ Неверный формат!\nИспользуйте: /adddate <название> <ГГГГ-ММ-ДД>\nПример: /adddate Свадьба 2023-06-15")
|
||
return
|
||
|
||
# Название даты может состоять из нескольких слов
|
||
date_str = context.args[-1]
|
||
label = " ".join(context.args[:-1]).strip()
|
||
|
||
if not label:
|
||
await update.message.reply_text("❌ Укажите название даты!\nПример: /adddate Первое свидание 2020-03-01")
|
||
return
|
||
|
||
try:
|
||
d = datetime.strptime(date_str, "%Y-%m-%d").date()
|
||
today = date.today()
|
||
|
||
# Проверка, что дата не в будущем
|
||
if d > today:
|
||
await update.message.reply_text("❌ Дата не может быть в будущем!")
|
||
return
|
||
|
||
data = load_data()
|
||
if user_id not in data:
|
||
data[user_id] = {}
|
||
|
||
# Проверяем, существует ли уже дата с таким названием
|
||
if label in data[user_id]:
|
||
await update.message.reply_text(f"⚠️ Дата '{label}' уже существует! Используйте другое название.")
|
||
return
|
||
|
||
# Сохраняем дату
|
||
data[user_id][label] = d.isoformat()
|
||
save_data(data)
|
||
|
||
# Рассчитываем сколько прошло времени
|
||
years, months, days, total_days = calculate_precise_difference(d, today)
|
||
|
||
# Формируем сообщение о високосном годе
|
||
leap_info = ""
|
||
if is_leap_year(d.year):
|
||
leap_info = "\n⭐ Это был високосный год!"
|
||
if d.month == 2 and d.day == 29:
|
||
leap_info = "\n🎉 Особенная дата - 29 февраля високосного года!"
|
||
|
||
await update.message.reply_text(
|
||
f"✅ Дата '{label}' успешно сохранена!\n"
|
||
f"📅 {d.strftime('%d.%m.%Y')}{leap_info}\n"
|
||
f"⏳ Уже прошло: {format_year_count(years)}, {format_month_count(months)}, {format_day_count(days)}\n"
|
||
f"📊 Всего дней: {format_day_count(total_days)}"
|
||
)
|
||
|
||
logger.info(f"Пользователь {user_id} добавил дату '{label}': {d}")
|
||
|
||
except ValueError:
|
||
await update.message.reply_text("❌ Ошибка в формате даты!\nИспользуйте: ГГГГ-ММ-ДД\nПример: 2020-03-01")
|
||
except Exception as e:
|
||
logger.error(f"Ошибка в add_date: {e}")
|
||
await update.message.reply_text("❌ Произошла ошибка при сохранении даты")
|
||
|
||
async def my_dates(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||
"""Обработчик команды /mydates"""
|
||
user_id = str(update.effective_chat.id)
|
||
data = load_data()
|
||
|
||
logger.info(f"Данные для пользователя {user_id}: {json.dumps(data.get(user_id, {}), ensure_ascii=False)}")
|
||
|
||
if user_id not in data or not data[user_id]:
|
||
await update.message.reply_text("📭 У вас пока нет сохранённых дат.\nДобавьте первую: /adddate <название> <дата>")
|
||
return
|
||
|
||
today = date.today()
|
||
message = "📋 Ваши особые даты:\n\n"
|
||
|
||
for label, date_str in data[user_id].items():
|
||
logger.info(f"Обработка: '{label}' -> '{date_str}'")
|
||
|
||
try:
|
||
d = datetime.strptime(date_str, "%Y-%m-%d").date()
|
||
years, months, days, total_days = calculate_precise_difference(d, today)
|
||
full_months = calculate_full_months(d, today)
|
||
|
||
# Иконки для особых дат
|
||
icon = "⭐" if is_leap_year(d.year) else "•"
|
||
if d.month == 2 and d.day == 29:
|
||
icon = "🎉"
|
||
|
||
message += f"{icon} {label}\n"
|
||
message += f" 📅 {d.strftime('%d.%m.%Y')}\n"
|
||
message += f" ⏳ {format_year_count(years)}, {format_month_count(months)}, {format_day_count(days)}\n"
|
||
message += f" 📈 Всего: {format_day_count(total_days)} ({format_month_count(full_months)} полных)\n\n"
|
||
|
||
except Exception as e:
|
||
logger.error(f"Ошибка обработки даты {label}: {e}")
|
||
message += f"⚠️ {label} - ошибка формата\n\n"
|
||
|
||
message += "📊 Подробная статистика: /stats <название>"
|
||
|
||
try:
|
||
await update.message.reply_text(message)
|
||
except Exception as e:
|
||
logger.error(f"Ошибка отправки my_dates: {e}")
|
||
await update.message.reply_text("❌ Ошибка при формировании списка дат")
|
||
|
||
async def stats_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||
"""Обработчик команды /stats"""
|
||
user_id = str(update.effective_chat.id)
|
||
|
||
if not context.args:
|
||
await update.message.reply_text("❌ Укажите название даты: /stats <название>")
|
||
return
|
||
|
||
label = " ".join(context.args).strip()
|
||
data = load_data()
|
||
|
||
if user_id not in data:
|
||
await update.message.reply_text("❌ У вас нет сохранённых дат!\nДобавьте первую: /adddate <название> <дата>")
|
||
return
|
||
|
||
# Поиск даты (регистронезависимый)
|
||
found_label = None
|
||
for stored_label in data[user_id].keys():
|
||
if stored_label.lower() == label.lower():
|
||
found_label = stored_label
|
||
break
|
||
|
||
if not found_label:
|
||
await update.message.reply_text(f"❌ Дата '{label}' не найдена!\nПосмотреть все даты: /mydates")
|
||
return
|
||
|
||
date_str = data[user_id][found_label]
|
||
d = datetime.strptime(date_str, "%Y-%m-%d").date()
|
||
today = date.today()
|
||
|
||
years, months, days, total_days = calculate_precise_difference(d, today)
|
||
full_months = calculate_full_months(d, today)
|
||
|
||
# Рассчитываем следующий годовщину
|
||
next_anniversary = d.replace(year=today.year)
|
||
if next_anniversary < today:
|
||
next_anniversary = d.replace(year=today.year + 1)
|
||
elif next_anniversary == today:
|
||
next_anniversary = d.replace(year=today.year + 1)
|
||
|
||
days_to_anniversary = (next_anniversary - today).days
|
||
|
||
# Рассчитываем до следующего месяца
|
||
if today.day >= d.day:
|
||
next_month_date = today.replace(day=min(d.day, calendar.monthrange(today.year, today.month)[1]))
|
||
if next_month_date <= today:
|
||
if today.month == 12:
|
||
next_month_date = date(today.year + 1, 1, min(d.day, calendar.monthrange(today.year + 1, 1)[1]))
|
||
else:
|
||
next_month_date = date(today.year, today.month + 1, min(d.day, calendar.monthrange(today.year, today.month + 1)[1]))
|
||
else:
|
||
next_month_date = today.replace(day=min(d.day, calendar.monthrange(today.year, today.month)[1]))
|
||
|
||
days_to_month = (next_month_date - today).days
|
||
|
||
# Информация о високосном годе
|
||
leap_info = ""
|
||
if is_leap_year(d.year):
|
||
leap_info = "\n⭐ Это был високосный год!"
|
||
if d.month == 2 and d.day == 29:
|
||
leap_info = "\n🎉 Особенная дата - 29 февраля високосного года!"
|
||
|
||
# Расчет следующего 29 февраля
|
||
next_feb29 = None
|
||
if d.month == 2 and d.day == 29:
|
||
year = today.year
|
||
while True:
|
||
if is_leap_year(year) and date(year, 2, 29) > today:
|
||
next_feb29 = date(year, 2, 29)
|
||
break
|
||
year += 1
|
||
|
||
message = f"📊 Статистика: {found_label}\n\n"
|
||
message += f"📅 Дата: {d.strftime('%d.%m.%Y')}{leap_info}\n\n"
|
||
|
||
message += f"⏳ Прошло времени:\n"
|
||
message += f" • {format_year_count(years)}, {format_month_count(months)}, {format_day_count(days)}\n"
|
||
message += f" • {format_month_count(full_months)} полностью\n"
|
||
message += f" • {format_day_count(total_days)} всего\n\n"
|
||
|
||
message += f"📈 Ближайшие события:\n"
|
||
if days_to_anniversary == 0:
|
||
message += f" 🎉 СЕГОДНЯ ГОДОВЩИНА! {format_year_count(years+1)} вместе! 🥳\n"
|
||
else:
|
||
message += f" • До годовщины: {format_day_count(days_to_anniversary)}\n"
|
||
|
||
if days_to_month == 0 and days_to_anniversary != 0:
|
||
message += f" 🎉 СЕГОДНЯ МЕСЯЧНАЯ ГОДОВЩИНА! {format_month_count(full_months+1)} вместе! 💫\n"
|
||
elif days_to_month > 0:
|
||
message += f" • До месячной годовщины: {format_day_count(days_to_month)}\n"
|
||
|
||
if next_feb29:
|
||
days_to_feb29 = (next_feb29 - today).days
|
||
message += f" • До след. 29 февраля: {format_day_count(days_to_feb29)}\n"
|
||
|
||
await update.message.reply_text(message)
|
||
|
||
async def remove_date(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||
"""Обработчик команды /remove"""
|
||
user_id = str(update.effective_chat.id)
|
||
|
||
if not context.args:
|
||
await update.message.reply_text("❌ Укажите название даты: /remove <название>")
|
||
return
|
||
|
||
label = " ".join(context.args).strip()
|
||
data = load_data()
|
||
|
||
if user_id not in data:
|
||
await update.message.reply_text("❌ У вас нет сохранённых дат!")
|
||
return
|
||
|
||
# Поиск даты (регистронезависимый)
|
||
found_label = None
|
||
for stored_label in data[user_id].keys():
|
||
if stored_label.lower() == label.lower():
|
||
found_label = stored_label
|
||
break
|
||
|
||
if not found_label:
|
||
await update.message.reply_text(f"❌ Дата '{label}' не найдена!")
|
||
return
|
||
|
||
del data[user_id][found_label]
|
||
|
||
# Если у пользователя больше нет дат, удаляем его запись
|
||
if not data[user_id]:
|
||
del data[user_id]
|
||
|
||
save_data(data)
|
||
await update.message.reply_text(f"✅ Дата '{found_label}' успешно удалена!")
|
||
|
||
# === ФОНОВЫЕ ЗАДАЧИ ===
|
||
|
||
async def daily_reminder(app):
|
||
"""Ежедневные уведомления"""
|
||
logger.info("Запущена задача ежедневных уведомлений")
|
||
|
||
last_notification_day = None
|
||
|
||
while True:
|
||
try:
|
||
now = datetime.now()
|
||
current_time = now.time()
|
||
today = now.date()
|
||
|
||
# Проверяем время для ежедневных уведомлений (09:00)
|
||
if (current_time.hour == NOTIFY_TIME.hour and
|
||
current_time.minute == NOTIFY_TIME.minute and
|
||
last_notification_day != today):
|
||
|
||
last_notification_day = today
|
||
data = load_data()
|
||
logger.info(f"Отправка ежедневных уведомлений на {today}")
|
||
|
||
for user_id, dates in data.items():
|
||
if not dates:
|
||
continue
|
||
|
||
message = f"💝 Доброе утро! {today.strftime('%d.%m.%Y')}\n\n"
|
||
|
||
for label, date_str in dates.items():
|
||
d = datetime.strptime(date_str, "%Y-%m-%d").date()
|
||
years, months, days, total_days = calculate_precise_difference(d, today)
|
||
full_months = calculate_full_months(d, today)
|
||
|
||
message += f"📅 {label}\n"
|
||
message += f" ⏳ {format_year_count(years)}, {format_month_count(months)}, {format_day_count(days)}\n"
|
||
|
||
# Проверка на годовщину
|
||
if days == 0 and months == 0 and years > 0:
|
||
message += f" 🎊 СЕГОДНЯ {format_year_count(years)} ВМЕСТЕ! 🥳\n"
|
||
# Проверка на месячную годовщину
|
||
elif days == 0 and months > 0:
|
||
message += f" 🎉 СЕГОДНЯ {format_month_count(full_months)} ВМЕСТЕ! 💫\n"
|
||
|
||
message += f" 📊 Всего: {format_day_count(total_days)}\n\n"
|
||
|
||
message += "💖 Хорошего дня! 💖"
|
||
try:
|
||
await app.bot.send_message(chat_id=int(user_id), text=message)
|
||
await asyncio.sleep(0.3)
|
||
except Exception as e:
|
||
logger.error(f"Ошибка отправки для {user_id}: {e}")
|
||
|
||
await asyncio.sleep(60)
|
||
|
||
await asyncio.sleep(30)
|
||
|
||
except Exception as e:
|
||
logger.error(f"Ошибка в daily_reminder: {e}")
|
||
await asyncio.sleep(60)
|
||
|
||
async def monthly_report(app):
|
||
"""Ежемесячные отчеты 1 числа"""
|
||
logger.info("Запущена задача ежемесячных отчетов")
|
||
|
||
last_report_month = None
|
||
|
||
while True:
|
||
try:
|
||
now = datetime.now()
|
||
today = now.date()
|
||
|
||
# Проверяем, наступило ли 1 число нового месяца
|
||
if (today.day == MONTHLY_REPORT_DAY and
|
||
(last_report_month is None or last_report_month != now.month)):
|
||
|
||
last_report_month = now.month
|
||
data = load_data()
|
||
logger.info(f"Отправка ежемесячных отчетов за {now.strftime('%B %Y')}")
|
||
|
||
for user_id, dates in data.items():
|
||
if not dates:
|
||
continue
|
||
|
||
message = f"📊 Ежемесячный отчет на {today.strftime('%d.%m.%Y')}\n\n"
|
||
|
||
for label, date_str in dates.items():
|
||
d = datetime.strptime(date_str, "%Y-%m-%d").date()
|
||
full_months = calculate_full_months(d, today)
|
||
years, months, days, total_days = calculate_precise_difference(d, today)
|
||
|
||
message += f"📅 {label}\n"
|
||
message += f" Начало: {d.strftime('%d.%m.%Y')}\n"
|
||
message += f" Полных месяцев: {format_month_count(full_months)}\n"
|
||
message += f" Всего дней: {format_day_count(total_days)}\n\n"
|
||
|
||
message += "💖 Пусть каждый месяц будет счастливым! 💖"
|
||
|
||
try:
|
||
await app.bot.send_message(chat_id=int(user_id), text=message)
|
||
await asyncio.sleep(0.3)
|
||
except Exception as e:
|
||
logger.error(f"Ошибка отправки месячного отчета для {user_id}: {e}")
|
||
|
||
await asyncio.sleep(60 * 60 * 24)
|
||
|
||
await asyncio.sleep(60 * 60)
|
||
|
||
except Exception as e:
|
||
logger.error(f"Ошибка в monthly_report: {e}")
|
||
await asyncio.sleep(60 * 60)
|
||
|
||
# === ОБРАБОТЧИК ОШИБОК ===
|
||
|
||
async def error_handler(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||
"""Обработчик ошибок"""
|
||
logger.error(f"Ошибка при обработке обновления: {context.error}")
|
||
|
||
if update and update.effective_chat:
|
||
try:
|
||
await context.bot.send_message(
|
||
chat_id=update.effective_chat.id,
|
||
text="😔 Произошла ошибка при обработке запроса. Попробуйте позже."
|
||
)
|
||
except Exception as e:
|
||
logger.error(f"Не удалось отправить сообщение об ошибке: {e}")
|
||
|
||
# === СТАРТ И ЗАВЕРШЕНИЕ ===
|
||
|
||
async def on_startup(app):
|
||
"""Запуск фоновых задач при старте"""
|
||
logger.info("=" * 50)
|
||
logger.info("Запуск Love Date Bot")
|
||
logger.info(f"Токен: {TOKEN[:10]}...")
|
||
logger.info(f"Рабочая директория: {os.getcwd()}")
|
||
logger.info(f"Файл данных: {DATA_FILE}")
|
||
logger.info(f"Файл логов: {LOG_FILE}")
|
||
logger.info("=" * 50)
|
||
|
||
# Загружаем и проверяем данные
|
||
data = load_data()
|
||
user_count = len(data)
|
||
date_count = sum(len(dates) for dates in data.values())
|
||
logger.info(f"Загружено {user_count} пользователей с {date_count} датами")
|
||
|
||
# Запускаем фоновые задачи
|
||
asyncio.create_task(daily_reminder(app))
|
||
asyncio.create_task(monthly_report(app))
|
||
logger.info("Бот запущен и готов к работе!")
|
||
|
||
async def on_shutdown(app):
|
||
"""Действия при завершении работы"""
|
||
logger.info("Бот завершает работу...")
|
||
save_data(load_data()) # Сохраняем данные перед выходом
|
||
logger.info("Данные сохранены")
|
||
|
||
def main():
|
||
try:
|
||
logger.info("Инициализация бота...")
|
||
|
||
# Проверка интернет-соединения
|
||
try:
|
||
import socket
|
||
socket.create_connection(("api.telegram.org", 443), timeout=10)
|
||
logger.info("✓ Соединение с Telegram API доступно")
|
||
except Exception as e:
|
||
logger.error(f"✗ Нет соединения с Telegram API: {e}")
|
||
|
||
app = ApplicationBuilder() \
|
||
.token(TOKEN) \
|
||
.post_init(on_startup) \
|
||
.post_shutdown(on_shutdown) \
|
||
.build()
|
||
|
||
# Регистрация обработчиков команд
|
||
app.add_handler(CommandHandler("start", start))
|
||
app.add_handler(CommandHandler("help", help_command))
|
||
app.add_handler(CommandHandler("adddate", add_date))
|
||
app.add_handler(CommandHandler("mydates", my_dates))
|
||
app.add_handler(CommandHandler("stats", stats_command))
|
||
app.add_handler(CommandHandler("remove", remove_date))
|
||
|
||
# Регистрация обработчика ошибок
|
||
app.add_error_handler(error_handler)
|
||
|
||
logger.info("Запуск бота в режиме polling...")
|
||
app.run_polling(
|
||
drop_pending_updates=True,
|
||
allowed_updates=Update.ALL_TYPES,
|
||
close_loop=False,
|
||
pool_timeout=30,
|
||
connect_timeout=30,
|
||
read_timeout=30
|
||
)
|
||
|
||
except Exception as e:
|
||
logger.error(f"Критическая ошибка при запуске бота: {e}")
|
||
sys.exit(1)
|
||
|
||
if __name__ == "__main__":
|
||
main() |