GithubHelp home page GithubHelp logo

morse's Introduction

Morse

Circle CI Clojars codecov

:)

Morse is a client for Telegram Bot API for the Clojure programming language.

Installation

Add [morse "0.4.3"] to the dependency section in your project.clj file.

There is also a template which you can use to bootstrap your project:

lein new morse my-project
cd my-project
export TELEGRAM_TOKEN=...
lein run

Detecting user's actions

Telegram sends updates about events in chats in form of Update objects.

Inside those there could be commands, inline queries and many more. To help you with these Morse provides you helpers and some macros in morse.handlers namespace.

If you are familiar with building web-service with Compojure, you'll find similarities here:

(ns user
  (:require [morse.handlers :as h]
            [morse.api :as t]))

(def token "YOUR-BIG-SECRET")          

; This will define bot-api function, which later could be
; used to start your bot
(h/defhandler bot-api
  ; Each bot has to handle /start and /help commands.
  ; This could be done in form of a function:
  (h/command-fn "start" (fn [{{id :id :as chat} :chat}]
                          (println "Bot joined new chat: " chat)
                          (t/send-text token id "Welcome!")))

  ; You can use short syntax for same purposes
  ; Destructuring works same way as in function above
  (h/command "help" {{id :id :as chat} :chat}
    (println "Help was requested in " chat)
    (t/send-text token id "Help is on the way"))

  ; Handlers will be applied until there are any of those
  ; returns non-nil result processing update.

  ; Note that sending stuff to the user returns non-nil
  ; response from Telegram API.     

  ; So match-all catch-through case would look something like this:
  (h/message message (println "Intercepted message:" message)))

Messages

Receives Message object as first parameter in a function or target of binding:

(command-fn "start" (fn [msg] (println "Received command: " msg)))
; or in a macro form
(command "start" msg (println "Received command: " msg))

If you wish to process messages that are not prefixed by a command, there is also a helper:

(message-fn (fn [msg] (println "Received message: " msg)))
; or in a macro form
(message msg (println "Received message: " msg))

Inline requests

There is also a helper to define handlers for InlineQueries in a similar form:

(inline-fn (fn [inline] (println "Received inline: " inline)))
; or in a macro form
(inline inline (println "Received inline: " inline))

Callbacks

You can provide handlers for Callbacks which are sent from inline keyboards

(callback-fn (fn [data] (println "Received callback: " inline)))
; or in a macro form
(callback data (println "Received callback: " inline))

Starting your bot

As Telegram documentation says, there are two ways of getting updates from the bot: webhook and long-polling.

Webhook

If you develop a web application, you can use api call to register one of your endpoints in Telegram:

