<commit_before>/////////////////////////////////////////
//
//             OpenLieroX
//
// code under LGPL, based on JasonBs work,
// enhanced by <NAME> and <NAME>
//
//
/////////////////////////////////////////


// IRC client class
// <NAME>

#include <iostream>
#include "IRC.h"
#include "LieroX.h"
#include "Options.h"
#include "Version.h"
#include "StringBuf.h"
#include "StringUtils.h"
#include "Event.h"
#include "libirc_rfcnumeric.h"


/////////////////////////
// Initialize the IRC networking things (private)
bool IRCClient::initNet()
{
	// Check if already inited
	if(m_socketOpened)
		return true;
	
	// Open the socket
	m_chatSocket = OpenReliableSocket(0);
	if (!IsSocketStateValid(m_chatSocket))  {
		printf("ERROR: Could not open a socket for IRC networking: " + GetSocketErrorStr(GetSocketErrorNr()) + "\n");
		return false;
	}

	m_socketOpened = true;

	// Get the address
	ResetNetAddr(m_chatServerAddr);
	if(!GetNetAddrFromNameAsync(m_chatServerAddrStr, m_chatServerAddr))	 {
		printf("ERROR: Wrong IRC server addr: %s" + m_chatServerAddrStr + "\n");
		return false;
	}

	return true;
}


///////////////////////////
// Add a chat message and call the "on update" event
void IRCClient::addChatMessage(const std::string &msg, IRCTextType type)
{
	// Add the text
	m_chatText.push_back(IRCChatLine(msg, type));

	// Get rid of old messages
	while (m_chatText.size() > 1024)
		m_chatText.pop_front();

	// Run the event function
	if (m_newMsgCallback)
		m_newMsgCallback(msg, (int)type);
}

///////////////////////
// Connecting process, returns true if finished
bool IRCClient::processConnecting()
{
	// If connected, just quit
	if (m_authorizedState != AUTH_NONE)
		return true;

	// Check for DNS resolution
	if (!IsNetAddrValid(m_chatServerAddr))
		return false;

	// Convert the address to string
	std::string addrStr;
	NetAddrToString(m_chatServerAddr, addrStr);
	
	// Add IRC port
	SetNetAddrPort(m_chatServerAddr, IRC_PORT );

	// Connect
	if (!m_socketConnected)  {
		if (!ConnectSocket(m_chatSocket, m_chatServerAddr))  {
			printf("IRC error: could not connect to the server " + addrStr);
			disconnect();
			return true;
		}

		// Connected
		m_socketConnected = true;
		m_socketIsReady = false;
		m_netBuffer.clear();
		m_authorizedState = AUTH_NONE;
	}

	// Check for socket readiness
	if (!IsSocketReady(m_chatSocket))
		return false;
	else
		m_socketIsReady = true;

	return true;
}


//////////////////////////
// Escapes special characters in the nick
void IRCClient::makeNickIRCFriendly()
{
	// Escape some symbols, make nick IRC-compatible
	replace(m_myNick, " ", "_" ); 
	#define S_LETTER_UPPER "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
	#define S_LETTER_LOWER "abcdefghijklmnopqrstuvwxyz"
	#define S_LETTER S_LETTER_UPPER S_LETTER_LOWER
	#define S_NUMBER "0123456789"
	#define S_SYMBOL "-[]\\`^{}_"

	// Length overflow
	if(m_myNick.size() > IRC_NICK_MAX_LEN)
		m_myNick.resize(IRC_NICK_MAX_LEN);

	// Empty nick
	if(m_myNick.empty())
		m_myNick = "z";

	// Nick starting with a number
	if(m_myNick.find_first_of(S_NUMBER) == 0 )
		*m_myNick.begin() = 'z';

	// Replace the dangerous characters
	while (m_myNick.find_first_not_of(S_LETTER S_NUMBER S_SYMBOL) != std::string::npos)
		m_myNick[m_myNick.find_first_not_of(S_LETTER S_NUMBER S_SYMBOL)] = '-';

	m_nickUniqueNumber = -1;
	m_updatingUserList = false;
}


/////////////////////////////
// Connect to server#channel
bool IRCClient::connect(const std::string &server, const std::string &channel, const std::string &nick)
{
	// Disconnect first
	if (m_socketOpened)
		disconnect();

	m_connecting = true;

	// Set the info
	m_chatServerAddrStr = server;
	m_chatServerChannel = channel;
	m_myNick = nick;

	// Initialize the network
	if (!initNet())
		return false;

	processConnecting();
	return true;
}

////////////////////////
// Process the IRC client (handle incoming messages etc.)
void IRCClient::process()
{
	// Re-connect once per 5 seconds
	if (!m_socketConnected && !m_connecting)  {
		if(tLX->fCurTime - m_connectionClosedTime >= 5.0f)  {
			m_connectionClosedTime = tLX->fCurTime;
			connect(m_chatServerAddrStr, m_chatServerChannel, m_myNick);
		}
		return;
	}

	// Connecting process
	if (!processConnecting())
		return;

	// Initiate server response
	if (m_authorizedState == AUTH_NONE)  {
		// Adjust the nickname
		if (m_myNick.empty())
			m_myNick = "OpenLieroXor";
		makeNickIRCFriendly();

		// Send the nick command
		sendNick();
		m_authorizedState = AUTH_NICK_SENT;
	}

	// Read data from the socket
	readData();
}

