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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 46204c6e35d975dfd68a53d97bc3db03d7b1b18b
4
- data.tar.gz: 796dfa27ea76aabe22fa19a865e9d20857d6a47f
3
+ metadata.gz: 5d9563871eee94af6e33be3d70c71c56b6a43664
4
+ data.tar.gz: fb92967dbdb6b9d8c796c78646529689c93c535a
5
5
  SHA512:
6
- metadata.gz: 610542f17841c5970325ce667121e4a1fda7c6511319c49d3d3b1482cd871a183f55df7372ac94b04aa3c47b69c2fc655f7c1124ff4a7f4018a2ccdd2c111d6d
7
- data.tar.gz: 9c4c3d3a783cfa7091eb62644cb587bd9d28ee5a96ba2ad68ae52f2350190a105983b6731d64079321597e6ffb2b512d14e96920bb661d7dd14d36e423fd55eb
6
+ metadata.gz: b3324d211fc6b4880394ad754e0e1d19f09f375aad63d386f6a358e90cac920dacac3f41bc5bc232a994864034872ca36144ac947da37ed20e0689b713d6d479
7
+ data.tar.gz: a9a737fb10adc36e254e168436b8903fa45fcb8f7b1bc6e80de5a5e72cdc0ffdc383c684166011ccb5e36c7510d9ee72c9c8c5eb2c7c785542bdb20b72f695dd
@@ -2,6 +2,12 @@
2
2
  # CHANGELOG.md
3
3
 
4
4
 
5
+ ## botaku 0.9.0 released 2017-07-19
6
+
7
+ - Read token as file if necessary
8
+ - Implement Botaku::Client, based on HTTPClient and Faye-Websocket
9
+
10
+
5
11
  ## botaku 0.1.0 released 2017-07-16
6
12
 
7
13
  - Initial empty release
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).
@@ -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 the slack-ruby-client gem.
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 'slack-ruby-client', '~> 0.8'
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
 
@@ -1,6 +1,9 @@
1
1
 
2
2
  module Botaku
3
3
 
4
- VERSION = '0.1.0'
4
+ VERSION = '0.9.0'
5
5
  end
6
6
 
7
+ require 'botaku/client'
8
+ require 'botaku/bot'
9
+
@@ -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(/&/, '&amp;').gsub(/</, '&lt;').gsub(/>/, '&gt;')
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.1.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-16 00:00:00.000000000 Z
11
+ date: 2017-07-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: slack-ruby-client
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.8'
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.8'
27
- description: A Slack bot abstraction, built on top of the slack-ruby-client gem.
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