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,378 @@
1
+ #--
2
+ # Copyright 2007 by Stefan Rusterholz.
3
+ # All rights reserved.
4
+ # See LICENSE.txt for permissions.
5
+ #++
6
+
7
+
8
+
9
+ require 'thread'
10
+ require 'socket'
11
+ require 'ostructfixed'
12
+ require 'log/comfort'
13
+ require 'ruby/string/chunks'
14
+
15
+
16
+
17
+ class Butler
18
+ module IRC
19
+
20
+ # ==Description
21
+ # Butler::IRC::Socket is a TCPSocket, retrofitted for communication with
22
+ # IRC-Servers.
23
+ # It provides specialized methods for sending messages to IRC-Server.
24
+ # All methods are safe to be used with Butler::IRC::* Objects (e.g. all
25
+ # parameters expecting a nickname will accept an Butler::IRC::User as well).
26
+ # It will adhere to its limit-settings, which will prevent from sending too
27
+ # many messages in a too short time to avoid excess flooding.
28
+ # Butler::IRC::Socket#write is the only synchronized method, since all other
29
+ # methods build up on it, IRC::Socket should be safe in threaded environments.
30
+ # Butler::IRC::Socket#read is NOT synchronized, so unless you read from only
31
+ # a single thread, statistics might get messed up.
32
+ # Length limits can only be safely guaranteed by specialized write methods,
33
+ # Butler::IRC::Socket#write will just warn and send the overlength message.
34
+ # If you are looking for queries (commands that get an answer from the server)
35
+ # take a look at Butler::IRC::Client.
36
+ #
37
+ # ==Synopsis
38
+ # irc = Butler::IRC::Socket.new('irc.freenode.org', :port => 6667, :charset => 'ISO-8859-1')
39
+ # irc.connect
40
+ # irc.login('your_nickname', 'YourUser', 'Your realname', ["#channel1", "#channel2"])
41
+ # irc.join("#channel3")
42
+ # irc.part("#channel3")
43
+ # irc.privmsg("Hi all of you in #channel1!", "#channel1")
44
+ # irc.close
45
+ #
46
+ # ==Notes
47
+ # Errno::EHOSTUNREACH: server not reached
48
+ # Errno::ECONNREFUSED: server is up, but refuses connection
49
+ # Errno::ECONNRESET: connection works, server did not yet accept connection, resets after
50
+ # Errno::EPIPE: writing to a server-side closed connection, nil on gets, connection was terminated
51
+ #
52
+ # ==FIXME
53
+ # mode commands don't test for length and split up
54
+ #
55
+ class Socket
56
+ VERSION = "1.0.0"
57
+
58
+ include Log::Comfort
59
+
60
+ # server the instance is linked with
61
+ attr_reader :server
62
+ # port used for connection
63
+ attr_reader :port
64
+ # the own host (nil if not supported)
65
+ attr_reader :host
66
+ # end-of-line used for communication
67
+ attr_reader :eol
68
+
69
+ # contains various counters, such as :received, :sent (lines)
70
+ attr_reader :count
71
+
72
+ # contains limits for the protocol, burst times/counts etc.
73
+ attr_reader :limit
74
+
75
+ OptionsDefault = {
76
+ :port => 6667,
77
+ :eol => "\r\n",
78
+ :host => nil,
79
+ }
80
+
81
+ # Initialize properties, doesn't connect automatically
82
+ # options:
83
+ # * :server: ip/domain of server (overrides a given server parameter)
84
+ # * :port: port to connect on, defaults to 6667
85
+ # * :eol: what character sequence terminates messages, defaults to \r\n
86
+ # * :host: what host address to bind to, defaults to nil
87
+ #
88
+ def initialize(server, options={})
89
+ options = OptionsDefault.merge(options)
90
+ @log_device = options.delete(:log)
91
+ @server = server # options.delete(:server)
92
+ @port = options.delete(:port)
93
+ @eol = options.delete(:eol).dup.freeze
94
+ @host = options[:host] ? options.delete(:host).dup.freeze : options.delete(:host)
95
+ @last_sent = Time.new()
96
+ @count = Hash.new(0)
97
+ @limit = OpenStruct.new({
98
+ :message_length => 300, # max. length of a text message (e.g. in notice, privmsg) sent to server
99
+ :raw_length => 400, # max. length of a raw message sent to server
100
+ :burst => 4, # max. messages that can be sent with send_delay (0 = infinite)
101
+ :burst2 => 20, # max. messages that can be sent with send_delay (0 = infinite)
102
+ :send_delay => 0.1, # minimum delay between each message
103
+ :burst_delay => 1.5, # delay after a burst
104
+ :burst2_delay => 15, # delay after a burst2
105
+ })
106
+ @limit.each { |key, default|
107
+ @limit[key] = options.delete(key) if options.has_key?(key)
108
+ }
109
+ @mutex = Mutex.new
110
+ @socket = nil
111
+ @connected = false
112
+ raise ArgumentError, "Unknown arguments: #{options.keys.inspect}" unless options.empty?
113
+ end
114
+
115
+ # connects to the server
116
+ def connect
117
+ @socket = TCPSocket.open(@server, @port) #, @host)
118
+ info("Connected to #{@server}:#{@port} from #{@host || '<default>'}")
119
+ rescue ArgumentError => error
120
+ if @host then
121
+ warn("host-parameter is not supported by your ruby version. Parameter discarted.")
122
+ @host = nil
123
+ retry
124
+ else
125
+ raise
126
+ end
127
+ rescue Exception
128
+ error("Connection failed.")
129
+ raise
130
+ else
131
+ @connected = true
132
+ end
133
+
134
+ # get next message (eol already chomped) from server, blocking, returns nil if closed
135
+ def read
136
+ @count[:read] += 1
137
+ if m = @socket.gets(@eol) then
138
+ m.chomp(@eol)
139
+ else
140
+ nil
141
+ end
142
+ end
143
+
144
+ # Send a raw message to irc, eol will be appended
145
+ # Use specialized methods instead if possible since they will releave
146
+ # you from several tasks like translating newlines, take care of overlength
147
+ # messages etc.
148
+ # FIXME, wrong methodname, write implies nothing is appended
149
+ def write(data)
150
+ @mutex.synchronize {
151
+ warn("Raw too long (#{data.length} instead of #{@limit[:raw_length]})") if (data.length > @limit.raw_length)
152
+ now = Time.now
153
+
154
+ # keep delay between single (bursted) messages
155
+ sleeptime = @limit.send_delay-(now-@last_sent)
156
+ if sleeptime > 0 then
157
+ sleep(sleeptime)
158
+ now += sleeptime
159
+ end
160
+
161
+ # keep delay after a burst (1)
162
+ if (@count[:burst] >= @limit[:burst]) then
163
+ sleeptime = @limit.burst_delay-(now-@last_sent)
164
+ if sleeptime > 0 then
165
+ sleep(sleeptime)
166
+ now += sleeptime
167
+ end
168
+ @count[:burst] = 0
169
+ end
170
+
171
+ # keep delay after a burst (2)
172
+ if (@count[:burst2] >= @limit[:burst2]) then
173
+ sleeptime = @limit.burst2_delay-(now-@last_sent)
174
+ if sleeptime > 0 then
175
+ sleep(sleeptime)
176
+ now += sleeptime
177
+ end
178
+ @count[:burst2] = 0
179
+ end
180
+
181
+ # send data and update data
182
+ @last_sent = Time.new
183
+ @socket.write(data+@eol)
184
+ @count[:burst] += 1
185
+ @count[:burst2] += 1
186
+ @count[:sent] += 1
187
+ }
188
+ rescue IOError
189
+ error("Writing #{data.inspect} failed")
190
+ raise
191
+ end
192
+
193
+ # log into the irc-server (and connect if necessary)
194
+ def login(nickname, username, realname)
195
+ connect unless @connected
196
+ write("NICK #{nickname}")
197
+ write("USER #{username} 0 * :#{realname}")
198
+ end
199
+
200
+ # identify nickname to nickserv
201
+ # FIXME, figure out what the server supports, possibly requires it
202
+ # to be moved to Butler::IRC::Client (to allow ghosting, nickchange, identify)
203
+ def identify(password)
204
+ write("NS :IDENTIFY #{password}")
205
+ end
206
+
207
+ # FIXME, figure out what the server supports, possibly requires it
208
+ def ghost(nickname, password)
209
+ write("NS :GHOST #{nickname} #{password}")
210
+ end
211
+
212
+ def normalize_message(message, limit=:message_length)
213
+ messages = []
214
+ message.split(/\n/).each { |line|
215
+ messages.concat(line.chunks(@limit[limit]))
216
+ }
217
+ messages
218
+ end
219
+
220
+ # sends a privmsg to given user or channel (or multiple)
221
+ # messages containing newline or exceeding @limit[:message_length] are automatically splitted
222
+ # into multiple messages.
223
+ def privmsg(message, *recipients)
224
+ normalize_message(message).each { |message|
225
+ recipients.each { |recipient|
226
+ write("PRIVMSG #{recipient} :#{message}")
227
+ }
228
+ }
229
+ end
230
+
231
+ # same as privmsg except it's formatted for ACTION
232
+ def action(message, *recipients)
233
+ normalize_message(message).each { |message|
234
+ recipients.each { |recipient|
235
+ write("PRIVMSG #{recipient} :"+(1.chr)+"ACTION "+message+(1.chr))
236
+ }
237
+ }
238
+ end
239
+
240
+ # sends a notice to receiver (or multiple if receiver is array of receivers)
241
+ # formatted=true allows usage of ![]-format commands (see IRCmessage.getFormatted)
242
+ # messages containing newline automatically get splitted up into multiple messages.
243
+ # Too long messages will be tokenized into fitting sized messages (see @limit[:message_length])
244
+ def notice(message, *recipients)
245
+ normalize_message(message).each { |message|
246
+ recipients.each { |recipient|
247
+ write("NOTICE #{recipient} :#{message}")
248
+ }
249
+ }
250
+ end
251
+
252
+ # send a pong
253
+ def pong(*args)
254
+ if args.empty? then
255
+ write("PONG")
256
+ else
257
+ write("PONG #{args.join(' ')}")
258
+ end
259
+ end
260
+
261
+ # join specified channels
262
+ # use an array [channel, password] to join password-protected channels
263
+ # returns the channels joined.
264
+ def join(*channels)
265
+ channels.map { |channel, password|
266
+ if password then
267
+ write("JOIN #{channel} #{password}")
268
+ else
269
+ write("JOIN #{channel}")
270
+ end
271
+ channel
272
+ }
273
+ end
274
+
275
+ # part specified channels
276
+ # FIXME, better way to implement the reason? use a block (yay)?
277
+ # returns the channels parted from.
278
+ def part(reason=nil, *channels)
279
+ if channels.empty?
280
+ channels = [reason]
281
+ reason = nil
282
+ end
283
+ reason ||= "leaving"
284
+
285
+ # some servers still can't process lists of channels in part
286
+ channels.each { |channel|
287
+ write("PART #{channel} #{reason}")
288
+ }
289
+ end
290
+
291
+ # set your own nick
292
+ # does NO verification/validation of any kind
293
+ def nick(nick)
294
+ write("NICK #{nick}")
295
+ end
296
+
297
+ # set your status to away with reason 'reason'
298
+ def away(reason="")
299
+ return back if reason.empty?
300
+ write("AWAY :#{reason}")
301
+ end
302
+
303
+ # reset your away status to back
304
+ def back
305
+ write("AWAY")
306
+ end
307
+
308
+ # kick user in channel with reason
309
+ def kick(user, channel, reason)
310
+ write("KICK #{channel} #{user} :#{reason}")
311
+ end
312
+
313
+ # Give Op to user in channel
314
+ # User can be a nick or IRC::User, either one or an array.
315
+ def op(channel, *users)
316
+ write("MODE #{channel} +#{'o'*users.length} #{users*' '}")
317
+ end
318
+
319
+ # Take Op from user in channel
320
+ # User can be a nick or IRC::User, either one or an array.
321
+ def deop(channel, *users)
322
+ write("MODE #{channel} -#{'o'*users.length} #{users*' '}")
323
+ end
324
+
325
+ # Give voice to user in channel
326
+ # User can be a nick or IRC::User, either one or an array.
327
+ def voice(channel, *users)
328
+ write("MODE #{channel} +#{'v'*users.length} #{users*' '}")
329
+ end
330
+
331
+ # Take voice from user in channel.
332
+ # User can be a nick or IRC::User, either one or an array.
333
+ def devoice(channel, *users)
334
+ write("MODE #{channel} -#{'v'*users.length} #{users*' '}")
335
+ end
336
+
337
+ # Set ban in channel to mask
338
+ def ban(mask, channel)
339
+ write("MODE #{channel} +b #{mask}")
340
+ end
341
+
342
+ # Send a "who" to channel
343
+ def who(channel)
344
+ write("WHO #{channel}")
345
+ end
346
+
347
+ # Send a "whois" to server
348
+ def whois(nick)
349
+ write("WHOIS #{nick}")
350
+ end
351
+
352
+ # send the quit message to the server
353
+ # if you set close to true it will also close the socket
354
+ def quit(reason="leaving", close=false)
355
+ write("QUIT :#{reason}")
356
+ close() if close
357
+ end
358
+
359
+ # closes the connection to the irc-server
360
+ def close
361
+ raise "Socket not open" unless @socket
362
+ @socket.close unless @socket.closed?
363
+ end
364
+
365
+ def inspect # :nodoc:
366
+ "#<%s:0x%08x %s:%s from %s using '%s', stats: %s>" % [
367
+ self.class,
368
+ object_id << 1,
369
+ @server,
370
+ @port,
371
+ @host || "<default>",
372
+ @eol.inspect[1..-2],
373
+ @count.inspect
374
+ ]
375
+ end
376
+ end
377
+ end
378
+ end
@@ -0,0 +1,186 @@
1
+ #--
2
+ # Copyright 2007 by Stefan Rusterholz.
3
+ # All rights reserved.
4
+ # See LICENSE.txt for permissions.
5
+ #++
6
+
7
+
8
+
9
+ # provides IRC-related methods for string-class
10
+ class String
11
+ # Colors Lookup-table for mirc_translated_color
12
+ COLORS = {
13
+ 'white' => 0,
14
+ 'black' => 1,
15
+ 'blue' => 2,
16
+ 'green' => 3,
17
+ 'red' => 4,
18
+ 'brown' => 5,
19
+ 'purple' => 6,
20
+ 'orange' => 7,
21
+ 'yellow' => 8,
22
+ 'ltgreen' => 9,
23
+ 'teal' => 10,
24
+ 'ltcyan' => 11,
25
+ 'ltblue' => 12,
26
+ 'pink' => 13,
27
+ 'grey' => 14,
28
+ 'ltgrey' => 15
29
+ }
30
+
31
+ # returns whether or not a string represents a valid channelname
32
+ def valid_channelname?
33
+ self =~ /\A[&#!\+][^\x07\x0A\x0D,: ]{1,50}\z/
34
+ end
35
+
36
+ # returns if the the string represents a valid nickname
37
+ # this method does not take care of prefixes lik "@", "+", "-"
38
+ # see valid_user? for this funktionality or strip_user_prefixes
39
+ def valid_nickname?
40
+ #self =~ /\A[0-9A-Za-z_][0-9A-Za-z_\-\|\\\[\]\{\}\^\`]*\z/
41
+ self =~ /\A[0-9A-Za-z_\-\|\\\[\]\{\}\^\`]+\z/
42
+ end
43
+
44
+ # the same as valid_nickname? except that preceding @, + or - are ignored
45
+ def valid_user?
46
+ strip_user_prefixes.valid_nickname?
47
+ end
48
+
49
+ # removes indicators from nicknames and channelnames
50
+ def strip_user_prefixes
51
+ prefixes = Butler::IRC::User::PREFIXES.dup
52
+ index = 0
53
+ while (prefixes.has_key?(self[index,1]))
54
+ prefixes.delete(self[index,1])
55
+ index += 1
56
+ end
57
+ return self[index..-1]
58
+ end
59
+
60
+ # returns prefixes found in front of a nickname
61
+ # Sequential parsing since @@nickname is not valid.
62
+ def user_prefixes
63
+ prefixes = Butler::IRC::User::PREFIXES.dup
64
+ index = 0
65
+ found = 0
66
+ while (prefixes.has_key?(self[index,1]))
67
+ found |= prefixes.delete(self[index,1])
68
+ index += 1
69
+ end
70
+ return found
71
+ end
72
+
73
+ # Converts string representation of user-prefixes to binary flags
74
+ def to_flags
75
+ result = 0
76
+ 0.upto(self.length) { |index|
77
+ result |= Butler::IRC::User::PREFIXES[self[index,1]]
78
+ }
79
+ return result
80
+ end
81
+
82
+ # returns a string with formating codes in mirc-format stripped
83
+ def mirc_stripped
84
+ return self.gsub(/(?:[\x02\x0f\x12\x1f\x1d\x09]|\cc\d{1,2}(?:,\d{1,2})?)/, "")
85
+ end
86
+
87
+ =begin rdoc
88
+ provides mirc formatting:
89
+ ![b]: bold
90
+ ![i]: italic
91
+ ![u]: underline
92
+ ![r]: reverse
93
+ ![c]: reset color
94
+ ![cm]: set color, m=0-15 or (COLOR_CONSTANT)
95
+ ![cm,n]: set color, m is foreground, n is background, m,n=0-15 or (COLOR_CONSTANT)
96
+ ![o]: reset all effects
97
+
98
+ Samples:
99
+ ![bc(blue)]Bold blue text![o] and normal again
100
+ ![bc2]Bold blue text![o] and normal again (same as above)
101
+ ![bc(white),(black)]White bold text on black background![o]
102
+ ![bi]Bold italic text![o]
103
+
104
+ Valid colors are:
105
+ white (mirc-code: 0)
106
+ black (mirc-code: 1)
107
+ blue (mirc-code: 2)
108
+ green (mirc-code: 3)
109
+ red (mirc-code: 4)
110
+ brown (mirc-code: 5)
111
+ purple (mirc-code: 6)
112
+ orange (mirc-code: 7)
113
+ yellow (mirc-code: 8)
114
+ ltgreen (mirc-code: 9)
115
+ teal (mirc-code: 10)
116
+ ltcyan (mirc-code: 11)
117
+ ltblue (mirc-code: 12)
118
+ pink (mirc-code: 13)
119
+ grey (mirc-code: 14)
120
+ ltgrey (mirc-code: 15)
121
+
122
+
123
+ Note: not every font/size combination displays bold/italic text.
124
+ =end
125
+ def mirc_formatted # FIXME - copied code, ugly & slow
126
+ self.gsub( /!\[(.*?)\]/ ) do |match|
127
+ codes = $1.downcase
128
+ repl = ""
129
+ i = 0
130
+ while i < codes.length
131
+ case codes[i].chr
132
+ when 'b'
133
+ repl << 2.chr
134
+ when 'o'
135
+ repl << 15.chr
136
+ when 'r'
137
+ repl << 18.chr
138
+ when 'u'
139
+ repl << 31.chr
140
+ when 'i'
141
+ repl << 29.chr
142
+ when '|'
143
+ repl << 9.chr
144
+ when 'c'
145
+ bg = nil
146
+
147
+ i, fg = mirc_translated_color( i+1, codes )
148
+ i, bg = mirc_translated_color( i+1, codes ) if i < codes.length && codes[i].chr == ','
149
+
150
+ repl << "" << ( fg || "" )
151
+ repl << "," << bg if bg
152
+
153
+ i -= 1
154
+ end
155
+
156
+ i += 1
157
+ end
158
+ repl
159
+ end
160
+ end
161
+
162
+ # helper method for mirc_formatted, extracts color portions from ![c(color)] statements
163
+ def mirc_translated_color( i, s )
164
+ return [ i, nil ] if i >= s.length
165
+
166
+ if s[i].chr == '('
167
+ j = s.index( ')', i )
168
+ return [ j+1, "%02d" % COLORS[ s[i+1..j-1].downcase ] ]
169
+ end
170
+
171
+ j = i
172
+ j += 1 while j < s.length && s[j].chr =~ /[0-9]/
173
+ j += 1 if j == s.length
174
+ return [ j, "%02d" % s[i..j-1].to_i ]
175
+ end
176
+ end #String
177
+
178
+ if __FILE__ == $0 then
179
+ require 'test/unit'
180
+ class TestIRCString < Test::Unit::TestCase
181
+ def test_user
182
+ assert("foobar".valid_nickname?)
183
+ assert(!"{foobar}".valid_nickname?)
184
+ end
185
+ end
186
+ end