From 3a0d6b37011264a9c5beecdfb54b9ad150220c81 Mon Sep 17 00:00:00 2001 From: Sander Kolman Date: Mon, 7 Sep 2020 18:34:04 +0200 Subject: [PATCH 1/8] added implementation for qcodes direct-reply and req->morse --- src/morse/qcodes.clj | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 src/morse/qcodes.clj diff --git a/src/morse/qcodes.clj b/src/morse/qcodes.clj new file mode 100644 index 0000000..7f33a29 --- /dev/null +++ b/src/morse/qcodes.clj @@ -0,0 +1,21 @@ +(ns morse.qcodes + (:require [compojure.core :refer [make-route]] + [ring.middleware.json :refer [wrap-json-response wrap-json-body]] + [morse.api :as api])) + +(defn- only [pred val] + (when (pred val) + val)) + +(defn req->morse [url-path handler] + (make-route :post url-path + (wrap-json-response + (wrap-json-body + (comp handler :body) + {:keywords? true :bigdecimals? true})))) + +(defn direct-reply [token handler] + (fn [{{{chatid :id} :chat} :message :as req}] + (when-let [reply (only string? (handler req))] + (api/send-text token chatid reply)) + {:status 200})) From 5ecad1d0449611982b2fa9e4563d47e71177ed2f Mon Sep 17 00:00:00 2001 From: Sander Kolman Date: Mon, 7 Sep 2020 18:34:26 +0200 Subject: [PATCH 2/8] documented Q-Code usage --- README.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/README.md b/README.md index 81c94ab..089d215 100644 --- a/README.md +++ b/README.md @@ -263,6 +263,34 @@ Sends an answer to an callback query sent from inline keyboards. show-alert) ``` +## Q-Code + +[Q-Codes](https://en.wikipedia.org/wiki/Q_code) in Morse are shorthands for common patterns. + +### Direct Reply +This Q-code expects your handler to return a string which it will respond to the sender automatically. + +```clojure +(require '[morse.qcodes :as q] + '[morse.handlers :as h]) + +(defn just-say-hi [name] + (str "Hi " name "!")) + +(h/defhandler bot-api + (h/message {:keys [text]} (just-say-hi text))) + +;; direct-reply wraps your handler, it needs the token to send a response +(def wrapped-handler (q/direct-reply token bot-api)) +``` + +### Request to Morse +This Q-code wraps your handler and returns a ring compatible handler. When you use webhooks this should enable you to use the same handler in your server as you've used for long-polling. It will take care of routing to the path and converting the JSON body to EDN. + +```clojure +(def ring-handler (q/req->morse "/any-route" bot-api)) +``` + ## License Copyright © 2017 Anton Chebotaev From 72d651afb618376a9b95478a8e99f2da0ca25fed Mon Sep 17 00:00:00 2001 From: Sander Kolman Date: Tue, 8 Sep 2020 18:04:44 +0200 Subject: [PATCH 3/8] normalize extracting data between polling & webhook, don't do routing --- src/morse/api.clj | 4 +++- src/morse/qcodes.clj | 12 +++--------- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/src/morse/api.clj b/src/morse/api.clj index e50ce05..8f50234 100644 --- a/src/morse/api.clj +++ b/src/morse/api.clj @@ -9,6 +9,8 @@ (def base-url "https://api.telegram.org/bot") +(defn extract-data [resp] + (-> resp :body (json/parse-string true) :result)) (defn get-updates-async "Receive updates from Bot via long-polling endpoint" @@ -21,7 +23,7 @@ :async? true} result (a/chan) on-success (fn [resp] - (if-let [data (-> resp :body (json/parse-string true) :result)] + (if-let [data (extract-data resp)] (a/put! result data) (a/put! result ::error)) (a/close! result)) diff --git a/src/morse/qcodes.clj b/src/morse/qcodes.clj index 7f33a29..b94c030 100644 --- a/src/morse/qcodes.clj +++ b/src/morse/qcodes.clj @@ -1,18 +1,12 @@ (ns morse.qcodes - (:require [compojure.core :refer [make-route]] - [ring.middleware.json :refer [wrap-json-response wrap-json-body]] - [morse.api :as api])) + (:require [morse.api :as api])) (defn- only [pred val] (when (pred val) val)) -(defn req->morse [url-path handler] - (make-route :post url-path - (wrap-json-response - (wrap-json-body - (comp handler :body) - {:keywords? true :bigdecimals? true})))) +(defn req->morse [handler] + (comp handler api/extract-data)) (defn direct-reply [token handler] (fn [{{{chatid :id} :chat} :message :as req}] From d34130a12d6041626422d2b139a051caa610ec33 Mon Sep 17 00:00:00 2001 From: Sander Kolman Date: Tue, 8 Sep 2020 18:12:58 +0200 Subject: [PATCH 4/8] return non-nil value in line with other handlers, add send-text result as meta --- src/morse/qcodes.clj | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/morse/qcodes.clj b/src/morse/qcodes.clj index b94c030..2a7b7bd 100644 --- a/src/morse/qcodes.clj +++ b/src/morse/qcodes.clj @@ -1,15 +1,14 @@ (ns morse.qcodes (:require [morse.api :as api])) -(defn- only [pred val] - (when (pred val) - val)) - (defn req->morse [handler] (comp handler api/extract-data)) (defn direct-reply [token handler] (fn [{{{chatid :id} :chat} :message :as req}] - (when-let [reply (only string? (handler req))] - (api/send-text token chatid reply)) - {:status 200})) + (when-let [reply (handler req)] + (if (string? reply) + (with-meta + {:status 200} + {:direct-reply (api/send-text token chatid reply)}) + {:status 200})))) From 3499f04c425224db5c8989e4327430e82fbbdae3 Mon Sep 17 00:00:00 2001 From: Sander Kolman Date: Tue, 8 Sep 2020 18:16:19 +0200 Subject: [PATCH 5/8] initial test for direct-reply --- test/morse/qcodes_test.clj | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 test/morse/qcodes_test.clj diff --git a/test/morse/qcodes_test.clj b/test/morse/qcodes_test.clj new file mode 100644 index 0000000..1983962 --- /dev/null +++ b/test/morse/qcodes_test.clj @@ -0,0 +1,29 @@ +(ns morse.api-test + (:require [cheshire.core :as json] + [clojure.test :refer :all] + [morse.handlers :as h] + [morse.qcodes :as q] + [morse.test-utils :as u])) + +(defn command-message [command] + {:text (str "/" command) + :chat {:id "fake-chat-id"}}) + +(deftest direct-reply-handler + (let [handler (q/direct-reply "fake-token" (h/command-fn "start" (constantly "change"))) + start-resp (handler {:message (command-message "start")}) + other-resp (handler {:message (command-message "stop")}) + direct-reply-request (-> start-resp meta :direct-reply (u/capture-request)) + body (json/decode (slurp (:body direct-reply-request)) true)] + + ; check direct reply does not mess with the message when it shouldn't + (is (= other-resp nil)) + + ; check that it returns a status 200 + (is (= start-resp {:status 200})) + + ; check that direct-reply is now post request + (is (= :post (:request-method direct-reply-request))) + + ; check that default params are presented + (is (u/has-subset? {:chat_id "fake-chat-id" :text "change"} [body])))) From c113d0f2afc510e10816ea20713fc99991a37e29 Mon Sep 17 00:00:00 2001 From: Sander Kolman Date: Thu, 10 Sep 2020 18:17:37 +0200 Subject: [PATCH 6/8] fixed mistake with extracting data --- src/morse/api.clj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/morse/api.clj b/src/morse/api.clj index 8f50234..d8dee8a 100644 --- a/src/morse/api.clj +++ b/src/morse/api.clj @@ -10,7 +10,7 @@ (def base-url "https://api.telegram.org/bot") (defn extract-data [resp] - (-> resp :body (json/parse-string true) :result)) + (-> resp :body (json/parse-string true))) (defn get-updates-async "Receive updates from Bot via long-polling endpoint" @@ -23,7 +23,7 @@ :async? true} result (a/chan) on-success (fn [resp] - (if-let [data (extract-data resp)] + (if-let [data (-> resp extract-data :result)] (a/put! result data) (a/put! result ::error)) (a/close! result)) From 4c000498de3a13b15313d26f195f9f7750034d79 Mon Sep 17 00:00:00 2001 From: Sander Kolman Date: Thu, 10 Sep 2020 18:18:23 +0200 Subject: [PATCH 7/8] more exhaustive tests for direct-reply and extract-data --- test/morse/api_test.clj | 5 +++++ test/morse/qcodes_test.clj | 16 +++++++++++----- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/test/morse/api_test.clj b/test/morse/api_test.clj index 9369f8e..b8e51d7 100644 --- a/test/morse/api_test.clj +++ b/test/morse/api_test.clj @@ -11,6 +11,11 @@ (def inline-query-id 1337) (def callback-query-id 1338) +(deftest extract-data-from-update + (let [update-obj {:body "{\"key-num\": 1, \"key-ar-str\": [\"astring\"]}"} + result {:key-num 1 :key-ar-str ["astring"]}] + (is (= (api/extract-data update-obj) result)))) + (deftest get-file-request (let [req (-> (api/get-file token 116) (u/capture-request)) diff --git a/test/morse/qcodes_test.clj b/test/morse/qcodes_test.clj index 1983962..8c32d11 100644 --- a/test/morse/qcodes_test.clj +++ b/test/morse/qcodes_test.clj @@ -11,16 +11,22 @@ (deftest direct-reply-handler (let [handler (q/direct-reply "fake-token" (h/command-fn "start" (constantly "change"))) - start-resp (handler {:message (command-message "start")}) - other-resp (handler {:message (command-message "stop")}) - direct-reply-request (-> start-resp meta :direct-reply (u/capture-request)) + handler2 (q/direct-reply "fake-token" (h/command-fn "start" (constantly {}))) + string-resp (handler {:message (command-message "start")}) + non-string-resp (handler2 {:message (command-message "start")}) + nil-resp (handler {:message (command-message "stop")}) + direct-reply-request (-> string-resp meta :direct-reply (u/capture-request)) body (json/decode (slurp (:body direct-reply-request)) true)] ; check direct reply does not mess with the message when it shouldn't - (is (= other-resp nil)) + (is (= nil-resp nil)) ; check that it returns a status 200 - (is (= start-resp {:status 200})) + (is (= string-resp {:status 200})) + (is (= non-string-resp {:status 200})) + + ; check a non string responding handler does not generate a text + (is (= (-> non-string-resp meta :direct-reply) nil)) ; check that direct-reply is now post request (is (= :post (:request-method direct-reply-request))) From a65222c1bdaa7ddec2edd4e7e5002d4d262b30df Mon Sep 17 00:00:00 2001 From: Sander Kolman Date: Thu, 10 Sep 2020 20:38:37 +0200 Subject: [PATCH 8/8] refined some tests --- test/morse/qcodes_test.clj | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/test/morse/qcodes_test.clj b/test/morse/qcodes_test.clj index 8c32d11..8386e8d 100644 --- a/test/morse/qcodes_test.clj +++ b/test/morse/qcodes_test.clj @@ -1,4 +1,4 @@ -(ns morse.api-test +(ns morse.qcodes-test (:require [cheshire.core :as json] [clojure.test :refer :all] [morse.handlers :as h] @@ -9,24 +9,33 @@ {:text (str "/" command) :chat {:id "fake-chat-id"}}) +(deftest ring-req->morse + (let [handler (h/command-fn "start" :text) + webhandler (q/req->morse handler) + start-request {:body "{\"message\":{\"text\": \"/start\"}}"} + start-command {:message (command-message "start")}] + (is (= (handler start-command) (webhandler start-request))))) + (deftest direct-reply-handler (let [handler (q/direct-reply "fake-token" (h/command-fn "start" (constantly "change"))) handler2 (q/direct-reply "fake-token" (h/command-fn "start" (constantly {}))) - string-resp (handler {:message (command-message "start")}) non-string-resp (handler2 {:message (command-message "start")}) nil-resp (handler {:message (command-message "stop")}) - direct-reply-request (-> string-resp meta :direct-reply (u/capture-request)) + direct-reply-request (-> {:message (command-message "start")} handler u/capture-request) body (json/decode (slurp (:body direct-reply-request)) true)] ; check direct reply does not mess with the message when it shouldn't (is (= nil-resp nil)) - ; check that it returns a status 200 - (is (= string-resp {:status 200})) + ; check that it returns a status 200, optionally with meta + (u/capture-request + (let [string-resp (handler {:message (command-message "start")})] + (is (= string-resp {:status 200})) + (is (-> string-resp meta (contains? :direct-reply))))) (is (= non-string-resp {:status 200})) ; check a non string responding handler does not generate a text - (is (= (-> non-string-resp meta :direct-reply) nil)) + (is (-> non-string-resp meta (contains? :direct-reply) not)) ; check that direct-reply is now post request (is (= :post (:request-method direct-reply-request)))