(require '[morse.api :as api])

(api/set-webhook "abc:XXX" "http://example.com/handler")

Telegram will use this url to POST messages to it. You can also use handler to react on these messages. Here is quick example if you use compojure:

(require '[compojure.core :refer [GET POST defroutes]]
         '[compojure.route :as route])

(defhandler bot-api
  (command "help" {{id :id} :chat}
    (api/send-text token id "Help is on the way")))

(defroutes app-routes
  (POST "/handler" {body :body} (bot-api body))
  (route/not-found "Not Found"))

Long-polling

This solution works perfectly if you don't plan on having a webserver or want to test your bot from a local machine.

Start the process by simply calling start function and pass it token and your handler:

(require '[morse.polling :as p])

(def channel (p/start token handler))

Then if you want to stop created background processes, call stop on returned channel:

(p/stop channel)

Sending messages

Use morse.api to interact with Telegram chats:

(require '[morse.api :as api])

Following methods from the API are implemented at the moment. All of them may use the advanced options by providing an additional option map argument. For all functions sending files File, ByteArray and InputStream are supported as arguments.

(api/send-text token chat-id "Hello, fellows")

You can use advanced options:

(api/send-text token chat-id
               {:parse_mode "Markdown"}
               "**Hello**, fellows")

This sends a photo that will be displayed using the embedded image viewer where available.

(require '[clojure.java.io :as io])

(api/send-photo token chat-id
                (io/file (io/resource "photo.png")))

You can use advanced options:

(api/send-photo token chat-id
                {:caption "Here is a map:"}
                (io/file (io/resource "map.png")))

Sends the given mp4 file as a video to the chat which will be shown using the embedded player where available.

(api/send-video token chat-id
                (io/file (io/resource "video.mp4")))

Sends the given mp3 file as an audio message to the chat.

(api/send-audio token chat-id
                (io/file (io/resource "audio.mp3")))

Sends the given WebP image as a sticker to the chat.

(api/send-sticker token chat-id
                  (io/file (io/resource "sticker.webp")))

This method can be used for any other kind of file not supported by the other methods, or if you don't want telegram to make a special handling of your file (i.e. sending music as a voice message).

(api/send-document token chat-id
                   (io/file (io/resource "document.pdf")))

Sends an answer to an inline query.

(api/answer-inline token inline-query-id options
                   [{:type "gif"
                     :id "gif1"
                     :gif_url "http://funnygifs/gif.gif"}])

Sends an answer to an callback query sent from inline keyboards.

(api/answer-callback token
                     callback-query-id
                     text
                     show-alert)

License

Copyright ยฉ 2017 Anton Chebotaev

Distributed under the Eclipse Public License either version 1.0.

morse's People

Contributors

anastasiarods avatar dawsonfi avatar deril avatar dixel avatar heycalmdown avatar islander avatar jeiwan avatar jonathanharford avatar linuxsoares avatar marksto avatar martinklepsch avatar merrickluo avatar miikka avatar olegakbarov avatar otann avatar pheeria avatar setzer22 avatar tirkarthi avatar wmealing avatar yi-jiayu avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

morse's Issues

`polling/start` cannot signal when the updates channel is closed

I have a problem where my bot no longer receive messages over Telegram, until I restart my application. This usually happens over long-ish period (about 24 hours) and I don't have the best Internet connection.

Reading the code for morse.polling it appears that morse.polling/start has no way to signal that it is no longer polling, and so a user will never know to call start again. create-consumer returns a channel that will contain exactly one result when the loop exits (as a result of updates being closed.) If start returned the result of create-consumer instead of the running channel, that will never be closed, it would be possible for user code to restart in the event of the polling loop dying:

(go-loop [ch  nil
          res nil]
  (if res
    (recur ch (<! ch))
    (recur (polling/start token api) true)))

I'm happy to submit a PR if you don't have time or the inclination to do this yourself, @Otann.

Is it possible to pass a core.async channel to the "start" function in polling to get the updates directly into it?

.. or maybe have a channel returned directly if no "handler" function is passed.
From the perspective of an application relying on core.async and using morse, the most feasible thing to do in the "handler" function is to push the data to another channel in your application which in the current situation is a bit redundant since the "updates" channel in polling is already serving that purpose.
From the other hand I can understand that it's the matter of taste what is a better interface for the library - to provide the way to pass a callback handler (which can be more universal since not everybody use core.async in their applications) or a channel.
Anyway, I'd be happy to have this ability.

Support /command@YourBot style commands

If there are multiple bots in a group, you can disambiguate commands by postfixing the command with bot's @username, e.g. /command@YourBot. When autocompleting a command (if you have told Telegram about your bot's commands via /setcommands to BotFather), Telegram does this automatically.

It'd be great if morse.handlers/command would support this out of the box. If I understand Telegram's FAQ correctly, you won't ever get messages that were meant for other bots in the group, even if your bot is not in the privacy mode. This means morse could just split the received commands on @ and match on the first part - there's no need to know the bot's username.

Bot freezes for unknown reasons

We use this code:

(defn -main
  [& args]
  (println "Bot running!")
  (go-loop [ch (p/start token bot-api)]
    (<! (timeout 5000))
    (if (nil? (<! ch))
      (do
        (println "Bot restarted")
        (recur (p/start token bot-api)))
      (recur ch)))
  (Thread/sleep Long/MAX_VALUE))

but bot freezes sometimes for unknown reasons ๐Ÿค”

Update handler

Hi,

I want to use a feature of the api which doesn't have an explicit handler, luckily I can just:

(h/update-fn [:chosen_inline_result]
  (fn [{:keys [inline_message_id query]}]
    (println "Registering " inline_message_id "\n" query)
    (k/assoc-in db [inline_message_id] (register-question query))))

But then I noticed my function still got called on normal messages. I propose therefore a change:

(let [data (get-in update path)]

    (if-let [data (get-in update path)]

Hopefully you agree, if I have overlooked something let me know.

Sander

Don't leak dev-time dependencies

Currently when I run boot -d morse show -d I am presented with the following dependency graph:

[morse "0.2.4"]
โ”œโ”€โ”€ [cheshire "5.5.0"]
โ”‚   โ”œโ”€โ”€ [com.fasterxml.jackson.core/jackson-core "2.5.3"]
โ”‚   โ”œโ”€โ”€ [com.fasterxml.jackson.dataformat/jackson-dataformat-cbor "2.5.3"]
โ”‚   โ”œโ”€โ”€ [com.fasterxml.jackson.dataformat/jackson-dataformat-smile "2.5.3"]
โ”‚   โ””โ”€โ”€ [tigris "0.1.1"]
โ”œโ”€โ”€ [clj-http "2.1.0"]
โ”‚   โ”œโ”€โ”€ [commons-codec "1.10" :exclusions [[org.clojure/clojure]]]
โ”‚   โ”œโ”€โ”€ [commons-io "2.4" :exclusions [[org.clojure/clojure]]]
โ”‚   โ”œโ”€โ”€ [org.apache.httpcomponents/httpclient "4.5.1" :exclusions [[org.clojure/clojure]]]
โ”‚   โ”‚   โ””โ”€โ”€ [commons-logging "1.2"]
โ”‚   โ”œโ”€โ”€ [org.apache.httpcomponents/httpcore "4.4.4" :exclusions [[org.clojure/clojure]]]
โ”‚   โ”œโ”€โ”€ [org.apache.httpcomponents/httpmime "4.5.1" :exclusions [[org.clojure/clojure]]]
โ”‚   โ”œโ”€โ”€ [potemkin "0.4.3" :exclusions [[org.clojure/clojure]]]
โ”‚   โ”‚   โ”œโ”€โ”€ [clj-tuple "0.2.2"]
โ”‚   โ”‚   โ””โ”€โ”€ [riddley "0.1.12"]
โ”‚   โ””โ”€โ”€ [slingshot "0.12.2" :exclusions [[org.clojure/clojure]]]
โ”œโ”€โ”€ [org.clojure/core.async "0.2.374"]
โ”‚   โ””โ”€โ”€ [org.clojure/tools.analyzer.jvm "0.6.9"]
โ”‚       โ”œโ”€โ”€ [org.clojure/core.memoize "0.5.8"]
โ”‚       โ”‚   โ””โ”€โ”€ [org.clojure/core.cache "0.6.4"]
โ”‚       โ”‚       โ””โ”€โ”€ [org.clojure/data.priority-map "0.0.4"]
โ”‚       โ”œโ”€โ”€ [org.clojure/tools.analyzer "0.6.7"]
โ”‚       โ”œโ”€โ”€ [org.clojure/tools.reader "1.0.0-alpha1"]
โ”‚       โ””โ”€โ”€ [org.ow2.asm/asm-all "4.2"]
โ”œโ”€โ”€ [org.clojure/tools.macro "0.1.5"]
โ”‚   โ””โ”€โ”€ [org.clojure/clojure "1.4.0"]
โ””โ”€โ”€ [venantius/ultra "0.4.1"]
    โ”œโ”€โ”€ [grimradical/clj-semver "0.3.0" :exclusions [[org.clojure/clojure]]]
    โ”œโ”€โ”€ [im.chit/hara.class "2.2.15"]
    โ”‚   โ”œโ”€โ”€ [im.chit/hara.class.checks "2.2.15"]
    โ”‚   โ”œโ”€โ”€ [im.chit/hara.class.inheritance "2.2.15"]
    โ”‚   โ””โ”€โ”€ [im.chit/hara.namespace.import "2.2.15"]
    โ”œโ”€โ”€ [im.chit/hara.reflect "2.2.15"]
    โ”‚   โ”œโ”€โ”€ [im.chit/hara.common.checks "2.2.15"]
    โ”‚   โ”œโ”€โ”€ [im.chit/hara.common.string "2.2.15"]
    โ”‚   โ”œโ”€โ”€ [im.chit/hara.common "2.2.15"]
    โ”‚   โ”‚   โ”œโ”€โ”€ [im.chit/hara.common.error "2.2.15"]
    โ”‚   โ”‚   โ”œโ”€โ”€ [im.chit/hara.common.hash "2.2.15"]
    โ”‚   โ”‚   โ””โ”€โ”€ [im.chit/hara.common.primitives "2.2.15"]
    โ”‚   โ”œโ”€โ”€ [im.chit/hara.data.map "2.2.15"]
    โ”‚   โ””โ”€โ”€ [im.chit/hara.protocol.string "2.2.15"]
    โ”œโ”€โ”€ [io.aviso/pretty "0.1.24"]
    โ”œโ”€โ”€ [mvxcvi/puget "1.0.0"]
    โ”‚   โ”œโ”€โ”€ [fipp "0.6.3"]
    โ”‚   โ”‚   โ””โ”€โ”€ [org.clojure/core.rrb-vector "0.0.11"]
    โ”‚   โ””โ”€โ”€ [mvxcvi/arrangement "1.0.0"]
    โ”œโ”€โ”€ [mvxcvi/whidbey "1.3.0"]
    โ”‚   โ””โ”€โ”€ [org.clojure/data.codec "0.1.0"]
    โ”œโ”€โ”€ [org.clojars.brenton/google-diff-match-patch "0.1"]
    โ”œโ”€โ”€ [org.clojure/tools.nrepl "0.2.12"]
    โ”œโ”€โ”€ [robert/hooke "1.3.0"]
    โ””โ”€โ”€ [venantius/glow "0.1.3"]
        โ”œโ”€โ”€ [clj-antlr "0.2.2"]
        โ”‚   โ”œโ”€โ”€ [org.antlr/antlr4-runtime "4.2.2"]
        โ”‚   โ”‚   โ”œโ”€โ”€ [org.abego.treelayout/org.abego.treelayout.core "1.0.1"]
        โ”‚   โ”‚   โ””โ”€โ”€ [org.antlr/antlr4-annotations "4.2.2"]
        โ”‚   โ””โ”€โ”€ [org.antlr/antlr4 "4.2.2"]
        โ”‚       โ”œโ”€โ”€ [org.antlr/ST4 "4.0.8"]
        โ”‚       โ””โ”€โ”€ [org.antlr/antlr-runtime "3.5.2"]
        โ”œโ”€โ”€ [garden "1.1.7"]
        โ”‚   โ””โ”€โ”€ [com.yahoo.platform.yui/yuicompressor "2.4.7"]
        โ”‚       โ””โ”€โ”€ [rhino/js "1.6R7"]
        โ”œโ”€โ”€ [hiccup "1.0.5"]
        โ””โ”€โ”€ [instaparse "1.4.1"]

Half of this isn't needed during run-time and thus it would be nice to remove it from the list of transitive dependencies.

Longpolling: Does not survive random 502 from Telegram

My bot did not resume polling after encountering a 502 from Telegram by longpolling.

Exception in thread "async-dispatch-18" clojure.lang.ExceptionInfo: clj-http: status 502 {:cached nil, :request-time 6929, :repeatable? false, :protocol-version {:name "HTTP", :major 1, :minor 1}, :streaming? true, :http-client #object[org.apache.http.impl.client.InternalHttpClient 0x6e992d0a "org.apache.http.impl.client.InternalHttpClient@6e992d0a"], :chunked? false, :type :clj-http.client/unexceptional-status, :reason-phrase "Bad Gateway", :headers {"Server" "nginx/1.12.2", "Date" "Sun, 28 Oct 2018 01:16:34 GMT", "Content-Type" "application/json", "Content-Length" "57", "Connection" "close", "Access-Control-Allow-Origin" "*", "Access-Control-Expose-Headers" "Content-Length,Content-Type,Date,Server,Connection"}, :orig-content-encoding nil, :status 502, :length 57, :body "{\"ok\":false,\"error_code\":502,\"description\":\"Bad Gateway\"}", :trace-redirects []}
	at slingshot.support$stack_trace.invoke(support.clj:201)
	at clj_http.client$exceptions_response.invokeStatic(client.clj:245)
	at clj_http.client$exceptions_response.invoke(client.clj:236)
	at clj_http.client$wrap_exceptions$fn__10385.invoke(client.clj:254)
	at clj_http.client$wrap_accept$fn__10627.invoke(client.clj:737)
	at clj_http.client$wrap_accept_encoding$fn__10634.invoke(client.clj:759)
	at clj_http.client$wrap_content_type$fn__10621.invoke(client.clj:720)
	at clj_http.client$wrap_form_params$fn__10724.invoke(client.clj:961)
	at clj_http.client$wrap_nested_params$fn__10741.invoke(client.clj:995)
	at clj_http.client$wrap_flatten_nested_params$fn__10750.invoke(client.clj:1019)
	at clj_http.client$wrap_method$fn__10682.invoke(client.clj:895)
	at clj_http.cookies$wrap_cookies$fn__7853.invoke(cookies.clj:131)
	at clj_http.links$wrap_links$fn__9284.invoke(links.clj:63)
	at clj_http.client$wrap_unknown_host$fn__10758.invoke(client.clj:1048)
	at clj_http.client$request_STAR_.invokeStatic(client.clj:1176)
	at clj_http.client$request_STAR_.invoke(client.clj:1169)
	at clj_http.client$get.invokeStatic(client.clj:1182)
	at clj_http.client$get.doInvoke(client.clj:1178)
	at clojure.lang.RestFn.invoke(RestFn.java:423)
	at morse.api$get_updates.invokeStatic(api.clj:17)
	at morse.api$get_updates.invoke(api.clj:10)

Would recommend retrying a connection every 10-30 seconds as longpolling sessions may be arbitrarily resumed later

Got HTTP request timed out

after switching on 0.3.4 version for handling 502 error i've got an another frustrating bug

ั„ะตะฒ 23, 2018 10:49:53 AM morse.polling invoke
SEVERE: HTTP request timed out, stopping polling

i've just strarted pooling as
(<!! (p/start token handler))

please help=)

Missing functions for some of the API calls:

I don't see send-video, send-document or send-audio functions in the api namespace, but the telegram API has those methods.

If you're ok with that, I'll be adding those in a pull request.

Exception causes long polling process shutdown

OK, I'm a complete clojure rookie, but I think this is what's happening:
If background process performing long-polling encounters an exception (for example 502 from the api) it shuts down and stops polling updates, which is not nice.
An example exception:

user=> Exception in thread "async-dispatch-32" java.lang.Error: java.io.EOFException: SSL peer shut down incorrectly
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1148)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	at java.lang.Thread.run(Thread.java:745)
Caused by: java.io.EOFException: SSL peer shut down incorrectly
	at sun.security.ssl.InputRecord.read(InputRecord.java:505)
	at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:973)
	at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1375)
	at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1403)
	at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1387)
	at org.apache.http.conn.ssl.SSLSocketFactory.createLayeredSocket(SSLSocketFactory.java:573)
	at org.apache.http.conn.ssl.SSLSocketFactory.createLayeredSocket(SSLSocketFactory.java:447)
	at org.apache.http.impl.conn.DefaultClientConnectionOperator.updateSecureConnection(DefaultClientConnectionOperator.java:219)
	at org.apache.http.impl.conn.ManagedClientConnectionImpl.layerProtocol(ManagedClientConnectionImpl.java:421)
	at org.apache.http.impl.client.DefaultRequestDirector.establishRoute(DefaultRequestDirector.java:815)
	at org.apache.http.impl.client.DefaultRequestDirector.tryConnect(DefaultRequestDirector.java:616)
	at org.apache.http.impl.client.DefaultRequestDirector.execute(DefaultRequestDirector.java:447)
	at org.apache.http.impl.client.AbstractHttpClient.doExecute(AbstractHttpClient.java:884)
	at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:82)
	at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:107)
	at clj_http.core$request.invokeStatic(core.clj:304)
	at clj_http.core$request.invoke(core.clj:208)
	at clojure.lang.Var.invoke(Var.java:379)
	at clj_http.client$wrap_request_timing$fn__11582.invoke(client.clj:835)
	at clj_http.headers$wrap_header_map$fn__9803.invoke(headers.clj:143)
	at clj_http.client$wrap_query_params$fn__11485.invoke(client.clj:651)
	at clj_http.client$wrap_basic_auth$fn__11492.invoke(client.clj:677)
	at clj_http.client$wrap_oauth$fn__11496.invoke(client.clj:687)
	at clj_http.client$wrap_user_info$fn__11501.invoke(client.clj:700)
	at clj_http.client$wrap_url$fn__11568.invoke(client.clj:801)
	at clj_http.client$wrap_redirects$fn__11267.invoke(client.clj:267)
	at clj_http.client$wrap_decompression$fn__11292.invoke(client.clj:339)
	at clj_http.client$wrap_input_coercion$fn__11422.invoke(client.clj:518)
	at clj_http.client$wrap_additional_header_parsing$fn__11443.invoke(client.clj:552)
	at clj_http.client$wrap_output_coercion$fn__11413.invoke(client.clj:468)
	at clj_http.client$wrap_exceptions$fn__11253.invoke(client.clj:219)
	at clj_http.client$wrap_accept$fn__11457.invoke(client.clj:595)
	at clj_http.client$wrap_accept_encoding$fn__11463.invoke(client.clj:609)
	at clj_http.client$wrap_content_type$fn__11452.invoke(client.clj:585)
	at clj_http.client$wrap_form_params$fn__11546.invoke(client.clj:765)
	at clj_http.client$wrap_nested_params$fn__11563.invoke(client.clj:790)
	at clj_http.client$wrap_method$fn__11506.invoke(client.clj:707)
	at clj_http.cookies$wrap_cookies$fn__8753.invoke(cookies.clj:124)
	at clj_http.links$wrap_links$fn__10040.invoke(links.clj:51)
	at clj_http.client$wrap_unknown_host$fn__11572.invoke(client.clj:810)
	at clj_http.client$get.invokeStatic(client.clj:913)
	at clj_http.client$get.doInvoke(client.clj:909)
	at clojure.lang.RestFn.invoke(RestFn.java:423)
	at morse.api$get_updates.invokeStatic(api.clj:17)
	at morse.api$get_updates.invoke(api.clj:10)
	at morse.polling$create_producer$fn__11694$fn__11722$state_machine__6688__auto____11723$fn__11725.invoke(polling.clj:24)
	at morse.polling$create_producer$fn__11694$fn__11722$state_machine__6688__auto____11723.invoke(polling.clj:24)
	at clojure.core.async.impl.ioc_macros$run_state_machine.invokeStatic(ioc_macros.clj:1011)
	at clojure.core.async.impl.ioc_macros$run_state_machine.invoke(ioc_macros.clj:1010)
	at clojure.core.async.impl.ioc_macros$run_state_machine_wrapped.invokeStatic(ioc_macros.clj:1015)
	at clojure.core.async.impl.ioc_macros$run_state_machine_wrapped.invoke(ioc_macros.clj:1013)
	at morse.polling$create_producer$fn__11694$fn__11722.invoke(polling.clj:24)
	at clojure.lang.AFn.run(AFn.java:22)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
	... 2 more

