Weather eggdrop script for wunderground.com API

This is a simple script tcl script for an eggdrop that will allow users to set a default location and then grabs current conditions or a 3 day forecast using that default location or a defined search each time. The data is pulled from the very nice people at http://www.wunderground.com/ using their API which has no cost up until a limit of 500 calls per day.

Should hopefully be quite readable (as its quite simple? :P) but any issues or suggestions as ever please just let me know. An example of its output:

weather tcl

TCL file can be grabbed from [here] or the full script is below:

#########################################################################################
# Name                  m00nie::weather
# Description           Uses wunderground API to grab some weather...
#
#           Requires channel to be flagged with +weather
#
#           Commands:
#           !wl  
#                can be 0 for Metric, 1 for Imperial or 2 for both
#           !w 
#               Grabs current weather in location. If no location is suuplied will try to use users
#               saved location
#           !wf 
#               Same as !w but provides a 3 day forecast
#
# Version               2.3 - Fixes mutiple result issue for !wf (shit code)
#                                              2.2 - You can now query other users saved locations using their nick
#                               e.g "!w m00nie" would return the current weather for a user called m00nie
#                                              2.1 - Adds sunrise/sunset info (controlled by variable as requires additional API call)
#                       2.0 - Add option below to automagically add forecast for next 3 hours to each current weather
#                               request
#                       1.8 - Trying to improve code around mask/nick changes...
#                       1.7 - Users can now set their own prefered metric type (Defaults to global forc variable type for
#                       users that dont have metric type defined).
#                       1.6 - Corrects wind conditions when set to Metric, or Combined
#                               Adjusted styling for better readability (Contibuted by random4t4x14)
#                       1.5 - Corrects flag detection for chans and adds option for current conditions to display
#                               both F and C temps in the same output. Might add this to forecast in future version
#                       1.4 - Warn on more than 5 possible restults (stops LOTS of spam)
#                       1.3 - Added warning when mutlple/unclear matches
#                       1.2 - Correct RFC compliant output for some IRCDs (thanks to Alan and Robert for highlighting)
#                       1.1 - F/C tempreture option
#                       1.0 - Initial Release
#
# Website               https://www.m00nie.com
# Notes                 Grab your own key @ http://www.wunderground.com/weather/api
#########################################################################################
namespace eval m00nie {
    namespace eval weather {
        package require http
        package require tdom
        bind pub - !w m00nie::weather::current_call
        bind pub - !wl m00nie::weather::location
        bind pub - !wf m00nie::weather::forecast_call
        variable version "2.3"
        # Set forc to 0 for tempreture in Celsius, 1 for Farenheit or 2 for both!
        # At the moment forecasts can only use 0 or 1, 2 will default back to Celsius
        variable forc "2"
        # Set below to 1 to add hourly info for the next few hours to each current weather report
        # (note this adds an extra API call to each query so on a busy chan you may it API limits)
        variable hourinf "1"
        # Set below to 1 to add sunrise/sunset info to each current weather report
        # (note this adds an extra API call to each query so on a busy chan you may it API limits)
        variable suninf "1"
        setudef flag weather
        variable key "GET-YOUR-OWN"
        ::http::config -useragent "Mozilla/5.0 (X11; Fedora; Linux x86_64; rv:54.0) Gecko/20100101 Firefox/54.0"

proc current_call {nick uhost hand chan text} {
    set location [verify $hand $chan $text]
    if {$location != 0} {
        set forc [metricpref $hand]
        current $forc $location $chan
    }
}

proc forecast_call {nick uhost hand chan text} {
    set location [verify $hand $chan $text]
    if {$location != 0} {
        set forc [metricpref $hand]
        forecast $forc $location $chan
    }
}

# Allow a user to save their location so they can search without defining it each time
proc location {nick uhost hand chan text} {
    putlog "nick: $nick, uhost: $uhost, hand: $hand"
    set forc [string index $text 0]
    set text [string range $text 2 end]

    if {!(($forc == 0) || ($forc == 1) || ($forc == 2)) } {
        puthelp "PRIVMSG $chan :Output units must be specified from 0-2 where 0 = metric, 1 = imperial and 2 = both. E.g \"!wl 2 London Uk\" would spam both unit types for London."
        return
    }
    if { [string length $text] <= 0 } {
        puthelp "PRIVMSG $chan :Your location seemed very short? :("
    }
    if {![validuser $hand]} {
        adduser $nick
        set mask [maskhost [getchanhost $nick $chan]]
        setuser $nick HOSTS $mask
        chattr $nick -hp
        putlog "m00nie::weather::location added user $nick with host $mask"
    }
    setuser $hand XTRA m00nie:weather.location $text
    setuser $hand XTRA m00nie:weather.forc $forc
    if { $forc == 0 } {
        set unit "metric"
    } elseif { $forc == 1 } {
        set unit "imperial"
    } elseif { $forc == 2 } {
        set unit "both imperial & metric"
    }
    puthelp "PRIVMSG $chan :Default weather location for \002$nick\002 set to \002$text\002 and output units set to \002$unit\002"
    putlog "m00nie::weather::location $nick set their default location to $text."
}

proc metricpref {hand} {
    set forc [getuser $hand XTRA m00nie:weather.forc]
    if {!(($forc == 0) || ($forc == 1) || ($forc == 2)) } {
        set forc $m00nie::weather::forc
    }
    return $forc
}

# Search for current weather
 proc verify {hand chan text} {
    set text [string trimright $text]
    if {(![channel get $chan weather])} {
        putlog "m00nie::weather::search Trigger seen but channel doesnt have +weather set!"
        return 0
    }
    if {$text != ""} {
        set location $text
        if {[validuser $text] == 1} {
            set location [getuser $text XTRA m00nie:weather.location]
        }
    } else {
        set location [getuser $hand XTRA m00nie:weather.location]
    }
    if {[string length $location] == 0 || [regexp {[^0-9a-zA-Z,. ]} $location match] == 1} {
        putlog "m00nie::weather::search location b0rked or no location said/default? Argument: $location"
        puthelp "PRIVMSG $chan :Did you ask to search somewhere? Or use !wl to set a default location"
    return
    } else {
        return $location
    }
}

