butler 1.8.0 → 1.8.1

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 (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)