If this is intentional, then I'd appreciate any hints on how to tackle this on application level :)

Global timeout outside of clj-http for getting updates in the polling process.

Running a telegram-bot in a long-polling mode I sometimes experienced a complete freeze of the polling process.
According to some issues I had in the past, clj-http doesn't usually handle timeouts in an expected way, probably mainly because of this issue in JDK: https://bugs.openjdk.java.net/browse/JDK-8075484.
Having some external timeout that could inform the user application can be quite useful in such scenarios, but obviously would be good to address this issue to the maintainers of clj-http as well.

Not reconnected after 502 error

We using morse.pooling. After telegram returning 502 error:

Oct 29, 2017 9:36:29 PM morse.api invoke
SEVERE: Telegram returned 502 from /getUpdates: <html>
<head><title>502 Bad Gateway</title></head>
<body bgcolor="white">
<center><h1>502 Bad Gateway</h1></center>
<hr><center>nginx/1.10.0</center>
</body>
</html>

bot stops receiving requests. But expected behavior is trying to reconnect.

Infinite loop with web hook

When i set web hook and start send commands to bot, it goes to infinite loop answering me. As soon as bot answers me for my request, it starts to handle the same request again and again. Any ideas why it happens?

Message is sent but throws an exception

I tried sending the message as per the example in the docs

