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 &param)
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