beerbot 0.1.5 → 0.2.0.pre.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6483fc83799ee9b532e10c4f96a1c7d1b7eb0a29
4
- data.tar.gz: 9564a54aa28c251d87b09f72222f63284b7f5d35
3
+ metadata.gz: 87f292c599a9b6b747cf3946ba5693c3eac5e6fc
4
+ data.tar.gz: 0a3a0612f3cedbb8c52508dba7cf67af4f5cae6f
5
5
  SHA512:
6
- metadata.gz: d857137c3322faabef1fc1da6a9155c0acd4c185a18de60cb87d57404ba67896b48137bd72de91ea2d157224da0cbefce57aa880465ada41f5c59ce30ddb24ef
7
- data.tar.gz: 452fca254f50a355a141dab0c4ea7d758c85debde316eb8023e257424913e52f4f85b3e7e12afe06e26756169db693961285eb40e05076ae300b351506961780
6
+ metadata.gz: e3d16c56405388a6183dc99057dc1953c18f3df825e295ca07f4c851ddff2d3103449f89d8e56ad9a12b56fa7c3f67a0f3d53cb1ce90f088e29e5fa74107eb66
7
+ data.tar.gz: 4b629d26bd44bd7a0a9e1590f07ed74f41323b3575a9415db57518a28d7c8c24d9f91a7f1163ec83bb5e7ec5a31a4715b16f23b7f172c65896c152281040cb6f
@@ -7,6 +7,11 @@
7
7
  # enclosed with this project in the file LICENSE. If not
8
8
  # see <http://www.gnu.org/licenses/>.
9
9
 
10
+ # If you're running this WITHOUT the gem, you probably want to do
11
+ # something like this (in beerbot's root directory):
12
+ #
13
+ # ruby -Ilib bin/beerbot-run-irb.rb path/to/conf.json
14
+
10
15
  raise "Needs ruby 2" if /^1/===RUBY_VERSION
11
16
  require_relative '../lib/RunIRC'
12
17
 
@@ -17,8 +22,9 @@ if ARGV.size == 0 then
17
22
  end
18
23
 
19
24
  conffile = ARGV[0]
20
- BeerBot::Config.load JSON.load(File.read(conffile))
21
- BeerBot::Config.validate!
25
+ config = BeerBot::Config.new
26
+ config.load JSON.load(File.read(conffile))
27
+ config.validate!
22
28
 
23
- $runirc = BeerBot::RunIRC.new BeerBot::Config
29
+ $runirc = BeerBot::RunIRC.new(config)
24
30
  $runirc.start
@@ -9,7 +9,7 @@ require 'set'
9
9
  require 'rubygems'
10
10
  require 'pry'
11
11
  require 'json'
12
- require_relative 'BeerBot'
12
+ require_relative 'beerbot'
13
13
 
14
14
  # Run the irc bot.
15
15
  #
@@ -23,15 +23,15 @@ module BeerBot; end
23
23
  class BeerBot::RunIRC
24
24
 
25
25
  Utils = BeerBot::Utils
26
- IRCWorld = BeerBot::Utils::IRCWorld
27
26
  InOut = BeerBot::Utils::InOut
28
27
  IRCConnection = BeerBot::IRCConnection
29
28
  IRC = BeerBot::Protocol::IRC
30
29
  Bot = BeerBot::Bot
30
+ BotMsg = BeerBot::BotMsg
31
31
  Dispatcher = BeerBot::Dispatchers::Dispatcher
32
32
  Scheduler = BeerBot::Scheduler
33
33
 
34
- attr_accessor :config,:bot,:scheduler,:dispatcher,:world,:conn,:postq,:parse,:more
34
+ attr_accessor :config,:bot,:scheduler,:dispatcher,:conn,:postq,:parse,:more
35
35
 
36
36
  # Initialize all parts of the system here.
37
37
  #
@@ -41,27 +41,27 @@ class BeerBot::RunIRC
41
41
 
42
42
  def initialize config
43
43
 
44
+ @echo = true
44
45
  @path = File.expand_path(File.dirname(__FILE__)+'/..')
