This repository has been archived by the owner on Apr 20, 2018. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Chauncey
committed
Jun 10, 2013
0 parents
commit d7de0e9
Showing
25 changed files
with
1,009 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
/target | ||
/lib | ||
/classes | ||
/checkouts | ||
/logs | ||
pom.xml | ||
*.jar | ||
*.class | ||
.lein-deps-sum | ||
.lein-failures | ||
.lein-plugins | ||
*.ipr | ||
*.iws | ||
*.iml | ||
.lein-repl-history | ||
*.log | ||
*.gz | ||
.DS_Store |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
language: clojure | ||
lein: lein2 | ||
script: lein2 test | ||
jdk: | ||
- openjdk6 | ||
- openjdk7 | ||
- oraclejdk7 | ||
|
||
notifications: | ||
email: false |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
The MIT License | ||
|
||
Copyright (c) Rally Software Development Corp. 2013 (see https://github.com/RallySoftware-cookbooks/jarvis) | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy | ||
of this software and associated documentation files (the "Software"), to deal | ||
in the Software without restriction, including without limitation the rights | ||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
copies of the Software, and to permit persons to whom the Software is | ||
furnished to do so, subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in | ||
all copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
THE SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,255 @@ | ||
# Jarvis | ||
|
||
A FlowDock robot similar to [Hubot](http://hubot.github.com/). | ||
|
||
## Why not just use Hubot? | ||
|
||
I think Jarvis can take advantage of the full power of FlowDock. Context. Think of all those build messages that get | ||
sent to FlowDock. Wouldn't it be nice if you could reply to those messages and have Jarvis do work for you. Reply to | ||
a failed build message in FlowDock with: | ||
|
||
``` | ||
~claim portfolio_item/bulk_edit_spec.rb failed again | ||
``` | ||
|
||
Notice that we didn't have to tell Jarvis which build to claim. Jarvis can infer that information from the context | ||
of message command. | ||
|
||
## How do I use Jarvis? | ||
|
||
The Jarvis jar file includes a bot namespace which exposes an init function. This will spin up a new thread pool with a specified number of threads and connect to the flows that he has been invited to. | ||
|
||
For example - | ||
|
||
```clojure | ||
(ns my-jarvis.core | ||
(:require [jarvis.bot :as jarvis])) | ||
|
||
(defn -main [] | ||
(jarvis/init)) | ||
``` | ||
|
||
## How is Jarvis different from Hubot. | ||
|
||
* Jarvis works on the JVM. | ||
* Plugins can currently be created with Groovy, Java and Clojure. It should be easy to add support for other JVM languages too. | ||
* Jarvis only works with FlowDock | ||
* Jarvis is developed in a more functional style. (full FlowDock message) -> (response) | ||
* Jarvis is designed to take full advantage of FlowDock context. | ||
* Jarvis can be used in private flows. | ||
* Jarvis can be invited to flows, and be asked to leave. | ||
|
||
## How to discover available commands | ||
Type ~help in a flow that Jarvis has been invited to. You will receive a reply along these lines, with examples of the required syntax for parameterized commands: | ||
``` | ||
assign-team: assign-team <username> <team> - Assigns the user to the GitHub team. | ||
build: Trigger build pipeline (which may cause a deployment) for specified service. | ||
claim: claim <message> - Claims a broken build. - This is broken until we get Jenkins authentication for Jarvis. | ||
create-repo: create-repo <repo> <team> - Creates a GitHub repo. | ||
health-report: Prints the health report for the specified job. | ||
hello: Simple Hello from Groovy | ||
help: Prints the description of all plugins. | ||
join: Join the flow that is specified. | ||
last-successful-build: Prints the last successful build for the specified job. | ||
list-flows: Prints all flows that the jarvis is listening for. | ||
list-github-teams: list-github-teams - Shows the list of all our Github teams. | ||
status: Determines the status of all downstream jobs from the specified job. | ||
~leave can be used to have Jarvis leave a flow. He will need to be invited back through the flowdock api to join again. | ||
~<command> can be used also in private-messages with Jarvis. | ||
~~<command> @<name> will private message the people listed the results of the executed command. | ||
``` | ||
|
||
## Example Plugin | ||
|
||
```groovy | ||
import com.rallydev.jarvis.Bot | ||
Bot.addCommand("hello", "Simple Hello", "Adam Esterline") { message -> | ||
"Hello from Groovy" | ||
} | ||
``` | ||
|
||
## What information does a FlowDock message contain? | ||
|
||
FlowDock has a pretty good [API](https://www.flowdock.com/api/). But... with any documentation, it is not perfect. | ||
I have taken the time to "document" some of the API in the [wiki](https://github.com/RallySoftware/jarvis/wiki). | ||
|
||
## Jarvis specific message information | ||
|
||
Jarvis adds information to the basic FlowDock information. This information allows plugin developers to access | ||
FlowDock "context" information. Two pieces of information have been altered: ```user, parent```. | ||
|
||
### user | ||
|
||
In the original ```user``` key, the user value was the user id of the sender of the message. Jarvis takes this an extra | ||
step and adds all the user information. | ||
|
||
```json | ||
{ "app" : null, | ||
"attachments" : [ ], | ||
"content" : { "last_activity" : 1361748229643 }, | ||
"event" : "activity.user", | ||
"flow" : "rally-software:the-fellowship", | ||
"id" : 904125, | ||
"parent" : null, | ||
"sent" : 1361748227604, | ||
"tags" : [ ], | ||
"user" : { "avatar" : "https://d2cxspbh1aoie1.cloudfront.net/avatars/3a5f36a2e4e283537ee9e037d9390dbd/", | ||
"email" : "[email protected]", | ||
"id" : 29990, | ||
"name" : "Adam Esterline", | ||
"nick" : "Adam" | ||
}, | ||
"uuid" : null | ||
} | ||
``` | ||
|
||
### parent | ||
|
||
If the current message is a comment to a message, Jarvis will add a ```parent``` key to the message map. This key | ||
will contain the original message for the comment. | ||
|
||
```json | ||
{ "app" : "chat", | ||
"attachments" : [ ], | ||
"content" : { "text" : "$example", | ||
"title" : "Everything operating normally." | ||
}, | ||
"event" : "comment", | ||
"flow" : "rally-software:the-fellowship", | ||
"id" : 904196, | ||
"parent" : { "app" : "influx", | ||
"attachments" : [ ], | ||
"content" : { "contributors" : null, | ||
"coordinates" : null, | ||
"created_at" : "Fri Feb 22 20:41:43 +0000 2013", | ||
"entities" : { "hashtags" : [ ], | ||
"urls" : [ ], | ||
"user_mentions" : [ ] | ||
}, | ||
"favorited" : false, | ||
"filter_level" : "medium", | ||
"geo" : null, | ||
"id" : 305054788356280320, | ||
"id_str" : "305054788356280320", | ||
"in_reply_to_screen_name" : null, | ||
"in_reply_to_status_id" : null, | ||
"in_reply_to_status_id_str" : null, | ||
"in_reply_to_user_id" : null, | ||
"in_reply_to_user_id_str" : null, | ||
"place" : null, | ||
"retweet_count" : 0, | ||
"retweeted" : false, | ||
"source" : "<a href=\"https://status.github.com/\" rel=\"nofollow\">OctoStatus Production</a>", | ||
"text" : "Everything operating normally.", | ||
"truncated" : false, | ||
"user" : { "contributors_enabled" : false, | ||
"created_at" : "Tue Aug 28 00:04:59 +0000 2012", | ||
"default_profile" : true, | ||
"default_profile_image" : false, | ||
"description" : null, | ||
"favourites_count" : 0, | ||
"follow_request_sent" : null, | ||
"followers_count" : 1305, | ||
"following" : null, | ||
"friends_count" : 1, | ||
"geo_enabled" : false, | ||
"id" : 785764172, | ||
"id_str" : "785764172", | ||
"is_translator" : false, | ||
"lang" : "en", | ||
"listed_count" : 43, | ||
"location" : "", | ||
"name" : "GitHub Status", | ||
"notifications" : null, | ||
"profile_background_color" : "C0DEED", | ||
"profile_background_image_url" : "http://a0.twimg.com/images/themes/theme1/bg.png", | ||
"profile_background_image_url_https" : "https://si0.twimg.com/images/themes/theme1/bg.png", | ||
"profile_background_tile" : false, | ||
"profile_image_url" : "http://a0.twimg.com/profile_images/2577880769/687474703a2f2f636c2e6c792f696d6167652f337330463237324b3254324c2f636f6e74656e74_normal.png", | ||
"profile_image_url_https" : "https://si0.twimg.com/profile_images/2577880769/687474703a2f2f636c2e6c792f696d6167652f337330463237324b3254324c2f636f6e74656e74_normal.png", | ||
"profile_link_color" : "0084B4", | ||
"profile_sidebar_border_color" : "C0DEED", | ||
"profile_sidebar_fill_color" : "DDEEF6", | ||
"profile_text_color" : "333333", | ||
"profile_use_background_image" : true, | ||
"protected" : false, | ||
"screen_name" : "githubstatus", | ||
"statuses_count" : 131, | ||
"time_zone" : "Pacific Time (US & Canada)", | ||
"url" : "http://status.github.com/", | ||
"utc_offset" : -28800, | ||
"verified" : false | ||
} | ||
}, | ||
"edited" : null, | ||
"event" : "twitter", | ||
"flow" : "rally-software:the-fellowship", | ||
"id" : 848344, | ||
"sent" : 1361565704184, | ||
"tags" : [ ":thread" ], | ||
"user" : 0 | ||
}, | ||
"sent" : 1361748434205, | ||
"tags" : [ "influx:848344" ], | ||
"user" : { "avatar" : "https://d2cxspbh1aoie1.cloudfront.net/avatars/3a5f36a2e4e283537ee9e037d9390dbd/", | ||
"email" : "[email protected]", | ||
"id" : 29990, | ||
"name" : "Adam Esterline", | ||
"nick" : "Adam" | ||
}, | ||
"uuid" : "Lq4SLeiil4NXb5ud" | ||
} | ||
``` | ||
|
||
|
||
## How can I test my plugin? | ||
|
||
Take a look at the tests currently written for the | ||
[existing](https://github.com/RallySoftware/jarvis/tree/master/src/test/clojure/jarvis/plugins) | ||
[plugins](https://github.com/RallySoftware/jarvis/blob/master/src/test/clojure/jarvis/help_test.clj). | ||
|
||
## How do I try out my plugin? | ||
|
||
The Jarvis core is written in Clojure, so you will need to install | ||
[Leiningen] (https://github.com/technomancy/leiningen#installation). Once you have install Leiningen, run the below | ||
commands to start Jarvis and try out your plugin. | ||
|
||
``` | ||
> export FLOWDOCK_TOKEN="<insert flowdock user token here>" | ||
> lein run | ||
``` | ||
Your FlowDock token is located on the account page. https://www.flowdock.com/account/tokens | ||
|
||
## TODO | ||
|
||
* Log some of the things | ||
* ~~Plugin discovery and registration~~ - Currently everything is logged to STDOUT. | ||
* ~~Incoming messages (at least in debug mode)~~ ```(listen-and-reply true)``` | ||
* Prefetch users so that Jarvis warms up faster | ||
* ~~Treat the stream of messages like a infinite sequence. I think this will make it easier to play with messages in the repl.~~ | ||
* Add support for JavaScript/CoffeeScript plugins | ||
* ~~Add a better way to start Jarvis. Maybe just a script at the root of the project.~~ | ||
* Add support for different plugin directories that are outside of the project. | ||
|
||
## Why the name Jarvis? | ||
|
||
See [http://ironman.wikia.com/wiki/J.A.R.V.I.S.](http://ironman.wikia.com/wiki/J.A.R.V.I.S.) | ||
|
||
## Dependencies | ||
```clojure | ||
[org.clojure/clojure "1.5.0"] | ||
[com.rallydev/clj-flowdock "1.1.0"] | ||
[org.codehaus.groovy/groovy-all "2.1.0"] | ||
[org.clojure/java.classpath "0.2.0"] | ||
[org.clojure/tools.logging "0.2.6"] | ||
[clj-http "0.7.2"] | ||
[fs "1.3.2"] | ||
[ch.qos.logback/logback-classic "1.0.9"] | ||
[compojure "1.1.3"] | ||
[netty-ring-adapter "0.2.4"] | ||
``` | ||
|
||
# License | ||
Copyright (c) Rally Software Development Corp. 2013 | ||
Distributed under the MIT License. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
(use '[clojure.java.shell :only [sh]]) | ||
(require '(leiningen deploy)) | ||
|
||
(defproject com.rallydev/jarvis-core "1.0.0" | ||
:description "A robot for FlowDock" | ||
:url "http://github.com/RallySoftware/jarvis-core" | ||
:license {:name "The MIT License (MIT)" | ||
:url "http://opensource.org/licenses/MIT"} | ||
:dependencies [[org.clojure/clojure "1.5.0"] | ||
[com.rallydev/clj-flowdock "1.1.0"] | ||
[org.codehaus.groovy/groovy-all "2.1.0"] | ||
[org.clojure/java.classpath "0.2.0"] | ||
[org.clojure/tools.logging "0.2.6"] | ||
[clj-http "0.7.2"] | ||
[fs "1.3.2"] | ||
[ch.qos.logback/logback-classic "1.0.9"]] | ||
:source-paths ["src/main/clojure" "src/plugins"] | ||
:java-source-paths ["src/main/java"] | ||
:test-paths ["src/test/clojure"] | ||
:resource-paths ["src/main/resources"] | ||
:plugins [[lein-clojars "0.9.1"]] | ||
:jvm-opts ["-Xmx1G" "-DLOG_DIR=./logs"]) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
(ns jarvis.bot | ||
(:require [jarvis.command :as command] | ||
[jarvis.plugins :as plugins] | ||
[jarvis.util :as util] | ||
[clj-flowdock.api.flow :as flow] | ||
[clj-flowdock.streaming :as streaming] | ||
[clojure.tools.logging :as log]) | ||
(:import [java.util.concurrent Executors ExecutorService]) | ||
(:gen-class )) | ||
|
||
(declare init-flow-thread) | ||
|
||
(def threadpool (atom (Executors/newFixedThreadPool (util/config-property "THREAD_POOL_SIZE" 100)))) | ||
|
||
(defmacro listen [[flow message-sym flow-con-sym] & body] | ||
`(with-open [~flow-con-sym (streaming/open (flow/flow->flow-id ~flow))] | ||
(loop [] | ||
(when-let [~message-sym (.read ~flow-con-sym)] | ||
~@body) | ||
(recur)))) | ||
|
||
(defn invoke-plugin [raw-message plugins] | ||
(when-let [plugin (command/command->plugin raw-message plugins)] | ||
(let [message (util/enhance-message raw-message)] | ||
(try | ||
(cond | ||
(command/tell-command? message) (command/tell message plugin) | ||
:else (command/reply message plugin)) | ||
(catch Exception e | ||
(log/error e (plugins/command-name plugin) " threw an exception")))))) | ||
|
||
(defn flow-stream [flow plugins] | ||
(listen [flow msg flow-connection] | ||
(cond | ||
(command/leave-command? msg) (util/close-flow-connection flow-connection) | ||
:else (invoke-plugin msg plugins)))) | ||
|
||
(defn user-stream [plugins] | ||
(listen ["" msg flow-connection] | ||
(cond | ||
(command/join-command? msg) (init-flow-thread (get msg "content") plugins) | ||
(command/private-message? msg) (command/private-message msg (command/command->plugin msg plugins))))) | ||
|
||
(defn init-flow-thread [flow plugins] | ||
(.submit @threadpool #(flow-stream flow plugins))) | ||
|
||
(defn init-user-thread [plugins] | ||
(.submit @threadpool #(user-stream plugins))) | ||
|
||
(defn init-threads [plugins] | ||
(doseq [flow (flow/list)] | ||
(init-flow-thread flow plugins)) | ||
(init-user-thread plugins)) | ||
|
||
(defn init [] | ||
(log/info "Starting Jarvis...") | ||
(when-let [plugins (plugins/load-plugins)] | ||
(log/info "Starting to read from flowdock streams") | ||
(init-threads plugins))) |
Oops, something went wrong.