/////////////////////////
// Read data from the socket and process it
void IRCClient::readData()
{
	// Get data from the socket
	char buf[1024];

	int read = 1;
	while (read > 0)  {
		int read = ReadSocket(m_chatSocket, buf, sizeof(buf)); // HINT: non-blocking

		// Nothing yet
		if (read == 0)
			break;

		// Error
		if(read < 0)  {
			if (!IsMessageEndSocketErrorNr(GetSocketErrorNr()))  {
				printf("IRC: network error - " + GetSocketErrorStr(GetSocketErrorNr()) + "\n");
				disconnect();
			}
			break;
		}

		// Add the data to the buffer
		m_netBuffer.append(buf, read);
	}

	if (m_netBuffer.empty())
		return;

	size_t pos = m_netBuffer.find("\r\n");
	while(pos != std::string::npos)  {
		std::string line = m_netBuffer.substr(0, pos);

		//printf("IRC: %s\n", line.c_str());

		// Check if the sender is specified
		m_netBuffer.erase(0, pos + 2);
		IRCCommand cmd;
		if(line.size() && line[0] == ':')  {
			size_t pos2 = line.find(' ');
			cmd.sender = line.substr(1, pos2);
			line.erase(0, pos2 + 1);
		}

		// Get the command and parameters
		cmd.cmd = line.substr(0, line.find(' '));
		while (line.find(' ') != std::string::npos )  {
			line = line.substr(line.find(' ') + 1);
			if (line.size() == 0)
				break;
			if (line[0] == ':')  {
				cmd.params.push_back(line.substr(1));
				break;
			}

			cmd.params.push_back(line.substr(0, line.find(' ')));
		}

		// Parse the command
		parseCommand(cmd);

		pos = m_netBuffer.find("\r\n");
	}
}

///////////////////////
// Disconnect from the server
void IRCClient::disconnect()
{
	// Disconnect only when connected
	if (!m_socketOpened)
		return;

	// Call the disconnect callback
	if (m_disconnectCallback)
		m_disconnectCallback();

	// Close socket
	if (IsSocketReady(m_chatSocket))
		WriteSocket(m_chatSocket, "QUIT\r\n");
	if (m_socketConnected)
		CloseSocket(m_chatSocket);

	// Clear the variables
	m_socketConnected = false;
	m_socketIsReady = false;
	m_socketOpened = false;
	m_connecting = false;
	m_connectionClosedTime = tLX->fCurTime;
	InvalidateSocketState(m_chatSocket);
	SetNetAddrValid(m_chatServerAddr, false);
	m_netBuffer.clear();
	m_authorizedState = AUTH_NONE;
	m_nickUniqueNumber = -1;
	m_updatingUserList = false;
	m_chatUsers.clear();

	// Call the update user list callback
	if (m_updateUsersCallback)
		m_updateUsersCallback(m_chatUsers);

	printf("IRC: disconnected\n");
}

/*
 *
 * Sending part
 *
 */

///////////////
// Join a channel
void IRCClient::sendJoin()
{
	WriteSocket(m_chatSocket, "JOIN " + m_chatServerChannel + "\r\n");
	m_authorizedState = AUTH_JOINED_CHANNEL;
}


///////////////////
// Tell the server our nickname
void IRCClient::sendNick()
{
	if (m_nickUniqueNumber < 0)
		WriteSocket(m_chatSocket, "NICK " + m_myNick + "\r\n");
	else
		WriteSocket(m_chatSocket, "NICK " + m_myNick + itoa(m_nickUniqueNumber) + "\r\n");
}

///////////////////////
// Send a ping reply
void IRCClient::sendPong(const std::string &param)
{
	WriteSocket(m_chatSocket, "PONG :" + param + "\r\n");
}

///////////////////////
// Request nick list from the server
void IRCClient::sendRequestNames()
{
	WriteSocket(m_chatSocket, "NAMES " + m_chatServerChannel + "\r\n");
}

///////////////////////
// Sends user authentication to the server
void IRCClient::sendUserAuth()
{
	std::string nick = m_myNick;
	if (m_nickUniqueNumber >= 0)
		nick += itoa(m_nickUniqueNumber);
	WriteSocket(m_chatSocket, "USER " + nick + " ? ? :" + GetGameVersion().asString() + "\r\n");
	m_authorizedState = AUTH_USER_SENT;
}

/////////////////////////
// Send a message to the IRC channel, returns true if the chat has been sent
bool IRCClient::sendChat(const std::string &text)
{
	// Make sure we are connected
	if (!m_socketConnected || !m_socketIsReady || m_authorizedState != AUTH_JOINED_CHANNEL || text.empty())
		return false;

	// Send the text
	WriteSocket(m_chatSocket, "PRIVMSG " + m_chatServerChannel + " :" + text + "\r\n");
	//printf("Menu_Net_Chat_Send(): sent %s\n", text.c_str());

	// Route the same message back to our parser func, there's no echo in IRC
	IRCCommand cmd;
	cmd.sender = m_myNick;
	cmd.cmd = "PRIVMSG";
	cmd.params.push_back(m_chatServerChannel);
	cmd.params.push_back(text);
	parseCommand(cmd);

	return true;	
}


