butler 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (164) hide show
  1. data/CHANGELOG +4 -0
  2. data/GPL.txt +340 -0
  3. data/LICENSE.txt +52 -0
  4. data/README +37 -0
  5. data/Rakefile +334 -0
  6. data/bin/botcontrol +230 -0
  7. data/data/butler/config_template.yaml +4 -0
  8. data/data/butler/dialogs/backup.rb +19 -0
  9. data/data/butler/dialogs/botcontrol.rb +4 -0
  10. data/data/butler/dialogs/config.rb +1 -0
  11. data/data/butler/dialogs/create.rb +53 -0
  12. data/data/butler/dialogs/delete.rb +3 -0
  13. data/data/butler/dialogs/en/backup.yaml +6 -0
  14. data/data/butler/dialogs/en/botcontrol.yaml +5 -0
  15. data/data/butler/dialogs/en/create.yaml +11 -0
  16. data/data/butler/dialogs/en/delete.yaml +2 -0
  17. data/data/butler/dialogs/en/help.yaml +17 -0
  18. data/data/butler/dialogs/en/info.yaml +13 -0
  19. data/data/butler/dialogs/en/list.yaml +4 -0
  20. data/data/butler/dialogs/en/notyetimplemented.yaml +2 -0
  21. data/data/butler/dialogs/en/rename.yaml +3 -0
  22. data/data/butler/dialogs/en/start.yaml +3 -0
  23. data/data/butler/dialogs/en/sync_plugins.yaml +3 -0
  24. data/data/butler/dialogs/en/uninstall.yaml +5 -0
  25. data/data/butler/dialogs/en/unknown_command.yaml +2 -0
  26. data/data/butler/dialogs/help.rb +11 -0
  27. data/data/butler/dialogs/info.rb +27 -0
  28. data/data/butler/dialogs/interactive.rb +1 -0
  29. data/data/butler/dialogs/list.rb +10 -0
  30. data/data/butler/dialogs/notyetimplemented.rb +1 -0
  31. data/data/butler/dialogs/rename.rb +4 -0
  32. data/data/butler/dialogs/selectbot.rb +2 -0
  33. data/data/butler/dialogs/start.rb +5 -0
  34. data/data/butler/dialogs/sync_plugins.rb +30 -0
  35. data/data/butler/dialogs/uninstall.rb +17 -0
  36. data/data/butler/dialogs/unknown_command.rb +1 -0
  37. data/data/butler/plugins/core/logout.rb +41 -0
  38. data/data/butler/plugins/core/plugins.rb +134 -0
  39. data/data/butler/plugins/core/privilege.rb +103 -0
  40. data/data/butler/plugins/core/user.rb +166 -0
  41. data/data/butler/plugins/dev/eval.rb +64 -0
  42. data/data/butler/plugins/dev/nometa.rb +14 -0
  43. data/data/butler/plugins/dev/onhandlers.rb +93 -0
  44. data/data/butler/plugins/dev/raw.rb +36 -0
  45. data/data/butler/plugins/dev/rawlog.rb +77 -0
  46. data/data/butler/plugins/games/eightball.rb +54 -0
  47. data/data/butler/plugins/games/mastermind.rb +174 -0
  48. data/data/butler/plugins/irc/action.rb +36 -0
  49. data/data/butler/plugins/irc/join.rb +38 -0
  50. data/data/butler/plugins/irc/notice.rb +36 -0
  51. data/data/butler/plugins/irc/part.rb +38 -0
  52. data/data/butler/plugins/irc/privmsg.rb +36 -0
  53. data/data/butler/plugins/irc/quit.rb +36 -0
  54. data/data/butler/plugins/operator/deop.rb +41 -0
  55. data/data/butler/plugins/operator/devoice.rb +41 -0
  56. data/data/butler/plugins/operator/limit.rb +47 -0
  57. data/data/butler/plugins/operator/op.rb +41 -0
  58. data/data/butler/plugins/operator/voice.rb +41 -0
  59. data/data/butler/plugins/public/help.rb +69 -0
  60. data/data/butler/plugins/public/login.rb +72 -0
  61. data/data/butler/plugins/public/usage.rb +49 -0
  62. data/data/butler/plugins/service/clones.rb +56 -0
  63. data/data/butler/plugins/service/define.rb +47 -0
  64. data/data/butler/plugins/service/log.rb +183 -0
  65. data/data/butler/plugins/service/svn.rb +91 -0
  66. data/data/butler/plugins/util/cycle.rb +98 -0
  67. data/data/butler/plugins/util/load.rb +41 -0
  68. data/data/butler/plugins/util/pong.rb +29 -0
  69. data/data/butler/strings/random/acknowledge.en.yaml +5 -0
  70. data/data/butler/strings/random/gratitude.en.yaml +3 -0
  71. data/data/butler/strings/random/hello.en.yaml +4 -0
  72. data/data/butler/strings/random/ignorance.en.yaml +7 -0
  73. data/data/butler/strings/random/ignorance_about.en.yaml +3 -0
  74. data/data/butler/strings/random/insult.en.yaml +3 -0
  75. data/data/butler/strings/random/rejection.en.yaml +12 -0
  76. data/data/man/botcontrol.1 +17 -0
  77. data/lib/access.rb +187 -0
  78. data/lib/access/admin.rb +16 -0
  79. data/lib/access/privilege.rb +122 -0
  80. data/lib/access/role.rb +102 -0
  81. data/lib/access/savable.rb +18 -0
  82. data/lib/access/user.rb +180 -0
  83. data/lib/access/yamlbase.rb +126 -0
  84. data/lib/butler.rb +188 -0
  85. data/lib/butler/bot.rb +247 -0
  86. data/lib/butler/control.rb +93 -0
  87. data/lib/butler/dialog.rb +64 -0
  88. data/lib/butler/initialvalues.rb +40 -0
  89. data/lib/butler/irc/channel.rb +135 -0
  90. data/lib/butler/irc/channels.rb +96 -0
  91. data/lib/butler/irc/client.rb +351 -0
  92. data/lib/butler/irc/hostmask.rb +53 -0
  93. data/lib/butler/irc/message.rb +184 -0
  94. data/lib/butler/irc/parser.rb +125 -0
  95. data/lib/butler/irc/parser/commands.rb +83 -0
  96. data/lib/butler/irc/parser/generic.rb +343 -0
  97. data/lib/butler/irc/socket.rb +378 -0
  98. data/lib/butler/irc/string.rb +186 -0
  99. data/lib/butler/irc/topic.rb +15 -0
  100. data/lib/butler/irc/user.rb +265 -0
  101. data/lib/butler/irc/users.rb +112 -0
  102. data/lib/butler/plugin.rb +249 -0
  103. data/lib/butler/plugin/configproxy.rb +35 -0
  104. data/lib/butler/plugin/mapper.rb +85 -0
  105. data/lib/butler/plugin/matcher.rb +55 -0
  106. data/lib/butler/plugin/onhandlers.rb +70 -0
  107. data/lib/butler/plugin/trigger.rb +58 -0
  108. data/lib/butler/plugins.rb +147 -0
  109. data/lib/butler/version.rb +17 -0
  110. data/lib/cloptions.rb +217 -0
  111. data/lib/cloptions/adapters.rb +24 -0
  112. data/lib/cloptions/switch.rb +132 -0
  113. data/lib/configuration.rb +223 -0
  114. data/lib/dialogline.rb +296 -0
  115. data/lib/dialogline/localizations.rb +24 -0
  116. data/lib/durations.rb +57 -0
  117. data/lib/event.rb +295 -0
  118. data/lib/event/at.rb +64 -0
  119. data/lib/event/every.rb +56 -0
  120. data/lib/event/timed.rb +112 -0
  121. data/lib/installer.rb +75 -0
  122. data/lib/iterator.rb +34 -0
  123. data/lib/log.rb +68 -0
  124. data/lib/log/comfort.rb +85 -0
  125. data/lib/log/converter.rb +23 -0
  126. data/lib/log/entry.rb +152 -0
  127. data/lib/log/fakeio.rb +55 -0
  128. data/lib/log/file.rb +54 -0
  129. data/lib/log/filereader.rb +81 -0
  130. data/lib/log/forward.rb +49 -0
  131. data/lib/log/methods.rb +39 -0
  132. data/lib/log/nolog.rb +18 -0
  133. data/lib/log/splitter.rb +26 -0
  134. data/lib/ostructfixed.rb +26 -0
  135. data/lib/ruby/array/columnize.rb +38 -0
  136. data/lib/ruby/dir/mktree.rb +28 -0
  137. data/lib/ruby/enumerable/join.rb +13 -0
  138. data/lib/ruby/exception/detailed.rb +24 -0
  139. data/lib/ruby/file/append.rb +11 -0
  140. data/lib/ruby/file/write.rb +11 -0
  141. data/lib/ruby/hash/zip.rb +15 -0
  142. data/lib/ruby/kernel/bench.rb +15 -0
  143. data/lib/ruby/kernel/daemonize.rb +42 -0
  144. data/lib/ruby/kernel/non_verbose.rb +17 -0
  145. data/lib/ruby/kernel/safe_fork.rb +18 -0
  146. data/lib/ruby/range/stepped.rb +11 -0
  147. data/lib/ruby/string/arguments.rb +72 -0
  148. data/lib/ruby/string/chunks.rb +15 -0
  149. data/lib/ruby/string/post_arguments.rb +44 -0
  150. data/lib/ruby/string/unescaped.rb +17 -0
  151. data/lib/scheduler.rb +164 -0
  152. data/lib/scriptfile.rb +101 -0
  153. data/lib/templater.rb +86 -0
  154. data/test/cloptions.rb +134 -0
  155. data/test/cv.rb +28 -0
  156. data/test/irc/client.rb +85 -0
  157. data/test/irc/client_login.txt +53 -0
  158. data/test/irc/client_subscribe.txt +8 -0
  159. data/test/irc/message.rb +30 -0
  160. data/test/irc/messages.txt +64 -0
  161. data/test/irc/parser.rb +13 -0
  162. data/test/irc/profile_parser.rb +12 -0
  163. data/test/irc/users.rb +28 -0
  164. metadata +256 -0
