slack-ruby-bot-server 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
+
|