rbot 0.9.9
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.
- 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
|