-
Notifications
You must be signed in to change notification settings - Fork 3
/
http.clj
110 lines (95 loc) · 3.59 KB
/
http.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
(ns telegrambot-lib.http
"Formats and sends the HTTP request to the Telegram Bot API."
(:gen-class)
(:require [clj-http.client :as clj-http]
[clojure.core.async :as a]
[clojure.string :as string]
[clojure.tools.logging :as log]
[telegrambot-lib.json :as json]))
(defn- upper-case-name
"Transform the passed in value to an all upper cased name."
[x]
(string/upper-case (name x)))
(defmulti client
"Send HTTP requests to the URL. Methods supported:
- POST"
(fn [http-method & _] http-method))
(defmethod client :post
([http-method url]
(client http-method url nil))
([http-method url req & [respond raise]]
(log/debugf "HTTP %s /%s %s"
(upper-case-name http-method)
(last (string/split url #"/"))
(:body req))
(let [req (merge {:content-type :auto} req)]
(clj-http/post url req respond raise))))
(defmethod client :default
[http-method & _]
(log/errorf "HTTP method '%s' not supported"
(upper-case-name http-method)))
(def bot-api "Telegram Bot API URL." "https://api.telegram.org/bot")
(defn gen-url
"Generate the url to use for the http call, given the method `path`."
[this path]
(format "%s%s/%s" bot-api (:bot-token this) path))
(defn parse-resp
"Parse the JSON response body into a keywordized map."
[resp]
(try
(-> resp :body json/parse-str)
(catch Exception parsing-ex
{:error parsing-ex})))
(defn ex->parsed-resp
"Uniformly transform an `Exception` into a map to return."
[ex]
(let [body (parse-resp (ex-data ex))]
;; NB: This may overwrite the parsing exception, if any,
;; but it's totally fine, since the original `ex` is
;; more important.
(assoc body :error ex)))
(defmulti request
"Send the request to the Telegram Bot API `path` with an optional `content`,
either sync- or asynchronously, depending on the value of the `:async` key
of `this` (the passed bot instance). A sync version returns an \"invocation
result\", while an async one returns a one-off channel with an \"invocation
result\" which is then closed.
The \"invocation result\" is either:
- the response body in case the Telegram Bot API request was successful, or
- a map composed of the response body and the `[:error ex]` entry when the
request was unsuccessful (in terms of the Telegram Bot API), or
- just an `{:error ex}` map in any other exceptional situation when we are
unable to retrieve the response body."
(fn [this & _]
(true? (:async this))))
(defmethod request true
([this path]
(request this path nil))
([this path content]
(let [url (gen-url this path)
req {:body (json/generate-str content)
:content-type :json
:async? true}
resp-channel (a/chan)
on-success (fn [resp]
(let [parsed-resp (parse-resp resp)]
(a/put! resp-channel parsed-resp))
(a/close! resp-channel))
on-failure (fn [ex]
(let [parsed-resp (ex->parsed-resp ex)]
(a/put! resp-channel parsed-resp))
(a/close! resp-channel))]
(client :post url req on-success on-failure)
resp-channel)))
(defmethod request false
([this path]
(request this path nil))
([this path content]
(let [url (gen-url this path)
req {:body (json/generate-str content)
:content-type :json}]
(try
(let [resp (client :post url req)]
(parse-resp resp))
(catch Throwable ex
(ex->parsed-resp ex))))))