45
46
  @module_path = config['moduledir']
46
47
  @config = config
47
48
 
48
49
  # Create the bot.
49
- @bot = Bot.new(@module_path,config['modules'])
50
-
51
- # Create a world associated with this irc connection.
52
- # (lists channels and users we know about)
53
- @world = IRCWorld.new(config['nick'])
50
+ @bot = Bot.new
51
+ @bot.load!(config['modules'],@module_path)
52
+ config.bot = @bot
54
53
 
55
54
  # Dispatcher which receives messages and interacts with the bot.
56
55
  @dispatcher = Dispatcher.new(
57
56
  @bot,
58
57
  config['nick'],
59
58
  prefix:config['cmd_prefix'],
60
- world:@world
59
+ config:config
61
60
  )
62
61
 
63
62
  # Set up scheduler (this doesn't start it yet)...
64
63
  @scheduler = Scheduler.instance(config['timezone'])
64
+ config.scheduler = @scheduler
65
65
 
66
66
  # Create but don't open the irc connection.
67
67
  @conn = IRCConnection.new(
@@ -85,12 +85,32 @@ class BeerBot::RunIRC
85
85
  # which also need to be dispatched.
86
86
 
87
87
  @scheduler_thread = InOut.new(inq:@scheduler.queue,outq:@conn.writeq) {|cron_job|
88
- puts "<< scheduler #{cron_job.inspect}"
89
- puts "<< scheduler #{@scheduler.time}"
88
+ puts "<< scheduler #{cron_job.inspect}" if @echo
89
+ puts "<< scheduler #{@scheduler.time}" if @echo
90
90
  IRC.to_irc(cron_job.run)
91
91
  }
92
92
  @scheduler_thread.start!
93
93
 
94
+ # Active messaging queue.
95
+ #
96
+ # 'config' will be injected into bot modules.
97
+ # config.out should be a queue that we can dequeue.
98
+
99
+ @active_thread = InOut.new(inq:@config.out,outq:@conn.writeq) {|replies|
100
+ puts "<< active #{replies}" if @echo
101
+ # TODO: almost identical logic in the dispatcher class (in
102
+ # @dispatcher_thread).
103
+ case replies
104
+ when String # assume protocol string eg irc
105
+ replies
106
+ when Hash,Array,Proc
107
+ IRC.to_irc(BotMsg.to_a(replies))
108
+ else
109
+ []
110
+ end
111
+ }
112
+ @active_thread.start!
113
+
94
114
  # Set up a repl in a separate thread.
95
115
  #
96
116
  # In pry, you can then do:
@@ -102,6 +122,9 @@ class BeerBot::RunIRC
102
122
  binding.pry
103
123
  }
104
124
 
125
+ @bot.init(@config)
126
+ @bot.update_config(@config)
127
+
105
128
  # Do stuff once we've identified with the irc server...
106
129
  #
107
130
  # Join channels.
@@ -118,6 +141,15 @@ class BeerBot::RunIRC
118
141
  }
119
142
  end
120
143
 
144
+ # Toggle whether inputs and outputs show on the repl screen.
145
+ #
146
+ # Call this from the pry repl.
147
+
148
+ def echo
149
+ @echo = !@echo
150
+ @conn.echo = @echo
151
+ end
152
+
121
153
  # Start the connection.
122
154
 
123
155
  def start
@@ -142,14 +174,10 @@ class BeerBot::RunIRC
142
174
  @conn.writeq.enq(IRC.join(chan))
143
175
  end
144
176
 
145
- # Reload @bot using module list 'modules'.
146
- #
147
- # You could use
177
+ # Convenience method to leave a channel.
148
178
 
149
- def reload! modules=[]
150
- @config['modules'] = modules
151
- @bot = Bot.new(@module_path,modules)
152
- @dispatcher.bot = @bot
179
+ def leave chan
180
+ @conn.writeq.enq(IRC.leave(chan))
153
181
  end
154
182
 
155
183
  end
