beerbot 0.1.5 → 0.2.0.pre.1

Sign up to get free protection for your applications and to get access to all the features.
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
  }