slack-ruby-bot-server 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +10 -0
- data/.rspec +3 -0
- data/.rubocop.yml +2 -0
- data/.rubocop_todo.yml +45 -0
- data/.travis.yml +9 -0
- data/CHANGELOG.md +16 -0
- data/CONTRIBUTING.md +125 -0
- data/DEBUGGING.md +25 -0
- data/Gemfile +3 -0
- data/Guardfile +8 -0
- data/LICENSE +21 -0
- data/README.md +49 -0
- data/RELEASING.md +69 -0
- data/Rakefile +16 -0
- data/app.json +9 -0
- data/config/initializers/bson/object_id.rb +12 -0
- data/config/initializers/grape/sort_extension.rb +8 -0
- data/config/initializers/slack-ruby-bot/client.rb +17 -0
- data/config/initializers/slack-ruby-bot/commands/base.rb +21 -0
- data/images/new.png +0 -0
- data/images/slackbotserver.gif +0 -0
- data/images/slackbutton.gif +0 -0
- data/lib/slack-ruby-bot-server.rb +17 -0
- data/lib/slack-ruby-bot-server/api.rb +8 -0
- data/lib/slack-ruby-bot-server/api/endpoints.rb +3 -0
- data/lib/slack-ruby-bot-server/api/endpoints/root_endpoint.rb +22 -0
- data/lib/slack-ruby-bot-server/api/endpoints/status_endpoint.rb +16 -0
- data/lib/slack-ruby-bot-server/api/endpoints/teams_endpoint.rb +70 -0
- data/lib/slack-ruby-bot-server/api/helpers.rb +4 -0
- data/lib/slack-ruby-bot-server/api/helpers/cursor_helpers.rb +37 -0
- data/lib/slack-ruby-bot-server/api/helpers/error_helpers.rb +52 -0
- data/lib/slack-ruby-bot-server/api/helpers/pagination_parameters.rb +19 -0
- data/lib/slack-ruby-bot-server/api/helpers/sort_helpers.rb +53 -0
- data/lib/slack-ruby-bot-server/api/middleware.rb +44 -0
- data/lib/slack-ruby-bot-server/api/presenters.rb +9 -0
- data/lib/slack-ruby-bot-server/api/presenters/paginated_presenter.rb +38 -0
- data/lib/slack-ruby-bot-server/api/presenters/root_presenter.rb +44 -0
- data/lib/slack-ruby-bot-server/api/presenters/status_presenter.rb +40 -0
- data/lib/slack-ruby-bot-server/api/presenters/team_presenter.rb +24 -0
- data/lib/slack-ruby-bot-server/api/presenters/teams_presenter.rb +14 -0
- data/lib/slack-ruby-bot-server/app.rb +85 -0
- data/lib/slack-ruby-bot-server/info.rb +11 -0
- data/lib/slack-ruby-bot-server/models.rb +1 -0
- data/lib/slack-ruby-bot-server/models/team.rb +66 -0
- data/lib/slack-ruby-bot-server/rspec.rb +5 -0
- data/lib/slack-ruby-bot-server/rspec/fabricators/team.rb +5 -0
- data/lib/slack-ruby-bot-server/server.rb +21 -0
- data/lib/slack-ruby-bot-server/service.rb +81 -0
- data/lib/slack-ruby-bot-server/version.rb +3 -0
- data/public/favicon.ico +0 -0
- data/public/img/slack.png +0 -0
- data/public/index.html.erb +28 -0
- data/public/robots.txt +2 -0
- data/public/scripts/jquery-1.7.1.min.js +4 -0
- data/public/scripts/register.js +50 -0
- data/public/scripts/stats.js +14 -0
- data/public/scripts/url.min.js +1 -0
- data/sample_app/.rspec +3 -0
- data/sample_app/Gemfile +14 -0
- data/sample_app/Procfile +1 -0
- data/sample_app/Rakefile +10 -0
- data/sample_app/commands.rb +2 -0
- data/sample_app/commands/help.rb +19 -0
- data/sample_app/commands/whoami.rb +6 -0
- data/sample_app/config.ru +20 -0
- data/sample_app/config/mongoid.yml +27 -0
- data/sample_app/config/newrelic.yml +217 -0
- data/sample_app/spec/commands/help_spec.rb +14 -0
- data/sample_app/spec/commands/whoami_spec.rb +14 -0
- data/sample_app/spec/spec_helper.rb +25 -0
- data/script/console +9 -0
- data/slack-ruby-bot-server.gemspec +48 -0
- data/tasks/db.rake +44 -0
- metadata +523 -0
@@ -0,0 +1,44 @@
|
|
1
|
+
module SlackRubyBotServer
|
2
|
+
module Api
|
3
|
+
module Presenters
|
4
|
+
module RootPresenter
|
5
|
+
include Roar::JSON::HAL
|
6
|
+
include Roar::Hypermedia
|
7
|
+
include Grape::Roar::Representer
|
8
|
+
|
9
|
+
link :self do |opts|
|
10
|
+
"#{base_url(opts)}/api"
|
11
|
+
end
|
12
|
+
|
13
|
+
link :status do |opts|
|
14
|
+
"#{base_url(opts)}/api/status"
|
15
|
+
end
|
16
|
+
|
17
|
+
link :teams do |opts|
|
18
|
+
{
|
19
|
+
href: "#{base_url(opts)}/api/teams/#{link_params(Helpers::PaginationParameters::ALL, :active)}",
|
20
|
+
templated: true
|
21
|
+
}
|
22
|
+
end
|
23
|
+
|
24
|
+
link :team do |opts|
|
25
|
+
{
|
26
|
+
href: "#{base_url(opts)}/api/teams/{id}",
|
27
|
+
templated: true
|
28
|
+
}
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def base_url(opts)
|
34
|
+
request = Grape::Request.new(opts[:env])
|
35
|
+
request.base_url
|
36
|
+
end
|
37
|
+
|
38
|
+
def link_params(*args)
|
39
|
+
"{?#{args.join(',')}}"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module SlackRubyBotServer
|
2
|
+
module Api
|
3
|
+
module Presenters
|
4
|
+
module StatusPresenter
|
5
|
+
include Roar::JSON::HAL
|
6
|
+
include Roar::Hypermedia
|
7
|
+
include Grape::Roar::Representer
|
8
|
+
|
9
|
+
link :self do |opts|
|
10
|
+
"#{base_url(opts)}/status"
|
11
|
+
end
|
12
|
+
|
13
|
+
property :teams_count
|
14
|
+
property :active_teams_count
|
15
|
+
property :ping
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def ping
|
20
|
+
team = Team.asc(:_id).first
|
21
|
+
return unless team
|
22
|
+
team.ping!
|
23
|
+
end
|
24
|
+
|
25
|
+
def teams_count
|
26
|
+
Team.count
|
27
|
+
end
|
28
|
+
|
29
|
+
def active_teams_count
|
30
|
+
Team.active.count
|
31
|
+
end
|
32
|
+
|
33
|
+
def base_url(opts)
|
34
|
+
request = Grape::Request.new(opts[:env])
|
35
|
+
request.base_url
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module SlackRubyBotServer
|
2
|
+
module Api
|
3
|
+
module Presenters
|
4
|
+
module TeamPresenter
|
5
|
+
include Roar::JSON::HAL
|
6
|
+
include Roar::Hypermedia
|
7
|
+
include Grape::Roar::Representer
|
8
|
+
|
9
|
+
property :id, type: String, desc: 'Team ID.'
|
10
|
+
property :team_id, type: String, desc: 'Slack team ID.'
|
11
|
+
property :name, type: String, desc: 'Team name.'
|
12
|
+
property :domain, type: String, desc: 'Team domain.'
|
13
|
+
property :active, type: Boolean, desc: 'Team is active.'
|
14
|
+
property :created_at, type: DateTime, desc: 'Date/time when the team was created.'
|
15
|
+
property :updated_at, type: DateTime, desc: 'Date/time when the team was accepted, declined or canceled.'
|
16
|
+
|
17
|
+
link :self do |opts|
|
18
|
+
request = Grape::Request.new(opts[:env])
|
19
|
+
"#{request.base_url}/api/teams/#{id}"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module SlackRubyBotServer
|
2
|
+
module Api
|
3
|
+
module Presenters
|
4
|
+
module TeamsPresenter
|
5
|
+
include Roar::JSON::HAL
|
6
|
+
include Roar::Hypermedia
|
7
|
+
include Grape::Roar::Representer
|
8
|
+
include PaginatedPresenter
|
9
|
+
|
10
|
+
collection :results, extend: TeamPresenter, as: :teams, embedded: true
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
module SlackRubyBotServer
|
2
|
+
class App
|
3
|
+
def prepare!
|
4
|
+
silence_loggers!
|
5
|
+
check_mongodb_provider!
|
6
|
+
check_database!
|
7
|
+
create_indexes!
|
8
|
+
mark_teams_active!
|
9
|
+
migrate_from_single_team!
|
10
|
+
update_team_name_and_id!
|
11
|
+
purge_inactive_teams!
|
12
|
+
configure_global_aliases!
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.instance
|
16
|
+
@instance ||= SlackRubyBotServer::App.new
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def logger
|
22
|
+
@logger ||= begin
|
23
|
+
$stdout.sync = true
|
24
|
+
Logger.new(STDOUT)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def silence_loggers!
|
29
|
+
Mongoid.logger.level = Logger::INFO
|
30
|
+
Mongo::Logger.logger.level = Logger::INFO
|
31
|
+
end
|
32
|
+
|
33
|
+
def check_mongodb_provider!
|
34
|
+
return unless ENV['RACK_ENV'] == 'production'
|
35
|
+
fail "Missing ENV['MONGO_URL'], ENV['MONGOHQ_URI'] or ENV['MONGOLAB_URI']." unless ENV['MONGO_URL'] || ENV['MONGOHQ_URI'] || ENV['MONGOLAB_URI']
|
36
|
+
end
|
37
|
+
|
38
|
+
def check_database!
|
39
|
+
rc = Mongoid.default_client.command(ping: 1)
|
40
|
+
return if rc && rc.ok?
|
41
|
+
fail rc.documents.first['error'] || 'Unexpected error.'
|
42
|
+
rescue Exception => e
|
43
|
+
warn "Error connecting to MongoDB: #{e.message}"
|
44
|
+
raise e
|
45
|
+
end
|
46
|
+
|
47
|
+
def create_indexes!
|
48
|
+
::Mongoid::Tasks::Database.create_indexes
|
49
|
+
end
|
50
|
+
|
51
|
+
def mark_teams_active!
|
52
|
+
Team.where(active: nil).update_all(active: true)
|
53
|
+
end
|
54
|
+
|
55
|
+
def update_team_name_and_id!
|
56
|
+
Team.active.where(team_id: nil).each do |team|
|
57
|
+
begin
|
58
|
+
auth = team.ping![:auth]
|
59
|
+
team.update_attributes!(team_id: auth['team_id'], name: auth['team'])
|
60
|
+
rescue StandardError => e
|
61
|
+
logger.warn "Error pinging team #{team.id}: #{e.message}."
|
62
|
+
team.set(active: false)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def migrate_from_single_team!
|
68
|
+
return unless ENV.key?('SLACK_API_TOKEN')
|
69
|
+
logger.info 'Migrating from env SLACK_API_TOKEN ...'
|
70
|
+
team = Team.find_or_create_from_env!
|
71
|
+
logger.info "Automatically migrated team: #{team}."
|
72
|
+
logger.warn "You should unset ENV['SLACK_API_TOKEN']."
|
73
|
+
end
|
74
|
+
|
75
|
+
def purge_inactive_teams!
|
76
|
+
Team.purge!
|
77
|
+
end
|
78
|
+
|
79
|
+
def configure_global_aliases!
|
80
|
+
SlackRubyBot.configure do |config|
|
81
|
+
config.aliases = ENV['SLACK_RUBY_BOT_ALIASES'].split(' ') if ENV['SLACK_RUBY_BOT_ALIASES']
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module SlackRubyBotServer
|
2
|
+
INFO = <<-EOS
|
3
|
+
BotServer #{SlackRubyBotServer::VERSION}
|
4
|
+
|
5
|
+
© 2016 Daniel Doubrovkine & Contributors, MIT License
|
6
|
+
https://twitter.com/dblockdotorg
|
7
|
+
|
8
|
+
Free Service at http://slack-ruby-bot-server.herokuapp.com
|
9
|
+
Open-Source at https://github.com/dblock/slack-ruby-bot-server
|
10
|
+
EOS
|
11
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'slack-ruby-bot-server/models/team'
|
@@ -0,0 +1,66 @@
|
|
1
|
+
class Team
|
2
|
+
include Mongoid::Document
|
3
|
+
include Mongoid::Timestamps
|
4
|
+
|
5
|
+
SORT_ORDERS = ['created_at', '-created_at', 'updated_at', '-updated_at']
|
6
|
+
|
7
|
+
field :team_id, type: String
|
8
|
+
field :name, type: String
|
9
|
+
field :domain, type: String
|
10
|
+
field :token, type: String
|
11
|
+
field :active, type: Boolean, default: true
|
12
|
+
|
13
|
+
scope :active, -> { where(active: true) }
|
14
|
+
|
15
|
+
validates_uniqueness_of :token, message: 'has already been used'
|
16
|
+
validates_presence_of :token
|
17
|
+
validates_presence_of :team_id
|
18
|
+
|
19
|
+
def deactivate!
|
20
|
+
update_attributes!(active: false)
|
21
|
+
end
|
22
|
+
|
23
|
+
def activate!(token)
|
24
|
+
update_attributes!(active: true, token: token)
|
25
|
+
end
|
26
|
+
|
27
|
+
def to_s
|
28
|
+
{
|
29
|
+
name: name,
|
30
|
+
domain: domain,
|
31
|
+
id: team_id
|
32
|
+
}.map do |k, v|
|
33
|
+
"#{k}=#{v}" if v
|
34
|
+
end.compact.join(', ')
|
35
|
+
end
|
36
|
+
|
37
|
+
def ping!
|
38
|
+
client = Slack::Web::Client.new(token: token)
|
39
|
+
auth = client.auth_test
|
40
|
+
{
|
41
|
+
auth: auth,
|
42
|
+
presence: client.users_getPresence(user: auth['user_id'])
|
43
|
+
}
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.find_or_create_from_env!
|
47
|
+
token = ENV['SLACK_API_TOKEN']
|
48
|
+
return unless token
|
49
|
+
team = Team.where(token: token).first
|
50
|
+
team ||= Team.new(token: token)
|
51
|
+
info = Slack::Web::Client.new(token: token).team_info
|
52
|
+
team.team_id = info['team']['id']
|
53
|
+
team.name = info['team']['name']
|
54
|
+
team.domain = info['team']['domain']
|
55
|
+
team.save!
|
56
|
+
team
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.purge!
|
60
|
+
# destroy teams inactive for two weeks
|
61
|
+
Team.where(active: false, :updated_at.lte => 2.weeks.ago).each do |team|
|
62
|
+
Mongoid.logger.info "Destroying #{team}, inactive since #{team.updated_at}, over two weeks ago."
|
63
|
+
team.destroy
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module SlackRubyBotServer
|
2
|
+
class Server < SlackRubyBot::Server
|
3
|
+
attr_accessor :team
|
4
|
+
|
5
|
+
def initialize(attrs = {})
|
6
|
+
@team = attrs[:team]
|
7
|
+
fail 'Missing team' unless @team
|
8
|
+
options = { token: @team.token }
|
9
|
+
super(options)
|
10
|
+
client.owner = @team
|
11
|
+
end
|
12
|
+
|
13
|
+
def restart!(wait = 1)
|
14
|
+
# when an integration is disabled, a live socket is closed, which causes the default behavior of the client to restart
|
15
|
+
# it would keep retrying without checking for account_inactive or such, we want to restart via service which will disable an inactive team
|
16
|
+
logger.info "#{team.name}: socket closed, restarting ..."
|
17
|
+
SlackRubyBotServer::Service.instance.restart! team, self, wait
|
18
|
+
client.owner = team
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
module SlackRubyBotServer
|
2
|
+
class Service
|
3
|
+
include SlackRubyBot::Loggable
|
4
|
+
|
5
|
+
def self.start!
|
6
|
+
Thread.new do
|
7
|
+
Thread.current.abort_on_exception = true
|
8
|
+
instance.start_from_database!
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.instance
|
13
|
+
@instance ||= new
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize
|
17
|
+
@lock = Mutex.new
|
18
|
+
@services = {}
|
19
|
+
end
|
20
|
+
|
21
|
+
def start!(team)
|
22
|
+
fail 'Token already known.' if @services.key?(team.token)
|
23
|
+
logger.info "Starting team #{team}."
|
24
|
+
server = SlackRubyBotServer::Server.new(team: team)
|
25
|
+
@lock.synchronize do
|
26
|
+
@services[team.token] = server
|
27
|
+
end
|
28
|
+
restart!(team, server)
|
29
|
+
rescue StandardError => e
|
30
|
+
logger.error e
|
31
|
+
end
|
32
|
+
|
33
|
+
def stop!(team)
|
34
|
+
@lock.synchronize do
|
35
|
+
fail 'Token unknown.' unless @services.key?(team.token)
|
36
|
+
logger.info "Stopping team #{team}."
|
37
|
+
@services[team.token].stop!
|
38
|
+
@services.delete(team.token)
|
39
|
+
end
|
40
|
+
rescue StandardError => e
|
41
|
+
logger.error e
|
42
|
+
end
|
43
|
+
|
44
|
+
def start_from_database!
|
45
|
+
Team.active.each do |team|
|
46
|
+
start!(team)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def restart!(team, server, wait = 1)
|
51
|
+
server.start_async
|
52
|
+
rescue StandardError => e
|
53
|
+
case e.message
|
54
|
+
when 'account_inactive', 'invalid_auth' then
|
55
|
+
logger.error "#{team.name}: #{e.message}, team will be deactivated."
|
56
|
+
deactivate!(team)
|
57
|
+
else
|
58
|
+
logger.error "#{team.name}: #{e.message}, restarting in #{wait} second(s)."
|
59
|
+
sleep(wait)
|
60
|
+
restart! team, server, [wait * 2, 60].min
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def deactivate!(team)
|
65
|
+
team.deactivate!
|
66
|
+
@lock.synchronize do
|
67
|
+
@services.delete(team.token)
|
68
|
+
end
|
69
|
+
rescue Mongoid::Errors::Validations => e
|
70
|
+
logger.error "#{team.name}: #{e.message}, error - #{e.document.class}, #{e.document.errors.to_hash}, ignored."
|
71
|
+
rescue StandardError => e
|
72
|
+
logger.error "#{team.name}: #{e.class}, #{e.message}, ignored."
|
73
|
+
end
|
74
|
+
|
75
|
+
def reset!
|
76
|
+
@services.values.to_a.each do |server|
|
77
|
+
stop!(server.team)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
data/public/favicon.ico
ADDED
Binary file
|
Binary file
|
@@ -0,0 +1,28 @@
|
|
1
|
+
<html>
|
2
|
+
<head>
|
3
|
+
<title>Slack Ruby Bot Server</title>
|
4
|
+
<script src="/scripts/jquery-1.7.1.min.js"></script>
|
5
|
+
<script src="/scripts/url.min.js"></script>
|
6
|
+
<script src="/scripts/register.js"></script>
|
7
|
+
<script src="/scripts/stats.js"></script>
|
8
|
+
</head>
|
9
|
+
<body style="text-align: center">
|
10
|
+
<p>
|
11
|
+
<img src='img/slack.png' width="120px">
|
12
|
+
</p>
|
13
|
+
<p>
|
14
|
+
<h3>An opinionated boilerplate for a Slack bot service.</h3>
|
15
|
+
</p>
|
16
|
+
<p id='messages' />
|
17
|
+
<p id='register'>
|
18
|
+
<a href="https://slack.com/oauth/authorize?scope=bot&client_id=<%= ENV['SLACK_CLIENT_ID'] %>"><img alt="Add to Slack" height="40" width="139" src="https://platform.slack-edge.com/img/add_to_slack.png" srcset="https://platform.slack-edge.com/img/add_to_slack.png 1x, https://platform.slack-edge.com/img/add_to_slack@2x.png 2x"></a>
|
19
|
+
</p>
|
20
|
+
<p id='active_teams_count'> </p>
|
21
|
+
<p>
|
22
|
+
<small>
|
23
|
+
made by <a href="https://twitter.com/dblockdotorg" target="_blank">@dblockdotorg</a>, <a href="https://github.com/dblock/slack-ruby-bot-server" target="_blank">fork me on github</a>
|
24
|
+
</small>
|
25
|
+
</p>
|
26
|
+
</body>
|
27
|
+
</html>
|
28
|
+
|