Controller.cpp 33 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097
  1. /*
  2. * Controller.cpp
  3. * nirc
  4. *
  5. * Created by Niklas Bölter on 03.02.10.
  6. * Copyright 2010 Frubar Corporation.
  7. *
  8. *
  9. * This program is free software; you can redistribute it and/or modify
  10. * it under the terms of the GNU General Public License as published by
  11. * the Free Software Foundation; either version 2 of the License, or
  12. * (at your option) any later version.
  13. *
  14. *
  15. *
  16. */
  17. #include "Controller.h"
  18. Controller::Controller(MainWindow *mw)
  19. {
  20. this->mw = mw;
  21. userlists = new UserLists();
  22. connect(this->mw, SIGNAL( sendLine(QString) ), this, SLOT( readInput(QString) ));
  23. // handle user input
  24. connect(this->mw, SIGNAL( tabChanged(QString) ), this, SLOT( tabChanged(QString) ));
  25. // user selected a different tab in the tab list (Tree View) - change the rest of the window
  26. // These are signals that are emitted when the corresponding Command is received from the IRC Server,
  27. // Parameters are int cid (The Connection ID), QStringList params (The Parameters of the Command)
  28. // and Prefix prefix, which contains info about the sender of the command (nick/user/hostname or servername respectively)
  29. connect(this, SIGNAL(gotJoin(int, QStringList, Prefix)), this, SLOT(handleJoin(int, QStringList, Prefix)));
  30. connect(this, SIGNAL(gotPart(int, QStringList, Prefix)), this, SLOT(handlePart(int, QStringList, Prefix)));
  31. connect(this, SIGNAL(gotQuit(int, QStringList, Prefix)), this, SLOT(handleQuit(int, QStringList, Prefix)));
  32. connect(this, SIGNAL(gotKick(int, QStringList, Prefix)), this, SLOT(handleKick(int, QStringList, Prefix)));
  33. connect(this, SIGNAL(gotPrivmsg(int, QStringList, Prefix)), this, SLOT(handlePrivmsg(int, QStringList, Prefix)));
  34. connect(this, SIGNAL(gotPing(int, QStringList, Prefix)), this, SLOT(handlePing(int, QStringList, Prefix)));
  35. connect(this, SIGNAL(gotNick(int, QStringList, Prefix)), this, SLOT(handleNick(int, QStringList, Prefix)));
  36. connect(this, SIGNAL(gotNotice(int, QStringList, Prefix)), this, SLOT(handleNotice(int, QStringList, Prefix)));
  37. connect(this, SIGNAL(gotMode(int, QStringList, Prefix)), this, SLOT(handleMode(int, QStringList, Prefix)));
  38. connect(this, SIGNAL(got001(int, QStringList, Prefix)), this, SLOT(handle001(int, QStringList, Prefix)));
  39. connect(this, SIGNAL(got002(int, QStringList, Prefix)), this, SLOT(handle002(int, QStringList, Prefix)));
  40. connect(this, SIGNAL(got003(int, QStringList, Prefix)), this, SLOT(handle003(int, QStringList, Prefix)));
  41. connect(this, SIGNAL(got004(int, QStringList, Prefix)), this, SLOT(handle004(int, QStringList, Prefix)));
  42. connect(this, SIGNAL(got005(int, QStringList, Prefix)), this, SLOT(handle005(int, QStringList, Prefix)));
  43. connect(this, SIGNAL(got332(int, QStringList, Prefix)), this, SLOT(handle332(int, QStringList, Prefix)));
  44. connect(this, SIGNAL(got353(int, QStringList, Prefix)), this, SLOT(handle353(int, QStringList, Prefix)));
  45. connect(this, SIGNAL(got366(int, QStringList, Prefix)), this, SLOT(handle366(int, QStringList, Prefix)));
  46. // Allow the user to provide his own CA Certificates for SSL Verification
  47. QSslSocket::addDefaultCaCertificates(QString("%1/.nirc/ca.pem").arg(QDir::homePath()),QSsl::Pem);
  48. QSslSocket::addDefaultCaCertificates(QString("%1/.nirc/ca.der").arg(QDir::homePath()),QSsl::Der);
  49. // Parse the Connection Information in Connectionsettings objects (which is required by the Connection class)
  50. int size = settings.beginReadArray("connections");
  51. for(int i = 0; i < size; i++)
  52. {
  53. settings.setArrayIndex(i);
  54. ConnectionSettings connectionsetting;
  55. connectionsetting.id = i;
  56. connectionsetting.host = settings.value("host").toString();
  57. connectionsetting.port = settings.value("port").toUInt();
  58. connectionsetting.name = settings.value("name").toString();
  59. connectionsetting.isSSL = settings.value("isSSL").toBool();
  60. connectionsetting.ignoreSSLErrors = settings.value("ignoreSSLErrors").toBool();
  61. connectionsettings.append(connectionsetting);
  62. }
  63. settings.endArray();
  64. // Each class has its own namespace in the settings, we use controller for our settings
  65. settings.beginGroup("controller");
  66. }
  67. void Controller::replaceHTML(QString & line)
  68. {
  69. // Warning: This uses references! Make sure you use it AFTER you sent the data to the server
  70. line.replace("&","&amp;");
  71. line.replace("<","&lt;");
  72. line.replace(">","&gt;");
  73. line.replace("'","&#039;");
  74. line.replace("\"","&quot;");
  75. }
  76. void Controller::setup()
  77. {
  78. // Initialize the Connections
  79. openConnections();
  80. // Display main window
  81. mw->show();
  82. }
  83. void Controller::openConnections()
  84. {
  85. int cid;
  86. for(cid = 0; cid < connectionsettings.size(); cid++)
  87. {
  88. // cid is the connection id, which is always needed do distinguish between networks
  89. // This takes care of the prefixes for nicks in the NAMES reply numeric, so we can find out
  90. // who has which priviledge in the channel
  91. // (Usually overriden by RPL_ISUPPORT, but we use this as default as defined in RFC 1459)
  92. parseISupportPrefix(cid, "(ov)@+");
  93. // RFC 2811 channel modes - hopefully overriden by RPL_ISUPPORT
  94. parseISupportChanmodes(cid, "beI,k,l,aimnpstr");
  95. // This is the list of joined channels for this network - it's empty for now
  96. channels.append(QSet<QString>());
  97. // We are not yet registered with the Network, wait until RPL_WELCOME
  98. registered.insert(cid, false);
  99. // Show a nice and dandy status info in the titlebar for the connection
  100. titlebars.insert(QString("%1").arg(cid), "Connecting ...");
  101. updateTitleBar();
  102. Connection *connection = new Connection(cid);
  103. // We received data form the server!
  104. connect(connection, SIGNAL( dataReady(int) ), this, SLOT( dataReady(int) ));
  105. connect(connection, SIGNAL( connected(int) ), this, SLOT( connected(int) ));
  106. connect(connection, SIGNAL( disconnected(int) ), this, SLOT( disconnected(int) ));
  107. // We got a status message from the socket (errors etc) - just show it to the user
  108. // (We only care about connect/disconnect and data events)
  109. connect(connection, SIGNAL( message(int, QString) ), this, SLOT( socketmessage(int, QString) ));
  110. connections.insert(cid, connection);
  111. // open the connection with specified settings
  112. connection->open(connectionsettings.at(cid));
  113. }
  114. }
  115. void Controller::dataReady(int cid)
  116. {
  117. Connection *c = connections.at(cid);
  118. c->splitLines(); // Split lines (we do this one time now so that hasLines() and nextLine() do not have to do it each time)
  119. while (c->hasLines())
  120. {
  121. parse(cid, c->nextLine());
  122. }
  123. }
  124. void Controller::sendLine(int cid, QString line) // Sends stuff to the server
  125. {
  126. // First send it to the server
  127. connections.at(cid)->sendLine(line);
  128. // Then sanitize the HTML
  129. replaceHTML(line);
  130. Q_ASSERT(tabs.contains(QString("%1").arg(cid)));
  131. // And append it to the connection tab
  132. addLine(getTab(cid), QString("<span style=\"color:green;white-space:pre;\">%1</grey>").arg(line));
  133. }
  134. void Controller::addLine(QTextCursor *cursor, QString line) // Sends stuff to the tab
  135. {
  136. // cursor is a textcursor pointing to the textdocument which holds the tabs content
  137. bool atBottom = mw->contentAtBottom();
  138. if(!cursor->atStart()) cursor->insertBlock(); // No newline in front of first line
  139. cursor->insertHtml(line);
  140. if(atBottom)
  141. {
  142. mw->contentScrollDown();
  143. }
  144. }
  145. void Controller::socketmessage(int cid, QString message)
  146. {
  147. QTextCursor *cursor = getTab(cid);
  148. if(cursor == 0) // We don't have a connection tab yet - create it
  149. {
  150. QString name = connectionsettings.at(cid).name;
  151. QString identifier = QString("%1").arg(cid);
  152. // Create the textdocument and textcursor
  153. addTab(identifier, settings.value("maxConsoleHistory").toInt());
  154. // Tell the MainWindow that we have a new tab which it can request via tabChanged()
  155. mw->addTab(name, identifier, "", true);
  156. Q_ASSERT(tabs.contains(QString("%1").arg(cid)));
  157. cursor = getTab(cid);
  158. }
  159. // Finally add the message to the tabs content
  160. addLine(cursor, message);
  161. }
  162. void Controller::connected(int cid)
  163. {
  164. titlebars.insert(QString("%1").arg(cid),
  165. QString("Connected to %1:%2").arg(connectionsettings.at(cid).host).arg(connectionsettings.at(cid).port));
  166. updateTitleBar();
  167. socketmessage(cid, QString("Connected to %1").arg(connectionsettings.at(cid).name));
  168. // TODO
  169. mynicks.insert(cid, settings.value("nickname").toString());
  170. sendLine(cid, QString("NICK %1").arg(mynicks.value(cid)));
  171. sendLine(cid, QString("USER %1 8 * :%2").arg(settings.value("username").toString()).arg(settings.value("realname").toString()));
  172. }
  173. void Controller::disconnected(int cid)
  174. {
  175. titlebars.insert(QString("%1").arg(cid),
  176. QString("Disconnected from %1:%2").arg(connectionsettings.at(cid).host).arg(connectionsettings.at(cid).port));
  177. updateTitleBar();
  178. socketmessage(cid, QString("Disconnected from %1").arg(connectionsettings.at(cid).name));
  179. }
  180. void Controller::parse(int cid, QString line)
  181. {
  182. QStringList params;
  183. QString prefstr;
  184. QString command;
  185. Prefix prefix;
  186. int lastParameterStart = line.indexOf(" :");
  187. if(lastParameterStart == -1)
  188. {
  189. params = line.split(QRegExp(" +"));
  190. }
  191. else
  192. {
  193. params = line.left(lastParameterStart).split(QRegExp(" +"));
  194. params.append(line.right(line.size() - (2 + lastParameterStart)));
  195. }
  196. if(params.isEmpty()) return; // Empty line, ignore it
  197. replaceHTML(line);
  198. addLine(getTab(cid), QString("<span style=\"white-space:pre;\">%1</grey>").arg(line));
  199. if(params.at(0)[0] == ':')
  200. {
  201. prefstr = params.takeFirst().mid(1);
  202. }
  203. else
  204. {
  205. prefstr = "";
  206. }
  207. if(params.isEmpty())// Prefix without command?!
  208. {
  209. qWarning() << "Received Line with prefix but without command (?) - Ignoring";
  210. return;
  211. }
  212. command = params.takeFirst();
  213. if(isServer(prefstr))
  214. {
  215. prefix.isServer = true;
  216. prefix.nickname = prefstr;
  217. }
  218. else
  219. {
  220. int shriekpos = prefstr.indexOf("!");
  221. int strudelpos;
  222. if(shriekpos == -1)
  223. {
  224. strudelpos = prefstr.indexOf("@");
  225. if(strudelpos == -1)
  226. {
  227. prefix.nickname = prefstr;
  228. }
  229. else
  230. {
  231. prefix.nickname = prefstr.left(strudelpos);
  232. prefix.hostname = prefstr.mid(strudelpos + 1);
  233. }
  234. }
  235. else
  236. {
  237. prefix.nickname = prefstr.left(shriekpos);
  238. strudelpos = prefstr.indexOf("@", shriekpos);
  239. shriekpos++;
  240. if(strudelpos == -1)
  241. {
  242. prefix.username = prefstr.mid(shriekpos);
  243. }
  244. else
  245. {
  246. prefix.username = prefstr.mid(shriekpos, strudelpos - shriekpos);
  247. prefix.hostname = prefstr.mid(strudelpos + 1);
  248. }
  249. }
  250. }
  251. command = command.toLower();
  252. if(command.contains(QRegExp("[^a-z0-9]+"))) // I don't know enough about QMetaObject, but we better not allow arbitrary input
  253. {
  254. qWarning() << "Got a weird command from server (not alphanumerical), ignoring it. Command was:" << command;
  255. return;
  256. }
  257. command[0] = command[0].toUpper();
  258. command.prepend("got");
  259. if(!QMetaObject::invokeMethod(this, command.toUtf8().data(),
  260. Qt::DirectConnection,
  261. Q_ARG(int, cid),
  262. Q_ARG(QStringList, params),
  263. Q_ARG(Prefix, prefix)))
  264. {
  265. qDebug() << "Got unhandled" << command;
  266. }
  267. }
  268. bool Controller::isServer(QString prefix)
  269. {
  270. if(prefix.size() == 0) // Missing prefix should be interpreted as originating from connection partner
  271. {
  272. return true;
  273. }
  274. if(prefix.contains(".") && !prefix.contains("!") && !prefix.contains("@"))
  275. {
  276. return true;
  277. }
  278. return false;
  279. }
  280. void Controller::parseInputCommand(QString line)
  281. {
  282. QStringList params;
  283. QString tmp;
  284. QString command;
  285. QString target;
  286. QTextCursor *cursor;
  287. QString identifier;
  288. if(line.size() == 0) return;
  289. params = line.split(' ');
  290. command = params.takeFirst().toLower();
  291. if(command.size() == 0) return;
  292. if(command == "me" && tab.name != "")
  293. {
  294. line = line.mid(3);
  295. tmp = QString("PRIVMSG %1 :%2ACTION %3%4").arg(tab.name).arg(char(1)).arg(line).arg(char(1));
  296. sendLine(tab.cid, tmp);
  297. replaceHTML(line);
  298. Q_ASSERT(tabs.contains(tab.identifier));
  299. addLine(getTab(tab.identifier), QString("%1 <strong style=\"color:blue\">*&nbsp;%2</strong>&nbsp;<span style=\"white-space:pre\">%3</span>").arg(QDateTime::currentDateTime().toString("[hh:mm]")).arg(mynicks.value(tab.cid)).arg(line));
  300. return;
  301. }
  302. else if(command == "msg")
  303. {
  304. if(params.count() < 2)
  305. {
  306. addLine(getTab(tab.identifier), "<span style=\"color:red\">Not enough parameters for /msg</span>");
  307. return;
  308. }
  309. else
  310. {
  311. target = params.takeFirst();
  312. tmp = QString("PRIVMSG %2 :%3").arg(target).arg(params.join(" "));
  313. sendLine(tab.cid, tmp);
  314. cursor = getTab(tab.cid, target);
  315. if(cursor != 0)
  316. {
  317. QString("%1 <strong style=\"color:blue\">&lt;%2&gt;</strong>&nbsp;<span style=\"white-space:pre\">%3</span>").arg(QDateTime::currentDateTime().toString("[hh:mm]")).arg(mynicks.value(tab.cid)).arg(params.join(" "));
  318. }
  319. }
  320. }
  321. else if(command == "query")
  322. {
  323. if(params.count() < 1)
  324. {
  325. addLine(getTab(tab.identifier), "<span style=\"color:red\">Not enough parameters for /query</span>");
  326. }
  327. else
  328. {
  329. identifier = QString("%1/%2").arg(tab.cid).arg(params.at(0));
  330. addTab(identifier, settings.value("maxPrivateHistory").toInt());
  331. mw->addTab(params.at(0), identifier, QString("%1").arg(tab.cid), true);
  332. cursor = getTab(tab.cid, params.at(0));
  333. }
  334. }
  335. else
  336. {
  337. sendLine(tab.cid, line);
  338. }
  339. }
  340. void Controller::readInput(QString line)
  341. {
  342. QString tmp;
  343. if(line.size() == 0) return;
  344. if(line.startsWith("/"))
  345. {
  346. line = line.mid(1);
  347. if(!line.startsWith("/"))
  348. {
  349. parseInputCommand(line);
  350. return;
  351. }
  352. }
  353. if(tab.name == "")
  354. {
  355. Q_ASSERT(tabs.contains(QString("%1").arg(tab.cid)));
  356. sendLine(tab.cid, line);
  357. }
  358. else
  359. {
  360. sendLine(tab.cid, QString("PRIVMSG %1 :%2").arg(tab.name).arg(line));
  361. replaceHTML(line);
  362. Q_ASSERT(tabs.contains(tab.identifier));
  363. addLine(getTab(tab.identifier), QString("%1 <strong style=\"color:blue\">&lt;%2&gt;</strong>&nbsp;<span style=\"white-space:pre\">%3</span>").arg(QDateTime::currentDateTime().toString("[hh:mm]")).arg(mynicks.value(tab.cid)).arg(line));
  364. }
  365. }
  366. void Controller::addTab(QString key, quint16 maximumBlockCount)
  367. {
  368. Q_ASSERT(!tabs.contains(key));
  369. QTextDocument *tab = new QTextDocument();
  370. tab->setDefaultFont(QFont("Menlo",11));
  371. tab->setMaximumBlockCount(maximumBlockCount);
  372. QTextCursor *cursor = new QTextCursor(tab);
  373. tabs.insert(key, cursor);
  374. }
  375. void Controller::renameTab(int cid, QString oldName, QString newName)
  376. {
  377. Q_ASSERT(!channels[cid].contains(oldName)); // This should never be called for channel tabs!
  378. Q_ASSERT(!channels[cid].contains(newName));
  379. QString oldKey = QString("%1/%2").arg(cid).arg(oldName);
  380. QString newKey = QString("%1/%2").arg(cid).arg(newName);
  381. QString titlebar;
  382. QTextCursor * cursor = getTab(oldKey);
  383. if(cursor == 0)
  384. {
  385. return;
  386. }
  387. tabs.remove(oldKey);
  388. if(tabs.contains(newKey)) // Tab Name collision! We rename the other one as it is probably just a disconnected user's query
  389. {
  390. renameTab(cid, newName, QString("%1~").arg(newName));
  391. }
  392. if(titlebars.contains(oldKey)) // Also rename the Titlebar!
  393. {
  394. titlebar = titlebars.value(oldKey);
  395. titlebars.remove(oldKey);
  396. titlebars.insert(newKey, titlebar);
  397. }
  398. tabs.insert(newKey, cursor);
  399. mw->renameTab(oldKey, newKey, newName);
  400. }
  401. void Controller::delTab(QString key)
  402. {
  403. Q_ASSERT(tabs.contains(key));
  404. tabs.remove(key);
  405. }
  406. QTextCursor * Controller::getTab(int cid)
  407. {
  408. QString identifier = QString("%1").arg(cid);
  409. return getTab(identifier);
  410. }
  411. QTextCursor * Controller::getTab(int cid, QString name)
  412. {
  413. QString identifier = QString("%1/%2").arg(cid).arg(name);
  414. return getTab(identifier);
  415. }
  416. QTextCursor * Controller::getTab(QString identifier)
  417. {
  418. if(!tabs.contains(identifier))
  419. {
  420. return 0;
  421. }
  422. return tabs.value(identifier);
  423. }
  424. void Controller::tabChanged(QString identifier)
  425. {
  426. Q_ASSERT(tabs.contains(identifier));
  427. mw->setContent(getTab(identifier)->document());
  428. int pos = identifier.indexOf("/");
  429. tab.identifier = identifier;
  430. if(pos == -1)
  431. {
  432. tab.cid = identifier.toInt();
  433. tab.name = "";
  434. }
  435. else
  436. {
  437. tab.cid = identifier.left(pos).toInt();
  438. tab.name = identifier.mid(pos+1);
  439. }
  440. updateChannelList();
  441. updateTitleBar();
  442. }
  443. void Controller::updateChannelList()
  444. {
  445. QStringList channellist;
  446. if(channels[tab.cid].contains(tab.name))
  447. {
  448. mw->setChannelList(userlists->getUsers(tab.cid, tab.name));
  449. }
  450. else
  451. {
  452. mw->hideChannelList();
  453. return;
  454. }
  455. }
  456. void Controller::updateTitleBar()
  457. {
  458. if(titlebars.contains(tab.identifier))
  459. {
  460. mw->setTitleBarContent(titlebars.value(tab.identifier));
  461. }
  462. else
  463. {
  464. qWarning() << "Got tab without titlebar";
  465. mw->setTitleBarContent("");
  466. }
  467. }
  468. void Controller::parseISupportChanmodes(int cid, QString chanmodes)
  469. {
  470. QStringList sections;
  471. sections = chanmodes.split(',');
  472. if(sections.count() != 4)
  473. {
  474. qWarning() << "Received malformed CHANMODES argument in RPL_ISUPPORT - ignoring";
  475. return;
  476. }
  477. support_chanmodes.insert(cid, sections);
  478. }
  479. void Controller::parseISupportPrefix(int cid, QString prefix)
  480. {
  481. QStringList sections;
  482. QChar mode;
  483. QChar pref;
  484. QHash<QChar, QChar> prefhash;
  485. QHash<QChar, QChar> prefhash_reverse;
  486. QList<QChar> preforder;
  487. int i;
  488. int size;
  489. if(!prefix.startsWith('('))
  490. {
  491. qWarning() << "Received malformed PREFIX in RPL_ISUPPORT: PREFIX=" << prefix;
  492. return;
  493. }
  494. prefix = prefix.mid(1);
  495. sections = prefix.split(')');
  496. if(sections.count() != 2 || sections.at(0).size() != sections.at(1).size())
  497. {
  498. qWarning() << "Received malformed PREFIX in RPL_ISUPPORT: PREFIX=" << prefix;
  499. return;
  500. }
  501. size = sections.at(0).size();
  502. for(i = 0; i < size; i++)
  503. {
  504. mode = sections.at(0).at(i);
  505. pref = sections.at(1).at(i);
  506. prefhash.insert(pref, mode);
  507. prefhash_reverse.insert(mode, pref);
  508. preforder.append(pref);
  509. }
  510. support_prefix.insert(cid, prefhash);
  511. support_prefix_reverse.insert(cid, prefhash_reverse);
  512. support_prefix_order.insert(cid, preforder);
  513. }
  514. void Controller::parseChannelModes(int cid, QStringList params)
  515. {
  516. bool set = true; // When this is true we set the mode, else we unset the mode
  517. QStringList chanmodes = support_chanmodes.at(cid);
  518. // chanmodes[0] -> Mode that adds or removes a nick or address to a list. Always has a parameter.
  519. // chanmodes[1] -> Mode that changes a setting and always has a parameter.
  520. // chanmodes[2] -> Mode that changes a setting and only has a parameter when set.
  521. // chanmodes[3] -> Mode that changes a setting and never has a parameter.
  522. // http://www.irc.org/tech_docs/005.html
  523. QString name = params.takeAt(0);
  524. QString modes = params.takeAt(0);
  525. QChar cur;
  526. QChar oldprefix;
  527. QChar newprefix;
  528. QString arg;
  529. int j = 0;
  530. int k;
  531. for(int i = 0; i < modes.length(); i++)
  532. {
  533. cur = modes.at(i);
  534. if(cur == '+')
  535. {
  536. set = true;
  537. continue;
  538. }
  539. else if(cur == '-')
  540. {
  541. set = false;
  542. continue;
  543. }
  544. else if(support_prefix_reverse.at(cid).contains(cur))
  545. {
  546. arg = params.at(j);
  547. qDebug() << "MODE:";
  548. qDebug() << arg;
  549. qDebug() << cur;
  550. if(set)
  551. {
  552. qDebug() << "userlists->addPriv(" << cid << "," << name << "," << arg << "," << cur << ");";
  553. userlists->addPriv(cid, name, arg, cur);
  554. oldprefix = userlists->getPrefix(cid, name, arg);
  555. newprefix = support_prefix_reverse.at(cid).value(cur);
  556. for(k = 0; k < support_prefix_order.at(cid).count(); k++)
  557. {
  558. if(support_prefix_order.at(cid).at(k) == oldprefix)
  559. {
  560. break; // The current prefix is more important than the user mode that is unset
  561. }
  562. else if(support_prefix_order.at(cid).at(k) == newprefix)
  563. {
  564. // The new prefix is more important then the old, replace it
  565. userlists->setPrefix(cid, name, arg, newprefix);
  566. }
  567. }
  568. }
  569. else
  570. {
  571. qDebug() << "userlists->removePriv(" << cid << "," << name << "," << arg << "," << cur << ");";
  572. userlists->removePriv(cid, name, arg, cur);
  573. oldprefix = userlists->getPrefix(cid, name, arg);
  574. qDebug() << "oldprefix:" << oldprefix;
  575. qDebug() << "oldprefix' priv:" << support_prefix.at(cid).value(oldprefix);
  576. qDebug() << "current priv:" << cur;
  577. if(support_prefix.at(cid).value(oldprefix) == cur) // The current prefix shows the user mode
  578. {
  579. qDebug() << "Remove the prefix";
  580. userlists->setPrefix(cid, name, arg, 0); // Unset the prefix first
  581. // Now we have no idea what the actual mode on that user could be, but at least we can guess
  582. for(k = 0; k < support_prefix_order.at(cid).count(); k++)
  583. {
  584. // support_prefix_order.at(cid).at(k) << this is the PREFIX we are currently at, we are going from
  585. // MOST important to LEAST important, as they override each other in that order
  586. // support_prefix.at(cid).value() << this gives us the channel mode that corresponds to that prefix,
  587. // eg, @ -> o, + -> v
  588. // userlists->hasPriv(cid, name, arg, ) -> this checks if the user has the right to have that prefix,
  589. // eg, if he possesses that channel mode in that channel
  590. qDebug() << "Current Prefix:" << support_prefix_order.at(cid).at(k);
  591. qDebug() << "Current priv:" << support_prefix.at(cid).value(support_prefix_order.at(cid).at(k));
  592. qDebug() << "Current privs:" << userlists->getPrivs(cid, name, arg);
  593. if(userlists->hasPriv(cid, name, arg, support_prefix.at(cid).value(support_prefix_order.at(cid).at(k))))
  594. {
  595. qDebug() << "User has the right priv, set the Prefix to: " << support_prefix_order.at(cid).at(k);
  596. // The user has the privilege with this prefix needs to be, as it is the most important, we set the prefix
  597. userlists->setPrefix(cid, name, arg, support_prefix_order.at(cid).at(k));
  598. // and break the loop
  599. break;
  600. }
  601. }
  602. // Now we did everything possible to guess the right prefix, it's time to ask the server
  603. //sendLine(cid, QString("NAMES %1").arg(name));
  604. }
  605. }
  606. j++;
  607. }
  608. else if(chanmodes.at(0).contains(cur))
  609. {
  610. j++;
  611. }
  612. else if(chanmodes.at(1).contains(cur))
  613. {
  614. j++;
  615. }
  616. else if(chanmodes.at(2).contains(cur))
  617. {
  618. if(set)
  619. {
  620. j++;
  621. }
  622. else
  623. {
  624. }
  625. }
  626. else if(chanmodes.at(3).contains(cur))
  627. {
  628. }
  629. }
  630. if(cid == tab.cid && tab.name == name)
  631. {
  632. updateChannelList();
  633. }
  634. }
  635. void Controller::parseUserModes(int, QStringList)
  636. {
  637. }
  638. void Controller::handleJoin(int cid, QStringList params, Prefix prefix)
  639. {
  640. if(params.count() < 1)
  641. {
  642. qWarning() << "Not enough parameters for JOIN";
  643. }
  644. QTextCursor *cursor = getTab(cid, params.at(0));
  645. QString name = params.at(0);
  646. QString identifier = QString("%1/%2").arg(cid).arg(name);
  647. if(prefix.nickname == mynicks.value(cid))
  648. {
  649. if(cursor == 0)
  650. {
  651. addTab(identifier, settings.value("maxChannelHistory").toInt());
  652. mw->addTab(name, identifier, QString("%1").arg(cid), true);
  653. Q_ASSERT(tabs.contains(identifier));
  654. cursor = getTab(identifier);
  655. }
  656. channels[cid].insert(name);
  657. addLine(cursor, QString("You joined %1.").arg(name));
  658. // We don't need to add ourself to the channel list, the server will send a NAMES which must include us
  659. }
  660. else
  661. {
  662. if(cursor == 0)
  663. {
  664. qWarning() << "Received JOIN <unknown Channel> for another User - IGNORING";
  665. return;
  666. }
  667. addLine(cursor, QString("%1 has joined %2.").arg(prefix.nickname).arg(name));
  668. userlists->addUser(cid, name, prefix.nickname);
  669. updateChannelList();
  670. }
  671. }
  672. void Controller::handlePart(int cid, QStringList params, Prefix prefix)
  673. {
  674. if(params.count() < 1)
  675. {
  676. qWarning() << "Not enough parameters for PART";
  677. }
  678. QString name = params.at(0);
  679. QTextCursor *cursor = getTab(cid, params.at(0));
  680. QString identifier = QString("%1/%2").arg(cid).arg(name);
  681. if(cursor == 0)
  682. {
  683. qWarning() << "Received PART <unknown Channel> - IGNORING";
  684. return;
  685. }
  686. if(prefix.nickname == mynicks.value(cid))
  687. {
  688. channels[cid].remove(name);
  689. addLine(cursor, QString("You left %1.").arg(name));
  690. userlists->clearChannel(cid, name);
  691. updateChannelList();
  692. }
  693. else
  694. {
  695. addLine(cursor, QString("%1 has left %2.").arg(prefix.nickname).arg(name));
  696. userlists->removeUser(cid, name, prefix.nickname);
  697. updateChannelList();
  698. }
  699. }
  700. void Controller::handleQuit(int cid, QStringList params, Prefix prefix)
  701. {
  702. QString message;
  703. QStringList channels;
  704. QString channel;
  705. QString identifier;
  706. QTextCursor *cursor;
  707. if(params.count() > 0)
  708. {
  709. message = params.at(0);
  710. }
  711. channels = userlists->getChannels(cid, prefix.nickname);
  712. for(int i = 0; i < channels.count(); i++)
  713. {
  714. channel = channels.at(i);
  715. identifier = QString("%1/%2").arg(cid).arg(channel);
  716. cursor = getTab(cid, channel);
  717. if(cursor != 0)
  718. {
  719. addLine(cursor, QString("%1 has quit IRC (%2)").arg(prefix.nickname).arg(message));
  720. }
  721. userlists->removeUser(cid, channel, prefix.nickname);
  722. }
  723. updateChannelList();
  724. }
  725. void Controller::handlePrivmsg(int cid, QStringList params, Prefix prefix)
  726. {
  727. QTextCursor *cursor;
  728. QString line = params.at(1);
  729. if(line.contains(mynicks.value(cid)))
  730. {
  731. // TODO
  732. QSound::play("/Applications/Adium.app/Contents/Resources/Sounds/TokyoTrainStation.AdiumSoundset/New_Message.m4a");
  733. }
  734. replaceHTML(line);
  735. line = line.replace(QRegExp("((http|https):\\/\\/[\\w\\-_]+(\\.[\\w\\-_]+)+([\\w\\-\\.,@?^=%&amp;:/~\\+#]*[\\w\\-\\@?^=%&amp;/~\\+#])?)"),"<a href=\"\\1\">\\1</a>");
  736. if(line.startsWith(QString("%1ACTION").arg(char(1))) and line.endsWith(char(1)))
  737. {
  738. line = QString("%1 <strong>*&nbsp;%2</strong><span style=\"white-space:pre\">%3</span>").arg(QDateTime::currentDateTime().toString("[hh:mm]")).arg(prefix.nickname).arg(line.mid(7, line.size()-8));
  739. }
  740. else
  741. {
  742. line = QString("%1 <strong>&lt;%2&gt;</strong>&nbsp;<span style=\"white-space:pre\">%3</span>").arg(QDateTime::currentDateTime().toString("[hh:mm]")).arg(prefix.nickname).arg(line);
  743. }
  744. if(params.at(0) != mynicks.value(cid))
  745. {
  746. cursor = getTab(cid, params.at(0));
  747. }
  748. else
  749. {
  750. QString identifier = QString("%1/%2").arg(cid).arg(prefix.nickname);
  751. cursor = getTab(identifier);
  752. if(cursor == 0)
  753. {
  754. // TODO
  755. QSound::play("/Applications/Adium.app/Contents/Resources/Sounds/TokyoTrainStation.AdiumSoundset/New_Message.m4a");
  756. addTab(identifier, settings.value("maxPrivateHistory").toInt());
  757. mw->addTab(prefix.nickname, identifier, QString("%1").arg(cid), true);
  758. cursor = getTab(cid, prefix.nickname);
  759. }
  760. if(!prefix.isServer)
  761. {
  762. QString prefstr = QString("%1!%2@%3").arg(prefix.nickname).arg(prefix.username).arg(prefix.hostname);
  763. if(titlebars.value(identifier) != prefstr)
  764. {
  765. qDebug() << "Updating prefix in query title bar";
  766. titlebars.insert(identifier, prefstr);
  767. updateTitleBar();
  768. }
  769. }
  770. }
  771. if(cursor != 0)
  772. {
  773. addLine(cursor, line);
  774. }
  775. }
  776. void Controller::handleKick(int cid, QStringList params, Prefix prefix)
  777. {
  778. if(params.count() < 2)
  779. {
  780. qWarning() << "Not enough parameters for KICK";
  781. }
  782. QString name = params.at(0);
  783. QTextCursor *cursor = getTab(cid, params.at(0));
  784. QString identifier = QString("%1/%2").arg(cid).arg(name);
  785. QString message;
  786. QString nick = params.at(1);
  787. if(params.count() > 2)
  788. {
  789. message = params.at(2);
  790. }
  791. if(cursor == 0)
  792. {
  793. qWarning() << "Received KICK <unknown Channel> - IGNORING";
  794. return;
  795. }
  796. if(nick == mynicks.value(cid))
  797. {
  798. channels[cid].remove(name);
  799. addLine(cursor, QString("You were kicked from %1 by %2 (%3).").arg(name).arg(prefix.nickname).arg(message));
  800. userlists->clearChannel(cid, name);
  801. updateChannelList();
  802. }
  803. else
  804. {
  805. addLine(cursor, QString("%1 was kicked from %2 by %3 (%4).").arg(nick).arg(name).arg(prefix.nickname).arg(message));
  806. userlists->removeUser(cid, name, nick);
  807. updateChannelList();
  808. }
  809. }
  810. void Controller::handleNick(int cid, QStringList params, Prefix prefix)
  811. {
  812. QStringList channels;
  813. QString channel;
  814. QTextCursor *cursor;
  815. QString identifier;
  816. if(params.count() < 1)
  817. {
  818. qWarning() << "Not enough parameters for NICK";
  819. return;
  820. }
  821. if(prefix.nickname == mynicks.value(cid))
  822. {
  823. mynicks.insert(cid, params.at(0));
  824. }
  825. channels = userlists->getChannels(cid, prefix.nickname);
  826. for(int i = 0; i < channels.count(); i++)
  827. {
  828. channel = channels.at(i);
  829. identifier = QString("%1/%2").arg(cid).arg(channel);
  830. cursor = getTab(identifier);
  831. if(cursor != 0)
  832. {
  833. addLine(cursor, QString("%1 is now known as %2.").arg(prefix.nickname).arg(params.at(0)));
  834. }
  835. userlists->changeNick(cid, channel, prefix.nickname, params.at(0));
  836. if(tab.identifier == identifier)
  837. {
  838. updateChannelList();
  839. }
  840. }
  841. cursor = getTab(cid, prefix.nickname);
  842. if(cursor != 0)
  843. {
  844. addLine(cursor, QString("%1 is now known as %2.").arg(prefix.nickname).arg(params.at(0)));
  845. renameTab(cid, prefix.nickname, params.at(0));
  846. QString prefstr = QString("%1!%2@%3").arg(prefix.nickname).arg(prefix.username).arg(prefix.hostname);
  847. qDebug() << "Updating prefix in query title bar";
  848. titlebars.insert(identifier, prefstr);
  849. if(tab.cid == cid && tab.name == prefix.nickname)
  850. {
  851. updateTitleBar();
  852. }
  853. }
  854. }
  855. void Controller::handleNotice(int cid, QStringList params, Prefix prefix)
  856. {
  857. if(params.count() < 2)
  858. {
  859. qWarning() << "Not enough parameters for NOTICE";
  860. return;
  861. }
  862. QString target = params.at(0);
  863. QString line = params.at(1);
  864. replaceHTML(line);
  865. QTextCursor *cursor;
  866. line = line.replace(QRegExp("((http|https):\\/\\/[\\w\\-_]+(\\.[\\w\\-_]+)+([\\w\\-\\.,@?^=%&amp;:/~\\+#]*[\\w\\-\\@?^=%&amp;/~\\+#])?)"),"<a href=\"\\1\">\\1</a>");
  867. line = QString("%1 <strong>-%2-</strong>&nbsp;<span style=\"white-space:pre\">%3</span>").arg(QDateTime::currentDateTime().toString("[hh:mm]")).arg(prefix.nickname).arg(line);
  868. if(target == mynicks.value(cid)) // Private Notice to me
  869. {
  870. cursor = getTab(cid, prefix.nickname); // Get Query Tab
  871. }
  872. else // Target must be a channel
  873. {
  874. cursor = getTab(cid, target);
  875. }
  876. if(cursor == 0) // No Query/Channel open
  877. {
  878. cursor = getTab(tab.identifier); // Display notice in active tab
  879. }
  880. if(cursor != 0)
  881. {
  882. addLine(cursor, line);
  883. }
  884. }
  885. void Controller::handlePing(int cid, QStringList params, Prefix)
  886. {
  887. if (params.isEmpty())
  888. {
  889. sendLine(cid, "PONG");
  890. }
  891. else
  892. {
  893. sendLine(cid, QString("PONG :%1").arg(params.at(0)));
  894. }
  895. }
  896. void Controller::handleMode(int cid, QStringList params, Prefix prefix)
  897. {
  898. QTextCursor *cursor;
  899. QStringList mode;
  900. mode = params;
  901. mode.removeFirst();
  902. if(params.size() < 2)
  903. {
  904. qWarning() << "Not enough Parameters for MODE";
  905. return;
  906. }
  907. if(params.at(0) == prefix.nickname and
  908. params.at(0) == mynicks.value(cid)) // We are the target - user mode change
  909. {
  910. parseUserModes(cid, params);
  911. }
  912. else if(channels.at(cid).contains(params.at(0)))
  913. {
  914. cursor = getTab(cid, params.at(0));
  915. if(cursor != 0)
  916. {
  917. addLine(cursor, QString("! <strong>%1</strong> sets mode %2.").arg(prefix.nickname).arg(mode.join(" ")));
  918. }
  919. parseChannelModes(cid, params);
  920. }
  921. else
  922. {
  923. qWarning() << "Could not parse MODE: " << prefix.nickname << params.join(" ");
  924. }
  925. }
  926. void Controller::handle001(int cid, QStringList params, Prefix prefix)
  927. {
  928. if(!prefix.isServer)
  929. {
  930. qWarning() << "Received RPL_WELCOME from USER";
  931. return;
  932. }
  933. registered.replace(cid, true);
  934. if(params.count() > 0)
  935. {
  936. QString nick = params.at(0);
  937. mynicks.insert(cid, nick);
  938. }
  939. }
  940. void Controller::handle002(int, QStringList, Prefix)
  941. {
  942. }
  943. void Controller::handle003(int, QStringList, Prefix)
  944. {
  945. }
  946. void Controller::handle004(int, QStringList, Prefix)
  947. {
  948. }
  949. void Controller::handle005(int cid, QStringList params, Prefix prefix)
  950. {
  951. QString param;
  952. if(!prefix.isServer)
  953. {
  954. qWarning() << "Received RPL_ISUPPORT from USER";
  955. return;
  956. }
  957. for(int i = 0; i < params.size(); i++)
  958. {
  959. param = params.at(i);
  960. if(param.startsWith("CHANMODES="))
  961. {
  962. parseISupportChanmodes(cid, param.mid(10));
  963. }
  964. else if(param.startsWith("PREFIX="))
  965. {
  966. parseISupportPrefix(cid, param.mid(7));
  967. }
  968. }
  969. }
  970. void Controller::handle332(int cid, QStringList params, Prefix prefix)
  971. {
  972. if(params.count() < 3)
  973. {
  974. qWarning() << "Not enough parameters for RPL_TOPIC";
  975. return;
  976. }
  977. if(!prefix.isServer)
  978. {
  979. qWarning() << "Received RPL_TOPIC from USER";
  980. return;
  981. }
  982. QString identifier = QString("%1/%2").arg(cid).arg(params.at(1));
  983. titlebars.insert(identifier, params.at(2));
  984. if(tab.identifier == identifier)
  985. {
  986. updateTitleBar();
  987. }
  988. }
  989. void Controller::handle353(int cid, QStringList params, Prefix prefix)
  990. {
  991. QString identifier;
  992. QStringList names;
  993. QHash<QChar, QChar> chanprefix;
  994. QString name;
  995. int i;
  996. if(params.count() < 4)
  997. {
  998. qWarning() << "Not enough parameters for RPL_NAMREPLY";
  999. return;
  1000. }
  1001. if(!prefix.isServer)
  1002. {
  1003. qWarning() << "Received RPL_NAMREPLY from USER";
  1004. return;
  1005. }
  1006. if(!registered.at(cid))
  1007. {
  1008. qWarning() << "Received RPL_NAMREPLY before End of Registration";
  1009. return;
  1010. }
  1011. chanprefix = support_prefix.at(cid);
  1012. name = params.at(2);
  1013. identifier = QString("%1/%2").arg(cid).arg(name);
  1014. if(!namreply.contains(identifier))
  1015. {
  1016. //userlists->clearChannel(cid, name);
  1017. // if the server and client go out of sync, this will not safe us any longer - probably
  1018. // implement a mechanism that removed nicks that were not part of the last NAMREPLYs :/
  1019. // userlists->startUpdating(cid, name);
  1020. namreply.insert(identifier);
  1021. }
  1022. names = params.at(3).split(" ");
  1023. for(i = 0; i < names.count(); i++)
  1024. {
  1025. userlists->addUser(cid, name, names.at(i), chanprefix);
  1026. }
  1027. }
  1028. void Controller::handle366(int cid, QStringList params, Prefix prefix)
  1029. {
  1030. QString identifier;
  1031. if(params.count() < 3)
  1032. {
  1033. qWarning() << "Not enough parameters for RPL_ENDOFNAMES";
  1034. return;
  1035. }
  1036. if(!prefix.isServer)
  1037. {
  1038. qWarning() << "Received RPL_ENDOFNAMES from USER";
  1039. return;
  1040. }
  1041. if(!registered.at(cid))
  1042. {
  1043. qWarning() << "Received RPL_ENDOFNAMES before End of Registration";
  1044. return;
  1045. }
  1046. identifier = QString("%1/%2").arg(cid).arg(params.at(1));
  1047. if(namreply.contains(identifier))
  1048. {
  1049. // userlists->stopUpdating(cid, name);
  1050. namreply.remove(identifier);
  1051. if(tab.identifier == identifier)
  1052. {
  1053. updateChannelList();
  1054. }
  1055. }
  1056. else
  1057. {
  1058. qWarning() << "Server sent RPL_ENDOFNAMES before RPL_NAMREPLY";
  1059. }
  1060. }