@@ -0,0 +1,17 @@
1
+ require 'CronR' # For scheduler
2
+
3
+ require_relative 'beerbot/00.utils/utils'
4
+ require_relative 'beerbot/00.utils/InOut'
5
+ require_relative 'beerbot/01.connect/IRCConnection'
6
+ require_relative 'beerbot/01.bot/botmsg'
7
+ require_relative 'beerbot/01.bot/BotModule'
8
+ require_relative 'beerbot/01.bot/Bot'
9
+ require_relative 'beerbot/02.protocols/irc'
10
+ require_relative 'beerbot/06.dispatchers/dispatcher'
11
+ require_relative 'beerbot/70.scheduler/scheduler'
12
+ require_relative 'beerbot/Config'
13
+
14
+ module BeerBot
15
+ module Modules
16
+ end
17
+ end
@@ -0,0 +1,34 @@
1
+ # The files in this directory are part of BeerBot, a a ruby irc bot library.
2
+ # Copyright (C) 2014 Daniel Bush
3
+ # This program is distributed under the terms of the GNU
4
+ # General Public License. A copy of the license should be
5
+ # enclosed with this project in the file LICENSE. If not
6
+ # see <http://www.gnu.org/licenses/>.
7
+
8
+
9
+ module BeerBot
10
+
11
+ module Utils
12
+
13
+ # Return a parser that takes string msg and extracts a specified
14
+ # prefix at beginning.
15
+ #
16
+ # The prefix might be a nick or a command prefix.
17
+ #
18
+ # Use this to get commands issued to the bot through a channel.
19
+ #
20
+ # TODO: make sure this returns msg without the prefix, or nil
21
+ # otherwise.
22
+
23
+ def self.make_prefix_parser prefix
24
+ rx = Regexp.new("^#{prefix}\\W?(.*)",'i')
25
+ lambda {|msg|
26
+ if m = rx.match(msg) then
27
+ m[1].strip
28
+ end
29
+ }
30
+ end
31
+
32
+ end
33
+
34
+ end
@@ -16,17 +16,41 @@ module BeerBot
16
16
 
17
17
  attr_accessor :module_path,:module_names
18
18
 
19
- def initialize module_path,module_names
19
+ def initialize
20
20
  super()
21
- @module_path = module_path
22
- @module_names = module_names
23
- self.load!
24
21
  end
25
22
 
26
- def load!
23
+ # Call all init methods on bot modules that have them.
24
+ #
25
+ # Should only be called once.
26
+
27
+ def init config
28
+ self.valid_modules.each {|botmodule|
29
+ if botmodule[:mod].respond_to?(:init) then
30
+ botmodule[:mod].init(config)
31
+ end
32
+ }
33
+ end
34
+
35
+ # Call #config on all valid bot modules.
36
+
37
+ def update_config config
38
+ self.valid_modules.each {|botmodule|
39
+ if botmodule[:mod].respond_to?(:config) then
40
+ botmodule[:mod].config(config)
41
+ end
42
+ }
43
+ end
44
+
45
+ # Purge existing modules from this array and load modules with
46
+ # names in module_names in module_path on disk into memory.
47
+
48
+ def load! module_names,module_path
49
+ @module_path = module_path
50
+ @module_names = module_names
27
51
  self.reject!{true} unless self.empty? # ick :)
