Class Irc::Client
In: lib/rbot/rfc2812.rb
Parent: Object
BasicUserMessage JoinMessage NamesMessage WhoisMessage ModeChangeMessage KickMessage MotdMessage QuitMessage BanlistMessage UserMessage NoSuchTargetMessage TopicMessage NickMessage WelcomeMessage UnknownMessage InviteMessage PartMessage NetmaskList UserList ArrayOf ChannelList Netmask User\n[lib/rbot/botuser.rb\nlib/rbot/irc.rb] Channel Singleton RfcCasemap StrictRfcCasemap AsciiCasemap Casemap PrivMessage NoticeMessage TokyoCabinet::BDB CIBDB Btree CIBtree Socket MessageQueue QueueRing Client DBHash\n[lib/rbot/registry/bdb.rb\nlib/rbot/registry/tc.rb] DBTree\n[lib/rbot/registry/bdb.rb\nlib/rbot/registry/tc.rb] Server NetmaskDb Bot\n[lib/rbot/botuser.rb\nlib/rbot/config.rb\nlib/rbot/ircbot.rb\nlib/rbot/language.rb\nlib/rbot/message.rb\nlib/rbot/messagemapper.rb\nlib/rbot/plugins.rb\nlib/rbot/rbotconfig.rb\nlib/rbot/registry/bdb.rb\nlib/rbot/registry/tc.rb] lib/rbot/ircsocket.rb lib/rbot/rfc2812.rb lib/rbot/registry/tc.rb lib/rbot/irc.rb lib/rbot/maskdb.rb lib/rbot/message.rb lib/rbot/messagemapper.rb lib/rbot/botuser.rb lib/rbot/registry/tc.rb (null) BotConfig PKGConfig ServerOrCasemap Irc dot/m_35_0.png

Implements RFC 2812 and prior IRC RFCs.

Clients should register Proc{}s to handle the various server events, and the Client class will handle dispatch.

Methods

[]=   deletehandler   handle   new   parse_mode   process   reset  

Attributes

server  [R]  the Server we‘re connected to
user  [R]  the User representing us on that server

Public Class methods

Create a new Client instance

[Source]

     # File lib/rbot/rfc2812.rb, line 961
961:     def initialize
962:       @server = Server.new         # The Server
963:       @user = @server.user("*!*@*")     # The User representing the client on this Server
964: 
965:       @handlers = Hash.new
966: 
967:       # This is used by some messages to build lists of users that
968:       # will be delegated when the ENDOF... message is received
969:       @tmpusers = []
970: 
971:       # Same as above, just for bans
972:       @tmpbans = []
973:     end

Public Instance methods

key:server event to handle
value:proc object called when event occurs

set a handler for a server event

server events currently supported:

TODO handle errors ERR_CHANOPRIVSNEEDED, ERR_CANNOTSENDTOCHAN

welcome:server welcome message on connect
yourhost:your host details (on connection)
created:when the server was started
isupport:information about what this server supports
ping:server pings you (default handler returns a pong)
nicktaken:you tried to change nick to one that‘s in use
badnick:you tried to change nick to one that‘s invalid
topic:someone changed the topic of a channel
topicinfo:on joining a channel or asking for the topic, tells you who set it and when
names:server sends list of channel members when you join
motd:server message of the day
privmsg:privmsg, the core of IRC, a message to you from someone
public:optionally instead of getting privmsg you can hook to only the public ones…
msg:or only the private ones, or both
kick:someone got kicked from a channel
part:someone left a channel
quit:someone quit IRC
join:someone joined a channel
changetopic:the topic of a channel changed
invite:you are invited to a channel
nick:someone changed their nick
mode:a mode change
notice:someone sends you a notice
unknown:any other message not handled by the above

[Source]

      # File lib/rbot/rfc2812.rb, line 1015
1015:     def []=(key, value)
1016:       @handlers[key] = value
1017:     end
key:event name

remove a handler for a server event

[Source]

      # File lib/rbot/rfc2812.rb, line 1021
1021:     def deletehandler(key)
1022:       @handlers.delete(key)
1023:     end

takes a server string, checks for PING, PRIVMSG, NOTIFY, etc, and parses numeric server replies, calling the appropriate handler for each, and sending it a hash containing the data from the server