@@ -0,0 +1,96 @@
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/string'
10
+ require 'butler/irc/channel'
11
+ require 'butler/irc/users'
12
+ require 'thread'
13
+
14
+
15
+
16
+ class Butler
17
+ module IRC
18
+ CHANNEL_SIGNALS = [
19
+ :joined, # myself joined a channel
20
+ :parted, # myself parted the channel
21
+ :kicked, # myself got kicked from the channel
22
+ :quitted, # myself has quit
23
+ :left, # same as :parted || :kicked || :quitted
24
+ :join, # the user joined the channel
25
+ :part, # the user parted the channel
26
+ :kick, # the user got kicked from the channel
27
+ :quit, # the user has quit
28
+ :leave, # same as :part || :kick || :quit
29
+ :topic, # topic has changed
30
+ :mode, # mode has changed
31
+ ]
32
+
33
+ # Butler::IRC::Channel represents a channel in IRC.
34
+ class Channels
35
+ include Enumerable
36
+
37
+ def initialize(client)
38
+ @all = {}
39
+ @client = client
40
+ @users = nil
41
+ @lock = Mutex.new
42
+ end
43
+
44
+ def users=(users)
45
+ raise "Can't set @users twice" if @users
46
+ @users = users
47
+ end
48
+
49
+ # Will return the single instance representing the channel
50
+ # with name 'channel'.
51
+ def create(channel, *args)
52
+ @lock.synchronize {
53
+ channel = self[channel] || Channel.new(@users, channel, *args)
54
+ @all[channel.to_str] = channel
55
+ }
56
+ end
57
+
58
+ # Iterates through all known channels (@all)
59
+ def each(&block)
60
+ @all.each_value(&block)
61
+ end
62
+
63
+ # returns channelcount
64
+ def length
65
+ @all.length
66
+ end
67
+
68
+ # returns the lowercased channelnames
69
+ def channel_names
70
+ @all.keys
71
+ end
72
+
73
+ # returns all channels with that name (names should
74
+ # be normalized to lowercased name)
75
+ #
76
+ def map_names(*names)
77
+ @all.values_at(*names)
78
+ end
79
+
80
+ # Remove a channel from the channels-list
81
+ def delete(channel)
82
+ @all.delete(channel.to_str.downcase)
83
+ end
84
+
85
+ # Remove user from all channels
86
+ def delete_user(user, reason) #:nodoc:
87
+ @all.each_value { |channel| channel.delete_user(user, reason) }
88
+ end
89
+
90
+ # Get the channel with name 'channel'
91
+ def [](channel)
92
+ @all[channel.to_str.downcase]
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,351 @@
1
+ require 'butler/irc/channels'
2
+ require 'butler/irc/parser'
3
+ require 'butler/irc/socket'
4
+ require 'butler/irc/users'
5
+ require 'log/comfort'
6
+ require 'timeout'
7
+ require 'thread'
8
+
9
+ class Butler
10
+ module IRC
11
+ # Reply for Butler::IRC::Client#whois
12
+ Whois = Struct.new(:exists, :nick, :user, :host, :real, :registered, :channels, :server, :idle, :signon)
13
+
14
+ # == Description
15
+ # Wraps Butler::IRC::Socket, providing methods to be aware of the environment.
16
+ # It parses messages, keeps track of users and channels, is aware of events,
17
+ # responds to pings, provides simple methods for requests (e.g. whois,
18
+ # banlist, ...) and offers dispatchers for messages and scheduled events.
19
+ # Butler::IRC::Client uses a separated thread for reading and dispatching.
20
+ #
21
+ # == Synopsis
22
+ # irc_client = Butler::IRC::Client.new('irc.server.com', :port => 6667, :server_charset => 'utf-8')
23
+ # irc_client.subscribe(:QUIT) { |listener, message| "#{message.from.nick} has left us..." }
24
+ # puts "Whois 'nickname':", irc_client.whois("nickname")
25
+ # puts *irc_client.banlist("#channel")
26
+ # irc_client.event_loop { |message|
27
+ # case message
28
+ # when /#{irc_client.myself.nick}[,:]/
29
+ # message.answer("#{message.from.nick}, you spoke to me?")
30
+ # when :JOIN
31
+ # message.from.notice("Welcome to #{message.channel}!")
32
+ # end
33
+ # puts "received: #{message}"
34
+ # }
35
+ # puts "If this point is reached, client has ended"
36
+ #
37
+ class Client
38
+ class Terminate < RuntimeError; end
39
+ # Created by Butler::IRC::Client#subscribe() and similar methods
40
+ Listener = Struct.new(:priority, :callback, :args, :unsubscriber) unless defined? Listener
41
+ class Listener
42
+ alias set_priority priority=
43
+ private :set_priority
44
+ private :unsubscriber
45
+
46
+ def initialize(priority, callback, args=[], &unsubscriber)
47
+ super(priority, callback, args, unsubscriber)
48
+ end
49
+
50
+ # will remove this listener from the clients dispatcher forever
51
+ def unsubscribe
52
+ unsubscriber.call(self)
53
+ end
54
+
55
+ # set the priority of this listener
56
+ # see Butler::IRC::Client#subscribe() for infos about priority
57
+ def priority=(value)
58
+ set_priority(value)
59
+ container.sort_by { |l| -l.priority }
60
+ end
61
+ end
62
+ module Filter # :nodoc:
63
+ attr_accessor :listener
64
+ def unsubscribe
65
+ listener.unsubscribe
66
+ end
67
+ end
68
+
69
+ # The timeout defaults
70
+ DefaultTimeout = {
71
+ :login => 150,
72
+ }
73
+
74
+ # Defaults for the opts argument in Butler::Bot.new
75
+ DefaultOptions = {
76
+ :client_charset => 'utf-8',
77
+ :server_charset => 'utf-8',
78
+ :channel_charset => {},
79
+ :timeout => {},
80
+ }
81
+
82
+ # The charset the client uses
83
+ attr_accessor :client_charset
84
+
85
+ # The charset the server needs messages sent to him and sends message to
86
+ # this client in.
87
+ attr_accessor :server_charset
88
+
89
+ # The charsets of individual channel, only set if they differ from
90
+ # server_charset
91
+ attr_reader :channel_charset
92
+
93
+ attr_reader :channels
94
+ attr_reader :users
95
+
96
+ # The Butler::IRC::Socket, most send commands have to be used on this
97
+ attr_reader :irc
98
+
99
+ # The user representing the bot
100
+ attr_reader :myself
101
+
102
+ def initialize(server, options={}, &on_disconnect)
103
+ options = DefaultOptions.merge(options)
104
+ @users = Users.new(self)
105
+ @channels = Channels.new(self)
106
+ @users.channels = @channels
107
+ @channels.users = @users
108
+ @parser = Parser.new(self, @users, @channels)
109
+
110
+ @client_charset = options.delete(:client_charset)
111
+ @server_charset = options.delete(:server_charset)
112
+ # proc needed because @server_charset might change
113
+ @channel_charset = Hash.new { |h,k| @server_charset }.merge(options.delete(:channel_charset))
114
+
115
+ @timeout = DefaultTimeout.merge(options.delete(:timeout))
116
+
117
+ @irc = Socket.new(server, options) # the socket, all methods to the socket are wrapped
118
+
119
+ @listen = Hash.new { |h,k| h[k] = [] }
120
+ @listener = {}
121
+ @event_loop = Queue.new
122
+ @dispatch_lock = Mutex.new
123
+ @thread_read = nil
124
+ @myself = @users.myself
125
+
126
+ subscribe(:PING, 100) { |listener, message| @irc.pong(message.pong) }
127
+ end
128
+
129
+ # callback is called whenever a message with Message#symbol == symbol
130
+ # (or on every message if symbol is nil)
131
+ # priority may be any numeric, higher priority is dispatched to first,
132
+ # lower priority later
133
+ # returns an Butler::IRC::Client::Listener
134
+ def subscribe(symbol=nil, priority=0, id=nil, *args, &callback)
135
+ id ||= callback
136
+ raise "#{id} already subscribed" if @listener.has_key?(id)
137
+ listener = Listener.new(priority, callback, args) { |item|
138
+ @dispatch_lock.synchronize {
139
+ @listener.delete(id)
140
+ @listen[symbol].delete(item)
141
+ @listen.delete(symbol) if @listen[symbol].empty?
142
+ }
143
+ }
144
+ @dispatch_lock.synchronize {
145
+ @listener[id] = listener
146
+ @listen[symbol] << listener
147
+ @listen[symbol].sort_by { |l| -l.priority }
148
+ }
149
+ listener
150
+ end
151
+
152
+ # unsubscribe a listener by id
153
+ def unsubscribe(id)
154
+ @dispatch_lock.synchronize {
155
+ @listener.delete(id)
156
+ @listen[symbol].delete(item)
157
+ @listen.delete(symbol) if @listen[symbol].empty?
158
+ }
159
+ end
160
+
161
+ # blocks current thread until a Message with symbol
162
+ # (optionally passing a test given as block) is received,
163
+ # returns the message received that matches.
164
+ # returns nil if it times out before a match
165
+ def wait_for(symbol, timeout=nil, &test)
166
+ timeout(timeout) {
167
+ queue = Queue.new
168
+ listener = subscribe(symbol) { |l, m| queue.push(m) }
169
+ begin
170
+ message = queue.shift
171
+ end until block_given? ? yield(message) : true
172
+ message
173
+ }
174
+ rescue Timeout::Error
175
+ return nil
176
+ ensure
177
+ listener.unsubscribe
178
+ end
179
+
180
+ # listens for all Messages with symbol (optionally
181
+ # passing a test given as block) and pushes them onto the Queue
182
+ # returns the Queue, extended with Filter. You are responsible to
183
+ # unsubscribe it (call Queue#unsubscribe on it)
184
+ def filter(symbol, priority=0, queue=Queue.new)
185
+ raise ArgumentError, "Invalid Queue #{queue}:#{queue.class}" unless queue.respond_to?(:push)
186
+ listener = if block_given? then
187
+ subscribe(symbol, priority) { |l, message| queue.push(message) if yield(message) }
188
+ else
189
+ subscribe(symbol, priority) { |l, message| queue.push(message) }
190
+ end
191
+ queue.extend Filter
192
+ queue.listener = listener
193
+ queue
194
+ end
195
+
196
+ # login under nick, user, real
197
+ def login(nick, user, real)
198
+ queue, nick_change = nil, nil
199
+ number = 0
200
+ timeout(@timeout[:login]) {
201
+ @irc.connect
202
+ @users.create_myself(nick, user, real)
203
+ @myself = @users.myself
204
+ queue = filter(:RPL_WELCOME)
205
+ filter(:ERR_NOMOTD, 0, queue)
206
+ nick_change = subscribe(:ERR_NICKNAMEINUSE) {
207
+ change = "[#{number+=1}]#{nick}"
208
+ @irc.nick(change)
209
+ @myself.nick = change
210
+ }
211
+ @thread_read = Thread.new(&method(:thread_read))
212
+ @irc.login(nick, user, real)
213
+ queue.shift
214
+ }
215
+ true
216
+ ensure
217
+ queue.unsubscribe if queue
218
+ nick_change.unsubscribe if nick_change
219
+ end
220
+
221
+ # Same as IRC::Socket#join, but will do a who on every joined channel
222
+ def join(*args)
223
+ @irc.join(*args).each { |channel|
224
+ @irc.who(channel)
225
+ }
226
+ end
227
+
228
+ # Do a whois on nick
229
+ # Returns an Butler::IRC::Whois-Struct
230
+ def whois(user)
231
+ nick = user.to_str.strip_user_prefixes
232
+ raise ArgumentError, "Invalid nick #{nick.inspect}" unless nick.valid_nickname?
233
+ queue = Queue.new
234
+ whois = Whois.new
235
+ whois.exists = true
236
+ [
237
+ :RPL_WHOISUSER,
238
+ :RPL_WHOISSERVER,
239
+ :RPL_WHOISIDLE,
240
+ :RPL_ENDOFWHOIS,
241
+ :RPL_UNIQOPIS,
242
+ :RPL_WHOISCHANNELS,
243
+ :RPL_IDENTIFIED_TO_SERVICES,
244
+ :ERR_NOSUCHNICK,
245
+ :RPL_REGISTERED_INFO
246
+ ].each { |reply| filter(reply, 1, queue) }
247
+ @irc.whois(nick)
248
+ until (message = queue.shift).symbol == :RPL_ENDOFWHOIS
249
+ case message.symbol
250
+ when :ERR_NOSUCHNICK
251
+ whois.exists = false
252
+ when :RPL_WHOISUSER
253
+ whois.exists = true
254
+ whois.nick = message.nick
255
+ whois.user = message.user
256
+ whois.host = message.host
257
+ whois.real = message.real
258
+ when :RPL_WHOISSERVER
259
+ when :RPL_WHOISIDLE
260
+ whois.exists = true
261
+ whois.signon = message.signon_time
262
+ whois.idle = message.seconds_idle
263
+ when :RPL_UNIQOPIS
264
+ when :RPL_WHOISCHANNELS
265
+ whois.exists = true
266
+ whois.channels = message.channels
267
+ when :RPL_REGISTERED_INFO
268
+ whois.exists = true
269
+ whois.registered = true
270
+ end
271
+ end
272
+ return whois
273
+ end
274
+
275
+ # Sends quit message to server, terminates connection
276
+ def quit(reason=nil)
277
+ @irc.quit(reason)
278
+ terminate
279
+ @irc.close
280
+ end
281
+
282
+ # terminate all processing and reading,
283
+ # see Client#quit
284
+ def terminate(stop_reading=true)
285
+ @thread_read.raise Terminate if stop_reading and @thread_read and @thread_read.alive?
286
+ terminate_event_loop
287
+ end
288
+
289
+ # this thread is responsible for reading the servers messages
290
+ # and dispatching them to responders
291
+ # normally this thread is alive as long as the client is connected to the server
292
+ def thread_read
293
+ while message = @irc.read; process(message); end
294
+ on_disconnect(:disconnect)
295
+ rescue Terminate => e # got the termination signal
296
+ info("Terminating read thread")
297
+ on_disconnect(:quit)
298
+ rescue Errno::EPIPE => error # irc server closed connection
299
+ exception(error)
300
+ on_disconnect(:disconnect)
301
+ rescue Exception => error
302
+ exception(error)
303
+ on_disconnect(:error)
304
+ ensure
305
+ @irc.close
306
+ end
307
+
308
+ def on_disconnect(reason)
309
+ end
310
+
311
+ def event_loop(priority=-1)
312
+ if block_given?
313
+ filter(nil, priority, @event_loop)
314
+ while message = @event_loop.shift; yield(message); end
315
+ else
316
+ sleep
317
+ end
318
+ ensure
319
+ @event_loop.unsubscribe if block_given?
320
+ end
321
+
322
+ def terminate_event_loop
323
+ @event_loop.push(nil) if @event_loop
324
+ end
325
+
326
+ # process a Butler::IRC::Message, normally fed from thread_read.
327
+ def process(message)
328
+ message = @parser.server_message(message)
329
+ message.transcode!(@channel_charset[message.channel], @client_charset)
330
+ @dispatch_lock.synchronize {
331
+ @listen[nil].each { |listener| listener.callback.call(listener, message) }
332
+ if @listen.has_key?(message.symbol)
333
+ @listen[message.symbol].each { |listener| listener.callback.call(listener, message, *listener.args) }
334
+ end
335
+ }
336
+ rescue Terminate, Errno::EPIPE => error
337
+ raise error # on these errors we got to get out of the loop
338
+ rescue Exception => error
339
+ exception(error) # those errors are logged, reading goes on
340
+ end
341
+
342
+ def inspect # :nodoc:
343
+ "#<%s:0x%08x irc=%s>" % [
344
+ self.class,
345
+ object_id << 1,
346
+ @irc.inspect
347
+ ]
348
+ end
349
+ end # Client
350
+ end # IRC
351
+ end # Butler
@@ -0,0 +1,53 @@
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/users'
10
+
11
+
12
+
13
+ class Butler
14
+ module IRC
15
+ # Provides methods to see if hostmasks match
16
+ class Hostmask
17
+ Filter = [
18
+ [/([\\\[\]{}^`.-])/, '\\\\\1'],
19
+ [/\?/, "."],
20
+ [/\*/, ".*?"],
21
+ ]
22
+
23
+ # FIXME, not yet correct, escaped * and ?
24
+ def initialize(mask)
25
+ @mask = mask.dup.freeze
26
+ filtered = Filter.inject('[]{}^`.-') { |s,(a,b)| s.gsub(a,b) }
27
+ #string.gsub(/[\\\x00-\x1f]/) { |match| ("\\%02x" % match[0]) } #implement later
28
+ @regex = Regexp.new("\A#{filtered}\z")
29
+ end
30
+
31
+ def matches?(mask)
32
+ mask = mask.hostmask if mask.kind_of?(User)
33
+ return !@regex.match(mask.to_str).nil?
34
+ end
35
+
36
+ def match(mask)
37
+ mask = mask.hostmask if mask.kind_of?(User)
38
+ @regex.match(mask.to_str)
39
+ end
40
+
41
+ def to_str
42
+ @mask
43
+ end
44
+ alias to_s to_str
45
+ end
46
+
47
+ class User
48
+ def hostmask
49
+ return Hostmask.new("#{@nick||'*'}!#{@user||'*'}@#{@host||'*'}")
50
+ end
51
+ end
52
+ end
53
+ end