У меня есть код:

import os import logging from dotenv import load_dotenv import requests from telegram import ReplyKeyboardMarkup, Update, ReplyKeyboardRemove from telegram.ext import ( Application, CommandHandler, MessageHandler, filters, ContextTypes, ConversationHandler, CallbackContext, ) from datetime import datetime, timedelta, time import pytz import asyncio

Настройка логгирования
logging.basicConfig( format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO ) logger = logging.getLogger(name)

Загружаем переменные из .env
load_dotenv() BOT_TOKEN = os.getenv('BOT_TOKEN') WEATHER_API_KEY = os.getenv('WEATHER_API_KEY')

Константы
DEMBEL_DATE = datetime(2025, 12, 6) # Дата дембеля MOSCOW_TZ = pytz.timezone('Europe/Moscow') # Часовой пояс Москвы

Мотивирующие фразы
MOTIVATIONAL_PHRASES = [ "💪 Ты сильнее, чем думаешь! Каждый день - шаг к цели!", "🔥 Осталось совсем немного! Ты справишься!", "🚀 Сегодняшний день приближает тебя к заветной дате!", "🦾 Терпение и труд всё перетрут! Держись!", "🌟 Ты уже прошел большую часть пути! Осталось чуть-чуть!", "🏆 Каждый день - это маленькая победа!", "💯 Ты молодец! Скоро долгожданный день!", ]

Состояния для ConversationHandler
SELECTING_ACTION, TYPING_CITY = range(2)

Эмодзи для оформления
WEATHER_EMOJI = { 'clear': '☀️', 'clouds': '☁️', 'rain': '🌧️', 'snow': '❄️', 'thunderstorm': '⛈️', 'drizzle': '🌦️', 'mist': '🌫️', 'default': '🌈' }

Клавиатура с городами
reply_keyboard = [ ["Ростов-на-Дону 🌊", "Анапа 🏖️"], ["Поиск 🔍"], ] markup = ReplyKeyboardMarkup(reply_keyboard, one_time_keyboard=True, resize_keyboard=True)

def get_days_until_dembel() -> int: """Возвращает количество дней до дембеля""" now = datetime.now(MOSCOW_TZ) delta = DEMBEL_DATE - now return delta.days

def get_motivational_phrase() -> str: """Возвращает случайную мотивирующую фразу""" now = datetime.now() return MOTIVATIONAL_PHRASES[now.weekday() % len(MOTIVATIONAL_PHRASES)]

async def send_daily_notification(context: CallbackContext) -> None: """Отправляет ежедневное уведомление""" try: chat_id = context.job.chat_id days_left = get_days_until_dembel() phrase = get_motivational_phrase()

    message = (
        f"⏳ До дембеля осталось {days_left} дней!\n"
        f"🗓 Дата: {DEMBEL_DATE.strftime('%d.%m.%Y')}\n\n"
        f"{phrase}"
    )
    
    await context.bot.send_message(chat_id=chat_id, text=message)
    logger.info(f"Отправлено ежедневное уведомление пользователю {chat_id}")
    
except Exception as e:
    logger.error(f"Ошибка при отправке уведомления: {e}")
def get_weather_emoji(description: str) -> str: """Возвращает соответствующий эмодзи для погодных условий""" description = description.lower() if 'ясно' in description: return WEATHER_EMOJI['clear'] elif 'облачно' in description or 'пасмурно' in description: return WEATHER_EMOJI['clouds'] elif 'дождь' in description: return WEATHER_EMOJI['rain'] elif 'снег' in description: return WEATHER_EMOJI['snow'] elif 'гроза' in description: return WEATHER_EMOJI['thunderstorm'] elif 'морось' in description: return WEATHER_EMOJI['drizzle'] elif 'туман' in description or 'дымка' in description: return WEATHER_EMOJI['mist'] else: return WEATHER_EMOJI['default']

def get_clothing_recommendation(temp: float, weather_desc: str) -> str: """Возвращает рекомендации по одежде""" weather_desc = weather_desc.lower()

if temp > 25:
    clothes = (
        "👕 Легкая одежда (шорты/юбка, футболка)\n"
        "🧢 Головной убор от солнца\n"
        "🕶️ Солнцезащитные очки"
    )
elif 18 <= temp <= 25:
    clothes = (
        "👕 Летняя одежда (рубашка/блузка)\n"
        "👖 Джинсы или легкие брюки"
    )
