/*
* Copyright (C) 2002-2004 Morten Brix Pedersen <morten@wtf.dk>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
*/
#include <iostream>
#include <sstream>
#include <vector>
#include <algorithm>
#include <cassert>
#include "Parser.h"
#include "Utils.h"
#include "Channel.h"
#include "FrontEnd.h"
#include "LostIRCApp.h"
#include "ServerConnection.h"
#include "DCC.h"
using std::vector;
using Glib::ustring;
/* different functions used in for_each */
namespace algo
{
struct removeUser : public std::unary_function<ChannelBase*, void>
{
removeUser(const Glib::ustring& n) : nick(n) { }
void operator() (ChannelBase* x) {
Channel *c = dynamic_cast<Channel*>(x);
if (c)
c->removeUser(nick);
}
Glib::ustring nick;
};
struct renameUser : public std::unary_function<ChannelBase*, void>
{
renameUser(const Glib::ustring& f, const Glib::ustring& t) : from(f), to(t) { }
void operator() (ChannelBase* x) {
x->renameUser(from, to);
}
Glib::ustring from;
Glib::ustring to;
};
}
Parser::Parser(ServerConnection *conn)
: _conn(conn)
{
}
void Parser::parseLine(ustring& data)
{
#ifdef DEBUG
App->log << "<< " << data << std::endl;
#endif
if (App->options.strip_colors)
data = stripColors(data, App->options.strip_boldandunderline);
if (data[0] == ':') {
/*
* Message in the form:
* message = [ ":" prefix SPACE ] command [ params ] crlf
* prefix = servername / ( nickname [ [ "!" user ] "@" host ] )
* command = 1*letter / 3digit
* params = *14( SPACE middle ) [ SPACE ":" trailing ]
* =/ 14( SPACE middle ) [ SPACE [ ":" ] trailing ]
*/
// Find prefix
ustring::size_type pos1 = data.find(" ", 1);
ustring from = data.substr(1, pos1 - 1);
// Find command
ustring::size_type pos2 = data.find(" ", pos1 + 1);
ustring command = data.substr(pos1 + 1, (pos2 - 1) - pos1);
// Check whether there is any params
ustring::size_type pos3 = data.find(" :", pos2);
ustring param;
if (pos3 != pos2) {
// We have params
param = data.substr(pos2 + 1, (pos3 - 1) - pos2);
}
// Get rest (whats after the ':', if there were any ':')
ustring rest;
if (pos3 != ustring::npos) {
rest = data.substr(pos3 + 2);
}
#ifdef DEBUG
App->log << "\t[from '" << from << "']";
App->log << " [command '" << command << "']";
App->log << " [param '" << param << "']";
App->log << " [rest '" << rest << "']" << std::endl;
#endif
// Redirect to the right parsing function...
int n = Util::convert<int>(command);
if (n)
numeric(n, from, param, rest);
else if (command == "PRIVMSG")
Privmsg(from, param, rest);
else if (command == "JOIN")
Join(from, param, rest);
else if (command == "PART")
Part(from, param, rest);
else if (command == "QUIT")
Quit(from, rest);
else if (command == "PONG")
// we received our lag check..
_conn->Session.sentLagCheck = false;
else if (command == "MODE")
Mode(from, param, rest);
else if (command == "TOPIC")
Topic(from, param, rest);
else if (command == "NOTICE")
Notice(from, param, rest);
else if (command == "KICK")
Kick(from, param, rest);
else if (command == "NICK")
Nick(from, rest);
else if (command == "WALLOPS")
Wallops(from, rest);
else if (command == "INVITE")
Invite(from, rest);
else if (command == "KILL")
Kill(from, rest);
else
FE::emit(FE::get(UNKNOWN) << data, FE::CURRENT, _conn);
} else {
// Parse ustring in form, eg. 'PING :23523525'
ustring::size_type pos1 = data.find_first_of(" ", 0);
ustring command = data.substr(0, pos1);
// Check whether there are any params
ustring::size_type pos2 = data.find_first_of(":", pos1 + 1);
ustring param;
if ((pos2 - 1) != pos1) {
// We have params
param = data.substr(pos1 + 1, (pos2 - 2) - pos1);
}
// Get rest (whats after the ':')
ustring rest = data.substr(pos2 + 1);
#ifdef DEBUG
App->log << "\t[command '" << command << "']";
App->log << " [param '" << param << "']";
App->log << " [rest '" << rest << "']" << std::endl;
#endif
// Redirect to the right parsing function...
if (command == "PING")
Ping(rest);
else if (command == "NOTICE")
Notice(param + " :" + rest);
else if (command == "ERRORMSG")
FE::emit(FE::get(ERRORMSG) << param + " " + rest, FE::CURRENT, _conn);
else
FE::emit(FE::get(SERVERMSG1) << data, FE::CURRENT, _conn);
}
}
inline
void Parser::Ping(const ustring& rest)
{
_conn->sendPong(rest);
}
void Parser::Privmsg(const ustring& from, const ustring& param, const ustring& rest)
{
if (rest[0] == '\001') {
// We got CTCP
Ctcp(from, param, rest);
} else {
// Normal privmsg
ChannelBase *chan;
if (param == _conn->Session.nick) {
// The message was intended for *us*, so its a query.
chan = _conn->findQuery(findNick(from));
if (!chan) {
chan = new Query(findNick(from));
_conn->Session.channels.push_back(chan);
}
} else {
chan = _conn->findChannel(param);
// Even though this should never happen, it happens with some
// bouncers.
if (!chan)
return;
}
if (shouldHighlight(rest)) {
FE::emit(FE::get(PRIVMSG_HIGHLIGHT) << findNick(from) << rest, *chan, _conn);
App->fe->highlight(*chan, _conn);
} else {
FE::emit(FE::get(PRIVMSG) << findNick(from) << rest, *chan, _conn);
}
}
}
void Parser::Ctcp(const ustring& from, const ustring& param, const ustring& rest)
{
ustring::size_type pos = rest.find_first_of(" \001", 1);
ustring command = rest.substr(1, pos - 1);
if (command == "VERSION") {
_conn->sendVersion(findNick(from));
} else if (command == "ACTION") {
ustring rest_ = rest.substr(pos + 1, (rest.length() - pos) - 2);
ChannelBase *chan;
if (param == _conn->Session.nick) {
// The message was intended for *us*, so its a query.
chan = _conn->findQuery(findNick(from));
if (!chan) {
chan = new Query(findNick(from));
_conn->Session.channels.push_back(chan);
}
} else {
chan = _conn->findChannel(param);
// Even though this should never happen, it happens with some
// bouncers.
if (!chan)
return;
}
if (shouldHighlight(rest)) {
FE::emit(FE::get(ACTION_HIGHLIGHT) << findNick(from) << rest_, *chan, _conn);
App->fe->highlight(*chan, _conn);
} else {
FE::emit(FE::get(ACTION) << findNick(from) << rest_, *chan, _conn);
}
return;
} else if (command == "PING") {
// Reply to the client with the same timestamp they sent us.
ustring rest_ = rest.substr(pos + 1, (rest.length() - pos) - 2);
_conn->sendCtcpNotice(findNick(from), "PING " + rest_);
} else if (command == "DCC") {
ustring dcc_type = getWord(rest, 2);
ustring dcc_address = getWord(rest, 4);
ustring dcc_port = getWord(rest, 5);
if (dcc_type == "SEND") {
ustring dcc_size = getWord(rest, 6);
ustring dcc_filename = getWord(rest, 3);
std::istringstream ss(dcc_address);
unsigned long address;
ss >> address;
std::istringstream ss2(dcc_port);
unsigned short port;
ss2 >> port;
std::istringstream ss3(dcc_size);
unsigned long size;
ss3 >> size;
int n = App->getDcc().addDccSendIn(dcc_filename, findNick(from), address, port, size);
if (n)
FE::emit(FE::get(DCC_RECEIVE) << findNick(from) << dcc_filename << n, FE::CURRENT);
} else if (dcc_type == "CHAT") {
FE::emit(FE::get(SERVERMSG1) << findNick(from) << rest, FE::CURRENT);
std::istringstream ss(dcc_address);
unsigned long address;
ss >> address;
std::istringstream ss2(dcc_port);
unsigned short port;
ss2 >> port;
//DCC d(findNick(from), address, port);
}
return;
}
if (param == _conn->Session.nick)
FE::emit(FE::get(CTCP) << command << findNick(from), FE::CURRENT, _conn);
else
FE::emit(FE::get(CTCP_MULTI) << command << findNick(from) << param, FE::CURRENT, _conn);
}
void Parser::Notice(const ustring& from, const ustring& to, const ustring& rest)
{
if (rest[0] == '\001') {
// CTCP notice
std::string tmp = rest;
std::string::iterator i = remove(tmp.begin(), tmp.end(), '\001');
ustring output(tmp.begin(), i);
FE::emit(FE::get(CTCP_REPLY) << getWord(output, 1) << findNick(from) << skipFirstWord(output), FE::CURRENT, _conn);
} else {
// Normal notice
if (to == _conn->Session.nick) {
FE::emit(FE::get(NOTICEPRIV) << findNick(from) << rest, FE::CURRENT, _conn);
} else {
Channel *c = _conn->findChannel(to);
if (c)
FE::emit(FE::get(NOTICEPUBL) << findNick(from) << to << rest, *c, _conn);
else
FE::emit(FE::get(NOTICEPUBL) << findNick(from) << to << rest, FE::CURRENT, _conn);
}
}
}
void Parser::Notice(const ustring& msg)
{
ustring::size_type pos = msg.find_first_of(" ");
ustring from = msg.substr(0, pos);
ustring rest = msg.substr(pos + 1);
FE::emit(FE::get(NOTICEPRIV) << from << rest, FE::CURRENT, _conn);
}
void Parser::Kick(const ustring& from, const ustring& param, const ustring& msg)
{
ustring chan, nick;
std::istringstream ss(param);
ss >> chan;
ss >> nick;
Channel *c = _conn->findChannel(chan);
assert(c);
c->removeUser(findNick(nick));
FE::emit(FE::get(KICKED) << nick << chan << findNick(from) << msg, *c, _conn);
App->fe->kick(findNick(from), *c, nick, msg, _conn);
if (nick == _conn->Session.nick) // We got kicked
_conn->removeChannel(chan);
}
void Parser::Join(const ustring& nick, const ustring& param, const ustring& rest)
{
ustring chan = param;
// Some clients/servers/bouncers might accidently send the channel name
// in the 'rest' string, a bug there, but we would like to avoid a
// segfault here. I noticed the same hack in the xchat source.
if (chan.empty() && !rest.empty())
chan = getWord(rest, 1);
Channel *c;
if (findNick(nick) == _conn->Session.nick) {
c = new Channel(chan);
_conn->Session.channels.push_back(c);
} else {
c = _conn->findChannel(chan);
assert(c);
c->addUser(findNick(nick));
}
App->fe->join(findNick(nick), *c, _conn);
FE::emit(FE::get(JOIN) << findNick(nick) << chan << findHost(nick), *c, _conn);
}
void Parser::Part(const ustring& nick, const ustring& param, ustring& rest)
{
ustring chan = param;
// Some clients/servers/bouncers might accidently send the channel name
// in the 'rest' string, a bug there, but we would like to avoid a
// segfault here. I noticed the same hack in the xchat sources.
if (chan.empty() && !rest.empty()) {
chan = getWord(rest, 1);
rest.erase();
}
Channel *c = _conn->findChannel(chan);
if (c) {
c->removeUser(findNick(nick));
if (rest.empty())
FE::emit(FE::get(PART2) << findNick(nick) << chan << findHost(nick) << rest, *c, _conn);
else
FE::emit(FE::get(PART) << findNick(nick) << chan << findHost(nick) << rest, *c, _conn);
App->fe->part(findNick(nick), *c, _conn);
if (findNick(nick) == _conn->Session.nick)
_conn->removeChannel(chan);
}
}
void Parser::Quit(const ustring& nick, const ustring& msg)
{
vector<ChannelBase*> chans = _conn->findUser(findNick(nick));
for_each(chans.begin(), chans.end(), algo::removeUser(nick));
App->fe->quit(findNick(nick), chans, _conn);
if (msg.empty())
FE::emit(FE::get(QUIT2) << findNick(nick), chans, _conn);
else
FE::emit(FE::get(QUIT) << findNick(nick) << msg, chans, _conn);
}
void Parser::Nick(const ustring& from, const ustring& to)
{
// When we receive an error that "nick change was too fast", 'to' will
// be empty. just return if it is.
if (to.empty())
return;
// Check whethers it's us who has changed nick
if (findNick(from) == _conn->Session.nick) {
_conn->Session.nick = to;
}
vector<ChannelBase*> chans = _conn->findUser(findNick(from));
App->fe->nick(findNick(from), to, chans, _conn);
for_each(chans.begin(), chans.end(), algo::renameUser(findNick(from), to));
FE::emit(FE::get(NICK) << findNick(from) << to, chans, _conn);
}
void Parser::Invite(const ustring& from, const ustring& rest)
{
FE::emit(FE::get(INVITED) << findNick(from) << rest, FE::CURRENT, _conn);
}
void Parser::Kill(const ustring& from, const ustring& rest)
{
FE::emit(FE::get(KILLED) << findNick(from) << rest, FE::CURRENT, _conn);
}
void Parser::Mode(const ustring& from, const ustring& param, const ustring& rest)
{
if (rest.empty()) {
// We encountered a channel mode message
CMode(from, param);
} else {
// User mode message
// We got line in the form: 'user +x'
FE::emit(FE::get(MODE) << findNick(from) << param << rest, FE::CURRENT, _conn);
}
}
void Parser::CMode(const ustring& from, const ustring& param)
{
// Parse line in the form: '#chan +ovo nick nick2 nick3'
ustring::size_type pos1 = param.find_first_of(" ");
ustring::size_type pos2 = param.find_first_of(" ", pos1 + 1);
// No real modes? No reason to continue.
if (pos1 == ustring::npos)
return;
ustring channel = param.substr(0, pos1);
ustring modes = param.substr(pos1 + 1, (pos2 - pos1) - 1);
ustring args = param.substr(pos2 + 1);
Channel *chan = _conn->findChannel(channel);
// Channel not found? Not sane to continue.
// This happened on a proxy/bouncer where no channel was mentioned in
// MODE, just this:
// :nick!ident@host.com MODE nick +iw
// Note the lack of ':' before +iw.
if (!chan)
return;
// Get arguments
vector<ustring> arguments;
Util::tokenizeWords(args, arguments);
std::vector<User> modesvec;
bool sign = false; // Used to track whether we get a + or a -
vector<ustring>::const_iterator arg_i = arguments.begin();
for (ustring::iterator i = modes.begin(); i != modes.end(); ++i) {
switch (*i)
{
case '+':
sign = true;
break;
case '-':
sign = false;
break;
case 'a':
{
Event e;
IRC::UserMode mode = IRC::ADMIN;
sign ? (e = ADMINED) : (e = DEADMINED);
ustring nick = *arg_i++;
User *user = chan->getUser(nick);
sign ? (user->setMode(mode)) : (user->removeMode(mode));
modesvec.push_back(*user);
FE::emit(FE::get(e) << findNick(from) << nick, *chan, _conn);
}
break;
case 'o':
{
Event e;
IRC::UserMode mode = IRC::OP;
sign ? (e = OPPED) : (e = DEOPPED);
ustring nick = *arg_i++;
User *user = chan->getUser(nick);
sign ? (user->setMode(mode)) : (user->removeMode(mode));
modesvec.push_back(*user);
FE::emit(FE::get(e) << findNick(from) << nick, *chan, _conn);
}
break;
case 'v':
{
Event e;
IRC::UserMode mode = IRC::VOICE;
sign ? (e = VOICED) : (e = DEVOICED);
ustring nick = *arg_i++;
User *user = chan->getUser(nick);
sign ? (user->setMode(mode)) : (user->removeMode(mode));
modesvec.push_back(*user);
FE::emit(FE::get(e) << findNick(from) << nick, *chan, _conn);
}
break;
case 'h':
{
Event e;
IRC::UserMode mode = IRC::HALFOP;
sign ? (e = HALFOPPED) : (e = HALFDEOPPED);
ustring nick = *arg_i++;
User *user = chan->getUser(nick);
sign ? (user->setMode(mode)) : (user->removeMode(mode));
modesvec.push_back(*user);
FE::emit(FE::get(e) << findNick(from) << nick, *chan, _conn);
}
break;
case 'b':
{
Event e;
sign ? (e = BANNED) : (e = UNBANNED);
ustring nick = *arg_i++;
FE::emit(FE::get(e) << findNick(from) << nick, *chan, _conn);
break;
}
default:
{
// none of the above modes, just display a default
// "foo set mode +X user" message. or alternatively
// MODE #chan +l 500
char modebuf[3];
if (sign)
modebuf[0] = '+';
else
modebuf[0] = '-';
modebuf[1] = (*i);
modebuf[2] = '\0';
if (isChannelMode(*i)) {
FE::emit(FE::get(CMODE) << findNick(from) << modebuf << channel, *chan, _conn);
} else {
ustring nick;
if (arg_i == arguments.end())
nick = *--arg_i;
else
nick = *arg_i++;
FE::emit(FE::get(MODE) << findNick(from) << modebuf << nick, *chan, _conn);
}
}
}
}
// Channel user mode
App->fe->CUMode(findNick(from), *chan, modesvec, _conn);
}
void Parser::Topic(const ustring& from, const ustring& chan, const ustring& rest)
{
Channel *c = _conn->findChannel(chan);
if (c)
FE::emit(FE::get(TOPICCHANGE) << findNick(from) << rest, *c, _conn);
else
FE::emit(FE::get(TOPICCHANGE) << findNick(from) << rest, FE::CURRENT, _conn);
}
void Parser::Topic(const ustring& param, const ustring& rest)
{
ustring::size_type pos1 = param.find_first_of("#&+!");
ustring::size_type pos2 = param.find_first_of(" ", pos1);
ustring chan = param.substr(pos1, pos2 - pos1);
Channel *c = _conn->findChannel(chan);
if (c)
FE::emit(FE::get(TOPICIS) << chan << rest, *c, _conn);
else
FE::emit(FE::get(TOPICIS) << chan << rest, FE::CURRENT, _conn);
}
void Parser::TopicTime(const ustring& param)
{
// Find channel
ustring::size_type pos1 = param.find_first_of("#&+!");
ustring::size_type pos2 = param.find_first_of(" ", pos1);
ustring chan = param.substr(pos1, pos2 - pos1);
// Find user who set the topic
ustring::size_type pos3 = param.find_first_of(" ", pos2 + 1);
ustring nick = param.substr(pos2 + 1, (pos3 - pos2) - 1);
ustring time = param.substr(pos3 + 1);
long date = std::atol(time.c_str());
time = std::ctime(reinterpret_cast<const time_t*>(&date));
Channel *c = _conn->findChannel(chan);
if (c)
FE::emit(FE::get(TOPICTIME) << nick << time.substr(0, time.size() - 1), *c, _conn);
else
FE::emit(FE::get(TOPICTIME) << nick << time.substr(0, time.size() - 1), FE::CURRENT, _conn);
}
void Parser::Away(const ustring& param, const ustring& rest)
{
ustring param1, param2;
std::istringstream ss(param);
ss >> param1;
ss >> param2;
FE::emit(FE::get(AWAY) << param2 << rest, FE::CURRENT, _conn);
}
void Parser::Wallops(const ustring& from, const ustring& rest)
{
FE::emit(FE::get(WALLOPS) << findNick(from) << rest, FE::CURRENT, _conn);
}
void Parser::Banlist(const ustring& param)
{
ustring dummy, chan, banmask, owner, time;
std::istringstream ss(param);
ss >> dummy;
ss >> chan;
ss >> banmask;
ss >> owner;
ss >> time;
long date = std::atol(time.c_str());
time = std::ctime((const time_t *)&date);
Channel *c = _conn->findChannel(chan);
assert(c);
FE::emit(FE::get(BANLIST) << banmask << owner, *c, _conn);
}
// Function to parse the 005 numeric string, also known as RPL_ISUPPORT
// Right now only a small subset of the parameters are being used, namely:
//
// CHANTYPES, PREFIX, CHANMODES, NETWORK
//
// The specification can be found (at the time of writing) at:
// http://www.irc.org/tech_docs/005.html
// http://www.irc.org/tech_docs/draft-brocklesby-irc-isupport-03.txt
void Parser::parseIRCSupports(const ustring& supports)
{
ustring::size_type pos;
pos = supports.find("CHANTYPES=");
if (pos != ustring::npos) {
pos += 10;
ustring::size_type endpos = supports.find_first_of(" ", pos);
_conn->supports.chantypes = supports.substr(pos, endpos - pos);
}
pos = supports.find("PREFIX=");
if (pos != ustring::npos) {
pos += 7;
ustring::size_type endpos = supports.find_first_of(" ", pos);
_conn->supports.prefix = supports.substr(pos, endpos - pos);
}
pos = supports.find("CHANMODES=");
if (pos != ustring::npos) {
pos += 10;
ustring::size_type endpos = supports.find_first_of(" ", pos);
_conn->supports.chanmodes = supports.substr(pos, endpos - pos);
}
pos = supports.find("NETWORK=");
if (pos != ustring::npos) {
pos += 8;
ustring::size_type endpos = supports.find_first_of(" ", pos);
_conn->supports.network = supports.substr(pos, endpos - pos);
}
}
void Parser::numeric(int n, const ustring& from, const ustring& param, const ustring& rest)
{
switch(n)
{
case 5: // RPL_ISUPPORT
parseIRCSupports(skipFirstWord(param));
case 4: // RPL_MYINFO
case 252: // RPL_LUSEROP
case 253: // RPL_LUSERUNKNOWN
case 254: // RPL_LUSERCHANNELS
FE::emit(FE::get(SERVERMSG1) << skipFirstWord(param) << rest, FE::CURRENT, _conn);
break;
case 1: // RPL_WELCOME
_conn->Session.servername = from;
_conn->Session.hasRegistered = true;
_conn->Session.nick = param;
_conn->addConnectionTimerCheck();
App->fe->connected(_conn);
case 2: // RPL_YOURHOST
case 3: // RPL_CREATED
case 251: // RPL_LUSERCLIENT
case 255: // RPL_LUSERME
FE::emit(FE::get(SERVERMSG1) << rest, FE::CURRENT, _conn);
break;
case 301: // RPL_AWAY
Away(param, rest);
break;
case 305: // RPL_UNAWAY
_conn->Session.isAway = false;
App->fe->away(false, _conn);
FE::emit(FE::get(SERVERMSG1) << rest, FE::CURRENT, _conn);
break;
case 306: // RPL_NOWAWAY
_conn->Session.isAway = true;
App->fe->away(true, _conn);
FE::emit(FE::get(SERVERMSG1) << rest, FE::CURRENT, _conn);
break;
case 332: // RPL_TOPIC
Topic(param, rest);
break;
case 333:
TopicTime(param);
break;
case 367: // RPL_BANLIST
Banlist(param);
break;
case 368: // RPL_END_OF_BANLIST
case 372: // RPL_MOTD
case 375: // RPL_MOTDSTART
FE::emit(FE::get(SERVERMSG1) << rest, FE::CURRENT, _conn);
break;
case 376: // RPL_ENDOFMOTD
FE::emit(FE::get(SERVERMSG1) << rest, FE::CURRENT, _conn);
_conn->Session.endOfMotd = true;
_conn->sendCmds();
break;
case 401: // ERR_NOSUCNICK
case 403: // ERR_NOSUCHCHANNEL
case 404: // ERR_CANNOTSENDTOCHAN
case 405: // ERR_TOOMANYCHANNELS
FE::emit(FE::get(ERRORMSG) << skipFirstWord(param) + ": " + rest, FE::CURRENT, _conn);
break;
case 412: // ERR_NOTEXTTOSEND
FE::emit(FE::get(SERVERMSG1) << rest, FE::CURRENT, _conn);
break;
case 422: // ERR_NOMOTD
_conn->sendCmds();
break;
case 433: // ERR_NICKNAMEINUSE
// Apply a _ to the nickname if we havn't received MOTD
// XXX: also send msg to frontend?
if (!_conn->Session.endOfMotd)
_conn->sendNick(_conn->Session.nick += "_");
else
FE::emit(FE::get(ERRORMSG) << rest, FE::CURRENT, _conn);
break;
case 438: // Nick change to fast
case 442: // ERR_NOTONCHANNEL
case 443: // ERR_USERONCHANNEL
case 451: // ERR_NOTREGISTERED
case 461: // ERR_NEEDMOREPARAMS
case 462: // ERR_ALLREADYREGISTERED
case 464: // ERR_PASSWDMISMATCH
case 465: // ERR_YOUREBANNEDCREEP
case 467: // ERR_KEYSET
case 471: // ERR_CHANNELISFULL
case 472: // ERR_UNKNOWMODE
case 473: // ERR_INVITEONLYCHAN
case 474: // ERR_BANNEDFROMCHAN
case 475: // ERR_BADCHANNELKEY
case 481: // ERR_NOPRIVILEGES
case 482: // ERR_CHANOPRIVSNEEDED
case 491: // ERR_NOOPERHOST
case 501: // ERR_UMODEUNKNOWNFLAG
case 502: // ERR_USERSDONTMATCH
FE::emit(FE::get(ERRORMSG) << skipFirstWord(param) + " " + rest, FE::CURRENT, _conn);
break;
case 353: // RPL_NAMREPLY
Names(param, rest);
break;
case 366: // RPL_ENDOFNAMES
{
Channel *c = _conn->findChannel(getWord(param, 2));
if (c && !c->endOfNames) {
c->endOfNames = true;
App->fe->names(*c, _conn);
} else {
FE::emit(FE::get(SERVERMSG2) << getWord(param, 2) << rest, FE::CURRENT, _conn);
}
}
break;
case 317: // RPL_WHOISIDLE
{
long idle = Util::convert<long>(getWord(param, 3));
std::ostringstream ss;
ss << _("idle: ");
ss << idle / 3600 << ":" << (idle / 60) % 60 << ":" << idle % 60;
long date = std::atol(getWord(param, 4).c_str());
ustring time = std::ctime(reinterpret_cast<const time_t*>(&date));
ss << _(", signon time: ") << time.substr(0, time.size() - 1);
FE::emit(FE::get(WHOIS_GENERIC) << getWord(param, 2) << ss.str(), FE::CURRENT, _conn);
}
break;
case 314: // RPL_WHOWASUSER
case 311: // RPL_WHOISUSER
FE::emit(FE::get(WHOIS_USER) << getWord(param, 2) << getWord(param, 3) << getWord(param, 4) << rest, FE::CURRENT, _conn);
break;
case 312: // RPL_WHOISSERVER
FE::emit(FE::get(WHOIS_SERVER) << getWord(param, 2) << getWord(param, 3) << rest, FE::CURRENT, _conn);
break;
case 313: // RPL_WHOISOPERATOR
// We need this find_first_of to omit the first word
FE::emit(FE::get(WHOIS_GENERIC) << getWord(param, 2) << rest, FE::CURRENT, _conn);
break;
case 318: // RPL_ENDOFWHOIS
break;
case 319: // RPL_WHOISCHANNELS
FE::emit(FE::get(WHOIS_CHANNELS) << getWord(param, 2) << rest, FE::CURRENT, _conn);
break;
case 320: // NickServ (freenode.net only?) "foo is an identified user" reply.
FE::emit(FE::get(WHOIS_GENERIC) << getWord(param, 2) << rest, FE::CURRENT, _conn);
break;
case 330: // QuakeNet "foo is authed as" reply.
FE::emit(FE::get(WHOIS_GENERIC) << getWord(param, 2) << rest + " " + getWord(param, 3), FE::CURRENT, _conn);
break;
case 421: // ERR_UNKNOWNCOMMAND
FE::emit(FE::get(SERVERMSG2) << getWord(param, 2) << rest, FE::CURRENT, _conn);
break;
default:
FE::emit(FE::get(SERVERMSG2) << skipFirstWord(param) << rest, FE::CURRENT, _conn);
}
}
void Parser::Names(const ustring& chan, const ustring& names)
{
// Find channel from a ustring like 'nick = #chan'
ustring::size_type pos = chan.find_first_of("#&+!");
ustring channel = chan.substr(pos);
Channel *c = _conn->findChannel(channel);
if (c && !c->endOfNames) {
vector<ustring> nicks;
Util::tokenizeWords(names, nicks);
vector<ustring>::const_iterator i;
for (i = nicks.begin(); i != nicks.end(); ++i)
{
if ((*i)[0] == '*')
c->addUser(i->substr(1), IRC::OWNER);
else if ((*i)[0] == '!')
c->addUser(i->substr(1), IRC::ADMIN);
else if ((*i)[0] == '@')
c->addUser(i->substr(1), IRC::OP);
else if ((*i)[0] == '+')
c->addUser(i->substr(1), IRC::VOICE);
else if ((*i)[0] == '%')
c->addUser(i->substr(1), IRC::HALFOP);
else
c->addUser(*i, IRC::NONE);
}
} else {
FE::emit(FE::get(NAMES) << channel << names, FE::CURRENT, _conn);
}
}
bool Parser::shouldHighlight(const ustring& str)
{
if (str.find(_conn->Session.nick) != ustring::npos)
return true;
std::istringstream ss(App->options.highlight_words->raw());
ustring tmp;
while (ss >> tmp)
if (str.find(tmp) != ustring::npos)
return true;
return false;
}
bool Parser::isChannelMode(char m)
{
if (_conn->supports.chanmodes.find_first_of(m) != ustring::npos)
return true;
return false;
}
ustring getWord(const ustring& str, int n)
{
int count = 0;
ustring::size_type lastPos = str.find_first_not_of(" ", 0);
ustring::size_type pos = str.find_first_of(" ", lastPos);
while (pos != ustring::npos || lastPos != ustring::npos)
{
if ((count + 1) == n)
return str.substr(lastPos, pos - lastPos);
lastPos = str.find_first_not_of(" ", pos);
pos = str.find_first_of(" ", lastPos);
count++;
}
return "";
}
// Strip mIRC colors. Spec at: http://www.mirc.co.uk/help/color.txt
// Only strips color-strings. Not bold and underline.
ustring stripColors(const ustring& str, const bool stripBoldAndUnderline)
{
ustring newstr;
bool color = false;
int numbercount = 0;
for (ustring::size_type i = 0; i < str.length(); ++i)
{
if (str[i] == '\017') { // RESET
color = false;
} else if (stripBoldAndUnderline && str[i] == '\002') {
// BOLD
// No-op.
} else if (stripBoldAndUnderline && str[i] == '\037') {
// UNDERLINE
// No-op.
} else if (str[i] == '\003') { // COLOR
color = true;
} else if (color && isdigit(str[i]) && numbercount < 2) {
numbercount++;
} else if (color && str[i] == ',' && numbercount < 3) {
numbercount = 0;
} else {
numbercount = 0;
color = false;
newstr += str[i];
}
}
return newstr;
}
Glib::ustring findNick(const Glib::ustring& str)
{
return str.substr(0, str.find_first_of("!"));
}
Glib::ustring findHost(const Glib::ustring& str)
{
return str.substr(str.find_first_of("!") + 1);
}
Glib::ustring skipFirstWord(const Glib::ustring& str)
{
return str.substr(str.find_first_of(" ") + 1);
}
syntax highlighted by Code2HTML, v. 0.9.1