/*
 *
 * Parsing part
 *
 */

/////////////////////
// End of connection
void IRCClient::parseDropped(const IRCClient::IRCCommand &cmd)
{
	disconnect();
}

///////////////////
// Kicked out of the server
void IRCClient::parseKicked(const IRCClient::IRCCommand &cmd)
{
	// Get our real nick (add the unique number) for comparison
	std::string real_nick = m_myNick;
	if (m_nickUniqueNumber >= 0)
		real_nick += itoa(m_nickUniqueNumber);

	// Invalid command
	if (cmd.params.size() < 2)
		return;

	// Check that it was us who gets kicked
	if (cmd.params[1] != m_myNick)
		return;

	// Get the kick reason
	std::string reason = "No reason was given";
	if (cmd.params.size() >= 3)
		reason = cmd.params[2];

	// Inform the user
	addChatMessage("You have been kicked: " + reason, IRC_TEXT_NOTICE);

	// Disconnect
	disconnect();
}

///////////////////
// End of name list
void IRCClient::parseEndOfNames(const IRCClient::IRCCommand &cmd)
{
	m_updatingUserList = false;
	m_chatUsers.sort();
}

////////////////////
// Join successful
void IRCClient::parseJoin(const IRCClient::IRCCommand& cmd)
{
	m_connecting = false;

	// Re-request the nick names
	sendRequestNames();

	// Callback
	if (m_connectCallback)
		m_connectCallback();

	printf("IRC connected to " + m_chatServerChannel + "@" + m_chatServerAddrStr + "\n");
}

/////////////////////
// Parse a mode packet
void IRCClient::parseMode(const IRCClient::IRCCommand &cmd)
{
	if (m_authorizedState == AUTH_USER_SENT)  {
		sendJoin();
	}
}

///////////////////////
// List of the nicknames in the channel
void IRCClient::parseNameReply(const IRCClient::IRCCommand &cmd)
{
	// Check
	if(cmd.params.size() < 4 )
		return;

	// If this is the first reply packet, clear the user list
	if(!m_updatingUserList )
		m_chatUsers.clear();

	m_updatingUserList = true;
	
	// Get the nick names
	StringBuf line(cmd.params[3]);
	std::vector<std::string> nicks = line.splitBy(' ');
	
	for (std::vector<std::string>::iterator it = nicks.begin(); it != nicks.end(); it++)  {
		// Check for validity
		std::string& user = *it;
		if (user.size() == 0)
			continue;

		if (user[0] == '@') // Channel Operator
			user.erase(0, 1);

		m_chatUsers.push_back(user); // Add the user to the list
	}

	// Callback
	if (m_updateUsersCallback)
		m_updateUsersCallback(m_chatUsers);
}

////////////////////
// Parse the NICK command
void IRCClient::parseNick(const IRCClient::IRCCommand &cmd)
{
	sendRequestNames(); // Re-request the nicknames
}

//////////////////////
// The nickname is in use
void IRCClient::parseNickInUse(const IRCClient::IRCCommand &cmd)
{
	++m_nickUniqueNumber;

	// Make sure we don't overflow the maximum allowed nick length
	if(m_myNick.size() + itoa(m_nickUniqueNumber).size() > IRC_NICK_MAX_LEN )
		m_myNick.resize(IRC_NICK_MAX_LEN - itoa(m_nickUniqueNumber).size());

	sendNick();
}

///////////////////////
// Ping
void IRCClient::parsePing(const IRCClient::IRCCommand &cmd)
{
	// Check
	if (cmd.params.size() == 0)
		return;

	// Send reply
	sendPong(cmd.params[0]);

	// Progress with authorisation if not yet connected
	if (m_authorizedState == AUTH_NICK_SENT)
		sendUserAuth();
}

///////////////////////
// Private message
void IRCClient::parsePrivmsg(const IRCClient::IRCCommand &cmd)
{
	// Check
	if (cmd.params.size() < 2 )
		return;

	// Add the message
	std::string nick = cmd.sender.substr(0, cmd.sender.find('!'));
	std::string text;

	IRCTextType type = IRC_TEXT_CHAT;
	if (cmd.params[1].size() && cmd.params[1][0] == '\1' && *cmd.params[1].rbegin() == '\1')  { // Action message
		text = cmd.params[1].substr(1, cmd.params[1].size() - 2);
		replace(text, "ACTION", nick);
		type = IRC_TEXT_ACTION;
	} else
		text = nick + ": " + cmd.params[1];
	addChatMessage(text, type);
}

///////////////////////
// Notice
void IRCClient::parseNotice(const IRCClient::IRCCommand &cmd)
{
	// Notice is actually a chat message

	// Check
	if (cmd.params.size() < 2 )
		return;

	// Ignore any notices before joining the channel (they are more or less some server spam)
	if (m_authorizedState != AUTH_JOINED_CHANNEL)
		return;

	// Get the nick
	std::string nick = cmd.sender.substr(0, cmd.sender.find('!'));

	// Add the message
	std::string text;
	if (nick.size())
		text = nick + ": " + cmd.params[1];
	else
		text = cmd.params[1];
	addChatMessage(text, IRC_TEXT_NOTICE);
}


