-
Notifications
You must be signed in to change notification settings - Fork 19
/
Copy pathweather-darksky.tcl
356 lines (295 loc) · 9.65 KB
/
weather-darksky.tcl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
# 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)"