(api/send-text token chat-id "Hello, fellows")

When I run the code using TELEGRAM_TOKEN="..." TELEGRAM_CHAT_ID="..." lein run, although I receive the message on my telegram, I get the exception (attached below). It seems it's trying to parse a JSON that isn't valid. When I comment the above line of code, everything is working.

{:clojure.main/message
 "Syntax error (NullPointerException) compiling at (/private/var/folders/n5/nsp1_zz15_39bkr4d8n97j3c0000gn/T/form-init7703393975619659398.clj:1:125).\nnull\n",
 :clojure.main/triage
 {:clojure.error/phase :compile-syntax-check,
  :clojure.error/line 1,
  :clojure.error/column 125,
  :clojure.error/source "form-init7703393975619659398.clj",
  :clojure.error/path
  "/private/var/folders/n5/nsp1_zz15_39bkr4d8n97j3c0000gn/T/form-init7703393975619659398.clj",
  :clojure.error/class java.lang.NullPointerException},
 :clojure.main/trace
 {:via
  [{:type clojure.lang.Compiler$CompilerException,
    :message
    "Syntax error compiling at (/private/var/folders/n5/nsp1_zz15_39bkr4d8n97j3c0000gn/T/form-init7703393975619659398.clj:1:125).",
    :data
    {:clojure.error/phase :compile-syntax-check,
     :clojure.error/line 1,
     :clojure.error/column 125,
     :clojure.error/source
     "/private/var/folders/n5/nsp1_zz15_39bkr4d8n97j3c0000gn/T/form-init7703393975619659398.clj"},
    :at [clojure.lang.Compiler load "Compiler.java" 7648]}
   {:type java.lang.NullPointerException,
    :at [clojure.core$apply invokeStatic "core.clj" 665]}],
  :trace
  [[clojure.core$apply invokeStatic "core.clj" 665]
   [clojure.core$apply invoke "core.clj" 660]
   [clj_http.client$json_decode invokeStatic "client.clj" 131]
   [clj_http.client$json_decode doInvoke "client.clj" 127]
   [clojure.lang.RestFn invoke "RestFn.java" 421]
   [clj_http.client$decode_json_body invokeStatic "client.clj" 457]
   [clj_http.client$decode_json_body invoke "client.clj" 449]
   [clj_http.client$coerce_json_body invokeStatic "client.clj" 465]
   [clj_http.client$coerce_json_body doInvoke "client.clj" 460]
   [clojure.lang.RestFn invoke "RestFn.java" 445]
   [clj_http.client$eval3960$fn__3961 invoke "client.clj" 527]
   [clojure.lang.MultiFn invoke "MultiFn.java" 234]
   [clj_http.client$output_coercion_response
    invokeStatic
    "client.clj"
    561]
   [clj_http.client$output_coercion_response invoke "client.clj" 558]
   [clj_http.client$wrap_output_coercion$fn__4004
    invoke
    "client.clj"
    572]
   [clj_http.client$wrap_exceptions$fn__3801 invoke "client.clj" 249]
   [clj_http.client$wrap_accept$fn__4057 invoke "client.clj" 726]
   [clj_http.client$wrap_accept_encoding$fn__4064
    invoke
    "client.clj"
    748]
   [clj_http.client$wrap_content_type$fn__4051 invoke "client.clj" 709]
   [clj_http.client$wrap_form_params$fn__4160 invoke "client.clj" 950]
   [clj_http.client$wrap_nested_params$fn__4181
    invoke
    "client.clj"
    984]
   [clj_http.client$wrap_flatten_nested_params$fn__4190
    invoke
    "client.clj"
    1008]
   [clj_http.client$wrap_method$fn__4118 invoke "client.clj" 884]
   [clj_http.cookies$wrap_cookies$fn__415 invoke "cookies.clj" 131]
   [clj_http.links$wrap_links$fn__1929 invoke "links.clj" 63]
   [clj_http.client$wrap_unknown_host$fn__4198
    invoke
    "client.clj"
    1037]
   [clj_http.client$request_STAR_ invokeStatic "client.clj" 1165]
   [clj_http.client$request_STAR_ invoke "client.clj" 1158]
   [clj_http.client$post invokeStatic "client.clj" 1183]
   [clj_http.client$post doInvoke "client.clj" 1179]
   [clojure.lang.RestFn invoke "RestFn.java" 423]
   [morse.api$send_text invokeStatic "api.clj" 73]
   [morse.api$send_text invoke "api.clj" 67]
   [morse.api$send_text invokeStatic "api.clj" 69]
   [morse.api$send_text invoke "api.clj" 67]
   [github_amazon_bot.core$_main invokeStatic "core.clj" 28]
   [github_amazon_bot.core$_main invoke "core.clj" 19]
   [clojure.lang.Var invoke "Var.java" 380]
   [user$eval140 invokeStatic "form-init7703393975619659398.clj" 1]
   [user$eval140 invoke "form-init7703393975619659398.clj" 1]
   [clojure.lang.Compiler eval "Compiler.java" 7177]
   [clojure.lang.Compiler eval "Compiler.java" 7167]
   [clojure.lang.Compiler load "Compiler.java" 7636]
   [clojure.lang.Compiler loadFile "Compiler.java" 7574]
   [clojure.main$load_script invokeStatic "main.clj" 475]
   [clojure.main$init_opt invokeStatic "main.clj" 477]
   [clojure.main$init_opt invoke "main.clj" 477]
   [clojure.main$initialize invokeStatic "main.clj" 508]
   [clojure.main$null_opt invokeStatic "main.clj" 542]
   [clojure.main$null_opt invoke "main.clj" 539]
   [clojure.main$main invokeStatic "main.clj" 664]
   [clojure.main$main doInvoke "main.clj" 616]
   [clojure.lang.RestFn applyTo "RestFn.java" 137]
   [clojure.lang.Var applyTo "Var.java" 705]
   [clojure.main main "main.java" 40]],
  :phase :compile-syntax-check}}