 proc current {forc location chan} {
    putlog "m00nie::weather::current is running against location: $location and metric pref of $forc"
    set rawpage [getinfo $location conditions]
    set doc [dom parse $rawpage]
    set root [$doc documentElement]
    # Check for no results!
    set notfound [$root selectNodes /response/error/description/text()]
    if {[llength $notfound] > 0 } {
        set errormsg [$notfound nodeValue]
        putlog "m00nie::weather::current ran but could not find any info for $location or an API error occured: $errormsg"
        puthelp "PRIVMSG $chan :$errormsg"
        return
    }
    # Check for multiple results
    set check [multiplecheck $chan $root]
    if {$check > 0} { return }
    set city [[$root selectNodes /response/current_observation/display_location/full/text()] nodeValue]
    if { $forc == 0 } {
        foreach var { observation_time weather temp_c wind_dir wind_kph wind_gust_kph feelslike_c precip_today_string } {
            set $var [[$root selectNodes /response/current_observation/$var/text()] nodeValue]
        }
        append temp_c "°C"
        append feelslike_c "°C"
        set spam "Current weather for \002$city\002 ($observation_time) \002Current conditions:\002 $weather, \002Temperature:\002 $temp_c, \002Wind:\002 From $wind_dir at $wind_kph KPH Gusting to $wind_gust_kph KPH, \002Rain today:\002 $precip_today_string, \002Feels like:\002 $feelslike_c"
    } elseif { $forc == 1} {
        foreach var { observation_time weather temp_f wind_string feelslike_f precip_today_string } {
            set $var [[$root selectNodes /response/current_observation/$var/text()] nodeValue]
        }
        append temp_f "F"
        append feelslike_f "F"
        set spam "Current weather for \002$city\002 ($observation_time) \002Current conditions:\002 $weather, \002Temperature:\002 $temp_f, \002Wind:\002 $wind_string, \002Rain today:\002 $precip_today_string, \002Feels like:\002 $feelslike_f"
    } elseif { $forc == 2} {
        foreach var { observation_time weather temp_f temp_c wind_dir wind_mph wind_kph wind_gust_mph wind_gust_kph feelslike_string precip_today_string } {
            set $var [[$root selectNodes /response/current_observation/$var/text()] nodeValue]
        }
        append temp_f "F"
        append temp_c "°C"
        set spam "Current weather for \002$city\002 ($observation_time) \002Current conditions:\002 $weather, \002Temperature:\002 $temp_f ($temp_c), \002Wind:\002 From the $wind_dir at $wind_mph MPH ($wind_kph KPH) Gusting to $wind_gust_mph MPH ($wind_gust_kph KPH), \002Rain today:\002 $precip_today_string, \002Feels like:\002 $feelslike_string"
    } else {
        putlog "m00nie::weather::current $forc is not a valid value for forc..."
        return
    }
    if { $m00nie::weather::hourinf == 1 } {
        set rawpage [getinfo $location hourly]
            set doc [dom parse $rawpage]
            set root [$doc documentElement]
            # Check for no results!
            set notfound [$root selectNodes /response/error/description/text()]
            if {[llength $notfound] > 0 } {
                set errormsg [$notfound nodeValue]
                putlog "m00nie::weather::current ran but could not find any info for $location or an API error occured: $errormsg"
                puthelp "PRIVMSG $chan :$errormsg"
                return
            }
        append spam " \002Three hour forecast:\002 "
        set i 0
        foreach hour [$root selectNodes "/response/hourly_forecast/forecast"] {
                        incr i
                        if {$i >= 4} {
                                break
                        }
                        set civ [[$hour selectNodes "FCTTIME/civil/text()"] nodeValue]
            set cond [[$hour selectNodes "condition/text()"] nodeValue]
            if { $i == 1 } {
                                append spam $civ
                        } else {
                            append spam ", " $civ
                        }
            append spam { } $cond
            if { $forc == 0 } {
                set temp [[$hour selectNodes "temp/metric/text()"] nodeValue]
                append temp "°C"
                append spam { } $temp
            } elseif { $forc == 1} {
                set temp [[$hour selectNodes "temp/english/text()"] nodeValue]
                append temp "F"
                                append spam { } $temp
            } else {
                set temp_c [[$hour selectNodes "temp/metric/text()"] nodeValue]
                set temp_f [[$hour selectNodes "temp/english/text()"] nodeValue]
                append temp_c "°C"
                append temp_f "F"
                set temp "$temp_f ($temp_c)"
                                append spam { } $temp
            }
                }
    }
    if { $m00nie::weather::suninf == 1 } {
        set rawpage [getinfo $location astronomy]
        set doc [dom parse $rawpage]
        set root [$doc documentElement]
        # Check for no results!
        set notfound [$root selectNodes /response/error/description/text()]
        if {[llength $notfound] > 0 } {
            set errormsg [$notfound nodeValue]
            putlog "m00nie::weather::current ran but could not find any info for $location or an API error occured: $errormsg"
            puthelp "PRIVMSG $chan :$errormsg"
            return
        }
        set sunsh [[$root selectNodes /response/sun_phase/sunset/hour/text()] nodeValue]
        set sunsm [[$root selectNodes /response/sun_phase/sunset/minute/text()] nodeValue]
        set sunrh [[$root selectNodes /response/sun_phase/sunrise/hour/text()] nodeValue]
        set sunrm [[$root selectNodes /response/sun_phase/sunrise/minute/text()] nodeValue]
        set sunspam "\002Sunrise\002 $sunrh:$sunrm, \002Sunset\002 $sunsh:$sunsm"
        append spam { } $sunspam
  }
    puthelp "PRIVMSG $chan :$spam"
}

proc forecast {forc location chan} {
    putlog "m00nie::weather::forecast is running against location: $location"
    set rawpage [getinfo $location forecast]
    set doc [dom parse $rawpage]
    set root [$doc documentElement]
    set check [multiplecheck $chan $root]
    if {$check > 0} { return }
    set dayList [$root selectNodes /response/forecast/txt_forecast/forecastdays/forecastday/title/text()]
    if { ($forc == 0) || ($forc == 2) } {
        set foreList [$root selectNodes /response/forecast/txt_forecast/forecastdays/forecastday/fcttext_metric/text()]
    } elseif { $forc == 1 } {
        set foreList [$root selectNodes /response/forecast/txt_forecast/forecastdays/forecastday/fcttext/text()]
    } else {
        putlog "m00nie::weather::forecast $forc is not a valid value for forc..."
        return
    }
    puthelp "PRIVMSG $chan :Three day forecast for \002$location\002"
    set x 0
    while { $x < 6 } {
        set dayname [[lindex $dayList $x] nodeValue]
        set fore [[lindex $foreList $x] nodeValue]
        puthelp "PRIVMSG $chan :\002$dayname:\002 $fore"
        incr x
    }
}

proc multiplecheck {chan root} {
 set multi [$root selectNodes /response/results/result]
 if {[llength $multi]} {
    putlog "m00nie::weather::multiplecheck multiple results found"
    # Lets check we dont have LOADS of results to spam
    set i 0
    foreach place [$root selectNodes "/response/results/result"] {
        incr i
        if {$i >= 5} {
            puthelp "PRIVMSG $chan :Your search returned more than 5 results. Please try a more specific search."
            return 1
        }
    }
    puthelp "PRIVMSG $chan :Multiple results found pick one and run again"
    foreach place [$root selectNodes "/response/results/result"] {
        set name [lindex [$place selectNodes "name"] 0]
        set country [lindex [$place selectNodes "country"] 0]
        if {[$country text] eq "US"} {
            set state [lindex [$place selectNodes "state"] 0]
            puthelp "PRIVMSG $chan : - [$name text] [$state text] [$country text]"
        } else {
            puthelp "PRIVMSG $chan : - [$name text] [$country text]"
        }
    }
    return 1
    }
}
proc getinfo {location type} {
    regsub -all -- { } $location {%20} location
    set url "http://api.wunderground.com/api/$m00nie::weather::key/$type/q/$location.xml"
    putlog "m00nie::weather::getinfo grabbing data from $url"
    for { set i 1 } { $i <= 5 } { incr i } {
        set xmlpage [::http::data [::http::geturl "$url" -timeout 10000]]
        if {[string length xmlpage] > 0} { break }
    }
    putlog "m00nie::weather::getinfo xmlpage length is: [string length $xmlpage]"
    if { [string length $xmlpage] == 0 }  {
        error "wunderground returned ZERO no data :( or we couldnt connect properly"
    }
    return $xmlpage
}
}
}
putlog "m00nie::weather $m00nie::weather::version loaded"

