Class: IRCSupport::Parser

Inherits:
Object
  • Object
show all
Defined in:
lib/ircsupport/parser.rb

Constant Summary collapse

@@eol =
'\x00\x0a\x0d'
@@space =
/ +/
@@maybe_space =
/ */
@@non_space =
/[^ ]+/
@@numeric =
/[0-9]{3}/
@@command =
/[a-zA-Z]+/
@@irc_name =
/[^#@@eol :][^#@@eol ]*/
@@irc_line =
/
  \A
  (?: : (?<prefix> #@@non_space ) #@@space )?
  (?<command> #@@numeric | #@@command )
  (?: #@@space (?<args> #@@irc_name (?: #@@space #@@irc_name )* ) )?
  (?: #@@space : (?<trailing_arg> [^#@@eol]* ) | #@@maybe_space )?
  \z
/x
@@low_quote_from =
/[\x00\x0a\x0d\x10]/
@@low_quote_to =
{
  "\x00" => "\x100",
  "\x0a" => "\x10n",
  "\x0d" => "\x10r",
  "\x10" => "\x10\x10",
}
@@low_dequote_from =
/\x10[0nr\x10]/
@@low_dequote_to =
{
  "\x100" => "\x00",
  "\x10n" => "\x0a",
  "\x10r" => "\x0d",
  "\x10\x10" => "\x10",
}
@@default_isupport =
{
  "PREFIX"    => {"o" => "@", "v" => "+"},
  "CHANTYPES" => ["#"],
  "CHANMODES" => {
    "A" => ["b"],
    "B" => ["k"],
    "C" => ["l"],
    "D" => %w[i m n p s t r]
  },
  "MODES"       => 1,
  "NICKLEN"     => Float::INFINITY,
  "MAXBANS"     => Float::INFINITY,
  "TOPICLEN"    => Float::INFINITY,
  "KICKLEN"     => Float::INFINITY,
  "CHANNELLEN"  => Float::INFINITY,
  "CHIDLEN"     => 5,
  "AWAYLEN"     => Float::INFINITY,
  "MAXTARGETS"  => 1,
  "MAXCHANNELS" => Float::INFINITY,
  "CHANLIMIT"   => {"#" => Float::INFINITY},
  "STATUSMSG"   => ["@", "+"],
  "CASEMAPPING" => :rfc1459,
  "ELIST"       => [],
  "MONITOR"     => 0,
}

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeParser

Returns a new instance of Parser.



85
86
87
88
# File 'lib/ircsupport/parser.rb', line 85

def initialize
  @isupport = @@default_isupport
  @capabilities = []
end

Instance Attribute Details

#capabilitiesArray (readonly)

A list of currently enabled capabilities. It will be updated in response to parsed ‘CAP ACK` messages.

Returns:

  • (Array)


82
83
84
# File 'lib/ircsupport/parser.rb', line 82

def capabilities
  @capabilities
end

#isupportHash (readonly)

The isupport configuration of the IRC server. The configuration will be seeded with sane defaults, and updated in response to parsed ‘005` messages.

Returns:

  • (Hash)


77
78
79
# File 'lib/ircsupport/parser.rb', line 77

def isupport
  @isupport
end

Instance Method Details

#compose_line(elems) ⇒ String

Compose an IRC protocol line.

Parameters:

  • elems (Hash)

    The attributes of the message (as returned by #decompose_line).

Returns:

  • (String)

    An IRC protocol line.



115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
# File 'lib/ircsupport/parser.rb', line 115

def compose_line(elems)
  line = ''
  line << ":#{elems[:prefix]} " if elems[:prefix]
  if !elems[:command]
    raise ArgumentError, "You must specify a command"
  end
  line << elems[:command]

  if elems[:args]
    elems[:args].each_with_index do |arg, idx|
      line << ' '
      if idx != elems[:args].count-1 and arg.match(@@space)
        raise ArgumentError, "Only the last argument may contain spaces"
      end
      if idx == elems[:args].count-1
        line << ':' if arg.match(@@space)
      end
      line << arg
    end
  end

  return line
end

#ctcp_quote(type, message) ⇒ String

CTCP-quote a message.

Parameters:

  • type (String)

    The CTCP type.

  • message (String)

    The text of the CTCP message.

Returns:

  • (String)

    A CTCP-quoted message.



215
216
217
218
219
# File 'lib/ircsupport/parser.rb', line 215

def ctcp_quote(type, message)
  line = low_quote(message)
  line.gsub!(/\x01/, '\a')
  return "\x01#{type} #{line}\x01"
end

#decompose_line(line) ⇒ Hash

Perform low-level parsing of an IRC protocol line.

Parameters:

  • line (String)

    An IRC protocol line you wish to decompose.

Returns:

  • (Hash)

    A decomposed IRC protocol line with 3 keys: ‘command`, the IRC command; `prefix`, the prefix to the command, if any; `args`, an array of any arguments to the command



95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# File 'lib/ircsupport/parser.rb', line 95

def decompose_line(line)
  if line =~ @@irc_line
    c = $~
    elems = {}
    elems[:prefix] = c[:prefix] if c[:prefix]
    elems[:command] = c[:command].upcase
    elems[:args] = []
    elems[:args].concat c[:args].split(@@space) if c[:args]
    elems[:args] << c[:trailing_arg] if c[:trailing_arg]
  else
    raise ArgumentError, "Line is not IRC protocol: #{line}"
  end

  return elems
end

#parse(line) ⇒ IRCSupport::Message

Parse an IRC protocol line into a complete message object.

Parameters:

  • line (String)

    An IRC protocol line.

Returns:



142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
# File 'lib/ircsupport/parser.rb', line 142

def parse(line)
  elems = decompose_line(line)
  elems[:isupport] = @isupport
  elems[:capabilities] = @capabilities

  if elems[:command] =~ /^(PRIVMSG|NOTICE)$/ && elems[:args][1] =~ /\x01/
    return handle_ctcp_message(elems)
  end

  if elems[:command] =~ /^\d{3}$/
    msg_class = "Numeric"
  elsif elems[:command] == "MODE"
    if @isupport['CHANTYPES'].include? elems[:args][0][0]
      msg_class = "ChannelModeChange"
    else
      msg_class = "UserModeChange"
    end
  elsif elems[:command] == "NOTICE" && (!elems[:prefix] || elems[:prefix] !~ /!/)
    msg_class = "ServerNotice"
  elsif elems[:command] =~ /^(PRIVMSG|NOTICE)$/
    msg_class = "Message"
    elems[:is_notice] = true if elems[:command] == "NOTICE"
    if @isupport['CHANTYPES'].include? elems[:args][0][0]
      elems[:is_public] = true
    end
    if @capabilities.include?('identify-msg')
      elems[:args][1], elems[:identified] = split_idmsg(elems[:args][1])
    end
  elsif elems[:command] == "CAP" && %w{LS LIST ACK}.include?(elems[:args][0])
    msg_class = "CAP::#{elems[:args][0]}"
  else
    msg_class = elems[:command]
  end

  begin
    if msg_class == "Numeric"
      begin
        msg_const = constantize("IRCSupport::Message::Numeric#{elems[:command]}")
      rescue
        msg_const = constantize("IRCSupport::Message::#{msg_class}")
      end
    else
      begin
        msg_const = constantize("IRCSupport::Message::#{msg_class}")
      rescue
        msg_const = constantize("IRCSupport::Message::#{msg_class.capitalize}")
      end
    end
  rescue
    msg_const = constantize("IRCSupport::Message")
  end

  message = msg_const.new(elems)

  if message.type == :'005'
    @isupport.merge! message.isupport
  elsif message.type == :cap_ack
    message.capabilities.each do |capability, options|
      if options.include?(:disable)
        @capabilities = @capabilities - [capability]
      elsif options.include?(:enable)
        @capabilities = @capabilities + [capability]
      end
    end
  end

  return message
end