forked from Mirrors/openclonk
832 lines
23 KiB
C++
832 lines
23 KiB
C++
/*
|
|
* OpenClonk, http://www.openclonk.org
|
|
*
|
|
* Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/
|
|
* Copyright (c) 2009-2013, The OpenClonk Team and contributors
|
|
*
|
|
* Distributed under the terms of the ISC license; see accompanying file
|
|
* "COPYING" for details.
|
|
*
|
|
* "Clonk" is a registered trademark of Matthes Bender, used with permission.
|
|
* See accompanying file "TRADEMARK" for details.
|
|
*
|
|
* To redistribute this file separately, substitute the full license texts
|
|
* for the above references.
|
|
*/
|
|
|
|
#include "C4Include.h"
|
|
#include "C4Network2IRC.h"
|
|
|
|
#include <C4Application.h>
|
|
#include "C4Config.h"
|
|
#include "C4Version.h"
|
|
#include "C4InteractiveThread.h"
|
|
#include "C4Gui.h" // for clearly visi
|
|
|
|
#include <cctype> // for isdigit
|
|
|
|
// Helper for IRC command parameter parsing
|
|
StdStrBuf ircExtractPar(const char **ppPar)
|
|
{
|
|
// No parameter left?
|
|
if (!ppPar || !*ppPar || !**ppPar)
|
|
return StdStrBuf("");
|
|
// Last parameter?
|
|
StdStrBuf Result;
|
|
if (**ppPar == ':')
|
|
{
|
|
// Reference everything after the double-colon
|
|
Result.Ref(*ppPar + 1);
|
|
*ppPar = NULL;
|
|
}
|
|
else
|
|
{
|
|
// Copy until next space (or end of string)
|
|
Result.CopyUntil(*ppPar, ' ');
|
|
// Go over parameters
|
|
*ppPar += Result.getLength();
|
|
if (**ppPar == ' ')
|
|
(*ppPar)++;
|
|
else
|
|
*ppPar = NULL;
|
|
}
|
|
// Done
|
|
return Result;
|
|
}
|
|
|
|
// *** C4Network2IRCUser
|
|
|
|
C4Network2IRCUser::C4Network2IRCUser(const char *szName)
|
|
: Name(szName)
|
|
{
|
|
|
|
}
|
|
|
|
// *** C4Network2IRCChannel
|
|
|
|
C4Network2IRCChannel::C4Network2IRCChannel(const char *szName)
|
|
: Name(szName), pUsers(NULL), fReceivingUsers(false)
|
|
{
|
|
|
|
}
|
|
|
|
C4Network2IRCChannel::~C4Network2IRCChannel()
|
|
{
|
|
ClearUsers();
|
|
}
|
|
|
|
C4Network2IRCUser *C4Network2IRCChannel::getUser(const char *szName) const
|
|
{
|
|
for (C4Network2IRCUser *pUser = pUsers; pUser; pUser = pUser->Next)
|
|
if (SEqual(pUser->getName(), szName))
|
|
return pUser;
|
|
return NULL;
|
|
}
|
|
|
|
void C4Network2IRCChannel::OnUsers(const char *szUsers, const char *szPrefixes)
|
|
{
|
|
// Find actual prefixes
|
|
szPrefixes = SSearch(szPrefixes, ")");
|
|
// Reconstructs the list
|
|
if (!fReceivingUsers)
|
|
ClearUsers();
|
|
while (szUsers && *szUsers)
|
|
{
|
|
// Get user name
|
|
StdStrBuf PrefixedName = ircExtractPar(&szUsers);
|
|
// Remove prefix(es)
|
|
const char *szName = PrefixedName.getData();
|
|
if (szPrefixes)
|
|
while (strchr(szPrefixes, *szName))
|
|
szName++;
|
|
// Copy prefix
|
|
StdStrBuf Prefix;
|
|
Prefix.Copy(PrefixedName.getData(), szName - PrefixedName.getData());
|
|
// Add user
|
|
AddUser(szName)->SetPrefix(Prefix.getData());
|
|
}
|
|
// Set flag the user list won't get cleared again until OnUsersEnd is called
|
|
fReceivingUsers = true;
|
|
}
|
|
|
|
void C4Network2IRCChannel::OnUsersEnd()
|
|
{
|
|
// Reset flag
|
|
fReceivingUsers = false;
|
|
}
|
|
|
|
void C4Network2IRCChannel::OnTopic(const char *szTopic)
|
|
{
|
|
Topic = szTopic;
|
|
}
|
|
|
|
void C4Network2IRCChannel::OnKick(const char *szUser, const char *szComment)
|
|
{
|
|
// Remove named user from channel list
|
|
C4Network2IRCUser *pUser = getUser(szUser);
|
|
if (pUser) DeleteUser(pUser);
|
|
}
|
|
|
|
void C4Network2IRCChannel::OnPart(const char *szUser, const char *szComment)
|
|
{
|
|
// Remove named user from channel list
|
|
C4Network2IRCUser *pUser = getUser(szUser);
|
|
if (pUser) DeleteUser(pUser);
|
|
}
|
|
|
|
void C4Network2IRCChannel::OnJoin(const char *szUser)
|
|
{
|
|
// Add user (do not set prefix)
|
|
if (!getUser(szUser))
|
|
AddUser(szUser);
|
|
}
|
|
|
|
C4Network2IRCUser *C4Network2IRCChannel::AddUser(const char *szName)
|
|
{
|
|
// Check if the user already exists
|
|
C4Network2IRCUser *pUser = getUser(szName);
|
|
if (pUser) return pUser;
|
|
// Add to list
|
|
pUser = new C4Network2IRCUser(szName);
|
|
pUser->Next = pUsers;
|
|
pUsers = pUser;
|
|
return pUser;
|
|
}
|
|
|
|
void C4Network2IRCChannel::DeleteUser(C4Network2IRCUser *pUser)
|
|
{
|
|
// Unlink
|
|
if (pUser == pUsers)
|
|
pUsers = pUser->Next;
|
|
else
|
|
{
|
|
C4Network2IRCUser *pPrev = pUsers;
|
|
while (pPrev && pPrev->Next != pUser)
|
|
pPrev = pPrev->Next;
|
|
if (pPrev)
|
|
pPrev->Next = pUser->Next;
|
|
}
|
|
// Delete
|
|
delete pUser;
|
|
}
|
|
|
|
void C4Network2IRCChannel::ClearUsers()
|
|
{
|
|
while (pUsers)
|
|
DeleteUser(pUsers);
|
|
}
|
|
|
|
|
|
// *** C4Network2IRCClient
|
|
// Created statically in C4Application.cpp, refer by &Application.IRCClient
|
|
|
|
C4Network2IRCClient::C4Network2IRCClient()
|
|
: fConnecting(false), fConnected(false),
|
|
pChannels(NULL),
|
|
pLog(NULL), pLogEnd(NULL), iLogLength(0), iUnreadLogLength(0),
|
|
pNotify(NULL)
|
|
{
|
|
|
|
}
|
|
|
|
C4Network2IRCClient::~C4Network2IRCClient()
|
|
{
|
|
Close();
|
|
}
|
|
|
|
void C4Network2IRCClient::PackPacket(const C4NetIOPacket &rPacket, StdBuf &rOutBuf)
|
|
{
|
|
// Enlarge buffer
|
|
int iSize = rPacket.getSize(),
|
|
iPos = rOutBuf.getSize();
|
|
rOutBuf.Grow(iSize + 2);
|
|
// Write packet
|
|
rOutBuf.Write(rPacket, iPos);
|
|
// Terminate
|
|
uint8_t *pPos = getMBufPtr<uint8_t>(rOutBuf, iPos + iSize);
|
|
*pPos = '\r'; *(pPos + 1) = '\n';
|
|
}
|
|
|
|
size_t C4Network2IRCClient::UnpackPacket(const StdBuf &rInBuf, const C4NetIO::addr_t &addr)
|
|
{
|
|
// Find line separation
|
|
const char *pSep = reinterpret_cast<const char *>(memchr(rInBuf.getData(), '\n', rInBuf.getSize()));
|
|
if (!pSep)
|
|
return 0;
|
|
// Check if it's actually correct separation (rarely the case)
|
|
int iSize = pSep - getBufPtr<char>(rInBuf) + 1,
|
|
iLength = iSize - 1;
|
|
if (iLength && *(pSep - 1) == '\r')
|
|
iLength--;
|
|
// Copy the line
|
|
StdStrBuf Buf; Buf.Copy(getBufPtr<char>(rInBuf), iLength);
|
|
// Ignore prefix
|
|
const char *pMsg = Buf.getData();
|
|
StdStrBuf Prefix;
|
|
if (*pMsg == ':')
|
|
{
|
|
Prefix.CopyUntil(pMsg + 1, ' ');
|
|
pMsg += Prefix.getLength() + 1;
|
|
}
|
|
// Strip whitespace
|
|
while (*pMsg == ' ')
|
|
pMsg++;
|
|
// Ignore empty message
|
|
if (!*pMsg)
|
|
return iSize;
|
|
// Get command
|
|
StdStrBuf Cmd; Cmd.CopyUntil(pMsg, ' ');
|
|
// Precess command
|
|
const char *szParameters = SSearch(pMsg, " ");
|
|
OnCommand(Prefix.getData(), Cmd.getData(), szParameters ? szParameters : "");
|
|
// Consume the line
|
|
return iSize;
|
|
}
|
|
|
|
bool C4Network2IRCClient::OnConn(const C4NetIO::addr_t &AddrPeer, const C4NetIO::addr_t &AddrConnect, const addr_t *pOwnAddr, C4NetIO *pNetIO)
|
|
{
|
|
// Security checks
|
|
if (!fConnecting || fConnected || !AddrEqual(AddrConnect, ServerAddr)) return false;
|
|
CStdLock Lock(&CSec);
|
|
// Save connection data
|
|
fConnected = true;
|
|
fConnecting = false;
|
|
C4Network2IRCClient::PeerAddr = AddrPeer;
|
|
// Send welcome message
|
|
if (!Password.isNull())
|
|
Send("PASS", Password.getData());
|
|
Send("NICK", Nick.getData());
|
|
Send("USER", FormatString("clonk x x :%s", RealName.getLength() ? RealName.getData() : " ").getData());
|
|
// Okay
|
|
return true;
|
|
}
|
|
|
|
void C4Network2IRCClient::OnDisconn(const C4NetIO::addr_t &AddrPeer, C4NetIO *pNetIO, const char *szReason)
|
|
{
|
|
fConnected = false;
|
|
// Show a message with the reason
|
|
PushMessage(MSG_Status, "", Nick.getData(), FormatString(LoadResStr("IDS_MSG_DISCONNECTEDFROMSERVER"), szReason).getData());
|
|
}
|
|
|
|
void C4Network2IRCClient::OnPacket(const class C4NetIOPacket &rPacket, C4NetIO *pNetIO)
|
|
{
|
|
// Won't get called
|
|
}
|
|
|
|
C4Network2IRCChannel *C4Network2IRCClient::getFirstChannel() const
|
|
{
|
|
return pChannels;
|
|
}
|
|
|
|
C4Network2IRCChannel *C4Network2IRCClient::getNextChannel(C4Network2IRCChannel *pPrevChan) const
|
|
{
|
|
return pPrevChan ? pPrevChan->Next : pChannels;
|
|
}
|
|
|
|
C4Network2IRCChannel *C4Network2IRCClient::getChannel(const char *szName) const
|
|
{
|
|
for (C4Network2IRCChannel *pChan = pChannels; pChan; pChan = pChan->Next)
|
|
if (SEqualNoCase(pChan->getName(), szName))
|
|
return pChan;
|
|
return NULL;
|
|
}
|
|
|
|
void C4Network2IRCClient::ClearMessageLog()
|
|
{
|
|
// Clear log
|
|
while (iLogLength)
|
|
PopMessage();
|
|
}
|
|
|
|
void C4Network2IRCClient::MarkMessageLogRead()
|
|
{
|
|
// set read marker to last message
|
|
pLogLastRead = pLogEnd;
|
|
iUnreadLogLength = 0;
|
|
// message buffer is smaller for messages already read: Remove old ones
|
|
while (iLogLength > C4NetIRCMaxReadLogLength)
|
|
PopMessage();
|
|
}
|
|
|
|
bool C4Network2IRCClient::Connect(const char *szServer, const char *szNick, const char *szRealName, const char *szPassword, const char *szAutoJoin)
|
|
{
|
|
// Already connected? Close connection
|
|
if (fConnecting || fConnected)
|
|
Close();
|
|
// Initialize
|
|
C4NetIOTCP::SetCallback(this);
|
|
if (!Init())
|
|
return false;
|
|
// Resolve address
|
|
if (!ResolveAddress(szServer, &ServerAddr, 6666))
|
|
{ SetError("Could no resolve server address!"); return false; }
|
|
// Set connection data
|
|
Nick = szNick; RealName = szRealName;
|
|
Password = szPassword; AutoJoin = szAutoJoin;
|
|
// Truncate password
|
|
if (Password.getLength() > 31)
|
|
Password.SetLength(31);
|
|
// Start connecting
|
|
if (!C4NetIOTCP::Connect(ServerAddr))
|
|
return false;
|
|
// Reset status data
|
|
Prefixes = "(ov)@+";
|
|
// Okay, let's wait for the connection.
|
|
fConnecting = true;
|
|
return true;
|
|
}
|
|
|
|
bool C4Network2IRCClient::Close()
|
|
{
|
|
// Close network
|
|
C4NetIOTCP::Close();
|
|
// Save & Clear channels
|
|
if(pChannels) // Don't override empty
|
|
{
|
|
// It's somewhat weird to loop backward through a singly linked list, but it's necessary to keep the order
|
|
StdStrBuf chanstr;
|
|
C4Network2IRCChannel * pChan = pChannels;
|
|
while(pChan->Next)
|
|
pChan = pChan->Next;
|
|
chanstr.Append(pChan->getName());
|
|
while (pChan != pChannels)
|
|
{
|
|
C4Network2IRCChannel * pChanPrev = pChannels;
|
|
while(pChanPrev->Next != pChan)
|
|
pChanPrev = pChanPrev->Next;
|
|
pChan = pChanPrev;
|
|
chanstr.Append(",");
|
|
chanstr.Append(pChan->getName());
|
|
}
|
|
strncpy(Config.IRC.Channel, chanstr.getData(), sizeof(Config.IRC.Channel)-1);
|
|
}
|
|
while (pChannels)
|
|
DeleteChannel(pChannels);
|
|
// Clear log
|
|
ClearMessageLog();
|
|
// Reset flags
|
|
fConnected = fConnecting = false;
|
|
return true;
|
|
}
|
|
|
|
bool C4Network2IRCClient::Send(const char *szCommand, const char *szParameters)
|
|
{
|
|
if (!fConnected)
|
|
{ SetError("not connected"); return false; }
|
|
// Create message
|
|
StdStrBuf Msg;
|
|
if (szParameters)
|
|
Msg.Format("%s %s", szCommand, szParameters);
|
|
else
|
|
Msg.Ref(szCommand);
|
|
// Send
|
|
return C4NetIOTCP::Send(C4NetIOPacket(Msg.getData(), Msg.getLength(), false, PeerAddr));
|
|
}
|
|
|
|
bool C4Network2IRCClient::Quit(const char *szReason)
|
|
{
|
|
if (!Send("QUIT", FormatString(":%s", szReason).getData()))
|
|
return false;
|
|
// Must be last message
|
|
return Close();
|
|
}
|
|
|
|
bool C4Network2IRCClient::Join(const char *szChannel)
|
|
{
|
|
// Newbie limitation: can only join channels beginning with #clonk
|
|
if (!Config.IRC.AllowAllChannels)
|
|
if (!SWildcardMatchEx(szChannel, "#*clonk*"))
|
|
{
|
|
const char* message = LoadResStr("IDS_ERR_CHANNELNOTALLOWED");
|
|
PushMessage(MSG_Status, "", "", message);
|
|
SetError("Joining this channel not allowed");
|
|
Application.InteractiveThread.ThreadPostAsync<bool>(std::bind(&C4GUI::Screen::ShowMessage, ::pGUI, message, LoadResStr("IDS_DLG_CHAT"), C4GUI::Ico_Error, static_cast<int32_t* const &>(0)));
|
|
return false;
|
|
}
|
|
return Send("JOIN", szChannel);
|
|
}
|
|
|
|
bool C4Network2IRCClient::Part(const char *szChannel)
|
|
{
|
|
return Send("PART", szChannel);
|
|
}
|
|
|
|
bool C4Network2IRCClient::Message(const char *szTarget, const char *szText)
|
|
{
|
|
if (!Send("PRIVMSG", FormatString("%s :%s", szTarget, szText).getData()))
|
|
return false;
|
|
PushMessage(MSG_Message, Nick.getData(), szTarget, szText);
|
|
return true;
|
|
}
|
|
|
|
bool C4Network2IRCClient::Notice(const char *szTarget, const char *szText)
|
|
{
|
|
if (!Send("NOTICE", FormatString("%s :%s", szTarget, szText).getData()))
|
|
return false;
|
|
PushMessage(MSG_Notice, Nick.getData(), szTarget, szText);
|
|
return true;
|
|
}
|
|
|
|
bool C4Network2IRCClient::Action(const char *szTarget, const char *szText)
|
|
{
|
|
if (!Send("PRIVMSG", FormatString("%s :\1ACTION %s\1", szTarget, szText).getData()))
|
|
return false;
|
|
PushMessage(MSG_Action, Nick.getData(), szTarget, szText);
|
|
return true;
|
|
}
|
|
|
|
bool C4Network2IRCClient::ChangeNick(const char *szNewNick)
|
|
{
|
|
return Send("NICK", szNewNick);
|
|
}
|
|
|
|
bool C4Network2IRCClient::RegisterNick(const char *szPassword, const char *szMail)
|
|
{
|
|
return Send("PRIVMSG", FormatString("NickServ :REGISTER %s %s", szPassword, szMail).getData());
|
|
}
|
|
|
|
void C4Network2IRCClient::OnCommand(const char *szSender, const char *szCommand, const char *szParameters)
|
|
{
|
|
CStdLock Lock(&CSec);
|
|
// Numeric command?
|
|
if (isdigit((unsigned char)*szCommand) && SLen(szCommand) == 3)
|
|
{
|
|
OnNumericCommand(szSender, atoi(szCommand), szParameters);
|
|
return;
|
|
}
|
|
// Sender's nick
|
|
StdStrBuf SenderNick;
|
|
if (szSender) SenderNick.CopyUntil(szSender, '!');
|
|
// Ping?
|
|
if (SEqualNoCase(szCommand, "PING"))
|
|
Send("PONG", szParameters);
|
|
// Message?
|
|
if (SEqualNoCase(szCommand, "NOTICE") || SEqualNoCase(szCommand, "PRIVMSG"))
|
|
{
|
|
// Get target
|
|
StdStrBuf Target = ircExtractPar(&szParameters);
|
|
// Get text
|
|
StdStrBuf Text = ircExtractPar(&szParameters);
|
|
// Process message
|
|
OnMessage(SEqualNoCase(szCommand, "NOTICE"), szSender, Target.getData(), Text.getData());
|
|
}
|
|
// Channel join?
|
|
if (SEqualNoCase(szCommand, "JOIN"))
|
|
{
|
|
// Get channel
|
|
StdStrBuf Channel = ircExtractPar(&szParameters);
|
|
C4Network2IRCChannel *pChan = AddChannel(Channel.getData());
|
|
// Add user
|
|
pChan->OnJoin(SenderNick.getData());
|
|
// Myself?
|
|
if (SenderNick == Nick)
|
|
PushMessage(MSG_Status, szSender, Channel.getData(), FormatString(LoadResStr("IDS_MSG_YOUHAVEJOINEDCHANNEL"), Channel.getData()).getData());
|
|
else
|
|
PushMessage(MSG_Status, szSender, Channel.getData(), FormatString(LoadResStr("IDS_MSG_HASJOINEDTHECHANNEL"), SenderNick.getData()).getData());
|
|
}
|
|
// Channel part?
|
|
if (SEqualNoCase(szCommand, "PART"))
|
|
{
|
|
// Get channel
|
|
StdStrBuf Channel = ircExtractPar(&szParameters);
|
|
C4Network2IRCChannel *pChan = AddChannel(Channel.getData());
|
|
// Get message
|
|
StdStrBuf Comment = ircExtractPar(&szParameters);
|
|
// Remove user
|
|
pChan->OnPart(SenderNick.getData(), Comment.getData());
|
|
// Myself?
|
|
if (SenderNick == Nick)
|
|
{
|
|
DeleteChannel(pChan);
|
|
PushMessage(MSG_Status, szSender, Nick.getData(), FormatString(LoadResStr("IDS_MSG_YOUHAVELEFTCHANNEL"), Channel.getData(), Comment.getData()).getData());
|
|
}
|
|
else
|
|
PushMessage(MSG_Status, szSender, Channel.getData(), FormatString(LoadResStr("IDS_MSG_HASLEFTTHECHANNEL"), SenderNick.getData(), Comment.getData()).getData());
|
|
}
|
|
// Kick?
|
|
if (SEqualNoCase(szCommand, "KICK"))
|
|
{
|
|
// Get channel
|
|
StdStrBuf Channel = ircExtractPar(&szParameters);
|
|
C4Network2IRCChannel *pChan = AddChannel(Channel.getData());
|
|
// Get kicked user
|
|
StdStrBuf Kicked = ircExtractPar(&szParameters);
|
|
// Get message
|
|
StdStrBuf Comment = ircExtractPar(&szParameters);
|
|
// Remove user
|
|
pChan->OnKick(Kicked.getData(), Comment.getData());
|
|
// Myself?
|
|
if (Kicked == Nick)
|
|
{
|
|
DeleteChannel(pChan);
|
|
PushMessage(MSG_Status, szSender, Nick.getData(), FormatString(LoadResStr("IDS_MSG_YOUWEREKICKEDFROMCHANNEL"), Channel.getData(), Comment.getData()).getData());
|
|
}
|
|
else
|
|
PushMessage(MSG_Status, szSender, Channel.getData(), FormatString(LoadResStr("IDS_MSG_WASKICKEDFROMTHECHANNEL"), Kicked.getData(), Comment.getData()).getData());
|
|
}
|
|
// Quit?
|
|
if (SEqualNoCase(szCommand, "QUIT"))
|
|
{
|
|
// Get comment
|
|
StdStrBuf Comment = ircExtractPar(&szParameters);
|
|
// Format status message
|
|
StdStrBuf Message = FormatString(LoadResStr("IDS_MSG_HASDISCONNECTED"), SenderNick.getData(), Comment.getData());
|
|
// Remove him from all channels
|
|
for (C4Network2IRCChannel *pChan = pChannels; pChan; pChan = pChan->Next)
|
|
if (pChan->getUser(SenderNick.getData()))
|
|
{
|
|
pChan->OnPart(SenderNick.getData(), "Quit");
|
|
PushMessage(MSG_Status, szSender, pChan->getName(), Message.getData());
|
|
}
|
|
}
|
|
// Topic change?
|
|
if (SEqualNoCase(szCommand, "TOPIC"))
|
|
{
|
|
// Get channel and topic
|
|
StdStrBuf Channel = ircExtractPar(&szParameters);
|
|
StdStrBuf Topic = ircExtractPar(&szParameters);
|
|
// Set topic
|
|
AddChannel(Channel.getData())->OnTopic(Topic.getData());
|
|
// Message
|
|
PushMessage(MSG_Status, szSender, Channel.getData(), FormatString(LoadResStr("IDS_MSG_CHANGESTHETOPICTO"), SenderNick.getData(), Topic.getData()).getData());
|
|
}
|
|
// Mode?
|
|
if (SEqualNoCase(szCommand, "MODE"))
|
|
{
|
|
// Get all data
|
|
StdStrBuf Channel = ircExtractPar(&szParameters);
|
|
StdStrBuf Flags = ircExtractPar(&szParameters);
|
|
StdStrBuf What = ircExtractPar(&szParameters);
|
|
// Make sure it's a channel
|
|
C4Network2IRCChannel *pChan = getChannel(Channel.getData());
|
|
if (pChan)
|
|
// Ask for names, because user prefixes might be out of sync
|
|
Send("NAMES", Channel.getData());
|
|
// Show Message
|
|
PushMessage(MSG_Status, szSender, Channel.getData(), FormatString(LoadResStr("IDS_MSG_SETSMODE"), SenderNick.getData(), Flags.getData(), What.getData()).getData());
|
|
}
|
|
// Error?
|
|
if (SEqualNoCase(szCommand, "ERROR"))
|
|
{
|
|
// Get message
|
|
StdStrBuf Message = ircExtractPar(&szParameters);
|
|
// Push it
|
|
PushMessage(MSG_Server, szSender, Nick.getData(), Message.getData());
|
|
}
|
|
// Nickchange?
|
|
if (SEqualNoCase(szCommand, "NICK"))
|
|
{
|
|
// Get new nick
|
|
StdStrBuf NewNick = ircExtractPar(&szParameters);
|
|
// Format status message
|
|
StdStrBuf Message = FormatString(LoadResStr("IDS_MSG_ISNOWKNOWNAS"), SenderNick.getData(), NewNick.getData());
|
|
// Rename on all channels
|
|
for (C4Network2IRCChannel *pChan = pChannels; pChan; pChan = pChan->Next)
|
|
if (pChan->getUser(SenderNick.getData()))
|
|
{
|
|
pChan->OnPart(SenderNick.getData(), "Nickchange");
|
|
pChan->OnJoin(NewNick.getData());
|
|
PushMessage(MSG_Status, szSender, pChan->getName(), Message.getData());
|
|
}
|
|
// Self?
|
|
if (SenderNick == Nick)
|
|
Nick = NewNick;
|
|
}
|
|
}
|
|
|
|
void C4Network2IRCClient::OnNumericCommand(const char *szSender, int iCommand, const char *szParameters)
|
|
{
|
|
bool fShowMessage = true;
|
|
// Get target
|
|
StdStrBuf Target = ircExtractPar(&szParameters);
|
|
// Handle command
|
|
switch (iCommand)
|
|
{
|
|
|
|
case 433: // Nickname already in use
|
|
{
|
|
StdStrBuf DesiredNick = ircExtractPar(&szParameters);
|
|
// Automatically try to choose a new one
|
|
DesiredNick.AppendChar('_');
|
|
Send("NICK", DesiredNick.getData());
|
|
break;
|
|
}
|
|
|
|
case 376: // End of MOTD
|
|
case 422: // MOTD missing
|
|
// Let's take this a sign that the connection is established.
|
|
OnConnected();
|
|
break;
|
|
|
|
case 331: // No topic set
|
|
case 332: // Topic notify / change
|
|
{
|
|
// Get Channel name and topic
|
|
StdStrBuf Channel = ircExtractPar(&szParameters);
|
|
StdStrBuf Topic = (iCommand == 332 ? ircExtractPar(&szParameters) : StdStrBuf(""));
|
|
// Set it
|
|
AddChannel(Channel.getData())->OnTopic(Topic.getData());
|
|
// Log
|
|
if (Topic.getLength())
|
|
PushMessage(MSG_Status, szSender, Channel.getData(), FormatString(LoadResStr("IDS_MSG_TOPICIN"), Channel.getData(), Topic.getData()).getData());
|
|
}
|
|
break;
|
|
|
|
case 333: // Last topic change
|
|
fShowMessage = false; // ignore
|
|
break;
|
|
|
|
case 353: // Names in channel
|
|
{
|
|
// Get Channel name and name list
|
|
StdStrBuf Junk = ircExtractPar(&szParameters); // ??!
|
|
StdStrBuf Channel = ircExtractPar(&szParameters);
|
|
StdStrBuf Names = ircExtractPar(&szParameters);
|
|
// Set it
|
|
AddChannel(Channel.getData())->OnUsers(Names.getData(), Prefixes.getData());
|
|
fShowMessage = false;
|
|
}
|
|
break;
|
|
|
|
case 366: // End of names list
|
|
{
|
|
// Get Channel name
|
|
StdStrBuf Channel = ircExtractPar(&szParameters);
|
|
// Finish
|
|
AddChannel(Channel.getData())->OnUsersEnd();
|
|
fShowMessage = false;
|
|
// Notify
|
|
if (pNotify) pNotify->PushEvent(Ev_IRC_Message, this);
|
|
}
|
|
break;
|
|
|
|
case 4: // Server version
|
|
fShowMessage = false; // ignore
|
|
break;
|
|
|
|
case 5: // Server support string
|
|
{
|
|
while (szParameters && *szParameters)
|
|
{
|
|
// Get support-token.
|
|
StdStrBuf Token = ircExtractPar(&szParameters);
|
|
StdStrBuf Parameter; Parameter.CopyUntil(Token.getData(), '=');
|
|
// Check if it's interesting and safe data if so.
|
|
if (SEqualNoCase(Parameter.getData(), "PREFIX"))
|
|
Prefixes.Copy(SSearch(Token.getData(), "="));
|
|
}
|
|
fShowMessage = false;
|
|
}
|
|
break;
|
|
|
|
}
|
|
// Show embedded message, if any?
|
|
if (fShowMessage)
|
|
{
|
|
// Check if first parameter is some sort of channel name
|
|
C4Network2IRCChannel *pChannel = NULL;
|
|
if (szParameters && *szParameters && *szParameters != ':')
|
|
pChannel = getChannel(ircExtractPar(&szParameters).getData());
|
|
// Go over other parameters
|
|
const char *pMsg = szParameters;
|
|
while (pMsg && *pMsg && *pMsg != ':')
|
|
pMsg = SSearch(pMsg, " ");
|
|
// Show it
|
|
if (pMsg && *pMsg)
|
|
{
|
|
if (!pChannel)
|
|
PushMessage(MSG_Server, szSender, Nick.getData(), pMsg + 1);
|
|
else
|
|
PushMessage(MSG_Status, szSender, pChannel->getName(), pMsg + 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
void C4Network2IRCClient::OnConnected()
|
|
{
|
|
|
|
// Set flag
|
|
fConnected = true;
|
|
|
|
// Try to join channel(s)
|
|
if (AutoJoin.getLength())
|
|
Join(AutoJoin.getData());
|
|
|
|
}
|
|
|
|
void C4Network2IRCClient::OnMessage(bool fNotice, const char *szSender, const char *szTarget, const char *szText)
|
|
{
|
|
// CTCP tagged data?
|
|
const char X_DELIM = '\001';
|
|
if (szText[0] == X_DELIM)
|
|
{
|
|
// Process messages (it's very rarely more than one, but the spec allows it)
|
|
const char *pMsg = szText + 1;
|
|
while (*pMsg)
|
|
{
|
|
// Find end
|
|
const char *pEnd = strchr(pMsg, X_DELIM);
|
|
if (!pEnd) pEnd = pMsg + SLen(pMsg);
|
|
// Copy CTCP query/reply, get tag
|
|
StdStrBuf CTCP; CTCP.Copy(pMsg, pEnd - pMsg);
|
|
StdStrBuf Tag; Tag.CopyUntil(CTCP.getData(), ' ');
|
|
const char *szData = SSearch(CTCP.getData(), " ");
|
|
StdStrBuf Sender; Sender.CopyUntil(szSender, '!');
|
|
// Process
|
|
if (SEqualNoCase(Tag.getData(), "ACTION"))
|
|
PushMessage(MSG_Action, szSender, szTarget, szData ? szData : "");
|
|
if (SEqualNoCase(Tag.getData(), "FINGER") && !fNotice)
|
|
{
|
|
StdStrBuf Answer;
|
|
Answer = LoadResStr("IDS_PRC_UNREGUSER"); //ToDo: Anser sth. else
|
|
|
|
Send("NOTICE", FormatString("%s :%cFINGER %s%c",
|
|
Sender.getData(), X_DELIM,
|
|
Answer.getData(),
|
|
X_DELIM).getData());
|
|
}
|
|
if (SEqualNoCase(Tag.getData(), "VERSION") && !fNotice)
|
|
Send("NOTICE", FormatString("%s :%cVERSION " C4ENGINECAPTION ":" C4VERSION ":" C4_OS "%c",
|
|
Sender.getData(), X_DELIM, X_DELIM).getData());
|
|
if (SEqualNoCase(Tag.getData(), "PING") && !fNotice)
|
|
Send("NOTICE", FormatString("%s :%cPING %s%c",
|
|
Sender.getData(), X_DELIM, szData, X_DELIM).getData());
|
|
// Get next message
|
|
pMsg = pEnd;
|
|
if (*pMsg == X_DELIM) pMsg++;
|
|
}
|
|
}
|
|
|
|
// Standard message (not CTCP tagged): Push
|
|
else
|
|
PushMessage(fNotice ? MSG_Notice : MSG_Message, szSender, szTarget, szText);
|
|
|
|
}
|
|
|
|
void C4Network2IRCClient::PopMessage()
|
|
{
|
|
if (!iLogLength) return;
|
|
// Unlink message
|
|
C4Network2IRCMessage *pMsg = pLog;
|
|
pLog = pMsg->Next;
|
|
if (!pLog) pLogEnd = NULL;
|
|
if (pLogLastRead == pMsg) pLogLastRead = NULL;
|
|
// Delete it
|
|
delete pMsg;
|
|
iLogLength--;
|
|
if (iUnreadLogLength > iLogLength) iUnreadLogLength = iLogLength;
|
|
}
|
|
|
|
void C4Network2IRCClient::PushMessage(C4Network2IRCMessageType eType, const char *szSource, const char *szTarget, const char *szText)
|
|
{
|
|
// Create message
|
|
C4Network2IRCMessage *pMsg = new C4Network2IRCMessage(eType, szSource, szTarget, szText);
|
|
// Add to list
|
|
if (pLogEnd)
|
|
{
|
|
pLogEnd->Next = pMsg;
|
|
}
|
|
else
|
|
{
|
|
pLog = pMsg;
|
|
}
|
|
pLogEnd = pMsg;
|
|
// Count
|
|
iLogLength++;
|
|
iUnreadLogLength++;
|
|
while (iLogLength > C4NetIRCMaxLogLength)
|
|
PopMessage();
|
|
// Notify
|
|
if (pNotify)
|
|
pNotify->PushEvent(Ev_IRC_Message, this);
|
|
}
|
|
|
|
C4Network2IRCChannel *C4Network2IRCClient::AddChannel(const char *szName)
|
|
{
|
|
// Already exists?
|
|
C4Network2IRCChannel *pChan = getChannel(szName);
|
|
if (pChan) return pChan;
|
|
// Create new, insert
|
|
pChan = new C4Network2IRCChannel(szName);
|
|
pChan->Next = pChannels;
|
|
pChannels = pChan;
|
|
return pChan;
|
|
}
|
|
|
|
void C4Network2IRCClient::DeleteChannel(C4Network2IRCChannel *pChannel)
|
|
{
|
|
// Unlink
|
|
if (pChannel == pChannels)
|
|
pChannels = pChannel->Next;
|
|
else
|
|
{
|
|
C4Network2IRCChannel *pPrev = pChannels;
|
|
while (pPrev && pPrev->Next != pChannel)
|
|
pPrev = pPrev->Next;
|
|
if (pPrev)
|
|
pPrev->Next = pChannel->Next;
|
|
}
|
|
// Delete
|
|
delete pChannel;
|
|
}
|