emiln / slacker Goto Github PK
View Code? Open in Web Editor NEWAn enthusiastically asynchronous Slack bot library.
An enthusiastically asynchronous Slack bot library.
The first attempt failed in several crucial ways:
Slacker is already taken on Clojars. Sigh.
It would be nice with a centralized logging system for Slacker. It seems obvious that all emitted events should be logged, and that any exceptions happening in handlers should be logged. It would be nice if this was handled by emit!
, but was fully configurable by the bot author.
While Slack API is working normally, I can use slacker.client/connect-bot
function and :bot-disconnected
event to connect/re-connect to the slack API.
The problem is, when Slack API is down, slacker.client/connect-bot
only will put the message to the logger and won't emit any event.
When slacker.client/connect-bot
failed, I want to retry after put some wait time. Is there any way to detect connection fail?
Attempting to connect a bot just threw the following NullPointerException
with Slacker 1.3.1:
boot.user=> (use 'slacker.client)
2015-07-08 09:05:45.355:INFO::nREPL-worker-0: Logging initialized @12086ms
nil
boot.user=> (emit! :slacker.client/connect-bot "...")
nil
boot.user=> Jul 08, 2015 9:05:47 AM clojure.tools.logging$eval650$fn__654 invoke
SEVERE: Error in ns=[slacker.client], handler=[slacker.client$eval8995$fn__8996@79b0cd8f]:
java.lang.NullPointerException:
...
clojure.data.json/read-str json.clj: 278
...
slacker.client/eval8995/fn client.clj: 72
...
clojure.core/apply core.clj: 624
slacker.client/handle/fn/state-machine--auto--/fn/inst-8925/state-machine--auto--/fn client.clj: 48
slacker.client/handle/fn/state-machine--auto--/fn/inst-8925/state-machine--auto-- client.clj: 44
clojure.core.async.impl.ioc-macros/run-state-machine ioc_macros.clj: 940
clojure.core.async.impl.ioc-macros/run-state-machine-wrapped ioc_macros.clj: 944
slacker.client/handle/fn/state-machine--auto--/fn/inst-8925 client.clj: 44
...
This is very unhelpful as the user clearly hasn't supplied a null pointer.
I think the README should contain a simple (already has) and intermediate (lacking) example of using emit!
/handle
to actually achieve something. The examples should implement concepts that are relevant to bot authors.
Ideas:
I created a test branch of my slacker sample repository.
It tries to print passed args when some events happened (hello, message...).
I expect some arguments will be passed but it seems always passed no argument.
Here is the log when I run a bot with lein run
command and tried some actions in my personal slack team's channel which have two members, bot and me.
hello args: nil
message args: nil
pin-added args: nil
message args: nil
pin-removed args: nil
There's a cool new kid on the testing block and their name is matcha. It seems to provide excellent improvements on feedback so I think we should make the (simple) switch.
The following open questions should be answered:
handle
and emit!
calls (as described in the comment below)?The following good-to-go tasks should just be implemented:
slacker.client
.slacker.converters
slacker.lookups
It would be a nice smoke test to connect to a network and ensure the the hello event is actually received.
It would be nice to ask queries like "what's the full name of the user with this ID?".
I think handle
requires a rethink. The following questions seem to warrant some deliberation:
handle
concerned with docstrings?I genuinely don't know. It shouldn't be.
handle
return anything?No.
That doesn't seem to make any sense. Returning a boolean regarding its success implies that failure is allowed. It should rather die horribly with whatever exception occurred if anything goes wrong, as you'd always call a failure to register a handler exceptional. If all goes well, you should receive nothing as return value.
A possible return value could be a channel onto which all emissions under the topic are put, but I would much prefer a separate function like handle-with-feedback
for this. It is not really a part of the concept of registering handlers.
Just register a handler and return nil
.
handle
be divided into constituent parts? It seems to handle many things: subscribing handlers, spawning go loops, logging, error handling, spawning a go block with try/catch, computations, etc.Probably.
Let's look into this during the refactoring taking place in this issue.
handle
return nil
.handle-with-feedback
(or some other name) which doesn't take a handle-fn and instead returns a channel onto which any event under the topic is pushed.Closed
This isn't really driven by any requirement.
handle
can be simplified (in terms of implementation and internals). It is currently quite messy.Currently it's far too easy to crash the entire handle
flow. If you have multiple handler for :topic
and one of them fails, it can prevent the other handlers from running. This is particularly easy to achieve through simple arity mismatch.
With the overhaul done in v1.3.0 it makes no sense for emit!
to return anything. There is emit-with-feedback!
for that.
Hi, I'm interested in writing slack bot in Clojure so found this project.
I'm trying to wrote a simple ping-pong example project.
https://github.com/supermomonga/sample-slacker-pingpong
But when I run lein run
, the application seems immediately shutdown after (emit! :slacker.client/connect-bot api-token)
.
I couldn't figure out how to make the application keep alive after connected to slack, so could you advice me to make this example works?
Slacker currently stops working if the websocket connection dies and makes no attempt to reconnect. There are two critical aspects to this issue:
Does the underlying websocket library support this?
Yes, gniazdo supports :on-error
, :on-close
, etc.
What is that, though? It seems prudent to reconnect as quickly as possible if the connection comes back, but beyond that Slacker should probably just report connection issues in the typical emit!
fashion.
The current way of testing has several drawbacks:
Currently Slacker seems to fail silently if it can't connect to the Slack network over websocket. This failure should be reported. I will go so far as to consider this a bug as I didn't intend to hide the error.
I don't really see them point of having both functions publicly exposed with precisely the same returned mutable socket connection. They might as well be one call under the emit!
/handle
flow. You call
(emit! :slacker.client/connect "my-token")
and the client attempts to connect, emitting either
(emit! :slacker.client/connection-succeeded websocket-object)
;; or
(emit! :slacker.client/connection-failed error-message)
The application programmer can handle these events or not at their leisure. They can save the websocket-object
if they need it, but they probably won't.
Clojure 1.7 has been released and the world at large is caught up in enormous celebrations. Slacker should join in, recover, and then eventually:
1.6
to 1.7
.As a bot author it would be great if I could enable a simple webservice so I could query my running bots about their health status.
Expose REST endpoints for:
This will allow the bots who depend on it to just include it as a dependency.
Perhaps the hardcoded ID to the Dongers Inc #srsbsns channel isn't the most meaningful default.
https://github.com/emiln/slacker/blob/master/src/slacker/converters.clj#L31
I would probably prefer that there wasn't a default and having to specify the channel, to avoid unintended behavior.
This would of course mean the library should support some easy way to look up the channel ID from name, or from the message it received.
A few suggestions:
:connection-failed
:connection-succeeded
:message-sent
:connection-failure
:connection-success
:message-transmission
Slack has a bunch of HTTP RPC-style methods, and it seems likely that users of Slacker will want to make use of them. As the token is already known to Slacker, it should be simple to provide a handy wrapper for calling the methods.
Some usage ideas:
1. Return a plain Clojure map
This would be a blocking call and returns everything from the server.
(slacker.method/emoji-list)
{:ok true,
:emoji
{:bowtie "https://my.slack.com/emoji/bowtie/46ec6f2bb0.png",
:squirrel "https://my.slack.com/emoji/squirrel/f35f40c0e0.png",
:shipit "alias:squirrel"}}
2. Return a promise-chan (non-blocking)
This would be a non-blocking call, and everything from the server would be put on the promise-chan when available. As a "rejected" promise-chan only contains nil
, there's no way to communicate error messages, which is annoying.
(if-let [emojis @(slacker.method/emoji-list)]
emojis
:scary-error)
3. Register success and error handlers (non-blocking)
This would make use of the :ok
value in the map to determine whether to call the success or error handler. It would pass on the content to the success-fn and the error to the error-fn. In both cases this seems to be just the map with the :ok
key removed.
(slacker.method/emoji-list success-fn error-fn)
At times it would be great to be able to await a response to some emitted event.
(handle :add +)
(println (<!! (emit! :add 1 2 3)))
The idea is that any handler for :do-some-io
can return a non-nil value, and this will be put onto the channel awaited by the function that called emit! :do-some-io
. This raises a few questions, however, and answering these will be the first task of this story:
Yes
By the caller of emit!
; warrants function split
This is actually a fairly difficult question to answer. It seems unlikely that any one handler will be able to ascertain that no future puts from other handlers could occur to the channel. This leaves the question of whether emit!
should be split into two functions: one returning an open channel that handlers may communicate over, and one returning nil, opening no channels. I think this might be preferable as it does not risk leaving open channels everywhere, and does not require the caller of emit!
to diligently close all open, unused feedback channels.
emit!
and handle
at this point provide a genuinely simpler and easier interface to the user, or are they really just reinventing core.async poorly?They are warranted
If you wanted to do this without handle
and emit!
, you'd declare the following once:
(def publisher (chan))
(def publication (pub publisher first))
And the following for every pair of handle
and emit!
calls:
(let [handler (chan)]
(sub publication :add handler)
(go (let [[_ return-chan & args] (<! handler)]
(>! return-chan (apply + args)))))
(let [return-chan (chan)]
(go (>! publisher [:add return-chan 1 2 3])
(println (<!! return-chan))))
While this is certainly not a big task, I do think handle
and emit!
provide an easy as well as simple way to deal with this very topic-centric flow.
nil
? It is not legal to put on a channel.Nothing is put
It is not safe to assume you can close the channel as more puts from other handlers may happen in the future. It is not legal to put nil
on the channel either, so the sensible and most simple solution is most likely to just don't put anything on at all in this case.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.