mos-eisley 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +71 -0
- data/bin/mos-eisley +17 -0
- data/lib/http-client/client.rb +57 -0
- data/lib/http-client/webapi.rb +44 -0
- data/lib/http-server/helper.rb +36 -0
- data/lib/http-server/server.rb +118 -0
- data/lib/mos-eisley/config.rb +53 -0
- data/lib/mos-eisley/handler.rb +87 -0
- data/lib/mos-eisley/logger.rb +24 -0
- data/lib/mos-eisley/version.rb +5 -0
- data/lib/mos-eisley.rb +7 -0
- data/lib/s3po/action.rb +53 -0
- data/lib/s3po/generic.rb +17 -0
- data/lib/s3po/message.rb +111 -0
- data/lib/s3po/s3po.rb +68 -0
- data/mos-eisley.gemspec +24 -0
- metadata +117 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 04aa61103f6ecc6125a9c6ee418ba29f83e21c60
|
4
|
+
data.tar.gz: 0afb292fddbfa032b38f59a4c3395bd3d19e26b3
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 02396f7926f9c24e5c4985a75f377dc474cb496bc30dcb527cf84e3701f26f810e6a5a2d2fcec8b0bde4089f65404c0c0fea4cfd8a655154095aa5c70c0b2162
|
7
|
+
data.tar.gz: 99601b8d3ac02b31ec45d9673c3c85fdfe7733aaf579af4d9f696be0b913da1c526224610359ee86c57e097497c9cc4f41647db0b9176946f5f0f1b2e5e71877
|
data/README.md
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
# mos-eisley
|
2
|
+
|
3
|
+
```markdown
|
4
|
+
[![Gem Version](https://badge.fury.io/rb/mos-eisley.svg)](http://badge.fury.io/rb/mos-eisley) [![Code Climate](https://codeclimate.com/github/kenjij/mos-eisley/badges/gpa.svg)](https://codeclimate.com/github/kenjij/mos-eisley) [![security](https://hakiri.io/github/kenjij/mos-eisley/master.svg)](https://hakiri.io/github/kenjij/mos-eisley/master)
|
5
|
+
```
|
6
|
+
|
7
|
+
Ruby server for running a Slackbot.
|
8
|
+
|
9
|
+
## Requirements
|
10
|
+
|
11
|
+
- [Ruby](https://www.ruby-lang.org/) >= 2.1
|
12
|
+
|
13
|
+
Powered by [Sinatra](http://www.sinatrarb.com/).
|
14
|
+
|
15
|
+
## Getting Started
|
16
|
+
|
17
|
+
### Install
|
18
|
+
|
19
|
+
```
|
20
|
+
$ gem install mos-eisley
|
21
|
+
```
|
22
|
+
|
23
|
+
### Configure
|
24
|
+
|
25
|
+
Create a configuration file following the example below.
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
# Configure application logging
|
29
|
+
MosEisley.logger = Logger.new(STDOUT)
|
30
|
+
MosEisley.logger.level = Logger::DEBUG
|
31
|
+
|
32
|
+
MosEisley::Config.setup do |c|
|
33
|
+
# User custom data
|
34
|
+
c.user = {my_data1: 'Something', my_data2: 'Somethingelse'}
|
35
|
+
|
36
|
+
# HTTP server (Sinatra) settings
|
37
|
+
c.dump_errors = true
|
38
|
+
c.logging = true
|
39
|
+
|
40
|
+
# Your handlers
|
41
|
+
c.handler_paths = [
|
42
|
+
'handlers'
|
43
|
+
]
|
44
|
+
|
45
|
+
# Slack info
|
46
|
+
c.verification_tokens = [
|
47
|
+
'vErIf1c4t0k3n5'
|
48
|
+
]
|
49
|
+
c.bot_access_token = 'xoxb-1234567890-b0t4cCe5sToK3N'
|
50
|
+
end
|
51
|
+
```
|
52
|
+
|
53
|
+
### Use
|
54
|
+
|
55
|
+
To see help:
|
56
|
+
|
57
|
+
```
|
58
|
+
$ mos-eisley -h
|
59
|
+
Usage: mos-eisley [options] {start|stop}
|
60
|
+
-c, --config=<s> Load config from file
|
61
|
+
-d, --daemonize Run in the background
|
62
|
+
-l, --log=<s> Log output to file
|
63
|
+
-P, --pid=<s> Store PID to file
|
64
|
+
-p, --port=<i> Use port (default: 4567)
|
65
|
+
```
|
66
|
+
|
67
|
+
The minimum to start a server:
|
68
|
+
|
69
|
+
```
|
70
|
+
$ mos-eisley -c config.rb start
|
71
|
+
```
|
data/bin/mos-eisley
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'kajiki'
|
3
|
+
require 'mos-eisley'
|
4
|
+
|
5
|
+
|
6
|
+
opts = Kajiki.preset_options(:server, {config: true})
|
7
|
+
|
8
|
+
Kajiki.run(opts) do |cmd|
|
9
|
+
case cmd
|
10
|
+
when 'start'
|
11
|
+
MosEisley::Config.load_config(opts[:config]) if opts[:config]
|
12
|
+
MosEisley::Handler.autoload
|
13
|
+
require 'http-server/server'
|
14
|
+
MosEisley.logger.warn('Mos Eisley server starting...')
|
15
|
+
MosEisley::Server.run!({Host: opts[:address], Port: opts[:port]})
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'eventmachine'
|
2
|
+
require 'em-http'
|
3
|
+
|
4
|
+
|
5
|
+
module MosEisley
|
6
|
+
|
7
|
+
class HTTPClient
|
8
|
+
|
9
|
+
def self.post_form(url:, params:, &block)
|
10
|
+
if EM.reactor_running?
|
11
|
+
MosEisley.logger.debug('POSTing form')
|
12
|
+
MosEisley::HTTPClient.request(url: url, body: params, &block)
|
13
|
+
else
|
14
|
+
MosEisley.logger.debug('Starting reactor...')
|
15
|
+
EM.run {
|
16
|
+
MosEisley.logger.debug('POSTing form')
|
17
|
+
MosEisley::HTTPClient.request(url: url, body: params, stop: true, &block)
|
18
|
+
}
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.post_json(url:, params: nil, body: nil, &block)
|
23
|
+
head = {'Content-Type' => 'application/json'}
|
24
|
+
body = S3PO.json_with_object(params) if params
|
25
|
+
if EM.reactor_running?
|
26
|
+
MosEisley.logger.debug('POSTing JSON')
|
27
|
+
MosEisley::HTTPClient.request(url: url, body: body, head: head, &block)
|
28
|
+
else
|
29
|
+
MosEisley.logger.debug('Starting reactor...')
|
30
|
+
EM.run {
|
31
|
+
MosEisley.logger.debug('POSTing JSON')
|
32
|
+
MosEisley::HTTPClient.request(url: url, body: body, head: head, stop: true, &block)
|
33
|
+
}
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.request(url:, head: nil, body:, stop: false, &block)
|
38
|
+
http = EM::HttpRequest.new(url).post(body: body)
|
39
|
+
http.errback {
|
40
|
+
MosEisley.logger.error('HTTP error')
|
41
|
+
if stop
|
42
|
+
EM.stop
|
43
|
+
MosEisley.logger.debug('Stopped reactor.')
|
44
|
+
end
|
45
|
+
}
|
46
|
+
http.callback {
|
47
|
+
block.call(http) if block_given?
|
48
|
+
if stop
|
49
|
+
EM.stop
|
50
|
+
MosEisley.logger.debug('Stopped reactor.')
|
51
|
+
end
|
52
|
+
}
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module MosEisley
|
2
|
+
|
3
|
+
class WebAPI
|
4
|
+
|
5
|
+
BaseURL = 'https://slack.com/api/'
|
6
|
+
|
7
|
+
def self.auth_test
|
8
|
+
MosEisley.logger.debug('auth_test')
|
9
|
+
m = 'auth.test'
|
10
|
+
url = BaseURL + m
|
11
|
+
params = {token: MosEisley.config.bot_access_token}
|
12
|
+
MosEisley.logger.debug("#{url}\n#{params}")
|
13
|
+
HTTPClient.post_form(url: url, params: params) do |h|
|
14
|
+
MosEisley.config.meta.merge!(S3PO.parse_json(h.response))
|
15
|
+
MosEisley.logger.debug("meta data updated:\n#{MosEisley.config.meta}")
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.post_message(msg)
|
20
|
+
m = 'chat.postMessage'
|
21
|
+
url = BaseURL + m
|
22
|
+
msg[:token] = MosEisley.config.bot_access_token
|
23
|
+
HTTPClient.post_form(url: url, params: msg) do |h|
|
24
|
+
MosEisley.logger.debug("chat.postMessage POSTed: #{h.response}")
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.post_ephemeral(msg)
|
29
|
+
m = 'chat.postEphemeral'
|
30
|
+
url = BaseURL + m
|
31
|
+
msg[:token] = MosEisley.config.bot_access_token
|
32
|
+
HTTPClient.post_form(url: url, params: msg) do |h|
|
33
|
+
MosEisley.logger.debug("chat.postEphemeral POSTed: #{h.response}")
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# def self.me_message()
|
38
|
+
# m = 'chat.meMessage'
|
39
|
+
# url = BaseURL + m
|
40
|
+
# end
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 's3po/s3po'
|
2
|
+
|
3
|
+
module MosEisley
|
4
|
+
|
5
|
+
module Helper
|
6
|
+
|
7
|
+
def logger
|
8
|
+
MosEisley.logger
|
9
|
+
end
|
10
|
+
|
11
|
+
def parse_command(params)
|
12
|
+
cmd = {}
|
13
|
+
params.each { |k, v| cmd[k.to_sym] = v }
|
14
|
+
return cmd
|
15
|
+
end
|
16
|
+
|
17
|
+
# Parse JSON to Hash with key symbolization by default
|
18
|
+
def parse_json(json)
|
19
|
+
MosEisley::S3PO.parse_json(json)
|
20
|
+
end
|
21
|
+
|
22
|
+
def valid_token?(token)
|
23
|
+
Config.shared.verification_tokens.include?(token)
|
24
|
+
end
|
25
|
+
|
26
|
+
# Convert object into JSON, optionally pretty-format
|
27
|
+
# @param obj [Object] any Ruby object
|
28
|
+
# @param opts [Hash] any JSON options
|
29
|
+
# @return [String] JSON string
|
30
|
+
def json_with_object(obj, pretty: true, opts: nil)
|
31
|
+
MosEisley::S3PO.json_with_object(obj, pretty: pretty, opts: opts)
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
require 'thin'
|
2
|
+
require 'sinatra/base'
|
3
|
+
require 'http-server/helper'
|
4
|
+
|
5
|
+
module MosEisley
|
6
|
+
|
7
|
+
# The Sinatra server
|
8
|
+
class Server < Sinatra::Base
|
9
|
+
|
10
|
+
helpers Helper
|
11
|
+
|
12
|
+
configure do
|
13
|
+
set :environment, :production
|
14
|
+
c = Config.shared
|
15
|
+
# set :public_folder, c.public_folder if c.public_folder
|
16
|
+
# set :dump_errors, c.dump_errors
|
17
|
+
# set :logging, c.logging
|
18
|
+
enable :logging
|
19
|
+
MosEisley::WebAPI.auth_test
|
20
|
+
end
|
21
|
+
|
22
|
+
before do
|
23
|
+
end
|
24
|
+
|
25
|
+
# Interactive message buttons
|
26
|
+
# Receive POST form with payload containing JSON string; use "token" to verify
|
27
|
+
post '/action' do
|
28
|
+
logger.info('Incoming request received.')
|
29
|
+
logger.debug("Body size: #{request.content_length} bytes")
|
30
|
+
event = parse_json(params[:payload])
|
31
|
+
halt 400 if event.nil?
|
32
|
+
logger.debug("#{event}")
|
33
|
+
unless valid_token?(event[:token])
|
34
|
+
logger.debug("Invalid Slack Events token: #{event[:token]}")
|
35
|
+
halt 401
|
36
|
+
end
|
37
|
+
res = Handler.run(:action, S3PO.create_event(event, :action))
|
38
|
+
if res
|
39
|
+
json_with_object(res)
|
40
|
+
else
|
41
|
+
200
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Slash commands
|
46
|
+
# Receive POST form; use "token" to verify
|
47
|
+
# Respond within 3 sec directly; raw text or formatted
|
48
|
+
# OR, use response_url
|
49
|
+
post '/command' do
|
50
|
+
cmd = parse_command(params)
|
51
|
+
unless valid_token?(cmd[:token])
|
52
|
+
logger.debug("Invalid Slack Events token: #{cmd[:token]}")
|
53
|
+
halt 401
|
54
|
+
end
|
55
|
+
res = Handler.run(:command, cmd)
|
56
|
+
if res
|
57
|
+
json_with_object(res)
|
58
|
+
else
|
59
|
+
200
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# Event API
|
64
|
+
# Receive POST JSON; use "token" to verify
|
65
|
+
post '/event' do
|
66
|
+
logger.info('Incoming request received.')
|
67
|
+
logger.debug("Body size: #{request.content_length} bytes")
|
68
|
+
request.body.rewind
|
69
|
+
event = parse_json(request.body.read)
|
70
|
+
halt 400 if event.nil?
|
71
|
+
logger.debug("#{event}")
|
72
|
+
|
73
|
+
unless valid_token?(event[:token])
|
74
|
+
logger.debug("Invalid Slack Events token: #{event[:token]}")
|
75
|
+
halt 401
|
76
|
+
end
|
77
|
+
resp = {}
|
78
|
+
case event[:type]
|
79
|
+
when 'url_verification'
|
80
|
+
resp[:challenge] = event[:challenge]
|
81
|
+
when 'event_callback'
|
82
|
+
Handler.run(:event, S3PO.create_event(event[:event]))
|
83
|
+
resp[:text] = 'OK'
|
84
|
+
else
|
85
|
+
resp[:text] = "Unknown event type: #{event[:type]}"
|
86
|
+
end
|
87
|
+
logger.debug("#{resp}")
|
88
|
+
logger.debug("#{ME.config.meta}")
|
89
|
+
json_with_object(resp)
|
90
|
+
end
|
91
|
+
|
92
|
+
not_found do
|
93
|
+
logger.info('Invalid request.')
|
94
|
+
logger.debug("Request method and path: #{request.request_method} #{request.path}")
|
95
|
+
json_with_object({message: 'Huh, nothing here.'})
|
96
|
+
end
|
97
|
+
|
98
|
+
error 400 do
|
99
|
+
json_with_object({message: 'Um, I did not get that.'})
|
100
|
+
end
|
101
|
+
|
102
|
+
error 401 do
|
103
|
+
json_with_object({message: 'Oops, need a valid token.'})
|
104
|
+
end
|
105
|
+
|
106
|
+
error do
|
107
|
+
status 500
|
108
|
+
err = env['sinatra.error']
|
109
|
+
logger.error "#{err.class.name} - #{err}"
|
110
|
+
json_with_object({message: 'Yikes, internal error.'})
|
111
|
+
end
|
112
|
+
|
113
|
+
after do
|
114
|
+
content_type 'application/json'
|
115
|
+
end
|
116
|
+
|
117
|
+
end
|
118
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module MosEisley
|
2
|
+
|
3
|
+
def self.config
|
4
|
+
Config.shared
|
5
|
+
end
|
6
|
+
|
7
|
+
class Config
|
8
|
+
|
9
|
+
# Load Ruby config file
|
10
|
+
# @param path [String] config file
|
11
|
+
def self.load_config(path)
|
12
|
+
MosEisley.logger.debug("Loading config file: #{path}")
|
13
|
+
require File.expand_path(path)
|
14
|
+
MosEisley.logger.info('Config.load_config done.')
|
15
|
+
end
|
16
|
+
|
17
|
+
# Returns the shared instance
|
18
|
+
# @return [MosEisley::Config]
|
19
|
+
def self.shared
|
20
|
+
@shared_config ||= Config.new
|
21
|
+
end
|
22
|
+
|
23
|
+
# Call this from your config file
|
24
|
+
def self.setup
|
25
|
+
yield Config.shared
|
26
|
+
MosEisley.logger.debug('Config.setup block executed.')
|
27
|
+
end
|
28
|
+
|
29
|
+
attr_accessor :user
|
30
|
+
attr_accessor :handler_paths
|
31
|
+
attr_accessor :public_folder
|
32
|
+
attr_accessor :dump_errors
|
33
|
+
attr_accessor :logging
|
34
|
+
|
35
|
+
attr_reader :meta
|
36
|
+
|
37
|
+
attr_accessor :verification_tokens
|
38
|
+
attr_accessor :bot_access_token
|
39
|
+
|
40
|
+
def initialize
|
41
|
+
@handler_paths = []
|
42
|
+
@dump_errors = false
|
43
|
+
@logging = false
|
44
|
+
|
45
|
+
@meta = {}
|
46
|
+
|
47
|
+
@verification_tokens = []
|
48
|
+
@bot_access_token = ''
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
module MosEisley
|
2
|
+
|
3
|
+
def self.handlers
|
4
|
+
Handler.handlers
|
5
|
+
end
|
6
|
+
|
7
|
+
class Handler
|
8
|
+
|
9
|
+
# Load handlers from directories designated in config
|
10
|
+
def self.autoload
|
11
|
+
MosEisley.config.handler_paths.each { |path|
|
12
|
+
load_from_path(path)
|
13
|
+
}
|
14
|
+
end
|
15
|
+
|
16
|
+
# Load handlers from a directory
|
17
|
+
# @param path [String] directory name
|
18
|
+
def self.load_from_path(path)
|
19
|
+
Dir.chdir(path) {
|
20
|
+
Dir.foreach('.') { |f| load f unless File.directory?(f) }
|
21
|
+
}
|
22
|
+
end
|
23
|
+
|
24
|
+
# Call as often as necessary to add handlers; each call creates a MosEisley::Handler object
|
25
|
+
def self.add(type, &block)
|
26
|
+
@handlers ||= {
|
27
|
+
action: [],
|
28
|
+
command: [],
|
29
|
+
event: []
|
30
|
+
}
|
31
|
+
@handlers[type] << Handler.new(type, &block)
|
32
|
+
MosEisley.logger.debug("Added #{type} handler: #{@handlers[type].last}")
|
33
|
+
end
|
34
|
+
|
35
|
+
# @return [Hash<Symbol, Array>] containing all the handlers
|
36
|
+
def self.handlers
|
37
|
+
@handlers
|
38
|
+
end
|
39
|
+
|
40
|
+
# Run the handlers, typically called by the server
|
41
|
+
# @param event [Hash] from Slack Events API JSON data
|
42
|
+
def self.run(type, event)
|
43
|
+
logger = MosEisley.logger
|
44
|
+
logger.info("Running #{type} handlers...")
|
45
|
+
responses = []
|
46
|
+
@handlers[type].each { |h| responses << h.run(event) }
|
47
|
+
logger.info("Done running #{type} handlers.")
|
48
|
+
responses = [] if type == :event
|
49
|
+
merged_res = {}
|
50
|
+
responses.each do |r|
|
51
|
+
next unless r.class == Hash
|
52
|
+
[:response_type, :replace_original].each { |k| merged_res[k] = r[k] if r.has_key?(k) }
|
53
|
+
if r[:text]
|
54
|
+
if merged_res[:text]
|
55
|
+
merged_res[:text] += "\n#{r[:text]}"
|
56
|
+
else
|
57
|
+
merged_res[:text] = r[:text]
|
58
|
+
end
|
59
|
+
end
|
60
|
+
if r[:attachments]
|
61
|
+
merged_res[:attachments] ||= []
|
62
|
+
merged_res[:attachments] += r[:attachments]
|
63
|
+
end
|
64
|
+
end
|
65
|
+
return nil if merged_res.empty?
|
66
|
+
return merged_res
|
67
|
+
end
|
68
|
+
|
69
|
+
def initialize(type, &block)
|
70
|
+
@type = type
|
71
|
+
@block = block
|
72
|
+
end
|
73
|
+
|
74
|
+
def run(event)
|
75
|
+
logger = MosEisley.logger
|
76
|
+
logger.warn("No block to execute for #{@type} handler: #{self}") unless @block
|
77
|
+
logger.debug("Running #{@type} handler: #{self}")
|
78
|
+
@block.call(event)
|
79
|
+
rescue => e
|
80
|
+
logger.error(e.message)
|
81
|
+
logger.error(e.backtrace.join("\n"))
|
82
|
+
{text: "Woops, encountered an error."}
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
3
|
+
|
4
|
+
module MosEisley
|
5
|
+
|
6
|
+
def self.logger=(logger)
|
7
|
+
@logger = logger
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.logger
|
11
|
+
@logger ||= NullLogger.new()
|
12
|
+
end
|
13
|
+
|
14
|
+
class NullLogger < Logger
|
15
|
+
|
16
|
+
def initialize(*args)
|
17
|
+
end
|
18
|
+
|
19
|
+
def add(*args, &block)
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
data/lib/mos-eisley.rb
ADDED
data/lib/s3po/action.rb
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
module MosEisley
|
2
|
+
|
3
|
+
module S3PO
|
4
|
+
|
5
|
+
class Action
|
6
|
+
|
7
|
+
attr_reader :event
|
8
|
+
attr_reader :original_message
|
9
|
+
|
10
|
+
def initialize(e)
|
11
|
+
@event = e
|
12
|
+
if e[:original_message]
|
13
|
+
@original_message = Message.new(e[:original_message])
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def action
|
18
|
+
event[:actions][0]
|
19
|
+
end
|
20
|
+
|
21
|
+
def callback_id
|
22
|
+
event[:callback_id]
|
23
|
+
end
|
24
|
+
|
25
|
+
def channel
|
26
|
+
event[:channel][:id]
|
27
|
+
end
|
28
|
+
|
29
|
+
def user
|
30
|
+
event[:user][:id]
|
31
|
+
end
|
32
|
+
|
33
|
+
def message_ts
|
34
|
+
event[:message_ts]
|
35
|
+
end
|
36
|
+
|
37
|
+
def attachment_id
|
38
|
+
event[:attachment_id].to_i
|
39
|
+
end
|
40
|
+
|
41
|
+
def response_url
|
42
|
+
event[:response_url]
|
43
|
+
end
|
44
|
+
|
45
|
+
def message_age
|
46
|
+
Time.now.to_i - message_ts.to_i
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
data/lib/s3po/generic.rb
ADDED
data/lib/s3po/message.rb
ADDED
@@ -0,0 +1,111 @@
|
|
1
|
+
module MosEisley
|
2
|
+
|
3
|
+
module S3PO
|
4
|
+
|
5
|
+
class Message
|
6
|
+
|
7
|
+
attr_reader :event
|
8
|
+
|
9
|
+
def initialize(e)
|
10
|
+
@event = e
|
11
|
+
end
|
12
|
+
|
13
|
+
def message_type
|
14
|
+
return nil if event[:channel].nil?
|
15
|
+
t = :na
|
16
|
+
case event[:channel][0]
|
17
|
+
when 'C'
|
18
|
+
t = :channel
|
19
|
+
when 'D'
|
20
|
+
t = :im
|
21
|
+
when 'G'
|
22
|
+
t = :group
|
23
|
+
end
|
24
|
+
return t
|
25
|
+
end
|
26
|
+
|
27
|
+
def simple_message?
|
28
|
+
event[:subtype].nil?
|
29
|
+
end
|
30
|
+
|
31
|
+
def for_me?
|
32
|
+
# check user is not myself
|
33
|
+
myid = MosEisley.config.meta[:user_id]
|
34
|
+
return false if event[:user] == myid
|
35
|
+
return true if message_type == :im && simple_message?
|
36
|
+
return true if (event[:text] =~ /^<@#{myid}[|>]/) && simple_message?
|
37
|
+
return false
|
38
|
+
end
|
39
|
+
|
40
|
+
def text
|
41
|
+
event[:text]
|
42
|
+
end
|
43
|
+
|
44
|
+
def text=(t)
|
45
|
+
event[:text] = t
|
46
|
+
end
|
47
|
+
|
48
|
+
def attachments
|
49
|
+
event[:attachments]
|
50
|
+
end
|
51
|
+
|
52
|
+
def attachments=(a)
|
53
|
+
event[:attachments] = a
|
54
|
+
end
|
55
|
+
|
56
|
+
def user
|
57
|
+
event[:user]
|
58
|
+
end
|
59
|
+
|
60
|
+
def ts
|
61
|
+
event[:ts]
|
62
|
+
end
|
63
|
+
|
64
|
+
def channel
|
65
|
+
event[:channel]
|
66
|
+
end
|
67
|
+
|
68
|
+
def thread_ts
|
69
|
+
event[:thread_ts]
|
70
|
+
end
|
71
|
+
|
72
|
+
def arguments
|
73
|
+
return nil unless simple_message?
|
74
|
+
t = event[:text]
|
75
|
+
t = t.sub(/^<@#{MosEisley.config.meta[:user_id]}[|]?[^>]*>/, '') if for_me?
|
76
|
+
t.split
|
77
|
+
end
|
78
|
+
|
79
|
+
def postable_object
|
80
|
+
obj = @event.dup
|
81
|
+
return obj unless obj[:attachments]
|
82
|
+
obj[:attachments] = S3PO.json_with_object(obj[:attachments])
|
83
|
+
return obj
|
84
|
+
end
|
85
|
+
|
86
|
+
def reply(t = nil)
|
87
|
+
s = {
|
88
|
+
channel: channel,
|
89
|
+
as_user: true,
|
90
|
+
attachments: []
|
91
|
+
}
|
92
|
+
s[:text] = "<@#{user}> " unless message_type == :im
|
93
|
+
if thread_ts
|
94
|
+
s[:thread_ts] = thread_ts
|
95
|
+
elsif t
|
96
|
+
s[:thread_ts] = t
|
97
|
+
end
|
98
|
+
Message.new(s)
|
99
|
+
end
|
100
|
+
|
101
|
+
def reply_in_thread
|
102
|
+
t = thread_ts
|
103
|
+
t ||= ts
|
104
|
+
reply(t)
|
105
|
+
end
|
106
|
+
|
107
|
+
end
|
108
|
+
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
data/lib/s3po/s3po.rb
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'time'
|
3
|
+
require 's3po/generic'
|
4
|
+
require 's3po/action'
|
5
|
+
require 's3po/message'
|
6
|
+
|
7
|
+
module MosEisley
|
8
|
+
|
9
|
+
module S3PO
|
10
|
+
|
11
|
+
def self.parse_json(json)
|
12
|
+
return JSON.parse(json, {symbolize_names: true})
|
13
|
+
rescue => e
|
14
|
+
MosEisley.logger.warn("JSON parse error: #{e}")
|
15
|
+
return nil
|
16
|
+
end
|
17
|
+
|
18
|
+
# Convert object into JSON, optionally pretty-format
|
19
|
+
# @param obj [Object] any Ruby object
|
20
|
+
# @param opts [Hash] any JSON options
|
21
|
+
# @return [String] JSON string
|
22
|
+
def self.json_with_object(obj, pretty: false, opts: nil)
|
23
|
+
return '{}' if obj.nil?
|
24
|
+
if pretty
|
25
|
+
opts = {
|
26
|
+
indent: ' ',
|
27
|
+
space: ' ',
|
28
|
+
object_nl: "\n",
|
29
|
+
array_nl: "\n"
|
30
|
+
}
|
31
|
+
end
|
32
|
+
JSON.fast_generate(MosEisley::S3PO.format_json_value(obj), opts)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Return Ruby object/value to JSON standard format
|
36
|
+
# @param val [Object]
|
37
|
+
# @return [Object]
|
38
|
+
def self.format_json_value(val)
|
39
|
+
s3po = MosEisley::S3PO
|
40
|
+
case val
|
41
|
+
when Array
|
42
|
+
val.map { |v| s3po.format_json_value(v) }
|
43
|
+
when Hash
|
44
|
+
val.reduce({}) { |h, (k, v)| h.merge({k => s3po.format_json_value(v)}) }
|
45
|
+
when String
|
46
|
+
val.encode!('UTF-8', {invalid: :replace, undef: :replace})
|
47
|
+
when Time
|
48
|
+
val.utc.iso8601
|
49
|
+
else
|
50
|
+
val
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.create_event(e, type = nil)
|
55
|
+
type = e[:type] if e[:type]
|
56
|
+
case type
|
57
|
+
when 'message'
|
58
|
+
return Message.new(e)
|
59
|
+
when :action
|
60
|
+
return Action.new(e)
|
61
|
+
else
|
62
|
+
return GenericEvent.new(e)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
data/mos-eisley.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.expand_path('../lib', __FILE__))
|
2
|
+
require 'mos-eisley/version'
|
3
|
+
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = 'mos-eisley'
|
7
|
+
s.version = MosEisley::Version
|
8
|
+
s.authors = ['Ken J.']
|
9
|
+
s.email = ['kenjij@gmail.com']
|
10
|
+
s.summary = %q{A Ruby based multi-purpose Slack app server}
|
11
|
+
s.description = %q{A Ruby based HTTP server for accepting Slack actions, commands, events.}
|
12
|
+
s.homepage = 'https://github.com/kenjij/mos-eisley'
|
13
|
+
s.license = 'MIT'
|
14
|
+
|
15
|
+
s.files = `git ls-files`.split($/)
|
16
|
+
s.executables = s.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
17
|
+
s.require_paths = ['lib']
|
18
|
+
|
19
|
+
s.required_ruby_version = '>= 2.1'
|
20
|
+
s.add_runtime_dependency 'kajiki', '~> 1.1'
|
21
|
+
s.add_runtime_dependency 'thin', '~> 1.7'
|
22
|
+
s.add_runtime_dependency 'sinatra', '~> 2.0'
|
23
|
+
s.add_runtime_dependency 'em-http-request', '~> 1.1'
|
24
|
+
end
|
metadata
ADDED
@@ -0,0 +1,117 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mos-eisley
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Ken J.
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-09-19 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: kajiki
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.1'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.1'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: thin
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.7'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.7'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: sinatra
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '2.0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '2.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: em-http-request
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.1'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '1.1'
|
69
|
+
description: A Ruby based HTTP server for accepting Slack actions, commands, events.
|
70
|
+
email:
|
71
|
+
- kenjij@gmail.com
|
72
|
+
executables:
|
73
|
+
- mos-eisley
|
74
|
+
extensions: []
|
75
|
+
extra_rdoc_files: []
|
76
|
+
files:
|
77
|
+
- README.md
|
78
|
+
- bin/mos-eisley
|
79
|
+
- lib/http-client/client.rb
|
80
|
+
- lib/http-client/webapi.rb
|
81
|
+
- lib/http-server/helper.rb
|
82
|
+
- lib/http-server/server.rb
|
83
|
+
- lib/mos-eisley.rb
|
84
|
+
- lib/mos-eisley/config.rb
|
85
|
+
- lib/mos-eisley/handler.rb
|
86
|
+
- lib/mos-eisley/logger.rb
|
87
|
+
- lib/mos-eisley/version.rb
|
88
|
+
- lib/s3po/action.rb
|
89
|
+
- lib/s3po/generic.rb
|
90
|
+
- lib/s3po/message.rb
|
91
|
+
- lib/s3po/s3po.rb
|
92
|
+
- mos-eisley.gemspec
|
93
|
+
homepage: https://github.com/kenjij/mos-eisley
|
94
|
+
licenses:
|
95
|
+
- MIT
|
96
|
+
metadata: {}
|
97
|
+
post_install_message:
|
98
|
+
rdoc_options: []
|
99
|
+
require_paths:
|
100
|
+
- lib
|
101
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
102
|
+
requirements:
|
103
|
+
- - ">="
|
104
|
+
- !ruby/object:Gem::Version
|
105
|
+
version: '2.1'
|
106
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
requirements: []
|
112
|
+
rubyforge_project:
|
113
|
+
rubygems_version: 2.4.8
|
114
|
+
signing_key:
|
115
|
+
specification_version: 4
|
116
|
+
summary: A Ruby based multi-purpose Slack app server
|
117
|
+
test_files: []
|