diff --git a/modules/commands/alternatives/wx_international.py b/modules/commands/alternatives/wx_international.py index 8b7aea9..ba25f65 100644 --- a/modules/commands/alternatives/wx_international.py +++ b/modules/commands/alternatives/wx_international.py @@ -62,7 +62,7 @@ class GlobalWxCommand(BaseCommand): self.wxsim_parser = None # Get default state and country from config for city disambiguation - self.default_state = self.bot.config.get('Weather', 'default_state', fallback='WA') + self.default_state = self.bot.config.get('Weather', 'default_state', fallback='') self.default_country = self.bot.config.get('Weather', 'default_country', fallback='US') # Get unit preferences from config diff --git a/modules/commands/aqi_command.py b/modules/commands/aqi_command.py index c9489cc..bb78132 100644 --- a/modules/commands/aqi_command.py +++ b/modules/commands/aqi_command.py @@ -48,8 +48,9 @@ class AqiCommand(BaseCommand): self.aqi_enabled = self.get_config_value('Aqi_Command', 'enabled', fallback=True, value_type='bool') self.url_timeout = 10 # seconds - # Get default state from config for city disambiguation - self.default_state = self.bot.config.get('Weather', 'default_state', fallback='WA') + # Get default state and country from config for city disambiguation + self.default_state = self.bot.config.get('Weather', 'default_state', fallback='') + self.default_country = self.bot.config.get('Weather', 'default_country', fallback='US') # Get timezone from config self.timezone = self.bot.config.get('Bot', 'timezone', fallback='America/Los_Angeles') @@ -95,7 +96,8 @@ class AqiCommand(BaseCommand): } def get_help_text(self) -> str: - return f"Usage: aqi - Get AQI for city/neighborhood in {self.default_state}, international cities, coordinates, or pollutant help" + region = self.default_state or self.default_country + return f"Usage: aqi - Get AQI for city/neighborhood in {region}, international cities, coordinates, or pollutant help" def can_execute(self, message: MeshMessage) -> bool: """Check if this command can be executed with the given message. @@ -475,7 +477,8 @@ class AqiCommand(BaseCommand): if ',' in location and any(country in location.lower() for country in ['canada', 'mexico', 'uk', 'france', 'germany', 'italy', 'spain', 'australia', 'japan', 'china', 'india', 'brazil', 'uae', 'russia', 'korea', 'thailand', 'singapore', 'egypt', 'turkey']): return f"Could not find city '{location}'" else: - return f"Could not find city '{location}' in {self.default_state}" + region = self.default_state or self.default_country + return f"Could not find city '{location}' in {region}" # Check if the found city is in a different state than default actual_city = location @@ -528,7 +531,7 @@ class AqiCommand(BaseCommand): if location_type == "city" and address_info: # Always try to include city name if there's space # Use abbreviate_location to shorten long location strings (e.g., "United States of America" -> "USA") - full_location = f"{actual_city}, {actual_state}" + full_location = f"{actual_city}, {actual_state}" if actual_state else actual_city city_display = abbreviate_location(full_location, max_length=30) # Check if we have space for the city name @@ -588,7 +591,7 @@ class AqiCommand(BaseCommand): actual_state = "USA" # Use abbreviate_location to shorten long location strings (e.g., "United States of America" -> "USA") - full_location = f"{actual_city}, {actual_state}" + full_location = f"{actual_city}, {actual_state}" if actual_state else actual_city city_display = abbreviate_location(full_location, max_length=30) # Check if we have space for the city name diff --git a/modules/commands/satpass_command.py b/modules/commands/satpass_command.py index 247cf6b..9d6156d 100644 --- a/modules/commands/satpass_command.py +++ b/modules/commands/satpass_command.py @@ -72,9 +72,8 @@ class SatpassCommand(BaseCommand): # Check if user provided a satellite number content = message.content.strip() if content == 'satpass': - # No satellite specified, show help - help_text = self._get_help_text() - await self.send_response(message, help_text) + # No satellite specified, show short usage (fits message length limit) + await self.send_response(message, self.translate('commands.satpass.usage_short')) return True # Extract satellite identifier from command @@ -113,44 +112,6 @@ class SatpassCommand(BaseCommand): await self.send_response(message, error_msg) return False - def _get_help_text(self) -> str: - """Get detailed help text with shortcuts. - - Returns: - str: Detailed help text including shortcuts. - """ - shortcuts_text = self.translate('commands.satpass.help_header') - - # Group shortcuts by category for better organization - weather_sats = ['noaa15', 'noaa18', 'noaa19', 'metop-a', 'metop-b', 'metop-c', 'goes16', 'goes17', 'goes18'] - space_stations = ['iss', 'tiangong', 'tiangong1', 'tiangong2'] - telescopes = ['hst', 'hubble'] - other = ['starlink'] - - # Add weather satellites - shortcuts_text += self.translate('commands.satpass.category_weather') - weather_list = [f"{name} ({self.SATELLITE_SHORTCUTS[name]})" for name in weather_sats if name in self.SATELLITE_SHORTCUTS] - shortcuts_text += ", ".join(weather_list) + "\n" - - # Add space stations - shortcuts_text += self.translate('commands.satpass.category_stations') - station_list = [f"{name} ({self.SATELLITE_SHORTCUTS[name]})" for name in space_stations if name in self.SATELLITE_SHORTCUTS] - shortcuts_text += ", ".join(station_list) + "\n" - - # Add telescopes - shortcuts_text += self.translate('commands.satpass.category_telescopes') - telescope_list = [f"{name} ({self.SATELLITE_SHORTCUTS[name]})" for name in telescopes if name in self.SATELLITE_SHORTCUTS] - shortcuts_text += ", ".join(telescope_list) + "\n" - - # Add other satellites - shortcuts_text += self.translate('commands.satpass.category_other') - other_list = [f"{name} ({self.SATELLITE_SHORTCUTS[name]})" for name in other if name in self.SATELLITE_SHORTCUTS] - shortcuts_text += ", ".join(other_list) + "\n" - - shortcuts_text += self.translate('commands.satpass.examples') - - return shortcuts_text - def get_help_text(self) -> str: """Get help text for this command. diff --git a/modules/commands/solarforecast_command.py b/modules/commands/solarforecast_command.py index 5691ae4..28e7b8b 100644 --- a/modules/commands/solarforecast_command.py +++ b/modules/commands/solarforecast_command.py @@ -59,7 +59,7 @@ class SolarforecastCommand(BaseCommand): self.forecast_cache = {} # Get default state from config for city disambiguation - self.default_state = self.bot.config.get('Weather', 'default_state', fallback='WA') + self.default_state = self.bot.config.get('Weather', 'default_state', fallback='') # Initialize geocoder (will use rate-limited helpers for actual calls) self.geolocator = get_nominatim_geocoder() diff --git a/modules/commands/wx_command.py b/modules/commands/wx_command.py index 47d1204..68dfa88 100644 --- a/modules/commands/wx_command.py +++ b/modules/commands/wx_command.py @@ -90,8 +90,9 @@ class WxCommand(BaseCommand): self.use_metric = False # Use imperial units by default self.zulu_time = False # Use local time by default - # Get default state from config for city disambiguation - self.default_state = self.bot.config.get('Weather', 'default_state', fallback='WA') + # Get default state and country from config for city disambiguation + self.default_state = self.bot.config.get('Weather', 'default_state', fallback='') + self.default_country = self.bot.config.get('Weather', 'default_country', fallback='US') # Initialize geocoder (will use rate-limited helpers for actual calls) # Keep geolocator for backwards compatibility, but prefer rate-limited helpers @@ -572,7 +573,8 @@ class WxCommand(BaseCommand): else: lat, lon = result if lat is None or lon is None: - await self.send_response(message, self.translate('commands.wx.no_location_city', location=location, state=self.default_state)) + region = self.default_state or self.default_country + await self.send_response(message, self.translate('commands.wx.no_location_city', location=location, state=region)) return True # Get and display full alert list @@ -667,11 +669,12 @@ class WxCommand(BaseCommand): address_info = None if lat is None or lon is None: - return self.translate('commands.wx.no_location_city', location=location, state=self.default_state) + region = self.default_state or self.default_country + return self.translate('commands.wx.no_location_city', location=location, state=region) # Check if the found city is in a different state than default actual_city = location - actual_state = self.default_state + actual_state = self.default_state or self.default_country if address_info: # Try to get the best city name from various address fields actual_city = (address_info.get('city') or @@ -715,7 +718,7 @@ class WxCommand(BaseCommand): actual_state != default_state_full) # Always show location if using companion location, or if state is different if using_companion_location or states_different: - location_prefix = f"{actual_city}, {actual_state}: " + location_prefix = f"{actual_city}, {actual_state}: " if actual_state else f"{actual_city}: " elif location_type == "zipcode" and using_companion_location: # For zipcode with companion location, try to get city name from reverse geocoding location_str = self._coordinates_to_location_string(lat, lon) diff --git a/modules/utils.py b/modules/utils.py index c2e75b6..a3adfbb 100644 --- a/modules/utils.py +++ b/modules/utils.py @@ -499,21 +499,23 @@ def is_country_name(text: str) -> bool: Returns: bool: True if text appears to be a country name """ - if not text or len(text) <= 2: + if not text: return False if PYCOUNTRY_AVAILABLE: iso_code, _ = normalize_country_name(text) - return iso_code is not None + if iso_code is not None: + return True - # Fallback: if it's longer than 2 chars and not a common US state, assume country if US_AVAILABLE: state_abbr, _ = normalize_us_state(text) if state_abbr: return False # It's a US state, not a country - # If longer than 2 chars and not a US state, likely a country - return len(text) > 2 + if len(text) <= 2: + return False # Unknown 2-char (not a known country or US state) + + return len(text) > 2 # Longer text, assume country def is_us_state(text: str) -> bool: @@ -801,7 +803,7 @@ async def geocode_city(bot: Any, city: str, default_state: str = None, try: # Get defaults from config if not provided if default_state is None: - default_state = bot.config.get('Weather', 'default_state', fallback='WA') + default_state = bot.config.get('Weather', 'default_state', fallback='') if default_country is None: default_country = bot.config.get('Weather', 'default_country', fallback='US') @@ -845,9 +847,10 @@ async def geocode_city(bot: Any, city: str, default_state: str = None, else: country_name = second_part - # Handle major cities with multiple locations (prioritize major cities) + # Handle major cities with multiple locations (prioritize major cities). + # Skip when user specified a country (e.g. "Paris, FR") so we honor their choice. major_city_queries = get_major_city_queries(city_clean, state_abbr) - if major_city_queries: + if major_city_queries and not country_name: # Try major city options first for major_city_query in major_city_queries: cached_lat, cached_lon = bot.db_manager.get_cached_geocoding(major_city_query) @@ -1017,38 +1020,40 @@ async def geocode_city(bot: Any, city: str, default_state: str = None, address_info = {} return lat, lon, address_info - # Try with default state (fallback for US cities when no country specified) - cache_query = f"{city_clean}, {default_state}, {default_country}" - cached_lat, cached_lon = bot.db_manager.get_cached_geocoding(cache_query) - if cached_lat and cached_lon: - lat, lon = cached_lat, cached_lon - else: - location = await rate_limited_nominatim_geocode(bot, cache_query, timeout=timeout) - if location: - bot.db_manager.cache_geocoding(cache_query, location.latitude, location.longitude) - lat, lon = location.latitude, location.longitude + # Try with default state (fallback for US cities when no country specified). + # Skip when default_state is empty (e.g. non-US default_country or key unset). + if default_state and default_state.strip(): + cache_query = f"{city_clean}, {default_state}, {default_country}" + cached_lat, cached_lon = bot.db_manager.get_cached_geocoding(cache_query) + if cached_lat and cached_lon: + lat, lon = cached_lat, cached_lon else: - lat, lon = None, None - - if lat and lon: - address_info = None - if include_address_info: - # Check cache for reverse geocoding result - reverse_cache_key = f"reverse_{lat}_{lon}" - cached_address = bot.db_manager.get_cached_json(reverse_cache_key, "geolocation") - if cached_address: - address_info = cached_address + location = await rate_limited_nominatim_geocode(bot, cache_query, timeout=timeout) + if location: + bot.db_manager.cache_geocoding(cache_query, location.latitude, location.longitude) + lat, lon = location.latitude, location.longitude else: - try: - reverse_location = await rate_limited_nominatim_reverse(bot, f"{lat}, {lon}", timeout=timeout) - if reverse_location: - address_info = reverse_location.raw.get('address', {}) - # Cache the reverse geocoding result - bot.db_manager.cache_json(reverse_cache_key, address_info, "geolocation", cache_hours=720) - except: - address_info = {} - return lat, lon, address_info - + lat, lon = None, None + + if lat and lon: + address_info = None + if include_address_info: + # Check cache for reverse geocoding result + reverse_cache_key = f"reverse_{lat}_{lon}" + cached_address = bot.db_manager.get_cached_json(reverse_cache_key, "geolocation") + if cached_address: + address_info = cached_address + else: + try: + reverse_location = await rate_limited_nominatim_reverse(bot, f"{lat}, {lon}", timeout=timeout) + if reverse_location: + address_info = reverse_location.raw.get('address', {}) + # Cache the reverse geocoding result + bot.db_manager.cache_json(reverse_cache_key, address_info, "geolocation", cache_hours=720) + except: + address_info = {} + return lat, lon, address_info + # Try without state location = await rate_limited_nominatim_geocode(bot, f"{city_clean}, {default_country}", timeout=timeout) if location: @@ -1102,7 +1107,7 @@ def geocode_city_sync(bot: Any, city: str, default_state: str = None, try: # Get defaults from config if not provided if default_state is None: - default_state = bot.config.get('Weather', 'default_state', fallback='WA') + default_state = bot.config.get('Weather', 'default_state', fallback='') if default_country is None: default_country = bot.config.get('Weather', 'default_country', fallback='US') @@ -1147,9 +1152,10 @@ def geocode_city_sync(bot: Any, city: str, default_state: str = None, else: country_name = second_part - # Handle major cities with multiple locations (prioritize major cities) + # Handle major cities with multiple locations (prioritize major cities). + # Skip when user specified a country (e.g. "Paris, FR") so we honor their choice. major_city_queries = get_major_city_queries(city_clean, state_abbr) - if major_city_queries: + if major_city_queries and not country_name: # Try major city options first for major_city_query in major_city_queries: cached_lat, cached_lon = bot.db_manager.get_cached_geocoding(major_city_query) @@ -1314,38 +1320,40 @@ def geocode_city_sync(bot: Any, city: str, default_state: str = None, address_info = {} return lat, lon, address_info - # Try with default state (fallback for US cities when no country specified) - cache_query = f"{city_clean}, {default_state}, {default_country}" - cached_lat, cached_lon = bot.db_manager.get_cached_geocoding(cache_query) - if cached_lat and cached_lon: - lat, lon = cached_lat, cached_lon - else: - location = rate_limited_nominatim_geocode_sync(bot, cache_query, timeout=timeout) - if location: - bot.db_manager.cache_geocoding(cache_query, location.latitude, location.longitude) - lat, lon = location.latitude, location.longitude + # Try with default state (fallback for US cities when no country specified). + # Skip when default_state is empty (e.g. non-US default_country or key unset). + if default_state and default_state.strip(): + cache_query = f"{city_clean}, {default_state}, {default_country}" + cached_lat, cached_lon = bot.db_manager.get_cached_geocoding(cache_query) + if cached_lat and cached_lon: + lat, lon = cached_lat, cached_lon else: - lat, lon = None, None - - if lat and lon: - address_info = None - if include_address_info: - # Check cache for reverse geocoding result - reverse_cache_key = f"reverse_{lat}_{lon}" - cached_address = bot.db_manager.get_cached_json(reverse_cache_key, "geolocation") - if cached_address: - address_info = cached_address + location = rate_limited_nominatim_geocode_sync(bot, cache_query, timeout=timeout) + if location: + bot.db_manager.cache_geocoding(cache_query, location.latitude, location.longitude) + lat, lon = location.latitude, location.longitude else: - try: - reverse_location = rate_limited_nominatim_reverse_sync(bot, f"{lat}, {lon}", timeout=timeout) - if reverse_location: - address_info = reverse_location.raw.get('address', {}) - # Cache the reverse geocoding result - bot.db_manager.cache_json(reverse_cache_key, address_info, "geolocation", cache_hours=720) - except: - address_info = {} - return lat, lon, address_info - + lat, lon = None, None + + if lat and lon: + address_info = None + if include_address_info: + # Check cache for reverse geocoding result + reverse_cache_key = f"reverse_{lat}_{lon}" + cached_address = bot.db_manager.get_cached_json(reverse_cache_key, "geolocation") + if cached_address: + address_info = cached_address + else: + try: + reverse_location = rate_limited_nominatim_reverse_sync(bot, f"{lat}, {lon}", timeout=timeout) + if reverse_location: + address_info = reverse_location.raw.get('address', {}) + # Cache the reverse geocoding result + bot.db_manager.cache_json(reverse_cache_key, address_info, "geolocation", cache_hours=720) + except: + address_info = {} + return lat, lon, address_info + # Try without state location = rate_limited_nominatim_geocode_sync(bot, f"{city_clean}, {default_country}", timeout=timeout) if location: diff --git a/translations/de.json b/translations/de.json index 62fe0f5..25b5c7d 100644 --- a/translations/de.json +++ b/translations/de.json @@ -514,6 +514,7 @@ "header": "🛰️ Satellitenvorbeiflug:\n{pass_info}", "error": "Fehler beim Abruf der Satellitenvorbeiflug-Info: {error}", "no_satellite": "Bitte NORAD-Nummer oder Kürzel angeben. Beispiel: satpass iss", + "usage_short": "🛰️ satpass [visuell]. Z.B.: satpass iss, satpass 25544. help satpass für Kürzel.", "help_header": "🛰️ Satellitenvorbeiflug-Info\n\nVerwendung: satpass \n\nKürzel:\n", "category_weather": "🌤️ Wetter: ", "category_stations": "🚀 Stationen: ", diff --git a/translations/en-GB.json b/translations/en-GB.json index 10c194d..eefb891 100644 --- a/translations/en-GB.json +++ b/translations/en-GB.json @@ -494,6 +494,7 @@ "header": "🛰️ Satellite Pass:\n{pass_info}", "error": "Error getting satellite pass info: {error}", "no_satellite": "Please provide a satellite NORAD number or shortcut. Example: satpass iss", + "usage_short": "🛰️ satpass [visual]. Ex: satpass iss, satpass 25544. help satpass for shortcuts.", "help_header": "🛰️ Satellite Pass Info\n\nUsage: satpass \n\nShortcuts:\n", "category_weather": "🌤️ Weather: ", "category_stations": "🚀 Stations: ", diff --git a/translations/en.json b/translations/en.json index 1afc453..1363745 100644 --- a/translations/en.json +++ b/translations/en.json @@ -437,6 +437,7 @@ "header": "🛰️ Satellite Pass:\n{pass_info}", "error": "Error getting satellite pass info: {error}", "no_satellite": "Please provide a satellite NORAD number or shortcut. Example: satpass iss", + "usage_short": "🛰️ satpass [visual]. Ex: satpass iss, satpass 25544. help satpass for shortcuts.", "help_header": "🛰️ Satellite Pass Info\n\nUsage: satpass \n\nShortcuts:\n", "category_weather": "🌤️ Weather: ", "category_stations": "🚀 Stations: ", diff --git a/translations/es.json b/translations/es.json index 4b11569..8a0abaa 100644 --- a/translations/es.json +++ b/translations/es.json @@ -370,6 +370,7 @@ "header": "🛰️ Paso de Satélite:\n{pass_info}", "error": "Error al obtener información de paso de satélite: {error}", "no_satellite": "Por favor proporciona un número NORAD de satélite o atajo. Ejemplo: satpass iss", + "usage_short": "🛰️ satpass [visual]. Ej: satpass iss, satpass 25544. help satpass para atajos.", "help_header": "🛰️ Info de Paso de Satélite\n\nUso: satpass \n\nAtajos:\n", "category_weather": "🌤️ Clima: ", "category_stations": "🚀 Estaciones: ", diff --git a/translations/fr-CA.json b/translations/fr-CA.json index 8689e5f..70dc624 100644 --- a/translations/fr-CA.json +++ b/translations/fr-CA.json @@ -566,6 +566,7 @@ "header": "🛰️ Passage de satellite :\n{pass_info}", "error": "Erreur lors de l'obtention des infos de passage : {error}", "no_satellite": "Veuillez fournir un numéro NORAD ou un raccourci de satellite. Exemple : satpass iss", + "usage_short": "🛰️ satpass [visuel]. Ex : satpass iss, satpass 25544. help satpass pour les raccourcis.", "help_header": "🛰️ Infos de passage de satellite\n\nUtilisation : satpass \n\nRaccourcis :\n", "category_weather": "🌤️ Météo : ", "category_stations": "🚀 Stations : ", diff --git a/translations/fr.json b/translations/fr.json index 9649852..59be72d 100644 --- a/translations/fr.json +++ b/translations/fr.json @@ -494,6 +494,7 @@ "header": "🛰️ Passage satellite:\n{pass_info}", "error": "Erreur infos passage satellite: {error}", "no_satellite": "Fournissez numéro NORAD satellite ou raccourci. Ex: satpass iss", + "usage_short": "🛰️ satpass [visuel]. Ex: satpass iss, satpass 25544. help satpass pour raccourcis.", "help_header": "🛰️ Infos passage satellite\n\nUsage: satpass \n\nRaccourcis:\n", "category_weather": "🌤️ Météo: ", "category_stations": "🚀 Stations: ", diff --git a/translations/nl.json b/translations/nl.json index bb87f0f..a877af3 100644 --- a/translations/nl.json +++ b/translations/nl.json @@ -494,6 +494,7 @@ "header": "🛰️ Satellietpassage:\n{pass_info}", "error": "Fout bij ophalen satellietpassage-info: {error}", "no_satellite": "Geef een NORAD-nummer of snelkoppeling op. Voorbeeld: satpass iss", + "usage_short": "🛰️ satpass [visueel]. Bijv: satpass iss, satpass 25544. help satpass voor snelkoppelingen.", "help_header": "🛰️ Satellietpassage info\n\nGebruik: satpass \n\nSnelkoppelingen:\n", "category_weather": "🌤️ Weer: ", "category_stations": "🚀 Stations: ", diff --git a/translations/pt-BR.json b/translations/pt-BR.json index 485659f..9221f83 100644 --- a/translations/pt-BR.json +++ b/translations/pt-BR.json @@ -491,6 +491,7 @@ "header": "🛰️ Passagem de Satélite:\n{pass_info}", "error": "Erro ao pegar info de passagem: {error}", "no_satellite": "Manda o número NORAD ou atalho do satélite. Ex: satpass iss", + "usage_short": "🛰️ satpass [visual]. Ex: satpass iss, satpass 25544. help satpass pra atalhos.", "help_header": "🛰️ Info Passagem Satélite\n\nUso: satpass \n\nAtalhos:\n", "category_weather": "🌤️ Meteorológicos: ", "category_stations": "🚀 Estações: ", diff --git a/translations/pt.json b/translations/pt.json index 9b8bd19..f41a23e 100644 --- a/translations/pt.json +++ b/translations/pt.json @@ -490,6 +490,7 @@ "header": "🛰️ Passagem de Satélite:\n{pass_info}", "error": "Erro ao obter info de passagem: {error}", "no_satellite": "Por favor forneça número NORAD ou atalho do satélite. Exemplo: satpass iss", + "usage_short": "🛰️ satpass [visual]. Ex: satpass iss, satpass 25544. help satpass para atalhos.", "help_header": "🛰️ Info Passagem Satélite\n\nUso: satpass \n\nAtalhos:\n", "category_weather": "🌤️ Meteorológicos: ", "category_stations": "🚀 Estações: ",