///////////////////////////
// Fatal error from the server
void IRCClient::parseError(const IRCClient::IRCCommand &cmd)
{
	if (cmd.params.size() > 0)  {
		printf("IRC server error: " + cmd.params[0] + "\n");
		addChatMessage("Server error: " + cmd.params[0], IRC_TEXT_NOTICE);
		disconnect();
	}
}

//////////////////////////////
// Parse an IRC command (private)
void IRCClient::parseCommand(const IRCClient::IRCCommand &cmd)
{
	
	/*printf("IRC: sender '%s' cmd '%s'", cmd.sender.c_str(), cmd.cmd.c_str() );
	for( size_t i=0; i<cmd.params.size(); i++ )
		printf(" param %i '%s'", i, cmd.params[i].c_str());
	printf("\n");*/
	

	// Process depending on the command

	bool fail = false;
	int num_command = from_string<int>(cmd.cmd, fail);

	// String commands
	if (fail)  {
		if (cmd.cmd == "PING")
			parsePing(cmd);

		else if (cmd.cmd == "MODE")
			parseMode(cmd);

		else if (cmd.cmd == "PRIVMSG")
			parsePrivmsg(cmd);

		else if (cmd.cmd == "KICK")
			parseKicked(cmd);

		else if (cmd.cmd == "PART" || cmd.cmd == "QUIT")
			parseDropped(cmd);

		else if (cmd.cmd == "JOIN")
			parseJoin(cmd);

		else if (cmd.cmd == "NICK")
			parseNick(cmd);

		else if (cmd.cmd == "NOTICE")
			parseNotice(cmd);

		else if (cmd.cmd == "ERROR")
			parseError(cmd);

		else
			printf("IRC: unknown command " + cmd.cmd + "\n");

	// Numeric commands
	} else {
		switch (num_command)  {

		// Nick in use
		case LIBIRC_RFC_ERR_NICKNAMEINUSE:
			parseNickInUse(cmd);
			break;

		// List of names
		case LIBIRC_RFC_RPL_NAMREPLY:
			parseNameReply(cmd);
			break;

		// End of name list
		case LIBIRC_RFC_RPL_ENDOFNAMES:
			parseEndOfNames(cmd);
			break;

		// Message of the day
		case LIBIRC_RFC_RPL_MOTDSTART:
		case LIBIRC_RFC_RPL_MOTD:
		case LIBIRC_RFC_RPL_ENDOFMOTD:
			// Message of the day (ignored currently)
			break;

		// Messages sent upon a successful join
		case LIBIRC_RFC_RPL_WELCOME:
		case LIBIRC_RFC_RPL_YOURHOST:
		case LIBIRC_RFC_RPL_CREATED:
		case LIBIRC_RFC_RPL_MYINFO:
			// Quite useless stuff...
			break;

		default: {}
			// Just ignore, there are many "pro" commands that we don't need
		}
	}
}


/*
 *
 * Global stuff
 *
 */

IRCClient *globalIRC = NULL;

/////////////////////////
// Initializes the IRC client and connects to the server (specified in options)
bool InitializeIRC()
{
	// Already initialized?
	if (globalIRC)
		return true;

	if (!tLXOptions->bEnableChat)
		return false;

	// Allocate the IRC client
	try  {
		globalIRC = new IRCClient();
	} catch (...)  {
		return false;
	}

	// Get the server
	FILE *fp = OpenGameFile("cfg/chatserver.txt", "r");
	if (fp)  {
		std::string addr = ReadUntil(fp, '/');
		std::string chann = ReadUntil(fp, '\n');
		fclose(fp);
		return globalIRC->connect(addr, chann, tLXOptions->tGameinfo.sLastSelectedPlayer);
	} else { // Defaults
		return globalIRC->connect("irc.quakenet.org", "#LieroX", tLXOptions->tGameinfo.sLastSelectedPlayer);
	}
}

/////////////////////////
// Disconnects the IRC client and does all the cleanup
void ShutdownIRC()
{
	if (globalIRC)
		delete globalIRC;
	globalIRC = NULL;
}

////////////////////////
// Returns an instance of the global IRC client
IRCClient *GetGlobalIRC()
{
	return globalIRC;
}

/////////////////////////
// Handles processing of the global IRC
void ProcessIRC()
{
	if (globalIRC)
		globalIRC->process();
}
<commit_msg>Fixes for IRC: remove connection info from sender and handle QUIT/PART commands correctly<commit_after>/////////////////////////////////////////
//
//             OpenLieroX
//
// code under LGPL, based on JasonBs work,
// enhanced by <NAME> and <NAME>
//
//
/////////////////////////////////////////


// IRC client class
// <NAME>

#include <iostream>
#include "IRC.h"
#include "LieroX.h"
#include "Options.h"
#include "Version.h"
#include "StringBuf.h"
#include "StringUtils.h"
#include "Event.h"
#include "libirc_rfcnumeric.h"


