beerbot 0.1.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 +7 -0
- data/bin/run-irc.rb +24 -0
- data/lib/BeerBot.rb +20 -0
- data/lib/BeerBot/00.utils/DataFile.rb +56 -0
- data/lib/BeerBot/00.utils/InOut.rb +46 -0
- data/lib/BeerBot/00.utils/More.rb +68 -0
- data/lib/BeerBot/00.utils/paramExpand.rb +77 -0
- data/lib/BeerBot/00.utils/utils.rb +149 -0
- data/lib/BeerBot/00.utils/world/IRCWorld.rb +47 -0
- data/lib/BeerBot/00.utils/world/World.rb +84 -0
- data/lib/BeerBot/01.connect/Connection.rb +23 -0
- data/lib/BeerBot/01.connect/IRCConnection.rb +133 -0
- data/lib/BeerBot/01.protocols/botmsg.rb +59 -0
- data/lib/BeerBot/01.protocols/irc.rb +250 -0
- data/lib/BeerBot/02.bot/bot.rb +234 -0
- data/lib/BeerBot/03.more/BotMsgMore.rb +34 -0
- data/lib/BeerBot/06.dispatchers/irc.rb +160 -0
- data/lib/BeerBot/70.scheduler/scheduler.rb +22 -0
- data/lib/BeerBot/Config.rb +62 -0
- data/lib/RunIRC.rb +153 -0
- metadata +134 -0
@@ -0,0 +1,234 @@
|
|
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
|
+
require_relative '../01.protocols/botmsg.rb'
|
9
|
+
|
10
|
+
module BeerBot
|
11
|
+
|
12
|
+
BotMsg = BeerBot::Protocol::BotMsg
|
13
|
+
|
14
|
+
# Represents a bot module and may contain a reference to the loaded
|
15
|
+
# ruby module or any errors associated with loading it.
|
16
|
+
|
17
|
+
class BotModule < Hash
|
18
|
+
def initialize name,status:false,mod:nil,modname:nil,errors:[]
|
19
|
+
self[:status] = status
|
20
|
+
self[:name] = name # The module name.
|
21
|
+
self[:mod] = mod # The loaded ruby module.
|
22
|
+
self[:modname] = modname
|
23
|
+
self[:errors] = errors
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# Represents a sequence of BotModule instances.
|
28
|
+
|
29
|
+
class Bot < Array
|
30
|
+
|
31
|
+
attr_accessor :module_path,:module_names
|
32
|
+
|
33
|
+
def initialize module_path,module_names
|
34
|
+
super()
|
35
|
+
@module_path = module_path
|
36
|
+
@module_names = module_names
|
37
|
+
self.load!
|
38
|
+
end
|
39
|
+
|
40
|
+
def load!
|
41
|
+
self.reject!{true} unless self.empty? # ick :)
|
42
|
+
Dir.chdir(@module_path) {
|
43
|
+
@module_names.each {|name|
|
44
|
+
initfile = "#{name}/init.rb"
|
45
|
+
modname = "::BeerBot::Modules::#{name}"
|
46
|
+
mod = nil
|
47
|
+
err = [
|
48
|
+
[:nodir,!File.directory?(name)],
|
49
|
+
[:badname,name !~ /^[A-Z]/ ],
|
50
|
+
[:noinit,!File.exists?(initfile)],
|
51
|
+
].select{|e| e[1]}
|
52
|
+
ok = (err.size == 0)
|
53
|
+
|
54
|
+
if ok then
|
55
|
+
puts "loading #{initfile}..."
|
56
|
+
load(initfile)
|
57
|
+
mod = Object.const_get(modname)
|
58
|
+
if mod.respond_to?(:instance) then
|
59
|
+
mod = mod.instance
|
60
|
+
end
|
61
|
+
else
|
62
|
+
p [initfile,err]
|
63
|
+
raise "Can't load (some) modules."
|
64
|
+
end
|
65
|
+
|
66
|
+
bm = BotModule.new(
|
67
|
+
name,status:ok,mod:mod,
|
68
|
+
modname:modname,errors:err
|
69
|
+
)
|
70
|
+
self.push(bm)
|
71
|
+
}
|
72
|
+
}
|
73
|
+
end
|
74
|
+
|
75
|
+
def has_errors
|
76
|
+
if self.find{|bot_module| !bot_module[:status]} then
|
77
|
+
true
|
78
|
+
else
|
79
|
+
false
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# Return list of valid (loaded) modules.
|
84
|
+
|
85
|
+
def valid_modules
|
86
|
+
self.select{|bot_module| bot_module[:status]}
|
87
|
+
end
|
88
|
+
|
89
|
+
# Call :meth on valid (loaded) modules and maybe accumulate result
|
90
|
+
# or return first valid response...
|
91
|
+
#
|
92
|
+
# Returns array-based botmsg. The array could be empty, which
|
93
|
+
# means nothing was returned (or we couldn't interpret the output
|
94
|
+
# of the bot modules).
|
95
|
+
#
|
96
|
+
# We expect each bot_module to return nil or a botmsg (Hash, Array
|
97
|
+
# or Proc that returns the first two).
|
98
|
+
#
|
99
|
+
# At the moment, we use inject and break on first valid
|
100
|
+
# response...
|
101
|
+
|
102
|
+
def run meth,*args,**kargs
|
103
|
+
self.valid_modules.inject([]) {|arr,bot_module|
|
104
|
+
name,mod = bot_module.values_at(:name,:mod)
|
105
|
+
next arr unless mod.respond_to?(meth)
|
106
|
+
botmsg = mod.send(meth,*args,**kargs)
|
107
|
+
if botmsg then
|
108
|
+
#arr << [name,botmsg]
|
109
|
+
arr += BotMsg.to_a(botmsg)
|
110
|
+
break arr # TODO allow multi-module response?
|
111
|
+
else
|
112
|
+
arr
|
113
|
+
end
|
114
|
+
}
|
115
|
+
end
|
116
|
+
|
117
|
+
def cmd msg,from:nil,to:nil,me:false,world:nil
|
118
|
+
if @cmd then
|
119
|
+
botmsg = @cmd.call(msg,from:from,to:to,world:world,me:me)
|
120
|
+
return botmsg
|
121
|
+
else
|
122
|
+
self.run(:cmd,msg,from:from,to:to,world:world,me:me)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def hear msg,from:nil,to:nil,me:false,world:nil
|
127
|
+
if @hear then
|
128
|
+
botmsg = @hear.call(msg,from:from,to:to,me:me,world:world)
|
129
|
+
return botmsg
|
130
|
+
else
|
131
|
+
self.run(:hear,msg,from:from,to:to,me:me,world:world)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def help arr,from:nil,to:nil,world:nil,me:false
|
136
|
+
m = []
|
137
|
+
modname,*topics = arr
|
138
|
+
|
139
|
+
# "help"
|
140
|
+
|
141
|
+
if arr.empty? then
|
142
|
+
helplist = self.valid_modules.select {|bot_module|
|
143
|
+
bot_module[:mod].respond_to?(:help)
|
144
|
+
}.map{|bot_module|
|
145
|
+
bot_module[:name]
|
146
|
+
}
|
147
|
+
reply = [
|
148
|
+
{
|
149
|
+
to:from,
|
150
|
+
msg:"To issue commands to the bot over a channel, you need to start with a command prefix like ','."
|
151
|
+
},
|
152
|
+
{
|
153
|
+
to:from,
|
154
|
+
msg:"Modules (type: help <module-name>): "+helplist.join('; ')
|
155
|
+
}
|
156
|
+
]
|
157
|
+
|
158
|
+
# "help modname [subtopic [... [... etc]]]"
|
159
|
+
|
160
|
+
else
|
161
|
+
|
162
|
+
bot_module = self.valid_modules.find{|bot_module|
|
163
|
+
bot_module[:name]==modname
|
164
|
+
}
|
165
|
+
|
166
|
+
# Can't find module...
|
167
|
+
if bot_module.nil? then
|
168
|
+
reply = [to:to,msg:"Don't know this topic #{from}"]
|
169
|
+
|
170
|
+
# Can find module...
|
171
|
+
else
|
172
|
+
|
173
|
+
mod = bot_module[:mod]
|
174
|
+
reply = []
|
175
|
+
|
176
|
+
|
177
|
+
# Module has help...
|
178
|
+
if mod.respond_to?(:help) then
|
179
|
+
arr = mod.help(topics)
|
180
|
+
|
181
|
+
if !arr || arr.empty? then
|
182
|
+
reply += [to:from,msg:"hmmm, the module didn't say anything..."]
|
183
|
+
else
|
184
|
+
if topics.empty? then
|
185
|
+
reply += [to:from,msg:"Note: modules should list topics which you can access like: help #{modname} <topic>"]
|
186
|
+
end
|
187
|
+
reply += arr.map{|a| {to:from,msg:a}}
|
188
|
+
end
|
189
|
+
|
190
|
+
# Module doesn't have help...
|
191
|
+
else
|
192
|
+
reply += [to:from,msg:"type: #{modname} doesn't seem to have any help"]
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
end
|
197
|
+
|
198
|
+
if not me then
|
199
|
+
reply += [to:to,msg:"pm'ing you #{from}"]
|
200
|
+
end
|
201
|
+
return reply
|
202
|
+
end
|
203
|
+
|
204
|
+
|
205
|
+
# Override normal cmd processing with Proc.
|
206
|
+
#
|
207
|
+
# You might want to do this to temporarily stop normal bot command
|
208
|
+
# behaviour in order for the bot to go into some sort of exclusive
|
209
|
+
# mode.
|
210
|
+
#
|
211
|
+
# To disable normal response behaviour do:
|
212
|
+
# bot.set_cmd {|msg,**kargs| nil }
|
213
|
+
# bot.set_hear {|msg,**kargs| nil }
|
214
|
+
#
|
215
|
+
#
|
216
|
+
# To unset,
|
217
|
+
# bot.set_cmd
|
218
|
+
# bot.set_hear
|
219
|
+
|
220
|
+
def set_cmd &block
|
221
|
+
@cmd = block
|
222
|
+
end
|
223
|
+
|
224
|
+
# Override normal hear-processing with Proc.
|
225
|
+
#
|
226
|
+
# See set_cmd.
|
227
|
+
|
228
|
+
def set_hear &block
|
229
|
+
@hear = block
|
230
|
+
end
|
231
|
+
|
232
|
+
end
|
233
|
+
|
234
|
+
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
|
+
require_relative '../00.utils/More'
|
9
|
+
|
10
|
+
module BeerBot
|
11
|
+
|
12
|
+
# More-based system (see More).
|
13
|
+
#
|
14
|
+
# #filter expects a botmsg and returns an array botmsg.
|
15
|
+
|
16
|
+
class BotMsgMore < ::BeerBot::Utils::More
|
17
|
+
def filter botmsg
|
18
|
+
return nil unless botmsg
|
19
|
+
replies = []
|
20
|
+
by_to = Hash.new{|h,k| h[k]=[]}
|
21
|
+
arr = BeerBot::Protocol::BotMsg.to_a(botmsg)
|
22
|
+
|
23
|
+
arr.inject(by_to){|h,v| h[v[:to]].push(v); h}
|
24
|
+
by_to.each_pair{|to,a|
|
25
|
+
replies += super(a,to)
|
26
|
+
if replies.size < a.size then
|
27
|
+
replies += [msg:"Type: more",to:to]
|
28
|
+
end
|
29
|
+
}
|
30
|
+
return replies
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
@@ -0,0 +1,160 @@
|
|
1
|
+
# The files in this directory are part of BeerBot, a a ruby irc bot library.
|
2
|
+
# Copyright (C) 2013,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
|
+
require_relative '../00.utils/utils'
|
9
|
+
require_relative '../01.protocols/irc'
|
10
|
+
require_relative '../03.more/BotMsgMore'
|
11
|
+
|
12
|
+
module BeerBot
|
13
|
+
|
14
|
+
# Dispatchers receive incoming messages from a connection and
|
15
|
+
# decide what to do with them.
|
16
|
+
|
17
|
+
module Dispatchers
|
18
|
+
|
19
|
+
# This dispatcher calls BeerBot::Protocol::IRC.parse on what it
|
20
|
+
# receives and then processes the response.
|
21
|
+
#
|
22
|
+
# There are several ways to specify how the result of parse is
|
23
|
+
# processed. You can:
|
24
|
+
# 1) pass in a block at instantiation time
|
25
|
+
# 2) set a block using #set_receive
|
26
|
+
# 3) subclass this class and write your own #receive
|
27
|
+
|
28
|
+
class IRCDispatcher
|
29
|
+
|
30
|
+
IRC = BeerBot::Protocol::IRC
|
31
|
+
Utils = BeerBot::Utils
|
32
|
+
BotMsgMore = BeerBot::BotMsgMore
|
33
|
+
|
34
|
+
attr_accessor :bot,:nick,:prefix,:world,:more
|
35
|
+
|
36
|
+
def initialize bot,nick,prefix:',',world:nil,&block
|
37
|
+
@bot = bot
|
38
|
+
@more = BotMsgMore.new
|
39
|
+
|
40
|
+
@nick = nick
|
41
|
+
@get_nick_cmd = Utils.make_prefix_parser(nick)
|
42
|
+
@nickrx = Regexp.new("^#{nick}$",'i')
|
43
|
+
|
44
|
+
@prefix = prefix
|
45
|
+
@get_prefix_cmd = Utils.make_prefix_parser(prefix)
|
46
|
+
|
47
|
+
@world = world
|
48
|
+
|
49
|
+
if block_given? then
|
50
|
+
@block = block
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# Set a receiving proc.
|
55
|
+
#
|
56
|
+
# If no block given, @block is set to nil and #receive is used.
|
57
|
+
|
58
|
+
def set_receive &block
|
59
|
+
if block_given? then
|
60
|
+
@block = block
|
61
|
+
else
|
62
|
+
@block = nil
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def parse irc_str
|
67
|
+
IRC.parse(irc_str)
|
68
|
+
end
|
69
|
+
|
70
|
+
def receive irc_str
|
71
|
+
|
72
|
+
event,*args = self.parse(irc_str)
|
73
|
+
|
74
|
+
if @block then
|
75
|
+
return self.instance_exec(event,*args,&@block)
|
76
|
+
end
|
77
|
+
|
78
|
+
# Otherwise, here is the default behaviour...
|
79
|
+
|
80
|
+
case event
|
81
|
+
when :unknown
|
82
|
+
#puts "protocol/irc :unknown"
|
83
|
+
when :default
|
84
|
+
#puts "protocol/irc :default"
|
85
|
+
when :nick
|
86
|
+
old,nick = args
|
87
|
+
@world.nick(old,nick) if @world
|
88
|
+
when :part
|
89
|
+
nick,channel = args
|
90
|
+
@world.part(nick,channel) if @world
|
91
|
+
when :join
|
92
|
+
nick,channel = args
|
93
|
+
@world.join(nick,channel) if @world
|
94
|
+
when :chanlist
|
95
|
+
#p "[dispatcher] :chanlist"
|
96
|
+
channel,users = args
|
97
|
+
if @world then
|
98
|
+
users.each {|user|
|
99
|
+
@world.join(user,channel)
|
100
|
+
}
|
101
|
+
end
|
102
|
+
|
103
|
+
when :privmsg
|
104
|
+
|
105
|
+
from,to,msg = args
|
106
|
+
|
107
|
+
# Somebody messaging us privately:
|
108
|
+
me = (@nickrx === to)
|
109
|
+
|
110
|
+
# Somebody talking to us on channel: "Beerbot: ..."
|
111
|
+
cmd = @get_nick_cmd.call(msg)
|
112
|
+
if not cmd then
|
113
|
+
# Somebody commanding us on channel: ",command ..."
|
114
|
+
cmd = @get_prefix_cmd.call(msg)
|
115
|
+
end
|
116
|
+
|
117
|
+
if cmd then
|
118
|
+
case cmd
|
119
|
+
# dispatch more-filtering...
|
120
|
+
when /^more!*|moar!*$/i
|
121
|
+
replies = @more.more(to)
|
122
|
+
# dispatch help...
|
123
|
+
when /^\s*help(?:\s+(.*))?$/
|
124
|
+
if $1.nil? then
|
125
|
+
args = []
|
126
|
+
else
|
127
|
+
args = $1.strip.split(/\s+/)
|
128
|
+
end
|
129
|
+
replies = @bot.help(args,from:from,to:to,me:me,world:world)
|
130
|
+
# dispatch cmd...
|
131
|
+
else
|
132
|
+
replies = @bot.cmd(cmd,from:from,to:to,me:me,world:world)
|
133
|
+
end
|
134
|
+
else
|
135
|
+
# We're just hearing something on a channel...
|
136
|
+
replies = @bot.hear(msg,from:from,to:to,me:me,world:world)
|
137
|
+
end
|
138
|
+
|
139
|
+
else
|
140
|
+
puts "protocol/irc unrecognised event: '#{event}'"
|
141
|
+
end
|
142
|
+
|
143
|
+
case replies
|
144
|
+
when String # assume irc string
|
145
|
+
replies
|
146
|
+
when Hash,Array,Proc
|
147
|
+
# more-filter the reply...
|
148
|
+
replies = @more.filter(replies)
|
149
|
+
IRC.to_irc(replies)
|
150
|
+
else
|
151
|
+
nil
|
152
|
+
end
|
153
|
+
|
154
|
+
end
|
155
|
+
|
156
|
+
end
|
157
|
+
|
158
|
+
end
|
159
|
+
|
160
|
+
end
|
@@ -0,0 +1,22 @@
|
|
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
|
+
module BeerBot
|
9
|
+
module Scheduler
|
10
|
+
def self.instance timezone=nil
|
11
|
+
@@instance ||= CronR::Cron.new
|
12
|
+
if timezone then
|
13
|
+
@@instance.time {
|
14
|
+
Time.use_zone(timezone) {
|
15
|
+
Time.zone.now
|
16
|
+
}
|
17
|
+
}
|
18
|
+
end
|
19
|
+
@@instance
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|