| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356 |
- # A weather script that uses www.darksky.net as its source. Its output is based
- # on incith-weather.tcl.
- #
- # It relies on two packages:
- # - https://github.com/horgh/geonames-tcl (for looking up latitude/longitude)
- # - https://github.com/horgh/darksky-tcl (for looking up weather)
- #
- # Setup:
- # - Create a weather-darksky.conf and put it in your Eggdrop root directory.
- # See weather-darksky.conf.sample for the format.
- # - You'll need to source both of those .tcl files in your bot prior to this
- # one.
- # - Partyline: .chanset #channel +weather-darksky
- # - Channel: .wz <location> for current weather or .wzf <location> for a
- # forecast
- package require darksky
- package require geonames
- namespace eval ::wds {
- variable output_cmd putserv
- variable geonames_cache [dict create]
- }
- proc ::wds::lookup_current {nick uhost hand chan argv} {
- if {![channel get $chan weather-darksky]} { return }
- set query [::wds::get_query $nick $uhost $argv]
- if {$query == ""} {
- $::wds::output_cmd "PRIVMSG $chan :Usage: .wz <location>"
- return
- }
- set data [::wds::get_data $chan $query]
- if {![dict exists $data geonames] || ![dict exists $data darksky]} {
- return
- }
- set geonames [dict get $data geonames]
- set darksky [dict get $data darksky]
- if {[catch {::wds::output_current $chan $geonames $darksky} error]} {
- $::wds::output_cmd "PRIVMSG $chan :Error: $error"
- }
- }
- proc ::wds::lookup_forecast {nick uhost hand chan argv} {
- if {![channel get $chan weather-darksky]} { return }
- set query [::wds::get_query $nick $uhost $argv]
- if {$query == ""} {
- $::wds::output_cmd "PRIVMSG $chan :Usage: .wzf <location>"
- return
- }
- set data [::wds::get_data $chan $query]
- if {![dict exists $data geonames] || ![dict exists $data darksky]} {
- return
- }
- set geonames [dict get $data geonames]
- set darksky [dict get $data darksky]
- if {[catch {::wds::output_forecast $chan $geonames $darksky} error]} {
- $::wds::output_cmd "PRIVMSG $chan :Error: $error"
- }
- }
- # Retrieve the query to look up. If none is given, return the last one we used.
- # If one was given, remember it.
- #
- # We only remember it if there is a matching user record. In theory we could add
- # users but in order to do that we must have a handle to assign them, and what
- # to use is unclear. I suppose we could generate something random.
- proc ::wds::get_query {nick uhost argv} {
- set query [string trim $argv]
- set query [string tolower $query]
- if {$query != ""} {
- ::wds::set_default_location $nick $uhost $query
- return $query
- }
- return [::wds::get_default_location $nick $uhost]
- }
- proc ::wds::set_default_location {nick uhost query} {
- set hand [finduser $nick!$uhost]
- if {$hand == "*"} {
- return
- }
- setuser $hand XTRA weather-darksky $query
- }
- proc ::wds::get_default_location {nick uhost} {
- set hand [finduser $nick!$uhost]
- if {$hand == "*"} {
- return ""
- }
- set location [getuser $hand XTRA weather-darksky]
- return $location
- }
- proc ::wds::get_data {chan query} {
- set conf [::wds::load_config]
- set geonames_result [::wds::get_lat_long $conf $query]
- if {[dict exists $geonames_result error]} {
- $::wds::output_cmd "PRIVMSG $chan :Error looking up latitude/longitude: [dict get $geonames_result error]"
- return [dict create]
- }
- set darksky [::darksky::new [dict get $conf darksky_key]]
- set darksky_result [::darksky::forecast $darksky \
- [dict get $geonames_result lat] [dict get $geonames_result lng]]
- if {[dict exists $darksky_result error]} {
- $::wds::output_cmd "PRIVMSG $chan :Error looking up forecast: [dict get $darksky_result error]"
- return [dict create]
- }
- return [dict create geonames $geonames_result darksky $darksky_result]
- }
- proc ::wds::get_lat_long {conf query} {
- if {[dict exists $::wds::geonames_cache $query]} {
- return [dict get $::wds::geonames_cache $query]
- }
- set geonames [::geonames::new [dict get $conf geonames_username]]
- set geonames_result {}
- # If the user gave us what looks like a US zip code, use the postal code
- # search API rather than the text search API. The text search API gives
- # unreliable results using zip codes alone.
- if {[regexp -- {\A[0-9]{5}\Z} $query]} {
- set geonames_result [::geonames::postalcode_latlong $geonames $query US]
- } else {
- set geonames_result [::geonames::search_latlong $geonames $query]
- }
- if {![dict exists $geonames_result error]} {
- dict set ::wds::geonames_cache $query $geonames_result
- ::wds::save_cache $conf
- }
- return $geonames_result
- }
- proc ::wds::save_cache {conf} {
- set fh [open [dict get $conf geonames_cache_file] w]
- puts -nonewline $fh $::wds::geonames_cache
- close $fh
- }
- proc ::wds::load_cache {} {
- set conf [::wds::load_config]
- if {![file exists [dict get $conf geonames_cache_file]]} {
- set ::wds::geonames_cache [dict create]
- return
- }
- set fh [open [dict get $conf geonames_cache_file]]
- set ::wds::geonames_cache [read -nonewline $fh]
- close $fh
- }
- proc ::wds::load_config {} {
- set fh [open weather-darksky.conf]
- set contents [read -nonewline $fh]
- close $fh
- set lines [split $contents \n]
- set conf [dict create]
- foreach line $lines {
- set line [string trim $line]
- if {$line == ""} {
- continue
- }
- set pieces [split $line "="]
- if {[llength $pieces] != 2} {
- putlog "weather-darksky.tcl: Invalid configuration line: $line"
- continue
- }
- set key [string trim [lindex $pieces 0]]
- set value [string trim [lindex $pieces 1]]
- if {$key == ""} {
- putlog "weather-darksky.tcl: Invalid key on line: $line"
- continue
- }
- if {$value == ""} {
- putlog "weather-darksky.tcl: Invalid value on line: $line"
- continue
- }
- dict set conf $key $value
- }
- if {![dict exists $conf geonames_username]} {
- error "no geonames_username set"
- }
- if {![dict exists $conf darksky_key]} {
- error "no darksky_key set"
- }
- if {![dict exists $conf geonames_cache_file ]} {
- error "no geonames_cache_file set"
- }
- return $conf
- }
- proc ::wds::output_current {chan geonames darksky} {
- set output ""
- append output [dict get $geonames name]
- append output ", "
- append output [dict get $geonames countryName]
- append output " ("
- append output [::wds::format_decimal [dict get $darksky latitude]]
- append output "°N/"
- append output [::wds::format_decimal [dict get $darksky longitude]]
- append output "°W)"
- append output " \002Local time\002: "
- append output [clock format [dict get $darksky time] \
- -format "%H:%M" \
- -timezone :[dict get $darksky timezone] \
- ]
- append output " \002Conditions\002: "
- append output [dict get $darksky summary]
- $::wds::output_cmd "PRIVMSG $chan :$output"
- set output ""
- append output "\002Temperature\002: "
- append output [dict get $darksky temperature]
- append output "°C"
- append output " ("
- append output [::wds::celsius_to_fahrenheit [dict get $darksky temperature]]
- append output "°F"
- append output ")"
- append output " \002Humidity\002: "
- append output [::wds::format_decimal [expr [dict get $darksky humidity]*100]]
- append output "%"
- append output " \002Pressure\002: "
- append output [dict get $darksky pressure]
- append output " hPa"
- $::wds::output_cmd "PRIVMSG $chan :$output"
- set output ""
- append output "\002Wind\002: "
- append output [dict get $darksky windSpeed]
- append output "m/s"
- append output " \002Clouds\002: "
- append output [::wds::format_decimal \
- [expr [dict get $darksky cloudCover]*100] \
- ]
- append output "%"
- $::wds::output_cmd "PRIVMSG $chan :$output"
- }
- proc ::wds::output_forecast {chan geonames darksky} {
- set output ""
- append output [dict get $geonames name]
- append output ", "
- append output [dict get $geonames countryName]
- append output " ("
- append output [::wds::format_decimal [dict get $darksky latitude]]
- append output "°N/"
- append output [::wds::format_decimal [dict get $darksky longitude]]
- append output "°W) "
- $::wds::output_cmd "PRIVMSG $chan :$output"
- set output ""
- set count 0
- foreach forecast [dict get $darksky forecast] {
- if {$count == 5} {
- break
- }
- if {![wds::should_show_forecast [dict get $darksky timezone] $forecast]} {
- continue
- }
- if {$output != ""} {
- append output " "
- }
- set day [clock format [dict get $forecast time] -format "%A"]
- append output "\002$day\002: "
- append output [dict get $forecast summary]
- append output " "
- append output [dict get $forecast temperatureMax]
- append output "/"
- append output [dict get $forecast temperatureMin]
- append output "°C"
- append output " ("
- append output [::wds::celsius_to_fahrenheit \
- [dict get $forecast temperatureMax] \
- ]
- append output "/"
- append output [::wds::celsius_to_fahrenheit \
- [dict get $forecast temperatureMin] \
- ]
- append output "°F"
- append output ")"
- incr count
- }
- $::wds::output_cmd "PRIVMSG $chan :$output"
- }
- # We can get old forecast info. For example if it's Sunday we could get
- # Saturday's. We don't want to show that.
- #
- # We do want to show today's though. The time given in the forecast is at
- # 00:00:00 of the day.
- proc ::wds::should_show_forecast {timezone forecast} {
- set now [clock seconds]
- set today [::wds::format_ymd $timezone $now]
- set forecast_day [::wds::format_ymd $timezone [dict get $forecast time]]
- return $today == $forecast_day || [dict get $forecast time] >= $now
- }
- proc ::wds::format_ymd {timezone time} {
- return [clock format $time \
- -format "%Y-%m-%d" \
- -timezone :$timezone \
- ]
- }
- proc ::wds::celsius_to_fahrenheit {celsius} {
- set fahrenheit [expr $celsius*9.0/5.0+32.0]
- return [::wds::format_decimal $fahrenheit]
- }
- proc ::wds::format_decimal {number} {
- return [format "%.2f" $number]
- }
- setudef flag weather-darksky
- bind pub -|- .wz ::wds::lookup_current
- bind pub -|- .wzf ::wds::lookup_forecast
- ::wds::load_cache
- putlog "weather-darksky.tcl (https://github.com/horgh/eggdrop-scripts) loaded"
- putlog "weather-darksky.tcl: Powered by Dark Sky (www.darksky.net)"
- putlog "weather-darksky.tcl: Powered by GeoNames (www.geonames.org)"
|