/////////////////////////
// Initialize the IRC networking things (private)
bool IRCClient::initNet()
{
	// Check if already inited
	if(m_socketOpened)
		return true;
	
	// Open the socket
	m_chatSocket = OpenReliableSocket(0);
	if (!IsSocketStateValid(m_chatSocket))  {
		printf("ERROR: Could not open a socket for IRC networking: " + GetSocketErrorStr(GetSocketErrorNr()) + "\n");
		return false;
	}

	m_socketOpened = true;

	// Get the address
	ResetNetAddr(m_chatServerAddr);
	if(!GetNetAddrFromNameAsync(m_chatServerAddrStr, m_chatServerAddr))	 {
		printf("ERROR: Wrong IRC server addr: %s" + m_chatServerAddrStr + "\n");
		return false;
	}

	return true;
}


///////////////////////////
// Add a chat message and call the "on update" event
void IRCClient::addChatMessage(const std::string &msg, IRCTextType type)
{
	// Add the text
	m_chatText.push_back(IRCChatLine(msg, type));

	// Get rid of old messages
	while (m_chatText.size() > 1024)
		m_chatText.pop_front();

	// Run the event function
	if (m_newMsgCallback)
		m_newMsgCallback(msg, (int)type);
}

///////////////////////
// Connecting process, returns true if finished
bool IRCClient::processConnecting()
{
	// If connected, just quit
	if (m_authorizedState != AUTH_NONE)
		return true;

	// Check for DNS resolution
	if (!IsNetAddrValid(m_chatServerAddr))
		return false;

	// Convert the address to string
	std::string addrStr;
	NetAddrToString(m_chatServerAddr, addrStr);
	
	// Add IRC port
	SetNetAddrPort(m_chatServerAddr, IRC_PORT );

	// Connect
	if (!m_socketConnected)  {
		if (!ConnectSocket(m_chatSocket, m_chatServerAddr))  {
			printf("IRC error: could not connect to the server " + addrStr);
			disconnect();
			return true;
		}

		// Connected
		m_socketConnected = true;
		m_socketIsReady = false;
		m_netBuffer.clear();
		m_authorizedState = AUTH_NONE;
	}

	// Check for socket readiness
	if (!IsSocketReady(m_chatSocket))
		return false;
	else
		m_socketIsReady = true;

	return true;
}


//////////////////////////
// Escapes special characters in the nick
void IRCClient::makeNickIRCFriendly()
{
	// Escape some symbols, make nick IRC-compatible
	replace(m_myNick, " ", "_" ); 
	#define S_LETTER_UPPER "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
	#define S_LETTER_LOWER "abcdefghijklmnopqrstuvwxyz"
	#define S_LETTER S_LETTER_UPPER S_LETTER_LOWER
	#define S_NUMBER "0123456789"
	#define S_SYMBOL "-[]\\`^{}_"

	// Length overflow
	if(m_myNick.size() > IRC_NICK_MAX_LEN)
		m_myNick.resize(IRC_NICK_MAX_LEN);

	// Empty nick
	if(m_myNick.empty())
		m_myNick = "z";

	// Nick starting with a number
	if(m_myNick.find_first_of(S_NUMBER) == 0 )
		*m_myNick.begin() = 'z';

	// Replace the dangerous characters
	while (m_myNick.find_first_not_of(S_LETTER S_NUMBER S_SYMBOL) != std::string::npos)
		m_myNick[m_myNick.find_first_not_of(S_LETTER S_NUMBER S_SYMBOL)] = '-';

	m_nickUniqueNumber = -1;
	m_updatingUserList = false;
}


/////////////////////////////
// Connect to server#channel
bool IRCClient::connect(const std::string &server, const std::string &channel, const std::string &nick)
{
	// Disconnect first
	if (m_socketOpened)
		disconnect();

	m_connecting = true;

	// Set the info
	m_chatServerAddrStr = server;
	m_chatServerChannel = channel;
	m_myNick = nick;

	// Initialize the network
	if (!initNet())
		return false;

	processConnecting();
	return true;
}

////////////////////////
// Process the IRC client (handle incoming messages etc.)
void IRCClient::process()
{
	// Re-connect once per 5 seconds
	if (!m_socketConnected && !m_connecting)  {
		if(tLX->fCurTime - m_connectionClosedTime >= 5.0f)  {
			m_connectionClosedTime = tLX->fCurTime;
			connect(m_chatServerAddrStr, m_chatServerChannel, m_myNick);
		}
		return;
	}

	// Connecting process
	if (!processConnecting())
		return;

	// Initiate server response
	if (m_authorizedState == AUTH_NONE)  {
		// Adjust the nickname
		if (m_myNick.empty())
			m_myNick = "OpenLieroXor";
		makeNickIRCFriendly();

		// Send the nick command
		sendNick();
		m_authorizedState = AUTH_NICK_SENT;
	}

	// Read data from the socket
	readData();
}

