import logging from aiogram import Bot, Dispatcher, types, F from aiogram.filters import Command from aiogram.utils.keyboard import ReplyKeyboardBuilder import requests import asyncio from config import BOT_TOKEN, WEATHER_API_KEY # Настройка логирования logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' ) logger = logging.getLogger(__name__) # Инициализация бота bot = Bot(token=BOT_TOKEN) dp = Dispatcher() # Клавиатура def get_keyboard(): builder = ReplyKeyboardBuilder() builder.button(text="🌤️ Узнать погоду") builder.button(text="📍 Мое местоположение", request_location=True) return builder.as_markup(resize_keyboard=True) async def get_coordinates(city: str): """Получение координат города через Nominatim""" url = f"https://nominatim.openstreetmap.org/search?city={city}&format=json" headers = {"User-Agent": "CityPhotoBot/1.0"} try: response = requests.get(url, headers=headers, timeout=10) if response.status_code == 200: data = response.json() if data: for item in data: if item.get('type') in ['city', 'town']: return float(item['lon']), float(item['lat']) return float(data[0]['lon']), float(data[0]['lat']) except Exception as e: logger.error(f"Geocoding error: {e}") return None async def get_weather(lat: float, lon: float, city_name: str) -> str: """Получение данных о погоде""" url = f"https://api.openweathermap.org/data/2.5/weather?lat={lat}&lon={lon}&appid={WEATHER_API_KEY}&units=metric&lang=ru" try: response = requests.get(url, timeout=10) if response.status_code == 200: data = response.json() weather_info = ( f"🌆 Город: {city_name}\n" f"🌡 Температура: {data['main']['temp']:.1f}°C\n" f"🧭 Ощущается как: {data['main']['feels_like']:.1f}°C\n" f"💨 Ветер: {data['wind']['speed']} м/с\n" f"☁️ Погода: {data['weather'][0]['description'].capitalize()}\n" f"💧 Влажность: {data['main']['humidity']}%" ) return weather_info except Exception as e: logger.error(f"Weather API error: {e}") return "Не удалось получить данные о погоде" async def get_city_photo(city: str, lat: float = None, lon: float = None): """Поиск фотографии города через Wikipedia и другие источники""" # 1. Попробуем Wikipedia wikipedia_url = f"https://ru.wikipedia.org/w/api.php?action=query&prop=pageimages&titles={city}&pithumbsize=800&format=json" try: response = requests.get(wikipedia_url, timeout=10) if response.status_code == 200: data = response.json() pages = data.get('query', {}).get('pages', {}) for page in pages.values(): if 'thumbnail' in page: return page['thumbnail']['source'], "Wikipedia" except Exception as e: logger.error(f"Wikipedia API error: {e}") # 2. Попробуем Wikimedia Commons wikimedia_url = f"https://commons.wikimedia.org/w/api.php?action=query&generator=images&titles={city}&prop=imageinfo&iiprop=url&iiurlwidth=800&format=json" try: response = requests.get(wikimedia_url, timeout=10) if response.status_code == 200: data = response.json() pages = data.get('query', {}).get('pages', {}) for page in pages.values(): if 'imageinfo' in page: return page['imageinfo'][0]['thumburl'], "Wikimedia Commons" except Exception as e: logger.error(f"Wikimedia API error: {e}") # 3. Для российских городов попробуем PhotoBank RG if "россия" in city.lower() or any(city.lower().endswith(x) for x in ["ск", "ов", "ев", "ин"]): try: photobank_url = f"https://rusneb.ru/api/v1/photos?query={city}&limit=1" response = requests.get(photobank_url, timeout=10) if response.status_code == 200: data = response.json() if data.get('results'): return data['results'][0]['image_url'], "PhotoBank RG" except Exception as e: logger.error(f"PhotoBank error: {e}") return None, None @dp.message(Command("start", "help")) async def cmd_start(message: types.Message): await message.answer( "🏙️ Бот Погоды с фотографиями городов\n\n" "Отправьте мне название города или поделитесь геопозицией, " "и я покажу погоду и фото города:", reply_markup=get_keyboard() ) @dp.message(F.text == "🌤️ Узнать погоду") async def weather_button(message: types.Message): await message.answer("Введите точное название города:") @dp.message(F.location) async def handle_location(message: types.Message): """Обработка геолокации""" try: lat = message.location.latitude lon = message.location.longitude # Получаем название города url = f"https://nominatim.openstreetmap.org/reverse?lat={lat}&lon={lon}&format=json" headers = {"User-Agent": "CityPhotoBot/1.0"} response = requests.get(url, headers=headers, timeout=10) if response.status_code == 200: data = response.json() city_name = data.get('address', {}).get('city', data.get('address', {}).get('town', data.get('address', {}).get('village', "это место"))) weather_data = await get_weather(lat, lon, city_name) photo_url, source = await get_city_photo(city_name, lat, lon) if photo_url: caption = f"{weather_data}\n\n📷 Источник: {source}" if source else weather_data await message.answer_photo(photo_url, caption=caption) else: await message.answer(f"{weather_data}\n\n(Не удалось найти фото города)") except Exception as e: logger.error(f"Location error: {e}") await message.answer("Ошибка обработки местоположения") @dp.message(F.text) async def city_handler(message: types.Message): """Обработка текстовых запросов""" city = message.text.strip() if city.startswith('/'): return logger.info(f"Processing city: {city}") try: coords = await get_coordinates(city) if not coords: await message.answer("Город не найден. Попробуйте уточнить название") return lon, lat = coords weather_data = await get_weather(lat, lon, city) # Пробуем получить фото с 3 попытками for attempt in range(3): photo_url, source = await get_city_photo(city, lat, lon) if photo_url: caption = f"{weather_data}\n\n📷 Источник: {source}" if source else weather_data await message.answer_photo(photo_url, caption=caption) return await asyncio.sleep(1) await message.answer(f"{weather_data}\n\n(Не удалось найти фото города)") except Exception as e: logger.error(f"City handler error: {e}") await message.answer("Произошла ошибка при обработке запроса") async def main(): await dp.start_polling(bot) if __name__ == "__main__": asyncio.run(main())