rbot 0.9.9
Sign up to get free protection for your applications and to get access to all the features.
- data/AUTHORS +16 -0
- data/COPYING +21 -0
- data/ChangeLog +418 -0
- data/INSTALL +8 -0
- data/README +44 -0
- data/REQUIREMENTS +34 -0
- data/TODO +5 -0
- data/Usage_en.txt +129 -0
- data/bin/rbot +81 -0
- data/data/rbot/contrib/plugins/figlet.rb +20 -0
- data/data/rbot/contrib/plugins/ri.rb +83 -0
- data/data/rbot/contrib/plugins/stats.rb +232 -0
- data/data/rbot/contrib/plugins/vandale.rb +49 -0
- data/data/rbot/languages/dutch.lang +73 -0
- data/data/rbot/languages/english.lang +75 -0
- data/data/rbot/languages/french.lang +39 -0
- data/data/rbot/languages/german.lang +67 -0
- data/data/rbot/plugins/autoop.rb +68 -0
- data/data/rbot/plugins/autorejoin.rb +16 -0
- data/data/rbot/plugins/cal.rb +15 -0
- data/data/rbot/plugins/dice.rb +81 -0
- data/data/rbot/plugins/eightball.rb +19 -0
- data/data/rbot/plugins/excuse.rb +470 -0
- data/data/rbot/plugins/fish.rb +61 -0
- data/data/rbot/plugins/fortune.rb +22 -0
- data/data/rbot/plugins/freshmeat.rb +98 -0
- data/data/rbot/plugins/google.rb +51 -0
- data/data/rbot/plugins/host.rb +14 -0
- data/data/rbot/plugins/httpd.rb.disabled +35 -0
- data/data/rbot/plugins/insult.rb +258 -0
- data/data/rbot/plugins/karma.rb +85 -0
- data/data/rbot/plugins/lart.rb +181 -0
- data/data/rbot/plugins/math.rb +122 -0
- data/data/rbot/plugins/nickserv.rb +89 -0
- data/data/rbot/plugins/nslookup.rb +43 -0
- data/data/rbot/plugins/opme.rb +19 -0
- data/data/rbot/plugins/quakeauth.rb +51 -0
- data/data/rbot/plugins/quotes.rb +321 -0
- data/data/rbot/plugins/remind.rb +228 -0
- data/data/rbot/plugins/roshambo.rb +54 -0
- data/data/rbot/plugins/rot13.rb +10 -0
- data/data/rbot/plugins/roulette.rb +147 -0
- data/data/rbot/plugins/rss.rb.disabled +414 -0
- data/data/rbot/plugins/seen.rb +89 -0
- data/data/rbot/plugins/slashdot.rb +94 -0
- data/data/rbot/plugins/spell.rb +36 -0
- data/data/rbot/plugins/tube.rb +71 -0
- data/data/rbot/plugins/url.rb +88 -0
- data/data/rbot/plugins/weather.rb +649 -0
- data/data/rbot/plugins/wserver.rb +71 -0
- data/data/rbot/plugins/xmlrpc.rb.disabled +52 -0
- data/data/rbot/templates/keywords.rbot +4 -0
- data/data/rbot/templates/lart/larts +98 -0
- data/data/rbot/templates/lart/praises +5 -0
- data/data/rbot/templates/levels.rbot +30 -0
- data/data/rbot/templates/users.rbot +1 -0
- data/lib/rbot/auth.rb +203 -0
- data/lib/rbot/channel.rb +54 -0
- data/lib/rbot/config.rb +363 -0
- data/lib/rbot/dbhash.rb +112 -0
- data/lib/rbot/httputil.rb +141 -0
- data/lib/rbot/ircbot.rb +808 -0
- data/lib/rbot/ircsocket.rb +185 -0
- data/lib/rbot/keywords.rb +433 -0
- data/lib/rbot/language.rb +69 -0
- data/lib/rbot/message.rb +256 -0
- data/lib/rbot/messagemapper.rb +262 -0
- data/lib/rbot/plugins.rb +291 -0
- data/lib/rbot/post-install.rb +8 -0
- data/lib/rbot/rbotconfig.rb +36 -0
- data/lib/rbot/registry.rb +271 -0
- data/lib/rbot/rfc2812.rb +1104 -0
- data/lib/rbot/timer.rb +201 -0
- data/lib/rbot/utils.rb +83 -0
- data/setup.rb +1360 -0
- metadata +129 -0
@@ -0,0 +1,141 @@
|
|
1
|
+
module Irc
|
2
|
+
module Utils
|
3
|
+
|
4
|
+
require 'resolv'
|
5
|
+
require 'net/http'
|
6
|
+
Net::HTTP.version_1_2
|
7
|
+
|
8
|
+
# class for making http requests easier (mainly for plugins to use)
|
9
|
+
# this class can check the bot proxy configuration to determine if a proxy
|
10
|
+
# needs to be used, which includes support for per-url proxy configuration.
|
11
|
+
class HttpUtil
|
12
|
+
BotConfig.register BotConfigBooleanValue.new('http.use_proxy',
|
13
|
+
:default => false, :desc => "should a proxy be used for HTTP requests?")
|
14
|
+
BotConfig.register BotConfigStringValue.new('http.proxy_uri', :default => false,
|
15
|
+
:desc => "Proxy server to use for HTTP requests (URI, e.g http://proxy.host:port)")
|
16
|
+
BotConfig.register BotConfigStringValue.new('http.proxy_user',
|
17
|
+
:default => nil,
|
18
|
+
:desc => "User for authenticating with the http proxy (if required)")
|
19
|
+
BotConfig.register BotConfigStringValue.new('http.proxy_pass',
|
20
|
+
:default => nil,
|
21
|
+
:desc => "Password for authenticating with the http proxy (if required)")
|
22
|
+
BotConfig.register BotConfigArrayValue.new('http.proxy_include',
|
23
|
+
:default => [],
|
24
|
+
:desc => "List of regexps to check against a URI's hostname/ip to see if we should use the proxy to access this URI. All URIs are proxied by default if the proxy is set, so this is only required to re-include URIs that might have been excluded by the exclude list. e.g. exclude /.*\.foo\.com/, include bar\.foo\.com")
|
25
|
+
BotConfig.register BotConfigArrayValue.new('http.proxy_exclude',
|
26
|
+
:default => [],
|
27
|
+
:desc => "List of regexps to check against a URI's hostname/ip to see if we should use avoid the proxy to access this URI and access it directly")
|
28
|
+
|
29
|
+
def initialize(bot)
|
30
|
+
@bot = bot
|
31
|
+
@headers = {
|
32
|
+
'User-Agent' => "rbot http util #{$version} (http://linuxbrit.co.uk/rbot/)",
|
33
|
+
}
|
34
|
+
end
|
35
|
+
|
36
|
+
# if http_proxy_include or http_proxy_exclude are set, then examine the
|
37
|
+
# uri to see if this is a proxied uri
|
38
|
+
# the in/excludes are a list of regexps, and each regexp is checked against
|
39
|
+
# the server name, and its IP addresses
|
40
|
+
def proxy_required(uri)
|
41
|
+
use_proxy = true
|
42
|
+
if @bot.config["http.proxy_exclude"].empty? && @bot.config["http.proxy_include"].empty?
|
43
|
+
return use_proxy
|
44
|
+
end
|
45
|
+
|
46
|
+
list = [uri.host]
|
47
|
+
begin
|
48
|
+
list.concat Resolv.getaddresses(uri.host)
|
49
|
+
rescue StandardError => err
|
50
|
+
puts "warning: couldn't resolve host uri.host"
|
51
|
+
end
|
52
|
+
|
53
|
+
unless @bot.config["http.proxy_exclude"].empty?
|
54
|
+
re = @bot.config["http.proxy_exclude"].collect{|r| Regexp.new(r)}
|
55
|
+
re.each do |r|
|
56
|
+
list.each do |item|
|
57
|
+
if r.match(item)
|
58
|
+
use_proxy = false
|
59
|
+
break
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
unless @bot.config["http.proxy_include"].empty?
|
65
|
+
re = @bot.config["http.proxy_include"].collect{|r| Regexp.new(r)}
|
66
|
+
re.each do |r|
|
67
|
+
list.each do |item|
|
68
|
+
if r.match(item)
|
69
|
+
use_proxy = true
|
70
|
+
break
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
debug "using proxy for uri #{uri}?: #{use_proxy}"
|
76
|
+
return use_proxy
|
77
|
+
end
|
78
|
+
|
79
|
+
# uri:: Uri to create a proxy for
|
80
|
+
#
|
81
|
+
# return a net/http Proxy object, which is configured correctly for
|
82
|
+
# proxying based on the bot's proxy configuration.
|
83
|
+
# This will include per-url proxy configuration based on the bot config
|
84
|
+
# +http_proxy_include/exclude+ options.
|
85
|
+
def get_proxy(uri)
|
86
|
+
proxy = nil
|
87
|
+
proxy_host = nil
|
88
|
+
proxy_port = nil
|
89
|
+
proxy_user = nil
|
90
|
+
proxy_pass = nil
|
91
|
+
|
92
|
+
if @bot.config["http.use_proxy"]
|
93
|
+
if (ENV['http_proxy'])
|
94
|
+
proxy = URI.parse ENV['http_proxy'] rescue nil
|
95
|
+
end
|
96
|
+
if (@bot.config["http.proxy_uri"])
|
97
|
+
proxy = URI.parse @bot.config["http.proxy_uri"] rescue nil
|
98
|
+
end
|
99
|
+
if proxy
|
100
|
+
debug "proxy is set to #{proxy.uri}"
|
101
|
+
if proxy_required(uri)
|
102
|
+
proxy_host = proxy.host
|
103
|
+
proxy_port = proxy.port
|
104
|
+
proxy_user = @bot.config["http.proxy_user"]
|
105
|
+
proxy_pass = @bot.config["http.proxy_pass"]
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
return Net::HTTP.new(uri.host, uri.port, proxy_host, proxy_port, proxy_user, proxy_port)
|
111
|
+
end
|
112
|
+
|
113
|
+
# uri:: uri to query (Uri object)
|
114
|
+
# readtimeout:: timeout for reading the response
|
115
|
+
# opentimeout:: timeout for opening the connection
|
116
|
+
#
|
117
|
+
# simple get request, returns response body if the status code is 200 and
|
118
|
+
# the request doesn't timeout.
|
119
|
+
def get(uri, readtimeout=10, opentimeout=5)
|
120
|
+
proxy = get_proxy(uri)
|
121
|
+
proxy.open_timeout = opentimeout
|
122
|
+
proxy.read_timeout = readtimeout
|
123
|
+
|
124
|
+
begin
|
125
|
+
proxy.start() {|http|
|
126
|
+
resp = http.get(uri.request_uri(), @headers)
|
127
|
+
if resp.code == "200"
|
128
|
+
return resp.body
|
129
|
+
else
|
130
|
+
puts "HttpUtil.get return code #{resp.code} #{resp.body}"
|
131
|
+
end
|
132
|
+
return nil
|
133
|
+
}
|
134
|
+
rescue StandardError, Timeout::Error => e
|
135
|
+
$stderr.puts "HttpUtil.get exception: #{e}, while trying to get #{uri}"
|
136
|
+
end
|
137
|
+
return nil
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
data/lib/rbot/ircbot.rb
ADDED
@@ -0,0 +1,808 @@
|
|
1
|
+
require 'thread'
|
2
|
+
require 'etc'
|
3
|
+
require 'fileutils'
|
4
|
+
|
5
|
+
$debug = false unless $debug
|
6
|
+
# print +message+ if debugging is enabled
|
7
|
+
def debug(message=nil)
|
8
|
+
print "DEBUG: #{message}\n" if($debug && message)
|
9
|
+
#yield
|
10
|
+
end
|
11
|
+
|
12
|
+
# these first
|
13
|
+
require 'rbot/rbotconfig'
|
14
|
+
require 'rbot/config'
|
15
|
+
require 'rbot/utils'
|
16
|
+
|
17
|
+
require 'rbot/rfc2812'
|
18
|
+
require 'rbot/keywords'
|
19
|
+
require 'rbot/ircsocket'
|
20
|
+
require 'rbot/auth'
|
21
|
+
require 'rbot/timer'
|
22
|
+
require 'rbot/plugins'
|
23
|
+
require 'rbot/channel'
|
24
|
+
require 'rbot/message'
|
25
|
+
require 'rbot/language'
|
26
|
+
require 'rbot/dbhash'
|
27
|
+
require 'rbot/registry'
|
28
|
+
require 'rbot/httputil'
|
29
|
+
|
30
|
+
module Irc
|
31
|
+
|
32
|
+
# Main bot class, which manages the various components, receives messages,
|
33
|
+
# handles them or passes them to plugins, and contains core functionality.
|
34
|
+
class IrcBot
|
35
|
+
# the bot's current nickname
|
36
|
+
attr_reader :nick
|
37
|
+
|
38
|
+
# the bot's IrcAuth data
|
39
|
+
attr_reader :auth
|
40
|
+
|
41
|
+
# the bot's BotConfig data
|
42
|
+
attr_reader :config
|
43
|
+
|
44
|
+
# the botclass for this bot (determines configdir among other things)
|
45
|
+
attr_reader :botclass
|
46
|
+
|
47
|
+
# used to perform actions periodically (saves configuration once per minute
|
48
|
+
# by default)
|
49
|
+
attr_reader :timer
|
50
|
+
|
51
|
+
# bot's Language data
|
52
|
+
attr_reader :lang
|
53
|
+
|
54
|
+
# bot's configured addressing prefixes
|
55
|
+
attr_reader :addressing_prefixes
|
56
|
+
|
57
|
+
# channel info for channels the bot is in
|
58
|
+
attr_reader :channels
|
59
|
+
|
60
|
+
# bot's irc socket
|
61
|
+
attr_reader :socket
|
62
|
+
|
63
|
+
# bot's object registry, plugins get an interface to this for persistant
|
64
|
+
# storage (hash interface tied to a bdb file, plugins use Accessors to store
|
65
|
+
# and restore objects in their own namespaces.)
|
66
|
+
attr_reader :registry
|
67
|
+
|
68
|
+
# bot's httputil help object, for fetching resources via http. Sets up
|
69
|
+
# proxies etc as defined by the bot configuration/environment
|
70
|
+
attr_reader :httputil
|
71
|
+
|
72
|
+
# create a new IrcBot with botclass +botclass+
|
73
|
+
def initialize(botclass, params = {})
|
74
|
+
# BotConfig for the core bot
|
75
|
+
BotConfig.register BotConfigStringValue.new('server.name',
|
76
|
+
:default => "localhost", :requires_restart => true,
|
77
|
+
:desc => "What server should the bot connect to?",
|
78
|
+
:wizard => true)
|
79
|
+
BotConfig.register BotConfigIntegerValue.new('server.port',
|
80
|
+
:default => 6667, :type => :integer, :requires_restart => true,
|
81
|
+
:desc => "What port should the bot connect to?",
|
82
|
+
:validate => Proc.new {|v| v > 0}, :wizard => true)
|
83
|
+
BotConfig.register BotConfigStringValue.new('server.password',
|
84
|
+
:default => false, :requires_restart => true,
|
85
|
+
:desc => "Password for connecting to this server (if required)",
|
86
|
+
:wizard => true)
|
87
|
+
BotConfig.register BotConfigStringValue.new('server.bindhost',
|
88
|
+
:default => false, :requires_restart => true,
|
89
|
+
:desc => "Specific local host or IP for the bot to bind to (if required)",
|
90
|
+
:wizard => true)
|
91
|
+
BotConfig.register BotConfigIntegerValue.new('server.reconnect_wait',
|
92
|
+
:default => 5, :validate => Proc.new{|v| v >= 0},
|
93
|
+
:desc => "Seconds to wait before attempting to reconnect, on disconnect")
|
94
|
+
BotConfig.register BotConfigStringValue.new('irc.nick', :default => "rbot",
|
95
|
+
:desc => "IRC nickname the bot should attempt to use", :wizard => true,
|
96
|
+
:on_change => Proc.new{|bot, v| bot.sendq "NICK #{v}" })
|
97
|
+
BotConfig.register BotConfigStringValue.new('irc.user', :default => "rbot",
|
98
|
+
:requires_restart => true,
|
99
|
+
:desc => "local user the bot should appear to be", :wizard => true)
|
100
|
+
BotConfig.register BotConfigArrayValue.new('irc.join_channels',
|
101
|
+
:default => [], :wizard => true,
|
102
|
+
:desc => "What channels the bot should always join at startup. List multiple channels using commas to separate. If a channel requires a password, use a space after the channel name. e.g: '#chan1, #chan2, #secretchan secritpass, #chan3'")
|
103
|
+
BotConfig.register BotConfigIntegerValue.new('core.save_every',
|
104
|
+
:default => 60, :validate => Proc.new{|v| v >= 0},
|
105
|
+
# TODO change timer via on_change proc
|
106
|
+
:desc => "How often the bot should persist all configuration to disk (in case of a server crash, for example")
|
107
|
+
BotConfig.register BotConfigFloatValue.new('server.sendq_delay',
|
108
|
+
:default => 2.0, :validate => Proc.new{|v| v >= 0},
|
109
|
+
:desc => "(flood prevention) the delay between sending messages to the server (in seconds)",
|
110
|
+
:on_change => Proc.new {|bot, v| bot.socket.sendq_delay = v })
|
111
|
+
BotConfig.register BotConfigIntegerValue.new('server.sendq_burst',
|
112
|
+
:default => 4, :validate => Proc.new{|v| v >= 0},
|
113
|
+
:desc => "(flood prevention) max lines to burst to the server before throttling. Most ircd's allow bursts of up 5 lines, with non-burst limits of 512 bytes/2 seconds",
|
114
|
+
:on_change => Proc.new {|bot, v| bot.socket.sendq_burst = v })
|
115
|
+
|
116
|
+
@argv = params[:argv]
|
117
|
+
|
118
|
+
unless FileTest.directory? Config::datadir
|
119
|
+
puts "data directory '#{Config::datadir}' not found, did you install.rb?"
|
120
|
+
exit 2
|
121
|
+
end
|
122
|
+
|
123
|
+
botclass = "/home/#{Etc.getlogin}/.rbot" unless botclass
|
124
|
+
@botclass = botclass.gsub(/\/$/, "")
|
125
|
+
|
126
|
+
unless FileTest.directory? botclass
|
127
|
+
puts "no #{botclass} directory found, creating from templates.."
|
128
|
+
if FileTest.exist? botclass
|
129
|
+
puts "Error: file #{botclass} exists but isn't a directory"
|
130
|
+
exit 2
|
131
|
+
end
|
132
|
+
FileUtils.cp_r Config::datadir+'/templates', botclass
|
133
|
+
end
|
134
|
+
|
135
|
+
Dir.mkdir("#{botclass}/logs") unless File.exist?("#{botclass}/logs")
|
136
|
+
|
137
|
+
@startup_time = Time.new
|
138
|
+
@config = BotConfig.new(self)
|
139
|
+
@timer = Timer::Timer.new(1.0) # only need per-second granularity
|
140
|
+
@registry = BotRegistry.new self
|
141
|
+
@timer.add(@config['core.save_every']) { save } if @config['core.save_every']
|
142
|
+
@channels = Hash.new
|
143
|
+
@logs = Hash.new
|
144
|
+
|
145
|
+
@httputil = Utils::HttpUtil.new(self)
|
146
|
+
@lang = Language::Language.new(@config['core.language'])
|
147
|
+
@keywords = Keywords.new(self)
|
148
|
+
@auth = IrcAuth.new(self)
|
149
|
+
|
150
|
+
Dir.mkdir("#{botclass}/plugins") unless File.exist?("#{botclass}/plugins")
|
151
|
+
@plugins = Plugins::Plugins.new(self, ["#{botclass}/plugins"])
|
152
|
+
|
153
|
+
@socket = IrcSocket.new(@config['server.name'], @config['server.port'], @config['server.bindhost'], @config['server.sendq_delay'], @config['server.sendq_burst'])
|
154
|
+
@nick = @config['irc.nick']
|
155
|
+
if @config['core.address_prefix']
|
156
|
+
@addressing_prefixes = @config['core.address_prefix'].split(" ")
|
157
|
+
else
|
158
|
+
@addressing_prefixes = Array.new
|
159
|
+
end
|
160
|
+
|
161
|
+
@client = IrcClient.new
|
162
|
+
@client[:privmsg] = proc { |data|
|
163
|
+
message = PrivMessage.new(self, data[:source], data[:target], data[:message])
|
164
|
+
onprivmsg(message)
|
165
|
+
}
|
166
|
+
@client[:notice] = proc { |data|
|
167
|
+
message = NoticeMessage.new(self, data[:source], data[:target], data[:message])
|
168
|
+
# pass it off to plugins that want to hear everything
|
169
|
+
@plugins.delegate "listen", message
|
170
|
+
}
|
171
|
+
@client[:motd] = proc { |data|
|
172
|
+
data[:motd].each_line { |line|
|
173
|
+
log "MOTD: #{line}", "server"
|
174
|
+
}
|
175
|
+
}
|
176
|
+
@client[:nicktaken] = proc { |data|
|
177
|
+
nickchg "#{data[:nick]}_"
|
178
|
+
}
|
179
|
+
@client[:badnick] = proc {|data|
|
180
|
+
puts "WARNING, bad nick (#{data[:nick]})"
|
181
|
+
}
|
182
|
+
@client[:ping] = proc {|data|
|
183
|
+
# (jump the queue for pongs)
|
184
|
+
@socket.puts "PONG #{data[:pingid]}"
|
185
|
+
}
|
186
|
+
@client[:nick] = proc {|data|
|
187
|
+
sourcenick = data[:sourcenick]
|
188
|
+
nick = data[:nick]
|
189
|
+
m = NickMessage.new(self, data[:source], data[:sourcenick], data[:nick])
|
190
|
+
if(sourcenick == @nick)
|
191
|
+
debug "my nick is now #{nick}"
|
192
|
+
@nick = nick
|
193
|
+
end
|
194
|
+
@channels.each {|k,v|
|
195
|
+
if(v.users.has_key?(sourcenick))
|
196
|
+
log "@ #{sourcenick} is now known as #{nick}", k
|
197
|
+
v.users[nick] = v.users[sourcenick]
|
198
|
+
v.users.delete(sourcenick)
|
199
|
+
end
|
200
|
+
}
|
201
|
+
@plugins.delegate("listen", m)
|
202
|
+
@plugins.delegate("nick", m)
|
203
|
+
}
|
204
|
+
@client[:quit] = proc {|data|
|
205
|
+
source = data[:source]
|
206
|
+
sourcenick = data[:sourcenick]
|
207
|
+
sourceurl = data[:sourceaddress]
|
208
|
+
message = data[:message]
|
209
|
+
m = QuitMessage.new(self, data[:source], data[:sourcenick], data[:message])
|
210
|
+
if(data[:sourcenick] =~ /#{@nick}/i)
|
211
|
+
else
|
212
|
+
@channels.each {|k,v|
|
213
|
+
if(v.users.has_key?(sourcenick))
|
214
|
+
log "@ Quit: #{sourcenick}: #{message}", k
|
215
|
+
v.users.delete(sourcenick)
|
216
|
+
end
|
217
|
+
}
|
218
|
+
end
|
219
|
+
@plugins.delegate("listen", m)
|
220
|
+
@plugins.delegate("quit", m)
|
221
|
+
}
|
222
|
+
@client[:mode] = proc {|data|
|
223
|
+
source = data[:source]
|
224
|
+
sourcenick = data[:sourcenick]
|
225
|
+
sourceurl = data[:sourceaddress]
|
226
|
+
channel = data[:channel]
|
227
|
+
targets = data[:targets]
|
228
|
+
modestring = data[:modestring]
|
229
|
+
log "@ Mode #{modestring} #{targets} by #{sourcenick}", channel
|
230
|
+
}
|
231
|
+
@client[:welcome] = proc {|data|
|
232
|
+
log "joined server #{data[:source]} as #{data[:nick]}", "server"
|
233
|
+
debug "I think my nick is #{@nick}, server thinks #{data[:nick]}"
|
234
|
+
if data[:nick] && data[:nick].length > 0
|
235
|
+
@nick = data[:nick]
|
236
|
+
end
|
237
|
+
|
238
|
+
@plugins.delegate("connect")
|
239
|
+
|
240
|
+
@config['irc.join_channels'].each {|c|
|
241
|
+
debug "autojoining channel #{c}"
|
242
|
+
if(c =~ /^(\S+)\s+(\S+)$/i)
|
243
|
+
join $1, $2
|
244
|
+
else
|
245
|
+
join c if(c)
|
246
|
+
end
|
247
|
+
}
|
248
|
+
}
|
249
|
+
@client[:join] = proc {|data|
|
250
|
+
m = JoinMessage.new(self, data[:source], data[:channel], data[:message])
|
251
|
+
onjoin(m)
|
252
|
+
}
|
253
|
+
@client[:part] = proc {|data|
|
254
|
+
m = PartMessage.new(self, data[:source], data[:channel], data[:message])
|
255
|
+
onpart(m)
|
256
|
+
}
|
257
|
+
@client[:kick] = proc {|data|
|
258
|
+
m = KickMessage.new(self, data[:source], data[:target],data[:channel],data[:message])
|
259
|
+
onkick(m)
|
260
|
+
}
|
261
|
+
@client[:invite] = proc {|data|
|
262
|
+
if(data[:target] =~ /^#{@nick}$/i)
|
263
|
+
join data[:channel] if (@auth.allow?("join", data[:source], data[:sourcenick]))
|
264
|
+
end
|
265
|
+
}
|
266
|
+
@client[:changetopic] = proc {|data|
|
267
|
+
channel = data[:channel]
|
268
|
+
sourcenick = data[:sourcenick]
|
269
|
+
topic = data[:topic]
|
270
|
+
timestamp = data[:unixtime] || Time.now.to_i
|
271
|
+
if(sourcenick == @nick)
|
272
|
+
log "@ I set topic \"#{topic}\"", channel
|
273
|
+
else
|
274
|
+
log "@ #{sourcenick} set topic \"#{topic}\"", channel
|
275
|
+
end
|
276
|
+
m = TopicMessage.new(self, data[:source], data[:channel], timestamp, data[:topic])
|
277
|
+
|
278
|
+
ontopic(m)
|
279
|
+
@plugins.delegate("listen", m)
|
280
|
+
@plugins.delegate("topic", m)
|
281
|
+
}
|
282
|
+
@client[:topic] = @client[:topicinfo] = proc {|data|
|
283
|
+
channel = data[:channel]
|
284
|
+
m = TopicMessage.new(self, data[:source], data[:channel], data[:unixtime], data[:topic])
|
285
|
+
ontopic(m)
|
286
|
+
}
|
287
|
+
@client[:names] = proc {|data|
|
288
|
+
channel = data[:channel]
|
289
|
+
users = data[:users]
|
290
|
+
unless(@channels[channel])
|
291
|
+
puts "bug: got names for channel '#{channel}' I didn't think I was in\n"
|
292
|
+
exit 2
|
293
|
+
end
|
294
|
+
@channels[channel].users.clear
|
295
|
+
users.each {|u|
|
296
|
+
@channels[channel].users[u[0].sub(/^[@&~+]/, '')] = ["mode", u[1]]
|
297
|
+
}
|
298
|
+
}
|
299
|
+
@client[:unknown] = proc {|data|
|
300
|
+
#debug "UNKNOWN: #{data[:serverstring]}"
|
301
|
+
log data[:serverstring], ":unknown"
|
302
|
+
}
|
303
|
+
end
|
304
|
+
|
305
|
+
# connect the bot to IRC
|
306
|
+
def connect
|
307
|
+
trap("SIGTERM") { quit }
|
308
|
+
trap("SIGHUP") { quit }
|
309
|
+
trap("SIGINT") { quit }
|
310
|
+
begin
|
311
|
+
@socket.connect
|
312
|
+
rescue => e
|
313
|
+
raise "failed to connect to IRC server at #{@config['server.name']} #{@config['server.port']}: " + e
|
314
|
+
end
|
315
|
+
@socket.puts "PASS " + @config['server.password'] if @config['server.password']
|
316
|
+
@socket.puts "NICK #{@nick}\nUSER #{@config['irc.user']} 4 #{@config['server.name']} :Ruby bot. (c) Tom Gilbert"
|
317
|
+
end
|
318
|
+
|
319
|
+
# begin event handling loop
|
320
|
+
def mainloop
|
321
|
+
while true
|
322
|
+
connect
|
323
|
+
@timer.start
|
324
|
+
|
325
|
+
begin
|
326
|
+
while true
|
327
|
+
if @socket.select
|
328
|
+
break unless reply = @socket.gets
|
329
|
+
@client.process reply
|
330
|
+
end
|
331
|
+
end
|
332
|
+
rescue TimeoutError, SocketError => e
|
333
|
+
puts "network exception: connection closed: #{e}"
|
334
|
+
puts e.backtrace.join("\n")
|
335
|
+
@socket.close # now we reconnect
|
336
|
+
rescue => e # TODO be selective, only grab Network errors
|
337
|
+
puts "unexpected exception: connection closed: #{e}"
|
338
|
+
puts e.backtrace.join("\n")
|
339
|
+
exit 2
|
340
|
+
end
|
341
|
+
|
342
|
+
puts "disconnected"
|
343
|
+
@channels.clear
|
344
|
+
@socket.clearq
|
345
|
+
|
346
|
+
puts "waiting to reconnect"
|
347
|
+
sleep @config['server.reconnect_wait']
|
348
|
+
end
|
349
|
+
end
|
350
|
+
|
351
|
+
# type:: message type
|
352
|
+
# where:: message target
|
353
|
+
# message:: message text
|
354
|
+
# send message +message+ of type +type+ to target +where+
|
355
|
+
# Type can be PRIVMSG, NOTICE, etc, but those you should really use the
|
356
|
+
# relevant say() or notice() methods. This one should be used for IRCd
|
357
|
+
# extensions you want to use in modules.
|
358
|
+
def sendmsg(type, where, message)
|
359
|
+
# limit it 440 chars + CRLF.. so we have to split long lines
|
360
|
+
left = 440 - type.length - where.length - 3
|
361
|
+
begin
|
362
|
+
if(left >= message.length)
|
363
|
+
sendq("#{type} #{where} :#{message}")
|
364
|
+
log_sent(type, where, message)
|
365
|
+
return
|
366
|
+
end
|
367
|
+
line = message.slice!(0, left)
|
368
|
+
lastspace = line.rindex(/\s+/)
|
369
|
+
if(lastspace)
|
370
|
+
message = line.slice!(lastspace, line.length) + message
|
371
|
+
message.gsub!(/^\s+/, "")
|
372
|
+
end
|
373
|
+
sendq("#{type} #{where} :#{line}")
|
374
|
+
log_sent(type, where, line)
|
375
|
+
end while(message.length > 0)
|
376
|
+
end
|
377
|
+
|
378
|
+
# queue an arbitraty message for the server
|
379
|
+
def sendq(message="")
|
380
|
+
# temporary
|
381
|
+
@socket.queue(message)
|
382
|
+
end
|
383
|
+
|
384
|
+
# send a notice message to channel/nick +where+
|
385
|
+
def notice(where, message)
|
386
|
+
message.each_line { |line|
|
387
|
+
line.chomp!
|
388
|
+
next unless(line.length > 0)
|
389
|
+
sendmsg("NOTICE", where, line)
|
390
|
+
}
|
391
|
+
end
|
392
|
+
|
393
|
+
# say something (PRIVMSG) to channel/nick +where+
|
394
|
+
def say(where, message)
|
395
|
+
message.to_s.gsub(/[\r\n]+/, "\n").each_line { |line|
|
396
|
+
line.chomp!
|
397
|
+
next unless(line.length > 0)
|
398
|
+
unless((where =~ /^#/) && (@channels.has_key?(where) && @channels[where].quiet))
|
399
|
+
sendmsg("PRIVMSG", where, line)
|
400
|
+
end
|
401
|
+
}
|
402
|
+
end
|
403
|
+
|
404
|
+
# perform a CTCP action with message +message+ to channel/nick +where+
|
405
|
+
def action(where, message)
|
406
|
+
sendq("PRIVMSG #{where} :\001ACTION #{message}\001")
|
407
|
+
if(where =~ /^#/)
|
408
|
+
log "* #{@nick} #{message}", where
|
409
|
+
elsif (where =~ /^(\S*)!.*$/)
|
410
|
+
log "* #{@nick}[#{where}] #{message}", $1
|
411
|
+
else
|
412
|
+
log "* #{@nick}[#{where}] #{message}", where
|
413
|
+
end
|
414
|
+
end
|
415
|
+
|
416
|
+
# quick way to say "okay" (or equivalent) to +where+
|
417
|
+
def okay(where)
|
418
|
+
say where, @lang.get("okay")
|
419
|
+
end
|
420
|
+
|
421
|
+
# log message +message+ to a file determined by +where+. +where+ can be a
|
422
|
+
# channel name, or a nick for private message logging
|
423
|
+
def log(message, where="server")
|
424
|
+
message.chomp!
|
425
|
+
stamp = Time.now.strftime("%Y/%m/%d %H:%M:%S")
|
426
|
+
unless(@logs.has_key?(where))
|
427
|
+
@logs[where] = File.new("#{@botclass}/logs/#{where}", "a")
|
428
|
+
@logs[where].sync = true
|
429
|
+
end
|
430
|
+
@logs[where].puts "[#{stamp}] #{message}"
|
431
|
+
#debug "[#{stamp}] <#{where}> #{message}"
|
432
|
+
end
|
433
|
+
|
434
|
+
# set topic of channel +where+ to +topic+
|
435
|
+
def topic(where, topic)
|
436
|
+
sendq "TOPIC #{where} :#{topic}"
|
437
|
+
end
|
438
|
+
|
439
|
+
# disconnect from the server and cleanup all plugins and modules
|
440
|
+
def shutdown(message = nil)
|
441
|
+
trap("SIGTERM", "DEFAULT")
|
442
|
+
trap("SIGHUP", "DEFAULT")
|
443
|
+
trap("SIGINT", "DEFAULT")
|
444
|
+
message = @lang.get("quit") if (message.nil? || message.empty?)
|
445
|
+
@socket.clearq
|
446
|
+
save
|
447
|
+
@plugins.cleanup
|
448
|
+
@channels.each_value {|v|
|
449
|
+
log "@ quit (#{message})", v.name
|
450
|
+
}
|
451
|
+
@socket.puts "QUIT :#{message}"
|
452
|
+
@socket.flush
|
453
|
+
@socket.shutdown
|
454
|
+
@registry.close
|
455
|
+
puts "rbot quit (#{message})"
|
456
|
+
end
|
457
|
+
|
458
|
+
# message:: optional IRC quit message
|
459
|
+
# quit IRC, shutdown the bot
|
460
|
+
def quit(message=nil)
|
461
|
+
shutdown(message)
|
462
|
+
exit 0
|
463
|
+
end
|
464
|
+
|
465
|
+
# totally shutdown and respawn the bot
|
466
|
+
def restart
|
467
|
+
shutdown("restarting, back in #{@config['server.reconnect_wait']}...")
|
468
|
+
sleep @config['server.reconnect_wait']
|
469
|
+
# now we re-exec
|
470
|
+
exec($0, *@argv)
|
471
|
+
end
|
472
|
+
|
473
|
+
# call the save method for bot's config, keywords, auth and all plugins
|
474
|
+
def save
|
475
|
+
@registry.flush
|
476
|
+
@config.save
|
477
|
+
@keywords.save
|
478
|
+
@auth.save
|
479
|
+
@plugins.save
|
480
|
+
end
|
481
|
+
|
482
|
+
# call the rescan method for the bot's lang, keywords and all plugins
|
483
|
+
def rescan
|
484
|
+
@lang.rescan
|
485
|
+
@plugins.rescan
|
486
|
+
@keywords.rescan
|
487
|
+
end
|
488
|
+
|
489
|
+
# channel:: channel to join
|
490
|
+
# key:: optional channel key if channel is +s
|
491
|
+
# join a channel
|
492
|
+
def join(channel, key=nil)
|
493
|
+
if(key)
|
494
|
+
sendq "JOIN #{channel} :#{key}"
|
495
|
+
else
|
496
|
+
sendq "JOIN #{channel}"
|
497
|
+
end
|
498
|
+
end
|
499
|
+
|
500
|
+
# part a channel
|
501
|
+
def part(channel, message="")
|
502
|
+
sendq "PART #{channel} :#{message}"
|
503
|
+
end
|
504
|
+
|
505
|
+
# attempt to change bot's nick to +name+
|
506
|
+
# FIXME
|
507
|
+
# if rbot is already taken, this happens:
|
508
|
+
# <giblet> rbot_, nick rbot
|
509
|
+
# --- rbot_ is now known as rbot__
|
510
|
+
# he should of course just keep his existing nick and report the error :P
|
511
|
+
def nickchg(name)
|
512
|
+
sendq "NICK #{name}"
|
513
|
+
end
|
514
|
+
|
515
|
+
# changing mode
|
516
|
+
def mode(channel, mode, target)
|
517
|
+
sendq "MODE #{channel} #{mode} #{target}"
|
518
|
+
end
|
519
|
+
|
520
|
+
# m:: message asking for help
|
521
|
+
# topic:: optional topic help is requested for
|
522
|
+
# respond to online help requests
|
523
|
+
def help(topic=nil)
|
524
|
+
topic = nil if topic == ""
|
525
|
+
case topic
|
526
|
+
when nil
|
527
|
+
helpstr = "help topics: core, auth, keywords"
|
528
|
+
helpstr += @plugins.helptopics
|
529
|
+
helpstr += " (help <topic> for more info)"
|
530
|
+
when /^core$/i
|
531
|
+
helpstr = corehelp
|
532
|
+
when /^core\s+(.+)$/i
|
533
|
+
helpstr = corehelp $1
|
534
|
+
when /^auth$/i
|
535
|
+
helpstr = @auth.help
|
536
|
+
when /^auth\s+(.+)$/i
|
537
|
+
helpstr = @auth.help $1
|
538
|
+
when /^keywords$/i
|
539
|
+
helpstr = @keywords.help
|
540
|
+
when /^keywords\s+(.+)$/i
|
541
|
+
helpstr = @keywords.help $1
|
542
|
+
else
|
543
|
+
unless(helpstr = @plugins.help(topic))
|
544
|
+
helpstr = "no help for topic #{topic}"
|
545
|
+
end
|
546
|
+
end
|
547
|
+
return helpstr
|
548
|
+
end
|
549
|
+
|
550
|
+
# returns a string describing the current status of the bot (uptime etc)
|
551
|
+
def status
|
552
|
+
secs_up = Time.new - @startup_time
|
553
|
+
uptime = Utils.secs_to_string secs_up
|
554
|
+
return "Uptime #{uptime}, #{@plugins.length} plugins active, #{@registry.length} items stored in registry, #{@socket.lines_sent} lines sent, #{@socket.lines_received} received."
|
555
|
+
end
|
556
|
+
|
557
|
+
|
558
|
+
private
|
559
|
+
|
560
|
+
# handle help requests for "core" topics
|
561
|
+
def corehelp(topic="")
|
562
|
+
case topic
|
563
|
+
when "quit"
|
564
|
+
return "quit [<message>] => quit IRC with message <message>"
|
565
|
+
when "restart"
|
566
|
+
return "restart => completely stop and restart the bot (including reconnect)"
|
567
|
+
when "join"
|
568
|
+
return "join <channel> [<key>] => join channel <channel> with secret key <key> if specified. #{@nick} also responds to invites if you have the required access level"
|
569
|
+
when "part"
|
570
|
+
return "part <channel> => part channel <channel>"
|
571
|
+
when "hide"
|
572
|
+
return "hide => part all channels"
|
573
|
+
when "save"
|
574
|
+
return "save => save current dynamic data and configuration"
|
575
|
+
when "rescan"
|
576
|
+
return "rescan => reload modules and static facts"
|
577
|
+
when "nick"
|
578
|
+
return "nick <nick> => attempt to change nick to <nick>"
|
579
|
+
when "say"
|
580
|
+
return "say <channel>|<nick> <message> => say <message> to <channel> or in private message to <nick>"
|
581
|
+
when "action"
|
582
|
+
return "action <channel>|<nick> <message> => does a /me <message> to <channel> or in private message to <nick>"
|
583
|
+
when "topic"
|
584
|
+
return "topic <channel> <message> => set topic of <channel> to <message>"
|
585
|
+
when "quiet"
|
586
|
+
return "quiet [in here|<channel>] => with no arguments, stop speaking in all channels, if \"in here\", stop speaking in this channel, or stop speaking in <channel>"
|
587
|
+
when "talk"
|
588
|
+
return "talk [in here|<channel>] => with no arguments, resume speaking in all channels, if \"in here\", resume speaking in this channel, or resume speaking in <channel>"
|
589
|
+
when "version"
|
590
|
+
return "version => describes software version"
|
591
|
+
when "botsnack"
|
592
|
+
return "botsnack => reward #{@nick} for being good"
|
593
|
+
when "hello"
|
594
|
+
return "hello|hi|hey|yo [#{@nick}] => greet the bot"
|
595
|
+
else
|
596
|
+
return "Core help topics: quit, restart, config, join, part, hide, save, rescan, nick, say, action, topic, quiet, talk, version, botsnack, hello"
|
597
|
+
end
|
598
|
+
end
|
599
|
+
|
600
|
+
# handle incoming IRC PRIVMSG +m+
|
601
|
+
def onprivmsg(m)
|
602
|
+
# log it first
|
603
|
+
if(m.action?)
|
604
|
+
if(m.private?)
|
605
|
+
log "* [#{m.sourcenick}(#{m.sourceaddress})] #{m.message}", m.sourcenick
|
606
|
+
else
|
607
|
+
log "* #{m.sourcenick} #{m.message}", m.target
|
608
|
+
end
|
609
|
+
else
|
610
|
+
if(m.public?)
|
611
|
+
log "<#{m.sourcenick}> #{m.message}", m.target
|
612
|
+
else
|
613
|
+
log "[#{m.sourcenick}(#{m.sourceaddress})] #{m.message}", m.sourcenick
|
614
|
+
end
|
615
|
+
end
|
616
|
+
|
617
|
+
# pass it off to plugins that want to hear everything
|
618
|
+
@plugins.delegate "listen", m
|
619
|
+
|
620
|
+
if(m.private? && m.message =~ /^\001PING\s+(.+)\001/)
|
621
|
+
notice m.sourcenick, "\001PING #$1\001"
|
622
|
+
log "@ #{m.sourcenick} pinged me"
|
623
|
+
return
|
624
|
+
end
|
625
|
+
|
626
|
+
if(m.address?)
|
627
|
+
case m.message
|
628
|
+
when (/^join\s+(\S+)\s+(\S+)$/i)
|
629
|
+
join $1, $2 if(@auth.allow?("join", m.source, m.replyto))
|
630
|
+
when (/^join\s+(\S+)$/i)
|
631
|
+
join $1 if(@auth.allow?("join", m.source, m.replyto))
|
632
|
+
when (/^part$/i)
|
633
|
+
part m.target if(m.public? && @auth.allow?("join", m.source, m.replyto))
|
634
|
+
when (/^part\s+(\S+)$/i)
|
635
|
+
part $1 if(@auth.allow?("join", m.source, m.replyto))
|
636
|
+
when (/^quit(?:\s+(.*))?$/i)
|
637
|
+
quit $1 if(@auth.allow?("quit", m.source, m.replyto))
|
638
|
+
when (/^restart$/i)
|
639
|
+
restart if(@auth.allow?("quit", m.source, m.replyto))
|
640
|
+
when (/^hide$/i)
|
641
|
+
join 0 if(@auth.allow?("join", m.source, m.replyto))
|
642
|
+
when (/^save$/i)
|
643
|
+
if(@auth.allow?("config", m.source, m.replyto))
|
644
|
+
save
|
645
|
+
m.okay
|
646
|
+
end
|
647
|
+
when (/^nick\s+(\S+)$/i)
|
648
|
+
nickchg($1) if(@auth.allow?("nick", m.source, m.replyto))
|
649
|
+
when (/^say\s+(\S+)\s+(.*)$/i)
|
650
|
+
say $1, $2 if(@auth.allow?("say", m.source, m.replyto))
|
651
|
+
when (/^action\s+(\S+)\s+(.*)$/i)
|
652
|
+
action $1, $2 if(@auth.allow?("say", m.source, m.replyto))
|
653
|
+
when (/^topic\s+(\S+)\s+(.*)$/i)
|
654
|
+
topic $1, $2 if(@auth.allow?("topic", m.source, m.replyto))
|
655
|
+
when (/^mode\s+(\S+)\s+(\S+)\s+(.*)$/i)
|
656
|
+
mode $1, $2, $3 if(@auth.allow?("mode", m.source, m.replyto))
|
657
|
+
when (/^ping$/i)
|
658
|
+
say m.replyto, "pong"
|
659
|
+
when (/^rescan$/i)
|
660
|
+
if(@auth.allow?("config", m.source, m.replyto))
|
661
|
+
m.okay
|
662
|
+
rescan
|
663
|
+
end
|
664
|
+
when (/^quiet$/i)
|
665
|
+
if(auth.allow?("talk", m.source, m.replyto))
|
666
|
+
m.okay
|
667
|
+
@channels.each_value {|c| c.quiet = true }
|
668
|
+
end
|
669
|
+
when (/^quiet in (\S+)$/i)
|
670
|
+
where = $1
|
671
|
+
if(auth.allow?("talk", m.source, m.replyto))
|
672
|
+
m.okay
|
673
|
+
where.gsub!(/^here$/, m.target) if m.public?
|
674
|
+
@channels[where].quiet = true if(@channels.has_key?(where))
|
675
|
+
end
|
676
|
+
when (/^talk$/i)
|
677
|
+
if(auth.allow?("talk", m.source, m.replyto))
|
678
|
+
@channels.each_value {|c| c.quiet = false }
|
679
|
+
m.okay
|
680
|
+
end
|
681
|
+
when (/^talk in (\S+)$/i)
|
682
|
+
where = $1
|
683
|
+
if(auth.allow?("talk", m.source, m.replyto))
|
684
|
+
where.gsub!(/^here$/, m.target) if m.public?
|
685
|
+
@channels[where].quiet = false if(@channels.has_key?(where))
|
686
|
+
m.okay
|
687
|
+
end
|
688
|
+
when (/^status\??$/i)
|
689
|
+
m.reply status if auth.allow?("status", m.source, m.replyto)
|
690
|
+
when (/^registry stats$/i)
|
691
|
+
if auth.allow?("config", m.source, m.replyto)
|
692
|
+
m.reply @registry.stat.inspect
|
693
|
+
end
|
694
|
+
when (/^(help\s+)?config(\s+|$)/)
|
695
|
+
@config.privmsg(m)
|
696
|
+
when (/^(version)|(introduce yourself)$/i)
|
697
|
+
say m.replyto, "I'm a v. #{$version} rubybot, (c) Tom Gilbert - http://linuxbrit.co.uk/rbot/"
|
698
|
+
when (/^help(?:\s+(.*))?$/i)
|
699
|
+
say m.replyto, help($1)
|
700
|
+
#TODO move these to a "chatback" plugin
|
701
|
+
when (/^(botsnack|ciggie)$/i)
|
702
|
+
say m.replyto, @lang.get("thanks_X") % m.sourcenick if(m.public?)
|
703
|
+
say m.replyto, @lang.get("thanks") if(m.private?)
|
704
|
+
when (/^(hello|howdy|hola|salut|bonjour|sup|niihau|hey|hi(\W|$)|yo(\W|$)).*/i)
|
705
|
+
say m.replyto, @lang.get("hello_X") % m.sourcenick if(m.public?)
|
706
|
+
say m.replyto, @lang.get("hello") if(m.private?)
|
707
|
+
else
|
708
|
+
delegate_privmsg(m)
|
709
|
+
end
|
710
|
+
else
|
711
|
+
# stuff to handle when not addressed
|
712
|
+
case m.message
|
713
|
+
when (/^\s*(hello|howdy|hola|salut|bonjour|sup|niihau|hey|hi(\W|$)|yo(\W|$))[\s,-.]+#{@nick}$/i)
|
714
|
+
say m.replyto, @lang.get("hello_X") % m.sourcenick
|
715
|
+
when (/^#{@nick}!*$/)
|
716
|
+
say m.replyto, @lang.get("hello_X") % m.sourcenick
|
717
|
+
else
|
718
|
+
@keywords.privmsg(m)
|
719
|
+
end
|
720
|
+
end
|
721
|
+
end
|
722
|
+
|
723
|
+
# log a message. Internal use only.
|
724
|
+
def log_sent(type, where, message)
|
725
|
+
case type
|
726
|
+
when "NOTICE"
|
727
|
+
if(where =~ /^#/)
|
728
|
+
log "-=#{@nick}=- #{message}", where
|
729
|
+
elsif (where =~ /(\S*)!.*/)
|
730
|
+
log "[-=#{where}=-] #{message}", $1
|
731
|
+
else
|
732
|
+
log "[-=#{where}=-] #{message}"
|
733
|
+
end
|
734
|
+
when "PRIVMSG"
|
735
|
+
if(where =~ /^#/)
|
736
|
+
log "<#{@nick}> #{message}", where
|
737
|
+
elsif (where =~ /^(\S*)!.*$/)
|
738
|
+
log "[msg(#{where})] #{message}", $1
|
739
|
+
else
|
740
|
+
log "[msg(#{where})] #{message}", where
|
741
|
+
end
|
742
|
+
end
|
743
|
+
end
|
744
|
+
|
745
|
+
def onjoin(m)
|
746
|
+
@channels[m.channel] = IRCChannel.new(m.channel) unless(@channels.has_key?(m.channel))
|
747
|
+
if(m.address?)
|
748
|
+
debug "joined channel #{m.channel}"
|
749
|
+
log "@ Joined channel #{m.channel}", m.channel
|
750
|
+
else
|
751
|
+
log "@ #{m.sourcenick} joined channel #{m.channel}", m.channel
|
752
|
+
@channels[m.channel].users[m.sourcenick] = Hash.new
|
753
|
+
@channels[m.channel].users[m.sourcenick]["mode"] = ""
|
754
|
+
end
|
755
|
+
|
756
|
+
@plugins.delegate("listen", m)
|
757
|
+
@plugins.delegate("join", m)
|
758
|
+
end
|
759
|
+
|
760
|
+
def onpart(m)
|
761
|
+
if(m.address?)
|
762
|
+
debug "left channel #{m.channel}"
|
763
|
+
log "@ Left channel #{m.channel} (#{m.message})", m.channel
|
764
|
+
@channels.delete(m.channel)
|
765
|
+
else
|
766
|
+
log "@ #{m.sourcenick} left channel #{m.channel} (#{m.message})", m.channel
|
767
|
+
@channels[m.channel].users.delete(m.sourcenick)
|
768
|
+
end
|
769
|
+
|
770
|
+
# delegate to plugins
|
771
|
+
@plugins.delegate("listen", m)
|
772
|
+
@plugins.delegate("part", m)
|
773
|
+
end
|
774
|
+
|
775
|
+
# respond to being kicked from a channel
|
776
|
+
def onkick(m)
|
777
|
+
if(m.address?)
|
778
|
+
debug "kicked from channel #{m.channel}"
|
779
|
+
@channels.delete(m.channel)
|
780
|
+
log "@ You have been kicked from #{m.channel} by #{m.sourcenick} (#{m.message})", m.channel
|
781
|
+
else
|
782
|
+
@channels[m.channel].users.delete(m.sourcenick)
|
783
|
+
log "@ #{m.target} has been kicked from #{m.channel} by #{m.sourcenick} (#{m.message})", m.channel
|
784
|
+
end
|
785
|
+
|
786
|
+
@plugins.delegate("listen", m)
|
787
|
+
@plugins.delegate("kick", m)
|
788
|
+
end
|
789
|
+
|
790
|
+
def ontopic(m)
|
791
|
+
@channels[m.channel] = IRCChannel.new(m.channel) unless(@channels.has_key?(m.channel))
|
792
|
+
@channels[m.channel].topic = m.topic if !m.topic.nil?
|
793
|
+
@channels[m.channel].topic.timestamp = m.timestamp if !m.timestamp.nil?
|
794
|
+
@channels[m.channel].topic.by = m.source if !m.source.nil?
|
795
|
+
|
796
|
+
debug "topic of channel #{m.channel} is now #{@channels[m.channel].topic}"
|
797
|
+
end
|
798
|
+
|
799
|
+
# delegate a privmsg to auth, keyword or plugin handlers
|
800
|
+
def delegate_privmsg(message)
|
801
|
+
[@auth, @plugins, @keywords].each {|m|
|
802
|
+
break if m.privmsg(message)
|
803
|
+
}
|
804
|
+
end
|
805
|
+
|
806
|
+
end
|
807
|
+
|
808
|
+
end
|