qbot 0.1.3 → 0.1.10

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
- SHA1:
3
- metadata.gz: 96175e44e35b00efdc98a08f7dc482c75358a2fa
4
- data.tar.gz: f56a2d9a7e9d7634d8c8f1dcbe8a443cad455b05
2
+ SHA256:
3
+ metadata.gz: 4c0dd3c3d9a34d8eae7415eb10ddefa307ebca4bd200321e18fee452de9de157
4
+ data.tar.gz: 3a4f94dda22cd3aa76a83efd0c6c664ab365ed18946bec7b3e3bd7862728a421
5
5
  SHA512:
6
- metadata.gz: b03726403afaad4534f48644c27184b7a7df7fa562bbcdae9bd87a67004b816594f5c15d6726e204438793bff9cf0d3b6a27dfa715465168a4194a6bab6f6600
7
- data.tar.gz: 0a33dbf259131511157139fefbe1faf4d790a40fc9962a77f8f4ddd962ae5b0a71de90f97748d29cc31219c2168c6f8c7b4d9d77e3b725c72d008f4070c668c6
6
+ metadata.gz: cf435067c01baa076842a9ce672291897668eeef96b4e33fb216c7149feb6481139b79944725ba0c4816223925648521c7ebedd64ad909601e9b3a89e2d8c502
7
+ data.tar.gz: b61c2f73b417f4d6eb08dbef13386385d7a221847ef9a7c52607f1f29fb519d11eb7e227fe91b0beb35d3055de81c0082664eee0a42080b3fcce7308c6e7892a
@@ -1,4 +1,5 @@
1
1
  require 'qbot/base'
2
+ require 'qbot/embed/help'
2
3
  require 'qbot/autorun'
3
4
 
4
5
  module Qbot
@@ -1,6 +1,6 @@
1
- module Qbot
1
+ require 'qbot/message'
2
2
 
3
- Message = Struct.new(:text, :data, :matched)
3
+ module Qbot
4
4
 
5
5
  module Adapter
6
6
 
@@ -20,7 +20,7 @@ module Qbot
20
20
 
21
21
  def run(bots)
22
22
  listen do |message|
23
- bots.each { |bot| bot.call(message.dup) }
23
+ bots.each { |bot| bot.listen(message.dup) }
24
24
  end
25
25
  end
26
26
 
@@ -32,10 +32,6 @@ module Qbot
32
32
  raise 'Not implemented'
33
33
  end
34
34
 
35
- def reply_to(message, text, **options)
36
- raise 'Not implemented'
37
- end
38
-
39
35
  end
40
36
 
41
37
  end
@@ -20,6 +20,8 @@ module Qbot
20
20
 
21
21
  access_token(resp.headers['token'])
22
22
  raise 'Login failed' unless @token
23
+
24
+ @bot_id = me['username']
23
25
  end
24
26
 
25
27
  def access_token(token)
@@ -35,22 +37,19 @@ module Qbot
35
37
  end
36
38
 
37
39
  def post(text, **options)
38
- resp = api_call(:post, "/posts", body: options.merge(message: text))
39
- Qbot.app.logger.info("#{self.class} - Post message: #{resp.status} - '#{text}'")
40
- end
41
-
42
- def reply_to(message, text, **options)
43
40
  if options[:channel_id]
44
- channel_id = options[:channel_id]
41
+ # do nothing
45
42
  elsif options[:channel_name]
46
- channel = channel(options[:channel_name])
47
- channel_id = channel['id'] if channel
43
+ channel = channel(options.delete(:channel_name))
44
+ options[:channel_id] = channel['id'] if channel
45
+ elsif options[:reply_to]
46
+ message = options.delete(:reply_to)
47
+ options[:channel_id] = message.data['channel_id']
48
48
  end
49
49
 
50
- channel_id ||= message.data['channel_id'] if message
51
- return unless channel_id
52
-
53
- post(text, **options.merge(channel_id: channel_id))
50
+ return unless options[:channel_id]
51
+ Qbot.app.logger.info("#{self.class} - Post message: '#{text}'")
52
+ api_call(:post, "/posts", body: options.merge(message: text))
54
53
  end
55
54
 
56
55
  private
@@ -59,10 +58,9 @@ module Qbot
59
58
  end
