-
Notifications
You must be signed in to change notification settings - Fork 65
/
weather.clj
126 lines (111 loc) 路 4.05 KB
/
weather.clj
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
(ns yetibot.commands.weather
(:require
[schema.core :as sch]
[clojure.string :as str]
[clj-http.client :as http.client]
[taoensso.timbre :refer [info warn error]]
[yetibot.core.config :refer [get-config]]
[yetibot.core.hooks :refer [cmd-hook]]
[yetibot.models.postal-code :refer [chk-postal-code]]
[yetibot.commands.weather.formatters :as fmt]))
(def config (:value (get-config sch/Any [:weather :weatherbitio])))
(def api-key (:key config))
(def default-zip (-> config :default :zip))
(defn get-json
[uri {:keys [query-params] :as opts}]
(try
(let [options (merge {:as :json :coerce :always}
opts
{:query-params (merge {:key api-key :units "M"}
query-params)})
{:keys [status body]} (http.client/get uri options)]
(condp = status
200 body
204 {:error "Location not found."}))
(catch Exception e
(let [{:keys [status body]} (ex-data e)]
(error "Request failed with status:" status)
body))))
(defn- endpoint
"API docs: https://www.weatherbit.io/api"
[path]
(str "https://api.weatherbit.io/v2.0/" path))
(defn- get-by-name
"Get current conditions by location name str"
[path city]
(get-json (endpoint path) {:query-params {:city city}}))
(defn- get-by-pc
"Get current conditions by post code and country code"
[path pc cc]
(get-json (endpoint path) {:query-params {:postal_code pc
:country cc}}))
(defn- get-by-loc
"Attempt to parse out postal code and call the corresponding get-by-name or
get-by-pc function"
[path loc]
(if-let [[pc cc] (apply chk-postal-code (str/split (str loc) #"\s*,\s*"))]
(get-by-pc path pc cc)
(get-by-name path loc)))
(defn- error-response [{:keys [error status_code status_message]}]
(cond
error {:result/error error}
(= 429 status_code) {:result/error status_message}))
(defn- format-current
[formatters c]
(cons (fmt/location-title c)
(map #(% formatters c) [fmt/description
fmt/feels-like
fmt/wind])))
(defn current
[loc]
(get-by-loc "current" loc))
(defn forecast
[loc]
(get-by-loc "forecast/daily" loc))
(defn parse-args
"parse args to vec of unit kw and args str"
[s]
(let [[_ unit args] (re-matches #"(?i)(?:\s*(-[micf]))?\s*(.+)", s)
unit (when-not (nil? unit)
(if (or (= unit "-i") (= unit "-f")) :i :m))]
[unit args]))
(defn weather-cmd
"weather <location> # look up current weather for <location> by name or postal code, optional country code, -c or -f to force units"
{:yb/cat #{:info}}
[{:keys [match]}]
(let [[unit loc] (parse-args match)
result (current loc)]
(or
(error-response result)
(let [{[cs] :data} result
formatters (fmt/get-formatters unit (:country_code cs))]
{:result/value (format-current formatters cs)
:result/data cs}))))
(defn default-weather-cmd
"weather # look up weather for default location"
{:yb/cat #{:info}}
[_]
(if default-zip
(weather-cmd {:match default-zip})
{:result/error "A default zip code is not configured.
Configure it at path weather.weatherbitio.default.zip"}))
(defn forecast-cmd
"weather forecast <location> # look up forecast for <location> by name or postal code, optional country code, -c or -f to force units"
{:yb/cat #{:info}}
[{[_ match] :match}]
(let [[unit loc] (parse-args match)
result (forecast loc)]
(or
(error-response result)
(let [{:keys [city_name country_code data]} result
formatters (fmt/get-formatters unit country_code)
location (fmt/location-title result)]
{:result/value (into [location]
(map
(partial fmt/forecast-item formatters)
data))
:result/data result}))))
(cmd-hook #"weather"
#"forecast\s+(.+)" forecast-cmd
#".+" weather-cmd
_ default-weather-cmd)