mos-eisley 0.0.1
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/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
|
+
[](http://badge.fury.io/rb/mos-eisley) [](https://codeclimate.com/github/kenjij/mos-eisley) [](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: []
|