60
59
 
61
60
  def start_connection(&block)
62
- running = true
63
61
  ws_url = URI.join(@server.gsub(/^http(s?):/, 'ws\1:'), endpoint('/websocket')).to_s
64
62
 
65
- ws = Faye::WebSocket::Client.new(ws_url)
63
+ ws = Faye::WebSocket::Client.new(ws_url, nil, {ping: 60})
66
64
  ws.send({seq: 1, action: 'authentication_challenge', data: {token: @token}}.to_json)
67
65
 
68
66
  ws.on :message do |e|
@@ -76,12 +74,12 @@ module Qbot
76
74
 
77
75
  ws.on :close do |e|
78
76
  Qbot.app.logger.info("#{self.class} - Websocket connection closed: #{e.code} #{e.reason}")
79
- if running then start_connection(&block) else Qbot.app.stop end
77
+ sleep 30
78
+ start_connection(&block)
80
79
  end
81
80
 
82
81
  ws.on :error do |e|
83
82
  Qbot.app.logger.error("#{self.class} - Websocket encountered error: #{e.message}")
84
- running = false
85
83
  end
86
84
  end
87
85
 
@@ -91,16 +89,16 @@ module Qbot
91
89
 
92
90
  case type
93
91
  when 'posted'
94
- post = JSON.parse(data['data']['post'])
92
+ data = JSON.parse(data['data']['post'])
95
93
 