Enjoy 🙂

m00nie

Comments

  • Thanks for that and it showing Imperial measurements now. My output down not match your example though:

    !w
    Current conditions: Thunderstorm, Temperature: 71.4 F, Wind: From the ENE at 4.5 MPH Gusting to 7.4 MPH, Rain today: 0.07 in (2 mm), Feels like: 71.4 F
    !wf
    MO
    of rain 90%.
    mph.
    mph.
    Winds light and variable.
    day. High 91F. Winds NNW at 5 to 10 mph. Chance of rain 40%.
    mph.
    !wl Springfield MO
    MO.

    Here is what the bot is doing:

    [16:51:48] m00nie::weather::getinfo grabbing data from http://api.wunderground.com/api/xxxxxxx/conditions/q/Springfield%20MO.xml
    [16:51:48] m00nie::weather::getinfo xmlpage length is: 3614
    [16:52:26] m00nie::weather::forecast is running against location: Springfield MO
    [16:52:26] m00nie::weather::getinfo grabbing data from http://api.wunderground.com/api/xxxxxxx/forecast/q/Springfield%20MO.xml
    [16:52:26] m00nie::weather::getinfo xmlpage length is: 10563
    [16:54:20] m00nie::weather::location Gatewayy set their default location to Springfield MO.

    Is that correct behavior for the script?

  • Hi gatewayy

    I’ve tried a fresh copy of the script on a test bot and it seems to be outputting as expected for me:

    !wf Springfield MO
    Three day forecast for Springfield MO
    Saturday: Lots of sunshine. High 93F. Winds SE at 10 to 20 mph.
    Saturday Night: Mainly clear. Low 73F. Winds SSE at 5 to 10 mph.
    Sunday: Sunny. High 98F. Winds SW at 10 to 15 mph.
    Sunday Night: Mainly clear early, then a few clouds later on. Low 74F. Winds light and variable.
    Monday: Partly to mostly cloudy with scattered showers and thunderstorms in the afternoon. High 92F. Winds NNW at 5 to 10 mph. Chance of rain 40%.
    Monday Night: Mostly clear skies. Low 69F. Winds N at 5 to 10 mph.
    !w Springfield MO
    Current weather for Springfield, MO (Last Updated on August 8, 1:29 PM CDT) Current conditions: Clear, Temperature: 81.1 F, Wind: Calm, Rain today: 0.07 in (2 mm), Feels like: 86 F

    I see your output is very slightly different (“Current conditions”) have you modified the script at all? I’ve also edited your post to remove the API key but its maybe best to cease that key and get a new one just to be sure 🙂
    Cheers

    m00nie

  • I had to add a colon in the output lines as follows:

    puthelp “PRIVMSG $chan $spam”

    to

    puthelp “PRIVMSG $chan :$spam”

    My bot is on freenode for what it’s worth.

    Thanks for the script!

  • I had to modify this a bit to work on Ratbox servers. line 42, 60, 76, 94, 112, and 117 all require a : to be placed after the chan. Example:

    puthelp “PRIVMSG $chan :$spam”

    Otherwise the IRCD would truncate the message down to the first word (per RFC)

    Otherwise this looks great! Thanks!

  • Hi Robert/Alan

    Thanks for the info my ircd doesn’t care about the : so explains why I could replicate myself. I’ve updated the script and added a note.
    Cheers 🙂

    m00nie

  • Hi m00nie!
    His TCL is excellent ! But I’m struggling to make it work , to put it in bot and run it it displays the following error message:

    12:55] can’t find package tdom
    while executing
    “package require tdom”
    (in namespace eval “::m00nie::weather” script line 3)
    invoked from within
    “namespace eval weather {
    package require http
    package require tdom
    bind pub – !w m00nie::weather::current_call
    bind pub – !wl m00nie::weather::loc…”
    (in namespace eval “::m00nie” script line 2)
    invoked from within
    “namespace eval m00nie {
    namespace eval weather {
    package require http
    package require tdom
    bind pub – !w m00nie::weather::current_call
    bind pub…”
    (file “scripts/tempo.tcl” line 12)
    invoked from within
    “source scripts/tempo.tcl”
    (file “brasil.conf” line 283)
    [12:55] * CONFIG FILE NOT LOADED (NOT FOUND, OR ERROR)
    [eggdrop@sbyte eggdrop]$ ./eggdrop

    You could help me with installation and configuration , please?

    I understood that she did not find tdown package, but could not install this package on the machine, use CentOS 6.5 and tdown package is not in the repositories , you can help me with installation and configuration also this package ?

  • Any chance that we can have both imperial as well as metric results turned on? I have users who use both, and would love them to be able to get their weather info in both. Thanks!

  • Hi random4t4x14

    V1.5 just uploaded should allow forecasts to display both F/C temps. In the next version I’ll be adding the function for users to define their own preferred output. 🙂
    Cheers

    m00nie

  • Hey, just wanted to say thanks for the update! I notice that when using forc 2 for the display of both Imperial and Metric units, the reply does show correct Temps, as well as Rain, however Wind is still presented in just Imperial units. Below is an example. Is there any way to have this display both MPH and KPH? Thanks again for creating an amazingly useful (and maintained) weather script!

    Current weather for Texarkana, TX (Last Updated on December 5, 9:20 PM CST) Current conditions: Clear, Temperature: 48.0 F (8.9 C), Wind: From the ESE at 1.0 MPH Gusting to 4.0 MPH, Rain today: 0.00 in (0 mm), Feels like: 48 F (9 C)

  • Actually, I resolved the previously mentioned issue myself. Take a look at http://pastebin.com/WeaP5Z6V to see my changes. This fixes the Wind conditions in both forc 0, and forc 2 modes. In addition, I made some slight changes to the formatting to improve readability. Feel free to use that updated version on your site, as I’m not looking for credit or anything, just want a really nice weather script that works how it should 🙂 Thanks again for the amazing scripts you’ve released!

  • 1.7 adds the ability to have users specific their preferred output units (metric/imperial/both) when saving their favorite location 🙂

    m00nie

  • works great .. nice work.

    Keep getting this message right before the weather output ..

    !w 94122
    Wikipedia Error: No html to parse.
    Current weather for San Francisco, CA (Last Updated on April 30, 1:16 PM PDT)
    Current conditions: Clear, Temperature: 18.5 C, Wind: From WNW at 6.4 KPH
    Gusting to 7.9 KPH, Rain today: 0.00 in (0 mm), Feels like: 18.5 C

    any way to get rid of the Wikipedia error message?

  • Hi bee

    Glad its useful for you 🙂 It sounds like you maybe have something else bound on !w? You can check on the party chan with “.bind” and see whats returned for !w or just change the weather binding to !wea or something and retest? 😀
    Cheers

    m00nie

  • 2.0 adds a short 3 hour forecast to current weather check option (does use twice as many API calls but can be disabled for busy chans in config)

    m00nie

  • 2.1 adds the config option to spam sunrise/sunset info for each current call (again uses an additional API call so beware for busy chans)

    m00nie

  • hi there, nice script.. but if two user use the same user@ a conflict will happen if they try to set a specific location. like user 1 (user@host.com) !wl 2 Chesham, United Kingdom .. !w will send that one.. if user 2 (user@hostname.of.another.isp.but.same.user try to !w it will gave him that weather too

  • Hi Tabb 🙂

    Just to check the case here would be two different users sharing the same username? We can probably improve the usermask matching to help a bit.
    Cheers

    m00nie

  • First of all thank you for you contribution with this script!
    In my case, there are occasions where a search term (ex. Trikala, GR) returns many exact matches and that complicates things as there is no way to retrieve a result. On a side note, the email on the “About’ page doesn’t exist anymore ?

    Thanks in advance! 🙂

  • Hi tennisfan 🙂

    Quite a tricky one to resolve in the script as the search is only as good as wundergrounds and search here https://www.wunderground.com/gr/trikala shows three results although when you click to inspect them they seem to have different names you could use. E.g the first of the three options goes to https://www.wunderground.com/global/stations/16622.html which you can find with “Thessaloniki, Greece” and that seems to work.
    Hope that helps
    Cheers

    m00nie

  • 2.2 adds the option to query other users saved locations “!w m00nie” would return the weather in m00nies weather location 🙂

    m00nie

  • is there a way to get high/low temp for the day included in the !w call? i mean i can see forecast has it, but its several lines of text to find out the high/low for the current day. also im so psyched someone actualyl wrote a script to use the proper API that doesnt break all the time. thank you for that!

  • Hi LordDragon

    I’ll try and add this as an option in the next few days but it will use up an extra API call since they only give the high/low info in the forecast call rather than the current weather call 🙂
    Glad you’re finding it useful so far
    Cheers

    m00nie

  • Thanks for your reply m00nie. I have been looking through your script and made a few modifications that adds barometric pressure and humidity. I think I can figure out how to do the High/Low as well. If you are interested, I can send you the mods I made and maybe you’ll like them.

  • I accidently deleted my previous post I think. m00nie, I actually made a few mods to the script that now include humidity as well as barometeric pressure information. I think I might be able to script the high/low in myself too. If you are interested the mods I made, I’d be happy to pass it along. 🙂

    -LordDragon

Leave a Reply