#---------------------------------------------------------------------# # incith:weather $Rev:: 107 $ # # $Id:: incith-weather.tcl 107 2009-02-12 01:28:44Z incith $ # # # # retrieves weather and forecast data from www.wunderground.com # # tested on Eggdrop v1.6.19 & Windrop v1.6.17 # # # # Usage: # # .chanset #channel +weather # # !weather # # returns the current conditions # # !forecast # # returns a 3-5day forecast (depends on data) # # !sky # # returns the astronomy data (sun/moonrise, daylight, etc) # # !time # # returns the time and date # # # # Forum Support: # # http://forum.egghelp.org/viewtopic.php?t=10466 # # # # ChangeLog (m/d/y): # # 1/17/09: license changed to GPLv3. tweaks for svn. added !time. # # # # TODO: # # - locales for output (languages) # # + fix multiple results, take best match, etc # # + user defaults (so we can just !weather) # # - separate proc for forecast fetching, to speed up results # # - theoretically :) # # - forecast updated time # # - merge _handlers, allow all %var% in all _formats, max_results # # - limit size of output, check length of variables? # # - Suggestions/Thanks/Bugs/Ideas, e-mail at bottom of header. # # # # LICENSE (GPLv3): # # This program is free software: you can redistribute it and/or # # modify it under the terms of the GNU General Public License as # # published by the Free Software Foundation, either version 3 of # # the License, or (at your option) any later version. # # # # This program is distributed in the hope that it will be useful, # # but WITHOUT ANY WARRANTY; without even the implied warranty of # # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # # # # See the GNU General Public License for more details. # # (http://www.gnu.org/licenses/gpl-3.0.txt) # # # # Copyright (C) 2009, Jordan # # http://incith.com ~ incith@gmail.com ~ irc.freenode.net/#incith # #---------------------------------------------------------------------# package require http 2.3 setudef flag weather # 0 (zero) will disable an optional variable, 1 or above enables # namespace eval incith { namespace eval weather { # the bind prefix/command char ("!" or ".", etc) variable command_char "!" # binds ("one two three") as many as you need, "" to disable one variable weather_binds "w weather" variable forecast_binds "f fc forecast" variable astronomy_binds "sky astronomy" variable time_binds "time" # if you want to change the order or remove some items that are sent # w0 - location, w1 - updated, w2 - conditions, w3 - temperature, w4 - windchill # w5 - high/low, w6 - humidity, w7 - dew point, w8 - UV index, w9 - pressure # w10 - wind variable weather_format "%w0% %w1% %w2% %w3% %w4% %w5% %w8% %w6% %w7% %w9% %w10%" # some control over the forecast format, too # f0 - location, f1 - updated, f2 - first day high/low, f3 - second day high/low # f4 - third day, f5 - fourth day, f6 - fifth day variable forecast_format "%f0% %f1% %f2% %f3% %f4% %f5% %f6%" # show latitude and longitude for locations? variable show_lat_lon 1 # symbol to put beside temperature numbers variable degree_symbol "°" # display celsius temperatures first, and fahrenheit in () variable celsius_first 0 # allow binds to be used in /msg's to the bot? variable private_messages 1 # send public/channel replies to the user instead of to the channel? variable public_to_private 0 # send replies as notices instead of messages? variable notices 0 variable notice_errors_only 0 # this will be used to separate items (Foo; Bar; Baz) # setting this to "\n" will have *everything* on a new line variable separator "; " # use this if you would only like to break results into 3 new lines variable separate_lines 0 # convert update time into 24-hour time variable 24hour_time 0 # make use of bolding where appropriate? variable bold 1 # the maximum length a line sent should be variable split_length "440" # if you're using a proxy, enter it here (proxy.example.com:3128) variable proxy "" # how long (in seconds) before the http request times out? variable timeout 15 # number of minute(s) to ignore flooders, 0 to disable flood protection variable ignore 1 # how many requests in how many seconds is considered flooding? # by default, this allows 3 queries in 10 seconds, the 4th being ignored # and ignoring requests for 'variable ignore' minutes variable flood 4:10 } } # script begings namespace eval incith::weather { variable version "incith:weather-SVN" variable debug 0 # language testing variable language en if {[file exists "scripts/lang/incith-weather-${incith::weather::language}.txt"]} { putlog " - Reading language file" source scripts/lang/incith-weather-${incith::weather::language}.txt } elseif {[file exists "${incith::weather::language}"]} { # specifying a direct path to the language file putlog " - Reading language file" source ${incith::weather::language} } else { putlog " - Language '${incith::weather::language}' not found, defaulting to English." namespace eval incith::weather::lang { variable temperature "Temperature_default" } } } # bind the binds if {${incith::weather::weather_binds} != ""} { foreach bind [split ${incith::weather::weather_binds} " "] { # public message binds bind pub -|- "${incith::weather::command_char}${bind}" incith::weather::weather_handler # private message binds if {${incith::weather::private_messages} >= 1} { bind msg -|- "${incith::weather::command_char}${bind}" incith::weather::weather_handler } } } if {${incith::weather::forecast_binds} != ""} { foreach bind [split ${incith::weather::forecast_binds} " "] { # public message binds bind pub -|- "${incith::weather::command_char}${bind}" incith::weather::forecast_handler # private message binds if {${incith::weather::private_messages} >= 1} { bind msg -|- "${incith::weather::command_char}${bind}" incith::weather::forecast_handler } } } if {${incith::weather::astronomy_binds} != ""} { foreach bind [split ${incith::weather::astronomy_binds} " "] { # public message binds bind pub -|- "${incith::weather::command_char}${bind}" incith::weather::astronomy_handler # private message binds if {${incith::weather::private_messages} >= 1} { bind msg -|- "${incith::weather::command_char}${bind}" incith::weather::astronomy_handler } } } if {${incith::weather::time_binds} != ""} { foreach bind [split ${incith::weather::time_binds} " "] { # public message binds bind pub -|- "${incith::weather::command_char}${bind}" incith::weather::time_handler # private message binds if {${incith::weather::private_messages} >= 1} { bind msg -|- "${incith::weather::command_char}${bind}" incith::weather::time_handler } } } namespace eval incith { namespace eval weather { # [custom_format] : converts a string to the desired output # proc custom_format {htmla type input} { array set html $htmla # for separate line results option if {${incith::weather::separate_lines} >= 1} { set alternate_sep "\n" } else { set alternate_sep ${incith::weather::separator} } # spaces make things look wrong regsub -all {\s+} $input {} input if {$type == "weather"} { if {[info exists html(location)]} { if {[info exists html(latitude)] && [info exists html(longitude)] && $incith::weather::show_lat_lon >= 1} { set input [string map "{%w0%} {${incith::weather::separator}[ibold $html(location)] ($html(latitude)\/$html(longitude))}" $input] } else { set input [string map "{%w0%} {${incith::weather::separator}[ibold $html(location)]}" $input] } } else { set input [string map "{%w0%} {}" $input] } if {[info exists html(update_time)] && [info exists html(update_date)]} { set input [string map "{%w1%} {${incith::weather::separator}[ibold "Updated:"] $html(update_time) $html(update_timezone) ($html(update_date))}" $input] } else { set input [string map "{%w1%} {}" $input] } if {[info exists html(conditions)] && [string length $html(conditions)] >= 1} { set input [string map "{%w2%} {${incith::weather::separator}[ibold "Conditions:"] $html(conditions)}" $input] } else { set input [string map "{%w2%} {}" $input] } if {[info exists html(tempf)] && [info exists html(tempc)]} { set input [string map "{%w3%} {${alternate_sep}[ibold "Temperature:"] [i2fac $html(tempf) $html(tempc)]}" $input] } else { set input [string map "{%w3%} {}" $input] } if {[info exists html(windchillf)] && [info exists html(windchillc)]} { set input [string map "{%w4%} {${incith::weather::separator}[ibold "Windchill:"] [i2fac $html(windchillf) $html(windchillc)]}" $input] } else { set input [string map "{%w4%} {}" $input] } # compare the date the weather was updated to output todays high/low if {([info exists html(fc1d)]) && $html(fc1d) == $html(todays_day)} { set input [string map "%w5% {${incith::weather::separator}[ibold "High/Low:"] [if2c $html(fc1hf) $html(fc1lf)]}" $input] } elseif {([info exists html(fc2d)]) && $html(fc2d) == $html(todays_day)} { set input [string map "%w5% {${incith::weather::separator}[ibold "High/Low:"] [if2c $html(fc2hf) $html(fc2lf)]}" $input] } elseif {([info exists html(fc3d)]) && $html(fc3d) == $html(todays_day)} { set input [string map "%w5% {${incith::weather::separator}[ibold "High/Low:"] [if2c $html(fc3hf) $html(fc3lf)]}" $input] } elseif {([info exists html(fc4d)]) && $html(fc4d) == $html(todays_day)} { set input [string map "%w5% {${incith::weather::separator}[ibold "High/Low:"] [if2c $html(fc4hf) $html(fc4lf)]}" $input] } elseif {([info exists html(fc5d)]) && $html(fc5d) == $html(todays_day)} { set input [string map "%w5% {${incith::weather::separator}[ibold "High/Low:"] [if2c $html(fc5hf) $html(fc5lf)]}" $input] } else { set input [string map "%w5% {${incith::weather::separator}[ibold "High/Low:"] Unavailable}" $input] } if {[info exists html(humidity)]} { set input [string map "{%w6%} {${incith::weather::separator}[ibold "Humidity:"] $html(humidity)%}" $input] } else { set input [string map "{%w6%} {}" $input] } if {[info exists html(dewf)] && [info exists html(dewc)]} { set input [string map "{%w7%} {${alternate_sep}[ibold "Dew Point:"] [i2fac $html(dewf) $html(dewc)]}" $input] } else { set input [string map "{%w7%} {}" $input] } if {[info exists html(uv_min)] && [info exists html(uv_max)]} { set input [string map "{%w8%} {${incith::weather::separator}[ibold "UV:"] $html(uv_min)\/$html(uv_max)}" $input] } else { set input [string map "{%w8%} {}" $input] } if {[info exists html(pressure_hpa)] && [info exists html(pressure_in)]} { set input [string map "{%w9%} {${incith::weather::separator}[ibold "Pressure:"] ${html(pressure_in)} in\/${html(pressure_hpa)} hPa}" $input] } else { set input [string map "{%w9%} {}" $input] } if {[info exists html(windd)]} { set temp_wind "${incith::weather::separator}[ibold "Wind:"] $html(windd)" if {[info exists html(windm)] && [info exists html(windk)]} { if {${incith::weather::celsius_first} >= 1} { append temp_wind " at $html(windk) KPH ($html(windm) MPH)" } else { append temp_wind " at $html(windm) MPH ($html(windk) KPH)" } } set input [string map "{%w10%} {${temp_wind}}" $input] } else { set input [string map "{%w10%} {}" $input] } regsub -- "^\\s*${incith::weather::separator}" $input {} input return $input } elseif {$type == "forecast"} { if {[info exists html(location)]} { set input [string map "{%f0%} {${incith::weather::separator}[ibold "$html(location) Forecast"] (High/Low)}" $input] } else { set input [string map "{%f0%} {}" $input] } if {[info exists html(update_time)] && [info exists html(update_date)]} { set input [string map "{%f1%} {${incith::weather::separator}[ibold "Updated:"] $html(update_time) $html(update_timezone) ($html(update_date))}" $input] } else { set input [string map "{%f1%} {}" $input] } # prepend the chance of precipitation if necessary if {[info exists html(fc1chance)]} { set html(fc1c) "$html(fc1chance) $html(fc1c)" } if {[info exists html(fc2chance)]} { set html(fc2c) "$html(fc2chance) $html(fc2c)" } if {[info exists html(fc3chance)]} { set html(fc3c) "$html(fc3chance) $html(fc3c)" } if {[info exists html(fc4chance)]} { set html(fc4c) "$html(fc4chance) $html(fc4c)" } if {[info exists html(fc5chance)]} { set html(fc5c) "$html(fc5chance) $html(fc5c)" } if {[info exists html(fc1hf)] && [info exists html(fc1lf)] && [info exists html(fc1c)]} { set input [string map "{%f2%} {${incith::weather::separator}[ibold "$html(fc1d):"] $html(fc1c), [if2c $html(fc1hf) $html(fc1lf)]}" $input] } else { set input [string map "{%f2%} {}" $input] } if {[info exists html(fc2hf)] && [info exists html(fc2lf)] && [info exists html(fc2c)]} { set input [string map "{%f3%} {${alternate_sep}[ibold "$html(fc2d):"] $html(fc2c), [if2c $html(fc2hf) $html(fc2lf)]}" $input] } else { set input [string map "{%f3%} {}" $input] } if {[info exists html(fc3hf)] && [info exists html(fc3lf)] && [info exists html(fc3c)]} { set input [string map "{%f4%} {${incith::weather::separator}[ibold "$html(fc3d):"] $html(fc3c), [if2c $html(fc3hf) $html(fc3lf)]}" $input] } else { set input [string map "{%f4%} {}" $input] } if {[info exists html(fc4hf)] && [info exists html(fc4lf)] && [info exists html(fc4c)]} { set input [string map "{%f5%} {${incith::weather::separator}[ibold "$html(fc4d):"] $html(fc4c), [if2c $html(fc4hf) $html(fc4lf)]}" $input] } else { set input [string map "{%f5%} {}" $input] } if {[info exists html(fc5hf)] && [info exists html(fc5lf)] && [info exists html(fc5c)]} { set input [string map "{%f6%} {${incith::weather::separator}[ibold "$html(fc5d):"] $html(fc5c), [if2c $html(fc5hf) $html(fc5lf)]}" $input] } else { set input [string map "{%f6%} {}" $input] } regsub -- "^\\s*${incith::weather::separator}" $input {} input return $input } else { return 0 } } # [weather_handler] : handles public & private messages for !weather # proc weather_handler {nick uhand hand args} { global lastbind if {[llength $args] >= 2} { # public message if {${incith::weather::public_to_private} >= 1} { set where $nick } else { set where [lindex $args 0] if {[lsearch -exact [channel info $where] +weather] == -1} { return } } set chan [lindex $args 0] set input [lindex $args 1] } else { # private message if {${incith::weather::private_messages} <= 0} { return } set where $nick set chan "private" set input [lindex $args 0] } # flood protection if {[flood $nick $uhand $hand $where]} { return } # user defaults, user must exist as a user on the bot if {[lindex $input 0] == "-set"} { if {[validuser $hand]} { setuser $hand XTRA incith:weather.location "[lrange $input 1 end]" send_output $nick "Default weather location set to [lrange $input 1 end]" return } else { send_output $nick "Sorry, your bot handle was not found. Unable to set a default." return } set input [lrange $input 1 end] } elseif {[regexp -- "^\\s*$" $input] && [validuser $hand]} { set input [getuser $hand XTRA incith:weather.location] } # log it putlog "${incith::weather::version}: <${nick}/${chan}> ${lastbind} $input" # no input given if {[regexp -- "^\\s*$" $input]} { send_output $where "Please visit http://www.wunderground.com for more details on searching methods." return } # fetch the html array set html [fetch_html $input] # check for html's existence if {[info exists html(error)]} { send_output $where $html(error) return } # for separate line results option if {${incith::weather::separate_lines} >= 1} { set alternate_sep "\n" } else { set alternate_sep ${incith::weather::separator} } # make sure we have data to send, and send it set reply [custom_format [array get html] "weather" $incith::weather::weather_format] if {$reply == "0" || [string length $reply] >= 750} { unset reply } if {[info exists reply]} { send_output $where $reply } else { send_output $where "There was a problem while attempting to parse the data." } } # [forecast_handler] : handles forecast requests # proc forecast_handler {nick uhand hand args} { global lastbind if {[llength $args] >= 2} { # public message if {${incith::weather::public_to_private} >= 1} { set where $nick } else { set where [lindex $args 0] if {[lsearch -exact [channel info $where] +weather] == -1} { return } } set chan [lindex $args 0] set input [lindex $args 1] } else { # private message if {${incith::weather::private_messages} <= 0} { return } set where $nick set chan "private" set input [lindex $args 0] } # flood protection if {[flood $nick $uhand $hand $where]} { return } # user defaults, user must exist as a user on the bot if {[lindex $input 0] == "-set"} { if {[validuser $hand]} { setuser $hand XTRA incith:weather.location "[lrange $input 1 end]" send_output $nick "Default weather location set to [lrange $input 1 end]." return } else { send_output $nick "Sorry, your bot handle was not found. Unable to set a default." } set input [lrange $input 1 end] } elseif {[regexp -- "^\\s*$" $input] && [validuser $hand]} { set input [getuser $hand XTRA incith:weather.location] } # log it putlog "${incith::weather::version}: <${nick}/${chan}> ${lastbind} $input" # no input given if {[regexp -- "^\\s*$" $input]} { send_output $where "Please visit http://www.wunderground.com for more details on searching methods." return } # fetch the html array set html [fetch_html $input] # check for html's existence if {[info exists html(error)]} { send_output $where $html(error) return } # for separate line results option if {${incith::weather::separate_lines} >= 1} { set alternate_sep "\n" } else { set alternate_sep ${incith::weather::separator} } # make sure we have data to send, and send it set reply [custom_format [array get html] "forecast" $incith::weather::forecast_format] if {$reply == "0" || [string length $reply] >= 750} { unset reply } if {[info exists reply]} { send_output $where $reply } else { send_output $where "There was a problem while attempting to parse the data." } } # [astronomy_handler] : handles public & private messages for !sky # proc astronomy_handler {nick uhand hand args} { global lastbind if {[llength $args] >= 2} { # public message if {${incith::weather::public_to_private} >= 1} { set where $nick } else { set where [lindex $args 0] if {[lsearch -exact [channel info $where] +weather] == -1} { return } } set chan [lindex $args 0] set input [lindex $args 1] } else { # private message set where $nick set chan "private" set input [lindex $args 0] if {${incith::weather::private_messages} <= 0} { return } } # flood protection if {[flood $nick $uhand $hand $where]} { return } # user defaults, user must exist as a user on the bot if {[lindex $input 0] == "-set"} { if {[validuser $hand]} { setuser $hand XTRA incith:weather.location "[lrange $input 1 end]" send_output $nick "Default weather location set to [lrange $input 1 end]." return } else { send_output $nick "Sorry, your bot handle was not found. Unable to set a default." return } set input [lrange $input 1 end] } elseif {[regexp -- "^\\s*$" $input] && [validuser $hand]} { set input [getuser $hand XTRA incith:weather.location] } # log it putlog "${incith::weather::version}: <${nick}/${chan}> ${lastbind} $input" # no input given if {[regexp -- "^\\s*$" $input]} { send_output $where "Please visit http://www.wunderground.com for more details on searching methods." return } # fetch the html array set html [fetch_html $input] # check for html's existence if {[info exists html(error)]} { send_output $where $html(error) return } # for separate line results option if {${incith::weather::separate_lines} >= 1} { set alternate_sep "\n" } else { set alternate_sep ${incith::weather::separator} } # make sure we have data to send, and send it if {[info exists html(location)]} { set reply "[ibold "$html(location) Astronomy"]" if {[info exists html(sun_rise)] && [info exists html(sun_set)]} { append reply "${incith::weather::separator}[ibold "Sunrise:"] $html(sun_rise)${incith::weather::separator}[ibold "Sunset:"] $html(sun_set)" } if {[info exists html(moon_rise)] && [info exists html(moon_set)]} { if {[info exists html(moon_phase)] && [info exists html(moon_percent)]} { append reply "${incith::weather::separator}[ibold "Moon:"] $html(moon_phase) ($html(moon_percent))" } append reply "${incith::weather::separator}[ibold "Moonrise:"] $html(moon_rise)" append reply "${incith::weather::separator}[ibold "Moonset:"] $html(moon_set)" } if {[info exists html(length_light)]} { append reply "${incith::weather::separator}[ibold "Visible Light:"] $html(length_light)" } if {[info exists html(length_day)]} { append reply "${incith::weather::separator}[ibold "Daylight Length:"] $html(length_day)" } if {[info exists html(length_tomorrow)]} { append reply "${incith::weather::separator}[ibold "Tomorrow:"] $html(length_tomorrow)" } send_output $where $reply } else { send_output $where "There was a problem while attempting to parse the data." } } # [time_handler] : handles public & private messages for !time # proc time_handler {nick uhand hand args} { global lastbind if {[llength $args] >= 2} { # public message if {${incith::weather::public_to_private} >= 1} { set where $nick } else { set where [lindex $args 0] if {[lsearch -exact [channel info $where] +weather] == -1} { return } } set chan [lindex $args 0] set input [lindex $args 1] } else { # private message if {${incith::weather::private_messages} <= 0} { return } set where $nick set chan "private" set input [lindex $args 0] } # flood protection if {[flood $nick $uhand $hand $where]} { return } # user defaults, user must exist as a user on the bot if {[lindex $input 0] == "-set"} { if {[validuser $hand]} { setuser $hand XTRA incith:weather.location "[lrange $input 1 end]" send_output $nick "Default weather location set to [lrange $input 1 end]" return } else { send_output $nick "Sorry, your bot handle was not found. Unable to set a default." return } set input [lrange $input 1 end] } elseif {[regexp -- "^\\s*$" $input] && [validuser $hand]} { set input [getuser $hand XTRA incith:weather.location] } # log it putlog "${incith::weather::version}: <${nick}/${chan}> ${lastbind} $input" # no input given if {[regexp -- "^\\s*$" $input]} { send_output $where "Please visit http://www.wunderground.com for more details on searching methods." return } # fetch the html array set html [fetch_html $input] # check for html's existence if {[info exists html(error)]} { send_output $where $html(error) return } # for separate line results option if {${incith::weather::separate_lines} >= 1} { set alternate_sep "\n" } else { set alternate_sep ${incith::weather::separator} } # make sure we have data to send, and send it set reply "" if {[info exists html(location)]} { set reply "[ibold "$html(location)"]" if {[info exists html(local_time)] && [info exists html(local_timezone)]} { append reply "${incith::weather::separator}[ibold "Time:"] $html(local_time) $html(local_timezone)" } if {![info exists html(local_date)] && [info exists html(update_date)]} { set html(local_date) $html(update_date) } if {![info exists html(local_date)] && ![info exists html(update_date)] && [info exists html(fupdate_date)]} { set html(local_date) $html(fupdate_date) } if {[info exists html(local_date)]} { append reply "${incith::weather::separator}[ibold "Date:"] $html(local_date)" } } if {$reply != ""} { send_output $where $reply } else { send_output $where "There was a problem while attempting to parse the data." } } # [fetch_html] : fetches and returns a list of usable data # proc fetch_html {query {query_url "http://www.wunderground.com/cgi-bin/findweather/getForecast?query="} {no_format 0}} { set multiple_results 0 # store the website we are retrieving set output(query) $query if {$no_format != 1} { set output(query) [::http::formatQuery $query] set output(query) [string trimleft $output(query) "+"] } # setup proxy information, if any if {[string match {*:*} ${incith::weather::proxy}] == 1} { set proxy_info [split ${incith::weather::proxy} ":"] } # the "browser" we are using set ua "Lynx/2.8.5rel.1 libwww-FM/2.14 SSL-MM/1.4.1 OpenSSL/0.9.7e" if {[info exists proxy_info] == 1} { set http [::http::config -useragent $ua -proxyhost [lindex $proxy_info 0] -proxyport [lindex $proxy_info 1]] } else { set http [::http::config -useragent $ua] } # retrieve the html; round()'ed because -timeout likes integers. [catch] for error messages catch {set http [::http::geturl "${query_url}$output(query)" -headers {Cookie "Units=both"} -timeout [expr round(1000 * ${incith::weather::timeout})]]} output(status) # make sure the http request succeeded if {![string match {::http::*} $output(status)]} { set output(error) "Failed to connect." } elseif {[::http::status $http] == "timeout"} { set output(error) "The operation timed out after ${incith::weather::timeout} seconds." } elseif {[::http::status $http] != "ok"} { set output(error) "The server could not complete our request (server error)." } # in case we're erroring out, close the socket and report why if {[info exists output(error)]} { ::http::cleanup $http return [array get output] } # $html will contain our html source code set html [::http::data $http] # we no longer require the connection ::http::cleanup $http # debug: output the html to a file if {${incith::weather::debug} >= 1} { set fopen [open incith-weather-pre.txt w] puts $fopen $html close $fopen } if {[regexp {Search Results} $html] || [regexp {Scroll down to view a list of all all cities} $html]} { set multiple_results 1 } # html cleanups regsub -all {\n} $html {} html regsub -all {\t} $html {} html regsub -all { } $html { } html regsub -all {°} $html {} html regsub -all {>} $html {>} html regsub -all {<} $html {<} html regsub -all {.*?} $html {} html regsub -all {Add to My Favorites} $html {} html # debug: output the html to a file if {${incith::weather::debug} >= 1} { set fopen [open incith-weather-post.txt w] puts $fopen $html close $fopen } # check for problems set multiple 0 if {[regexp {City Not Found

} $html]} { set output(error) "The requested city was not found." } elseif {[regexp {Redirect page

There is nothing} $html] || [regexp {Choose the first letter of a city or search for any location.

} $html] || [regexp {

Europe

Choose a Country
} $html] || [regexp {

Australia

Select a country or Australian province below.
} $html] || [regexp {

Africa

Choose a Country
} $html]} { set multiple_results 0 set output(error) "Please refine your search." } elseif {$multiple_results == 1} { # todo: store bad results too in case duplicate results both have no data # e.g., first bad result put to bad_list, second matching bad result put to good list set i 1; set multiple 2; set num_results 0 set loop_reply ""; set loop_loc ""; set loop_url "" # skip down a bit in the html regsub -- {.*?Search Results} $html {} html regsub -- {.*?Scroll down to view a list of all all cities} $html {} html # this regexp removes locations with no data foreach {- row} [regexp -inline -all -- {(.+?)} $html] { if {[string match "* *" $row]} { regsub -all -- $row $html {} html } } while {$i <= 1} { # grab a search result regexp -- {(.+?) .+? = 750} { set output(error) "Output too long, please refine your search." } } # bail out if there's a problem if {[info exists output(error)]} { return [array get output] } # html parsing # # conditions regexp -- {Local Time:(.+? A?P?M)\s+(.+?)( on (.+?))?} $html - output(local_time) output(local_timezone) output(local_date) output(local_date) if {[info exists output(local_date)]} { if {$output(local_date) == ""} { unset output(local_date) } } regexp -- {Updated: (.+? A?P?M)\s+(.+?) on (.+?)(?:|)} $html - output(update_time) output(update_timezone) output(update_date) if {![info exists output(update_time)]} { regexp -- {Updated: (.+? A?P?M)\s+(.+?)( on (.+?))?} $html - output(update_time) output(update_timezone) output(update_date) output(update_date) } # multiple location parses just in case regexp -- {id="cityTable">

(.+?)\ ?

} $html - output(location) if {![info exists output(location)]} { regexp -- {(.+?)(?:\ \(Airport\)|\(PWS\))?} $html - output(location) } regexp -- {pwsvariable="tempf".+?> (.+?) F / (.+?) C} $html - output(tempf) output(tempc) if {[info exists output(tempf)]} { set templength [string length $output(tempf)] if {$templength > 20} { putlog "length fubar" set output(error) "Something is fubar! :)" return [array get output] } } regexp -- {Windchill: (.+?) F / (.+?) C} $html - output(windchillf) output(windchillc) regexp -- {pwsvariable="humidity" english="" metric="" value="(.+?)">} $html - output(humidity) regexp -- {pwsvariable="dewptf".+?> (.+?) °F / (.+?) °C} $html - output(dewf) output(dewc) # normal wind: regexp -- {Wind:.+? pwsvariable="windspeedmph" english="mph" metric="km/h"> (.+?) mph / (.+?) km/h} $html - output(windm) output(windk) # wind direction.. regexp -- {from the .+? pwsvariable="winddir" english="" metric="" value="(.+?)">} $html - output(windd) # calm wind direction: if {![info exists output(windd)]} { regexp -- {Wind:.+? pwsvariable="windspeedmph" english="mph" metric="km/h">(Calm)} $html - output(windd) } # since adding 'from the .+? ' to the above RE, if it fails, the wind should be Calm # removing 'from the .+? ' causes it to fetch wind dir data from the wrong location # moved snippet of code below mobile fetch regexp -- {
(.+?)
} $html - output(conditions) regexp -- {UV:(.+?) out of (.+?)} $html - output(uv_min) output(uv_max) # parse only the pressure block regexp -- {pwsvariable="baromin" english="in" metric="hPa" value=".*?">(.*?)} $html - output(pressure_data) if {[info exists output(pressure_data)]} { regexp -- {(.+?) in / (.+?) hPa} $output(pressure_data) - output(pressure_in) output(pressure_hpa) } # lat/lon regexp -- {Lat/Lon: (\d+\.?\d+?\ \w)\ (\d+\.?\d+?\ \w)} $html - output(latitude) output(longitude) if {[info exists output(latitude)] && [info exists output(longitude)]} { regsub -- "\ " $output(latitude) ${incith::weather::degree_symbol} output(latitude) regsub -- "\ " $output(longitude) ${incith::weather::degree_symbol} output(longitude) } # astronomy sunrise/set and moonrise/set data regexp -- {Actual Time(.*?)(.*?)} $html - output(sun_rise) output(sun_set) if {[info exists output(sun_rise)]} { if {$output(sun_rise) == ""} { set output(sun_rise) "Unavailable" } } if {[info exists output(sun_set)]} { if {$output(sun_set) == ""} { set output(sun_set) "Unavailable" } } if {![info exists output(sun_rise)]} { set output(sun_rise) "Unavailable" } if {![info exists output(sun_set)]} { set output(sun_set) "Unavailable" } regexp -- {Moon(.+?)(.*?)} $html - output(moon_rise) output(moon_set) regexp -- {Length Of Visible Light:(.+?)Length of Day
(.+?)
Tomorrow will be (.+?)} $html - output(length_light) output(length_day) output(length_tomorrow) regexp -- {
(.+?)\, (\d+\%) of the Moon is Illuminated
} $html - output(moon_phase) output(moon_percent) # some locations have spaces, some don't if {[info exists output(moon_rise)] && [info exists output(moon_set)]} { set output(moon_rise) [string trim $output(moon_rise)] set output(moon_set) [string trim $output(moon_set)] } # forecast # skip down to the actual forecast data regsub -- {.*?5-Day Forecast} $html {} html # fixes for % chance of precipitation regsub -all -- {Chance of T-storms} $html {Chance of Thunderstorms} html regsub -all -- {Chance of a Thunderstorm} $html {Chance of Thunderstorms} html regsub -all -- {T-storms} $html {Chance of Thunderstorms} html regsub -all -- {alt="Thunderstorm"} $html {alt="Chance of Thunderstorms"} html set fc_regexp_day {(.+?)} set fc_regexp_cond {
(.+?)
} set fc_regexp_highlow {
(.+?)° F\|(.+?)° F
.*?° C.*?° C.*?
} regexp -- "${fc_regexp_day}" $html - output(fc1d) regsub -- $fc_regexp_day $html {} html regexp -- "${fc_regexp_day}" $html - output(fc2d) regsub -- $fc_regexp_day $html {} html regexp -- "${fc_regexp_day}" $html - output(fc3d) regsub -- $fc_regexp_day $html {} html regexp -- "${fc_regexp_day}" $html - output(fc4d) regsub -- $fc_regexp_day $html {} html regexp -- "${fc_regexp_day}" $html - output(fc5d) regsub -- $fc_regexp_day $html {} html regexp -- "${fc_regexp_cond}" $html - output(fc1c) regsub -- $fc_regexp_cond $html {} html regexp -- "${fc_regexp_cond}" $html - output(fc2c) regsub -- $fc_regexp_cond $html {} html regexp -- "${fc_regexp_cond}" $html - output(fc3c) regsub -- $fc_regexp_cond $html {} html regexp -- "${fc_regexp_cond}" $html - output(fc4c) regsub -- $fc_regexp_cond $html {} html regexp -- "${fc_regexp_cond}" $html - output(fc5c) regsub -- $fc_regexp_cond $html {} html # chance of precipitation if {[info exists output(fc1c)]} { set fc_re_chance "$output(fc1c)
(\\d+\\%) chance of precipitation
" regexp $fc_re_chance $html - output(fc1chance) regsub $fc_re_chance $html {} html } if {[info exists output(fc2c)]} { set fc_re_chance "$output(fc2c)
(\\d+\\%) chance of precipitation
" regexp $fc_re_chance $html - output(fc2chance) regsub $fc_re_chance $html {} html } if {[info exists output(fc3c)]} { set fc_re_chance "$output(fc3c)
(\\d+\\%) chance of precipitation
" regexp $fc_re_chance $html - output(fc3chance) regsub $fc_re_chance $html {} html } if {[info exists output(fc4c)]} { set fc_re_chance "$output(fc4c)
(\\d+\\%) chance of precipitation
" regexp $fc_re_chance $html - output(fc4chance) regsub $fc_re_chance $html {} html } if {[info exists output(fc5c)]} { set fc_re_chance "$output(fc5c)
(\\d+\\%) chance of precipitation
" regexp $fc_re_chance $html - output(fc5chance) regsub $fc_re_chance $html {} html } regexp -- "${fc_regexp_highlow}" $html - output(fc1hf) output(fc1lf) regsub -- $fc_regexp_highlow $html {} html regexp -- "${fc_regexp_highlow}" $html - output(fc2hf) output(fc2lf) regsub -- $fc_regexp_highlow $html {} html regexp -- "${fc_regexp_highlow}" $html - output(fc3hf) output(fc3lf) regsub -- $fc_regexp_highlow $html {} html regexp -- "${fc_regexp_highlow}" $html - output(fc4hf) output(fc4lf) regsub -- $fc_regexp_highlow $html {} html regexp -- "${fc_regexp_highlow}" $html - output(fc5hf) output(fc5lf) regsub -- $fc_regexp_highlow $html {} html # debug stuff if {${incith::weather::debug} >= 1} { send_putlog "output(fc1d):" $output(fc1d) send_putlog "output(fc1c):" $output(fc1c) send_putlog "output(fc1hf):" $output(fc1hf) send_putlog "output(fc1lf):" $output(fc1lf) } # use mobile data if we are missing some things if {$multiple <= 1} { if {![info exists output(fc1c)] || ![info exists output(fc1d)] || ![info exists output(fc1hf)] || ![info exists output(update_time)] || ![info exists output(tempf)] || ![info exists output(windd)]} { if {$incith::weather::debug >= 1} { set mobtmperror "pulling mobile data" if {![info exists output(fc1c)]} { append mobtmperror ", missing fc1c" } if {![info exists output(fc1d)]} { append mobtmperror ", missing fc1d" } if {![info exists output(fc1hf)]} { append mobtmperror ", missing fc1hf" } if {![info exists output(update_time)]} { append mobtmperror ", missing update_time" } if {![info exists output(tempf)]} { append mobtmperror ", missing tempf" } if {![info exists output(windd)]} { append mobtmperror ", missing windd" } putlog $mobtmperror } array set output [fetch_mobile_html $output(query) [array get output]] # mobile doesn't give the chance of precipitation if {![info exists output(fc1hf)] || ![info exists output(fc1d)]} { if {[info exists output(fc1chance)]} { unset output(fc1chance) } if {[info exists output(fc2chance)]} { unset output(fc2chance) } if {[info exists output(fc3chance)]} { unset output(fc3chance) } if {[info exists output(fc4chance)]} { unset output(fc4chance) } if {[info exists output(fc5chance)]} { unset output(fc5chance) } } } } # this just looks silly if {[info exists output(windm)] && [info exists output(windk)]} { if {$output(windm) == "0.0" && $output(windk) == "0.0"} { unset output(windm) unset output(windk) } } # set wind if we still do not have one if {![info exists output(windd)]} { set output(windd) "Calm" } # get the current day for this location, used for proper high/low display if {$multiple <= 1} { if {[info exists output(local_time)] && [info exists output(local_date)]} { set output(todays_day) [clock format [clock scan "$output(local_time) $output(local_date)"] -format "%A"] } else { if {[info exists output(update_time)]} { set output(todays_day) [clock format [clock scan "$output(update_time) $output(update_date)"] -format "%A"] } } } # convert updated time into 24 hour time if wanted if {${incith::weather::24hour_time} >= 1 && [info exists output(update_time)]} { regsub -- {^(.* (AM)?(PM)?).*?$} $output(update_time) {\1} output(update_time) set output(update_time) [clock format [clock scan "$output(update_time)"] -format "%H:%M"] } # this returns our 'output' array return [array get output] } # [fetch_mobile_html] : fetches and returns a list of usable data # proc fetch_mobile_html {query current_array} { array set foutput $current_array # setup proxy information, if any if {[string match {*:*} ${incith::weather::proxy}] == 1} { set proxy_info [split ${incith::weather::proxy} ":"] } # the "browser" we are using set ua "Lynx/2.8.5rel.1 libwww-FM/2.14 SSL-MM/1.4.1 OpenSSL/0.9.7e" if {[info exists proxy_info] == 1} { set http [::http::config -useragent $ua -proxyhost [lindex $proxy_info 0] -proxyport [lindex $proxy_info 1]] } else { set http [::http::config -useragent $ua] } # retrieve the html; round()'ed because -timeout likes integers. [catch] for error messages catch {set http [::http::geturl "http://mobile.wunderground.com/cgi-bin/findweather/getForecast?brand=mobile&query=$query" -headers {Cookie "Units=both"} -timeout [expr round(1000 * ${incith::weather::timeout})]]} foutput(status) # make sure the http request succeeded if {![string match {::http::*} $foutput(status)]} { set foutput(error) "Failed to connect." } elseif {[::http::status $http] == "timeout"} { set foutput(error) "The operation timed out after ${incith::weather::timeout} seconds." } elseif {[::http::status $http] != "ok"} { set output(error) "The server could not complete our request (server error)." } # in case we're erroring out, close the socket and report why if {[info exists foutput(error)]} { ::http::cleanup $http return [array get foutput] } # $html will contain our html source code set html [::http::data $http] # we no longer require the connection ::http::cleanup $http # html cleanups regsub -all {\n} $html {} html regsub -all {\t} $html {} html regsub -all { } $html { } html regsub -all {°} $html {} html regsub -all {>} $html {>} html regsub -all {<} $html {<} html regsub -all {(?:|)} $html {} html # html parsing # regexp -- {Updated: (.+? A?P?M)\ ?(.+?) on (.+?)} $html - output(update_time) output(update_timezone) output(update_date) regexp -- {Observed at\ ?(.+?) } $html - output(location) regexp -- {Temperature (.+?)F / (.+?)C} $html - foutput(tempf) foutput(tempc) # wind regexp -- {Wind(.+?) at (.+?) mph / (.+?) km/h} $html - foutput(windd) foutput(windm) foutput(windk) # forecast # todo: update to grab 1 by 1 regexp -- {Forecast as of (.+?) on (.+?)\ ?} $html - foutput(fupdate_time) foutput(fupdate_date) # the Night-named days get removed, to parse the non-Night days easier regsub -all {(?:Sunday|Monday|Tuesday|Wednesday|Thursday|Friday|Saturday) Night
} $html {} html if {$incith::weather::debug >= 1} { putlog "in mobile, fc1hf = $foutput(fc1hf)\nfc1chance = $foutput(fc1chance)\n" } if {![info exists foutput(fc1d)] || ![info exists foutput(fc1hf)]} { if {$incith::weather::debug >= 1} { putlog "fetching forecast in mobile" } set fc_regexp_day {(.+?)
(.+?)\.High: (.+?)° F\..+? Low: (.+?)° F\. / .+?} regexp -- "${fc_regexp_day}.+?${fc_regexp_day}.+?${fc_regexp_day}" $html - foutput(fc1d) foutput(fc1c) foutput(fc1hf) foutput(fc1lf) foutput(fc2d) foutput(fc2c) foutput(fc2hf) foutput(fc2lf) foutput(fc3d) foutput(fc3c) foutput(fc3hf) foutput(fc3lf) } # convert updated time into 24 hour time if wanted if {${incith::weather::24hour_time} >= 1 && [info exists foutput(fupdate_time)]} { regsub -- {^(.* (AM)?(PM)?).*?$} $foutput(fupdate_time) {\1} foutput(fupdate_time) set foutput(fupdate_time) [clock format [clock scan "$foutput(fupdate_time)"] -format "%H:%M"] } # this returns our 'foutput' array return [array get foutput] } # [ilang] : get the translation (eh, better description to come) # proc ilang {word} { set word [string tolower $word] set string [set incith::weather::lang::$word] return $string } # [send_output] : sends $data appropriately out to $where # proc send_output {where data {isErrorNick {}}} { if {${incith::weather::notices} >= 1} { foreach line [incith::weather::parse_output $data] { putquick "NOTICE $where :${line}" } } elseif {${incith::weather::notice_errors_only} >= 1 && $isErrorNick != ""} { foreach line [incith::weather::parse_output $data] { putquick "NOTICE $isErrorNick :${line}" } } else { foreach line [incith::weather::parse_output $data] { putquick "PRIVMSG $where :${line}" } } } # [send_putlog] : sends $data appropriately out to putlog # proc send_putlog {prefix {data ""}} { if {![info exists data]} { foreach line [incith::weather::parse_output $prefix] { putlog "${line}" } } else { foreach line [incith::weather::parse_output $data] { putlog "${prefix} ${line}" } } } # [parse_output] : prepares output for sending to a channel/user, calls line_wrap # proc parse_output {input} { set parsed_output [set parsed_current {}] if {[string match "\n" $incith::weather::separator] == 1 || ${incith::weather::separate_lines} >= 1} { regsub {\n\s*$} $input "" input foreach newline [split $input "\n"] { foreach line [incith::weather::line_wrap $newline] { lappend parsed_output $line } } } else { regsub "(?:${incith::weather::separator}|\\|)\\s*$" $input {} input foreach line [incith::weather::line_wrap $input] { lappend parsed_output $line } } return $parsed_output } # LINE_WRAP # takes a long line in, and chops it before the specified length # http://forum.egghelp.org/viewtopic.php?t=6690 # proc line_wrap {str {splitChr { }}} { set out [set cur {}] set i 0 set len $incith::weather::split_length foreach word [split [set str][set str ""] $splitChr] { if {[incr i [string len $word]] > $len} { lappend out [join $cur $splitChr] set cur [list $word] set i [string len $word] } else { lappend cur $word } incr i } lappend out [join $cur $splitChr] } # [ibold] : bolds some text, if bolding is enabled # proc ibold {input} { if {${incith::weather::bold} >= 1} { return "\002${input}\002" } return $input } # [i2fac] : display only 2 temperatures # proc i2fac {f c} { if {${incith::weather::celsius_first} >= 1} { return "${c}${incith::weather::degree_symbol}C (${f}${incith::weather::degree_symbol}F)" } else { return "${f}${incith::weather::degree_symbol}F (${c}${incith::weather::degree_symbol}C)" } } # [ifac] : helper proc for forecast-type temperatures & conversion procs # proc ifac {fh fl ch cl} { if {${incith::weather::celsius_first} >= 1} { return "${ch}/${cl}${incith::weather::degree_symbol}C (${fh}/${fl}${incith::weather::degree_symbol}F)" } else { return "${fh}/${fl}${incith::weather::degree_symbol}F (${ch}/${cl}${incith::weather::degree_symbol}C)" } } # IF2C # converts fahrenheit to celsius & returns a formatted string # proc if2c {fh fl} { set ch [expr {int((5.0/9.0)*(${fh} - 32.0))}] set cl [expr {int((5.0/9.0)*(${fl} - 32.0))}] return [ifac $fh $fl $ch $cl] } # IC2F # converts celsius to fahrenheit & returns a formatted string # proc ic2f {ch cl} { set fh [expr {int(((9.0/5.0)*${ch}) + 32.0)}] set fl [expr {int(((9.0/5.0)*${cl}) + 32.0)}] return [ifac $fh $fl $ch $cl] } # FLOOD_INIT # modified from bseen # variable flood_data variable flood_array proc flood_init {} { if {$incith::weather::ignore < 1} { return 0 } if {![string match *:* $incith::weather::flood]} { putlog "${incith::weather::version}: variable flood not set correctly." return 1 } set incith::weather::flood_data(flood_num) [lindex [split $incith::weather::flood :] 0] set incith::weather::flood_data(flood_time) [lindex [split $incith::weather::flood :] 1] set i [expr $incith::weather::flood_data(flood_num) - 1] while {$i >= 0} { set incith::weather::flood_array($i) 0 incr i -1 } } ; flood_init # FLOOD # updates and returns a users flood status # proc flood {nick uhand hand where} { # exempt friends if {[matchattr $hand "f|f" $where]} { return 0 } if {$incith::weather::ignore < 1} { return 0 } if {$incith::weather::flood_data(flood_num) == 0} { return 0 } set i [expr ${incith::weather::flood_data(flood_num)} - 1] while {$i >= 1} { set incith::weather::flood_array($i) $incith::weather::flood_array([expr $i - 1]) incr i -1 } set incith::weather::flood_array(0) [unixtime] if {[expr [unixtime] - $incith::weather::flood_array([expr ${incith::weather::flood_data(flood_num)} - 1])] <= ${incith::weather::flood_data(flood_time)}} { putlog "${incith::weather::version}: flood detected from ${nick}." newignore [join [maskhost *!*[string trimleft $uhand ~]]] $incith::weather::version flooding $incith::weather::ignore return 1 } else { return 0 } } } } # the script has loaded. putlog " - ${incith::weather::version} loaded." # EOF