/////////////////////////
// Read data from the socket and process it
void IRCClient::readData()
{
	// Get data from the socket
	char buf[1024];

	int read = 1;
	while (read > 0)  {
		int read = ReadSocket(m_chatSocket, buf, sizeof(buf)); // HINT: non-blocking

		// Nothing yet
		if (read == 0)
			break;

		// Error
		if(read < 0)  {
			if (!IsMessageEndSocketErrorNr(GetSocketErrorNr()))  {
				printf("IRC: network error - " + GetSocketErrorStr(GetSocketErrorNr()) + "\n");
				disconnect();
			}
			break;
		}

		// Add the data to the buffer
		m_netBuffer.append(buf, read);
	}

	if (m_netBuffer.empty())
		return;

	size_t pos = m_netBuffer.find("\r\n");
	while(pos != std::string::npos)  {
		std::string line = m_netBuffer.substr(0, pos);

		//printf("IRC: %s\n", line.c_str());

		// Check if the sender is specified
		m_netBuffer.erase(0, pos + 2);
		IRCCommand cmd;
		if(line.size() && line[0] == ':')  {
			size_t pos2 = line.find(' ');
			cmd.sender = line.substr(1, pos2);

			// Check for exclamation mark in the nick as a special character
			size_t excl_pos = cmd.sender.find('!');
			if (excl_pos != std::string::npos)  {
				cmd.sender.erase(excl_pos); // Erase anything (usually IP + ISP info) from the nick
			}

			line.erase(0, pos2 + 1);
		}

		// Get the command and parameters
		cmd.cmd = line.substr(0, line.find(' '));
		while (line.find(' ') != std::string::npos )  {
			line = line.substr(line.find(' ') + 1);
			if (line.size() == 0)
				break;
			if (line[0] == ':')  {
				cmd.params.push_back(line.substr(1));
				break;
			}

			cmd.params.push_back(line.substr(0, line.find(' ')));
		}

		// Parse the command
		parseCommand(cmd);

		pos = m_netBuffer.find("\r\n");
	}
}

///////////////////////
// Disconnect from the server
void IRCClient::disconnect()
{
	// Disconnect only when connected
	if (!m_socketOpened)
		return;

	// Call the disconnect callback
	if (m_disconnectCallback)
		m_disconnectCallback();

	// Close socket
	if (IsSocketReady(m_chatSocket))
		WriteSocket(m_chatSocket, "QUIT\r\n");
	if (m_socketConnected)
		CloseSocket(m_chatSocket);

	// Clear the variables
	m_socketConnected = false;
	m_socketIsReady = false;
	m_socketOpened = false;
	m_connecting = false;
	m_connectionClosedTime = tLX->fCurTime;
	InvalidateSocketState(m_chatSocket);
	SetNetAddrValid(m_chatServerAddr, false);
	m_netBuffer.clear();
	m_authorizedState = AUTH_NONE;
	m_nickUniqueNumber = -1;
	m_updatingUserList = false;
	m_chatUsers.clear();

	// Call the update user list callback
	if (m_updateUsersCallback)
		m_updateUsersCallback(m_chatUsers);

	printf("IRC: disconnected\n");
}

/*
 *
 * Sending part
 *
 */

///////////////
// Join a channel
void IRCClient::sendJoin()
{
	WriteSocket(m_chatSocket, "JOIN " + m_chatServerChannel + "\r\n");
	m_authorizedState = AUTH_JOINED_CHANNEL;
}


///////////////////
// Tell the server our nickname
void IRCClient::sendNick()
{
	if (m_nickUniqueNumber < 0)
		WriteSocket(m_chatSocket, "NICK " + m_myNick + "\r\n");
	else
		WriteSocket(m_chatSocket, "NICK " + m_myNick + itoa(m_nickUniqueNumber) + "\r\n");
}

///////////////////////
// Send a ping reply
void IRCClient::sendPong(const std::string &param)
{
	WriteSocket(m_chatSocket, "PONG :" + param + "\r\n");
}

///////////////////////
// Request nick list from the server
void IRCClient::sendRequestNames()
{
	WriteSocket(m_chatSocket, "NAMES " + m_chatServerChannel + "\r\n");
}

///////////////////////
// Sends user authentication to the server
void IRCClient::sendUserAuth()
{
	std::string nick = m_myNick;
	if (m_nickUniqueNumber >= 0)
		nick += itoa(m_nickUniqueNumber);
	WriteSocket(m_chatSocket, "USER " + nick + " ? ? :" + GetGameVersion().asString() + "\r\n");
	m_authorizedState = AUTH_USER_SENT;
}

/////////////////////////
// Send a message to the IRC channel, returns true if the chat has been sent
bool IRCClient::sendChat(const std::string &text)
{
	// Make sure we are connected
	if (!m_socketConnected || !m_socketIsReady || m_authorizedState != AUTH_JOINED_CHANNEL || text.empty())
		return false;

	// Send the text
	WriteSocket(m_chatSocket, "PRIVMSG " + m_chatServerChannel + " :" + text + "\r\n");
	//printf("Menu_Net_Chat_Send(): sent %s\n", text.c_str());

	// Route the same message back to our parser func, there's no echo in IRC
	IRCCommand cmd;
	cmd.sender = m_myNick;
	cmd.cmd = "PRIVMSG";
	cmd.params.push_back(m_chatServerChannel);
	cmd.params.push_back(text);
	parseCommand(cmd);

	return true;	
}