[Source]

      # File lib/rbot/rfc2812.rb, line 1028
1028:     def process(serverstring)
1029:       data = Hash.new
1030:       data[:serverstring] = serverstring
1031: 
1032:       unless serverstring.chomp =~ /^(:(\S+)\s)?(\S+)(\s(.*))?$/
1033:         raise "Unparseable Server Message!!!: #{serverstring.inspect}"
1034:       end
1035: 
1036:       prefix, command, params = $2, $3, $5
1037: 
1038:       if prefix != nil
1039:         # Most servers will send a full nick!user@host prefix for
1040:         # messages from users. Therefore, when the prefix doesn't match this
1041:         # syntax it's usually the server hostname.
1042:         #
1043:         # This is not always true, though, since some servers do not send a
1044:         # full hostmask for user messages.
1045:         #
1046:         if prefix =~ /^#{Regexp::Irc::BANG_AT}$/
1047:           data[:source] = @server.user(prefix)
1048:         else
1049:           if @server.hostname
1050:             if @server.hostname != prefix
1051:               # TODO do we want to be able to differentiate messages that are passed on to us from /other/ servers?
1052:               debug "Origin #{prefix} for message\n\t#{serverstring.inspect}\nis neither a user hostmask nor the server hostname\nI'll pretend that it's from the server anyway"
1053:               data[:source] = @server
1054:             else
1055:               data[:source] = @server
1056:             end
1057:           else
1058:             @server.instance_variable_set(:@hostname, prefix)
1059:             data[:source] = @server
1060:           end
1061:         end
1062:       end
1063: 
1064:       # split parameters in an array
1065:       argv = []
1066:       params.scan(/(?!:)(\S+)|:(.*)/) { argv << ($1 || $2) } if params
1067: 
1068:       if command =~ /^(\d+)$/ # Numeric replies
1069:         data[:target] = argv[0]
1070:         # A numeric reply /should/ be directed at the client, except when we're connecting with a used nick, in which case
1071:         # it's directed at '*'
1072:         not_us = !([@user.nick, '*'].include?(data[:target]))
1073:         if not_us
1074:           warning "Server reply #{serverstring.inspect} directed at #{data[:target]} instead of client (#{@user.nick})"
1075:         end
1076: 
1077:         num=command.to_i
1078:         case num
1079:         when RPL_WELCOME
1080:           data[:message] = argv[1]
1081:           # "Welcome to the Internet Relay Network
1082:           # <nick>!<user>@<host>"
1083:           if not_us
1084:             warning "Server thinks client (#{@user.inspect}) has a different nick"
1085:             @user.nick = data[:target]
1086:           end
1087:           if data[:message] =~ /([^@!\s]+)(?:!([^@!\s]+?))?@(\S+)/
1088:             nick = $1
1089:             user = $2
1090:             host = $3
1091:             warning "Welcome message nick mismatch (#{nick} vs #{data[:target]})" if nick != data[:target]
1092:             @user.user = user if user
1093:             @user.host = host if host
1094:           end
1095:           handle(:welcome, data)
1096:         when RPL_YOURHOST
1097:           # "Your host is <servername>, running version <ver>"
1098:           data[:message] = argv[1]
1099:           handle(:yourhost, data)
1100:         when RPL_CREATED
1101:           # "This server was created <date>"
1102:           data[:message] = argv[1]
1103:           handle(:created, data)
1104:         when RPL_MYINFO
1105:           # "<servername> <version> <available user modes>
1106:           # <available channel modes>"
1107:           @server.parse_my_info(params.split(' ', 2).last)
1108:           data[:servername] = @server.hostname
1109:           data[:version] = @server.version
1110:           data[:usermodes] = @server.usermodes
1111:           data[:chanmodes] = @server.chanmodes
1112:           handle(:myinfo, data)
1113:         when RPL_ISUPPORT
1114:           # "PREFIX=(ov)@+ CHANTYPES=#& :are supported by this server"
1115:           # "MODES=4 CHANLIMIT=#:20 NICKLEN=16 USERLEN=10 HOSTLEN=63
1116:           # TOPICLEN=450 KICKLEN=450 CHANNELLEN=30 KEYLEN=23 CHANTYPES=#
1117:           # PREFIX=(ov)@+ CASEMAPPING=ascii CAPAB IRCD=dancer :are available
1118:           # on this server"
1119:           #
1120:           @server.parse_isupport(argv[1..-2].join(' '))
1121:           handle(:isupport, data)
1122:         when ERR_NICKNAMEINUSE
1123:           # "* <nick> :Nickname is already in use"
1124:           data[:nick] = argv[1]
1125:           data[:message] = argv[2]
1126:           handle(:nicktaken, data)
1127:         when ERR_ERRONEUSNICKNAME
1128:           # "* <nick> :Erroneous nickname"
1129:           data[:nick] = argv[1]
1130:           data[:message] = argv[2]
1131:           handle(:badnick, data)
1132:         when RPL_TOPIC
1133:           data[:channel] = @server.channel(argv[1])
1134:           data[:topic] = argv[2]
1135:           data[:channel].topic.text = data[:topic]
1136: 
1137:           handle(:topic, data)
1138:         when RPL_TOPIC_INFO
1139:           data[:nick] = @server.user(argv[0])
1140:           data[:channel] = @server.channel(argv[1])
1141: 
1142:           # This must not be an IRC::User because it might not be an actual User,
1143:           # and we risk overwriting valid User data
1144:           data[:source] = argv[2].to_irc_netmask(:server => @server)
1145: 
1146:           data[:time] = Time.at(argv[3].to_i)
1147: 
1148:           data[:channel].topic.set_by = data[:source]
1149:           data[:channel].topic.set_on = data[:time]
1150: 
1151:           handle(:topicinfo, data)
1152:         when RPL_NAMREPLY
1153:           # "( "=" / "*" / "@" ) <channel>
1154:           # :[ "@" / "+" ] <nick> *( " " [ "@" / "+" ] <nick> )
1155:           # - "@" is used for secret channels, "*" for private
1156:           # channels, and "=" for others (public channels).
1157:           data[:channeltype] = argv[1]
1158:           data[:channel] = chan = @server.channel(argv[2])
1159: 
1160:           users = []
1161:           argv[3].scan(/\S+/).each { |u|
1162:             # FIXME beware of servers that allow multiple prefixes
1163:             if(u =~ /^([#{@server.supports[:prefix][:prefixes].join}])?(.*)$/)
1164:               umode = $1
1165:               user = $2
1166:               users << [user, umode]
1167:             end
1168:           }
1169: 
1170:           users.each { |ar|
1171:             u = @server.user(ar[0])
1172:             chan.add_user(u, :silent => true)
1173:             debug "Adding user #{u}"
1174:             if ar[1]
1175:               ms = @server.mode_for_prefix(ar[1].to_sym)
1176:               debug "\twith mode #{ar[1]} (#{ms})"
1177:               chan.mode[ms].set(u)
1178:             end
1179:           }
1180:           @tmpusers += users
1181:         when RPL_ENDOFNAMES
1182:           data[:channel] = @server.channel(argv[1])
1183:           data[:users] = @tmpusers
1184:           handle(:names, data)
1185:           @tmpusers = Array.new
1186:         when RPL_BANLIST
1187:           data[:channel] = @server.channel(argv[1])
1188:           data[:mask] = argv[2]
1189:           data[:by] = argv[3]
1190:           data[:at] = argv[4]
1191:           @tmpbans << data
1192:         when RPL_ENDOFBANLIST
1193:           data[:channel] = @server.channel(argv[1])
1194:           data[:bans] = @tmpbans
1195:           handle(:banlist, data)
1196:           @tmpbans = Array.new
1197:         when RPL_LUSERCLIENT
1198:           # ":There are <integer> users and <integer>
1199:           # services on <integer> servers"
1200:           data[:message] = argv[1]
1201:           handle(:luserclient, data)
1202:         when RPL_LUSEROP
1203:           # "<integer> :operator(s) online"
1204:           data[:ops] = argv[1].to_i
1205:           handle(:luserop, data)
1206:         when RPL_LUSERUNKNOWN
1207:           # "<integer> :unknown connection(s)"
1208:           data[:unknown] = argv[1].to_i
1209:           handle(:luserunknown, data)
1210:         when RPL_LUSERCHANNELS
1211:           # "<integer> :channels formed"
1212:           data[:channels] = argv[1].to_i
1213:           handle(:luserchannels, data)
1214:         when RPL_LUSERME
1215:           # ":I have <integer> clients and <integer> servers"
1216:           data[:message] = argv[1]
1217:           handle(:luserme, data)
1218:         when ERR_NOMOTD
1219:           # ":MOTD File is missing"
1220:           data[:message] = argv[1]
1221:           handle(:motd_missing, data)
1222:         when RPL_LOCALUSERS
1223:           # ":Current local  users: 3  Max: 4"
1224:           data[:message] = argv[1]
1225:           handle(:localusers, data)
1226:         when RPL_GLOBALUSERS
1227:           # ":Current global users: 3  Max: 4"
1228:           data[:message] = argv[1]
1229:           handle(:globalusers, data)
1230:         when RPL_STATSCONN
1231:           # ":Highest connection count: 4 (4 clients) (251 since server was
1232:           # (re)started)"
1233:           data[:message] = argv[1]
1234:           handle(:statsconn, data)
1235:         when RPL_MOTDSTART
1236:           # "<nick> :- <server> Message of the Day -"
1237:           if argv[1] =~ /^-\s+(\S+)\s/
1238:             server = $1
1239:           else
1240:             warning "Server doesn't have an RFC compliant MOTD start."
1241:           end
1242:           @motd = ""
1243:         when RPL_MOTD
1244:           if(argv[1] =~ /^-\s+(.*)$/)
1245:             @motd << $1
1246:             @motd << "\n"
1247:           end
1248:         when RPL_ENDOFMOTD
1249:           data[:motd] = @motd
1250:           handle(:motd, data)
1251:         when RPL_DATASTR
1252:           data[:text] = argv[1]
1253:           handle(:datastr, data)
1254:         when RPL_AWAY
1255:           data[:nick] = user = @server.user(argv[1])
1256:           data[:message] = argv[-1]
1257:           user.away = data[:message]
1258:           handle(:away, data)
1259:         when RPL_WHOREPLY
1260:           data[:channel] = channel = @server.channel(argv[1])
1261:           data[:user] = argv[2]
1262:           data[:host] = argv[3]
1263:           data[:userserver] = argv[4]
1264:           data[:nick] = user = @server.user(argv[5])
1265:           if argv[6] =~ /^(H|G)(\*)?(.*)?$/
1266:             data[:away] = ($1 == 'G')
1267:             data[:ircop] = $2
1268:             data[:modes] = $3.scan(/./).map { |mode|
1269:               m = @server.supports[:prefix][:prefixes].index(mode.to_sym)
1270:               @server.supports[:prefix][:modes][m]
1271:             } rescue []
1272:           else
1273:             warning "Strange WHO reply: #{serverstring.inspect}"
1274:           end
1275:           data[:hopcount], data[:real_name] = argv[7].split(" ", 2)
1276: 
1277:           user.user = data[:user]
1278:           user.host = data[:host]
1279:           user.away = data[:away] # FIXME doesn't provide the actual message
1280:           # TODO ircop status
1281:           # TODO userserver
1282:           # TODO hopcount
1283:           user.real_name = data[:real_name]
1284: 
1285:           channel.add_user(user, :silent=>true)
1286:           data[:modes].map { |mode|
1287:             channel.mode[mode].set(user)
1288:           }
1289: 
1290:           handle(:who, data)
1291:         when RPL_ENDOFWHO
1292:           handle(:eowho, data)
1293:         when RPL_WHOISUSER
1294:           @whois ||= Hash.new
1295:           @whois[:nick] = argv[1]
1296:           @whois[:user] = argv[2]
1297:           @whois[:host] = argv[3]
1298:           @whois[:real_name] = argv[-1]
1299: 
1300:           user = @server.user(@whois[:nick])
1301:           user.user = @whois[:user]
1302:           user.host = @whois[:host]
1303:           user.real_name = @whois[:real_name]
1304:         when RPL_WHOISSERVER
1305:           @whois ||= Hash.new
1306:           @whois[:nick] = argv[1]
1307:           @whois[:server] = argv[2]
1308:           @whois[:server_info] = argv[-1]
1309:           # TODO update user info
1310:         when RPL_WHOISOPERATOR
1311:           @whois ||= Hash.new
1312:           @whois[:nick] = argv[1]
1313:           @whois[:operator] = argv[-1]
1314:           # TODO update user info
1315:         when RPL_WHOISIDLE
1316:           @whois ||= Hash.new
1317:           @whois[:nick] = argv[1]
1318:           user = @server.user(@whois[:nick])
1319:           @whois[:idle] = argv[2].to_i
1320:           user.idle_since = Time.now - @whois[:idle]
1321:           if argv[-1] == 'seconds idle, signon time'
1322:             @whois[:signon] = Time.at(argv[3].to_i)
1323:             user.signon = @whois[:signon]
1324:           end
1325:         when RPL_ENDOFWHOIS
1326:           @whois ||= Hash.new
1327:           @whois[:nick] = argv[1]
1328:           data[:whois] = @whois.dup
1329:           @whois.clear
1330:           handle(:whois, data)
1331:         when RPL_WHOISCHANNELS
1332:           @whois ||= Hash.new
1333:           @whois[:nick] = argv[1]
1334:           @whois[:channels] ||= []
1335:           user = @server.user(@whois[:nick])
1336:           argv[-1].split.each do |prechan|
1337:             pfx = prechan.scan(/[#{@server.supports[:prefix][:prefixes].join}]/)
1338:             modes = pfx.map { |p| @server.mode_for_prefix p }
1339:             chan = prechan[pfx.length..prechan.length]
1340: 
1341:             channel = @server.channel(chan)
1342:             channel.add_user(user, :silent => true)
1343:             modes.map { |mode| channel.mode[mode].set(user) }
1344: 
1345:             @whois[:channels] << [chan, modes]
1346:           end
1347:         when RPL_CHANNELMODEIS
1348:           parse_mode(serverstring, argv[1..-1], data)
1349:           handle(:mode, data)
1350:         when RPL_CREATIONTIME
1351:           data[:channel] = @server.channel(argv[1])
1352:           data[:time] = Time.at(argv[2].to_i)
1353:           data[:channel].creation_time=data[:time]
1354:           handle(:creationtime, data)
1355:         when RPL_CHANNEL_URL
1356:           data[:channel] = @server.channel(argv[1])
1357:           data[:url] = argv[2]
1358:           data[:channel].url=data[:url].dup
1359:           handle(:channel_url, data)
1360:         when ERR_NOSUCHNICK
1361:           data[:target] = argv[1]
1362:           data[:message] = argv[2]
1363:           handle(:nosuchtarget, data)
1364:           if user = @server.get_user(data[:target])
1365:             @server.delete_user(user)
1366:           end
1367:         when ERR_NOSUCHCHANNEL
1368:           data[:target] = argv[1]
1369:           data[:message] = argv[2]
1370:           handle(:nosuchtarget, data)
1371:           if channel = @server.get_channel(data[:target])
1372:             @server.delete_channel(channel)
1373:           end
1374:         else
1375:           warning "Unknown message #{serverstring.inspect}"
1376:           handle(:unknown, data)
1377:         end
1378:         return # We've processed the numeric reply
1379:       end
1380: 
1381:       # Otherwise, the command should be a single word
1382:       case command.to_sym
1383:       when :PING
1384:         data[:pingid] = argv[0]
1385:         handle(:ping, data)
1386:       when :PONG
1387:         data[:pingid] = argv[0]
1388:         handle(:pong, data)
1389:       when :PRIVMSG
1390:         # you can either bind to 'PRIVMSG', to get every one and
1391:         # parse it yourself, or you can bind to 'MSG', 'PUBLIC',
1392:         # etc and get it all nicely split up for you.
1393: 
1394:         begin
1395:           data[:target] = @server.user_or_channel(argv[0])
1396:         rescue
1397:           # The previous may fail e.g. when the target is a server or something
1398:           # like that (e.g. $<mask>). In any of these cases, we just use the
1399:           # String as a target
1400:           # FIXME we probably want to explicitly check for the #<mask> $<mask>
1401:           data[:target] = argv[0]
1402:         end
1403:         data[:message] = argv[1]
1404:         handle(:privmsg, data)
1405: 
1406:         # Now we split it
1407:         if data[:target].kind_of?(Channel)
1408:           handle(:public, data)
1409:         else
1410:           handle(:msg, data)
1411:         end
1412:       when :NOTICE
1413:         begin
1414:           data[:target] = @server.user_or_channel(argv[0])
1415:         rescue
1416:           # The previous may fail e.g. when the target is a server or something
1417:           # like that (e.g. $<mask>). In any of these cases, we just use the
1418:           # String as a target
1419:           # FIXME we probably want to explicitly check for the #<mask> $<mask>
1420:           data[:target] = argv[0]
1421:         end
1422:         data[:message] = argv[1]
1423:         case data[:source]
1424:         when User
1425:           handle(:notice, data)
1426:         else
1427:           # "server notice" (not from user, noone to reply to)
1428:           handle(:snotice, data)
1429:         end
1430:       when :KICK
1431:         data[:channel] = @server.channel(argv[0])
1432:         data[:target] = @server.user(argv[1])
1433:         data[:message] = argv[2]
1434: 
1435:         @server.delete_user_from_channel(data[:target], data[:channel])
1436:         if data[:target] == @user
1437:           @server.delete_channel(data[:channel])
1438:         end
1439: 
1440:         handle(:kick, data)
1441:       when :PART
1442:         data[:channel] = @server.channel(argv[0])
1443:         data[:message] = argv[1]
1444: 
1445:         @server.delete_user_from_channel(data[:source], data[:channel])
1446:         if data[:source] == @user
1447:           @server.delete_channel(data[:channel])
1448:         end
1449: 
1450:         handle(:part, data)
1451:       when :QUIT
1452:         data[:message] = argv[0]
1453:         data[:was_on] = @server.channels.inject(ChannelList.new) { |list, ch|
1454:           list << ch if ch.has_user?(data[:source])
1455:           list
1456:         }
1457: 
1458:         @server.delete_user(data[:source])
1459: 
1460:         handle(:quit, data)
1461:       when :JOIN
1462:         data[:channel] = @server.channel(argv[0])
1463:         data[:channel].add_user(data[:source])
1464: 
1465:         handle(:join, data)
1466:       when :TOPIC
1467:         data[:channel] = @server.channel(argv[0])
1468:         data[:topic] = Channel::Topic.new(argv[1], data[:source], Time.new)
1469:         data[:channel].topic.replace(data[:topic])
1470: 
1471:         handle(:changetopic, data)
1472:       when :INVITE
1473:         data[:target] = @server.user(argv[0])
1474:         data[:channel] = @server.channel(argv[1])
1475: 
1476:         handle(:invite, data)
1477:       when :NICK
1478:         data[:is_on] = @server.channels.inject(ChannelList.new) { |list, ch|
1479:           list << ch if ch.has_user?(data[:source])
1480:           list
1481:         }
1482: 
1483:         data[:newnick] = argv[0]
1484:         data[:oldnick] = data[:source].nick.dup
1485:         data[:source].nick = data[:newnick]
1486: 
1487:         debug "#{data[:oldnick]} (now #{data[:newnick]}) was on #{data[:is_on].join(', ')}"
1488: 
1489:         handle(:nick, data)
1490:       when :MODE
1491:         parse_mode(serverstring, argv, data)
1492:         handle(:mode, data)
1493:       when :ERROR
1494:         data[:message] = argv[1]
1495:         handle(:error, data)
1496:       else
1497:         warning "Unknown message #{serverstring.inspect}"
1498:         handle(:unknown, data)
1499:       end
1500:     end

Clear the server and reset the user

[Source]

     # File lib/rbot/rfc2812.rb, line 976
976:     def reset
977:       @server.clear
978:       @user = @server.user("*!*@*")
979:     end

Private Instance methods

key:server event name
data:hash containing data about the event, passed to the proc

call client‘s proc for an event, if they set one as a handler

[Source]

      # File lib/rbot/rfc2812.rb, line 1507
1507:     def handle(key, data)
1508:       if(@handlers.has_key?(key))
1509:         @handlers[key].call(data)
1510:       end
1511:     end

RPL_CHANNELMODEIS MODE ([+-]<modes> (<params>)*)* When a MODE message is received by a server, Type C will have parameters too, so we must be able to consume parameters for all but Type D modes

[Source]

      # File lib/rbot/rfc2812.rb, line 1519
1519:     def parse_mode(serverstring, argv, data)
1520:       data[:target] = @server.user_or_channel(argv[0])
1521:       data[:modestring] = argv[1..-1].join(" ")
1522:       # data[:modes] is an array where each element
1523:       # is an array with two elements, the first of which
1524:       # is either :set or :reset, and the second symbol
1525:       # is the mode letter. An optional third element
1526:       # is present e.g. for channel modes that need
1527:       # a parameter
1528:       data[:modes] = []
1529:       case data[:target]
1530:       when User
1531:         # User modes aren't currently handled internally,
1532:         # but we still parse them and delegate to the client
1533:         warning "Unhandled user mode message '#{serverstring}'"
1534:         argv[1..-1].each { |arg|
1535:           setting = arg[0].chr
1536:           if "+-".include?(setting)
1537:             setting = setting == "+" ? :set : :reset
1538:             arg[1..-1].each_byte { |b|
1539:               m = b.chr.intern
1540:               data[:modes] << [setting, m]
1541:             }
1542:           else
1543:             # Although typically User modes don't take an argument,
1544:             # this is not true for all modes on all servers. Since
1545:             # we have no knowledge of which modes take parameters
1546:             # and which don't we just assign it to the last
1547:             # mode. This is not going to do strange things often,
1548:             # as usually User modes are only set one at a time
1549:             warning "Unhandled user mode parameter #{arg} found"
1550:             data[:modes].last << arg
1551:           end
1552:         }
1553:       when Channel
1554:         # array of indices in data[:modes] where parameters
1555:         # are needed
1556:         who_wants_params = []
1557: 
1558:         modes = argv[1..-1].dup
1559:         debug modes
1560:         getting_args = false
1561:         while arg = modes.shift
1562:           debug arg
1563:           if getting_args
1564:             # getting args for previously set modes
1565:             idx = who_wants_params.shift
1566:             if idx.nil?
1567:               warning "Oops, problems parsing #{serverstring.inspect}"
1568:               break
1569:             end
1570:             data[:modes][idx] << arg
1571:             getting_args = false if who_wants_params.empty?
1572:           else
1573:             debug @server.supports[:chanmodes]
1574:             setting = :set
1575:             arg.each_byte do |c|
1576:               m = c.chr.intern
1577:               case m
1578:               when :+
1579:                 setting = :set
1580:               when :-
1581:                 setting = :reset
1582:               else
1583:                 data[:modes] << [setting, m]
1584:                 case m
1585:                 when *@server.supports[:chanmodes][:typea]
1586:                   who_wants_params << data[:modes].length - 1
1587:                 when *@server.supports[:chanmodes][:typeb]
1588:                   who_wants_params << data[:modes].length - 1
1589:                 when *@server.supports[:chanmodes][:typec]
1590:                   if setting == :set
1591:                     who_wants_params << data[:modes].length - 1
1592:                   end
1593:                 when *@server.supports[:chanmodes][:typed]
1594:                   # Nothing to do
1595:                 when *@server.supports[:prefix][:modes]
1596:                   who_wants_params << data[:modes].length - 1
1597:                 else
1598:                   warning "Ignoring unknown mode #{m} in #{serverstring.inspect}"
1599:                   data[:modes].pop
1600:                 end
1601:               end
1602:             end
1603:             getting_args = true unless who_wants_params.empty?
1604:           end
1605:         end
1606:         unless who_wants_params.empty?
1607:           warning "Unhandled malformed modeline #{data[:modestring]} (unexpected empty arguments)"
1608:           return
1609:         end
1610: 
1611:         data[:modes].each { |mode|
1612:           set, key, val = mode
1613:           if val
1614:             data[:target].mode[key].send(set, val)
1615:           else
1616:             data[:target].mode[key].send(set)
1617:           end
1618:         }
1619:       else
1620:         warning "Ignoring #{data[:modestring]} for unrecognized target #{argv[0]} (#{data[:target].inspect})"
1621:       end
1622:     end

[Validate]