X-Git-Url: https://git.netwichtig.de/gitweb/?a=blobdiff_plain;ds=sidebyside;f=lib%2Frbot%2Firc.rb;h=129f947e61d1d9d906e1b02d7b7e7deb486fd5f3;hb=7205060ebc35daf26a22ff6453b4faef477aaca7;hp=69b5d23917f0808089d0622f94b2ba11991df7de;hpb=5659ef2f6717cd713d5202e54a5c4573eeaa3a45;p=user%2Fhenk%2Fcode%2Fruby%2Frbot.git
diff --git a/lib/rbot/irc.rb b/lib/rbot/irc.rb
index 69b5d239..129f947e 100644
--- a/lib/rbot/irc.rb
+++ b/lib/rbot/irc.rb
@@ -3,7 +3,11 @@
# * do we want to handle a Channel list for each User telling which
# Channels is the User on (of those the client is on too)?
# We may want this so that when a User leaves all Channels and he hasn't
-# sent us privmsgs, we know remove him from the Server @users list
+# sent us privmsgs, we know we can remove him from the Server @users list
+# * Maybe ChannelList and UserList should be HashesOf instead of ArrayOf?
+# See items marked as TODO Ho.
+# The framework to do this is now in place, thanks to the new [] method
+# for NetmaskList, which allows retrieval by Netmask or String
#++
# :title: IRC module
#
@@ -17,6 +21,16 @@
require 'singleton'
+class Object
+
+ # We extend the Object class with a method that
+ # checks if the receiver is nil or empty
+ def nil_or_empty?
+ return true unless self
+ return true if self.respond_to? :empty and self.empty?
+ return false
+ end
+end
# The Irc module is used to keep all IRC-related classes
# in the same namespace
@@ -84,11 +98,18 @@ module Irc
@key.to_s
end
+ # Two Casemaps are equal if they have the same upper and lower ranges
+ #
+ def ==(arg)
+ other = arg.to_irc_casemap
+ return self.upper == other.upper && self.lower == other.lower
+ end
+
# Raise an error if _arg_ and self are not the same Casemap
#
def must_be(arg)
other = arg.to_irc_casemap
- raise "Casemap mismatch (#{self} != #{other})" unless self == other
+ raise "Casemap mismatch (#{self.inspect} != #{other.inspect})" unless self == other
return true
end
@@ -176,14 +197,18 @@ module Irc
# @server (if possible) or at the @casemap otherwise
#
def casemap
- @server.casemap rescue @casemap
+ return @server.casemap if defined?(@server) and @server
+ return @casemap
end
# Returns a hash with the current @server and @casemap as values of
# :server and :casemap
#
def server_and_casemap
- {:server => @server, :casemap => @casemap}
+ h = {}
+ h[:server] = @server if defined?(@server) and @server
+ h[:casemap] = @casemap if defined?(@casemap) and @casemap
+ return h
end
# We allow up/downcasing with a different casemap
@@ -296,7 +321,7 @@ class String
raise "Unexpected match #{m} when converting #{self}"
end
}
- Regexp.new(regmask)
+ Regexp.new("^#{regmask}$")
end
end
@@ -436,6 +461,13 @@ class ArrayOf < Array
}
end
+ # We introduce the 'downcase' method, which maps downcase() to all the Array
+ # elements, properly failing when the elements don't have a downcase method
+ #
+ def downcase
+ self.map { |el| el.downcase }
+ end
+
# Modifying methods which we don't handle yet are made private
#
private :[]=, :collect!, :map!, :fill, :flatten!
@@ -443,6 +475,103 @@ class ArrayOf < Array
end
+# We extend the Regexp class with an Irc module which will contain some
+# Irc-specific regexps
+#
+class Regexp
+
+ # We start with some general-purpose ones which will be used in the
+ # Irc module too, but are useful regardless
+ DIGITS = /\d+/
+ HEX_DIGIT = /[0-9A-Fa-f]/
+ HEX_DIGITS = /#{HEX_DIGIT}+/
+ HEX_OCTET = /#{HEX_DIGIT}#{HEX_DIGIT}?/
+ DEC_OCTET = /[01]?\d?\d|2[0-4]\d|25[0-5]/
+ DEC_IP_ADDR = /#{DEC_OCTET}.#{DEC_OCTET}.#{DEC_OCTET}.#{DEC_OCTET}/
+ HEX_IP_ADDR = /#{HEX_OCTET}.#{HEX_OCTET}.#{HEX_OCTET}.#{HEX_OCTET}/
+ IP_ADDR = /#{DEC_IP_ADDR}|#{HEX_IP_ADDR}/
+
+ # IPv6, from Resolv::IPv6, without the \A..\z anchors
+ HEX_16BIT = /#{HEX_DIGIT}{1,4}/
+ IP6_8Hex = /(?:#{HEX_16BIT}:){7}#{HEX_16BIT}/
+ IP6_CompressedHex = /((?:#{HEX_16BIT}(?::#{HEX_16BIT})*)?)::((?:#{HEX_16BIT}(?::#{HEX_16BIT})*)?)/
+ IP6_6Hex4Dec = /((?:#{HEX_16BIT}:){6,6})#{DEC_IP_ADDR}/
+ IP6_CompressedHex4Dec = /((?:#{HEX_16BIT}(?::#{HEX_16BIT})*)?)::((?:#{HEX_16BIT}:)*)#{DEC_IP_ADDR}/
+ IP6_ADDR = /(?:#{IP6_8Hex})|(?:#{IP6_CompressedHex})|(?:#{IP6_6Hex4Dec})|(?:#{IP6_CompressedHex4Dec})/
+
+ # We start with some IRC related regular expressions, used to match
+ # Irc::User nicks and users and Irc::Channel names
+ #
+ # For each of them we define two versions of the regular expression:
+ # * a generic one, which should match for any server but may turn out to
+ # match more than a specific server would accept
+ # * an RFC-compliant matcher
+ #
+ module Irc
+
+ # Channel-name-matching regexps
+ CHAN_FIRST = /[#&+]/
+ CHAN_SAFE = /![A-Z0-9]{5}/
+ CHAN_ANY = /[^\x00\x07\x0A\x0D ,:]/
+ GEN_CHAN = /(?:#{CHAN_FIRST}|#{CHAN_SAFE})#{CHAN_ANY}+/
+ RFC_CHAN = /#{CHAN_FIRST}#{CHAN_ANY}{1,49}|#{CHAN_SAFE}#{CHAN_ANY}{1,44}/
+
+ # Nick-matching regexps
+ SPECIAL_CHAR = /[\x5b-\x60\x7b-\x7d]/
+ NICK_FIRST = /#{SPECIAL_CHAR}|[[:alpha:]]/
+ NICK_ANY = /#{SPECIAL_CHAR}|[[:alnum:]]|-/
+ GEN_NICK = /#{NICK_FIRST}#{NICK_ANY}+/
+ RFC_NICK = /#{NICK_FIRST}#{NICK_ANY}{0,8}/
+
+ USER_CHAR = /[^\x00\x0a\x0d @]/
+ GEN_USER = /#{USER_CHAR}+/
+
+ # Host-matching regexps
+ HOSTNAME_COMPONENT = /[[:alnum:]](?:[[:alnum:]]|-)*[[:alnum:]]*/
+ HOSTNAME = /#{HOSTNAME_COMPONENT}(?:\.#{HOSTNAME_COMPONENT})*/
+ HOSTADDR = /#{IP_ADDR}|#{IP6_ADDR}/
+
+ GEN_HOST = /#{HOSTNAME}|#{HOSTADDR}/
+
+ # # FreeNode network replaces the host of affiliated users with
+ # # 'virtual hosts'
+ # # FIXME we need the true syntax to match it properly ...
+ # PDPC_HOST_PART = /[0-9A-Za-z.-]+/
+ # PDPC_HOST = /#{PDPC_HOST_PART}(?:\/#{PDPC_HOST_PART})+/
+
+ # # NOTE: the final optional and non-greedy dot is needed because some
+ # # servers (e.g. FreeNode) send the hostname of the services as "services."
+ # # which is not RFC compliant, but sadly done.
+ # GEN_HOST_EXT = /#{PDPC_HOST}|#{GEN_HOST}\.??/
+
+ # Sadly, different networks have different, RFC-breaking ways of cloaking
+ # the actualy host address: see above for an example to handle FreeNode.
+ # Another example would be Azzurra, wich also inserts a "=" in the
+ # cloacked host. So let's just not care about this and go with the simplest
+ # thing:
+ GEN_HOST_EXT = /\S+/
+
+ # User-matching Regexp
+ GEN_USER_ID = /(#{GEN_NICK})(?:(?:!(#{GEN_USER}))?@(#{GEN_HOST_EXT}))?/
+
+ # Things such has the BIP proxy send invalid nicks in a complete netmask,
+ # so we want to match this, rather: this matches either a compliant nick
+ # or a a string with a very generic nick, a very generic hostname after an
+ # @ sign, and an optional user after a !
+ BANG_AT = /#{GEN_NICK}|\S+?(?:!\S+?)?@\S+?/
+
+ # # For Netmask, we want to allow wildcards * and ? in the nick
+ # # (they are already allowed in the user and host part
+ # GEN_NICK_MASK = /(?:#{NICK_FIRST}|[?*])?(?:#{NICK_ANY}|[?*])+/
+
+ # # Netmask-matching Regexp
+ # GEN_MASK = /(#{GEN_NICK_MASK})(?:(?:!(#{GEN_USER}))?@(#{GEN_HOST_EXT}))?/
+
+ end
+
+end
+
+
module Irc
@@ -478,8 +607,6 @@ module Irc
# Empty +nick+, +user+ or +host+ are converted to the generic glob pattern
#
def initialize(str="", opts={})
- debug "String: #{str.inspect}, options: #{opts.inspect}"
-
# First of all, check for server/casemap option
#
init_server_or_casemap(opts)
@@ -487,7 +614,9 @@ module Irc
# Now we can see if the given string _str_ is an actual Netmask
if str.respond_to?(:to_str)
case str.to_str
- when /^(?:(\S+?)(?:!(\S+)@(?:(\S+))?)?)?$/
+ # We match a pretty generic string, to work around non-compliant
+ # servers
+ when /^(?:(\S+?)(?:(?:!(\S+?))?@(\S+))?)?$/
# We do assignment using our internal methods
self.nick = $1
self.user = $2
@@ -500,24 +629,48 @@ module Irc
end
end
- # A Netmask is easily converted to a String for the usual representation
+ # A Netmask is easily converted to a String for the usual representation.
+ # We skip the user or host parts if they are "*", unless we've been asked
+ # for the full form
#
+ def to_s
+ ret = nick.dup
+ ret << "!" << user unless user == "*"
+ ret << "@" << host unless host == "*"
+ return ret
+ end
+
def fullform
"#{nick}!#{user}@#{host}"
end
- alias :to_s :fullform
+
+ # This method downcases the fullform of the netmask. While this may not be
+ # significantly different from the #downcase() method provided by the
+ # ServerOrCasemap mixin, it's significantly different for Netmask
+ # subclasses such as User whose simple downcasing uses the nick only.
+ #
+ def full_irc_downcase(cmap=casemap)
+ self.fullform.irc_downcase(cmap)
+ end
+
+ # full_downcase() will return the fullform downcased according to the
+ # User's own casemap
+ #
+ def full_downcase
+ self.full_irc_downcase
+ end
# Converts the receiver into a Netmask with the given (optional)
# server/casemap association. We return self unless a conversion
# is needed (different casemap/server)
#
- # Subclasses of Netmask will return a new Netmask
+ # Subclasses of Netmask will return a new Netmask, using full_downcase
#
def to_irc_netmask(opts={})
if self.class == Netmask
return self if fits_with_server_and_casemap?(opts)
end
- return self.fullform.to_irc_netmask(opts)
+ return self.full_downcase.to_irc_netmask(opts)
end
# Converts the receiver into a User with the given (optional)
@@ -525,7 +678,7 @@ module Irc
# is needed (different casemap/server)
#
def to_irc_user(opts={})
- self.fullform.to_irc_user(opts)
+ self.fullform.to_irc_user(server_and_casemap.merge(opts))
end
# Inspection of a Netmask reveals the server it's bound to (if there is
@@ -533,10 +686,10 @@ module Irc
#
def inspect
str = "<#{self.class}:#{'0x%x' % self.object_id}:"
- str << " @server=#{@server}" if @server
+ str << " @server=#{@server}" if defined?(@server) and @server
str << " @nick=#{@nick.inspect} @user=#{@user.inspect}"
- str << " @host=#{@host.inspect} casemap=#{casemap.inspect}>"
- str
+ str << " @host=#{@host.inspect} casemap=#{casemap.inspect}"
+ str << ">"
end
# Equality: two Netmasks are equal if they downcase to the same thing
@@ -614,10 +767,15 @@ module Irc
#
def matches?(arg)
cmp = arg.to_irc_netmask(:casemap => casemap)
+ debug "Matching #{self.fullform} against #{arg.inspect} (#{cmp.fullform})"
[:nick, :user, :host].each { |component|
us = self.send(component).irc_downcase(casemap)
them = cmp.send(component).irc_downcase(casemap)
- raise NotImplementedError if us.has_irc_glob? && them.has_irc_glob?
+ if us.has_irc_glob? && them.has_irc_glob?
+ next if us == them
+ warn NotImplementedError
+ return false
+ end
return false if us.has_irc_glob? && !them.has_irc_glob?
return false unless us =~ them.to_irc_regexp
}
@@ -655,10 +813,40 @@ module Irc
super(Netmask, ar)
end
+ # We enhance the [] method by allowing it to pick an element that matches
+ # a given Netmask, a String or a Regexp
+ # TODO take into consideration the opportunity to use select() instead of
+ # find(), and/or a way to let the user choose which one to take (second
+ # argument?)
+ #
+ def [](*args)
+ if args.length == 1
+ case args[0]
+ when Netmask
+ self.find { |mask|
+ mask.matches?(args[0])
+ }
+ when String
+ self.find { |mask|
+ mask.matches?(args[0].to_irc_netmask(:casemap => mask.casemap))
+ }
+ when Regexp
+ self.find { |mask|
+ mask.fullform =~ args[0]
+ }
+ else
+ super(*args)
+ end
+ else
+ super(*args)
+ end
+ end
+
end
end
+
class String
# We keep extending String, this time adding a method that converts a
@@ -697,16 +885,18 @@ module Irc
class User < Netmask
alias :to_s :nick
+ attr_accessor :real_name
+
# Create a new IRC User from a given Netmask (or anything that can be converted
# into a Netmask) provided that the given Netmask does not have globs.
#
def initialize(str="", opts={})
- debug "String: #{str.inspect}, options: #{opts.inspect}"
super
raise ArgumentError, "#{str.inspect} must not have globs (unescaped * or ?)" if nick.has_irc_glob? && nick != "*"
raise ArgumentError, "#{str.inspect} must not have globs (unescaped * or ?)" if user.has_irc_glob? && user != "*"
raise ArgumentError, "#{str.inspect} must not have globs (unescaped * or ?)" if host.has_irc_glob? && host != "*"
@away = false
+ @real_name = String.new
end
# The nick of a User may be changed freely, but it must not contain glob patterns.
@@ -737,7 +927,7 @@ module Irc
# Checks if a User is well-known or not by looking at the hostname and user
#
def known?
- return nick!= "*" && user!="*" && host!="*"
+ return nick != "*" && user != "*" && host != "*"
end
# Is the user away?
@@ -764,7 +954,7 @@ module Irc
#
def to_irc_user(opts={})
return self if fits_with_server_and_casemap?(opts)
- return self.fullform.to_irc_user(opts)
+ return self.full_downcase.to_irc_user(opts)
end
# We can replace everything at once with data from another User
@@ -772,29 +962,68 @@ module Irc
def replace(other)
case other
when User
- nick = other.nick
- user = other.user
- host = other.host
+ self.nick = other.nick
+ self.user = other.user
+ self.host = other.host
@server = other.server
@casemap = other.casemap unless @server
- @away = other.away
+ @away = other.away?
else
- replace(other.to_irc_user(server_and_casemap))
+ self.replace(other.to_irc_user(server_and_casemap))
end
end
+ def modes_on(channel)
+ case channel
+ when Channel
+ channel.modes_of(self)
+ else
+ return @server.channel(channel).modes_of(self) if @server
+ raise "Can't resolve channel #{channel}"
+ end
+ end
+
+ def is_op?(channel)
+ case channel
+ when Channel
+ channel.has_op?(self)
+ else
+ return @server.channel(channel).has_op?(self) if @server
+ raise "Can't resolve channel #{channel}"
+ end
+ end
+
+ def is_voice?(channel)
+ case channel
+ when Channel
+ channel.has_voice?(self)
+ else
+ return @server.channel(channel).has_voice?(self) if @server
+ raise "Can't resolve channel #{channel}"
+ end
+ end
end
# A UserList is an ArrayOf User
s
+ # We derive it from NetmaskList, which allows us to inherit any special
+ # NetmaskList method
#
- class UserList < ArrayOf
+ class UserList < NetmaskList
# Create a new UserList, optionally filling it with the elements from
# the Array argument fed to it.
#
def initialize(ar=[])
- super(User, ar)
+ super(ar)
+ @element_class = User
+ end
+
+ # Convenience method: convert the UserList to a list of nicks. The indices
+ # are preserved
+ #
+ def nicks
+ self.map { |user| user.nick }
end
end
@@ -807,7 +1036,6 @@ class String
# String into an Irc::User object
#
def to_irc_user(opts={})
- debug "opts = #{opts.inspect}"
Irc::User.new(self, opts)
end
@@ -829,6 +1057,7 @@ module Irc
# Mode on a Channel
#
class Mode
+ attr_reader :channel
def initialize(ch)
@channel = ch
end
@@ -838,7 +1067,10 @@ module Irc
# Channel modes of type A manipulate lists
#
+ # Example: b (banlist)
+ #
class ModeTypeA < Mode
+ attr_reader :list
def initialize(ch)
super
@list = NetmaskList.new
@@ -859,12 +1091,19 @@ module Irc
# Channel modes of type B need an argument
#
+ # Example: k (key)
+ #
class ModeTypeB < Mode
def initialize(ch)
super
@arg = nil
end
+ def status
+ @arg
+ end
+ alias :value :status
+
def set(val)
@arg = val
end
@@ -882,6 +1121,8 @@ module Irc
# modes of type A
#
class UserMode < ModeTypeB
+ attr_reader :list
+ alias :users :list
def initialize(ch)
super
@list = UserList.new
@@ -903,22 +1144,25 @@ module Irc
# Channel modes of type C need an argument when set,
# but not when they get reset
#
+ # Example: l (limit)
+ #
class ModeTypeC < Mode
def initialize(ch)
super
- @arg = false
+ @arg = nil
end
def status
@arg
end
+ alias :value :status
def set(val)
@arg = val
end
def reset
- @arg = false
+ @arg = nil
end
end
@@ -926,6 +1170,8 @@ module Irc
# Channel modes of type D are basically booleans
#
+ # Example: m (moderate)
+ #
class ModeTypeD < Mode
def initialize(ch)
super
@@ -959,7 +1205,7 @@ module Irc
#
def initialize(text="", set_by="", set_on=Time.new)
@text = text
- @set_by = set_by.to_irc_user
+ @set_by = set_by.to_irc_netmask
@set_on = set_on
end
@@ -1011,8 +1257,8 @@ module Irc
str = "<#{self.class}:#{'0x%x' % self.object_id}:"
str << " on server #{server}" if server
str << " @name=#{@name.inspect} @topic=#{@topic.text.inspect}"
- str << " @users=<#{@users.sort.join(', ')}>"
- str
+ str << " @users=[#{user_nicks.sort.join(', ')}]"
+ str << ">"
end
# Returns self
@@ -1021,6 +1267,35 @@ module Irc
self
end
+ # TODO Ho
+ def user_nicks
+ @users.map { |u| u.downcase }
+ end
+
+ # Checks if the receiver already has a user with the given _nick_
+ #
+ def has_user?(nick)
+ @users.index(nick.to_irc_user(server_and_casemap))
+ end
+
+ # Returns the user with nick _nick_, if available
+ #
+ def get_user(nick)
+ idx = has_user?(nick)
+ @users[idx] if idx
+ end
+
+ # Adds a user to the channel
+ #
+ def add_user(user, opts={})
+ silent = opts.fetch(:silent, false)
+ if has_user?(user)
+ warn "Trying to add user #{user} to channel #{self} again" unless silent
+ else
+ @users << user.to_irc_user(server_and_casemap)
+ end
+ end
+
# Creates a new channel with the given name, optionally setting the topic
# and an initial users list.
#
@@ -1041,7 +1316,7 @@ module Irc
@users = UserList.new
users.each { |u|
- @users << u.to_irc_user(server_and_casemap)
+ add_user(u)
}
# Flags
@@ -1066,25 +1341,25 @@ module Irc
# A channel is local to a server if it has the '&' prefix
#
def local?
- name[0] = 0x26
+ name[0] == 0x26
end
# A channel is modeless if it has the '+' prefix
#
def modeless?
- name[0] = 0x2b
+ name[0] == 0x2b
end
# A channel is safe if it has the '!' prefix
#
def safe?
- name[0] = 0x21
+ name[0] == 0x21
end
# A channel is normal if it has the '#' prefix
#
def normal?
- name[0] = 0x23
+ name[0] == 0x23
end
# Create a new mode
@@ -1093,6 +1368,21 @@ module Irc
@mode[sym.to_sym] = kl.new(self)
end
+ def modes_of(user)
+ l = []
+ @mode.map { |s, m|
+ l << s if (m.class <= UserMode and m.list[user])
+ }
+ l
+ end
+
+ def has_op?(user)
+ @mode.has_key?(:o) and @mode[:o].list[user]
+ end
+
+ def has_voice?(user)
+ @mode.has_key?(:v) and @mode[:v].list[user]
+ end
end
@@ -1107,6 +1397,13 @@ module Irc
super(Channel, ar)
end
+ # Convenience method: convert the ChannelList to a list of channel names.
+ # The indices are preserved
+ #
+ def names
+ self.map { |chan| chan.name }
+ end
+
end
end
@@ -1137,10 +1434,12 @@ module Irc
attr_reader :channels, :users
+ # TODO Ho
def channel_names
@channels.map { |ch| ch.downcase }
end
+ # TODO Ho
def user_nicks
@users.map { |u| u.downcase }
end
@@ -1157,8 +1456,8 @@ module Irc
str = "<#{self.class}:#{'0x%x' % self.object_id}:"
str << " @hostname=#{hostname}"
str << " @channels=#{chans}"
- str << " @users=#{users}>"
- str
+ str << " @users=#{users}"
+ str << ">"
end
# Create a new Server, with all instance variables reset to nil (for
@@ -1179,7 +1478,7 @@ module Irc
#
def reset_capabilities
@supports = {
- :casemapping => 'rfc1459',
+ :casemapping => 'rfc1459'.to_irc_casemap,
:chanlimit => {},
:chanmodes => {
:typea => nil, # Type A: address lists
@@ -1187,8 +1486,8 @@ module Irc
:typec => nil, # Type C: needs a parameter when set
:typed => nil # Type D: must not have a parameter
},
- :channellen => 200,
- :chantypes => "#&",
+ :channellen => 50,
+ :chantypes => "#&!+",
:excepts => nil,
:idchan => {},
:invex => nil,
@@ -1198,8 +1497,8 @@ module Irc
:network => nil,
:nicklen => 9,
:prefix => {
- :modes => 'ov'.scan(/./),
- :prefixes => '@+'.scan(/./)
+ :modes => [:o, :v],
+ :prefixes => [:"@", :+]
},
:safelist => nil,
:statusmsg => nil,
@@ -1213,10 +1512,10 @@ module Irc
# Resets the Channel and User list
#
def reset_lists
- @users.each { |u|
+ @users.reverse_each { |u|
delete_user(u)
}
- @channels.each { |u|
+ @channels.reverse_each { |u|
delete_channel(u)
}
end
@@ -1226,6 +1525,7 @@ module Irc
def clear
reset_lists
reset_capabilities
+ @hostname = @version = @usermodes = @chanmodes = nil
end
# This method is used to parse a 004 RPL_MY_INFO line
@@ -1272,27 +1572,22 @@ module Irc
key = prekey.downcase.to_sym
end
case key
- when :casemapping, :network
+ when :casemapping
noval_warn(key, val) {
- @supports[key] = val
+ @supports[key] = val.to_irc_casemap
}
when :chanlimit, :idchan, :maxlist, :targmax
noval_warn(key, val) {
groups = val.split(',')
groups.each { |g|
k, v = g.split(':')
- @supports[key][k] = v.to_i
+ @supports[key][k] = v.to_i || 0
+ if @supports[key][k] == 0
+ warn "Deleting #{key} limit of 0 for #{k}"
+ @supports[key].delete(k)
+ end
}
}
- when :maxchannels
- noval_warn(key, val) {
- reparse += "CHANLIMIT=(chantypes):#{val} "
- }
- when :maxtargets
- noval_warn(key, val) {
- @supports[key]['PRIVMSG'] = val.to_i
- @supports[key]['NOTICE'] = val.to_i
- }
when :chanmodes
noval_warn(key, val) {
groups = val.split(',')
@@ -1315,6 +1610,19 @@ module Irc
when :invex
val ||= 'I'
@supports[key] = val
+ when :maxchannels
+ noval_warn(key, val) {
+ reparse += "CHANLIMIT=(chantypes):#{val} "
+ }
+ when :maxtargets
+ noval_warn(key, val) {
+ @supports[:targmax]['PRIVMSG'] = val.to_i
+ @supports[:targmax]['NOTICE'] = val.to_i
+ }
+ when :network
+ noval_warn(key, val) {
+ @supports[key] = val
+ }
when :nicklen
noval_warn(key, val) {
@supports[key] = val.to_i
@@ -1379,13 +1687,15 @@ module Irc
# Checks if the receiver already has a channel with the given _name_
#
def has_channel?(name)
- channel_names.index(name.downcase)
+ return false if name.nil_or_empty?
+ channel_names.index(name.irc_downcase(casemap))
end
alias :has_chan? :has_channel?
# Returns the channel with name _name_, if available
#
def get_channel(name)
+ return nil if name.nil_or_empty?
idx = has_channel?(name)
channels[idx] if idx
end
@@ -1394,9 +1704,15 @@ module Irc
# Create a new Channel object bound to the receiver and add it to the
# list of Channel
s on the receiver, unless the channel was
# present already. In this case, the default action is to raise an
- # exception, unless _fails_ is set to false
+ # exception, unless _fails_ is set to false. An exception can also be
+ # raised if _str_ is nil or empty, again only if _fails_ is set to true;
+ # otherwise, the method just returns nil
#
def new_channel(name, topic=nil, users=[], fails=true)
+ if name.nil_or_empty?
+ raise "Tried to look for empty or nil channel name #{name.inspect}" if fails
+ return nil
+ end
ex = get_chan(name)
if ex
raise "Channel #{name} already exists on server #{self}" if fails
@@ -1421,7 +1737,8 @@ module Irc
channel_names.each { |n|
count += 1 if k.include?(n[0])
}
- raise IndexError, "Already joined #{count} channels with prefix #{k}" if count == @supports[:chanlimit][k]
+ # raise IndexError, "Already joined #{count} channels with prefix #{k}" if count == @supports[:chanlimit][k]
+ warn "Already joined #{count}/#{@supports[:chanlimit][k]} channels with prefix #{k}, we may be going over server limits" if count >= @supports[:chanlimit][k]
}
# So far, everything is fine. Now create the actual Channel
@@ -1483,7 +1800,8 @@ module Irc
# Checks if the receiver already has a user with the given _nick_
#
def has_user?(nick)
- user_nicks.index(nick.downcase)
+ return false if nick.nil_or_empty?
+ user_nicks.index(nick.irc_downcase(casemap))
end
# Returns the user with nick _nick_, if available
@@ -1496,22 +1814,31 @@ module Irc
# Create a new User object bound to the receiver and add it to the list
# of User
s on the receiver, unless the User was present
# already. In this case, the default action is to raise an exception,
- # unless _fails_ is set to false
+ # unless _fails_ is set to false. An exception can also be raised
+ # if _str_ is nil or empty, again only if _fails_ is set to true;
+ # otherwise, the method just returns nil
#
def new_user(str, fails=true)
+ if str.nil_or_empty?
+ raise "Tried to look for empty or nil user name #{str.inspect}" if fails
+ return nil
+ end
tmp = str.to_irc_user(:server => self)
old = get_user(tmp.nick)
+ # debug "Tmp: #{tmp.inspect}"
+ # debug "Old: #{old.inspect}"
if old
# debug "User already existed as #{old.inspect}"
if tmp.known?
if old.known?
+ # debug "Both were known"
# Do not raise an error: things like Freenode change the hostname after identification
warning "User #{tmp.nick} has inconsistent Netmasks! #{self} knows #{old.inspect} but access was tried with #{tmp.inspect}" if old != tmp
raise "User #{tmp} already exists on server #{self}" if fails
end
- if old != tmp
+ if old.fullform.downcase != tmp.fullform.downcase
old.replace(tmp)
- # debug "User improved to #{old.inspect}"
+ # debug "Known user now #{old.inspect}"
end
end
return old
@@ -1562,7 +1889,7 @@ module Irc
@users.inject(UserList.new) {
|list, user|
if user.user == "*" or user.host == "*"
- list << user if user.nick.downcase =~ nm.nick.downcase.to_irc_regexp
+ list << user if user.nick.irc_downcase(casemap) =~ nm.nick.irc_downcase(casemap).to_irc_regexp
else
list << user if user.matches?(nm)
end