/*
 *
 * Parsing part
 *
 */

/////////////////////
// End of connection
void IRCClient::parseDropped(const IRCClient::IRCCommand &cmd)
{
	// Get our real nick (add the unique number) for comparison
	std::string real_nick = m_myNick;
	if (m_nickUniqueNumber >= 0)
		real_nick += itoa(m_nickUniqueNumber);

	// Make sure the quit is directed to us (if not, someone else has been dropped/has left)
	if (cmd.sender == real_nick)
		disconnect();
}

///////////////////
// Kicked out of the server
void IRCClient::parseKicked(const IRCClient::IRCCommand &cmd)
{
	// Get our real nick (add the unique number) for comparison
	std::string real_nick = m_myNick;
	if (m_nickUniqueNumber >= 0)
		real_nick += itoa(m_nickUniqueNumber);

	// Invalid command
	if (cmd.params.size() < 2)
		return;

	// Check that it was us who gets kicked
	if (cmd.params[1] != m_myNick)
		return;

	// Get the kick reason
	std::string reason = "No reason was given";
	if (cmd.params.size() >= 3)
		reason = cmd.params[2];

	// Inform the user
	addChatMessage("You have been kicked: " + reason, IRC_TEXT_NOTICE);

	// Disconnect
	disconnect();
}

///////////////////
// End of name list
void IRCClient::parseEndOfNames(const IRCClient::IRCCommand &cmd)
{
	m_updatingUserList = false;
	m_chatUsers.sort();
}

////////////////////
// Join successful
void IRCClient::parseJoin(const IRCClient::IRCCommand& cmd)
{
	m_connecting = false;

	// Re-request the nick names
	sendRequestNames();

	// Callback
	if (m_connectCallback)
		m_connectCallback();

	printf("IRC connected to " + m_chatServerChannel + "@" + m_chatServerAddrStr + "\n");
}

/////////////////////
// Parse a mode packet
void IRCClient::parseMode(const IRCClient::IRCCommand &cmd)
{
	if (m_authorizedState == AUTH_USER_SENT)  {
		sendJoin();
	}
}

///////////////////////
// List of the nicknames in the channel
void IRCClient::parseNameReply(const IRCClient::IRCCommand &cmd)
{
	// Check
	if(cmd.params.size() < 4 )
		return;

	// If this is the first reply packet, clear the user list
	if(!m_updatingUserList )
		m_chatUsers.clear();

	m_updatingUserList = true;
	
	// Get the nick names
	StringBuf line(cmd.params[3]);
	std::vector<std::string> nicks = line.splitBy(' ');
	
	for (std::vector<std::string>::iterator it = nicks.begin(); it != nicks.end(); it++)  {
		// Check for validity
		std::string& user = *it;
		if (user.size() == 0)
			continue;

		if (user[0] == '@') // Channel Operator
			user.erase(0, 1);

		m_chatUsers.push_back(user); // Add the user to the list
	}

	// Callback
	if (m_updateUsersCallback)
		m_updateUsersCallback(m_chatUsers);
}

////////////////////
// Parse the NICK command
void IRCClient::parseNick(const IRCClient::IRCCommand &cmd)
{
	sendRequestNames(); // Re-request the nicknames
}

//////////////////////
// The nickname is in use
void IRCClient::parseNickInUse(const IRCClient::IRCCommand &cmd)
{
	++m_nickUniqueNumber;

	// Make sure we don't overflow the maximum allowed nick length
	if(m_myNick.size() + itoa(m_nickUniqueNumber).size() > IRC_NICK_MAX_LEN )
		m_myNick.resize(IRC_NICK_MAX_LEN - itoa(m_nickUniqueNumber).size());

	sendNick();
}

///////////////////////
// Ping
void IRCClient::parsePing(const IRCClient::IRCCommand &cmd)
{
	// Check
	if (cmd.params.size() == 0)
		return;

	// Send reply
	sendPong(cmd.params[0]);

	// Progress with authorisation if not yet connected
	if (m_authorizedState == AUTH_NICK_SENT)
		sendUserAuth();
}

///////////////////////
// Private message
void IRCClient::parsePrivmsg(const IRCClient::IRCCommand &cmd)
{
	// Check
	if (cmd.params.size() < 2 )
		return;

	// Add the message
	std::string nick = cmd.sender.substr(0, cmd.sender.find('!'));
	std::string text;

	IRCTextType type = IRC_TEXT_CHAT;
	if (cmd.params[1].size() && cmd.params[1][0] == '\1' && *cmd.params[1].rbegin() == '\1')  { // Action message
		text = cmd.params[1].substr(1, cmd.params[1].size() - 2);
		replace(text, "ACTION", nick);
		type = IRC_TEXT_ACTION;
	} else
		text = nick + ": " + cmd.params[1];
	addChatMessage(text, type);
}