pooling started, but not working

I started pooling and I set the timeout to 300 and I start communicating with my Bot in Telegram.
but nothing happens.

(morse.handlers/defhandler bot-api
              ; Each bot has to handle /start and /help commands.
              ; This could be done in form of a function:
              (morse.handlers/command-fn "start" (fn [{{id :id :as chat} :chat}]
                                      (println "Bot joined new chat: " chat)
                                      (morse.api/send-text token id "Welcome!")))

              ; You can use short syntax for same purposes
              ; Destructuring works same way as in function above
              (morse.handlers/command "help" {{id :id :as chat} :chat}
                         (println "Help was requested in " chat)
                         (morse.api/send-text token id "Help is on the way"))

              ; Handlers will be applied until there are any of those
              ; returns non-nil result processing update.

              ; Note that sending stuff to the user returns non-nil
              ; response from Telegram API.

              ; So match-all catch-through case would look something like this:
              (morse.handlers/message message (println "Intercepted message:" message)))
...
(def channelx (morse.polling/start token bot-api {:timeout 300}))

Handle upgrading a group to supergroup

When a group where bot is present is upgraded, the following exception appears, and bot thread crashes:

Exception in thread "async-dispatch-23" clojure.lang.ExceptionInfo: clj-http: status 400 {:status 400, :headers {"Server" "nginx/1.12.2", "Date" "Fri, 12 Oct 2018 09:30:18 GMT", "Content-Type" "application/json", "Content-Length" "154", "Connection" "close", "Access-Control-Allow-Origin" "*", "Access-Control-Expose-Headers" "Content-Length,Content-Type,Date,Server,Connection"}, :body "{\"ok\":false,\"error_code\":400,\"description\":\"Bad Request: group chat was upgraded to a supergroup chat\",\"parameters\":{\"migrate_to_chat_id\":-1001158883424}}", :request-time 219, :trace-redirects ["https://api.telegram.org/bot***:*********/sendMessage"], :orig-content-encoding nil}
	at slingshot.support$stack_trace.invoke(support.clj:201)
	at clj_http.client$wrap_exceptions$fn__10012.invoke(client.clj:196)
	at clj_http.client$wrap_accept$fn__10216.invoke(client.clj:565)
	at clj_http.client$wrap_accept_encoding$fn__10222.invoke(client.clj:579)
	at clj_http.client$wrap_content_type$fn__10211.invoke(client.clj:554)
	at clj_http.client$wrap_form_params$fn__10303.invoke(client.clj:722)
	at clj_http.client$wrap_nested_params$fn__10317.invoke(client.clj:760)
	at clj_http.client$wrap_method$fn__10263.invoke(client.clj:670)
	at clj_http.cookies$wrap_cookies$fn__7643.invoke(cookies.clj:124)
	at clj_http.links$wrap_links$fn__8927.invoke(links.clj:51)
	at clj_http.client$wrap_unknown_host$fn__10326.invoke(client.clj:776)
	at clj_http.client$post.invokeStatic(client.clj:891)
	at clj_http.client$post.doInvoke(client.clj:887)
	at clojure.lang.RestFn.invoke(RestFn.java:423)
	at morse.api$send_text.invokeStatic(api.clj:35)
	at morse.api$send_text.invoke(api.clj:29)
	at morse.api$send_text.invokeStatic(api.clj:31)
	at morse.api$send_text.invoke(api.clj:29)
	at craplimiter_bot.handler$fn__10722.invokeStatic(handler.clj:23)
	at craplimiter_bot.handler$fn__10722.invoke(handler.clj:21)
	at morse.handlers$update_fn$fn__10686.invoke(handlers.clj:57)
	at morse.handlers$handling$fn__10663.invoke(handlers.clj:9)
	at clojure.core$some.invokeStatic(core.clj:2592)
	at clojure.core$some.invoke(core.clj:2583)
	at morse.handlers$handling.invokeStatic(handlers.clj:9)
	at morse.handlers$handling.doInvoke(handlers.clj:6)
	at clojure.lang.RestFn.applyTo(RestFn.java:139)
	at clojure.core$apply.invokeStatic(core.clj:648)
	at clojure.core$apply.invoke(core.clj:641)
	at morse.handlers$handlers$fn__10667.invoke(handlers.clj:15)
	at morse.polling$create_consumer$fn__10516$state_machine__5526__auto____10517$fn__10519.invoke(polling.clj:42)
	at morse.polling$create_consumer$fn__10516$state_machine__5526__auto____10517.invoke(polling.clj:42)
	at clojure.core.async.impl.ioc_macros$run_state_machine.invokeStatic(ioc_macros.clj:1011)
	at clojure.core.async.impl.ioc_macros$run_state_machine.invoke(ioc_macros.clj:1010)
	at clojure.core.async.impl.ioc_macros$run_state_machine_wrapped.invokeStatic(ioc_macros.clj:1015)
	at clojure.core.async.impl.ioc_macros$run_state_machine_wrapped.invoke(ioc_macros.clj:1013)
	at clojure.core.async.impl.ioc_macros$take_BANG_$fn__5542.invoke(ioc_macros.clj:1024)
	at clojure.core.async.impl.channels.ManyToManyChannel$fn__464.invoke(channels.clj:135)
	at clojure.lang.AFn.run(AFn.java:22)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	at java.lang.Thread.run(Thread.java:745)

Nevertheless, after the bot is restarted, it keeps working fine. But it would be good to not add some kind of trigger to restart bot in such cases :)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.