butler 1.8.0

Sign up to get free protection for your applications and to get access to all the features.
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