butler 1.8.0 → 1.8.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. data/CHANGELOG +40 -0
  2. data/README +9 -9
  3. data/Rakefile +15 -71
  4. data/bin/botcontrol +151 -146
  5. data/data/butler/dialogs/botcontrol.rb +8 -3
  6. data/data/butler/dialogs/create.rb +18 -18
  7. data/data/butler/dialogs/create_config.rb +8 -0
  8. data/data/butler/dialogs/en/create_config.yaml +2 -0
  9. data/data/butler/dialogs/en/list.yaml +2 -1
  10. data/data/butler/dialogs/info.rb +2 -2
  11. data/data/butler/dialogs/list.rb +13 -8
  12. data/data/butler/plugins/games/countdown.rb +41 -0
  13. data/data/butler/plugins/games/roll.rb +59 -0
  14. data/lib/access.rb +6 -107
  15. data/lib/access/admin.rb +3 -0
  16. data/lib/access/role.rb +37 -2
  17. data/lib/access/savable.rb +5 -0
  18. data/lib/access/user.rb +21 -2
  19. data/lib/access/yamlbase.rb +4 -0
  20. data/lib/butler.rb +4 -4
  21. data/lib/butler/bot.rb +13 -13
  22. data/lib/butler/irc/client.rb +10 -2
  23. data/lib/butler/irc/parser.rb +7 -2
  24. data/lib/butler/irc/parser/commands.rb +24 -7
  25. data/lib/butler/irc/parser/generic.rb +27 -315
  26. data/lib/butler/irc/parser/rfc2812.rb +328 -0
  27. data/lib/butler/irc/socket.rb +1 -1
  28. data/lib/butler/irc/user.rb +13 -0
  29. data/lib/butler/plugin.rb +1 -1
  30. data/lib/butler/plugin/configproxy.rb +4 -4
  31. data/lib/butler/plugins.rb +1 -1
  32. data/lib/butler/version.rb +1 -1
  33. data/lib/configuration.rb +22 -71
  34. data/lib/dialogline.rb +12 -0
  35. data/lib/event.rb +5 -2
  36. data/lib/installer.rb +52 -24
  37. data/lib/iterator.rb +17 -7
  38. data/lib/log.rb +32 -5
  39. data/lib/log/comfort.rb +33 -16
  40. data/lib/log/entry.rb +25 -5
  41. data/lib/log/fakeio.rb +1 -0
  42. data/lib/log/splitter.rb +6 -2
  43. data/lib/ostructfixed.rb +5 -0
  44. data/lib/ruby/exception/detailed.rb +3 -3
  45. data/lib/scheduler.rb +19 -4
  46. data/lib/scriptfile.rb +9 -2
  47. data/lib/string.rb +176 -0
  48. data/lib/string/ascii.rb +31 -0
  49. data/lib/string/mbencoded.rb +79 -0
  50. data/lib/string/sbencoded.rb +77 -0
  51. data/lib/string/utf8.rb +157 -0
  52. data/lib/templater.rb +68 -10
  53. data/lib/w3validator.rb +86 -0
  54. data/test/irc/serverlistings/test_rpl_hiddenhost.txt +60 -0
  55. data/test/test_access.rb +101 -0
  56. data/test/test_configuration.rb +63 -0
  57. metadata +19 -2
@@ -7,8 +7,13 @@
7
7
 
8
8
 
9
9
  class Access
10
+ # Mixin for classes representing records
10
11
  module Savable
12
+
13
+ # The Access instance that manages this records base
11
14
  attr_accessor :access
15
+
16
+ # The "Base" instance that manages this record
12
17
  attr_accessor :base
13
18
 
14
19
  def save
@@ -48,11 +48,19 @@ class Access
48
48
  end
49
49
  end
50
50
 
51
+ # The record-id
51
52
  attr_reader :id
53
+
54
+ # The credentials of the user (password)
52
55
  attr_reader :credentials
56
+
57
+ # Privileges of the user (Privileges instance)
53
58
  attr_reader :privileges
59
+
60
+ # Roles the user is in (Roles instance)
54
61
  attr_reader :roles
55
62
 
63
+ # Metadata - anything you want
56
64
  attr_accessor :meta
57
65
 
58
66
  # The data needed to restore a user.
@@ -87,6 +95,10 @@ class Access
87
95
  extend(Admin) if @admin
88
96
  end
89
97
 
98
+ # Set the credentials of the user
99
+ # pass in nil (revokes the password, which will lead to no password being
100
+ # accepted as correct) or a plaintext password, the method uses
101
+ # Access#hash_credentials to hash the passed in value.
90
102
  def credentials=(value)
