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,184 @@
1
+ #--
2
+ # Copyright 2007 by Stefan Rusterholz.
3
+ # All rights reserved.
4
+ # See LICENSE.txt for permissions.
5
+ #++
6
+
7
+
8
+
9
+ #require 'ruby/string' # we need the transcode! method
10
+
11
+ class Butler
12
+ module IRC
13
+
14
+ # Butler::IRC::Message represents messages received by the server.
15
+ # It provides convenience methods that allow to access information about
16
+ # those messages easier, e.g. who (as Butler::IRC::User object) sent the
17
+ # message in which channel (IRC::Channel object) with what text.
18
+ # Raw message and raw parsed data are still available though.
19
+ #
20
+ # =FIXME
21
+ class Message
22
+ # the command-symbol, see COMMANDS (e.g. :PRIVMSG, :JOIN, ...)
23
+ attr_reader :symbol
24
+
25
+ # a string containing the raw message as received
26
+ attr_reader :raw
27
+
28
+ # the prefix, normally either the sending user or your irc-server
29
+ attr_reader :prefix
30
+ # the raw command (e.g. "352", "notice", ...)
31
+ attr_reader :command
32
+ # the parameter part
33
+ attr_reader :params
34
+
35
+ def initialize(client, symbol, raw, prefix, command, params)
36
+ @client = client
37
+
38
+ #raw message
39
+ @raw = raw
40
+
41
+ #parsed data
42
+ @prefix = prefix
43
+ @command = command
44
+ @params = params
45
+ @symbol = symbol
46
+
47
+ #specific data
48
+ @fields = Hash.new { |h,k| raise IndexError, "No member '#{k}' available." }.merge({
49
+ :raw => @raw,
50
+ :prefix => @prefix,
51
+ :command => @command,
52
+ :params => @params,
53
+ :symbol => @symbol,
54
+ :from => nil,
55
+ :for => nil,
56
+ :channel => nil,
57
+ :text => nil,
58
+ })
59
+ end
60
+
61
+ def initialize_copy(original) #:nodoc:
62
+ super
63
+ @client = original.instance_variable_get(:@client)
64
+ @fields = original.to_hash
65
+ @raw = @fields[:raw]
66
+ @prefix = @fields[:prefix]
67
+ @command = @fields[:command]
68
+ @params = @fields[:params]
69
+ @symbol = @fields[:symbol]
70
+ end
71
+
72
+ def answer(text)
73
+ reply = channel || from
74
+ case @symbol
75
+ when :PRIVMSG: @client.irc.privmsg(text, reply)
76
+ when :NOTICE: @client.irc.notice(text, reply)
77
+ end
78
+ end
79
+
80
+ def from
81
+ @fields[:from]
82
+ end
83
+
84
+ def for
85
+ @fields[:for]
86
+ end
87
+
88
+ def channel
89
+ @fields[:channel]
90
+ end
91
+
92
+ def text
93
+ @fields[:text]
94
+ end
95
+
96
+ # access to raw parsed data
97
+ def [](index)
98
+ @fields[index.to_sym]
99
+ end
100
+
101
+ def []=(index, value)
102
+ raise NoMethodError, "Can't write to #{index}" unless respond_to?("#{index}=")
103
+ index = index.to_sym
104
+ @fields[index] = value
105
+ end
106
+
107
+ def has_key?(key)
108
+ @fields.has_key?(key.to_sym)
109
+ end
110
+
111
+ def create_method(name, &code)
112
+ singleton = class << self; self; end
113
+ singleton.send(:define_method, name, &code)
114
+ name
115
+ end
116
+
117
+ # create a member with it's value, if writable is true, it creates a member= method too
118
+ def create_member(index, value=nil, writable=false)
119
+ index = index.to_sym
120
+ raise IndexError, "Member '#{index}' exists already" if @fields.has_key?(index)
121
+ singleton = class << self; self; end
122
+ singleton.send(:define_method, index) { @fields[index] }
123
+ singleton.send(:define_method, "#{index}=") { |val| @fields[index] = val } if writable
124
+ @fields[index] = value
125
+ end
126
+
127
+ # alter a member value with it's value
128
+ # should not be used other than by Butler::IRC::Parser
129
+ def alter_member(index, value=nil, create=false)
130
+ index = index.to_sym
131
+ if @fields.has_key?(index)
132
+ @fields[index] = value
133
+ elsif create then
134
+ create_member(index, value)
135
+ else
136
+ raise IndexError, "No member '#{index}' available."
137
+ end
138
+ end
139
+
140
+ # delete a field
141
+ def delete_member(index)
142
+ index = index.to_sym
143
+ raise IndexError, "No member '#{index}' available." unless @fields.has_key?(index)
144
+ remove_method(index)
145
+ remove_method("#{index}=") if respond_to?("#{index}=")
146
+ @fields.delete(index)
147
+ end
148
+
149
+ # change charset encoding
150
+ def transcode!(from, to)
151
+ end
152
+
153
+ def ===(other)
154
+ case other
155
+ when Symbol: @symbol == other
156
+ when Regexp: @text && @text =~ other
157
+ end
158
+ end
159
+
160
+ # return a hash of the fields
161
+ def to_hash
162
+ @fields.dup
163
+ end
164
+
165
+ def hash #:nodoc:
166
+ @raw.hash
167
+ end
168
+
169
+ def eql?(other) #:nodoc:
170
+ @raw == other.raw
171
+ end
172
+
173
+ def inspect #:nodoc:
174
+ fields = @fields.dup
175
+ #[:raw, :prefix, :command, :params, :command_raw]
176
+ [:raw, :command, :prefix, :params].each { |field| fields.delete(field) }
177
+ fields = fields.map { |k,v| "#{k}=#{v.inspect}" }.join(" ")
178
+ "#<%s:0x%x\n%s\n%s>" % [self.class, object_id, @raw.inspect, fields]
179
+ end
180
+
181
+ alias to_s inspect
182
+ end
183
+ end
184
+ end
@@ -0,0 +1,125 @@
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/channels'
10
+ require 'butler/irc/hostmask'
11
+ require 'butler/irc/message'
12
+ require 'butler/irc/parser/commands'
13
+ require 'butler/irc/string'
14
+ require 'butler/irc/users'
15
+ require 'ruby/exception/detailed'
16
+ require 'ruby/hash/zip'
17
+
18
+
19
+
20
+ class Butler
21
+ module IRC
22
+ # Parses messages, automatically converts
23
+ # provides a parser that automatically connects users and channels
24
+ # regarding who myself is (out_of_sight, back_in_sight for users)
25
+ # allows creation of dialogs from privmsg and notice messages
26
+ class Parser
27
+ # RFC 2812, Parser itself uses a simplified matching
28
+ module Expressions
29
+ Special = /[\[\]\\`_^{|}]/
30
+ Letter = /[A-Za-z]/
31
+ Hex = /[\dA-Fa-f]/
32
+ ChannelID = /[A-Z\d]{5}/
33
+ Chanstring = /[^\x00\x07\x10\x0D\x20,:]/
34
+ Channel = /(?:[#+&]|!#{ChannelID})#{Chanstring}(?::#{Chanstring})?/
35
+ User = /[^\x00\x10\x0D\x20@]/
36
+ Nick = /[A-Za-z\[\]\\`_^{|}][A-Za-z\d\[\]\\`_^{|}-]*/
37
+ Command = /[A-Za-z]+|\d{3}/
38
+ IP4addr = /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/
39
+ IP6addr = /[\dA-Fa-f](?::[\dA-Fa-f]){7}|0:0:0:0:0:(?:0|[Ff]{4}):#{IP4addr}/
40
+ Hostaddr = /#{IP4addr}|#{IP6addr}/
41
+ Shortname = /[A-Za-z0-9][A-Za-z0-9-]*/
42
+ Hostname = /#{Shortname}(?:\.#{Shortname})*/
43
+ Host = /#{Hostname}|#{Hostaddr}/
44
+ Prefix = /#{Hostname}|#{Nick}(?:(?:!#{User})?@#{Host})?/
45
+ Params = /.*/ # FIXME
46
+ Message = /(:#{Prefix} )?#{Command}(#{Params})?/
47
+ end
48
+
49
+ class InvalidMessageFormat < RuntimeError; end
50
+ class UnknownCommand < RuntimeError; end
51
+ module ParseError
52
+ include Exception::Detailed
53
+ end
54
+
55
+ RMessage = /\A(?::([^ \0]+) )?([A-Za-z\d]+|\d{3})(?: (.*))?\z/
56
+ RHostmask = /(#{Expressions::Nick})(?:(?:!(#{Expressions::User}))?@(#{Expressions::Host}))?/
57
+
58
+ attr_reader :client
59
+ attr_reader :users
60
+ attr_reader :channels
61
+
62
+ def initialize(client, users, channels)
63
+ @client = client
64
+ @users = users
65
+ @channels = channels
66
+ @commands = Commands.new
67
+ end
68
+
69
+ # parses an incomming message and returns a Message object from which you
70
+ # can easily access parsed data.
71
+ # Expects the newlines to be already chomped off.
72
+ def server_message(raw)
73
+ prefix, command, params, symbol, from = nil
74
+
75
+ # Basic analysis of the message
76
+ raise InvalidMessageFormat, raw unless matched = raw.match(RMessage)
77
+ prefix, command, params = *matched.captures
78
+ command.downcase!
79
+
80
+ # Parse prefix if possible (<nick>!<user>@<host>)
81
+ from = @users.create(*matched.captures) if prefix and matched = prefix.match(RHostmask)
82
+
83
+ # in depth analyzis of the message
84
+ parser = @commands[command]
85
+ symbol = parser.symbol
86
+ message = Message.new(@client, symbol, raw, prefix, command, params)
87
+ message.alter_member(:from, from)
88
+
89
+ parser.create_fields(message)
90
+
91
+ if message.for then
92
+ if message.for.valid_channelname? then
93
+ channel = @channels.create(message.for)
94
+ message.alter_member(:channel, channel)
95
+ message.alter_member(:for, channel)
96
+ else
97
+ message.alter_member(:for, @users.create(message.for))
98
+ end
99
+ end
100
+ message.alter_member(:channel, @channels.create(message.channel)) if message.channel
101
+
102
+ parser.process(message, self)
103
+
104
+ return message
105
+ rescue IndexError
106
+ raise UnknownCommand, "Unknown command #{command}: #{raw.inspect}"
107
+ rescue => e
108
+ e.extend ParseError
109
+ e.prepend "Message: #{raw.inspect}"
110
+ raise e
111
+ end
112
+
113
+ def leave_channel(message, reason1, reason2)
114
+ if message.from && message.channel then
115
+ message.from.delete_channel(message.channel, reason1)
116
+ message.channel.delete_user(message.from, reason1)
117
+ if message.from == @users.myself then
118
+ @channels.delete(message.channel)
119
+ @users.delete_channel(message.channel, reason2)
120
+ end
121
+ end
122
+ end
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,83 @@
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/parser'
10
+
11
+
12
+
13
+ class Butler
14
+ module IRC
15
+ class Parser
16
+ class Commands
17
+ def initialize(specifics=nil)
18
+ @commands = Hash.new { |h,k| raise IndexError, "Unknown command #{k}" }
19
+ file = "#{File.dirname(__FILE__)}/generic.rb"
20
+ instance_eval(File.read(file), file)
21
+ end
22
+
23
+ def add(raw, *args, &proc)
24
+ raise IndexError, "Command #{raw} is already registered. Did you want 'alter'?" if @commands.has_key?(raw)
25
+ @commands[raw.downcase] = Command.new(raw, *args, &proc)
26
+ end
27
+
28
+ def alter(raw, *args, &proc)
29
+ raise IndexError, "Command #{raw} is not registered. Did you want 'add'?" unless @commands.has_key?(raw)
30
+ @commands[raw.downcase] = Command.new(raw, *args, &proc)
31
+ end
32
+
33
+ def [](raw)
34
+ @commands[raw.downcase]
35
+ end
36
+
37
+ def inspect
38
+ "#<%s:0x%x>" % [self.class, object_id]
39
+ end
40
+ end
41
+
42
+ # Provides parsing information about specific commands.
43
+ class Command
44
+ attr_reader :raw
45
+ attr_reader :symbol
46
+ attr_reader :matcher
47
+ attr_reader :mapping
48
+ attr_reader :processor
49
+
50
+ # See Butler::IRC::COMMANDS for samples on instanciation of
51
+ # Butler::IRC::Command objects.
52
+ def initialize(raw, symbol, matcher=nil, mapping=nil, &processor)
53
+ @raw = raw
54
+ @symbol = symbol
55
+ @matcher = matcher
56
+ @mapping = mapping
57
+ @processor = processor
58
+ end
59
+
60
+ def match(message)
61
+ @matcher.match(message)
62
+ end
63
+
64
+ def fields(message)
65
+ return nil unless @matcher and match = @matcher.match(message.params)
66
+ Hash.zip(@mapping, match.captures)
67
+ end
68
+
69
+ def create_fields(message)
70
+ if @matcher and match = @matcher.match(message.params) then
71
+ @mapping.zip(match.captures) { |name, value|
72
+ message.alter_member(name, value, true)
73
+ }
74
+ end
75
+ end
76
+
77
+ def process(message, parser)
78
+ @processor.call(message, parser) if @processor
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,343 @@
1
+ #--
2
+ # Copyright 2007 by Stefan Rusterholz.
3
+ # All rights reserved.
4
+ # See LICENSE.txt for permissions.
5
+ #++
6
+
7
+
8
+
9
+ # A list with all known IRC-Commands and it's parsing instructions
10
+ # Commands with __ in it are customs, not verified towards any standard.
11
+
12
+ #Text based codes
13
+ add("error", :ERROR) # ERROR :<error-message>
14
+
15
+ add("invite", :INVITE, /^([^\s]*) :(.*)/, [:invited, :channel])
16
+
17
+ add("join", :JOIN, /^:(.*)/, [:channel]) { |message, parser|
18
+ if message.from && message.channel then
19
+ message.from.add_channel(message.channel, :join)
20
+ message.channel.add_user(message.from, :join)
21
+ end
22
+ }
23
+
24
+ add("kick", :KICK, /^([^\s]*) ([^\s]*) :(.*)/, [:channel, :for, :text]) { |message, parser|
25
+ parser.leave_channel(message, :kick, :kicked)
26
+ }
27
+
28
+ add("kill", :KILL, /^([^\s]*) ([^\s]*) (.*)/, [:channel, :for, :text]) { |message, parser|
29
+ if message.for then
30
+ message.for.kill
31
+ parser.channels.delete_user(message.for, :kill)
32
+ end
33
+ }
34
+
35
+ # FIXME, really ':?' or just plain ':'?
36
+ add("mode", :MODE, /^([^\s]*) :?(.*)/, [:for, :arguments]) { |message, parser|
37
+ modifiers = message[:arguments].split(" ")
38
+ modes = modifiers.shift.split("")
39
+ flags = {"o" => User::Flags::OP, "v" => User::Flags::VOICE, "u" => User::Flags::UOP}
40
+ message.create_member(:modes, [])
41
+ argumentIndex = 0
42
+ direction = "+"
43
+
44
+ modes.each { |mode|
45
+ # MODEs direction
46
+ if (["+", "-"].include?(mode)) then
47
+ direction = mode
48
+ # MODEs taking an argument
49
+ elsif (["k", "o", "v", "u"].include?(mode) || (mode == "l" && direction == "+")) then
50
+ if ["o", "v", "u"].include?(mode) then
51
+ if (direction == "+") then
52
+ parser.users[modifiers[argumentIndex]].add_flags(message.channel, flags[mode]) #adding flags to user
53
+ else
54
+ parser.users[modifiers[argumentIndex]].delete_flags(message.channel, flags[mode]) #removing flags from user
55
+ end
56
+ end
57
+ message.modes << [direction, mode, modifiers[argumentIndex]]
58
+ argumentIndex = argumentIndex+1
59
+ # MODEs without argument
60
+ else
61
+ message.modes << [direction, mode, nil]
62
+ end
63
+ }
64
+ }
65
+ add("nick", :NICK, /^:(.*)/, [:nick]) { |message, parser|
66
+ message.create_member(:old_nick, message.from.nick)
67
+ message.from.nick = message.nick if message.from
68
+ }
69
+
70
+ add("notice", :NOTICE, /([^\s]+) :(.*)/, [:for, :text]) { |message, parser|
71
+ if message.channel then
72
+ message.create_member(:realm, :public)
73
+ message.create_method(:private?) { false }
74
+ message.create_method(:public?) { true }
75
+ else
76
+ message.create_member(:realm, :private)
77
+ message.create_method(:private?) { true }
78
+ message.create_method(:public?) { false }
79
+ end
80
+ }
81
+
82
+ add("part", :PART, /^([^\x00\x07\x10\x0D\x20,:]+)(?: :(.*))?/, [:channel, :reason]) { |message, parser|
83
+ parser.leave_channel(message, :part, :parted)
84
+ }
85
+
86
+ add("ping", :PING, /:(.*)/, [:pong])
87
+
88
+ add("pong", :PONG)
89
+
90
+ add("privmsg", :PRIVMSG, /([^\s]+) :(.*)/, [:for, :text]) { |message, parser|
91
+ if message.channel then
92
+ message.create_member(:realm, :public)
93
+ message.create_method(:private?) { false }
94
+ message.create_method(:public?) { true }
95
+ else
96
+ message.create_member(:realm, :private)
97
+ message.create_method(:private?) { true }
98
+ message.create_method(:public?) { false }
99
+ end
100
+ }
101
+
102
+ add("quit", :QUIT, /(.*)/, [:text]) { |message, parser|
103
+ if message.from then
104
+ message.from.quit
105
+ parser.channels.delete_user(message.from, :quit)
106
+ parser.users.delete(message.from, :quit)
107
+ end
108
+ }
109
+ add("topic", :TOPIC, /([^\s]+) :(.*)/, [:channel, :text])
110
+
111
+ #Unknown/Non-RFC codes
112
+ add("007", :UNK_007)
113
+ add("008", :UNK_008)
114
+ add("009", :UNK_009)
115
+ add("307", :RPL_REGISTERED_INFO) #FIXME non2812 "ConferenceRoom" bluewin, sent if a nick is registered
116
+ add("329", :RPL_CHANNEL_INFO) #channel creation time
117
+ add("377", :UNK_377)
118
+
119
+ #0** Codes
120
+ add("001", :RPL_WELCOME)
121
+ add("002", :RPL_YOURHOST)
122
+ add("003", :RPL_CREATED)
123
+ add("004", :RPL_MYINFO)
124
+ add("005", :RPL_ISUPPORT)
125
+
126
+ #2** Codes
127
+ add("200", :RPL_TRACELINK)
128
+ add("201", :RPL_TRACECONNECTING)
129
+ add("202", :RPL_TRACEHANDSHAKE)
130
+ add("203", :RPL_TRACEUNKNOWN)
131
+ add("204", :RPL_TRACEOPERATOR)
132
+ add("205", :RPL_TRACEUSER)
133
+ add("206", :RPL_TRACESERVER)
134
+ add("207", :RPL_TRACESERVICE)
135
+ add("208", :RPL_TRACENEWTYPE)
136
+ add("209", :RPL_TRACECLASS)
137
+ add("210", :RPL_TRACERECONNECT)
138
+ add("211", :RPL_STATSLINKINFO)
139
+ add("212", :RPL_STATSCOMMANDS)
140
+ add("213", :RPL_STATSCLINE)
141
+ add("214", :RPL_STATSNLINE)
142
+ add("215", :RPL_STATSILINE)
143
+ add("216", :RPL_STATSKLINE)
144
+ add("217", :RPL_STATSQLINE)
145
+ add("218", :RPL_STATSYLINE)
146
+ add("219", :RPL_ENDOFSTATS)
147
+ add("221", :RPL_UMODEIS)
148
+ add("231", :RPL_SERVICEINFO)
149
+ add("232", :RPL_ENDOFSERVICES)
150
+ add("233", :RPL_SERVICE)
151
+ add("234", :RPL_SERVLIST)
152
+ add("235", :RPL_SERVLISTEND)
153
+ add("240", :RPL_STATSVLINE)
154
+ add("241", :RPL_STATSLLINE)
155
+ add("242", :RPL_STATSUPTIME)
156
+ add("243", :RPL_STATSOLINE)
157
+ add("244", :RPL_STATSHLINE)
158
+ add("245", :RPL_STATSSLINE) # RFC 2812 seems to be erroneous, it assigns 244 double
159
+ add("246", :RPL_STATSPING)
160
+ add("247", :RPL_STATSBLINE)
161
+ add("250", :RPL_STATSCONN)
162
+ add("251", :RPL_LUSERCLIENT)
163
+ add("252", :RPL_LUSEROP)
164
+ add("253", :RPL_LUSERUNKNOWN)
165
+ add("254", :RPL_LUSERCHANNELS)
166
+ add("255", :RPL_LUSERME)
167
+ add("256", :RPL_ADMINME)
168
+ add("257", :RPL_ADMINLOC1)
169
+ add("258", :RPL_ADMINLOC2)
170
+ add("259", :RPL_ADMINEMAIL)
171
+ add("261", :RPL_TRACELOG)
172
+ add("262", :RPL_TRACEEND)
173
+ add("263", :RPL_TRYAGAIN)
174
+ add("265", :RPL_LOCALUSERS)
175
+ add("266", :RPL_GLOBALUSERS)
176
+ add("290", :RPL_IDENTIFY_MSG) # FIXME non2812 "hyperion-1.0.2b"
177
+
178
+ #3** Codes
179
+ add("300", :RPL_NONE)
180
+ add("301", :RPL_AWAY)
181
+ add("302", :RPL_USERHOST)
182
+ add("303", :RPL_ISON)
183
+ add("305", :RPL_UNAWAY)
184
+ add("306", :RPL_NOWAWAY)
185
+ add("311", :RPL_WHOISUSER, /^([^\s]+) ([^\s]+) ([^\s]+) ([^\s]+) \* :(.*)/, [:for, :nick, :user, :host, :real])
186
+ add("312", :RPL_WHOISSERVER)
187
+ add("313", :RPL_WHOISOPERATOR)
188
+ add("314", :RPL_WHOWASUSER)
189
+ add("315", :RPL_ENDOFWHO)
190
+ add("316", :RPL_WHOISCHANOP)
191
+ add("317", :RPL_WHOISIDLE, /^([^\s]+) ([^\s]+) ([^:]+) :(.*)/, [:for, :nick, :values, :descriptions]) { |message, parser|
192
+ values = message[:values].split(" ");
193
+ descriptions = message[:descriptions].split(", ").map { |desc| desc.gsub(" ", "_").to_sym };
194
+ message.delete(:values)
195
+ message.delete(:descriptions)
196
+ 0.upto([values.length, descriptions.length].min-1) { |index|
197
+ message[descriptions[index]] = values[index]
198
+ }
199
+ }
200
+ add("318", :RPL_ENDOFWHOIS)
201
+ add("319", :RPL_WHOISCHANNELS, /^([^\s]+) ([^\s]+) :(.*)/, [:for, :nick, :channels]) { |message, parser|
202
+ # FIXME
203
+ message.alter_member(:channels, message.channels.split(" ").map { |channel| parser.channels.create(channel) })
204
+ }
205
+ add("320", :RPL_IDENTIFIED_TO_SERVICES) # possibly hyperion only
206
+ add("321", :RPL_LISTSTART)
207
+ add("322", :RPL_LIST, /^([^\s]+) ([^\s]+) (\d+) :(.*)/, [:for, :channelname, :usercount, :topic])
208
+ add("323", :RPL_LISTEND)
209
+ add("324", :RPL_CHANNELMODEIS)
210
+ add("325", :RPL_UNIQOPIS)
211
+ add("331", :RPL_NOTOPIC)
212
+ # :irc.server.net 332 YourNickname #channel :Topic
213
+ add("332", :RPL_TOPIC, /^([^\s]+) ([^\s]+) :(.*)/, [:for, :channel, :topic]) { |message, parser|
214
+ message.channel.topic.text = message[:topic]
215
+ }
216
+
217
+ # :irc.server.net 333 YourNickname #channel SetByNick 1139902138
218
+ add("333", :RPL_TOPIC_INFO, /^([^\s]+) ([^\s]+) ([^\s]+) (\d+)/, [:for, :channel, :set_by, :set_at]) { |message, parser|
219
+ topic = message.channel.topic
220
+ topic.set_by = message[:set_by]
221
+ topic.set_at = message[:set_at]
222
+ }
223
+ add("341", :RPL_INVITING)
224
+ add("342", :RPL_SUMMONING)
225
+ add("343", :RPL__MAINTENANCE) #mainenance notice?
226
+ add("346", :RPL_INVITELIST)
227
+ add("347", :RPL_ENDOFINVITELIST)
228
+ add("348", :RPL_EXCEPTLIST)
229
+ add("349", :RPL_ENDOFEXCEPTLIST)
230
+ add("351", :RPL_VERSION)
231
+ # :irc.server.net 352 YourNickname <channel> <user> <host> <server> <nick> ( "H" / "G" > ["*"] [ ( "@" / "+" ) ] :<hopcount> <real name>
232
+ add("352", :RPL_WHOREPLY,
233
+ /([^\s]+) ([^\s]+) ([^\s]+) ([^\s]+) ([^\s]+) ([^\s]+) ([HG])[*%]{0,2}([@+-]{0,3}) :(\d+) (.*)/,
234
+ [:for, :channel, :user, :host, :server, :nick, :status, :flags, :hopcount, :real]) { |message, parser|
235
+ #"for", "channel", "user", "host", "server", "nick", "status", "flags", "hopcount", "real"
236
+ user = parser.users.create(message[:nick])
237
+ user.update(message[:user], message[:host], message[:real])
238
+ user.away = message[:status] == "G"
239
+ user.add_channel(message.channel, :joined)
240
+ message.channel.add_user(user, :joined)
241
+ user.add_flags(message.channel, message[:flags].to_flags)
242
+ }
243
+ add("353", :RPL_NAMEREPLY, /([^\s]+) [=@] ([^\s]+) :(.*)/, [:for, :channel, :users]) { |message, parser|
244
+ users = message[:users]
245
+ message.alter_member(:users, [])
246
+ users.split(/ /).each { |nick|
247
+ user = parser.users.create(nick)
248
+ message[:users] << user
249
+ user.add_channel(message.channel, :joined)
250
+ message.channel.add_user(user, :joined)
251
+ }
252
+ }
253
+ add("361", :RPL_KILLDONE)
254
+ add("362", :RPL_CLOSING)
255
+ add("363", :RPL_CLOSEEND)
256
+ add("364", :RPL_LINKS)
257
+ add("365", :RPL_ENDOFLINKS)
258
+ add("366", :RPL_ENDOFNAMES)
259
+ add("367", :RPL_BANLIST,
260
+ /([^\s]+) ([^\s]+) ([^\s]+) ([^\s]+) (\d+)/,
261
+ [:for, :channel, :banmask, :banned_by, :bantime]
262
+ ) { |message, parser| #367 nickname #channel nick!user@host nickname 1140125288
263
+ message[:bantime] = Time.at(message[:bantime].to_i)
264
+ message[:banmask] = Hostmask.new(message[:banmask])
265
+ }
266
+
267
+ add("368", :RPL_ENDOFBANLIST)
268
+ add("369", :RPL_ENDOFWHOWAS)
269
+ add("371", :RPL_INFO)
270
+ add("372", :RPL_MOTD)
271
+ add("373", :RPL_INFOSTART)
272
+ add("374", :RPL_ENDOFINFO)
273
+ add("375", :RPL_MOTDSTART)
274
+ add("376", :RPL_ENDOFMOTD)
275
+ add("381", :RPL_YOUREOPER)
276
+ add("382", :RPL_REHASHING)
277
+ add("383", :RPL_YOURESERVICE)
278
+ add("384", :RPL_MYPORTIS)
279
+ add("386", :RPL_PASSWORDACCEPTED) # Custom (chatroom)
280
+ add("391", :RPL_TIME)
281
+ add("392", :RPL_USERSSTART)
282
+ add("393", :RPL_USERS)
283
+ add("394", :RPL_ENDOFUSERS)
284
+ add("395", :RPL_NOUSERS)
285
+
286
+ #4** Codes
287
+ add("401", :ERR_NOSUCHNICK)
288
+ add("402", :ERR_NOSUCHSERVER)
289
+ add("403", :ERR_NOSUCHCHANNEL)
290
+ add("404", :ERR_CANNOTSENDTOCHAN)
291
+ add("405", :ERR_TOOMANYCHANNELS)
292
+ add("406", :ERR_WASNOSUCHNICK)
293
+ add("407", :ERR_TOOMANYTARGETS)
294
+ add("408", :ERR_NOSUCHSERVICE)
295
+ add("409", :ERR_NOORIGIN)
296
+ add("411", :ERR_NORECIPIENT)
297
+ add("412", :ERR_NOTEXTTOSEND)
298
+ add("413", :ERR_NOTOPLEVEL)
299
+ add("414", :ERR_WILDTOPLEVEL)
300
+ add("415", :ERR_BADMASK)
301
+ add("421", :ERR_UNKNOWNCOMMAND)
302
+ add("422", :ERR_NOMOTD)
303
+ add("423", :ERR_NOADMININFO)
304
+ add("424", :ERR_FILEERROR)
305
+ add("431", :ERR_NONICKNAMEGIVEN)
306
+ add("432", :ERR_ERRONEUSNICKNAME)
307
+ add("433", :ERR_NICKNAMEINUSE)
308
+ add("436", :ERR_NICKCOLLISION)
309
+ add("437", :ERR_UNAVAILRESOURCE)
310
+ add("441", :ERR_USERNOTINCHANNEL)
311
+ add("442", :ERR_NOTONCHANNEL)
312
+ add("443", :ERR_USERONCHANNEL)
313
+ add("444", :ERR_NOLOGIN)
314
+ add("445", :ERR_SUMMONDISABLED)
315
+ add("446", :ERR_USERSDISABLED)
316
+ add("451", :ERR_NOTREGISTERED)
317
+ add("461", :ERR_NEEDMOREPARAMS)
318
+ add("462", :ERR_ALREADYREGISTRED)
319
+ add("463", :ERR_NOPERMFORHOST)
320
+ add("464", :ERR_PASSWDMISMATCH)
321
+ add("465", :ERR_YOUREBANNEDCREEP)
322
+ add("466", :ERR_YOUWILLBEBANNED)
323
+ add("467", :ERR_KEYSET)
324
+ add("471", :ERR_CHANNELISFULL)
325
+ add("472", :ERR_UNKNOWNMODE)
326
+ add("473", :ERR_INVITEONLYCHAN)
327
+ add("474", :ERR_BANNEDFROMCHAN)
328
+ add("475", :ERR_BADCHANNELKEY)
329
+ add("476", :ERR_BADCHANMASK)
330
+ add("477", :ERR_NOCHANMODES)
331
+ add("478", :ERR_BANLISTFULL)
332
+ add("481", :ERR_NOPRIVILEGES)
333
+ add("482", :ERR_CHANOPRIVSNEEDED)
334
+ add("483", :ERR_CANTKILLSERVER)
335
+ add("484", :ERR_RESTRICTED)
336
+ add("485", :ERR_UNIQOPPRIVSNEEDED)
337
+ add("491", :ERR_NOOPERHOST)
338
+ add("492", :ERR_NOSERVICEHOST)
339
+
340
+ #5** Codes
341
+ add("501", :ERR_UMODEUNKNOWNFLAG)
342
+ add("502", :ERR_USERSDONTMATCH)
343
+ add("505", :ERR__NOPRIVMSG)