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 diff --git a/src/morse/api.clj b/src/morse/api.clj index e50ce05..d8dee8a 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))) (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 (-> resp extract-data :result)] (a/put! result data) (a/put! result ::error)) (a/close! result)) diff --git a/src/morse/qcodes.clj b/src/morse/qcodes.clj new file mode 100644 index 0000000..2a7b7bd --- /dev/null +++ b/src/morse/qcodes.clj @@ -0,0 +1,14 @@ +(ns morse.qcodes + (:require [morse.api :as api])) + +(defn req->morse [handler] + (comp handler api/extract-data)) + +(defn direct-reply [token handler] + (fn [{{{chatid :id} :chat} :message :as req}] + (when-let [reply (handler req)] + (if (string? reply) + (with-meta + {:status 200} + {:direct-reply (api/send-text token chatid reply)}) + {:status 200})))) 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 new file mode 100644 index 0000000..8386e8d --- /dev/null +++ b/test/morse/qcodes_test.clj @@ -0,0 +1,44 @@ +(ns morse.qcodes-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 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 {}))) + non-string-resp (handler2 {:message (command-message "start")}) + nil-resp (handler {:message (command-message "stop")}) + 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, 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 (contains? :direct-reply) not)) + + ; 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]))))