Lots of additions again. Working on implementing commands and prettifying the output.
[quassel.git] / network / server.cpp
1 /***************************************************************************
2  *   Copyright (C) 2005/06 by The Quassel Team                             *
3  *   devel@quassel-irc.org                                                 *
4  *                                                                         *
5  *   This program is free software; you can redistribute it and/or modify  *
6  *   it under the terms of the GNU General Public License as published by  *
7  *   the Free Software Foundation; either version 2 of the License, or     *
8  *   (at your option) any later version.                                   *
9  *                                                                         *
10  *   This program is distributed in the hope that it will be useful,       *
11  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
12  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
13  *   GNU General Public License for more details.                          *
14  *                                                                         *
15  *   You should have received a copy of the GNU General Public License     *
16  *   along with this program; if not, write to the                         *
17  *   Free Software Foundation, Inc.,                                       *
18  *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
19  ***************************************************************************/
20
21 #include "global.h"
22 #include "server.h"
23 #include "cmdcodes.h"
24 #include "message.h"
25
26 #include <QMetaObject>
27 #include <QDateTime>
28
29 Server::Server(QString net) : network(net) {
30
31 }
32
33 Server::~Server() {
34
35 }
36
37 void Server::run() {
38   connect(&socket, SIGNAL(connected()), this, SLOT(socketConnected()));
39   connect(&socket, SIGNAL(disconnected()), this, SLOT(socketDisconnected()));
40   connect(&socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(socketError(QAbstractSocket::SocketError)));
41   connect(&socket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), this, SLOT(socketStateChanged(QAbstractSocket::SocketState)));
42   connect(&socket, SIGNAL(readyRead()), this, SLOT(socketHasData()));
43
44   exec();
45 }
46
47 void Server::connectToIrc(QString net) {
48   if(net != network) return; // not me!
49   networkSettings = global->getData("Networks").toMap()[net].toMap();
50   identity = global->getData("Identities").toMap()[networkSettings["Identity"].toString()].toMap();
51   QList<QVariant> servers = networkSettings["Servers"].toList();
52   QString host = servers[0].toMap()["Address"].toString();
53   quint16 port = servers[0].toMap()["Port"].toUInt();
54   displayStatusMsg(QString("Connecting to %1:%2...").arg(host).arg(port));
55   socket.connectToHost(host, port);
56 }
57
58 void Server::disconnectFromIrc(QString net) {
59   if(net != network) return; // not me!
60   socket.disconnectFromHost();
61 }
62
63 void Server::socketHasData() {
64   while(socket.canReadLine()) {
65     QString s = socket.readLine().trimmed();
66     qDebug() << "Read: " << s;
67     emit recvRawServerMsg(s);
68     //Message *msg = Message::createFromServerString(this, s);
69     handleServerMsg(s);
70   }
71 }
72
73 void Server::socketError( QAbstractSocket::SocketError err ) {
74   //qDebug() << "Socket Error!";
75   //emit error(err);
76 }
77
78 void Server::socketConnected( ) {
79   putRawLine(QString("NICK :%1").arg(identity["NickList"].toStringList()[0]));
80   putRawLine(QString("USER %1 8 * :%2").arg(identity["Ident"].toString()).arg(identity["RealName"].toString()));
81 }
82
83 void Server::socketDisconnected( ) {
84   //qDebug() << "Socket disconnected!";
85   emit disconnected();
86 }
87
88 void Server::socketStateChanged(QAbstractSocket::SocketState state) {
89   //qDebug() << "Socket state changed: " << state;
90 }
91
92 QString Server::nickFromMask(QString mask) {
93   return mask.section('!', 0, 0);
94 }
95
96 QString Server::userFromMask(QString mask) {
97   QString userhost = mask.section('!', 1);
98   if(userhost.isEmpty()) return QString();
99   return userhost.section('@', 0, 0);
100 }
101
102 QString Server::hostFromMask(QString mask) {
103   QString userhost = mask.section('!', 1);
104   if(userhost.isEmpty()) return QString();
105   return userhost.section('@', 1);
106 }
107
108 void Server::userInput(QString net, QString buf, QString msg) {
109   if(net != network) return; // not me!
110   msg = msg.trimmed(); // remove whitespace from start and end
111   if(msg.isEmpty()) return;
112   if(!msg.startsWith('/')) {
113     msg = QString("/SAY ") + msg;
114   }
115   handleUserMsg(buf, msg);
116 }
117
118 void Server::putRawLine(QString s) {
119   qDebug() << "SentRaw: " << s;
120   s += "\r\n";
121   socket.write(s.toAscii());
122 }
123
124 void Server::putCmd(QString cmd, QStringList params, QString prefix) {
125   QString m;
126   if(!prefix.isEmpty()) m += ":" + prefix + " ";
127   m += cmd.toUpper();
128   for(int i = 0; i < params.size() - 1; i++) {
129     m += " " + params[i];
130   }
131   if(!params.isEmpty()) m += " :" + params.last();
132   qDebug() << "SentCmd: " << m;
133   m += "\r\n";
134   socket.write(m.toAscii());
135 }
136
137 /** Handle a raw message string sent by the server. We try to find a suitable handler, otherwise we call a default handler. */
138 void Server::handleServerMsg(QString msg) {
139   try {
140     if(msg.isEmpty()) {
141       qWarning() << "Received empty string from server!";
142       return;
143     }
144     // OK, first we split the raw message into its various parts...
145     QString prefix;
146     QString cmd;
147     QStringList params;
148     if(msg[0] == ':') {
149       msg.remove(0,1);
150       prefix = msg.section(' ', 0, 0);
151       msg = msg.section(' ', 1);
152     }
153     cmd = msg.section(' ', 0, 0).toUpper();
154     msg = msg.section(' ', 1);
155     QString left = msg.section(':', 0, 0);
156     QString trailing = msg.section(':', 1);
157     if(!left.isEmpty()) {
158       params << left.split(' ', QString::SkipEmptyParts);
159     }
160     if(!trailing.isEmpty()) {
161       params << trailing;
162     }
163     // numeric replies usually have our own nick as first param. Remove this!
164     // BTW, this behavior is not in the RFC.
165     uint num = cmd.toUInt();
166     if(num > 1 && params.count() > 0) {  // 001 sets our nick, so we shouldn't remove anything
167       if(params[0] == currentNick) params.removeFirst();
168       else qWarning((QString("First param NOT nick: %1:%2 %3").arg(prefix).arg(cmd).arg(params.join(" "))).toAscii());
169     }
170     // Now we try to find a handler for this message. BTW, I do love the Trolltech guys ;-)
171     QString hname = cmd.toLower();
172     hname[0] = hname[0].toUpper();
173     hname = "handleServer" + hname;
174     if(!QMetaObject::invokeMethod(this, hname.toAscii(), Q_ARG(QString, prefix), Q_ARG(QStringList, params))) {
175       // Ok. Default handler it is.
176       defaultServerHandler(cmd, prefix, params);
177     }
178   } catch(Exception e) {
179     emit displayMsg("", Message(e.msg(), "", Message::Error));
180   }
181 }
182
183 void Server::defaultServerHandler(QString cmd, QString prefix, QStringList params) {
184   uint num = cmd.toUInt();
185   if(num) {
186     // A lot of server messages don't really need their own handler because they don't do much.
187     // Catch and handle these here.
188     switch(num) {
189       // Welcome, status, info messages. Just display these.
190       case 2: case 3: case 4: case 5: case 251: case 252: case 253: case 254: case 255: case 372: case 375:
191         emit displayMsg("", Message(params.join(" "), prefix, Message::Server));
192         break;
193       // Ignore these commands.
194       case 366: case 376:
195         break;
196
197       // Everything else will be marked in red, so we can add them somewhere.
198       default:
199         emit displayMsg("", Message(cmd + " " + params.join(" "), prefix, Message::Error));
200     }
201     //qDebug() << prefix <<":"<<cmd<<params;
202   } else {
203     emit displayMsg("", Message(QString("Unknown: ") + cmd + " " + params.join(" "), prefix, Message::Error));
204     //qDebug() << prefix <<":"<<cmd<<params;
205   }
206 }
207
208 void Server::handleUserMsg(QString bufname, QString usrMsg) {
209   try {
210     Buffer *buffer = 0;
211     if(!bufname.isEmpty()) {
212       Q_ASSERT(buffers.contains(bufname));
213       buffer = buffers[bufname];
214     }
215     QString cmd = usrMsg.section(' ', 0, 0).remove(0, 1).toUpper();
216     QString msg = usrMsg.section(' ', 1).trimmed();
217     QString hname = cmd.toLower();
218     hname[0] = hname[0].toUpper();
219     hname = "handleUser" + hname;
220     if(!QMetaObject::invokeMethod(this, hname.toAscii(), Q_ARG(QString, msg), Q_ARG(Buffer*, buffer))) {
221         // Ok. Default handler it is.
222       defaultUserHandler(cmd, msg, buffer);
223     }
224   } catch(Exception e) {
225     emit displayMsg("", Message(e.msg(), "", Message::Error));
226   }
227 }
228
229 void Server::defaultUserHandler(QString cmd, QString msg, Buffer *buf) {
230   emit displayMsg("", Message(QString("Error: %1 %2").arg(cmd).arg(msg), "", Message::Error));
231
232 }
233
234 /**********************************************************************************/
235
236 /*
237 void Server::handleUser(QString msg, Buffer *buf) {
238
239
240 }
241 */
242
243 void Server::handleUserJoin(QString msg, Buffer *buf) {
244   putCmd("JOIN", QStringList(msg));
245
246 }
247
248 void Server::handleUserQuote(QString msg, Buffer *buf) {
249   putRawLine(msg);
250 }
251
252 void Server::handleUserSay(QString msg, Buffer *buf) {
253   if(!buf) return;  // server buffer
254   QStringList params;
255   params << buf->name() << msg;
256   putCmd("PRIVMSG", params);
257   emit displayMsg(params[0], Message(msg, currentNick, Message::Msg, Message::Self));
258 }
259
260 /**********************************************************************************/
261
262 void Server::handleServerJoin(QString prefix, QStringList params) {
263   Q_ASSERT(params.count() == 1);
264   QString nick = nickFromMask(prefix);
265   if(nick == currentNick) {
266     Q_ASSERT(!buffers.contains(params[0]));  // cannot join a buffer twice!
267     Buffer *buf = new Buffer(params[0]);
268     buffers[params[0]] = buf;
269   } else {
270     VarMap n;
271     if(nicks.contains(nick)) {
272       n = nicks[nick].toMap();
273       VarMap chans = n["Channels"].toMap();
274       // Q_ASSERT(!chans.keys().contains(params[0])); TODO uncomment
275       chans[params[0]] = VarMap();
276       n["Channels"] = chans;
277       nicks[nick] = n;
278       emit nickUpdated(network, nick, n);
279     } else {
280       VarMap chans;
281       chans[params[0]] = VarMap();
282       n["Channels"] = chans;
283       n["Nick"] = nick;
284       n["User"] = userFromMask(prefix);
285       n["Host"] = hostFromMask(prefix);
286       nicks[nick] = n;
287       emit nickAdded(network, nick, n);
288     }
289     QString user = n["User"].toString(); QString host = n["Host"].toString();
290     if(user.isEmpty() || host.isEmpty()) emit displayMsg(params[0], Message(tr("%1 has joined %2").arg(nick).arg(params[0]), "", Message::Join));
291     else emit displayMsg(params[0], Message(tr("%1 (%2@%3) has joined %4").arg(nick).arg(user).arg(host).arg(params[0]), "", Message::Join));
292   }
293 }
294
295
296
297 void Server::handleServerNotice(QString prefix, QStringList params) {
298   Message msg(params[1], prefix, Message::Notice);
299   if(prefix == currentServer) emit displayMsg("", Message(params[1], prefix, Message::Server));
300   else emit displayMsg("", Message(params[1], prefix, Message::Notice));
301 }
302
303 void Server::handleServerPing(QString prefix, QStringList params) {
304   putCmd("PONG", params);
305 }
306
307 void Server::handleServerPrivmsg(QString prefix, QStringList params) {
308   emit displayMsg(params[0], Message(params[1], nickFromMask(prefix), Message::Msg));
309
310 }
311
312 /* RPL_WELCOME */
313 void Server::handleServer001(QString prefix, QStringList params) {
314   currentServer = prefix;
315   currentNick = params[0];
316   emit ownNickSet(network, currentNick);
317   emit displayMsg("", Message(params[1], prefix, Message::Server));
318 }
319
320 /* RPL_NOTOPIC */
321 void Server::handleServer331(QString prefix, QStringList params) {
322   emit topicSet(network, params[0], "");
323 }
324
325 /* RPL_TOPIC */
326 void Server::handleServer332(QString prefix, QStringList params) {
327   emit topicSet(network, params[0], params[1]);
328   emit displayMsg(params[0], Message(tr("Topic for %1 is \"%2\"").arg(params[0]).arg(params[1]), "", Message::Server));
329 }
330
331 /* Topic set by... */
332 void Server::handleServer333(QString prefix, QStringList params) {
333   emit displayMsg(params[0], Message(tr("Topic set by %1 on %2").arg(params[1]).arg(QDateTime::fromTime_t(params[2].toUInt()).toString()), "", Message::Server));
334 }
335
336 /* RPL_NAMREPLY */
337 void Server::handleServer353(QString prefix, QStringList params) {
338   params.removeFirst(); // = or *
339   QString buf = params.takeFirst();
340   foreach(QString nick, params[0].split(' ')) {
341     // TODO: parse more prefix characters! use 005?
342     QString mode = "";
343     if(nick.startsWith('@')) { mode = "o"; nick.remove(0,1); }
344     else if(nick.startsWith('+')) { mode = "v"; nick.remove(0,1); }
345     VarMap c; c["Mode"] = mode;
346     if(nicks.contains(nick)) {
347       VarMap n = nicks[nick].toMap();
348       VarMap chans = n["Channels"].toMap();
349       chans[buf] = c;
350       n["Channels"] = chans;
351       nicks[nick] = n;
352       emit nickUpdated(network, nick, n);
353     } else {
354       VarMap n; VarMap c; VarMap chans;
355       c["Mode"] = mode;
356       chans[buf] = c;
357       n["Channels"] = chans;
358       n["Nick"] = nick;
359       nicks[nick] = n;
360       emit nickAdded(network, nick, n);
361     }
362   }
363 }
364 /***********************************************************************************/
365
366 /* Exception classes for message handling */
367 Server::ParseError::ParseError(QString cmd, QString prefix, QStringList params) {
368   _msg = QString("Command Parse Error: ") + cmd + params.join(" ");
369
370 }
371
372 Server::UnknownCmdError::UnknownCmdError(QString cmd, QString prefix, QStringList params) {
373   _msg = QString("Unknown Command: ") + cmd;
374
375 }