96
- message = Qbot::Message.new
97
- message.data = post
98
- message.text = post['message']
94
+ message = Qbot::Message.new(data)
95
+ message.text = data['message']
96
+ message.mention(/^\s*@#{@bot_id}\b/)
99
97
 
100
98
  callback.call(message)
101
99
  end
102
100
  rescue => err
103
- Qbot.app.logger.error("#{self.class} - ERROR! #{err}")
101
+ Qbot.app.logger.error("#{self.class} - ERROR: #{err}")
104
102
  end
105
103
 
106
104
  def api_call(method, path, **options, &block)
@@ -18,9 +18,9 @@ module Qbot
18
18
  def receive_line(text)
19
19
  exit 0 if text.strip == 'exit'
20
20
 
21
- message = Qbot::Message.new
22
- message.data = text
21
+ message = Qbot::Message.new(text)
23
22
  message.text = text
23
+ message.mention(/^\s*bot\b/)
24
24
 
25
25
  @callback.call(message)
26
26
  $stdout.print ">> "
@@ -39,10 +39,6 @@ module Qbot
39
39
  $stdout.puts text
40
40
  end
41
41
 
42
- def reply_to(message, text, **options)
43
- post(text, **options)
44
- end
45
-
46
42
  end
47
43
 
48
44
  end
@@ -12,9 +12,9 @@ module Qbot
12
12
  SLACK_API_URL = 'https://slack.com/api'
13
13
 
14
14
  def initialize(api_token: nil)
15
- @server = URI.join(SLACK_API_URL, '/').to_s
16
-
17
15
  access_token(api_token || ENV['QBOT_SLACK_API_TOKEN'])
16
+ @server = URI.join(SLACK_API_URL, '/').to_s
17
+ @bot_id = me['user_id']
18
18
  end
19
19
 
20
20
  def access_token(token)
@@ -26,26 +26,23 @@ module Qbot
26
26
  end
27
27
 
28
28
  def close
29
- EM.stop
29
+ EM.stop_event_loop
30
30
  end
31
31
 
32
32
  def post(text, **options)
33
- resp = api_call(:post, "/chat.postMessage", options.merge(text: text))
34
- Qbot.app.logger.info("#{self.class} - Post message: #{resp.status} - '#{text}'")
35
- end
36
-
37
- def reply_to(message, text, **options)
38
33
  if options[:channel_id]
39
- channel_id = options[:channel_id]
34
+ options[:channel] = options.delete(:channel_id)
40
35
  elsif options[:channel_name]
41
- channel = channel(options[:channel_name])
42
- channel_id = channel['id'] if channel
36
+ channel = channel(options.delete(:channel_name))
37
+ options[:channel] = channel['id'] if channel
38
+ elsif options[:reply_to]
39
+ message = options.delete(:reply_to)
40
+ options[:channel] = message.data['channel']
43
41
  end
44
42
 
45
- channel_id ||= message.data['channel'] if message
46
- return unless channel_id
47
-
48
- post(text, **options.merge(channel: channel_id))
43
+ return unless options[:channel]
44
+ Qbot.app.logger.info("#{self.class} - Post message: '#{text}'")
45
+ api_call(:post, "/chat.postMessage", options.merge(text: text))
49
46
  end
50
47
 
51
48
  private
@@ -57,7 +54,6 @@ module Qbot
57
54
  resp = api_call(:get, '/rtm.start')
58
55
  data = JSON.parse(resp.body)
59
56
 
60
- running = true
61
57
  ws_url = data['url']
62
58
  ws = Faye::WebSocket::Client.new(ws_url, nil, {ping: 60})
63
59
 
@@ -72,26 +68,26 @@ module Qbot
72
68
 
73
69
  ws.on :close do |e|
74
70
  Qbot.app.logger.info("#{self.class} - Websocket connection closed: #{e.code} #{e.reason}")
75
- if running then start_connection(&block) else Qbot.app.stop end
71
+ sleep 30
72
+ start_connection(&block)
76
73
  end
77
74
 
78
75
  ws.on :error do |e|
79
76
  Qbot.app.logger.error("#{self.class} - Websocket encountered error: #{e.message}")
80
- running = false
81
77
  end
82
78
  end
83
79
 
84
80
  def emit_event(data, callback)
85
- return unless type = data['event']
81
+ return unless type = data['type']
86
82
  Qbot.app.logger.debug("#{self.class} - Event '#{type}' recieved")
87
83
 
88
84
  case type
89
85
  when 'message'
90
86
  return if data['subtype']
91
87
 
92
- message = Qbot::Message.new
93
- message.data = data
88
+ message = Qbot::Message.new(data)
94
89
  message.text = data['text']
90
+ message.mention(/^\s*<@#{@bot_id}>/)
95
91
 
96
92
  callback.call(message)
97
93
  end
@@ -26,24 +26,25 @@ module Qbot
26
26
 
27
27
  def initialize
28
28
  @bots = []
29
+ @help = {}
29
30
  @threads = []
30
31
  @timers = Timers::Group.new
31
32
  @logger = Logger.new(STDOUT)
32
33
  end
33
34
 
34
- def add(bot)
35
+ def add_bot(bot)
35
36
  @bots << bot
36
37
  end
37
38
 
38
- def bots
39
- @bots.map { |bot| bot.class.name }.uniq
39
+ def help_text(usages = {})
40
+ @help.update(usages)
40
41
  end
41
42
 
42
43
  def start
43
44
  @logger.info("Booting #{self.class}.")
44
45
  @logger.info("#{storage.class} - Storage driver loaded.")
45
46
  @logger.info("#{adapter.class} - Adapter driver loaded.")
46
- bots.each { |bot| @logger.info("Loading #{bot}.") }
47
+ @bots.map { |bot| bot.class.name }.uniq.each { |bot| @logger.info("#{bot} loaded.") }
47
48
 
48
49
  Thread.abort_on_exception = true
49
50
  @threads << Thread.start { loop { @timers.wait } }
@@ -5,7 +5,10 @@ module Qbot
5
5
  exit if $!
6
6
 
7
7
  %i{INT TERM}.each do |signal|
8
- Signal.trap(signal) { exit }
8
+ Signal.trap(signal) do
9
+ Qbot.app.stop
10
+ exit
11
+ end
9
12
  end
10
13
 
11
14
  Qbot.run!(ARGV)
@@ -13,15 +13,20 @@ module Qbot
13
13
 
14
14
  class << self
15
15
 
16
- def on(pattern, &block)
17
- pattern = Regexp.new(pattern.to_s) unless Regexp === pattern
18
- Qbot.app.add(new(pattern, &block))
16
+ def on(pattern, **options, &block)
17
+ pattern = Regexp.new("\b#{pattern}\b") unless Regexp === pattern
18
+ instance = new(pattern, **options, &block)
19
+ Qbot.app.add_bot(instance)
19
20
  end
20
21
 
21
22
  def cron(pattern, &block)
22
23
  schedule(pattern, &block)
23
24
  end
24
25
 
26
+ def help(usages)
27
+ Qbot.app.help_text(usages)
28
+ end
29
+
25
30
  private
26
31
  def schedule(pattern, &block)
27
32
  parser = CronParser.new(pattern)
@@ -29,32 +34,43 @@ module Qbot
29
34
  delay = parser.next(current) - current
30
35
 
31
36
  Qbot.app.timers.after(delay) do
32
- new(pattern).instance_eval(&block)
37
+ begin
38
+ instance = new(pattern)
39
+ instance.instance_eval(&block)
40
+ rescue => e
41
+ Qbot.app.logger.error("#{instance.class} - Error: #{e}")
42
+ end
43
+
33
44
  schedule(pattern, &block)
34
45
  end
35
46
  end
36
47
 
37
48
  end
38
49
 
39
- def initialize(pattern, &block)
50
+ def initialize(pattern, **options, &block)
40
51
  @pattern = pattern
52
+ @options = options
41
53
  @callback = block
42
54
  end
43
55
 
44
- def call(message)
45
- @message = message
46
- return unless @pattern =~ @message.text.to_s.strip
47
-
48
- begin
49
- Qbot.app.logger.debug("#{self.class} - Recieve message: '#{message.text}'")
50
- instance_exec($~, &@callback)
51
- rescue => e
52
- Qbot.app.logger.error("#{self.class} - Error: #{e}")
56
+ def listen(message)
57
+ if @options[:global]
58
+ return unless message.match(@pattern)
59
+ else
60
+ return unless message.mentioned?
61
+ return unless message.match(@pattern, prefix: message.mention)
53
62
  end
63
+
64
+ Qbot.app.logger.debug("#{self.class} - Recieve message: '#{message.text}'")
65
+
66
+ @message = message
67
+ instance_exec(@message, &@callback)
68
+ rescue => e
69
+ Qbot.app.logger.error("#{self.class} - Error: #{e}")
54
70
  end
55
71
 
56
72
  def post(text, **options)
57
- Qbot.app.adapter.reply_to(@message, text, **options)
73
+ Qbot.app.adapter.post(text, reply_to: @message, **options)
58
74
  end
59
75
 
60
76
  def cache
@@ -2,25 +2,22 @@ module Qbot
2
2
 
3
3
  class Cron < Qbot::Base
4
4
 
5
+ help "cron add <pattern> <message>" => "Add a cron task."
5
6
  on /^cron add (\S+ \S+ \S+ \S+ \S+) (.+)$/ do |msg|
6
- post start(unique_id, msg[1], msg[2])
7
+ post start(unique_id, msg.captures[1], msg.captures[2])
7
8
  end
8
9
 
10
+ help "cron del <cron-id>" => "Remove a cron task."
9
11
  on /^cron del (\d+)/ do |msg|
10
- post stop(msg[1].to_i)
12
+ post stop(msg.captures[1].to_i)
11
13
  end
12
14
 
15
+ help "cron list" => "List all cron tasks."
13
16
  on /^cron list\b/ do |msg|
14
17
  post list_all
15
18
  end
16
19
 
17
- usage <<~EOL
18
- Usage:
19
- `cron add <pattern> <message>` - add a cron task.
20
- `cron del <cron-id>` - remove a cron task.
21
- `cron list` - list all cron tasks.
22
- EOL
23
-
20
+ private
24
21
  def self.timers
25
22
  @timers ||= {}
26
23
  end
@@ -29,26 +26,25 @@ module Qbot
29
26
  schedule(id, cron, text) rescue return
30
27
 
31
28
  cache[id] = [cron, text]
32
- "ADD " + dump_line(id, cron, text)
29
+ "ADD #{format(id, cron, text)}"
33
30
  end
34
31
 
35
32
  def stop(id)
36
33
  Cron.timers[id].cancel if Cron.timers[id]
37
34
  cron, text = cache[id]
38
- return unless cron && text
39
35
 
40
- cache[id] = nil
41
- "DEL " + dump_line(id, cron, text)
36
+ cache[id] = nil
37
+ "DEL #{format(id, cron, text)}"
42
38
  end
43
39
 
44
40
  def list_all
45
41
  resp = StringIO.new
46
42
  cache.each do |id, (cron, text)|
47
43
  next unless cron && text
48
- resp.puts dump_line(id, cron, text)
44
+ resp.puts format(id, cron, text)
49
45
  end
50
46
 
51
- resp.string
47
+ resp.string.chomp
52
48
  end
53
49
 
54
50
  private
@@ -63,7 +59,7 @@ module Qbot
63
59
  end
64
60
  end
65
61
 
66
- def dump_line(id, cron, text)
62
+ def format(id, cron, text)
67
63
  "#{id.to_s.rjust(3, '0')}: #{cron} #{text}"
68
64
  end
69
65
 
@@ -2,15 +2,11 @@ module Qbot
2
2
 
3
3
  class Echo < Qbot::Base
4
4
 
5
+ help "echo <text>" => "Echo back the given <text>."
5
6
  on /^echo( .+)$/ do |msg|
6
- post msg[1].strip
7
+ post msg.captures[1].strip
7
8
  end
8
9
 
9
- usage <<~EOL
10
- Usage:
11
- `echo <text>` - Reflect the given <text>.
12
- EOL
13
-
14
10
  end
15
11
 
16
12
  end
@@ -0,0 +1,18 @@
1
+ require 'stringio'
2
+
3
+ module Qbot
4
+
5
+ class Help < Qbot::Base
6
+
7
+ on /^help\b/i do |msg|
8
+ usage = Qbot.app.help_text
9
+
10
+ text = StringIO.new
11
+ text.puts 'Usage:'
12
+ text.puts usage.map { |key, val| "`#{key}` - #{usage[key]}" }
13
+ post text.string.chomp
14
+ end
15
+
16
+ end
17
+
18
+ end
@@ -2,8 +2,8 @@ module Qbot
2
2
 
3
3
  class Ping < Qbot::Base
4
4
 
5
- on /^ping\b/i do |msg|
6
- post msg[0].tr('iI', 'oO')
5
+ on /\bping\b/i, global: true do |msg|
6
+ post msg.captures[0].tr('iI', 'oO')
7
7
  end
8
8
 
9
9
  end
@@ -0,0 +1,29 @@
1
+ module Qbot
2
+
3
+ class Message
4
+
5
+ attr_accessor :text
6
+ attr_reader :data, :captures
7
+
8
+ def initialize(data, text = '')
9
+ @data = data
10
+ @text = text
11
+ end
12
+
13
+ def match(regexp, prefix: nil)
14
+ text = @text.dup
15
+ text.sub!(/^#{prefix}/, '') if prefix
16
+ @captures = text.strip.match(regexp)
17
+ end
18
+
19
+ def mention(regexp = nil)
20
+ @mention = text.slice(regexp) if regexp
21
+ @mention
22
+ end
23
+
24
+ def mentioned?
25
+ !!mention
26
+ end
27
+ end
28
+
29
+ end
@@ -9,7 +9,7 @@ module Qbot
9
9
  end
10
10
 
11
11
  def namespace(ns)
12
- @db[ns]
12
+ @db[ns] ||= {}
13
13
  end
14
14
 
15
15
  end
@@ -1,3 +1,3 @@
1
1
  module Qbot
2
- VERSION = "0.1.3"
2
+ VERSION = "0.1.10"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: qbot
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.1.10
5
5
  platform: ruby
6
6
  authors:
7
7
  - haccht
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-09-14 00:00:00.000000000 Z
11
+ date: 2019-05-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -175,7 +175,9 @@ files:
175
175
  - lib/qbot/base.rb
176
176
  - lib/qbot/embed/cron.rb
177
177
  - lib/qbot/embed/echo.rb
178
+ - lib/qbot/embed/help.rb
178
179
  - lib/qbot/embed/ping.rb
180
+ - lib/qbot/message.rb
179
181
  - lib/qbot/storage.rb
180
182
  - lib/qbot/storage/leveldb.rb
181
183
  - lib/qbot/storage/memory.rb
@@ -201,7 +203,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
201
203
  version: '0'
202
204
  requirements: []
203
205
  rubyforge_project:
204
- rubygems_version: 2.6.14.1
206
+ rubygems_version: 2.7.6
205
207
  signing_key:
206
208
  specification_version: 4
207
209
  summary: Tiny chatbot flamework.