Event backend porting
[quassel.git] / src / core / coresessioneventprocessor.cpp
1 /***************************************************************************
2  *   Copyright (C) 2005-2010 by the Quassel Project                        *
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) version 3.                                           *
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 "coresessioneventprocessor.h"
22
23 #include "coreirclisthelper.h"
24 #include "corenetwork.h"
25 #include "coresession.h"
26 #include "ircevent.h"
27 #include "ircuser.h"
28 #include "messageevent.h"
29
30 CoreSessionEventProcessor::CoreSessionEventProcessor(CoreSession *session)
31   : QObject(session),
32   _coreSession(session)
33 {
34
35 }
36
37 bool CoreSessionEventProcessor::checkParamCount(IrcEvent *e, int minParams) {
38   if(e->params().count() < minParams) {
39     if(e->type() == EventManager::IrcEventNumeric) {
40       qWarning() << "Command " << static_cast<IrcEventNumeric *>(e)->number() << " requires " << minParams << "params, got: " << e->params();
41     } else {
42       QString name = coreSession()->eventManager()->enumName(e->type());
43       qWarning() << qPrintable(name) << "requires" << minParams << "params, got:" << e->params();
44     }
45     e->stop();
46     return false;
47   }
48   return true;
49 }
50
51 void CoreSessionEventProcessor::tryNextNick(NetworkEvent *e, const QString &errnick, bool erroneus) {
52   QStringList desiredNicks = coreSession()->identity(e->network()->identity())->nicks();
53   int nextNickIdx = desiredNicks.indexOf(errnick) + 1;
54   QString nextNick;
55   if(nextNickIdx > 0 && desiredNicks.size() > nextNickIdx) {
56     nextNick = desiredNicks[nextNickIdx];
57   } else {
58     if(erroneus) {
59       // FIXME Make this an ErrorEvent or something like that, so it's translated in the client
60       MessageEvent *msgEvent = new MessageEvent(Message::Error, e->network(),
61                                                 tr("No free and valid nicks in nicklist found. use: /nick <othernick> to continue"),
62                                                 QString(), QString(), Message::None, e->timestamp());
63       coreSession()->eventManager()->sendEvent(msgEvent);
64       return;
65     } else {
66       nextNick = errnick + "_";
67     }
68   }
69   // FIXME Use a proper output event for this
70   coreNetwork(e)->putRawLine("NICK " + coreNetwork(e)->encodeServerString(nextNick));
71 }
72
73 void CoreSessionEventProcessor::processIrcEventNumeric(IrcEventNumeric *e) {
74   switch(e->number()) {
75
76   // CAP stuff
77   case 903: case 904: case 905: case 906: case 907:
78     qobject_cast<CoreNetwork *>(e->network())->putRawLine("CAP END");
79     break;
80
81   default:
82     break;
83   }
84 }
85
86 void CoreSessionEventProcessor::processIrcEventAuthenticate(IrcEvent *e) {
87   if(!checkParamCount(e, 1))
88     return;
89
90   if(e->params().at(0) != "+") {
91     qWarning() << "Invalid AUTHENTICATE" << e;
92     return;
93   }
94
95   CoreNetwork *net = coreNetwork(e);
96
97   QString construct = net->saslAccount();
98   construct.append(QChar(QChar::Null));
99   construct.append(net->saslAccount());
100   construct.append(QChar(QChar::Null));
101   construct.append(net->saslPassword());
102   QByteArray saslData = QByteArray(construct.toAscii().toBase64());
103   saslData.prepend("AUTHENTICATE ");
104   net->putRawLine(saslData);
105 }
106
107 void CoreSessionEventProcessor::processIrcEventCap(IrcEvent *e) {
108   // for SASL, there will only be a single param of 'sasl', however you can check here for
109   // additional CAP messages (ls, multi-prefix, et cetera).
110
111   if(e->params().count() == 3) {
112     if(e->params().at(2) == "sasl") {
113       // FIXME use event
114       coreNetwork(e)->putRawLine(coreNetwork(e)->serverEncode("AUTHENTICATE PLAIN")); // Only working with PLAIN atm, blowfish later
115     }
116   }
117 }
118
119 void CoreSessionEventProcessor::processIrcEventInvite(IrcEvent *e) {
120   if(checkParamCount(e, 2)) {
121     e->network()->updateNickFromMask(e->prefix());
122   }
123 }
124
125 void CoreSessionEventProcessor::processIrcEventKick(IrcEvent *e) {
126   if(checkParamCount(e, 2)) {
127     e->network()->updateNickFromMask(e->prefix());
128     IrcUser *victim = e->network()->ircUser(e->params().at(1));
129     if(victim) {
130       victim->partChannel(e->params().at(0));
131       //if(e->network()->isMe(victim)) e->network()->setKickedFromChannel(channel);
132     }
133   }
134 }
135
136 void CoreSessionEventProcessor::processIrcEventNick(IrcEvent *e) {
137   if(checkParamCount(e, 1)) {
138     IrcUser *ircuser = e->network()->updateNickFromMask(e->prefix());
139     if(!ircuser) {
140       qWarning() << Q_FUNC_INFO << "Unknown IrcUser!";
141       return;
142     }
143     QString newnick = e->params().at(0);
144     QString oldnick = ircuser->nick();
145
146     // the order is cruicial
147     // otherwise the client would rename the buffer, see that the assigned ircuser doesn't match anymore
148     // and remove the ircuser from the querybuffer leading to a wrong on/offline state
149     ircuser->setNick(newnick);
150     coreSession()->renameBuffer(e->networkId(), newnick, oldnick);
151   }
152 }
153
154 void CoreSessionEventProcessor::processIrcEventPart(IrcEvent *e) {
155   if(checkParamCount(e, 1)) {
156     IrcUser *ircuser = e->network()->updateNickFromMask(e->prefix());
157     if(!ircuser) {
158       qWarning() << Q_FUNC_INFO<< "Unknown IrcUser!";
159       return;
160     }
161     QString channel = e->params().at(0);
162     ircuser->partChannel(channel);
163     if(e->network()->isMe(ircuser))
164       qobject_cast<CoreNetwork *>(e->network())->setChannelParted(channel);
165   }
166 }
167
168 void CoreSessionEventProcessor::processIrcEventPong(IrcEvent *e) {
169   // the server is supposed to send back what we passed as param. and we send a timestamp
170   // but using quote and whatnought one can send arbitrary pings, so we have to do some sanity checks
171   if(checkParamCount(e, 2)) {
172     QString timestamp = e->params().at(1);
173     QTime sendTime = QTime::fromString(timestamp, "hh:mm:ss.zzz");
174     if(sendTime.isValid())
175       e->network()->setLatency(sendTime.msecsTo(QTime::currentTime()) / 2);
176   }
177 }
178
179 void CoreSessionEventProcessor::processIrcEventTopic(IrcEvent *e) {
180   if(checkParamCount(e, 2)) {
181     e->network()->updateNickFromMask(e->prefix());
182     IrcChannel *channel = e->network()->ircChannel(e->params().at(0));
183     if(channel)
184       channel->setTopic(e->params().at(1));
185   }
186 }
187
188 /* RPL_WELCOME */
189 void CoreSessionEventProcessor::processIrcEvent001(IrcEvent *e) {
190   if(!checkParamCount(e, 1))
191     return;
192
193   QString myhostmask = e->params().at(0).section(' ', -1, -1);
194   e->network()->setCurrentServer(e->prefix());
195   e->network()->setMyNick(nickFromMask(myhostmask));
196 }
197
198 /* RPL_ISUPPORT */
199 // TODO Complete 005 handling, also use sensible defaults for non-sent stuff
200 void CoreSessionEventProcessor::processIrcEvent005(IrcEvent *e) {
201   if(!checkParamCount(e, 1))
202     return;
203
204   QString key, value;
205   for(int i = 0; i < e->params().count() - 1; i++) {
206     QString key = e->params()[i].section("=", 0, 0);
207     QString value = e->params()[i].section("=", 1);
208     e->network()->addSupport(key, value);
209   }
210
211   /* determine our prefixes here to get an accurate result */
212   e->network()->determinePrefixes();
213 }
214
215 /* RPL_UMODEIS - "<user_modes> [<user_mode_params>]" */
216 void CoreSessionEventProcessor::processIrcEvent221(IrcEvent *) {
217   // TODO: save information in network object
218 }
219
220 /* RPL_STATSCONN - "Highest connection cout: 8000 (7999 clients)" */
221 void CoreSessionEventProcessor::processIrcEvent250(IrcEvent *) {
222   // TODO: save information in network object
223 }
224
225 /* RPL_LOCALUSERS - "Current local user: 5024  Max: 7999 */
226 void CoreSessionEventProcessor::processIrcEvent265(IrcEvent *) {
227   // TODO: save information in network object
228 }
229
230 /* RPL_GLOBALUSERS - "Current global users: 46093  Max: 47650" */
231 void CoreSessionEventProcessor::processIrcEvent266(IrcEvent *) {
232   // TODO: save information in network object
233 }
234
235 /*
236 WHOIS-Message:
237    Replies 311 - 313, 317 - 319 are all replies generated in response to a WHOIS message.
238   and 301 (RPL_AWAY)
239               "<nick> :<away message>"
240 WHO-Message:
241    Replies 352 and 315 paired are used to answer a WHO message.
242
243 WHOWAS-Message:
244    Replies 314 and 369 are responses to a WHOWAS message.
245
246 */
247
248 /* RPL_AWAY - "<nick> :<away message>" */
249 void CoreSessionEventProcessor::processIrcEvent301(IrcEvent *e) {
250   if(!checkParamCount(e, 2))
251     return;
252
253   IrcUser *ircuser = e->network()->ircUser(e->params().at(0));
254   if(ircuser) {
255     ircuser->setAway(true);
256     ircuser->setAwayMessage(e->params().at(1));
257     //ircuser->setLastAwayMessage(now);
258   }
259 }
260
261 /* RPL_UNAWAY - ":You are no longer marked as being away" */
262 void CoreSessionEventProcessor::processIrcEvent305(IrcEvent *e) {
263   IrcUser *me = e->network()->me();
264   if(me)
265     me->setAway(false);
266
267   if(e->network()->autoAwayActive()) {
268     e->network()->setAutoAwayActive(false);
269     e->setFlag(EventManager::Silent);
270   }
271 }
272
273 /* RPL_NOWAWAY - ":You have been marked as being away" */
274 void CoreSessionEventProcessor::processIrcEvent306(IrcEvent *e) {
275   IrcUser *me = e->network()->me();
276   if(me)
277     me->setAway(true);
278 }
279
280 /* RPL_WHOISSERVICE - "<user> is registered nick" */
281 void CoreSessionEventProcessor::processIrcEvent307(IrcEvent *e) {
282   if(!checkParamCount(e, 1))
283     return;
284
285   IrcUser *ircuser = e->network()->ircUser(e->params().at(0));
286   if(ircuser)
287     ircuser->setWhoisServiceReply(e->params().join(" "));
288 }
289
290 /* RPL_SUSERHOST - "<user> is available for help." */
291 void CoreSessionEventProcessor::processIrcEvent310(IrcEvent *e) {
292   if(!checkParamCount(e, 1))
293     return;
294
295   IrcUser *ircuser = e->network()->ircUser(e->params().at(0));
296   if(ircuser)
297     ircuser->setSuserHost(e->params().join(" "));
298 }
299
300 /*  RPL_WHOISUSER - "<nick> <user> <host> * :<real name>" */
301 void CoreSessionEventProcessor::processIrcEvent311(IrcEvent *e) {
302   if(!checkParamCount(e, 3))
303     return;
304
305   IrcUser *ircuser = e->network()->ircUser(e->params().at(0));
306   if(ircuser) {
307     ircuser->setUser(e->params().at(1));
308     ircuser->setHost(e->params().at(2));
309     ircuser->setRealName(e->params().last());
310   }
311 }
312
313 /*  RPL_WHOISSERVER -  "<nick> <server> :<server info>" */
314 void CoreSessionEventProcessor::processIrcEvent312(IrcEvent *e) {
315   if(!checkParamCount(e, 2))
316     return;
317
318   IrcUser *ircuser = e->network()->ircUser(e->params().at(0));
319   if(ircuser)
320     ircuser->setServer(e->params().at(1));
321 }
322
323 /*  RPL_WHOISOPERATOR - "<nick> :is an IRC operator" */
324 void CoreSessionEventProcessor::processIrcEvent313(IrcEvent *e) {
325   if(!checkParamCount(e, 1))
326     return;
327
328   IrcUser *ircuser = e->network()->ircUser(e->params().at(0));
329   if(ircuser)
330     ircuser->setIrcOperator(e->params().last());
331 }
332
333 /*  RPL_ENDOFWHO: "<name> :End of WHO list" */
334 void CoreSessionEventProcessor::processIrcEvent315(IrcEvent *e) {
335   if(!checkParamCount(e, 1))
336     return;
337
338   if(coreNetwork(e)->setAutoWhoDone(e->params()[0]))
339     e->setFlag(EventManager::Silent);
340 }
341
342 /*  RPL_WHOISIDLE - "<nick> <integer> :seconds idle"
343    (real life: "<nick> <integer> <integer> :seconds idle, signon time) */
344 void CoreSessionEventProcessor::processIrcEvent317(IrcEvent *e) {
345   if(!checkParamCount(e, 2))
346     return;
347
348   QDateTime loginTime;
349
350   int idleSecs = e->params()[1].toInt();
351   if(e->params().count() > 3) { // if we have more then 3 params we have the above mentioned "real life" situation
352     int logintime = e->params()[2].toInt();
353     loginTime = QDateTime::fromTime_t(logintime);
354   }
355
356   IrcUser *ircuser = e->network()->ircUser(e->params()[0]);
357   if(ircuser) {
358     ircuser->setIdleTime(e->timestamp().addSecs(-idleSecs));
359     if(loginTime.isValid())
360       ircuser->setLoginTime(loginTime);
361   }
362 }
363
364 /* RPL_LIST -  "<channel> <# visible> :<topic>" */
365 void CoreSessionEventProcessor::processIrcEvent322(IrcEvent *e) {
366   if(!checkParamCount(e, 1))
367     return;
368
369   QString channelName;
370   quint32 userCount = 0;
371   QString topic;
372
373   switch(e->params().count()) {
374   case 3:
375     topic = e->params()[2];
376   case 2:
377     userCount = e->params()[1].toUInt();
378   case 1:
379     channelName = e->params()[0];
380   default:
381     break;
382   }
383   if(coreSession()->ircListHelper()->addChannel(e->networkId(), channelName, userCount, topic))
384     e->stop(); // consumed by IrcListHelper, so don't further process/show this event
385 }
386
387 /* RPL_LISTEND ":End of LIST" */
388 void CoreSessionEventProcessor::processIrcEvent323(IrcEvent *e) {
389   if(!checkParamCount(e, 1))
390     return;
391
392   if(coreSession()->ircListHelper()->endOfChannelList(e->networkId()))
393     e->stop(); // consumed by IrcListHelper, so don't further process/show this event
394 }
395
396 /* RPL_NOTOPIC */
397 void CoreSessionEventProcessor::processIrcEvent331(IrcEvent *e) {
398   if(!checkParamCount(e, 1))
399     return;
400
401   IrcChannel *chan = e->network()->ircChannel(e->params()[0]);
402   if(chan)
403     chan->setTopic(QString());
404 }
405
406 /* RPL_TOPIC */
407 void CoreSessionEventProcessor::processIrcEvent332(IrcEvent *e) {
408   if(!checkParamCount(e, 2))
409     return;
410
411   IrcChannel *chan = e->network()->ircChannel(e->params()[0]);
412   if(chan)
413     chan->setTopic(e->params()[1]);
414 }
415
416 /*  RPL_WHOREPLY: "<channel> <user> <host> <server> <nick>
417               ( "H" / "G" > ["*"] [ ( "@" / "+" ) ] :<hopcount> <real name>" */
418 void CoreSessionEventProcessor::processIrcEvent352(IrcEvent *e) {
419   if(!checkParamCount(e, 6))
420     return;
421
422   QString channel = e->params()[0];
423   IrcUser *ircuser = e->network()->ircUser(e->params()[4]);
424   if(ircuser) {
425     ircuser->setUser(e->params()[1]);
426     ircuser->setHost(e->params()[2]);
427
428     bool away = e->params()[5].startsWith("G");
429     ircuser->setAway(away);
430     ircuser->setServer(e->params()[3]);
431     ircuser->setRealName(e->params().last().section(" ", 1));
432   }
433
434   if(coreNetwork(e)->isAutoWhoInProgress(channel))
435     e->setFlag(EventManager::Silent);
436 }
437
438 /* RPL_NAMREPLY */
439 void CoreSessionEventProcessor::processIrcEvent353(IrcEvent *e) {
440   if(!checkParamCount(e, 3))
441     return;
442
443   // param[0] is either "=", "*" or "@" indicating a public, private or secret channel
444   // we don't use this information at the time beeing
445   QString channelname = e->params()[1];
446
447   IrcChannel *channel = e->network()->ircChannel(channelname);
448   if(!channel) {
449     qWarning() << Q_FUNC_INFO << "Received unknown target channel:" << channelname;
450     return;
451   }
452
453   QStringList nicks;
454   QStringList modes;
455
456   foreach(QString nick, e->params()[2].split(' ')) {
457     QString mode;
458
459     if(e->network()->prefixes().contains(nick[0])) {
460       mode = e->network()->prefixToMode(nick[0]);
461       nick = nick.mid(1);
462     }
463
464     nicks << nick;
465     modes << mode;
466   }
467
468   channel->joinIrcUsers(nicks, modes);
469 }
470
471 /* ERR_ERRONEUSNICKNAME */
472 void CoreSessionEventProcessor::processIrcEvent432(IrcEventNumeric *e) {
473   QString errnick;
474   if(e->params().count() < 2) {
475     // handle unreal-ircd bug, where unreal ircd doesnt supply a TARGET in ERR_ERRONEUSNICKNAME during registration phase:
476     // nick @@@
477     // :irc.scortum.moep.net 432  @@@ :Erroneous Nickname: Illegal characters
478     // correct server reply:
479     // :irc.scortum.moep.net 432 * @@@ :Erroneous Nickname: Illegal characters
480     e->params().prepend(e->target());
481     e->setTarget("*");
482   }
483   errnick = e->params()[0];
484
485   tryNextNick(e, errnick, true /* erroneus */);
486 }
487
488 /* ERR_NICKNAMEINUSE */
489 void CoreSessionEventProcessor::processIrcEvent433(IrcEventNumeric *e) {
490   if(!checkParamCount(e, 1))
491     return;
492
493   QString errnick = e->params().first();
494
495   // if there is a problem while connecting to the server -> we handle it
496   // but only if our connection has not been finished yet...
497   if(!e->network()->currentServer().isEmpty())
498     return;
499
500   tryNextNick(e, errnick);
501 }
502
503 /* ERR_UNAVAILRESOURCE */
504 void CoreSessionEventProcessor::processIrcEvent437(IrcEventNumeric *e) {
505   if(!checkParamCount(e, 1))
506     return;
507
508   QString errnick = e->params().first();
509
510   // if there is a problem while connecting to the server -> we handle it
511   // but only if our connection has not been finished yet...
512   if(!e->network()->currentServer().isEmpty())
513     return;
514
515   if(!e->network()->isChannelName(errnick))
516     tryNextNick(e, errnick);
517 }
518
519 /* template
520 void CoreSessionEventProcessor::processIrcEvent(IrcEvent *e) {
521   if(!checkParamCount(e, 1))
522     return;
523
524 }
525 */
526