28
- Dir.chdir(@module_path) {
29
- @module_names.each {|name|
52
+ Dir.chdir(module_path) {
53
+ module_names.each {|name|
30
54
  initfile = "#{name}/init.rb"
31
55
  modname = "::BeerBot::Modules::#{name}"
32
56
  mod = nil
@@ -88,12 +112,16 @@ module BeerBot
88
112
  def run meth,*args,**kargs
89
113
  self.valid_modules.inject([]) {|arr,bot_module|
90
114
  name,mod = bot_module.values_at(:name,:mod)
91
- next arr unless mod.respond_to?(meth)
92
- botmsg = mod.send(meth,*args,**kargs)
115
+ unless mod.respond_to?(meth) then
116
+ next arr
117
+ end
118
+ reply = mod.send(meth,*args,**kargs)
119
+ suppress,botmsg = BotMsg.to_reply_format(reply)
93
120
  if botmsg then
94
- #arr << [name,botmsg]
95
- arr += BotMsg.to_a(botmsg)
96
- break arr # TODO allow multi-module response?
121
+ arr += botmsg
122
+ end
123
+ if suppress then
124
+ break arr
97
125
  else
98
126
  arr
99
127
  end
@@ -102,26 +130,26 @@ module BeerBot
102
130
 
103
131
  # Process messages addressed directly to the bot.
104
132
 
105
- def cmd msg,from:nil,to:nil,me:false,world:nil
133
+ def cmd msg,from:nil,to:nil,me:false,config:nil
106
134
  if @cmd then
107
- @cmd.call(msg,from:from,to:to,world:world,me:me)
135
+ @cmd.call(msg,from:from,to:to,config:config,me:me)
108
136
  else
109
- self.run(:cmd,msg,from:from,to:to,world:world,me:me)
137
+ self.run(:cmd,msg,from:from,to:to,config:config,me:me)
110
138
  end
111
139
  end
112
140
 
113
141
  # Process messages the bot overhears.
114
142
 
115
- def hear msg,from:nil,to:nil,me:false,world:nil
143
+ def hear msg,from:nil,to:nil,me:false,config:nil
116
144
  if @hear then
117
- @hear.call(msg,from:from,to:to,me:me,world:world)
145
+ @hear.call(msg,from:from,to:to,me:me,config:config)
118
146
  else
119
- self.run(:hear,msg,from:from,to:to,me:me,world:world)
147
+ self.run(:hear,msg,from:from,to:to,me:me,config:config)
120
148
  end
121
149
  end
122
150
 
123
- def action action,from:nil,to:nil,me:false,world:nil
124
- self.run(:action,action,from:from,to:to,me:me,world:world)
151
+ def action action,from:nil,to:nil,me:false,config:nil
152
+ self.run(:action,action,from:from,to:to,me:me,config:config)
125
153
  end
126
154
 
127
155
  # Handle events other than being messaged.
@@ -135,7 +163,7 @@ module BeerBot
135
163
  self.run(:event,event,**kargs)
136
164
  end
137
165
 
138
- def help arr,from:nil,to:nil,world:nil,me:false
166
+ def help arr,from:nil,to:nil,config:nil,me:false
139
167
  m = []
140
168
  modname,*topics = arr
141
169
 
@@ -99,6 +99,57 @@ module BeerBot
99
99
  botmsg
100
100
  end
101
101
  end
102
+
103
+ # Transform all replies from bot modules to ARRAY FORMAT.
104
+ #
105
+ # A module may reply in either format. But we convert it here. The
106
+ # single return format is the simplest, which is why we have 2
107
+ # formats.
108
+ #
109
+ # ARRAY FORMAT
110
+ #
111
+ # [<bool>,<reply>]
112
+ #
113
+ # where bool = true => "use <reply> and keep going"
114
+ # = false => "use <reply> but stop here"
115
+ # where reply = whatever the bot returns.
116
+ # NOTE: any other type of array will assumed to be in
117
+ # single return form...
118
+ #
119
+ # SINGLE RETURN FORMAT
120
+ #
121
+ # This form assumes you return one thing, either nil/false or a
122
+ # botmsg. Returning nil/false won't suppress subsequent modules
123
+ # from being evaluated.
124
+ #
125
+ # Note, that "whatever the bot returns" will need to be one of the
126
+ # valid forms of a botmsg for it to get sent out.
127
+
128
+ def self.to_reply_format thing
129
+ case thing
130
+ when Array
131
+ bool,botmsg = thing
132
+ case bool
133
+ when TrueClass,FalseClass
134
+ # Assume array format:
135
+ [bool,self.to_a(botmsg)]
136
+ else
137
+ # Assume single return format...
138
+ # Array of any sort is truthy, so suppress.
139
+ [true,self.to_a(thing)]
140
+ end
141
+ else
142
+ # Asume single return format...
143
+ #
144
+ # Look at truthiness of thing to determine whether to suppress
145
+ # further responses (true) or continue (false).
146
+ if thing then
147
+ [true,self.to_a(thing)]
148
+ else
149
+ [false,self.to_a(thing)]
150
+ end
151
+ end
152
+ end
102
153
 
103
154
  end
104
155
 
@@ -21,8 +21,10 @@ module BeerBot
21
21
  # Queue containing received messages from the server.
22
22
  attr_accessor :queue,:writeq,:readyq
23
23
  attr_accessor :connection,:server,:port,:nick,:thread
24
+ attr_accessor :echo
24
25
 
25
26
  def initialize server:nil,port:6667,nick:'beerbot'
27
+ @echo = true
26
28
  @server = server
27
29
  @port = port
28
30
  @nick = nick
@@ -33,6 +35,7 @@ module BeerBot
33
35
  # to the irc server isn't ready yet:
34
36
  @readyq = Queue.new
35
37
  @ready = false
38
+ @ready_blocks = []
36
39
  @ready_mutex = Mutex.new
37
40
  @write_mutex = Mutex.new
38
41
 
@@ -44,9 +47,13 @@ module BeerBot
44
47
 
45
48
  def ready!
46
49
  @ready_mutex.synchronize {
50
+ unless @ready_blocks.empty? then
51
+ @ready_blocks.each{|b| @readyq.enq(b)}
52
+ end
47
53
  @ready = true
48
54
  while @readyq.size > 0
49
55
  block = @readyq.deq
56
+ @ready_blocks.push(block)
50
57
  block.call
51
58
  end
52
59
  }
@@ -70,30 +77,43 @@ module BeerBot
70
77
  # It should respond to whatever is called on @connection
71
78
  # eg open,gets,write.
72
79
  # Use for testing this class.
80
+ #
81
+ # May throw errors.
82
+ # - @connection.eof? can throw things like ECONNRESET etc
73
83
 
74
84
  def open connection=nil
75
- if connection then
76
- @connection = connection
77
- @connection.open(self.server, self.port)
78
- else
79
- @connection = TCPSocket.open(self.server, self.port)
80
- end
81
- self.write("USER #{@nick} #{@nick} #{@nick} :#{@nick}")
82
- self.write("NICK #{@nick}")
83
85
  @thread = Thread.new {
84
- while not @connection.eof? do
85
- str = @connection.gets()
86
- puts "<< #{str}"
87
- case str
88
- when /^PING (.*)$/
89
- self.write "PONG #{$1}"
90
- when / 001 / # ready
91
- self.ready!
92
- else
93
- self.queue.enq(str)
86
+ loop do
87
+ begin
88
+ if connection then
89
+ @connection = connection
90
+ @connection.open(self.server, self.port)
91
+ else
92
+ @connection = TCPSocket.open(self.server, self.port)
93
+ end
94
+ self.write("USER #{@nick} #{@nick} #{@nick} :#{@nick}")
95
+ self.write("NICK #{@nick}")
96
+ while not @connection.eof? do
97
+ str = @connection.gets()
98
+ puts "<< #{str}" if @echo
99
+ case str
100
+ when /^PING (.*)$/
101
+ self.write "PONG #{$1}"
102
+ when / 001 / # ready
103
+ self.ready!
104
+ else
105
+ self.queue.enq(str)
106
+ end
107
+ end
108
+ rescue => e
109
+ puts "Connection whoops: #{e}"
94
110
  end
111
+ @ready = false
112
+ puts "Sleeping #{10} then try again..."
113
+ sleep 10
95
114
  end
96
115
  }
116
+
97
117
  @write_thread = Thread.new {
98
118
  loop do
99
119
  thing = @writeq.deq
@@ -115,7 +135,7 @@ module BeerBot
115
135
  case message
116
136
  when String
117
137
  message = message.chomp + "\r\n"
118
- puts ">> #{message}"
138
+ puts ">> #{message}" if @echo
119
139
  @write_mutex.synchronize {
120
140
  @connection.print(message)
121
141
  }