From 9b7a786a9608b666ff4fff09c11963934eef0d7c Mon Sep 17 00:00:00 2001 From: Storm Dragon Date: Mon, 27 Oct 2025 14:39:46 -0400 Subject: [PATCH] Attempted fixes for weather. --- modules/weather/weather.sh | 153 ++++++++++++++++++++++++++++++++----- 1 file changed, 132 insertions(+), 21 deletions(-) diff --git a/modules/weather/weather.sh b/modules/weather/weather.sh index ada0513..0479aba 100755 --- a/modules/weather/weather.sh +++ b/modules/weather/weather.sh @@ -17,6 +17,8 @@ shift 2 # Database file for user locations and cached geocoding weatherDb="data/weather.db" +weatherDbLock="${weatherDb}.lock" +nominatimRateLimitFile="data/nominatim_last_request" mkdir -p "$(dirname "$weatherDb")" touch "$weatherDb" @@ -73,24 +75,66 @@ save_user_location() { local lon="$4" local formatted="$5" - # Remove old entry if exists - if [[ -f "$weatherDb" ]]; then - grep -v "^USER|${user}|" "$weatherDb" > "${weatherDb}.tmp" - mv "${weatherDb}.tmp" "$weatherDb" - fi + # Use flock to prevent race conditions + ( + flock -x 200 - # Add new entry - echo "USER|${user}|${location}|${lat}|${lon}|${formatted}" >> "$weatherDb" + # Remove old entry if exists + 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 delete_user_location() { local user="$1" - if [[ -f "$weatherDb" ]]; then - grep -v "^USER|${user}|" "$weatherDb" > "${weatherDb}.tmp" - mv "${weatherDb}.tmp" "$weatherDb" - fi + # Use flock to prevent race conditions + ( + flock -x 200 + + 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 @@ -106,10 +150,45 @@ cache_location() { local cached cached=$(get_cached_location "$query") 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 } +# 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) format_location() { local address="$1" @@ -179,9 +258,27 @@ geocode_location() { # URL encode the query (replace spaces with +) 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 \ -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 return 1 @@ -217,7 +314,7 @@ get_weather() { local url="https://api.open-meteo.com/v1/forecast" 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+="&timezone=auto&forecast_days=3&temperature_unit=fahrenheit&wind_speed_unit=mph" @@ -239,17 +336,35 @@ format_weather() { # Parse current weather local temp + local feelsLike local humidity local windSpeed local weatherCode 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"') windSpeed=$(echo "$weatherData" | jq -r '.current.wind_speed_10m // "N/A"') weatherCode=$(echo "$weatherData" | jq -r '.current.weather_code // 0') local conditions="${weatherCodes[$weatherCode]:-Unknown}" # 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 message+=", Humidity: ${humidity}%" @@ -325,17 +440,12 @@ format_forecast() { # Main command logic 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 set) # Set user's location shift if [[ $# -eq 0 ]]; then - msg "$channelName" "$name: Usage: weather -set " + msg "$channelName" "$name: Usage: weather set " exit 0 fi @@ -364,6 +474,7 @@ case "$subcommand" in save_user_location "$name" "$fullAddress" "$lat" "$lon" "$formattedLocation" msg "$channelName" "$name: Your location has been set to: ${formattedLocation}" + exit 0 ;; del|delete)