Обновить bot-tg.py
This commit is contained in:
parent
d996844726
commit
d979c105c6
1 changed files with 169 additions and 102 deletions
271
bot-tg.py
271
bot-tg.py
|
@ -1,122 +1,189 @@
|
|||
import logging
|
||||
from logging.handlers import RotatingFileHandler
|
||||
from aiogram import Bot, Dispatcher, types
|
||||
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
|
||||
|
||||
# настройка логов
|
||||
logger = logging.getLogger()
|
||||
logger.setLevel(logging.DEBUG)
|
||||
|
||||
formatter = logging.Formatter(
|
||||
'%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
||||
# Настройка логирования
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(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)
|
||||
dp = Dispatcher()
|
||||
|
||||
# клавиатура
|
||||
builder = ReplyKeyboardBuilder()
|
||||
builder.button(text="Узнать погоду")
|
||||
builder.button(text="Помощь")
|
||||
keyboard = builder.as_markup(resize_keyboard=True)
|
||||
# Клавиатура
|
||||
def get_keyboard():
|
||||
builder = ReplyKeyboardBuilder()
|
||||
builder.button(text="🌤️ Узнать погоду")
|
||||
builder.button(text="📍 Мое местоположение", request_location=True)
|
||||
return builder.as_markup(resize_keyboard=True)
|
||||
|
||||
# обработка команд
|
||||
@dp.message(Command("start", "help"))
|
||||
async def cmd_start(message: types.Message):
|
||||
logging.info(f"Новый пользователь: {message.from_user.id}")
|
||||
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})")
|
||||
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:
|
||||
# запрос к API
|
||||
url = f"https://api.openweathermap.org/data/2.5/weather?q={city}&appid={WEATHER_API_KEY}&units=metric&lang=ru"
|
||||
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()
|
||||
|
||||
# формирование ответа
|
||||
weather_report = (
|
||||
f"Город: {data['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']}%"
|
||||
)
|
||||
|
||||
logging.info(f"Успешный ответ для {city}")
|
||||
await message.answer(weather_report)
|
||||
|
||||
except requests.exceptions.Timeout:
|
||||
logging.error(f"Таймаут запроса для {city}")
|
||||
await message.answer("Сервер погоды не отвечает. Попробуйте позже.")
|
||||
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:
|
||||
logging.exception(f"Ошибка для {city}:")
|
||||
await message.answer("⚠️ Произошла внутренняя ошибка. Попробуйте другой город.")
|
||||
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():
|
||||
logging.info("Starting bot...")
|
||||
await dp.start_polling(bot)
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
asyncio.run(main())
|
||||
except KeyboardInterrupt:
|
||||
logging.info("Bot stopped by user")
|
||||
except Exception as e:
|
||||
logging.critical(f"Fatal error: {e}")
|
||||
asyncio.run(main())
|
Loading…
Add table
Reference in a new issue