///////////////////////
// Notice
void IRCClient::parseNotice(const IRCClient::IRCCommand &cmd)
{
	// Notice is actually a chat message

	// Check
	if (cmd.params.size() < 2 )
		return;

	// Ignore any notices before joining the channel (they are more or less some server spam)
	if (m_authorizedState != AUTH_JOINED_CHANNEL)
		return;

	// Get the nick
	std::string nick = cmd.sender.substr(0, cmd.sender.find('!'));

	// Add the message
	std::string text;
	if (nick.size())
		text = nick + ": " + cmd.params[1];
	else
		text = cmd.params[1];
	addChatMessage(text, IRC_TEXT_NOTICE);
}


///////////////////////////
// Fatal error from the server
void IRCClient::parseError(const IRCClient::IRCCommand &cmd)
{
	if (cmd.params.size() > 0)  {
		printf("IRC server error: " + cmd.params[0] + "\n");
		addChatMessage("Server error: " + cmd.params[0], IRC_TEXT_NOTICE);
		disconnect();
	}
}

//////////////////////////////
// Parse an IRC command (private)
void IRCClient::parseCommand(const IRCClient::IRCCommand &cmd)
{
	
	/*printf("IRC: sender '%s' cmd '%s'", cmd.sender.c_str(), cmd.cmd.c_str() );
	for( size_t i=0; i<cmd.params.size(); i++ )
		printf(" param %i '%s'", i, cmd.params[i].c_str());
	printf("\n");*/
	

	// Process depending on the command

	bool fail = false;
	int num_command = from_string<int>(cmd.cmd, fail);

	// String commands
	if (fail)  {
		if (cmd.cmd == "PING")
			parsePing(cmd);

		else if (cmd.cmd == "MODE")
			parseMode(cmd);

		else if (cmd.cmd == "PRIVMSG")
			parsePrivmsg(cmd);

		else if (cmd.cmd == "KICK")
			parseKicked(cmd);

		else if (cmd.cmd == "PART" || cmd.cmd == "QUIT")
			parseDropped(cmd);

		else if (cmd.cmd == "JOIN")
			parseJoin(cmd);

		else if (cmd.cmd == "NICK")
			parseNick(cmd);

		else if (cmd.cmd == "NOTICE")
			parseNotice(cmd);

		else if (cmd.cmd == "ERROR")
			parseError(cmd);

		else
			printf("IRC: unknown command " + cmd.cmd + "\n");

	// Numeric commands
	} else {
		switch (num_command)  {

		// Nick in use
		case LIBIRC_RFC_ERR_NICKNAMEINUSE:
			parseNickInUse(cmd);
			break;

		// List of names
		case LIBIRC_RFC_RPL_NAMREPLY:
			parseNameReply(cmd);
			break;

		// End of name list
		case LIBIRC_RFC_RPL_ENDOFNAMES:
			parseEndOfNames(cmd);
			break;

		// Message of the day
		case LIBIRC_RFC_RPL_MOTDSTART:
		case LIBIRC_RFC_RPL_MOTD:
		case LIBIRC_RFC_RPL_ENDOFMOTD:
			// Message of the day (ignored currently)
			break;

		// Messages sent upon a successful join
		case LIBIRC_RFC_RPL_WELCOME:
		case LIBIRC_RFC_RPL_YOURHOST:
		case LIBIRC_RFC_RPL_CREATED:
		case LIBIRC_RFC_RPL_MYINFO:
			// Quite useless stuff...
			break;

		default: {}
			// Just ignore, there are many "pro" commands that we don't need
		}
	}
}


/*
 *
 * Global stuff
 *
 */

IRCClient *globalIRC = NULL;

/////////////////////////
// Initializes the IRC client and connects to the server (specified in options)
bool InitializeIRC()
{
	// Already initialized?
	if (globalIRC)
		return true;

	if (!tLXOptions->bEnableChat)
		return false;

	// Allocate the IRC client
	try  {
		globalIRC = new IRCClient();
	} catch (...)  {
		return false;
	}

	// Get the server
	FILE *fp = OpenGameFile("cfg/chatserver.txt", "r");
	if (fp)  {
		std::string addr = ReadUntil(fp, '/');
		std::string chann = ReadUntil(fp, '\n');
		fclose(fp);
		return globalIRC->connect(addr, chann, tLXOptions->tGameinfo.sLastSelectedPlayer);
	} else { // Defaults
		return globalIRC->connect("irc.quakenet.org", "#LieroX", tLXOptions->tGameinfo.sLastSelectedPlayer);
	}
}

/////////////////////////
// Disconnects the IRC client and does all the cleanup
void ShutdownIRC()
{
	if (globalIRC)
		delete globalIRC;
	globalIRC = NULL;
}

////////////////////////
// Returns an instance of the global IRC client
IRCClient *GetGlobalIRC()
{
	return globalIRC;
}

/////////////////////////
// Handles processing of the global IRC
void ProcessIRC()
{
	if (globalIRC)
		globalIRC->process();
}

{'fraction_non_alphanumeric': 0.17627168051304476, 'fraction_numerical': 0.0047514939513190494, 'mean_word_length': 2.818566340160285, 'pattern_counts': {'":': 4, '<': 31, '<?xml version=': 0, '>': 54, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3580040', 'n_tokens_mistral': 13043, 'n_tokens_neox': 11863, 'n_words': 2816}