1 /////////////////////////////////////////
2 //
3 // OpenLieroX
4 //
5 // code under LGPL, based on JasonBs work,
6 // enhanced by Dark Charlie and Albert Zeyer
7 //
8 //
9 /////////////////////////////////////////
10
11
12 // IRC client class
13 // Sergyi Pylypenko
14
15
16 #include "IRC.h"
17 #include "IRC_ReplyCodes.h"
18 #include "LieroX.h"
19 #include "FindFile.h"
20 #include "MathLib.h"
21 #include "Options.h"
22 #include "Version.h"
23 #include "StringBuf.h"
24 #include "StringUtils.h"
25 #include "Event.h"
26
27
28 /////////////////////////
29 // Initialize the IRC networking things (private)
initNet()30 bool IRCClient::initNet()
31 {
32 // Check if already inited
33 if(m_socketOpened)
34 return true;
35
36 // Open the socket
37 if (!m_chatSocket.OpenReliable(0)) {
38 errors("Could not open a socket for IRC networking: " + GetSocketErrorStr(GetSocketErrorNr()) + "\n");
39 return false;
40 }
41
42 m_socketOpened = true;
43
44 // Get the address
45 ResetNetAddr(m_chatServerAddr);
46 NetworkAddr ignored; // irc.quakenet.org has no IPv6 address, and I don't want to use irc.ipv6.quakenet.org
47
48 if(!GetFromDnsCache(m_chatServerAddrStr, m_chatServerAddr, ignored)) {
49 GetNetAddrFromNameAsync(m_chatServerAddrStr);
50 }
51
52 return true;
53 }
54
55
56 ///////////////////////////
57 // Add a chat message and call the "on update" event
addChatMessage(const std::string & msg,IRCTextType type)58 void IRCClient::addChatMessage(const std::string &msg, IRCTextType type)
59 {
60 // Add the text
61 m_chatText.push_back(IRCChatLine(msg, type));
62
63 // Get rid of old messages
64 while (m_chatText.size() > 1024)
65 m_chatText.pop_front();
66
67 // Run the event functions
68 for( std::set<IRCNewMessageCB>::const_iterator it = m_newMsgCallback.begin(); it != m_newMsgCallback.end(); it++ )
69 (*it)(msg, (int)type);
70 }
71
72 ///////////////////////
73 // Connecting process, returns true if finished
processConnecting()74 bool IRCClient::processConnecting()
75 {
76 // If connected, just quit
77 if (m_authorizedState != AUTH_NONE)
78 return true;
79
80 // Check for DNS resolution
81 NetworkAddr ignored; // irc.quakenet.org has no IPv6 address, and I don't want to use irc.ipv6.quakenet.org
82
83 if(!GetFromDnsCache(m_chatServerAddrStr, m_chatServerAddr, ignored))
84 return false;
85
86 if (!IsNetAddrValid(m_chatServerAddr))
87 return false;
88
89 // Convert the address to string
90 std::string addrStr;
91 NetAddrToString(m_chatServerAddr, addrStr);
92
93 // Add IRC port
94 SetNetAddrPort(m_chatServerAddr, IRC_PORT );
95
96 // Connect
97 if (!m_socketConnected) {
98 if (!m_chatSocket.Connect(m_chatServerAddr)) {
99 errors("IRC error: could not connect to the server " + addrStr);
100 disconnect();
101 return true;
102 }
103
104 // Connected
105 m_socketConnected = true;
106 m_socketIsReady = false;
107 m_netBuffer.clear();
108 m_authorizedState = AUTH_NONE;
109 }
110
111 // Check for socket readiness
112 if (!m_chatSocket.isReady())
113 return false;
114 else
115 m_socketIsReady = true;
116
117 return true;
118 }
119
120
121 //////////////////////////
122 // Escapes special characters in the nick
makeNickIRCFriendly()123 void IRCClient::makeNickIRCFriendly()
124 {
125 // Escape some symbols, make nick IRC-compatible
126 replace(m_myNick, " ", "_" );
127 #define S_LETTER_UPPER "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
128 #define S_LETTER_LOWER "abcdefghijklmnopqrstuvwxyz"
129 #define S_LETTER S_LETTER_UPPER S_LETTER_LOWER
130 #define S_NUMBER "0123456789"
131 #define S_SYMBOL "-[]\\`^{}_"
132
133 // Length overflow
134 if(m_myNick.size() > IRC_NICK_MAX_LEN)
135 m_myNick.resize(IRC_NICK_MAX_LEN);
136
137 // Empty nick
138 if(m_myNick.empty())
139 m_myNick = "z";
140
141 // Nick starting with a number
142 if(m_myNick.find_first_of(S_NUMBER) == 0 )
143 m_myNick[0] = 'z';
144
145 // Replace the dangerous characters
146 while (m_myNick.find_first_not_of(S_LETTER S_NUMBER S_SYMBOL) != std::string::npos)
147 m_myNick[m_myNick.find_first_not_of(S_LETTER S_NUMBER S_SYMBOL)] = '-';
148
149 if (m_myNick.size() > 0)
150 if (m_myNick[0] == '_' || m_myNick[0] == '-' )
151 m_myNick[0] = 'z';
152
153 m_nickUniqueNumber = -1;
154 m_updatingUserList = false;
155 }
156
157
158 /////////////////////////////
159 // Connect to server#channel
connect(const std::string & server,const std::string & channel,const std::string & nick)160 bool IRCClient::connect(const std::string &server, const std::string &channel, const std::string &nick)
161 {
162 // Disconnect first
163 if (m_socketOpened)
164 disconnect();
165
166 m_connecting = true;
167
168 // Set the info
169 m_chatServerAddrStr = server;
170 m_chatServerChannel = channel;
171 m_myNick = nick;
172
173 // Initialize the network
174 if (!initNet())
175 return false;
176
177 processConnecting();
178 return true;
179 }
180
181 ////////////////////////
182 // Process the IRC client (handle incoming messages etc.)
process()183 void IRCClient::process()
184 {
185 // Re-connect once per 5 seconds
186 if (!m_socketConnected && !m_connecting) {
187 if(tLX->currentTime - m_connectionClosedTime >= 5.0f) {
188 m_connectionClosedTime = tLX->currentTime;
189 connect(m_chatServerAddrStr, m_chatServerChannel, m_myNick);
190 }
191 return;
192 }
193
194 // Connecting process
195 if (!processConnecting())
196 return;
197
198 // Initiate server response
199 if (m_authorizedState == AUTH_NONE) {
200 // Adjust the nickname
201 if (m_myNick.empty())
202 m_myNick = "OpenLieroXor";
203 makeNickIRCFriendly();
204
205 // Send the nick command
206 sendNick();
207 m_authorizedState = AUTH_NICK_SENT;
208 }
209
210 // Read data from the socket
211 readData();
212 }
213
214 /////////////////////////
215 // Read data from the socket and process it
readData()216 void IRCClient::readData()
217 {
218 // Get data from the socket
219 char buf[1024];
220
221 int read = 1;
222 while (read > 0) {
223 int read = m_chatSocket.Read(buf, sizeof(buf)); // HINT: non-blocking
224
225 // Nothing yet
226 if (read == 0)
227 break;
228
229 // Error
230 if(read < 0) {
231 if (!IsMessageEndSocketErrorNr(GetSocketErrorNr())) {
232 errors("IRC: network error - " + GetSocketErrorStr(GetSocketErrorNr()) + "\n");
233 disconnect();
234 }
235 break;
236 }
237
238 // Add the data to the buffer
239 m_netBuffer.append(buf, read);
240 }
241
242 if (m_netBuffer.empty())
243 return;
244
245 size_t pos = m_netBuffer.find("\r\n");
246 while(pos != std::string::npos) {
247 std::string line = m_netBuffer.substr(0, pos);
248
249 //printf("IRC: %s\n", line.c_str());
250
251 // Check if the sender is specified
252 m_netBuffer.erase(0, pos + 2);
253 IRCCommand cmd;
254 if(line.size() && line[0] == ':') {
255 size_t pos2 = line.find(' ');
256 cmd.sender = line.substr(1, pos2);
257
258 // Check for exclamation mark in the nick as a special character
259 size_t excl_pos = cmd.sender.find('!');
260 if (excl_pos != std::string::npos) {
261 cmd.sender.erase(excl_pos); // Erase anything (usually IP + ISP info) from the nick
262 }
263
264 line.erase(0, pos2 + 1);
265 }
266
267 // Get the command and parameters
268 cmd.cmd = line.substr(0, line.find(' '));
269 while (line.find(' ') != std::string::npos ) {
270 line = line.substr(line.find(' ') + 1);
271 if (line.size() == 0)
272 break;
273 if (line[0] == ':') {
274 cmd.params.push_back(line.substr(1));
275 break;
276 }
277
278 cmd.params.push_back(line.substr(0, line.find(' ')));
279 }
280
281 // Parse the command
282 parseCommand(cmd);
283
284 pos = m_netBuffer.find("\r\n");
285 }
286 }
287
288 ///////////////////////
289 // Disconnect from the server
disconnect()290 void IRCClient::disconnect()
291 {
292 // Disconnect only when connected
293 if (!m_socketOpened)
294 return;
295
296 // Call the disconnect callback
297 for( std::set<IRCDisconnectCB>::const_iterator it = m_disconnectCallback.begin(); it != m_disconnectCallback.end(); it++ )
298 (*it)();
299
300 // Close socket
301 if (m_chatSocket.isReady())
302 m_chatSocket.Write("QUIT\r\n");
303 if (m_chatSocket.isOpen())
304 m_chatSocket.Close();
305
306 // Clear the variables
307 m_socketConnected = false;
308 m_socketIsReady = false;
309 m_socketOpened = false;
310 m_connecting = false;
311 m_connectionClosedTime = tLX->currentTime;
312 m_chatSocket.Clear();
313 SetNetAddrValid(m_chatServerAddr, false);
314 m_netBuffer.clear();
315 m_authorizedState = AUTH_NONE;
316 m_nickUniqueNumber = -1;
317 m_updatingUserList = false;
318 m_chatUsers.clear();
319
320 // Call the update user list callback
321 for( std::set<IRCUpdateUserListCB>::const_iterator it = m_updateUsersCallback.begin(); it != m_updateUsersCallback.end(); it++ )
322 (*it)(m_chatUsers);
323
324 notes("IRC: disconnected\n");
325 }
326
327 //////////////////////////
328 // Converts IRC formatting to HTML (OLX uses HTML for rich-text formatting)
ircFormattingToHtml(const std::string & irctext)329 std::string IRCClient::ircFormattingToHtml(const std::string &irctext)
330 {
331 std::list<std::string> open_tags;
332
333 // Indexed IRC colors
334 static const char * irc_colors[] = { "#FFFFFF", "#000000", "#000080", "#00FF00", "#FF0000", "#804040",
335 "#8000FF", "#808000", "#FFFF00", "#00FF00", "#008080", "#00FFFF", "#0000FF", "#FF00FF", "#808080",
336 "#C0C0C0" };
337
338 std::string result;
339 for (std::string::const_iterator it = irctext.begin(); it != irctext.end(); ) {
340 // Check for special characters
341 if ((unsigned char)*it < 32) {
342 switch (*it) {
343 case 2: { // Bold
344 // If the B tag is already open, consider this as an end of the bold text
345 bool bold = false;
346 for (std::list<std::string>::reverse_iterator rit = open_tags.rbegin(); rit != open_tags.rend(); rit++) {
347 if (*rit == "b") {
348 result += "</b>";
349 std::list<std::string>::iterator it = rit.base();
350 it--;
351 open_tags.erase(it);
352 bold = true;
353 break;
354 }
355 }
356
357 if (!bold) {
358 result += "<b>";
359 open_tags.push_back("b");
360 }
361
362 it++;
363 continue; // Skip the character
364 } break;
365 case 3: { // Color
366 it++; // Skip the control character
367 // Read the color index
368 std::string index;
369 while (it != irctext.end()) {
370 if (!isdigit((uchar)*it))
371 break;
372 index += *it;
373 it++;
374 }
375
376 // Don't add the format, if there's no text to format
377 if (it == irctext.end() || index.empty())
378 continue;
379
380 int col_index = CLAMP(atoi(index), 0, (int)(sizeof(irc_colors)/sizeof(char *) - 1)); // Convert the index
381
382 result += "<font color=\"" + std::string(irc_colors[col_index]) + "\">";
383 open_tags.push_back("font");
384 } break;
385
386 case 31: { // Underline
387 // If the U tag is already open, consider this as an end of the underlined text
388 bool u = false;
389 for (std::list<std::string>::reverse_iterator rit = open_tags.rbegin(); rit != open_tags.rend(); rit++) {
390 if (*rit == "u") {
391 result += "</u>";
392 std::list<std::string>::iterator it = rit.base();
393 it--;
394 open_tags.erase(it);
395 u = true;
396 break;
397 }
398 }
399
400 if (!u) {
401 result += "<u>";
402 open_tags.push_back("u");
403 }
404
405 it++;
406 continue; // Skip the character
407 } break;
408
409 case '\t': // Tab
410 break;
411
412 default:
413 it++;
414 continue; // Ignore the non-printable character
415 }
416 }
417
418 // Normal character
419 result += GetUtf8FromUnicode(GetNextUnicodeFromUtf8(it, irctext.end()));
420 }
421
422 // Close any open tags
423 while (open_tags.size()) {
424 std::string& tag = *open_tags.rbegin();
425 result += "</" + tag + ">";
426 open_tags.pop_back();
427 }
428
429 return result;
430 }
431
432 /*
433 *
434 * Sending part
435 *
436 */
437
438 ///////////////
439 // Join a channel
sendJoin()440 void IRCClient::sendJoin()
441 {
442 m_chatSocket.Write("JOIN " + m_chatServerChannel + "\r\n");
443 m_authorizedState = AUTH_JOINED_CHANNEL;
444 if( m_AwayMessage != "" )
445 m_chatSocket.Write("AWAY :" + m_AwayMessage + "\r\n");
446 }
447
448
449 ///////////////////
450 // Tell the server our nickname
sendNick()451 void IRCClient::sendNick()
452 {
453 if (m_nickUniqueNumber < 0)
454 m_chatSocket.Write("NICK " + m_myNick + "\r\n");
455 else
456 m_chatSocket.Write("NICK " + m_myNick + itoa(m_nickUniqueNumber) + "\r\n");
457 }
458
459 ///////////////////////
460 // Send a ping reply
sendPong(const std::string & param)461 void IRCClient::sendPong(const std::string ¶m)
462 {
463 m_chatSocket.Write("PONG :" + param + "\r\n");
464 }
465
466 ///////////////////////
467 // Request nick list from the server
sendRequestNames()468 void IRCClient::sendRequestNames()
469 {
470 m_chatSocket.Write("NAMES " + m_chatServerChannel + "\r\n");
471 }
472
473 ///////////////////////
474 // Sends user authentication to the server
sendUserAuth()475 void IRCClient::sendUserAuth()
476 {
477 std::string nick = m_myNick;
478 if (m_nickUniqueNumber >= 0)
479 nick += itoa(m_nickUniqueNumber);
480 // quakenet server doesn't like "--" or "__" or "-_" or anything like that in username
481 replace(nick, "_", "-");
482 replace(nick, "--", "-");
483 m_chatSocket.Write("USER " + nick + " ? ? :" + GetFullGameName() + "\r\n");
484 m_authorizedState = AUTH_USER_SENT;
485 }
486
487 /////////////////////////
488 // Send a message to the IRC channel, returns true if the chat has been sent
sendChat(const std::string & text1)489 bool IRCClient::sendChat(const std::string &text1)
490 {
491 // Make sure we are connected
492 if (!m_socketConnected || !m_socketIsReady || m_authorizedState != AUTH_JOINED_CHANNEL || text1.empty())
493 return false;
494
495 std::string text(text1);
496 // Some useful chat commands
497 if( (text.find("/pm ") == 0 && text.find(" ", 4) != std::string::npos) ||
498 (text.find("/msg ") == 0 && text.find(" ", 5) != std::string::npos) )
499 {
500 // PM specified user
501 std::string::size_type space1 = text.find(" ") + 1;
502 std::string user = text.substr( space1, space1 - text.find(" ", space1));
503 text = text.substr(text.find(" ", space1));
504 m_chatSocket.Write("PRIVMSG " + user + " :" + text + "\r\n");
505 }
506 else if( text.find("/nick ") == 0 || text.find("/name ") == 0 || text.find("/setmyname ") == 0 )
507 {
508 m_myNick = text.substr( text.find(" ") + 1 );
509 TrimSpaces(m_myNick);
510 m_nickUniqueNumber = -1;
511 makeNickIRCFriendly();
512 m_chatSocket.Write("NICK " + m_myNick + "\r\n");
513 text = "You changed your name to " + m_myNick;
514 }
515 else
516 {
517 // Send the text
518 m_chatSocket.Write("PRIVMSG " + m_chatServerChannel + " :" + text + "\r\n");
519 }
520
521 // Route the same message back to our parser func, there's no echo in IRC
522 IRCCommand cmd;
523 cmd.sender = m_myNick;
524 cmd.cmd = "PRIVMSG";
525 cmd.params.push_back(m_chatServerChannel);
526 cmd.params.push_back(text);
527 parseCommand(cmd);
528
529 return true;
530 }
531
532 /////////////////////////
533 // Set Away message (we put OLX version here)
setAwayMessage(const std::string & msg)534 void IRCClient::setAwayMessage(const std::string & msg)
535 {
536 m_AwayMessage = msg;
537
538 if (!m_socketConnected || !m_socketIsReady || m_authorizedState != AUTH_JOINED_CHANNEL)
539 return;
540
541 if( m_AwayMessage != "" )
542 m_chatSocket.Write("AWAY" "\r\n"); // Clean AWAY msg or server won't reset it
543 m_chatSocket.Write("AWAY :" + m_AwayMessage + "\r\n");
544 }
545
546 /////////////////////////
547 // Send Whois command on user
sendWhois(const std::string & userName)548 void IRCClient::sendWhois(const std::string & userName)
549 {
550 m_chatSocket.Write("WHOIS " + userName + "\r\n");
551 }
552
553 /////////////////////////
554 // Send Whois command on user, and get back info
getWhois(const std::string & user)555 std::string IRCClient::getWhois(const std::string & user)
556 {
557 if( m_whoisUserName != user )
558 {
559 m_whoisUserName = user;
560 m_whoisUserInfo = "";
561 m_whoisUserAwayMsg = "";
562 sendWhois( user );
563 }
564
565 std::string ret;
566 if( m_whoisUserInfo != "" )
567 ret = m_whoisUserInfo;
568 if( m_whoisUserAwayMsg != "" )
569 ret += "\n" + m_whoisUserAwayMsg;
570 return ret;
571 }
572
573
574 /*
575 *
576 * Parsing part
577 *
578 */
579
580 /////////////////////
581 // End of connection
parseDropped(const IRCClient::IRCCommand & cmd)582 void IRCClient::parseDropped(const IRCClient::IRCCommand &cmd)
583 {
584 // Get our real nick (add the unique number) for comparison
585 std::string real_nick = m_myNick;
586 if (m_nickUniqueNumber >= 0)
587 real_nick += itoa(m_nickUniqueNumber);
588
589 // Make sure the quit is directed to us (if not, someone else has been dropped/has left)
590 if (cmd.sender == real_nick)
591 disconnect();
592 else {
593 // Remove the person that has left from the list
594 for (std::list<std::string>::iterator it = m_chatUsers.begin(); it != m_chatUsers.end(); it++) {
595 if (*it == cmd.sender) {
596 m_chatUsers.erase(it);
597 for( std::set<IRCUpdateUserListCB>::const_iterator it = m_updateUsersCallback.begin(); it != m_updateUsersCallback.end(); it++ )
598 (*it)(m_chatUsers);
599 break;
600 }
601 }
602 }
603 }
604
605 ///////////////////
606 // Kicked out of the server
parseKicked(const IRCClient::IRCCommand & cmd)607 void IRCClient::parseKicked(const IRCClient::IRCCommand &cmd)
608 {
609 // Get our real nick (add the unique number) for comparison
610 std::string real_nick = m_myNick;
611 if (m_nickUniqueNumber >= 0)
612 real_nick += itoa(m_nickUniqueNumber);
613
614 // Invalid command
615 if (cmd.params.size() < 2)
616 return;
617
618 // Check that it was us who gets kicked
619 if (cmd.params[1] != m_myNick)
620 return;
621
622 // Get the kick reason
623 std::string reason = "No reason was given";
624 if (cmd.params.size() >= 3)
625 reason = cmd.params[2];
626
627 // Inform the user
628 addChatMessage("You have been kicked: " + reason, IRC_TEXT_NOTICE);
629
630 // Disconnect
631 disconnect();
632 }
633
634 ///////////////////
635 // End of name list
parseEndOfNames(const IRCClient::IRCCommand & cmd)636 void IRCClient::parseEndOfNames(const IRCClient::IRCCommand &cmd)
637 {
638 m_updatingUserList = false;
639 m_chatUsers.sort();
640 }
641
642 ////////////////////
643 // Join successful
parseJoin(const IRCClient::IRCCommand & cmd)644 void IRCClient::parseJoin(const IRCClient::IRCCommand& cmd)
645 {
646 m_connecting = false;
647
648 // Re-request the nick names
649 sendRequestNames();
650
651 // Callback
652 for( std::set<IRCConnectCB>::const_iterator it = m_connectCallback.begin(); it != m_connectCallback.end(); it++ )
653 (*it)();
654
655 notes("IRC connected to " + m_chatServerChannel + "@" + m_chatServerAddrStr + "\n");
656 }
657
658 /////////////////////
659 // Parse a mode packet
parseMode(const IRCClient::IRCCommand & cmd)660 void IRCClient::parseMode(const IRCClient::IRCCommand &cmd)
661 {
662 if (m_authorizedState == AUTH_USER_SENT) {
663 sendJoin();
664 }
665 }
666
667 ///////////////////////
668 // List of the nicknames in the channel
parseNameReply(const IRCClient::IRCCommand & cmd)669 void IRCClient::parseNameReply(const IRCClient::IRCCommand &cmd)
670 {
671 // Check
672 if(cmd.params.size() < 4 )
673 return;
674
675 // If this is the first reply packet, clear the user list
676 if(!m_updatingUserList )
677 m_chatUsers.clear();
678
679 m_updatingUserList = true;
680
681 // Get the nick names
682 StringBuf line(cmd.params[3]);
683 std::vector<std::string> nicks = line.splitBy(' ');
684
685 for (std::vector<std::string>::iterator it = nicks.begin(); it != nicks.end(); it++) {
686 // Check for validity
687 std::string& user = *it;
688 if (user.size() == 0)
689 continue;
690
691 if (user[0] == '@') // Channel Operator
692 user.erase(0, 1);
693
694 m_chatUsers.push_back(user); // Add the user to the list
695 }
696
697 // Callback
698 for( std::set<IRCUpdateUserListCB>::const_iterator it = m_updateUsersCallback.begin(); it != m_updateUsersCallback.end(); it++ )
699 (*it)(m_chatUsers);
700 }
701
702 ////////////////////
703 // Parse the NICK command
parseNick(const IRCClient::IRCCommand & cmd)704 void IRCClient::parseNick(const IRCClient::IRCCommand &cmd)
705 {
706 sendRequestNames(); // Re-request the nicknames
707 }
708
709 //////////////////////
710 // The nickname is in use
parseNickInUse(const IRCClient::IRCCommand & cmd)711 void IRCClient::parseNickInUse(const IRCClient::IRCCommand &cmd)
712 {
713 ++m_nickUniqueNumber;
714
715 // Make sure we don't overflow the maximum allowed nick length
716 if(m_myNick.size() + itoa(m_nickUniqueNumber).size() > IRC_NICK_MAX_LEN )
717 m_myNick.resize(IRC_NICK_MAX_LEN - itoa(m_nickUniqueNumber).size());
718
719 sendNick();
720 }
721
722 ///////////////////////
723 // Ping
parsePing(const IRCClient::IRCCommand & cmd)724 void IRCClient::parsePing(const IRCClient::IRCCommand &cmd)
725 {
726 // Check
727 if (cmd.params.size() == 0)
728 return;
729
730 // Send reply
731 sendPong(cmd.params[0]);
732
733 // Progress with authorisation if not yet connected
734 if (m_authorizedState == AUTH_NICK_SENT)
735 sendUserAuth();
736 }
737
738 ///////////////////////
739 // Private message
parsePrivmsg(const IRCClient::IRCCommand & cmd)740 void IRCClient::parsePrivmsg(const IRCClient::IRCCommand &cmd)
741 {
742 // Check
743 if (cmd.params.size() < 2 )
744 return;
745
746 // Add the message
747 std::string nick = cmd.sender.substr(0, cmd.sender.find('!'));
748 std::string text;
749
750 std::string my_nick = m_myNick;
751 if (m_nickUniqueNumber >= 0)
752 my_nick += itoa(m_nickUniqueNumber);
753
754 IRCTextType type = IRC_TEXT_CHAT;
755 if (cmd.params[1].size() && cmd.params[1][0] == '\1' && *cmd.params[1].rbegin() == '\1') { // Action message
756 text = cmd.params[1].substr(1, cmd.params[1].size() - 2);
757 replace(text, "ACTION", nick);
758 type = IRC_TEXT_ACTION;
759 } else if (cmd.params[0] == my_nick) {
760 text = nick + ": " + cmd.params[1];
761 type = IRC_TEXT_PRIVATE;
762 } else
763 text = nick + ": " + cmd.params[1];
764 addChatMessage(ircFormattingToHtml( text ), type);
765 }
766
767 ///////////////////////
768 // Notice
parseNotice(const IRCClient::IRCCommand & cmd)769 void IRCClient::parseNotice(const IRCClient::IRCCommand &cmd)
770 {
771 // Notice is actually a chat message
772
773 // Check
774 if (cmd.params.size() < 2 )
775 return;
776
777 // Ignore any notices before joining the channel (they are more or less some server spam)
778 if (m_authorizedState != AUTH_JOINED_CHANNEL)
779 return;
780
781 // Get the nick
782 std::string nick = cmd.sender.substr(0, cmd.sender.find('!'));
783
784 // Add the message
785 std::string text;
786 if (nick.size())
787 text = nick + ": " + cmd.params[1];
788 else
789 text = cmd.params[1];
790 addChatMessage(text, IRC_TEXT_NOTICE);
791 }
792
793
794 ///////////////////////////
795 // Fatal error from the server
parseError(const IRCClient::IRCCommand & cmd)796 void IRCClient::parseError(const IRCClient::IRCCommand &cmd)
797 {
798 if (cmd.params.size() > 0) {
799 warnings("IRC server error: " + cmd.params[0] + "\n");
800 addChatMessage("Server error: " + cmd.params[0], IRC_TEXT_NOTICE);
801 disconnect();
802 }
803 }
804
805
806 ///////////////////////////
807 // Away message (contains the server on which user is playing)
parseAway(const IRCCommand & cmd)808 void IRCClient::parseAway(const IRCCommand& cmd)
809 {
810 if (cmd.params.size() < 3 )
811 return;
812 if( cmd.params[1] != m_whoisUserName )
813 return;
814 m_whoisUserAwayMsg = cmd.params[2];
815 TrimSpaces(m_whoisUserAwayMsg);
816 };
817
818 ///////////////////////////
819 // Whois message (contains the OLX version of user)
parseWhois(const IRCCommand & cmd)820 void IRCClient::parseWhois(const IRCCommand& cmd)
821 {
822 if (cmd.params.size() < 6 )
823 return;
824 if( cmd.params[1] != m_whoisUserName )
825 return;
826 m_whoisUserInfo = cmd.params[5];
827 TrimSpaces(m_whoisUserInfo);
828 };
829
830 ///////////////////////////
831 // End of Whois message (pretty useless for us)
parseEndOfWhois(const IRCCommand & cmd)832 void IRCClient::parseEndOfWhois(const IRCCommand& cmd)
833 {
834 };
835
parseTopic(const IRCCommand & cmd)836 void IRCClient::parseTopic(const IRCCommand& cmd)
837 {
838 if( cmd.params.size() < 3 )
839 return;
840 addChatMessage("Topic: " + cmd.params[2], IRC_TEXT_NOTICE);
841 };
842
843 //////////////////////////////
844 // Parse an IRC command (private)
parseCommand(const IRCClient::IRCCommand & cmd)845 void IRCClient::parseCommand(const IRCClient::IRCCommand &cmd)
846 {
847
848 /*printf("IRC: sender '%s' cmd '%s'", cmd.sender.c_str(), cmd.cmd.c_str() );
849 for( size_t i=0; i<cmd.params.size(); i++ )
850 printf(" param %i '%s'", i, cmd.params[i].c_str());
851 printf("\n");*/
852
853
854 // Process depending on the command
855
856 bool fail = false;
857 int num_command = from_string<int>(cmd.cmd, fail);
858
859 // String commands
860 if (fail) {
861 if (cmd.cmd == "PING")
862 parsePing(cmd);
863
864 else if (cmd.cmd == "MODE")
865 parseMode(cmd);
866
867 else if (cmd.cmd == "PRIVMSG")
868 parsePrivmsg(cmd);
869
870 else if (cmd.cmd == "KICK")
871 parseKicked(cmd);
872
873 else if (cmd.cmd == "PART" || cmd.cmd == "QUIT")
874 parseDropped(cmd);
875
876 else if (cmd.cmd == "JOIN")
877 parseJoin(cmd);
878
879 else if (cmd.cmd == "NICK")
880 parseNick(cmd);
881
882 else if (cmd.cmd == "NOTICE")
883 parseNotice(cmd);
884
885 else if (cmd.cmd == "ERROR")
886 parseError(cmd);
887
888 else
889 warnings("IRC: unknown command " + cmd.cmd + "\n");
890
891 // Numeric commands
892 } else {
893 switch (num_command) {
894
895 // Nick in use
896 case LIBIRC_RFC_ERR_NICKNAMEINUSE:
897 parseNickInUse(cmd);
898 break;
899
900 // List of names
901 case LIBIRC_RFC_RPL_NAMREPLY:
902 parseNameReply(cmd);
903 break;
904
905 // End of name list
906 case LIBIRC_RFC_RPL_ENDOFNAMES:
907 parseEndOfNames(cmd);
908 break;
909
910
911 case LIBIRC_RFC_RPL_AWAY:
912 parseAway(cmd);
913 break;
914
915 case LIBIRC_RFC_RPL_WHOISUSER:
916 parseWhois(cmd);
917 break;
918
919 case LIBIRC_RFC_RPL_ENDOFWHOIS:
920 parseEndOfWhois(cmd);
921 break;
922
923 case LIBIRC_RFC_RPL_TOPIC:
924 parseTopic(cmd);
925 break;
926
927 // Message of the day
928 case LIBIRC_RFC_RPL_MOTDSTART:
929 case LIBIRC_RFC_RPL_MOTD:
930 case LIBIRC_RFC_RPL_ENDOFMOTD:
931 // Message of the day (ignored currently)
932 break;
933
934 // Messages sent upon a successful join
935 case LIBIRC_RFC_RPL_WELCOME:
936 case LIBIRC_RFC_RPL_YOURHOST:
937 case LIBIRC_RFC_RPL_CREATED:
938 case LIBIRC_RFC_RPL_MYINFO:
939 // Quite useless stuff...
940 break;
941
942 default: {}
943 // Just ignore, there are many "pro" commands that we don't need
944 }
945 }
946 }
947
948
949 /*
950 *
951 * Global stuff
952 *
953 */
954
955 IRCClient *globalIRC = NULL;
956
957 /////////////////////////
958 // Initializes the IRC client and connects to the server (specified in options)
InitializeIRC()959 bool InitializeIRC()
960 {
961 // Already initialized?
962 if (globalIRC)
963 return true;
964
965 if (!tLXOptions->bEnableChat)
966 return false;
967
968 // Allocate the IRC client
969 try {
970 globalIRC = new IRCClient();
971 } catch (...) {
972 return false;
973 }
974
975 // Get the server
976 FILE *fp = OpenGameFile("cfg/chatserver.txt", "r");
977 if (fp) {
978 std::string addr = ReadUntil(fp, '/');
979 std::string chann = ReadUntil(fp, '\n');
980 fclose(fp);
981 return globalIRC->connect(addr, chann, tLXOptions->sLastSelectedPlayer);
982 } else { // Defaults
983 return globalIRC->connect("irc.quakenet.org", "#LieroX", tLXOptions->sLastSelectedPlayer);
984 }
985 }
986
987 /////////////////////////
988 // Disconnects the IRC client and does all the cleanup
ShutdownIRC()989 void ShutdownIRC()
990 {
991 if (globalIRC)
992 delete globalIRC;
993 globalIRC = NULL;
994 }
995
996 ////////////////////////
997 // Returns an instance of the global IRC client
GetGlobalIRC()998 IRCClient *GetGlobalIRC()
999 {
1000 return globalIRC;
1001 }
1002
1003 /////////////////////////
1004 // Handles processing of the global IRC
ProcessIRC()1005 void ProcessIRC()
1006 {
1007 if (globalIRC)
1008 globalIRC->process();
1009 }
1010