butler 1.8.1 → 1.8.2
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.txt +212 -0
- data/{README → README.txt} +0 -0
- data/Rakefile +16 -11
- data/bin/botcontrol +35 -14
- data/data/butler/dialogs/create.rb +29 -40
- data/data/butler/dialogs/create_config.rb +1 -1
- data/data/butler/dialogs/dir.rb +13 -0
- data/data/butler/dialogs/en/create.yaml +24 -10
- data/data/butler/dialogs/en/dir.yaml +5 -0
- data/data/butler/dialogs/en/help.yaml +28 -11
- data/data/butler/dialogs/en/quickcreate.yaml +14 -0
- data/data/butler/dialogs/help.rb +16 -4
- data/data/butler/dialogs/quickcreate.rb +49 -0
- data/data/butler/plugins/core/access.rb +211 -0
- data/data/butler/plugins/core/logout.rb +11 -11
- data/data/butler/plugins/core/plugins.rb +23 -41
- data/data/butler/plugins/dev/bleakhouse.rb +46 -0
- data/data/butler/plugins/games/roll.rb +1 -1
- data/data/butler/plugins/operator/deop.rb +15 -20
- data/data/butler/plugins/operator/devoice.rb +14 -20
- data/data/butler/plugins/operator/limit.rb +56 -21
- data/data/butler/plugins/operator/op.rb +15 -20
- data/data/butler/plugins/operator/voice.rb +15 -20
- data/data/butler/plugins/service/define.rb +3 -3
- data/data/butler/plugins/service/more.rb +40 -0
- data/data/butler/plugins/util/cycle.rb +1 -1
- data/data/butler/plugins/util/load.rb +5 -5
- data/data/butler/plugins/util/pong.rb +3 -2
- data/lib/access/privilege.rb +17 -0
- data/lib/access/role.rb +33 -2
- data/lib/access/savable.rb +6 -0
- data/lib/access/yamlbase.rb +1 -2
- data/lib/butler/bot.rb +40 -7
- data/lib/butler/debuglog.rb +17 -0
- data/lib/butler/dialog.rb +1 -1
- data/lib/butler/irc/{channels.rb → channellist.rb} +2 -2
- data/lib/butler/irc/client.rb +60 -79
- data/lib/butler/irc/client/filter.rb +12 -0
- data/lib/butler/irc/client/listener.rb +55 -0
- data/lib/butler/irc/client/listenerlist.rb +69 -0
- data/lib/butler/irc/hostmask.rb +31 -16
- data/lib/butler/irc/message.rb +3 -3
- data/lib/butler/irc/parser.rb +2 -2
- data/lib/butler/irc/parser/rfc2812.rb +2 -6
- data/lib/butler/irc/socket.rb +12 -6
- data/lib/butler/irc/string.rb +4 -0
- data/lib/butler/irc/user.rb +0 -6
- data/lib/butler/irc/{users.rb → userlist.rb} +2 -2
- data/lib/butler/irc/whois.rb +6 -0
- data/lib/butler/plugin.rb +48 -14
- data/lib/butler/plugin/configproxy.rb +20 -0
- data/lib/butler/plugin/mapper.rb +308 -24
- data/lib/butler/plugin/matcher.rb +3 -1
- data/lib/butler/plugin/more.rb +65 -0
- data/lib/butler/plugin/onhandlers.rb +4 -4
- data/lib/butler/plugin/trigger.rb +4 -2
- data/lib/butler/plugins.rb +1 -1
- data/lib/butler/session.rb +11 -0
- data/lib/butler/version.rb +1 -1
- data/lib/cloptions.rb +1 -1
- data/lib/diagnostics.rb +20 -0
- data/lib/dialogline.rb +1 -1
- data/lib/durations.rb +19 -6
- data/lib/event.rb +8 -5
- data/lib/installer.rb +10 -3
- data/lib/ostructfixed.rb +11 -0
- data/lib/ruby/kernel/daemonize.rb +1 -2
- data/test/butler/plugin/mapper.rb +46 -0
- metadata +28 -11
- data/CHANGELOG +0 -44
- data/data/butler/plugins/core/privilege.rb +0 -103
@@ -17,10 +17,10 @@ trigger "define"
|
|
17
17
|
def on_trigger
|
18
18
|
word = @message.post_arguments[1]
|
19
19
|
doc = Hpricot(open(Query%[@message.language, CGI.escape(word)]))
|
20
|
-
see_also = (doc/"//
|
21
|
-
found = (doc/"//
|
20
|
+
see_also = (doc/"//font[1]/p/a")[5..-1].map { |e| e.inner_text }
|
21
|
+
found = (doc/"//ul/font/li").find { |e| (e/"//a").inner_text.include?("wikipedia.org") }
|
22
22
|
if found then
|
23
|
-
|
23
|
+
more('![b]word![o]', found.inner_text.sub(%r{\w+\.wikipedia\.org/wiki/.*?$}, ''))
|
24
24
|
else
|
25
25
|
answer(:not_found, :search_string => word)
|
26
26
|
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright 2007 by Stefan Rusterholz.
|
3
|
+
# All rights reserved.
|
4
|
+
# See LICENSE.txt for permissions.
|
5
|
+
#++
|
6
|
+
|
7
|
+
|
8
|
+
|
9
|
+
def on_trigger(*arguments)
|
10
|
+
more = @message.from.session["more"]
|
11
|
+
if more && more.succ? then
|
12
|
+
more.succ
|
13
|
+
answer(more.show)
|
14
|
+
else
|
15
|
+
answer(:no_more)
|
16
|
+
end
|
17
|
+
@message.from.session.delete("more") unless more and more.succ?
|
18
|
+
end
|
19
|
+
|
20
|
+
__END__
|
21
|
+
---
|
22
|
+
:about:
|
23
|
+
:mail: "apeiros@gmx.net"
|
24
|
+
:version: "1.0.0"
|
25
|
+
:author: "Stefan Rusterholz"
|
26
|
+
:help:
|
27
|
+
en:
|
28
|
+
"": "Continue output."
|
29
|
+
:revision:
|
30
|
+
:plugin: 1
|
31
|
+
:strings:
|
32
|
+
:no_more:
|
33
|
+
en: Nothing more to display.
|
34
|
+
:summary:
|
35
|
+
en: "Continue output."
|
36
|
+
:trigger:
|
37
|
+
en: more
|
38
|
+
de: mehr
|
39
|
+
:usage:
|
40
|
+
en: "![b]more![o]"
|
@@ -14,7 +14,7 @@ configuration(
|
|
14
14
|
|
15
15
|
trigger "cycle"
|
16
16
|
|
17
|
-
def on_part(listener, user, channel)
|
17
|
+
def on_part(listener, user, channel, reason)
|
18
18
|
if (
|
19
19
|
user != butler.myself && # don't trigger if butler itself parts
|
20
20
|
channel.length == 1 && # trigger only if butler is the last remaining
|
@@ -6,11 +6,9 @@
|
|
6
6
|
|
7
7
|
|
8
8
|
|
9
|
-
trigger "load"
|
10
|
-
|
11
9
|
def on_trigger
|
12
|
-
ps_out = `ps -o vsz,rss,%cpu,%mem -p #{Process.pid}`
|
13
|
-
vsz, rss, cpu, pmem = ps_out.scan(/\d+(
|
10
|
+
ps_out = `ps -o vsz,rss,%cpu,%mem -p #{Process.pid.to_i}`
|
11
|
+
vsz, rss, cpu, pmem = ps_out.scan(/\d+(?:[.,]\d+)?/).map { |e| e.gsub(/,/,'.').to_f } # ps on 10.5.1 outputs ',' instead of '.' for MEM%
|
14
12
|
virtual, real = (vsz-rss).div(1024), rss.div(1024)
|
15
13
|
answer(:memusage, :real => real, :virtual => virtual, :cpu => cpu, :pmem => pmem)
|
16
14
|
rescue Exception => e
|
@@ -29,10 +27,12 @@ __END__
|
|
29
27
|
:author: "Stefan Rusterholz"
|
30
28
|
:usage:
|
31
29
|
en: "![b]load![o]"
|
30
|
+
:trigger:
|
31
|
+
en: load
|
32
32
|
:strings:
|
33
33
|
:memusage:
|
34
34
|
en: |
|
35
|
-
|
35
|
+
Memory: <%= real %>MB real, <%= virtual %>MB virtual (<%= pmem %>%), CPU: <%= cpu %>%.
|
36
36
|
:failure:
|
37
37
|
en: |
|
38
38
|
Failed with exception <%= exception %>.
|
@@ -6,8 +6,6 @@
|
|
6
6
|
|
7
7
|
|
8
8
|
|
9
|
-
trigger "ping"
|
10
|
-
|
11
9
|
def on_trigger
|
12
10
|
message.answer("pong")
|
13
11
|
end
|
@@ -22,6 +20,9 @@ __END__
|
|
22
20
|
:mail: "apeiros@gmx.net"
|
23
21
|
:version: "1.0.0"
|
24
22
|
:author: "Stefan Rusterholz"
|
23
|
+
:trigger:
|
24
|
+
en:
|
25
|
+
ping
|
25
26
|
:usage:
|
26
27
|
en: "![b]ping![o]"
|
27
28
|
:help:
|
data/lib/access/privilege.rb
CHANGED
@@ -58,6 +58,15 @@ class Access
|
|
58
58
|
def hash
|
59
59
|
@id.hash
|
60
60
|
end
|
61
|
+
|
62
|
+
def inspect # :nodoc:
|
63
|
+
"#<%s:0x%08x id=%s description=%s>" % [
|
64
|
+
self.class,
|
65
|
+
object_id << 1,
|
66
|
+
@id,
|
67
|
+
@description
|
68
|
+
]
|
69
|
+
end
|
61
70
|
end
|
62
71
|
|
63
72
|
class Privileges
|
@@ -118,5 +127,13 @@ class Access
|
|
118
127
|
return true if yield("")
|
119
128
|
false
|
120
129
|
end
|
130
|
+
|
131
|
+
def inspect # :nodoc:
|
132
|
+
"#<%s:0x%08x %s>" % [
|
133
|
+
self.class,
|
134
|
+
object_id << 1,
|
135
|
+
@privileges.keys.join(', ')
|
136
|
+
]
|
137
|
+
end
|
121
138
|
end
|
122
139
|
end
|
data/lib/access/role.rb
CHANGED
@@ -12,6 +12,7 @@ class Access
|
|
12
12
|
# an additional restriction (which is applied globally).
|
13
13
|
#
|
14
14
|
class Role
|
15
|
+
include Savable
|
15
16
|
|
16
17
|
# The module to extend the Database manager
|
17
18
|
module Base
|
@@ -36,7 +37,7 @@ class Access
|
|
36
37
|
data[:roles] = data[:roles].map { |role| roles[role] }
|
37
38
|
array = data.values_at(:id, :description)
|
38
39
|
array << data
|
39
|
-
role = new(*array)
|
40
|
+
role = Role.new(*array)
|
40
41
|
role.access = access
|
41
42
|
role.base = self
|
42
43
|
role
|
@@ -48,6 +49,12 @@ class Access
|
|
48
49
|
|
49
50
|
# The description of the role
|
50
51
|
attr_reader :description
|
52
|
+
|
53
|
+
# The roles this role belongs to
|
54
|
+
attr_reader :roles
|
55
|
+
|
56
|
+
# Privileges this role has granted
|
57
|
+
attr_reader :privileges
|
51
58
|
|
52
59
|
# Create a new Role
|
53
60
|
# role is a role-id, should be \w+
|
@@ -57,7 +64,13 @@ class Access
|
|
57
64
|
@id = role
|
58
65
|
@privileges = Privileges.new(self, other[:privileges])
|
59
66
|
@roles = Roles.new(self, other[:roles])
|
60
|
-
@description = description || "No description"
|
67
|
+
@description = (description || "No description").freeze
|
68
|
+
end
|
69
|
+
|
70
|
+
# Change the description of this role
|
71
|
+
def description=(value)
|
72
|
+
@description = value.freeze
|
73
|
+
save
|
61
74
|
end
|
62
75
|
|
63
76
|
# :nodoc:
|
@@ -87,6 +100,16 @@ class Access
|
|
87
100
|
def hash
|
88
101
|
@id.hash
|
89
102
|
end
|
103
|
+
|
104
|
+
def inspect # :nodoc:
|
105
|
+
"#<%s:0x%08x description=%s privileges=%s roles=%s>" % [
|
106
|
+
self.class,
|
107
|
+
object_id << 1,
|
108
|
+
@description,
|
109
|
+
@privileges.inspect,
|
110
|
+
@roles.inspect,
|
111
|
+
]
|
112
|
+
end
|
90
113
|
end
|
91
114
|
|
92
115
|
# A list of roles
|
@@ -133,5 +156,13 @@ class Access
|
|
133
156
|
def storable
|
134
157
|
@roles.map { |role| role.id }
|
135
158
|
end
|
159
|
+
|
160
|
+
def inspect # :nodoc:
|
161
|
+
"#<%s:0x%08x roles=%s>" % [
|
162
|
+
self.class,
|
163
|
+
object_id << 1,
|
164
|
+
@roles.map { |r| r.id }.join(', '),
|
165
|
+
]
|
166
|
+
end
|
136
167
|
end
|
137
168
|
end
|
data/lib/access/savable.rb
CHANGED
@@ -16,8 +16,14 @@ class Access
|
|
16
16
|
# The "Base" instance that manages this record
|
17
17
|
attr_accessor :base
|
18
18
|
|
19
|
+
# save changes to this record
|
19
20
|
def save
|
20
21
|
base.save(id())
|
21
22
|
end
|
23
|
+
|
24
|
+
# delete this record from the database
|
25
|
+
def delete
|
26
|
+
base.delete(id())
|
27
|
+
end
|
22
28
|
end
|
23
29
|
end
|
data/lib/access/yamlbase.rb
CHANGED
data/lib/butler/bot.rb
CHANGED
@@ -8,18 +8,20 @@
|
|
8
8
|
|
9
9
|
require 'butler'
|
10
10
|
require 'butler/irc/client'
|
11
|
+
require 'butler/debuglog'
|
11
12
|
require 'configuration'
|
12
13
|
require 'log'
|
13
14
|
require 'butler/plugins'
|
15
|
+
require 'butler/session'
|
14
16
|
require 'ruby/string/arguments'
|
15
17
|
require 'ruby/string/post_arguments'
|
16
18
|
require 'scheduler'
|
17
19
|
require 'set'
|
18
20
|
require 'thread'
|
21
|
+
require 'timeout'
|
19
22
|
|
20
23
|
|
21
24
|
|
22
|
-
# FIXME: credits @ daemonize lib
|
23
25
|
class Butler
|
24
26
|
class Bot < IRC::Client
|
25
27
|
include Log::Comfort
|
@@ -42,10 +44,13 @@ class Butler
|
|
42
44
|
:access => @base+'/access',
|
43
45
|
:base => @base,
|
44
46
|
:config => @base+'/config',
|
47
|
+
:lib => @base+'/lib',
|
45
48
|
:log => @base+'/log',
|
46
49
|
:plugins => @base+'/plugins',
|
47
50
|
:strings => @base+'/strings'
|
48
51
|
)
|
52
|
+
$LOAD_PATH.unshift(@path.lib)
|
53
|
+
require 'butlerinit' if File.exist?(@path.lib+"/butlerinit.rb")
|
49
54
|
@logger = $stderr
|
50
55
|
@config = Configuration.new(@base+'/config')
|
51
56
|
@scheduler = Scheduler.new
|
@@ -71,6 +76,12 @@ class Butler
|
|
71
76
|
@commands = {}
|
72
77
|
@plugins = Plugins.new(self, @base+'/plugins')
|
73
78
|
|
79
|
+
if $DEBUG then
|
80
|
+
@irc.extend DebugLog
|
81
|
+
@irc.raw_log = File.open(@path.log+'/debug.log', 'wb')
|
82
|
+
@irc.raw_log.sync = true
|
83
|
+
end
|
84
|
+
|
74
85
|
subscribe(:PRIVMSG, -10, &method(:invoke_commands))
|
75
86
|
subscribe(:NOTICE, -10, &method(:invoke_commands))
|
76
87
|
|
@@ -111,7 +122,7 @@ class Butler
|
|
111
122
|
end
|
112
123
|
}
|
113
124
|
end
|
114
|
-
|
125
|
+
|
115
126
|
def add_command(command)
|
116
127
|
@commands[command.language] ||= {}
|
117
128
|
@commands[command.language][command.trigger] ||= SortedSet.new
|
@@ -148,26 +159,46 @@ class Butler
|
|
148
159
|
@config["connections/main/real"]
|
149
160
|
)
|
150
161
|
info("Logged in")
|
151
|
-
|
152
|
-
if pass && @myself.nick.downcase != nick.downcase then
|
162
|
+
if pass && @myself.nick.same_nick?(nick) then
|
153
163
|
@irc.ghost(nick, pass)
|
154
164
|
sleep(1) # FIXME, do a wait_for or similar
|
155
165
|
@irc.nick(nick)
|
156
166
|
end
|
157
167
|
@irc.identify(pass) if pass
|
158
168
|
join(*@config["connections/main/channels"])
|
169
|
+
plugins_dispatch(:on_login, "main")
|
159
170
|
end
|
160
171
|
|
161
172
|
def on_disconnect(reason)
|
162
173
|
return if reason == :quit
|
174
|
+
plugins_dispatch(:on_disconnect, reason)
|
163
175
|
unless @reconnect[1].zero? then
|
164
176
|
@reconnect[1] -= 1
|
165
177
|
sleep(@reconnect[0])
|
166
178
|
login
|
167
179
|
end
|
168
180
|
end
|
169
|
-
|
170
181
|
|
182
|
+
def quit(reason=nil, *args)
|
183
|
+
timeout(3) {
|
184
|
+
plugins_dispatch(:on_quit, reason, *args).join
|
185
|
+
}
|
186
|
+
ensure
|
187
|
+
super(reason)
|
188
|
+
end
|
189
|
+
|
190
|
+
def plugins_dispatch(event, *args)
|
191
|
+
Thread.new { # avoid total lockup due to an improperly written on_disconnect
|
192
|
+
begin
|
193
|
+
@plugins.instances.each { |plugin|
|
194
|
+
plugin.send(event, *args)
|
195
|
+
}
|
196
|
+
rescue Exception => e
|
197
|
+
exception(e)
|
198
|
+
end
|
199
|
+
}
|
200
|
+
end
|
201
|
+
|
171
202
|
def output_to_logfiles
|
172
203
|
# Errors still go to $stderr, $stdin handles puts as "info" level $stderr prints
|
173
204
|
@logger = Log.file(@path.log+'/error.log')
|
@@ -186,12 +217,13 @@ class Butler
|
|
186
217
|
end
|
187
218
|
end # Bot
|
188
219
|
|
189
|
-
class IRC::
|
220
|
+
class IRC::UserList
|
190
221
|
attr_reader :client
|
191
222
|
end
|
192
223
|
|
193
224
|
class IRC::User
|
194
225
|
attr_accessor :access
|
226
|
+
attr_reader :session
|
195
227
|
|
196
228
|
def authorized?(*args)
|
197
229
|
@access.authorized?(*args)
|
@@ -200,7 +232,8 @@ class Butler
|
|
200
232
|
alias butler_initialize initialize unless method_defined? :butler_initialize
|
201
233
|
def initialize(users, *args, &block)
|
202
234
|
butler_initialize(users, *args, &block)
|
203
|
-
@access
|
235
|
+
@access = users.client.access.default_user
|
236
|
+
@session = Session.new
|
204
237
|
end
|
205
238
|
end # IRC::User
|
206
239
|
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class Butler
|
2
|
+
module DebugLog
|
3
|
+
attr_accessor :raw_log
|
4
|
+
|
5
|
+
def read
|
6
|
+
data = super
|
7
|
+
@raw_log.printf("%s < %p\n", Time.now, data)
|
8
|
+
data
|
9
|
+
end
|
10
|
+
|
11
|
+
def write(data)
|
12
|
+
rv = super
|
13
|
+
@raw_log.printf("%s > %p\n", Time.now, data)
|
14
|
+
rv
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/butler/dialog.rb
CHANGED
@@ -29,7 +29,7 @@ class Butler
|
|
29
29
|
|
30
30
|
def initialize(plugin)
|
31
31
|
@plugin = plugin
|
32
|
-
@plugin.butler.subscribe(:PRIVMSG,
|
32
|
+
@plugin.butler.subscribe(:PRIVMSG, 20) { |listener, message|
|
33
33
|
if message.private? then
|
34
34
|
postpone
|
35
35
|
dialog(listener, message)
|
@@ -8,7 +8,7 @@
|
|
8
8
|
|
9
9
|
require 'butler/irc/string'
|
10
10
|
require 'butler/irc/channel'
|
11
|
-
require 'butler/irc/
|
11
|
+
require 'butler/irc/userlist'
|
12
12
|
require 'thread'
|
13
13
|
|
14
14
|
|
@@ -31,7 +31,7 @@ class Butler
|
|
31
31
|
]
|
32
32
|
|
33
33
|
# Butler::IRC::Channel represents a channel in IRC.
|
34
|
-
class
|
34
|
+
class ChannelList
|
35
35
|
include Enumerable
|
36
36
|
|
37
37
|
def initialize(client)
|
data/lib/butler/irc/client.rb
CHANGED
@@ -1,15 +1,27 @@
|
|
1
|
-
|
1
|
+
#--
|
2
|
+
# Copyright 2007 by Stefan Rusterholz.
|
3
|
+
# All rights reserved.
|
4
|
+
# See LICENSE.txt for permissions.
|
5
|
+
#++
|
6
|
+
|
7
|
+
|
8
|
+
|
9
|
+
require 'butler/irc/channellist'
|
10
|
+
require 'butler/irc/client/filter'
|
11
|
+
require 'butler/irc/client/listener'
|
12
|
+
require 'butler/irc/client/listenerlist'
|
2
13
|
require 'butler/irc/parser'
|
3
14
|
require 'butler/irc/socket'
|
4
|
-
require 'butler/irc/
|
15
|
+
require 'butler/irc/userlist'
|
16
|
+
require 'butler/irc/whois'
|
5
17
|
require 'log/comfort'
|
6
18
|
require 'timeout'
|
7
19
|
require 'thread'
|
8
20
|
|
21
|
+
|
22
|
+
|
9
23
|
class Butler
|
10
24
|
module IRC
|
11
|
-
# Reply for Butler::IRC::Client#whois
|
12
|
-
Whois = Struct.new(:exists, :nick, :user, :host, :real, :registered, :channels, :server, :idle, :signon)
|
13
25
|
|
14
26
|
# == Description
|
15
27
|
# Wraps Butler::IRC::Socket, providing methods to be aware of the environment.
|
@@ -28,7 +40,7 @@ class Butler
|
|
28
40
|
# when /#{irc_client.myself.nick}[,:]/
|
29
41
|
# message.answer("#{message.from.nick}, you spoke to me?")
|
30
42
|
# when :JOIN
|
31
|
-
#
|
43
|
+
# irc_client.irc.notice("Welcome to #{message.channel}!", message.from)
|
32
44
|
# end
|
33
45
|
# puts "received: #{message}"
|
34
46
|
# }
|
@@ -38,36 +50,7 @@ class Butler
|
|
38
50
|
include Log::Comfort
|
39
51
|
|
40
52
|
class Terminate < RuntimeError; end
|
41
|
-
# Created by Butler::IRC::Client#subscribe() and similar methods
|
42
|
-
Listener = Struct.new(:priority, :callback, :args, :unsubscriber) unless defined? Listener
|
43
|
-
class Listener
|
44
|
-
alias set_priority priority=
|
45
|
-
private :set_priority
|
46
|
-
private :unsubscriber
|
47
|
-
|
48
|
-
def initialize(priority, callback, args=[], &unsubscriber)
|
49
|
-
super(priority, callback, args, unsubscriber)
|
50
|
-
end
|
51
|
-
|
52
|
-
# will remove this listener from the clients dispatcher forever
|
53
|
-
def unsubscribe
|
54
|
-
unsubscriber.call(self)
|
55
|
-
end
|
56
53
|
|
57
|
-
# set the priority of this listener
|
58
|
-
# see Butler::IRC::Client#subscribe() for infos about priority
|
59
|
-
def priority=(value)
|
60
|
-
set_priority(value)
|
61
|
-
container.sort_by { |l| -l.priority }
|
62
|
-
end
|
63
|
-
end
|
64
|
-
module Filter # :nodoc:
|
65
|
-
attr_accessor :listener
|
66
|
-
def unsubscribe
|
67
|
-
listener.unsubscribe
|
68
|
-
end
|
69
|
-
end
|
70
|
-
|
71
54
|
# The timeout defaults
|
72
55
|
DefaultTimeout = {
|
73
56
|
:login => 150,
|
@@ -92,7 +75,10 @@ class Butler
|
|
92
75
|
# server_charset
|
93
76
|
attr_reader :channel_charset
|
94
77
|
|
78
|
+
# The channels the client participates
|
95
79
|
attr_reader :channels
|
80
|
+
|
81
|
+
# The users visible to the client
|
96
82
|
attr_reader :users
|
97
83
|
|
98
84
|
# The Butler::IRC::Socket, most send commands have to be used on this
|
@@ -101,30 +87,37 @@ class Butler
|
|
101
87
|
# The user representing the bot
|
102
88
|
attr_reader :myself
|
103
89
|
|
90
|
+
# A callback invoked on disconnects, accepts one argument: reason, which can be
|
91
|
+
# * :quit: you told the client to quit
|
92
|
+
# * :disconnect: the server disconnected your client
|
93
|
+
# * :error: an error occurred in the read-thread (shouldn't happen)
|
94
|
+
attr_accessor :disconnect_callback
|
95
|
+
|
96
|
+
# Arguments:
|
97
|
+
# * server: the server to connect to
|
104
98
|
def initialize(server, options={}, &on_disconnect)
|
105
|
-
options
|
106
|
-
@
|
107
|
-
@
|
108
|
-
@
|
109
|
-
@
|
110
|
-
@channels
|
111
|
-
@
|
99
|
+
options = DefaultOptions.merge(options)
|
100
|
+
@disconnect_callback =
|
101
|
+
@logger = nil
|
102
|
+
@users = UserList.new(self)
|
103
|
+
@channels = ChannelList.new(self)
|
104
|
+
@users.channels = @channels
|
105
|
+
@channels.users = @users
|
106
|
+
@parser = Parser.new(self, @users, @channels, "rfc2812", "generic")
|
112
107
|
|
113
|
-
@client_charset
|
114
|
-
@server_charset
|
108
|
+
@client_charset = options.delete(:client_charset)
|
109
|
+
@server_charset = options.delete(:server_charset)
|
115
110
|
# proc needed because @server_charset might change
|
116
|
-
@channel_charset
|
111
|
+
@channel_charset = Hash.new { |h,k| @server_charset }.merge(options.delete(:channel_charset))
|
117
112
|
|
118
|
-
@timeout
|
113
|
+
@timeout = DefaultTimeout.merge(options.delete(:timeout))
|
119
114
|
|
120
|
-
@irc
|
115
|
+
@irc = Socket.new(options.delete(:server) || server, options) # the socket, all methods to the socket are wrapped
|
121
116
|
|
122
|
-
@
|
123
|
-
@
|
124
|
-
@
|
125
|
-
@
|
126
|
-
@thread_read = nil
|
127
|
-
@myself = @users.myself
|
117
|
+
@listener = ListenerList.new
|
118
|
+
@event_loop = Queue.new
|
119
|
+
@thread_read = nil
|
120
|
+
@myself = @users.myself
|
128
121
|
|
129
122
|
subscribe(:PING, 100) { |listener, message| @irc.pong(message.pong) }
|
130
123
|
end
|
@@ -139,31 +132,20 @@ class Butler
|
|
139
132
|
# priority may be any numeric, higher priority is dispatched to first,
|
140
133
|
# lower priority later
|
141
134
|
# returns an Butler::IRC::Client::Listener
|
142
|
-
def subscribe(
|
143
|
-
|
144
|
-
|
145
|
-
listener = Listener.new(priority, callback, args) { |item|
|
146
|
-
@dispatch_lock.synchronize {
|
147
|
-
@listener.delete(id)
|
148
|
-
@listen[symbol].delete(item)
|
149
|
-
@listen.delete(symbol) if @listen[symbol].empty?
|
150
|
-
}
|
151
|
-
}
|
152
|
-
@dispatch_lock.synchronize {
|
153
|
-
@listener[id] = listener
|
154
|
-
@listen[symbol] << listener
|
155
|
-
@listen[symbol].sort_by { |l| -l.priority }
|
156
|
-
}
|
135
|
+
def subscribe(symbols=nil, priority=0, *args, &callback)
|
136
|
+
listener = Listener.new(symbols, priority, *args, &callback)
|
137
|
+
@listener.subscribe(listener)
|
157
138
|
listener
|
158
139
|
end
|
140
|
+
|
141
|
+
# subscribe a Listener instance (or anything that emulates its interface)
|
142
|
+
def subscribe_listener(listener)
|
143
|
+
@listener.subscribe(listener)
|
144
|
+
end
|
159
145
|
|
160
|
-
# unsubscribe a listener
|
161
|
-
def unsubscribe(
|
162
|
-
@
|
163
|
-
@listener.delete(id)
|
164
|
-
@listen[symbol].delete(item)
|
165
|
-
@listen.delete(symbol) if @listen[symbol].empty?
|
166
|
-
}
|
146
|
+
# unsubscribe a listener
|
147
|
+
def unsubscribe(listener)
|
148
|
+
@listener.unsubscribe(listener)
|
167
149
|
end
|
168
150
|
|
169
151
|
# blocks current thread until a Message with symbol
|
@@ -313,7 +295,9 @@ class Butler
|
|
313
295
|
@irc.close
|
314
296
|
end
|
315
297
|
|
298
|
+
# Called on disconnects, see disconnect_callback attribute documentation
|
316
299
|
def on_disconnect(reason)
|
300
|
+
@disconnect_callback.call(reasons) if @disconnect_callback
|
317
301
|
end
|
318
302
|
|
319
303
|
def event_loop(priority=-1)
|
@@ -335,11 +319,8 @@ class Butler
|
|
335
319
|
def process(message)
|
336
320
|
message = @parser.server_message(message)
|
337
321
|
message.transcode!(@channel_charset[message.channel], @client_charset)
|
338
|
-
@
|
339
|
-
|
340
|
-
if @listen.has_key?(message.symbol)
|
341
|
-
@listen[message.symbol].each { |listener| listener.callback.call(listener, message, *listener.args) }
|
342
|
-
end
|
322
|
+
@listener.synchronized_each_for(message.symbol) { |listener|
|
323
|
+
listener.call(message)
|
343
324
|
}
|
344
325
|
rescue Terminate, Errno::EPIPE => error
|
345
326
|
raise error # on these errors we got to get out of the loop
|