91
103
  @credentials = value ? access.hash_credentials(value, @id) : "*"
92
104
  save
@@ -139,32 +151,39 @@ class Access
139
151
  save
140
152
  end
141
153
 
154
+ # Set the logged? status of the user to true
142
155
  def login
143
156
  @logged = true
144
157
  end
145
158
 
159
+ # Set the logged? status of the user to false
146
160
  def logout
147
161
  @logged = false
148
162
  end
149
163
 
164
+ # Set the logged? status of the user
150
165
  def logged=(value)
151
- @logged = value
166
+ @logged = !!value
152
167
  end
153
168
 
169
+ # Whether the user is logged in or not
154
170
  def logged?
155
171
  @logged
156
172
  end
157
173
 
174
+ # :nodoc:
158
175
  def eql?(other)
159
176
  self.class == other.class && @id.eql?(other.id)
160
177
  end
161
178
  alias == eql? # in ruby1.8 that's unecessary as == uses eql? per default
162
179
 
180
+ # :nodoc:
163
181
  def hash
164
182
  @id.hash
165
183
  end
166
184
 
167
- def inspect # :nodoc:
185
+ # :nodoc:
186
+ def inspect
168
187
  "#<%s:0x%08x base: %s id: %s credentials: %s %s%s%s>" % [
169
188
  self.class,
170
189
  object_id << 1,
@@ -12,6 +12,7 @@ require 'yaml'
12
12
 
13
13
 
14
14
  class Access
15
+
15
16
  # An Access compatible storage backend
16
17
  class YAMLBase
17
18
  include Enumerable
@@ -37,10 +38,12 @@ class Access
37
38
  "#{@path}/#{escape(id)}.yaml"
38
39
  end
39
40
 
41
+ # Escapes path names
40
42
  def escape(id)
41
43
  id.gsub(/[\x00-\x1f.%]/) { |m| "%%%02x"%m }.gsub("/", ".")
42
44
  end
43
45
 
46
+ # Extracts the record-id from a path
44
47
  def path2id(path)
45
48
  path[(@path.length+1)..-6].gsub(".", "/").gsub(/%([\dA-Fa-f]{2})/) { $1.to_i(16).chr }
46
49
  end
@@ -84,6 +87,7 @@ class Access
84
87
  File.delete(filename(id))
85
88
  end
86
89
 
90
+ # All record id's
87
91
  def keys
88
92
  Dir.glob("#{@path}/*.yaml").map { |path|
89
93
  path2id(path)
@@ -20,8 +20,8 @@ require 'yaml'
20
20
 
21
21
 
22
22
  class Butler
23
- @path = nil
24
- @log_device = nil
23
+ @path = nil
24
+ @logger = nil
25
25
  extend Log::Comfort
26
26
 
27
27
  class <<self
@@ -53,7 +53,7 @@ class Butler
53
53
  daemonize(path.base) { }
54
54
  begin
55
55
  File.write(pidfile, $$)
56
- butler = Bot.new(path, botname)
56
+ butler = Bot.new(path, botname, opts)
57
57
  path = butler.path
58
58
  $stderr = File.open(path.log+"/error.log", "w")
59
59
  $stdout = File.open(path.log+"/out.log", "w")
@@ -81,7 +81,7 @@ class Butler
81
81
  pidfile = test_pidfile(path.run, botname)
82
82
  File.write(pidfile, $$)
83
83
  info("Running #{botname} with PID #{$$} interactively")
84
- butler = Bot.new(path, botname)
84
+ butler = Bot.new(path, botname, opts)
85
85
  butler.plugins.load_all
86
86
  butler.login
87
87
  if block then butler.event_loop(&block) else sleep end
@@ -27,7 +27,7 @@ class Butler
27
27
  attr_reader :access
28
28
  attr_reader :base
29
29
  attr_reader :config
30
- attr_reader :log_device
30
+ attr_reader :logger
31
31
  attr_reader :path
32
32
  attr_reader :plugins
33
33
  attr_reader :scheduler
@@ -46,7 +46,7 @@ class Butler
46
46
  :plugins => @base+'/plugins',
47
47
  :strings => @base+'/strings'
48
48
  )
49
- @log_device = $stderr
49
+ @logger = $stderr
50
50
  @config = Configuration.new(@base+'/config')
51
51
  @scheduler = Scheduler.new
52
52
  @access = Access.new(
@@ -62,9 +62,9 @@ class Butler
62
62
  opts.delete(:reconnect_tries) || -1
63
63
  ]
64
64
 
65
- super(@config["connections.main.server"], {
66
- :port => @config["connections.main.port"],
67
- :host => @config["connections.main.host"]
65
+ super(@config["connections/main/server"], {
66
+ :host => @config["connections/main/host"],
67
+ :port => @config["connections/main/port"],
68
68
  }.merge(opts))
69
69
 
70
70
  # { lang => { trigger => SortedSet[ *commands ] } }
@@ -140,12 +140,12 @@ class Butler
140
140
  @access.default_user = @access["default_user"]
141
141
  @access.default_user.login
142
142
 
143
- nick = @config["connections.main.nick"]
144
- pass = @config["connections.main.password"]
143
+ nick = @config["connections/main/nick"]
144
+ pass = @config["connections/main/password"]
145
145
  super(
146
146
  nick,
147
- @config["connections.main.user"],
148
- @config["connections.main.real"]
147
+ @config["connections/main/user"],
148
+ @config["connections/main/real"]
149
149
  )
150
150
  info("Logged in")
151
151
  # FIXME, use #same_nick?
@@ -155,7 +155,7 @@ class Butler
155
155
  @irc.nick(nick)
156
156
  end
157
157
  @irc.identify(pass) if pass
158
- join(*@config["connections.main.channels"])
158
+ join(*@config["connections/main/channels"])
159
159
  end
160
160
 
161
161
  def on_disconnect(reason)
@@ -170,9 +170,9 @@ class Butler
170
170
 
171
171
  def output_to_logfiles
172
172
  # Errors still go to $stderr, $stdin handles puts as "info" level $stderr prints
173
- @log_device = Log.file(@path.log+'/error.log')
174
- $stdout = Log.forward(@log_device, :warn)
175
- $stderr = Log.forward(@log_device, :info)
173
+ @logger = Log.file(@path.log+'/error.log')
174
+ $stdout = Log.forward(@logger, :warn)
175
+ $stderr = Log.forward(@logger, :info)
176
176
  end
177
177
 
178
178
  def inspect # :nodoc:
@@ -35,6 +35,8 @@ class Butler
35
35
  # puts "If this point is reached, client has ended"
36
36
  #
37
37
  class Client
38
+ include Log::Comfort
39
+
38
40
  class Terminate < RuntimeError; end
39
41
  # Created by Butler::IRC::Client#subscribe() and similar methods
40
42
  Listener = Struct.new(:priority, :callback, :args, :unsubscriber) unless defined? Listener
@@ -101,11 +103,12 @@ class Butler
101
103
 
102
104
  def initialize(server, options={}, &on_disconnect)
103
105
  options = DefaultOptions.merge(options)
106
+ @logger = nil
104
107
  @users = Users.new(self)
105
108
  @channels = Channels.new(self)
106
109
  @users.channels = @channels
107
110
  @channels.users = @users
108
- @parser = Parser.new(self, @users, @channels)
111
+ @parser = Parser.new(self, @users, @channels, "rfc2812", "generic")
109
112
 
110
113
  @client_charset = options.delete(:client_charset)
111
114
  @server_charset = options.delete(:server_charset)
@@ -114,7 +117,7 @@ class Butler
114
117
 
115
118
  @timeout = DefaultTimeout.merge(options.delete(:timeout))
116
119
 
117
- @irc = Socket.new(server, options) # the socket, all methods to the socket are wrapped
120
+ @irc = Socket.new(options.delete(:server) || server, options) # the socket, all methods to the socket are wrapped
118
121
 
119
122
  @listen = Hash.new { |h,k| h[k] = [] }
120
123
  @listener = {}
@@ -126,6 +129,11 @@ class Butler
126
129
  subscribe(:PING, 100) { |listener, message| @irc.pong(message.pong) }
127
130
  end
128
131
 
132
+ # Load an additional command-set for @parser
133
+ def load_command_set(*sets)
134
+ @parser.commands.load(*sets)
135
+ end
136
+
129
137
  # callback is called whenever a message with Message#symbol == symbol
130
138
  # (or on every message if symbol is nil)
131
139
  # priority may be any numeric, higher priority is dispatched to first,
@@ -50,6 +50,10 @@ class Butler
50
50
  class UnknownCommand < RuntimeError; end
51
51
  module ParseError
52
52
  include Exception::Detailed
53
+
54
+ def self.extended(obj)
55
+ obj.initialize_details
56
+ end
53
57
  end
54
58
 
55
59
  RMessage = /\A(?::([^ \0]+) )?([A-Za-z\d]+|\d{3})(?: (.*))?\z/
@@ -58,12 +62,13 @@ class Butler
58
62
  attr_reader :client
59
63
  attr_reader :users
60
64
  attr_reader :channels
65
+ attr_reader :commands
61
66
 
62
- def initialize(client, users, channels)
67
+ def initialize(client, users, channels, *command_sets)
63
68
  @client = client
64
69
  @users = users
65
70
  @channels = channels
66
- @commands = Commands.new
71
+ @commands = Commands.new(*command_sets)
67
72
  end
68
73
 
69
74
  # parses an incomming message and returns a Message object from which you
@@ -7,6 +7,7 @@
7
7
 
8
8
 
9
9
  require 'butler/irc/parser'
10
+ require 'log/comfort'
10
11
 
11
12
 
12
13
 
@@ -14,10 +15,22 @@ class Butler
14
15
  module IRC
15
16
  class Parser
16
17
  class Commands
17
- def initialize(specifics=nil)
18
+ include Log::Comfort
19
+
20
+ # enables chdirs
21
+ BasePath = File.expand_path(File.dirname(__FILE__))
22
+
23
+ def initialize(*files)
18
24
  @commands = Hash.new { |h,k| raise IndexError, "Unknown command #{k}" }
19
- file = "#{File.dirname(__FILE__)}/generic.rb"
20
- instance_eval(File.read(file), file)
25
+ load(*files)
26
+ end
27
+
28
+ def load(*files)
29
+ raise ArgumentError, "Requires at least one argument." if files.empty?
30
+ files.each { |file|
31
+ file = "#{BasePath}/#{file}.rb" unless file.include?('/')
32
+ instance_eval(File.read(file), file)
33
+ }
21
34
  end
22
35
 
23
36
  def add(raw, *args, &proc)
@@ -67,10 +80,14 @@ class Butler
67
80
  end
68
81
 
69
82
  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
- }
83
+ if @matcher then
84
+ if match = @matcher.match(message.params) then
85
+ @mapping.zip(match.captures) { |name, value|
86
+ message.alter_member(name, value, true)
87
+ }
88
+ else
89
+ warn "Matching failed in #{symbol} for #{message.params.inspect}"
90
+ end
74
91
  end
75
92
  end
76
93
 
@@ -6,338 +6,50 @@
6
6
 
7
7
 
8
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.
9
+ # A list with all common, but non-rfc2812 IRC-Commands and their parsing
10
+ # instructions.
11
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
12
+ # Seen:
13
+ # - ConferenceRoom (irc.bluewin.ch)
112
14
  add("007", :UNK_007)
15
+
16
+ # Seen:
17
+ # - ConferenceRoom (irc.bluewin.ch)
113
18
  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
19
 
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)
20
+ # Seen:
21
+ # - ConferenceRoom (irc.bluewin.ch)
22
+ add("009", :UNK_009)
125
23
 
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
24
  add("265", :RPL_LOCALUSERS)
175
25
  add("266", :RPL_GLOBALUSERS)
176
- add("290", :RPL_IDENTIFY_MSG) # FIXME non2812 "hyperion-1.0.2b"
177
26
 
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
- }
27
+ # Seen:
28
+ # - hyperion-1.0.2b (irc.freenode.net)
29
+ add("290", :RPL_IDENTIFY_MSG)
30
+
31
+ # Seen:
32
+ # - ConferenceRoom (irc.bluewin.ch), sent if a nick is registered
33
+ add("307", :RPL_REGISTERED_INFO)
205
34
  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
