import os import datetime import pytz import requests from dotenv import load_dotenv from langdetect import detect from geopy.geocoders import Nominatim from timezonefinder import TimezoneFinder import gradio as gr import re load_dotenv() def geocode_city(city: str, lang: str = "en"): """ Geocode city with fallback to simpler name and language. """ geolocator = Nominatim(user_agent="weather_assistant") location = geolocator.geocode(city, language=lang) if not location: # fallback: first word simple = city.split()[0] location = geolocator.geocode(simple, language=lang) return location def get_timezone_by_city(city: str) -> str: """ Get timezone for a city. """ location = geocode_city(city) if not location: return f"❌ Не удалось найти населенный пункт '{city}'." tz = TimezoneFinder().timezone_at(lat=location.latitude, lng=location.longitude) if not tz: return f"❌ Не удалось определить часовой пояс для '{city}'." return tz def get_current_time_in_timezone(timezone: str) -> str: """ Get local current time in a timezone. """ try: now = datetime.datetime.now(pytz.timezone(timezone)) return now.strftime("%Y-%m-%d %H:%M:%S") except Exception as e: return f"❌ Ошибка получения времени: {e}" def get_air_quality(city: str, lang: str = "en") -> str: """ Get translated air quality info. """ location = geocode_city(city) if not location: return f"❌ Не удалось найти населенный пункт '{city}'." api_key = os.environ.get("OPENWEATHERMAP_API_KEY") if not api_key: return "❌ Отсутствует API-ключ OpenWeatherMap." url = (f"http://api.openweathermap.org/data/2.5/air_pollution?" f"lat={location.latitude}&lon={location.longitude}&appid={api_key}") resp = requests.get(url) if resp.status_code != 200: return f"❌ Ошибка API качества воздуха: {resp.status_code}" data = resp.json().get("list", []) if not data: return "❌ Нет данных о качестве воздуха." aqi = data[0]["main"]["aqi"] comps = data[0]["components"] translations = { "en": { "title": "😷 Air Quality", "aqi_desc": {1: "Good", 2: "Fair", 3: "Moderate", 4: "Poor", 5: "Very Poor"}, "pm2_5": "Fine particles (PM2.5)", "pm10": "Coarse particles (PM10)", "co": "Carbon monoxide (CO)", }, "ru": { "title": "😷 Качество воздуха", "aqi_desc": {1: "Хорошее", 2: "Удовлетворительное", 3: "Среднее", 4: "Плохое", 5: "Очень плохое"}, "pm2_5": "Мелкие частицы (PM2.5)", "pm10": "Крупные частицы (PM10)", "co": "Углекислый газ (CO)", }, "hi": { "title": "😷 वायु गुणवत्ता", "aqi_desc": {1: "अच्छा", 2: "संतोषजनक", 3: "मध्यम", 4: "खराब", 5: "बहुत खराब"}, "pm2_5": "सूक्ष्म कण (PM2.5)", "pm10": "मोटे कण (PM10)", "co": "कार्बन मोनोऑक्साइड (CO)", }, "zh": { "title": "😷 空气质量", "aqi_desc": {1: "良好", 2: "一般", 3: "中等", 4: "较差", 5: "很差"}, "pm2_5": "细颗粒物 (PM2.5)", "pm10": "可吸入颗粒物 (PM10)", "co": "一氧化碳 (CO)", }, "es": { "title": "😷 Calidad del aire", "aqi_desc": {1: "Buena", 2: "Aceptable", 3: "Moderada", 4: "Pobre", 5: "Muy mala"}, "pm2_5": "Partículas finas (PM2.5)", "pm10": "Partículas gruesas (PM10)", "co": "Monóxido de carbono (CO)", }, "fr": { "title": "😷 Qualité de l'air", "aqi_desc": {1: "Bonne", 2: "Passable", 3: "Moyenne", 4: "Mauvaise", 5: "Très mauvaise"}, "pm2_5": "Particules fines (PM2.5)", "pm10": "Particules grossières (PM10)", "co": "Monoxyde de carbone (CO)", }, "ko": { "title": "😷 대기질", "aqi_desc": {1: "좋음", 2: "보통", 3: "보통 이상", 4: "나쁨", 5: "매우 나쁨"}, "pm2_5": "미세먼지 (PM2.5)", "pm10": "먼지 (PM10)", "co": "일산화탄소 (CO)", }, } t = translations.get(lang, translations["en"]) emoji = "🌱" if aqi <= 2 else "😷" if aqi <= 4 else "☣️" return ( f"{t['title']}: AQI {aqi} ({t['aqi_desc'][aqi]}) {emoji}\n" f"{t['pm2_5']}: {comps['pm2_5']} µg/m³\n" f"{t['pm10']}: {comps['pm10']} µg/m³\n" f"{t['co']}: {comps['co']} µg/m³" ) def get_weather(city: str, lang: str = "en") -> str: """ Get translated weather info. """ location = geocode_city(city) if not location: return f"❌ Не удалось найти населенный пункт '{city}'." api_key = os.environ.get("OPENWEATHERMAP_API_KEY") if not api_key: return "❌ Отсутствует API-ключ OpenWeatherMap." url = (f"http://api.openweathermap.org/data/2.5/weather?" f"lat={location.latitude}&lon={location.longitude}" f"&appid={api_key}&units=metric") resp = requests.get(url) if resp.status_code != 200: return f"❌ Ошибка API погоды: {resp.status_code}" d = resp.json() cond = d["weather"][0]["main"] temp = d["main"]["temp"] hum = d["main"]["humidity"] translations = { "en": { "title": "🌡️ Weather", "temp": "Temperature", "humidity": "Humidity", "conditions": { "Clear": "Clear ☀️", "Clouds": "Cloudy ☁️", "Rain": "Rain 🌧", "Snow": "Snow ❄️", "Thunderstorm": "Storm ⛈", "Drizzle": "Drizzle 🌦", "Mist": "Mist 🌫", "Haze": "Haze 🌫" } }, "ru": { "title": "🌡️ Погода", "temp": "Температура", "humidity": "Влажность", "conditions": { "Clear": "Ясно ☀️", "Clouds": "Облачно ☁️", "Rain": "Дождь 🌧", "Snow": "Снег ❄️", "Thunderstorm": "Гроза ⛈", "Drizzle": "Морось 🌦", "Mist": "Туман 🌫", "Haze": "Дымка 🌫" } }, "hi": { "title": "🌡️ मौसम", "temp": "तापमान", "humidity": "नमी", "conditions": { "Clear": "साफ ☀️", "Clouds": "बादल ☁️", "Rain": "बारिश 🌧", "Snow": "बर्फ ❄️", "Thunderstorm": "तूफान ⛈", "Drizzle": "बूंदाबांदी 🌦", "Mist": "कोहरा 🌫", "Haze": "धुंध 🌫" } }, "zh": { "title": "🌡️ 天气", "temp": "温度", "humidity": "湿度", "conditions": { "Clear": "晴 ☀️", "Clouds": "多云 ☁️", "Rain": "雨 🌧", "Snow": "雪 ❄️", "Thunderstorm": "雷暴 ⛈", "Drizzle": "毛毛雨 🌦", "Mist": "雾 🌫", "Haze": "霾 🌫" } }, "es": { "title": "🌡️ Clima", "temp": "Temperatura", "humidity": "Humedad", "conditions": { "Clear": "Despejado ☀️", "Clouds": "Nublado ☁️", "Rain": "Lluvia 🌧", "Snow": "Nieve ❄️", "Thunderstorm": "Tormenta ⛈", "Drizzle": "Llovizna 🌦", "Mist": "Niebla 🌫", "Haze": "Calina 🌫" } }, "fr": { "title": "🌡️ Météo", "temp": "Température", "humidity": "Humidité", "conditions": { "Clear": "Dégagé ☀️", "Clouds": "Nuageux ☁️", "Rain": "Pluie 🌧", "Snow": "Neige ❄️", "Thunderstorm": "Orage ⛈", "Drizzle": "Bruine 🌦", "Mist": "Brume 🌫", "Haze": "Brume légère 🌫" } }, "ko": { "title": "🌡️ 날씨", "temp": "온도", "humidity": "습도", "conditions": { "Clear": "맑음 ☀️", "Clouds": "흐림 ☁️", "Rain": "비 🌧", "Snow": "눈 ❄️", "Thunderstorm": "뇌우 ⛈", "Drizzle": "이슬비 🌦", "Mist": "안개 🌫", "Haze": "실안개 🌫" } }, } t = translations.get(lang, translations["en"]) desc = t["conditions"].get(cond, cond) return ( f"{t['title']}: {desc}\n" f"{t['temp']}: {temp}°C\n" f"{t['humidity']}: {hum}%" ) def process_input(user_input: str) -> str: lang = detect(user_input) if lang not in ["en","ru","hi","zh","es","fr","ko"]: if re.search(r'[\u0400-\u04FF]', user_input): lang = "ru" else: lang = "en" city = user_input.strip() loc_disp = geocode_city(city, lang) if not loc_disp: return f"❌ Не удалось найти населенный пункт '{city}'." parts = loc_disp.raw.get("display_name", "").split(", ") city_disp = parts[0] region_disp = parts[1] if len(parts) > 2 else "" country_disp = parts[-1] if parts else "" location_labels = { "en": "📍 Location", "ru": "📍 Местоположение", "hi": "📍 स्थान", "zh": "📍 位置", "es": "📍 Ubicación", "fr": "📍 Emplacement", "ko": "📍 위치", } time_labels = { "en": "🕒 Time", "ru": "🕒 Время", "hi": "🕒 समय", "zh": "🕒 时间", "es": "🕒 Hora", "fr": "🕒 Heure", "ko": "🕒 시간", } location_line = f"{location_labels.get(lang,'📍 Location')}: {city_disp}" if region_disp: location_line += f", {region_disp}" if country_disp: location_line += f", {country_disp}" tz = get_timezone_by_city(city) if tz.startswith("❌"): time_line = tz else: ct = get_current_time_in_timezone(tz) time_line = f"{time_labels.get(lang,'🕒 Time')}: {ct}" aq = get_air_quality(city, lang) w = get_weather(city, lang) return f"{location_line}\n\n{time_line}\n\n{aq}\n\n{w}" if __name__ == "__main__": gr.Interface( fn=process_input, inputs="text", outputs="text", title="Weather Assistant / Погодный ассистент", description="Введите свой населенный пункт — получите время, погоду и качество воздуха." ).launch()