elif 10 <= temp < 18:
    clothes = (
        "🧥 Легкая куртка или ветровка\n"
        "👖 Джинсы или брюки\n"
        "👟 Закрытая обувь"
    )
elif 0 <= temp < 10:
    clothes = (
        "🧥 Теплая куртка\n"
        "🧣 Шарф при ветре\n"
        "🧤 Перчатки, если холодно"
    )
else:
    clothes = (
        "🧥 Зимняя куртка\n"
        "🧣 Шарф\n"
        "🧤 Перчатки\n"
        "🎩 Теплая шапка"
    )

if 'дождь' in weather_desc:
    clothes += "\n☔ Зонт или дождевик"
elif 'снег' in weather_desc:
    clothes += "\n🧦 Теплые носки и зимняя обувь"
elif 'ясно' in weather_desc:
    clothes += "\n🧴 Не забудьте солнцезащитный крем"

return clothes
async def get_tomorrow_forecast(city: str) -> str: """Получает прогноз погоды на завтра""" try: url = f"http://api.openweathermap.org/data/2.5/forecast?q={city}&appid={WEATHER_API_KEY}&units=metric&lang=ru&cnt=16" response = requests.get(url, timeout=10) data = response.json()

    if data.get("cod") != "200":
        return ""
        
    tomorrow = (datetime.now() + timedelta(days=1)).date()
    temps = []
    descriptions = []
    
    for item in data['list']:
        forecast_date = datetime.fromtimestamp(item['dt']).date()
        if forecast_date == tomorrow:
            temps.append(item['main']['temp'])
            descriptions.append(item['weather'][0]['description'])
    
    if not temps:
        return ""
        
    avg_temp = round(sum(temps) / len(temps), 1)
    main_desc = max(set(descriptions), key=descriptions.count)
    emoji = get_weather_emoji(main_desc)
    
    return f"Завтра: {avg_temp}°C, {main_desc} {emoji}"
    
except Exception as e:
    logger.error(f"Ошибка получения прогноза на завтра: {e}")
    return ""
async def start(update: Update, context: CallbackContext) -> int: """Начало работы с ботом""" try: user = update.effective_user chat_id = update.effective_chat.id

    # Удаляем старое задание, если существует
    if 'job' in context.chat_data:
        old_job = context.chat_data['job']
        old_job.schedule_removal()

    # Создаем новое задание
time_utc = time(hour=3, minute=45) # 6:45 по МСК (UTC+3) new_job = context.job_queue.run_daily( send_daily_notification, time=time_utc, days=(0, 1, 2, 3, 4, 5, 6), chat_id=chat_id, name=str(chat_id) ) # Закрываем скобку здесь # Сохраняем задание в контексте context.chat_data['job'] = new_job

    days_left = get_days_until_dembel()
    phrase = get_motivational_phrase()
    
    await update.message.reply_text(
        f"Привет, {user.first_name}! 👋\n"
        f"⏳ До дембеля осталось {days_left} дней!\n"
        f"🗓 Дата: {DEMBEL_DATE.strftime('%d.%m.%Y')}\n\n"
        f"{phrase}\n\n"
        "Я бот, который покажет тебе текущую погоду и подскажу что надеть.\n"
        "Выбери город из списка или найди другой:",
        reply_markup=markup,
    )
    return SELECTING_ACTION
except Exception as e:
    logger.error(f"Ошибка в start: {str(e)}")
    await update.message.reply_text("😔 Произошла ошибка. Попробуйте /start")
    return ConversationHandler.END