- }
35
+ add("329", :RPL_CHANNEL_INFO) #channel creation time
216
36
 
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|
37
+ # :irc.server.net 333 YourNickname #channel SetByNick 1139902138
38
+ add("333", :RPL_TOPIC_INFO, /^(\S+) (\S+) (\S+) (\d+)/, [:for, :channel, :set_by, :set_at]) { |message, parser|
219
39
  topic = message.channel.topic
220
40
  topic.set_by = message[:set_by]
221
41
  topic.set_at = message[:set_at]
222
42
  }
223
- add("341", :RPL_INVITING)
224
- add("342", :RPL_SUMMONING)
225
43
  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
- }
44
+ add("377", :UNK_377)
45
+
266
46
 
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
47
  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
48
 
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)
49
+ # Seen:
50
+ # - ircu (undernet)
51
+ add("396", :RPL_HOSTHIDDEN, /^(\S+) (?:(.*?)@)?([:\S]+) :(.*)/, [:nick, :user, :displayed_host, :text]) { |message, parser|
52
+ parser.users.myself.force_update(nil, message.displayed_host, nil)
53
+ }
339
54
 
340
- #5** Codes
341
- add("501", :ERR_UMODEUNKNOWNFLAG)
342
- add("502", :ERR_USERSDONTMATCH)
343
55
  add("505", :ERR__NOPRIVMSG)