Обновить bot-tg.py

This commit is contained in:
yogurtmenn 2025-03-25 10:18:53 +00:00
parent d996844726
commit d979c105c6

259
bot-tg.py
View file

@ -1,122 +1,189 @@
import logging import logging
from logging.handlers import RotatingFileHandler from aiogram import Bot, Dispatcher, types, F
from aiogram import Bot, Dispatcher, types
from aiogram.filters import Command from aiogram.filters import Command
from aiogram.utils.keyboard import ReplyKeyboardBuilder from aiogram.utils.keyboard import ReplyKeyboardBuilder
import requests import requests
import asyncio import asyncio
from config import BOT_TOKEN, WEATHER_API_KEY
# настройка логов # Настройка логирования
logger = logging.getLogger() logging.basicConfig(
logger.setLevel(logging.DEBUG) level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
formatter = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(message)s'
) )
logger = logging.getLogger(__name__)
# логирование в файл, максимум 5мб по 3 бэкапа # Инициализация бота
file_handler = RotatingFileHandler(
'weather_bot.log',
maxBytes=5*1024*1024,
backupCount=3,
encoding='utf-8'
)
file_handler.setFormatter(formatter)
# вывод в консоль
console_handler = logging.StreamHandler()
console_handler.setFormatter(formatter)
logger.addHandler(file_handler)
logger.addHandler(console_handler)
# конфиг
try:
from config import BOT_TOKEN, WEATHER_API_KEY
except ImportError:
logging.critical("Ошибка: Создайте файл config.py с BOT_TOKEN и WEATHER_API_KEY!")
exit(1)
# инициализация бота
bot = Bot(token=BOT_TOKEN) bot = Bot(token=BOT_TOKEN)
dp = Dispatcher() dp = Dispatcher()
# клавиатура # Клавиатура
builder = ReplyKeyboardBuilder() def get_keyboard():
builder.button(text="Узнать погоду") builder = ReplyKeyboardBuilder()
builder.button(text="Помощь") builder.button(text="🌤️ Узнать погоду")
keyboard = builder.as_markup(resize_keyboard=True) builder.button(text="📍 Мое местоположение", request_location=True)
return builder.as_markup(resize_keyboard=True)
# обработка команд async def get_coordinates(city: str):
@dp.message(Command("start", "help")) """Получение координат города через Nominatim"""
async def cmd_start(message: types.Message): url = f"https://nominatim.openstreetmap.org/search?city={city}&format=json"
logging.info(f"Новый пользователь: {message.from_user.id}") headers = {"User-Agent": "CityPhotoBot/1.0"}
await message.answer(
"Бот Погоды\n\n"
"Отправьте мне название города, и я пришлю текущую погоду.\n"
"Или нажмите кнопку ниже:",
reply_markup=keyboard
)
@dp.message(lambda message: message.text in ["Узнать погоду", "Помощь"])
async def button_handler(message: types.Message):
if message.text == "Узнать погоду":
await message.answer("Введите название города:")
else:
await cmd_start(message)
@dp.message()
async def get_weather(message: types.Message):
city = message.text.strip()
if not city:
return
logging.info(f"Запрос погоды для: {city} (от {message.from_user.id})")
try: try:
# запрос к API response = requests.get(url, headers=headers, timeout=10)
url = f"https://api.openweathermap.org/data/2.5/weather?q={city}&appid={WEATHER_API_KEY}&units=metric&lang=ru" if response.status_code == 200:
response = requests.get(url, timeout=10)
logging.debug(f"API Response: {response.status_code} {response.text[:200]}...")
if response.status_code != 200:
error_msg = response.json().get('message', 'Unknown error')
logging.error(f"API Error for {city}: {response.status_code} - {error_msg}")
await message.answer(f"Ошибка: {error_msg.capitalize()}")
return
data = response.json() 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:
weather_report = ( """Получение данных о погоде"""
f"Город: {data['name']}\n" url = f"https://api.openweathermap.org/data/2.5/weather?lat={lat}&lon={lon}&appid={WEATHER_API_KEY}&units=metric&lang=ru"
f"Температура: {data['main']['temp']:.1f}°C\n"
f"Ощущается как: {data['main']['feels_like']:.1f}°C\n" try:
f"Ветер: {data['wind']['speed']} м/с\n" response = requests.get(url, timeout=10)
f"Погода: {data['weather'][0]['description'].capitalize()}\n" if response.status_code == 200:
f"Влажность: {data['main']['humidity']}%" 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()
) )
logging.info(f"Успешный ответ для {city}") @dp.message(F.text == "🌤️ Узнать погоду")
await message.answer(weather_report) async def weather_button(message: types.Message):
await message.answer("Введите точное название города:")
except requests.exceptions.Timeout: @dp.message(F.location)
logging.error(f"Таймаут запроса для {city}") async def handle_location(message: types.Message):
await message.answer("Сервер погоды не отвечает. Попробуйте позже.") """Обработка геолокации"""
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: except Exception as e:
logging.exception(f"Ошибка для {city}:") logger.error(f"Location error: {e}")
await message.answer("⚠️ Произошла внутренняя ошибка. Попробуйте другой город.") 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(): async def main():
logging.info("Starting bot...")
await dp.start_polling(bot) await dp.start_polling(bot)
if __name__ == "__main__": if __name__ == "__main__":
try:
asyncio.run(main()) asyncio.run(main())
except KeyboardInterrupt:
logging.info("Bot stopped by user")
except Exception as e:
logging.critical(f"Fatal error: {e}")