botaku 0.1.0 → 0.9.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 +4 -4
- data/CHANGELOG.md +6 -0
- data/README.md +55 -0
- data/botaku.gemspec +3 -2
- data/lib/botaku.rb +4 -1
- data/lib/botaku/bot.rb +98 -0
- data/lib/botaku/client.rb +210 -0
- metadata +22 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5d9563871eee94af6e33be3d70c71c56b6a43664
|
4
|
+
data.tar.gz: fb92967dbdb6b9d8c796c78646529689c93c535a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b3324d211fc6b4880394ad754e0e1d19f09f375aad63d386f6a358e90cac920dacac3f41bc5bc232a994864034872ca36144ac947da37ed20e0689b713d6d479
|
7
|
+
data.tar.gz: a9a737fb10adc36e254e168436b8903fa45fcb8f7b1bc6e80de5a5e72cdc0ffdc383c684166011ccb5e36c7510d9ee72c9c8c5eb2c7c785542bdb20b72f695dd
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -1,6 +1,61 @@
|
|
1
1
|
|
2
2
|
# botaku
|
3
3
|
|
4
|
+
A Slack bot abstraction, built on top of [faye-websocket](https://github.com/faye/faye-websocket-ruby) and [httpclient](https://github.com/nahi/httpclient).
|
5
|
+
|
6
|
+
|
7
|
+
## use
|
8
|
+
|
9
|
+
Botaku provides `Botaku::Client` and `Botaku::Bot`.
|
10
|
+
|
11
|
+
## Botaku::Client
|
12
|
+
|
13
|
+
```ruby
|
14
|
+
require 'botaku'
|
15
|
+
|
16
|
+
#c = Botaku::Client.new(token: 'xxxx-111111111111-aaaaaaaaaaaaaaaaaaaaaaaa')
|
17
|
+
# or
|
18
|
+
c = Botaku::Client.new(token: '.slack_api_token')
|
19
|
+
|
20
|
+
c.on('hello') do
|
21
|
+
p [ :hello ]
|
22
|
+
c.say('born to be alive...', '#test')
|
23
|
+
end
|
24
|
+
c.on('message') do |data|
|
25
|
+
p [ :message, data ]
|
26
|
+
c.say('pong', channel: data['channel']) if data['text'].match(/\A\s*ping\b/)
|
27
|
+
end
|
28
|
+
```
|
29
|
+
|
30
|
+
## Botaku::Bot
|
31
|
+
|
32
|
+
```ruby
|
33
|
+
require 'botaku'
|
34
|
+
|
35
|
+
class ZeroBot < Botaku::Bot
|
36
|
+
|
37
|
+
def on_hello
|
38
|
+
|
39
|
+
p [ :on_hello, _self['id'] ]
|
40
|
+
|
41
|
+
say(
|
42
|
+
"I am #{name} (#{self.class}) from #{`uname -a`}...",
|
43
|
+
'#test')
|
44
|
+
end
|
45
|
+
|
46
|
+
def on_message(data)
|
47
|
+
|
48
|
+
typing(data['channel'])
|
49
|
+
say("@#{user_name(data)} said: #{data['text'].inspect}", data['channel'])
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
#ZeroBot.new(token: 'xxxx-111111111111-aaaaaaaaaaaaaaaaaaaaaaaa')
|
54
|
+
# or
|
55
|
+
ZeroBot.new(token: 'test/.slack_api_token').run
|
56
|
+
```
|
57
|
+
|
58
|
+
|
4
59
|
## license
|
5
60
|
|
6
61
|
MIT, see [LICENSE.txt](LICENSE.txt).
|
data/botaku.gemspec
CHANGED
@@ -16,7 +16,7 @@ Gem::Specification.new do |s|
|
|
16
16
|
s.summary = 'a Slack bot abstraction'
|
17
17
|
|
18
18
|
s.description = %{
|
19
|
-
A Slack bot abstraction, built on top of
|
19
|
+
A Slack bot abstraction, built on top of faye-websocket and httpclient.
|
20
20
|
}.strip
|
21
21
|
|
22
22
|
#s.files = `git ls-files`.split("\n")
|
@@ -28,7 +28,8 @@ A Slack bot abstraction, built on top of the slack-ruby-client gem.
|
|
28
28
|
"#{s.name}.gemspec",
|
29
29
|
]
|
30
30
|
|
31
|
-
s.add_runtime_dependency '
|
31
|
+
s.add_runtime_dependency 'faye-websocket', '~> 0.10'
|
32
|
+
s.add_runtime_dependency 'httpclient', '~> 2.8'
|
32
33
|
|
33
34
|
#s.add_development_dependency 'rspec', '>= 2.13.0'
|
34
35
|
|
data/lib/botaku.rb
CHANGED
data/lib/botaku/bot.rb
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
|
2
|
+
module Botaku
|
3
|
+
|
4
|
+
class Bot
|
5
|
+
|
6
|
+
attr_reader :client, :opts
|
7
|
+
|
8
|
+
def initialize(opts)
|
9
|
+
|
10
|
+
fail ArgumentError.new('missing :token') unless opts[:token]
|
11
|
+
|
12
|
+
@opts = opts
|
13
|
+
end
|
14
|
+
|
15
|
+
def _self
|
16
|
+
|
17
|
+
@client._self
|
18
|
+
end
|
19
|
+
|
20
|
+
def name
|
21
|
+
|
22
|
+
@client.objects[_self['id']]['name']
|
23
|
+
end
|
24
|
+
|
25
|
+
def say(*as)
|
26
|
+
|
27
|
+
@client.say(*(as + [ @channel ]))
|
28
|
+
end
|
29
|
+
|
30
|
+
def typing(channel=@channel)
|
31
|
+
|
32
|
+
@client.typing(channel: channel)
|
33
|
+
end
|
34
|
+
|
35
|
+
def run
|
36
|
+
|
37
|
+
@client = Botaku::Client.new(@opts.dup)
|
38
|
+
|
39
|
+
@client.on('hello') { invoke(:hello) }
|
40
|
+
@client.on(:close) { |d| invoke(:close, d) }
|
41
|
+
|
42
|
+
@client.on('message') do |d|
|
43
|
+
(invoke_command(d) || invoke(:message, d)) if d['user'] != _self['id']
|
44
|
+
end
|
45
|
+
|
46
|
+
@client.run
|
47
|
+
end
|
48
|
+
alias join run
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def determine_conf_name
|
53
|
+
|
54
|
+
self.class.name.split(':').last.downcase[0..-4]
|
55
|
+
end
|
56
|
+
|
57
|
+
def invoke_command(data)
|
58
|
+
|
59
|
+
public_methods.sort.grep(/\Aon_command_[a-z]+\z/)
|
60
|
+
.each do |meth|
|
61
|
+
m = match_command(data, meth.to_s[11..-1])
|
62
|
+
next unless m
|
63
|
+
data['match'] = m
|
64
|
+
self.send(meth, data)
|
65
|
+
return true
|
66
|
+
end
|
67
|
+
|
68
|
+
false
|
69
|
+
end
|
70
|
+
|
71
|
+
def invoke(type, data=nil)
|
72
|
+
|
73
|
+
public_methods.sort.grep(/\Aon_#{type}/)
|
74
|
+
.each do |meth|
|
75
|
+
args = method(meth).arity == 1 ? [ data ] : []
|
76
|
+
r = self.send(meth, *args)
|
77
|
+
return true if r == true
|
78
|
+
end
|
79
|
+
|
80
|
+
false
|
81
|
+
end
|
82
|
+
|
83
|
+
def match_command(data, command)
|
84
|
+
|
85
|
+
m = data['text'].match(/\A\s*#{command}(?:\s+(.+))?\z/i)
|
86
|
+
m ? (m[1] || '').split : nil
|
87
|
+
end
|
88
|
+
|
89
|
+
%w[
|
90
|
+
obj obj_id obj_name
|
91
|
+
user user_id user_name
|
92
|
+
channel channel_id channel_name
|
93
|
+
].each do |m|
|
94
|
+
define_method(m) { |o| @client.send(m, o) }
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
@@ -0,0 +1,210 @@
|
|
1
|
+
|
2
|
+
require 'json'
|
3
|
+
require 'httpclient'
|
4
|
+
require 'faye/websocket'
|
5
|
+
require 'eventmachine'
|
6
|
+
|
7
|
+
|
8
|
+
module Botaku
|
9
|
+
|
10
|
+
class Client
|
11
|
+
|
12
|
+
def initialize(opts)
|
13
|
+
|
14
|
+
fail ArgumentError.new('missing :token') unless opts[:token]
|
15
|
+
|
16
|
+
@opts = opts
|
17
|
+
|
18
|
+
@base_uri = 'https://slack.com/api'
|
19
|
+
@modules = {}
|
20
|
+
@http_client = HTTPClient.new
|
21
|
+
@ws_client = nil
|
22
|
+
@rtm = nil
|
23
|
+
@handlers = {}
|
24
|
+
|
25
|
+
@http_client.debug_dev = $stderr if ENV['BOTAKU_DEBUG_HTTP']
|
26
|
+
|
27
|
+
if @opts[:token].match(/[\\\/.]/) || @opts[:token].count('-') != 2
|
28
|
+
@opts[:token_path] = @opts[:token]
|
29
|
+
@opts[:token] = File.read(@opts[:token]).strip
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def _self
|
34
|
+
|
35
|
+
@rtm['self']
|
36
|
+
end
|
37
|
+
|
38
|
+
%w[ api chat rtm ].each do |mod|
|
39
|
+
|
40
|
+
define_method mod do
|
41
|
+
@modules[mod] ||= WebApiModule.new(self, mod)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def on(type, &block)
|
46
|
+
|
47
|
+
(@handlers[type] ||= []) << block
|
48
|
+
end
|
49
|
+
|
50
|
+
def run
|
51
|
+
|
52
|
+
EM.run do
|
53
|
+
@rtm = rtm.start
|
54
|
+
@ws_client = Faye::WebSocket::Client.new(@rtm['url'])
|
55
|
+
[ :open, :error, :close ].each do |event_type|
|
56
|
+
@ws_client.on(event_type) { |event| dispatch(event_type, event) }
|
57
|
+
end
|
58
|
+
@ws_client.on(:message) { |event| dispatch_message(event) }
|
59
|
+
end
|
60
|
+
end
|
61
|
+
alias join run
|
62
|
+
|
63
|
+
# say('hello')
|
64
|
+
# say('hello', '#test_channel')
|
65
|
+
# say('hello', channel: 'C12345ABC')
|
66
|
+
# say('hello', channel: '#test_channel')
|
67
|
+
#
|
68
|
+
def say(*as)
|
69
|
+
|
70
|
+
args = rework_args(as)
|
71
|
+
args[:as_user] = true unless args.has_key?(:as_user)
|
72
|
+
|
73
|
+
args[:text] = args[:text]
|
74
|
+
.gsub(/&/, '&').gsub(/</, '<').gsub(/>/, '>')
|
75
|
+
|
76
|
+
args[:mrkdwn] = true
|
77
|
+
|
78
|
+
#chat.postMessage(args)
|
79
|
+
|
80
|
+
args[:type] = 'message'
|
81
|
+
args[:id] = next_id
|
82
|
+
|
83
|
+
do_send(args)
|
84
|
+
end
|
85
|
+
|
86
|
+
def channels; @rtm['channels']; end
|
87
|
+
def groups; @rtm['groups']; end
|
88
|
+
def users; @rtm['users']; end
|
89
|
+
|
90
|
+
def objects
|
91
|
+
|
92
|
+
@objects ||=
|
93
|
+
(channels + groups + users).inject({}) { |h, o| h[o['id']] = o; h }
|
94
|
+
end
|
95
|
+
|
96
|
+
def obj(o, cat=nil)
|
97
|
+
|
98
|
+
if o.is_a?(Hash)
|
99
|
+
cat = cat ? cat.to_s : nil
|
100
|
+
o = o[cat]
|
101
|
+
end
|
102
|
+
|
103
|
+
case o
|
104
|
+
when /\AU[0-9A-Z]+\z/
|
105
|
+
users.find { |u| u['id'] == o }
|
106
|
+
when /\A@[^\s]+\z/
|
107
|
+
users.find { |u| u['name'] == o[1..-1] }
|
108
|
+
when /\A[CG][0-9A-Z]+\z/
|
109
|
+
(channels + groups).find { |c| c['id'] == o }
|
110
|
+
when /\A#[^\s]+\z/
|
111
|
+
(channels + groups).find { |c| c['name'] == o[1..-1] }
|
112
|
+
else
|
113
|
+
objects[o] ||
|
114
|
+
objects.values.find { |x| x['name'] == o }
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def obj_id(o, cat=nil); h = obj(o, cat); h ? h['id'] : nil; end
|
119
|
+
def obj_name(o, cat=nil); h = obj(o, cat); h ? h['name'] : nil; end
|
120
|
+
|
121
|
+
def user(o); obj(o, :user); end
|
122
|
+
def user_id(o); obj_id(o, :user); end
|
123
|
+
def user_name(o); obj_name(o, :user); end
|
124
|
+
def channel(o); obj(o, :channel); end
|
125
|
+
def channel_id(o); obj_id(o, :channel); end
|
126
|
+
def channel_name(o); obj_name(o, :channel); end
|
127
|
+
|
128
|
+
def typing(args)
|
129
|
+
|
130
|
+
do_send(type: 'typing', id: next_id, channel: channel_id(args[:channel]))
|
131
|
+
end
|
132
|
+
|
133
|
+
private
|
134
|
+
|
135
|
+
def get(mod, meth, args)
|
136
|
+
|
137
|
+
uri = args
|
138
|
+
.inject(
|
139
|
+
"#{@base_uri}/#{mod}.#{meth}?token=#{@opts[:token]}"
|
140
|
+
) { |u, (k, v)|
|
141
|
+
u + "#{uri}&#{k}=#{escape(v)}"
|
142
|
+
}
|
143
|
+
|
144
|
+
@http_client.get(uri)
|
145
|
+
end
|
146
|
+
|
147
|
+
def do_send(args)
|
148
|
+
|
149
|
+
@ws_client.send(JSON.dump(args) + "\n")
|
150
|
+
# the \n seems to help flushing the buffer
|
151
|
+
end
|
152
|
+
|
153
|
+
def next_id
|
154
|
+
|
155
|
+
@id = (@id ||= -1) + 1
|
156
|
+
end
|
157
|
+
|
158
|
+
class WebApiModule
|
159
|
+
def initialize(client, name)
|
160
|
+
@client = client
|
161
|
+
@name = name
|
162
|
+
end
|
163
|
+
def method_missing(meth, *args)
|
164
|
+
args = [ {} ] if args.empty?
|
165
|
+
return super if args.size != 1 || ! args[0].is_a?(Hash)
|
166
|
+
r = @client.send(:get, @name, meth, args.first)
|
167
|
+
JSON.parse(r.body)
|
168
|
+
end
|
169
|
+
undef :test
|
170
|
+
end
|
171
|
+
|
172
|
+
def escape(arg_value)
|
173
|
+
|
174
|
+
v = arg_value.is_a?(String) ? arg_value : arg_value.inspect
|
175
|
+
|
176
|
+
URI.escape(v)
|
177
|
+
end
|
178
|
+
|
179
|
+
def dispatch(event_type, event)
|
180
|
+
|
181
|
+
(@handlers[event_type] || []).each do |block|
|
182
|
+
block.arity == 1 ? block.call(event) : block.call
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
def dispatch_message(event)
|
187
|
+
|
188
|
+
data = JSON.parse(event.data)
|
189
|
+
|
190
|
+
(@handlers[data['type']] || []).each do |block|
|
191
|
+
block.arity == 1 ? block.call(data) : block.call
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
def rework_args(as)
|
196
|
+
|
197
|
+
as.inject({}) do |h, a|
|
198
|
+
if h[:channel] == nil && h[:channel] = channel_id(a)
|
199
|
+
# cid just got assigned
|
200
|
+
elsif h[:text] == nil && a.is_a?(String)
|
201
|
+
h[:text] = a
|
202
|
+
elsif a.is_a?(Hash)
|
203
|
+
h.merge!(a)
|
204
|
+
end
|
205
|
+
h
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
metadata
CHANGED
@@ -1,30 +1,44 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: botaku
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.9.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- John Mettraux
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-07-
|
11
|
+
date: 2017-07-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: faye-websocket
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '0.
|
19
|
+
version: '0.10'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '0.
|
27
|
-
|
26
|
+
version: '0.10'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: httpclient
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '2.8'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '2.8'
|
41
|
+
description: A Slack bot abstraction, built on top of faye-websocket and httpclient.
|
28
42
|
email:
|
29
43
|
- jmettraux@gmail.com
|
30
44
|
executables: []
|
@@ -37,6 +51,8 @@ files:
|
|
37
51
|
- README.md
|
38
52
|
- botaku.gemspec
|
39
53
|
- lib/botaku.rb
|
54
|
+
- lib/botaku/bot.rb
|
55
|
+
- lib/botaku/client.rb
|
40
56
|
homepage: http://github.com/jmettraux/botaku
|
41
57
|
licenses:
|
42
58
|
- MIT
|