butler 1.8.1 → 1.8.2
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/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
|