async def get_weather(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int: """Получает погоду для выбранного города""" try: city = update.message.text

    if city == "Поиск 🔍":
        await update.message.reply_text(
            "🔍 Введите название города (например: Москва, Сочи):",
            reply_markup=ReplyKeyboardRemove(),
        )
        return TYPING_CITY
    
    # Определяем город для API
    if "Ростов" in city:
        city_api = "Rostov-on-Don"
        city_display = "Ростове-на-Дону 🌊"
    elif "Анапа" in city:
        city_api = "Anapa"
        city_display = "Анапе 🏖️"
    else:
        city_api = city
        city_display = city
    
    await update.message.reply_chat_action(action="typing")
    
    # Текущая погода
    current_url = f"http://api.openweathermap.org/data/2.5/weather?q={city_api}&appid={WEATHER_API_KEY}&units=metric&lang=ru"
    current_response = requests.get(current_url, timeout=10)
    current_data = current_response.json()
    
    if current_data.get("cod") != 200:
        error = current_data.get("message", "неизвестная ошибка")
        await update.message.reply_text(
            f"⚠️ Ошибка: {error}\nПопробуйте другой город.",
            reply_markup=markup,
        )
        return SELECTING_ACTION
    
    # Парсим данные
    weather_desc = current_data["weather"][0]["description"]
    temp = current_data["main"]["temp"]
    feels_like = current_data["main"]["feels_like"]
    humidity = current_data["main"]["humidity"]
    wind = current_data["wind"]["speed"]
    emoji = get_weather_emoji(weather_desc)
    
    # Получаем рекомендации по одежде
    clothes = get_clothing_recommendation(temp, weather_desc)
    
    # Получаем прогноз на завтра
    tomorrow_forecast = await get_tomorrow_forecast(city_api)
    
    # Формируем сообщение
    message = (
        f"{emoji} Погода в {city_display}:\n\n"
        f"• Сейчас: {temp}°C (ощущается как {feels_like}°C)\n"
        f"• Состояние: {weather_desc.capitalize()} {emoji}\n"
        f"• Влажность: {humidity}% 💧\n"
        f"• Ветер: {wind} м/с 💨\n\n"
    )
    
    if tomorrow_forecast:
        message += f"{tomorrow_forecast}\n\n"
    
    message += (
        f"Что надеть:\n{clothes}\n\n"
        f"Хотите узнать погоду в другом месте? Выберите город:"
    )
    
    await update.message.reply_text(message, reply_markup=markup)
    return SELECTING_ACTION
    
except requests.exceptions.Timeout:
    await update.message.reply_text(
        "⌛ Сервер погоды не отвечает. Попробуйте позже.",
        reply_markup=markup,
    )
    return SELECTING_ACTION
except Exception as e:
    logger.error(f"Ошибка в get_weather: {e}")
    await update.message.reply_text(
        "😔 Произошла ошибка. Попробуйте /start",
        reply_markup=markup,
    )
    return SELECTING_ACTION
async def search_city(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int: """Обрабатывает введенный пользователем город""" try: city = update.message.text return await get_weather(update, context) except Exception as e: logger.error(f"Ошибка в search_city: {e}") await update.message.reply_text( "😔 Ошибка обработки города. Попробуйте /start", reply_markup=markup, ) return SELECTING_ACTION

async def cancel(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int: """Завершает работу с ботом""" await update.message.reply_text( "До свидания! 👋 Нажмите /start для нового запроса.", reply_markup=ReplyKeyboardRemove(), ) return ConversationHandler.END

async def error_handler(update: object, context: ContextTypes.DEFAULT_TYPE) -> None: """Обрабатывает ошибки""" logger.error(f"Ошибка: {context.error}") if update: await update.message.reply_text( "😔 Произошла ошибка. Попробуйте /start", reply_markup=markup, )

def main() -> None: """Запуск бота""" try: application = Application.builder().token(BOT_TOKEN).build()

    conv_handler = ConversationHandler(
        entry_points=[CommandHandler("start", start)],
        states={
            SELECTING_ACTION: [
                MessageHandler(filters.Regex("^(Ростов-на-Дону|Анапа)"), get_weather),
                MessageHandler(filters.Regex("^Поиск 🔍$"), get_weather),
            ],
            TYPING_CITY: [
                MessageHandler(filters.TEXT & ~filters.COMMAND, search_city),
            ],
        },
        fallbacks=[CommandHandler("cancel", cancel)],
    )
    
    application.add_handler(conv_handler)
    application.add_error_handler(error_handler)
    
    logger.info("🤖 Бот запущен")
    application.run_polling()
    
except Exception as e:
    logger.critical(f"❌ Ошибка запуска: {e}")
finally:
    logger.info("🛑 Бот остановлен")
if name == "main": main()

Ошибка в терминале:

PS C:\Users\Rota-4\Desktop\WeatherBot> python weather_bot.py File "C:\Users\Rota-4\Desktop\WeatherBot\weather_bot.py", line 207 time_utc = time(hour=3, minute=45) # 6:45 по МСК (UTC+3) SyntaxError: expected 'except' or 'finally' block