Attempted fixes for weather.
This commit is contained in:
		| @@ -17,6 +17,8 @@ shift 2 | |||||||
|  |  | ||||||
| # Database file for user locations and cached geocoding | # Database file for user locations and cached geocoding | ||||||
| weatherDb="data/weather.db" | weatherDb="data/weather.db" | ||||||
|  | weatherDbLock="${weatherDb}.lock" | ||||||
|  | nominatimRateLimitFile="data/nominatim_last_request" | ||||||
| mkdir -p "$(dirname "$weatherDb")" | mkdir -p "$(dirname "$weatherDb")" | ||||||
| touch "$weatherDb" | touch "$weatherDb" | ||||||
|  |  | ||||||
| @@ -73,24 +75,66 @@ save_user_location() { | |||||||
|     local lon="$4" |     local lon="$4" | ||||||
|     local formatted="$5" |     local formatted="$5" | ||||||
|  |  | ||||||
|     # Remove old entry if exists |     # Use flock to prevent race conditions | ||||||
|     if [[ -f "$weatherDb" ]]; then |     ( | ||||||
|         grep -v "^USER|${user}|" "$weatherDb" > "${weatherDb}.tmp" |         flock -x 200 | ||||||
|         mv "${weatherDb}.tmp" "$weatherDb" |  | ||||||
|     fi |  | ||||||
|  |  | ||||||
|     # Add new entry |         # Remove old entry if exists | ||||||
|     echo "USER|${user}|${location}|${lat}|${lon}|${formatted}" >> "$weatherDb" |         if [[ -f "$weatherDb" ]]; then | ||||||
|  |             grep -v "^USER|${user}|" "$weatherDb" > "${weatherDb}.tmp" | ||||||
|  |             mv "${weatherDb}.tmp" "$weatherDb" | ||||||
|  |         fi | ||||||
|  |  | ||||||
|  |         # Add new entry | ||||||
|  |         echo "USER|${user}|${location}|${lat}|${lon}|${formatted}" >> "$weatherDb" | ||||||
|  |  | ||||||
|  |     ) 200>"$weatherDbLock" | ||||||
| } | } | ||||||
|  |  | ||||||
| # Function to delete user location | # Function to delete user location | ||||||
| delete_user_location() { | delete_user_location() { | ||||||
|     local user="$1" |     local user="$1" | ||||||
|  |  | ||||||
|     if [[ -f "$weatherDb" ]]; then |     # Use flock to prevent race conditions | ||||||
|         grep -v "^USER|${user}|" "$weatherDb" > "${weatherDb}.tmp" |     ( | ||||||
|         mv "${weatherDb}.tmp" "$weatherDb" |         flock -x 200 | ||||||
|     fi |  | ||||||
|  |         if [[ -f "$weatherDb" ]]; then | ||||||
|  |             grep -v "^USER|${user}|" "$weatherDb" > "${weatherDb}.tmp" | ||||||
|  |             mv "${weatherDb}.tmp" "$weatherDb" | ||||||
|  |         fi | ||||||
|  |  | ||||||
|  |     ) 200>"$weatherDbLock" | ||||||
|  | } | ||||||
|  |  | ||||||
|  | # Function to maintain cache size | ||||||
|  | maintain_cache() { | ||||||
|  |     # Use flock to prevent race conditions | ||||||
|  |     ( | ||||||
|  |         flock -x 200 | ||||||
|  |  | ||||||
|  |         if [[ ! -f "$weatherDb" ]]; then | ||||||
|  |             return 0 | ||||||
|  |         fi | ||||||
|  |  | ||||||
|  |         local lineCount | ||||||
|  |         lineCount=$(wc -l < "$weatherDb") | ||||||
|  |  | ||||||
|  |         # If file has 2000 or more lines, keep only the most recent 1500 | ||||||
|  |         if [[ $lineCount -ge 2000 ]]; then | ||||||
|  |             # Separate USER and CACHE entries | ||||||
|  |             grep "^USER|" "$weatherDb" > "${weatherDb}.tmp.user" 2>/dev/null || true | ||||||
|  |             grep "^CACHE|" "$weatherDb" | tail -n 1000 > "${weatherDb}.tmp.cache" 2>/dev/null || true | ||||||
|  |  | ||||||
|  |             # Combine them back (keep all user entries, trim cache entries) | ||||||
|  |             cat "${weatherDb}.tmp.user" "${weatherDb}.tmp.cache" > "${weatherDb}.tmp" 2>/dev/null | ||||||
|  |             mv "${weatherDb}.tmp" "$weatherDb" | ||||||
|  |  | ||||||
|  |             # Clean up temp files | ||||||
|  |             rm -f "${weatherDb}.tmp.user" "${weatherDb}.tmp.cache" | ||||||
|  |         fi | ||||||
|  |  | ||||||
|  |     ) 200>"$weatherDbLock" | ||||||
| } | } | ||||||
|  |  | ||||||
| # Function to cache geocode result | # Function to cache geocode result | ||||||
| @@ -106,10 +150,45 @@ cache_location() { | |||||||
|     local cached |     local cached | ||||||
|     cached=$(get_cached_location "$query") |     cached=$(get_cached_location "$query") | ||||||
|     if [[ -z "$cached" ]]; then |     if [[ -z "$cached" ]]; then | ||||||
|         echo "CACHE|${queryLower}|${location}|${lat}|${lon}|${formatted}" >> "$weatherDb" |         # Use flock to prevent race conditions | ||||||
|  |         ( | ||||||
|  |             flock -x 200 | ||||||
|  |             echo "CACHE|${queryLower}|${location}|${lat}|${lon}|${formatted}" >> "$weatherDb" | ||||||
|  |         ) 200>"$weatherDbLock" | ||||||
|  |  | ||||||
|  |         # Maintain cache size after adding new entry | ||||||
|  |         maintain_cache | ||||||
|     fi |     fi | ||||||
| } | } | ||||||
|  |  | ||||||
|  | # Function to enforce Nominatim rate limit (1 request per second) | ||||||
|  | rate_limit_nominatim() { | ||||||
|  |     local rateLimitLock="${nominatimRateLimitFile}.lock" | ||||||
|  |  | ||||||
|  |     ( | ||||||
|  |         flock -x 200 | ||||||
|  |  | ||||||
|  |         # Check if rate limit file exists and read last request time | ||||||
|  |         if [[ -f "$nominatimRateLimitFile" ]]; then | ||||||
|  |             local lastRequest | ||||||
|  |             lastRequest=$(cat "$nominatimRateLimitFile" 2>/dev/null || echo "0") | ||||||
|  |             local currentTime | ||||||
|  |             currentTime=$(date +%s) | ||||||
|  |             local timeSinceLastRequest=$((currentTime - lastRequest)) | ||||||
|  |  | ||||||
|  |             # If less than 1 second has passed, sleep for the remaining time | ||||||
|  |             if [[ $timeSinceLastRequest -lt 1 ]]; then | ||||||
|  |                 local sleepTime=$((1 - timeSinceLastRequest)) | ||||||
|  |                 sleep "$sleepTime" | ||||||
|  |             fi | ||||||
|  |         fi | ||||||
|  |  | ||||||
|  |         # Update last request time | ||||||
|  |         date +%s > "$nominatimRateLimitFile" | ||||||
|  |  | ||||||
|  |     ) 200>"$rateLimitLock" | ||||||
|  | } | ||||||
|  |  | ||||||
| # Function to format location (City, State or City, Country) | # Function to format location (City, State or City, Country) | ||||||
| format_location() { | format_location() { | ||||||
|     local address="$1" |     local address="$1" | ||||||
| @@ -179,9 +258,27 @@ geocode_location() { | |||||||
|     # URL encode the query (replace spaces with +) |     # URL encode the query (replace spaces with +) | ||||||
|     local encodedQuery="${query// /+}" |     local encodedQuery="${query// /+}" | ||||||
|  |  | ||||||
|  |     # Check if query contains common US indicators (case-insensitive) | ||||||
|  |     local countryCode="" | ||||||
|  |     local queryLower="${query,,}" | ||||||
|  |  | ||||||
|  |     # Common US state names and abbreviations | ||||||
|  |     local usStates="alabama|alaska|arizona|arkansas|california|colorado|connecticut|delaware|florida|georgia|hawaii|idaho|illinois|indiana|iowa|kansas|kentucky|louisiana|maine|maryland|massachusetts|michigan|minnesota|mississippi|missouri|montana|nebraska|nevada|new hampshire|new jersey|new mexico|new york|north carolina|north dakota|ohio|oklahoma|oregon|pennsylvania|rhode island|south carolina|south dakota|tennessee|texas|utah|vermont|virginia|washington|west virginia|wisconsin|wyoming" | ||||||
|  |  | ||||||
|  |     if [[ "$queryLower" =~ (usa|united states) ]] || \ | ||||||
|  |        [[ "$query" =~ [[:space:]][A-Z]{2}$ ]] || \ | ||||||
|  |        [[ "$queryLower" =~ [[:space:]](${usStates})$ ]] || \ | ||||||
|  |        [[ "$query" =~ ^[0-9]{5}([[:space:]]|$) ]]; then | ||||||
|  |         # Query mentions USA, ends with state abbreviation, ends with state name, or starts with 5-digit zip | ||||||
|  |         countryCode="&countrycodes=us" | ||||||
|  |     fi | ||||||
|  |  | ||||||
|  |     # Enforce rate limit before making API request | ||||||
|  |     rate_limit_nominatim | ||||||
|  |  | ||||||
|     response=$(curl -s --connect-timeout 5 --max-time 10 \ |     response=$(curl -s --connect-timeout 5 --max-time 10 \ | ||||||
|         -H "User-Agent: ${userAgent}" \ |         -H "User-Agent: ${userAgent}" \ | ||||||
|         "${url}?q=${encodedQuery}&format=json&limit=1&addressdetails=1") |         "${url}?q=${encodedQuery}&format=json&limit=1&addressdetails=1${countryCode}") | ||||||
|  |  | ||||||
|     if [[ -z "$response" || "$response" == "[]" ]]; then |     if [[ -z "$response" || "$response" == "[]" ]]; then | ||||||
|         return 1 |         return 1 | ||||||
| @@ -217,7 +314,7 @@ get_weather() { | |||||||
|  |  | ||||||
|     local url="https://api.open-meteo.com/v1/forecast" |     local url="https://api.open-meteo.com/v1/forecast" | ||||||
|     local params="latitude=${lat}&longitude=${lon}" |     local params="latitude=${lat}&longitude=${lon}" | ||||||
|     params+="¤t=temperature_2m,relative_humidity_2m,weather_code,wind_speed_10m" |     params+="¤t=temperature_2m,apparent_temperature,relative_humidity_2m,weather_code,wind_speed_10m" | ||||||
|     params+="&daily=weather_code,temperature_2m_max,temperature_2m_min" |     params+="&daily=weather_code,temperature_2m_max,temperature_2m_min" | ||||||
|     params+="&timezone=auto&forecast_days=3&temperature_unit=fahrenheit&wind_speed_unit=mph" |     params+="&timezone=auto&forecast_days=3&temperature_unit=fahrenheit&wind_speed_unit=mph" | ||||||
|  |  | ||||||
| @@ -239,17 +336,35 @@ format_weather() { | |||||||
|  |  | ||||||
|     # Parse current weather |     # Parse current weather | ||||||
|     local temp |     local temp | ||||||
|  |     local feelsLike | ||||||
|     local humidity |     local humidity | ||||||
|     local windSpeed |     local windSpeed | ||||||
|     local weatherCode |     local weatherCode | ||||||
|     temp=$(echo "$weatherData" | jq -r '.current.temperature_2m // "N/A"') |     temp=$(echo "$weatherData" | jq -r '.current.temperature_2m // "N/A"') | ||||||
|  |     feelsLike=$(echo "$weatherData" | jq -r '.current.apparent_temperature // "N/A"') | ||||||
|     humidity=$(echo "$weatherData" | jq -r '.current.relative_humidity_2m // "N/A"') |     humidity=$(echo "$weatherData" | jq -r '.current.relative_humidity_2m // "N/A"') | ||||||
|     windSpeed=$(echo "$weatherData" | jq -r '.current.wind_speed_10m // "N/A"') |     windSpeed=$(echo "$weatherData" | jq -r '.current.wind_speed_10m // "N/A"') | ||||||
|     weatherCode=$(echo "$weatherData" | jq -r '.current.weather_code // 0') |     weatherCode=$(echo "$weatherData" | jq -r '.current.weather_code // 0') | ||||||
|     local conditions="${weatherCodes[$weatherCode]:-Unknown}" |     local conditions="${weatherCodes[$weatherCode]:-Unknown}" | ||||||
|  |  | ||||||
|     # Build message |     # Build message | ||||||
|     local message="Weather for ${locationName}: ${temp}°F, ${conditions}" |     local message="Weather for ${locationName}: ${temp}°F" | ||||||
|  |  | ||||||
|  |     # Add feels-like if different from actual temp (round to nearest degree for comparison) | ||||||
|  |     if [[ "$feelsLike" != "N/A" && "$temp" != "N/A" ]]; then | ||||||
|  |         local tempRounded | ||||||
|  |         local feelsLikeRounded | ||||||
|  |         tempRounded=$(printf "%.0f" "$temp" 2>/dev/null || echo "$temp") | ||||||
|  |         feelsLikeRounded=$(printf "%.0f" "$feelsLike" 2>/dev/null || echo "$feelsLike") | ||||||
|  |  | ||||||
|  |         # Only show feels-like if it differs by at least 3 degrees | ||||||
|  |         local diff=$((tempRounded - feelsLikeRounded)) | ||||||
|  |         if [[ ${diff#-} -ge 3 ]]; then | ||||||
|  |             message+=" (feels like ${feelsLike}°F)" | ||||||
|  |         fi | ||||||
|  |     fi | ||||||
|  |  | ||||||
|  |     message+=", ${conditions}" | ||||||
|  |  | ||||||
|     if [[ "$humidity" != "N/A" ]]; then |     if [[ "$humidity" != "N/A" ]]; then | ||||||
|         message+=", Humidity: ${humidity}%" |         message+=", Humidity: ${humidity}%" | ||||||
| @@ -325,17 +440,12 @@ format_forecast() { | |||||||
| # Main command logic | # Main command logic | ||||||
| subcommand="${1:-}" | subcommand="${1:-}" | ||||||
|  |  | ||||||
| # If subcommand is the module name itself (w or weather), treat as no argument |  | ||||||
| if [[ "$subcommand" == "w" || "$subcommand" == "weather" ]]; then |  | ||||||
|     subcommand="" |  | ||||||
| fi |  | ||||||
|  |  | ||||||
| case "$subcommand" in | case "$subcommand" in | ||||||
|     set) |     set) | ||||||
|         # Set user's location |         # Set user's location | ||||||
|         shift |         shift | ||||||
|         if [[ $# -eq 0 ]]; then |         if [[ $# -eq 0 ]]; then | ||||||
|             msg "$channelName" "$name: Usage: weather -set <location>" |             msg "$channelName" "$name: Usage: weather set <location>" | ||||||
|             exit 0 |             exit 0 | ||||||
|         fi |         fi | ||||||
|  |  | ||||||
| @@ -364,6 +474,7 @@ case "$subcommand" in | |||||||
|         save_user_location "$name" "$fullAddress" "$lat" "$lon" "$formattedLocation" |         save_user_location "$name" "$fullAddress" "$lat" "$lon" "$formattedLocation" | ||||||
|  |  | ||||||
|         msg "$channelName" "$name: Your location has been set to: ${formattedLocation}" |         msg "$channelName" "$name: Your location has been set to: ${formattedLocation}" | ||||||
|  |         exit 0 | ||||||
|         ;; |         ;; | ||||||
|  |  | ||||||
|     del|delete) |     del|delete) | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user