// -*- mode: c++; c-file-style: "bsd"; c-basic-offset: 4; tabs-width: 4; indent-tabs-mode: nil -*- /* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. */ /* Copyright (C) 2002 Dario Abatianni Copyright (C) 2005 Ismail Donmez Copyright (C) 2005-2006 Peter Simonsson Copyright (C) 2006-2008 Eli J. MacKenzie Copyright (C) 2005-2008 Eike Hein */ #include "server.h" #include "ircqueue.h" #include "query.h" #include "channel.h" #include "konversationapplication.h" #include "connectionmanager.h" #include "dcccommon.h" #include "dcctransferpanel.h" #include "dcctransferpanelitem.h" #include "dcctransfersend.h" #include "dcctransferrecv.h" #include "dccrecipientdialog.h" #include "nick.h" #include "irccharsets.h" #include "viewcontainer.h" #include "statuspanel.h" #include "rawlog.h" #include "channellistpanel.h" #include "scriptlauncher.h" #include "servergroupsettings.h" #include "addressbook.h" #include "serverison.h" #include "common.h" #include "notificationhandler.h" #include "blowfish.h" #include "dcctransfermanager.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include int Server::m_availableConnectionId = 0; Server::Server(TQObject* parent, ConnectionSettings& settings) : TQObject(parent) { m_connectionId = m_availableConnectionId; m_availableConnectionId++; setConnectionSettings(settings); m_connectionState = Konversation::SSNeverConnected; for (int i=0;i<=_max_queue();i++) { TQValueList r=Preferences::queueRate(i); IRCQueue *q=new IRCQueue(this, staticrates[i]); //FIXME these are supposed to be in the rc m_queues.append(q); } m_processingIncoming = false; m_identifyMsg = false; m_autoIdentifyLock = false; m_autoJoin = false; m_nickIndices.clear(); m_nickIndices.append(0); m_currentLag = -1; m_rawLog = 0; m_channelListPanel = 0; m_serverISON = 0; m_away = false; m_socket = 0; m_prevISONList = TQStringList(); m_bytesReceived = 0; m_encodedBytesSent=0; m_bytesSent=0; m_linesSent=0; // TODO fold these into a TQMAP, and these need to be reset to RFC values if this server object is reused. m_serverNickPrefixModes = "ovh"; m_serverNickPrefixes = "@+%"; m_channelPrefixes = "#&"; setName(TQString("server_" + settings.name()).ascii()); setNickname(settings.initialNick()); obtainNickInfo(getNickname()); m_statusView = getViewContainer()->addStatusView(this); if (Preferences::rawLog()) addRawLog(false); m_inputFilter.setServer(this); m_outputFilter = new Konversation::OutputFilter(this); m_scriptLauncher = new ScriptLauncher(this); // don't delete items when they are removed m_channelList.setAutoDelete(false); // For /msg query completion m_completeQueryPosition = 0; updateAutoJoin(settings.initialChannel()); if (!getIdentity()->getShellCommand().isEmpty()) TQTimer::singleShot(0, this, TQT_SLOT(doPreShellCommand())); else TQTimer::singleShot(0, this, TQT_SLOT(connectToIRCServer())); initTimers(); if (getIdentity()->getShellCommand().isEmpty()) connectSignals(); } Server::~Server() { //send queued messages kdDebug() << "Server::~Server(" << getServerName() << ")" << endl; // Delete helper object. delete m_serverISON; m_serverISON = 0; // clear nicks online emit nicksNowOnline(this,TQStringList(),true); // Make sure no signals get sent to a soon to be dying Server Window if (m_socket) { m_socket->blockSignals(true); m_socket->deleteLater(); } if (m_statusView) delete m_statusView; closeRawLog(); closeChannelListPanel(); m_channelList.setAutoDelete(true); m_channelList.clear(); m_queryList.setAutoDelete(true); m_queryList.clear(); // Delete all the NickInfos and ChannelNick structures. m_allNicks.clear(); ChannelMembershipMap::ConstIterator it; for ( it = m_joinedChannels.begin(); it != m_joinedChannels.end(); ++it ) delete it.data(); m_joinedChannels.clear(); for ( it = m_unjoinedChannels.begin(); it != m_unjoinedChannels.end(); ++it ) delete it.data(); m_unjoinedChannels.clear(); m_queryNicks.clear(); //Delete the queues for (TQValueVector::iterator it=m_queues.begin(); it != m_queues.end(); ++it) delete *it; emit destroyed(m_connectionId); kdDebug() << "~Server done" << endl; } //... so called to match the ChatWindow derivatives. bool Server::closeYourself(bool) { TQTimer::singleShot(0, m_statusView, TQT_SLOT(serverSaysClose())); return true; } void Server::doPreShellCommand() { TQString command = getIdentity()->getShellCommand(); getStatusView()->appendServerMessage(i18n("Info"),"Running preconfigured command..."); connect(&m_preShellCommand,TQT_SIGNAL(processExited(TDEProcess*)), this, TQT_SLOT(preShellCommandExited(TDEProcess*))); TQStringList commandList = TQStringList::split(" ",command); for (TQStringList::ConstIterator it = commandList.begin(); it != commandList.end(); ++it) m_preShellCommand << *it; if (!m_preShellCommand.start()) preShellCommandExited(NULL); } void Server::_fetchRates() { for (int i=0;i<=_max_queue();i++) { TQValueList r=Preferences::queueRate(i); staticrates[i]=IRCQueue::EmptyingRate(r[0], r[1]*1000,IRCQueue::EmptyingRate::RateType(r[2])); } } void Server::_stashRates() { for (int i=0;i<=_max_queue();i++) { TQValueList r; r.append(staticrates[i].m_rate); r.append(staticrates[i].m_interval/1000); r.append(int(staticrates[i].m_type)); Preferences::setQueueRate(i, r); } } void Server::_resetRates() { for (int i=0;i<=_max_queue();i++) { Preferences::self()->queueRateItem(i)->setDefault(); TQValueList r=Preferences::queueRate(i); staticrates[i]=IRCQueue::EmptyingRate(r[0], r[1]*1000,IRCQueue::EmptyingRate::RateType(r[2])); } } void Server::initTimers() { m_notifyTimer.setName("notify_timer"); m_incomingTimer.setName("incoming_timer"); } void Server::connectSignals() { // Timers connect(&m_incomingTimer, TQT_SIGNAL(timeout()), this, TQT_SLOT(processIncomingData())); connect(&m_notifyTimer, TQT_SIGNAL(timeout()), this, TQT_SLOT(notifyTimeout())); connect(&m_pingResponseTimer, TQT_SIGNAL(timeout()), this, TQT_SLOT(updateLongPongLag())); // OutputFilter connect(getOutputFilter(), TQT_SIGNAL(requestDccSend()), this,TQT_SLOT(requestDccSend())); connect(getOutputFilter(), TQT_SIGNAL(requestDccSend(const TQString&)), this, TQT_SLOT(requestDccSend(const TQString&))); connect(getOutputFilter(), TQT_SIGNAL(multiServerCommand(const TQString&, const TQString&)), this, TQT_SLOT(sendMultiServerCommand(const TQString&, const TQString&))); connect(getOutputFilter(), TQT_SIGNAL(reconnectServer()), this, TQT_SLOT(reconnect())); connect(getOutputFilter(), TQT_SIGNAL(disconnectServer()), this, TQT_SLOT(disconnect())); connect(getOutputFilter(), TQT_SIGNAL(openDccSend(const TQString &, KURL)), this, TQT_SLOT(addDccSend(const TQString &, KURL))); connect(getOutputFilter(), TQT_SIGNAL(openDccChat(const TQString &)), this, TQT_SLOT(openDccChat(const TQString &))); connect(getOutputFilter(), TQT_SIGNAL(sendToAllChannels(const TQString&)), this, TQT_SLOT(sendToAllChannels(const TQString&))); connect(getOutputFilter(), TQT_SIGNAL(banUsers(const TQStringList&,const TQString&,const TQString&)), this, TQT_SLOT(requestBan(const TQStringList&,const TQString&,const TQString&))); connect(getOutputFilter(), TQT_SIGNAL(unbanUsers(const TQString&,const TQString&)), this, TQT_SLOT(requestUnban(const TQString&,const TQString&))); connect(getOutputFilter(), TQT_SIGNAL(openRawLog(bool)), this, TQT_SLOT(addRawLog(bool))); connect(getOutputFilter(), TQT_SIGNAL(closeRawLog()), this, TQT_SLOT(closeRawLog())); connect(getOutputFilter(), TQT_SIGNAL(encodingChanged()), this, TQT_SLOT(updateEncoding())); KonversationApplication* konvApp = static_cast(kapp); connect(getOutputFilter(), TQT_SIGNAL(connectTo(Konversation::ConnectionFlag, const TQString&, const TQString&, const TQString&, const TQString&, const TQString&, bool)), konvApp->getConnectionManager(), TQT_SLOT(connectTo(Konversation::ConnectionFlag, const TQString&, const TQString&, const TQString&, const TQString&, const TQString&, bool))); connect(konvApp->getDccTransferManager(), TQT_SIGNAL(newTransferQueued(DccTransfer*)), this, TQT_SLOT(slotNewDccTransferItemQueued(DccTransfer*))); connect(konvApp, TQT_SIGNAL(appearanceChanged()), this, TQT_SLOT(startNotifyTimer())); // ViewContainer connect(this, TQT_SIGNAL(showView(ChatWindow*)), getViewContainer(), TQT_SLOT(showView(ChatWindow*))); connect(this, TQT_SIGNAL(addDccPanel()), getViewContainer(), TQT_SLOT(addDccPanel())); connect(this, TQT_SIGNAL(addDccChat(const TQString&,const TQString&,const TQStringList&,bool)), getViewContainer(), TQT_SLOT(addDccChat(const TQString&,const TQString&,const TQStringList&,bool)) ); connect(this, TQT_SIGNAL(serverLag(Server*, int)), getViewContainer(), TQT_SIGNAL(updateStatusBarLagLabel(Server*, int))); connect(this, TQT_SIGNAL(tooLongLag(Server*, int)), getViewContainer(), TQT_SIGNAL(setStatusBarLagLabelTooLongLag(Server*, int))); connect(this, TQT_SIGNAL(resetLag()), getViewContainer(), TQT_SIGNAL(resetStatusBarLagLabel())); connect(getOutputFilter(), TQT_SIGNAL(showView(ChatWindow*)), getViewContainer(), TQT_SLOT(showView(ChatWindow*))); connect(getOutputFilter(), TQT_SIGNAL(openKonsolePanel()), getViewContainer(), TQT_SLOT(addKonsolePanel())); connect(getOutputFilter(), TQT_SIGNAL(openChannelList(const TQString&, bool)), getViewContainer(), TQT_SLOT(openChannelList(const TQString&, bool))); connect(getOutputFilter(), TQT_SIGNAL(closeDccPanel()), getViewContainer(), TQT_SLOT(closeDccPanel())); connect(getOutputFilter(), TQT_SIGNAL(addDccPanel()), getViewContainer(), TQT_SLOT(addDccPanel())); connect(&m_inputFilter, TQT_SIGNAL(addDccChat(const TQString&,const TQString&,const TQStringList&,bool)), getViewContainer(), TQT_SLOT(addDccChat(const TQString&,const TQString&,const TQStringList&,bool)) ); // Inputfilter connect(&m_inputFilter, TQT_SIGNAL(welcome(const TQString&)), this, TQT_SLOT(connectionEstablished(const TQString&))); connect(&m_inputFilter, TQT_SIGNAL(notifyResponse(const TQString&)), this, TQT_SLOT(notifyResponse(const TQString&))); connect(&m_inputFilter, TQT_SIGNAL(startReverseDccSendTransfer(const TQString&,const TQStringList&)), this, TQT_SLOT(startReverseDccSendTransfer(const TQString&,const TQStringList&))); connect(&m_inputFilter, TQT_SIGNAL(addDccGet(const TQString&, const TQStringList&)), this, TQT_SLOT(addDccGet(const TQString&, const TQStringList&))); connect(&m_inputFilter, TQT_SIGNAL(resumeDccGetTransfer(const TQString&, const TQStringList&)), this, TQT_SLOT(resumeDccGetTransfer(const TQString&, const TQStringList&))); connect(&m_inputFilter, TQT_SIGNAL(resumeDccSendTransfer(const TQString&, const TQStringList&)), this, TQT_SLOT(resumeDccSendTransfer(const TQString&, const TQStringList&))); connect(&m_inputFilter, TQT_SIGNAL(userhost(const TQString&,const TQString&,bool,bool)), this, TQT_SLOT(userhost(const TQString&,const TQString&,bool,bool)) ); connect(&m_inputFilter, TQT_SIGNAL(topicAuthor(const TQString&,const TQString&,TQDateTime)), this, TQT_SLOT(setTopicAuthor(const TQString&,const TQString&,TQDateTime)) ); connect(&m_inputFilter, TQT_SIGNAL(endOfWho(const TQString&)), this, TQT_SLOT(endOfWho(const TQString&)) ); connect(&m_inputFilter, TQT_SIGNAL(invitation(const TQString&,const TQString&)), this,TQT_SLOT(invitation(const TQString&,const TQString&)) ); connect(&m_inputFilter, TQT_SIGNAL(addToChannelList(const TQString&, int, const TQString& )), this, TQT_SLOT(addToChannelList(const TQString&, int, const TQString& ))); // Status View connect(this, TQT_SIGNAL(serverOnline(bool)), getStatusView(), TQT_SLOT(serverOnline(bool))); // Scripts connect(getOutputFilter(), TQT_SIGNAL(launchScript(const TQString&, const TQString&)), m_scriptLauncher, TQT_SLOT(launchScript(const TQString&, const TQString&))); connect(m_scriptLauncher, TQT_SIGNAL(scriptNotFound(const TQString&)), this, TQT_SLOT(scriptNotFound(const TQString&))); connect(m_scriptLauncher, TQT_SIGNAL(scriptExecutionError(const TQString&)), this, TQT_SLOT(scriptExecutionError(const TQString&))); // Stats connect(this, TQT_SIGNAL(sentStat(int, int)), TQT_SLOT(collectStats(int, int))); } int Server::getPort() { return getConnectionSettings().server().port(); } int Server::getLag() const { return m_currentLag; } bool Server::getAutoJoin() const { return m_autoJoin; } void Server::setAutoJoin(bool on) { m_autoJoin = on; } void Server::preShellCommandExited(TDEProcess* proc) { if (proc && proc->normalExit()) getStatusView()->appendServerMessage(i18n("Info"),"Process executed successfully!"); else getStatusView()->appendServerMessage(i18n("Warning"),"There was a problem while executing the command!"); connectToIRCServer(); connectSignals(); } void Server::connectToIRCServer() { if (!isConnected()) { updateConnectionState(Konversation::SSConnecting); m_autoIdentifyLock = false; m_ownIpByUserhost = TQString(); resetQueues(); // This is needed to support server groups with mixed SSL and nonSSL servers delete m_socket; m_socket = 0; resetNickSelection(); // connect() will do a async lookup too if(!getConnectionSettings().server().SSLEnabled()) { m_socket = new KNetwork::TDEBufferedSocket(TQString(), TQString(), 0L, "serverSocket"); connect(m_socket, TQT_SIGNAL(connected(const KResolverEntry&)), TQT_SLOT (ircServerConnectionSuccess())); } else { m_socket = new SSLSocket(getViewContainer()->getWindow(), 0L, "serverSSLSocket"); connect(m_socket, TQT_SIGNAL(sslInitDone()), TQT_SLOT(ircServerConnectionSuccess())); connect(m_socket, TQT_SIGNAL(sslFailure(const TQString&)), TQT_SIGNAL(sslInitFailure())); connect(m_socket, TQT_SIGNAL(sslFailure(const TQString&)), TQT_SLOT(sslError(const TQString&))); } m_socket->enableWrite(false); connect(m_socket, TQT_SIGNAL(hostFound()), TQT_SLOT(lookupFinished())); connect(m_socket, TQT_SIGNAL(gotError(int)), TQT_SLOT(broken(int)) ); connect(m_socket, TQT_SIGNAL(readyRead()), TQT_SLOT(incoming())); connect(m_socket, TQT_SIGNAL(closed()), TQT_SLOT(closed())); m_socket->connect(getConnectionSettings().server().host(), TQString::number(getConnectionSettings().server().port())); // set up the connection details setPrefixes(m_serverNickPrefixModes, m_serverNickPrefixes); getStatusView()->appendServerMessage(i18n("Info"),i18n("Looking for server %1:%2...") .arg(getConnectionSettings().server().host()) .arg(getConnectionSettings().server().port())); // reset InputFilter (auto request info, /WHO request info) m_inputFilter.reset(); } else kdDebug() << "connectToIRCServer() called while already connected: This should never happen." << endl; } void Server::showSSLDialog() { SSLSocket* sslsocket = dynamic_cast(m_socket); if (sslsocket) sslsocket->showInfoDialog(); } // set available channel types according to 005 RPL_ISUPPORT void Server::setChannelTypes(const TQString &pre) { m_channelPrefixes = pre; } TQString Server::getChannelTypes() const { return m_channelPrefixes; } // set user mode prefixes according to non-standard 005-Reply (see inputfilter.cpp) void Server::setPrefixes(const TQString &modes, const TQString& prefixes) { // NOTE: serverModes is TQString(), if server did not supply the // modes which relates to the network's nick-prefixes m_serverNickPrefixModes = modes; m_serverNickPrefixes = prefixes; } // return a nickname without possible mode character at the beginning void Server::mangleNicknameWithModes(TQString& nickname,bool& isAdmin,bool& isOwner, bool& isOp,bool& isHalfop,bool& hasVoice) { isAdmin = false; isOwner = false; isOp = false; isHalfop = false; hasVoice = false; int modeIndex; if (nickname.isEmpty()) return; while ((modeIndex = m_serverNickPrefixes.find(nickname[0])) != -1) { if(nickname.isEmpty()) return; nickname = nickname.mid(1); // cut off the prefix bool recognisedMode = false; // determine, whether status is like op or like voice while((modeIndex)status()) { // inform user about the error getStatusView()->appendServerMessage(i18n("Error"),i18n("Server %1 not found: %2") .arg(getConnectionSettings().server().host()) .arg(m_socket->TDESocketBase::errorString(m_socket->error()))); m_socket->resetStatus(); // broken connection broken(m_socket->error()); } else getStatusView()->appendServerMessage(i18n("Info"),i18n("Server found, connecting...")); } void Server::ircServerConnectionSuccess() { getConnectionSettings().setReconnectCount(0); Konversation::ServerSettings serverSettings = getConnectionSettings().server(); connect(this, TQT_SIGNAL(nicknameChanged(const TQString&)), getStatusView(), TQT_SLOT(setNickname(const TQString&))); getStatusView()->appendServerMessage(i18n("Info"),i18n("Connected; logging in...")); TQString connectString = "USER " + getIdentity()->getIdent() + " 8 * :" + // 8 = +i; 4 = +w getIdentity()->getRealName(); TQStringList ql; if (!serverSettings.password().isEmpty()) ql << "PASS " + serverSettings.password(); ql << "NICK "+getNickname(); ql << connectString; queueList(ql, HighPriority); emit nicknameChanged(getNickname()); m_socket->enableRead(true); } void Server::broken(int state) { kdDebug() << "Connection broken (Socket fd " << m_socket->socketDevice()->socket() << ") " << state << "!" << endl; m_socket->enableRead(false); m_socket->enableWrite(false); //FIXME if we rely on this signal, it should be turned back on somewhere... m_socket->blockSignals(true); resetQueues(); m_notifyTimer.stop(); m_pingResponseTimer.stop(); m_inputFilter.setLagMeasuring(false); m_currentLag = -1; // HACK Only show one nick change dialog at connection time if (getStatusView()) { KDialogBase* nickChangeDialog = dynamic_cast( getStatusView()->child("NickChangeDialog", "KInputDialog")); if (nickChangeDialog) nickChangeDialog->cancel(); } emit resetLag(); emit nicksNowOnline(this,TQStringList(),true); updateAutoJoin(); if (getConnectionState() != Konversation::SSDeliberatelyDisconnected) { static_cast(kapp)->notificationHandler()->connectionFailure(getStatusView(), getServerName()); TQString error = i18n("Connection to Server %1 lost: %2.") .arg(getConnectionSettings().server().host()) .arg(KNetwork::TDESocketBase::errorString((KNetwork::TDESocketBase::SocketError)state)); getStatusView()->appendServerMessage(i18n("Error"), error); updateConnectionState(Konversation::SSInvoluntarilyDisconnected); } } void Server::sslError(const TQString& reason) { TQString error = i18n("Could not connect to %1:%2 using SSL encryption.Maybe the server does not support SSL, or perhaps you have the wrong port? %3") .arg(getConnectionSettings().server().host()) .arg(getConnectionSettings().server().port()) .arg(reason); getStatusView()->appendServerMessage(i18n("SSL Connection Error"),error); updateConnectionState(Konversation::SSDeliberatelyDisconnected); } // Will be called from InputFilter as soon as the Welcome message was received void Server::connectionEstablished(const TQString& ownHost) { // Some servers don't include the userhost in RPL_WELCOME, so we // need to use RPL_USERHOST to get ahold of our IP later on if (!ownHost.isEmpty()) KNetwork::KResolver::resolveAsync(this,TQT_SLOT(gotOwnResolvedHostByWelcome(KResolverResults)),ownHost,"0"); updateConnectionState(Konversation::SSConnected); // Make a helper object to build ISON (notify) list and map offline nicks to addressbook. // TODO: Give the object a kick to get it started? m_serverISON = new ServerISON(this); // get first notify very early startNotifyTimer(1000); // Register with services registerWithServices(); // get own ip by userhost requestUserhost(getNickname()); // Start the PINGPONG match TQTimer::singleShot(1000 /*1 sec*/, this, TQT_SLOT(sendPing())); // Recreate away state if we were set away prior to a reconnect. if (m_away) { // Correct server's beliefs about its away state. m_away = false; requestAway(m_awayReason); } } void Server::registerWithServices() { if (getIdentity() && !getIdentity()->getBot().isEmpty() && !getIdentity()->getPassword().isEmpty() && !m_autoIdentifyLock) { queue("PRIVMSG "+getIdentity()->getBot()+" :identify "+getIdentity()->getPassword(), HighPriority); m_autoIdentifyLock = true; } } //FIXME operator[] inserts an empty T& so each destination might just as well have its own key storage TQCString Server::getKeyForRecipient(const TQString& recipient) const { return m_keyMap[recipient]; } void Server::setKeyForRecipient(const TQString& recipient, const TQCString& key) { m_keyMap[recipient] = key; } void Server::gotOwnResolvedHostByWelcome(KResolverResults res) { if (res.error() == KResolver::NoError && !res.isEmpty()) m_ownIpByWelcome = res.first().address().nodeName(); else kdDebug() << "Server::gotOwnResolvedHostByWelcome(): Got error: " << ( int )res.error() << endl; } void Server::quitServer() { // Make clear this is deliberate even if the QUIT never actually goes through the queue // (i.e. this is not redundant with _send_internal()'s updateConnectionState() call for // a QUIT). updateConnectionState(Konversation::SSDeliberatelyDisconnected); TQString command(Preferences::commandChar()+"QUIT"); Konversation::OutputFilterResult result = getOutputFilter()->parse(getNickname(),command, TQString()); queue(result.toServer, HighPriority); m_socket->enableRead(false); flushQueues(); m_socket->close(); getStatusView()->appendServerMessage(i18n("Info"), i18n("Disconnected from %1.").arg(getConnectionSettings().server().host())); } void Server::notifyAction(const TQString& nick) { // parse wildcards (toParse,nickname,channelName,nickList,parameter) TQString out = parseWildcards(Preferences::notifyDoubleClickAction(), getNickname(), TQString(), TQString(), nick, TQString()); // Send all strings, one after another TQStringList outList = TQStringList::split('\n',out); for (unsigned int index=0; indexparse(getNickname(),outList[index],TQString()); queue(result.toServer); } // endfor } void Server::notifyResponse(const TQString& nicksOnline) { bool nicksOnlineChanged = false; TQStringList actualList = TQStringList::split(' ',nicksOnline); TQString lcActual = ' ' + nicksOnline + ' '; TQString lcPrevISON = ' ' + (m_prevISONList.join(" ")) + ' '; TQStringList::iterator it; //Are any nicks gone offline for (it = m_prevISONList.begin(); it != m_prevISONList.end(); ++it) { if (lcActual.find(' ' + (*it) + ' ', 0, false) == -1) { setNickOffline(*it); nicksOnlineChanged = true; } } //Are any nicks gone online for (it = actualList.begin(); it != actualList.end(); ++it) { if (lcPrevISON.find(' ' + (*it) + ' ', 0, false) == -1) { setWatchedNickOnline(*it); nicksOnlineChanged = true; } } // Note: The list emitted in this signal *does* include nicks in joined channels. emit nicksNowOnline(this, actualList, nicksOnlineChanged); m_prevISONList = actualList; // Next round startNotifyTimer(); } void Server::startNotifyTimer(int msec) { // make sure the timer gets started properly in case we have reconnected m_notifyTimer.stop(); if (msec == 0) msec = Preferences::notifyDelay()*1000; // start the timer in one shot mode if (Preferences::useNotify()) m_notifyTimer.start(msec, true); } void Server::notifyTimeout() { // Notify delay time is over, send ISON request if desired if (Preferences::useNotify()) { // But only if there actually are nicks in the notify list TQString list = getISONListString(); if (!list.isEmpty()) queue("ISON "+list, LowPriority); } } void Server::autoCommandsAndChannels() { if (getServerGroup() && !getServerGroup()->connectCommands().isEmpty()) { TQString connectCommands = getServerGroup()->connectCommands(); if (!getNickname().isEmpty()) connectCommands.replace("%nick", getNickname()); TQStringList connectCommandsList = TQStringList::split(";", connectCommands); TQStringList::iterator iter; for (iter = connectCommandsList.begin(); iter != connectCommandsList.end(); ++iter) { TQString output(*iter); output = output.simplifyWhiteSpace(); getOutputFilter()->replaceAliases(output); Konversation::OutputFilterResult result = getOutputFilter()->parse(getNickname(),output,TQString()); queue(result.toServer); } } if (getAutoJoin()) { for ( TQStringList::Iterator it = m_autoJoinCommands.begin(); it != m_autoJoinCommands.end(); ++it ) queue((*it)); } } /** Create a set of indices into the nickname list of the current identity based on the current nickname. * * The index list is only used if the current nickname is not available. If the nickname is in the identity, * we do not want to retry it. If the nickname is not in the identity, it is considered to be at position -1. */ void Server::resetNickSelection() { m_nickIndices.clear(); //for equivalence testing in case the identity gets changed underneath us m_referenceNicklist = getIdentity()->getNicknameList(); //where in this identities nicklist will we have started? int start = m_referenceNicklist.findIndex(getNickname()); int len = m_referenceNicklist.count(); //we first use this list of indices *after* we've already tried the current nick, which we don't want //to retry if we wrapped, so exclude its index here //if it wasn't in the list, we get -1 back, so then we *want* to include 0 for (int i=start+1; igetNicknameList()) resetNickSelection(); TQString newNick = getIdentity()->getNickname(m_nickIndices.front()); m_nickIndices.pop_front(); if (newNick.isNull()) { TQString inputText = i18n("No nicknames from the \"%1\" identity were accepted by the connection \"%2\".\nPlease enter a new one or press Cancel to disconnect:").arg(getIdentity()->getName()).arg(getDisplayName()); newNick = KInputDialog::getText(i18n("Nickname error"), inputText, TQString(), 0, getStatusView(), "NickChangeDialog"); } return newNick; } void Server::processIncomingData() { m_incomingTimer.stop(); if (!m_inputBuffer.isEmpty() && !m_processingIncoming) { m_processingIncoming = true; TQString front(m_inputBuffer.front()); m_inputBuffer.pop_front(); if (m_rawLog) { TQString toRaw = front; m_rawLog->appendRaw(">> " + toRaw.replace("&","&").replace("<","<").replace(">",">").replace(TQRegExp("\\s"), " ")); } m_inputFilter.parseLine(front); m_processingIncoming = false; if (!m_inputBuffer.isEmpty()) m_incomingTimer.start(0); } } void Server::incoming() { if (getConnectionSettings().server().SSLEnabled()) emit sslConnected(this); // We read all available bytes here because readyRead() signal will be emitted when there is new data // else we will stall when displaying MOTD etc. int max_bytes = m_socket->bytesAvailable(); TQByteArray buffer(max_bytes+1); int len = 0; // Read at max "max_bytes" bytes into "buffer" len = m_socket->readBlock(buffer.data(),max_bytes); if (len <= 0 && getConnectionSettings().server().SSLEnabled()) return; if (len <= 0) // Zero means buffer is empty which shouldn't happen because readyRead signal is emitted { getStatusView()->appendServerMessage(i18n("Error"), i18n("There was an error reading the data from the server: %1"). arg(m_socket->TDESocketBase::errorString())); broken(m_socket->error()); return; } buffer[len] = 0; TQCString qcsBuffer = m_inputBufferIncomplete + TQCString(buffer); // split buffer to lines TQValueList qcsBufferLines; int lastLFposition = -1; for( int nextLFposition ; ( nextLFposition = qcsBuffer.find('\n', lastLFposition+1) ) != -1 ; lastLFposition = nextLFposition ) qcsBufferLines << qcsBuffer.mid(lastLFposition+1, nextLFposition-lastLFposition-1); // remember the incomplete line (split by packets) m_inputBufferIncomplete = qcsBuffer.right(qcsBuffer.length()-lastLFposition-1); while(!qcsBufferLines.isEmpty()) { // Pre parsing is needed in case encryption/decryption is needed // BEGIN set channel encoding if specified TQString senderNick; bool isServerMessage = false; TQString channelKey; TQTextCodec* codec = getIdentity()->getCodec(); TQCString front = qcsBufferLines.front(); TQStringList lineSplit = TQStringList::split(" ",codec->toUnicode(front)); if( lineSplit.count() >= 1 ) { if( lineSplit[0][0] == ':' ) // does this message have a prefix? { if( !lineSplit[0].contains('!') ) // is this a server(global) message? isServerMessage = true; else senderNick = lineSplit[0].mid(1, lineSplit[0].find('!')-1); lineSplit.pop_front(); // remove prefix } } // BEGIN pre-parse to know where the message belongs to TQString command = lineSplit[0].lower(); if( isServerMessage ) { if( lineSplit.count() >= 3 ) { if( command == "332" ) // RPL_TOPIC channelKey = lineSplit[2]; if( command == "372" ) // RPL_MOTD channelKey = ":server"; } } else // NOT a global message { if( lineSplit.count() >= 2 ) { // query if( ( command == "privmsg" || command == "notice" ) && lineSplit[1] == getNickname() ) { channelKey = senderNick; } // channel message else if( command == "privmsg" || command == "notice" || command == "join" || command == "kick" || command == "part" || command == "topic" ) { channelKey = lineSplit[1]; } } } // END pre-parse to know where the message belongs to // Decrypt if necessary if(command == "privmsg") Konversation::decrypt(channelKey,front,this); else if(command == "332" || command == "topic") { Konversation::decryptTopic(channelKey,front,this); } bool isUtf8 = Konversation::isUtf8(front); if( isUtf8 ) m_inputBuffer << TQString::fromUtf8(front); else { // check setting TQString channelEncoding; if( !channelKey.isEmpty() ) { channelEncoding = Preferences::channelEncoding(getDisplayName(), channelKey); } // END set channel encoding if specified if( !channelEncoding.isEmpty() ) codec = Konversation::IRCCharsets::self()->codecForName(channelEncoding); // if channel encoding is utf-8 and the string is definitely not utf-8 // then try latin-1 if ( !isUtf8 && codec->mibEnum() == 106 ) codec = TQTextCodec::codecForMib( 4 /* iso-8859-1 */ ); m_inputBuffer << codec->toUnicode(front); } qcsBufferLines.pop_front(); m_bytesReceived+=m_inputBuffer.back().length(); } if( !m_incomingTimer.isActive() && !m_processingIncoming ) m_incomingTimer.start(0); } /** Calculate how long this message premable will be. This is necessary because the irc server will clip messages so that the client receives a maximum of 512 bytes at once. */ int Server::getPreLength(const TQString& command, const TQString& dest) { NickInfo* info = getNickInfo(getNickname()); int hostMaskLength = 0; if(info) hostMaskLength = info->getHostmask().length(); //:Sho_!i=ehs1@konversation/developer/hein PRIVMSG #konversation :and then back to it //$nickname$hostmask$command$destination$message int x= 512 - 8 - (m_nickname.length() + hostMaskLength + command.length() + dest.length()); return x; } //Commands greater than 1 have localizeable text: 0 1 2 3 4 5 6 static TQStringList outcmds=TQStringList::split(TQChar(' '),"WHO QUIT PRIVMSG NOTICE KICK PART TOPIC"); int Server::_send_internal(TQString outputLine) { TQStringList outputLineSplit=TQStringList::split(" ", outputLine); //Lets cache the uppercase command so we don't miss or reiterate too much int outboundCommand=outcmds.findIndex(outputLineSplit[0].upper()); if (outputLine.at(outputLine.length()-1) == '\n') { kdDebug() << "found \\n on " << outboundCommand << endl; outputLine.setLength(outputLine.length()-1); } // remember the first arg of /WHO to identify responses if (outboundCommand == 0) //"WHO" { if (outputLineSplit.count() >= 2) m_inputFilter.addWhoRequest(outputLineSplit[1]); else // no argument (servers recognize it as "*") m_inputFilter.addWhoRequest("*"); } else if (outboundCommand == 1) //"QUIT" updateConnectionState(Konversation::SSDeliberatelyDisconnected); // set channel encoding if specified TQString channelCodecName; //[ PRIVMSG | NOTICE | KICK | PART | TOPIC ] target :message if (outputLineSplit.count() > 2 && outboundCommand > 1) channelCodecName=Preferences::channelEncoding(getDisplayName(), outputLineSplit[1]); TQTextCodec* codec; if (channelCodecName.isEmpty()) codec = getIdentity()->getCodec(); else codec = Konversation::IRCCharsets::self()->codecForName(channelCodecName); // Some codecs don't work with a negative value. This is a bug in TQt 3. // ex.: JIS7, eucJP, SJIS //int outlen=-1; int outlen=outputLine.length(); //leaving this done twice for now, i'm uncertain of the implications of not encoding other commands TQCString encoded=codec->fromUnicode(outputLine, outlen); TQString blowfishKey=getKeyForRecipient(outputLineSplit[1]); if (!blowfishKey.isEmpty() && outboundCommand >1) { int colon = outputLine.find(':'); if (colon > -1) { colon++; TQString pay(outputLine.mid(colon)); int len=pay.length(); //only encode the actual user text, IRCD *should* desire only ASCII 31 < x < 127 for protocol elements TQCString payload=codec->fromUnicode(pay, len); //apparently channel name isn't a protocol element... len=outputLineSplit[1].length(); TQCString dest=codec->fromUnicode(outputLineSplit[1], len); if (outboundCommand == 2 || outboundCommand == 6) // outboundCommand == 3 { bool doit = true; if (outboundCommand == 2) { //if its a privmsg and a ctcp but not an action, don't encrypt //not interpreting `payload` in case encoding bollixed it if (outputLineSplit[2].startsWith(":\x01") && outputLineSplit[2] != ":\x01""ACTION") doit = false; } if (doit) { Konversation::encrypt(blowfishKey, payload); encoded = outputLineSplit[0].ascii(); //two lines because the compiler insists on using the wrong operator+ encoded += ' ' + dest + " :" + payload; } } } } encoded += '\n'; TQ_LONG sout = m_socket->writeBlock(encoded, encoded.length()); if (m_rawLog) m_rawLog->appendRaw("<< " + outputLine.replace("&","&").replace("<","<").replace(">",">")); return sout; } void Server::toServer(TQString&s, IRCQueue* q) { int sizesent = _send_internal(s); emit sentStat(s.length(), sizesent, q); //tell the queues what we sent //tell everyone else emit sentStat(s.length(), sizesent); } void Server::collectStats(int bytes, int encodedBytes) { m_bytesSent += bytes; m_encodedBytesSent += encodedBytes; m_linesSent++; } bool Server::validQueue(QueuePriority priority) { if (priority >=0 && priority <= _max_queue()) return true; return false; } bool Server::queue(const TQString& line, QueuePriority priority) { if (!line.isEmpty() && validQueue(priority)) { IRCQueue& out=*m_queues[priority]; out.enqueue(line); return true; } return false; } bool Server::queueList(const TQStringList& buffer, QueuePriority priority) { if (buffer.isEmpty() || !validQueue(priority)) return false; IRCQueue& out=*(m_queues[priority]); for(unsigned int i=0;ireset(); } //this could flood you off, but you're leaving anyway... void Server::flushQueues() { int cue; do { cue=-1; int wait=0; for (int i=1;i<=_max_queue();i++) //slow queue can rot { IRCQueue *queue=m_queues[i]; //higher queue indices have higher priorty, higher queue priority wins tie if (!queue->isEmpty() && queue->currentWait()>=wait) { cue=i; wait=queue->currentWait(); } } if (cue>-1) m_queues[cue]->sendNow(); } while (cue>-1); } void Server::closed() { broken(m_socket->error()); } void Server::dcopRaw(const TQString& command) { if(command.startsWith(Preferences::commandChar())) { queue(command.section(Preferences::commandChar(), 1)); } else queue(command); } void Server::dcopSay(const TQString& target,const TQString& command) { if(isAChannel(target)) { Channel* channel=getChannelByName(target); if(channel) channel->sendChannelText(command); } else { class Query* query=getQueryByName(target); if(query==0) { NickInfoPtr nickinfo = obtainNickInfo(target); query=addQuery(nickinfo, true); } if(query) { if(!command.isEmpty()) query->sendQueryText(command); else { query->adjustFocus(); getViewContainer()->getWindow()->show(); KWin::demandAttention(getViewContainer()->getWindow()->winId()); KWin::activateWindow(getViewContainer()->getWindow()->winId()); } } } } void Server::dcopInfo(const TQString& string) { appendMessageToFrontmost(i18n("DCOP"),string); } void Server::ctcpReply(const TQString &receiver,const TQString &text) { queue("NOTICE "+receiver+" :"+'\x01'+text+'\x01'); } // Given a nickname, returns NickInfo object. 0 if not found. NickInfoPtr Server::getNickInfo(const TQString& nickname) { TQString lcNickname(nickname.lower()); if (m_allNicks.contains(lcNickname)) { NickInfoPtr nickinfo = m_allNicks[lcNickname]; Q_ASSERT(nickinfo); return nickinfo; } else return 0; } // Given a nickname, returns an existing NickInfo object, or creates a new NickInfo object. // Returns pointer to the found or created NickInfo object. NickInfoPtr Server::obtainNickInfo(const TQString& nickname) { NickInfoPtr nickInfo = getNickInfo(nickname); if (!nickInfo) { nickInfo = new NickInfo(nickname, this); m_allNicks.insert(TQString(nickname.lower()), nickInfo); } return nickInfo; } const NickInfoMap* Server::getAllNicks() { return &m_allNicks; } // Returns the list of members for a channel in the joinedChannels list. // 0 if channel is not in the joinedChannels list. // Using code must not alter the list. const ChannelNickMap *Server::getJoinedChannelMembers(const TQString& channelName) const { TQString lcChannelName = channelName.lower(); if (m_joinedChannels.contains(lcChannelName)) return m_joinedChannels[lcChannelName]; else return 0; } // Returns the list of members for a channel in the unjoinedChannels list. // 0 if channel is not in the unjoinedChannels list. // Using code must not alter the list. const ChannelNickMap *Server::getUnjoinedChannelMembers(const TQString& channelName) const { TQString lcChannelName = channelName.lower(); if (m_unjoinedChannels.contains(lcChannelName)) return m_unjoinedChannels[lcChannelName]; else return 0; } // Searches the Joined and Unjoined lists for the given channel and returns the member list. // 0 if channel is not in either list. // Using code must not alter the list. const ChannelNickMap *Server::getChannelMembers(const TQString& channelName) const { const ChannelNickMap *members = getJoinedChannelMembers(channelName); if (members) return members; else return getUnjoinedChannelMembers(channelName); } // Returns pointer to the ChannelNick (mode and pointer to NickInfo) for a given channel and nickname. // 0 if not found. ChannelNickPtr Server::getChannelNick(const TQString& channelName, const TQString& nickname) { TQString lcNickname = nickname.lower(); const ChannelNickMap *channelNickMap = getChannelMembers(channelName); if (channelNickMap) { if (channelNickMap->contains(lcNickname)) return (*channelNickMap)[lcNickname]; else return 0; } else { return 0; } } // Updates a nickname in a channel. If not on the joined or unjoined lists, and nick // is in the watch list, adds the channel and nick to the unjoinedChannels list. // If mode != 99, sets the mode for the nick in the channel. // Returns the NickInfo object if nick is on any lists, otherwise 0. ChannelNickPtr Server::setChannelNick(const TQString& channelName, const TQString& nickname, unsigned int mode) { TQString lcNickname = nickname.lower(); // If already on a list, update mode. ChannelNickPtr channelNick = getChannelNick(channelName, lcNickname); if (!channelNick) { // Get watch list from preferences. TQString watchlist=getWatchListString(); // Create a lower case nick list from the watch list. TQStringList watchLowerList=TQStringList::split(' ',watchlist.lower()); // If on the watch list, add channel and nick to unjoinedChannels list. if (watchLowerList.find(lcNickname) != watchLowerList.end()) { channelNick = addNickToUnjoinedChannelsList(channelName, nickname); channelNick->setMode(mode); } else return 0; } if (mode != 99) channelNick->setMode(mode); return channelNick; } // Returns a list of all the joined channels that a nick is in. TQStringList Server::getNickJoinedChannels(const TQString& nickname) { TQString lcNickname = nickname.lower(); TQStringList channellist; ChannelMembershipMap::ConstIterator channel; for( channel = m_joinedChannels.begin(); channel != m_joinedChannels.end(); ++channel ) { if (channel.data()->contains(lcNickname)) channellist.append(channel.key()); } return channellist; } // Returns a list of all the channels (joined or unjoined) that a nick is in. TQStringList Server::getNickChannels(const TQString& nickname) { TQString lcNickname = nickname.lower(); TQStringList channellist; ChannelMembershipMap::ConstIterator channel; for( channel = m_joinedChannels.begin(); channel != m_joinedChannels.end(); ++channel ) { if (channel.data()->contains(lcNickname)) channellist.append(channel.key()); } for( channel = m_unjoinedChannels.begin(); channel != m_unjoinedChannels.end(); ++channel ) { if (channel.data()->contains(lcNickname)) channellist.append(channel.key()); } return channellist; } bool Server::isNickOnline(const TQString &nickname) { NickInfoPtr nickInfo = getNickInfo(nickname); return (nickInfo != 0); } TQString Server::getOwnIpByNetworkInterface() { return m_socket->localAddress().nodeName(); } TQString Server::getOwnIpByServerMessage() { if(!m_ownIpByWelcome.isEmpty()) return m_ownIpByWelcome; else if(!m_ownIpByUserhost.isEmpty()) return m_ownIpByUserhost; else return TQString(); } class Query* Server::addQuery(const NickInfoPtr & nickInfo, bool weinitiated) { TQString nickname = nickInfo->getNickname(); // Only create new query object if there isn't already one with the same name class Query* query=getQueryByName(nickname); if (!query) { TQString lcNickname = nickname.lower(); query = getViewContainer()->addQuery(this, nickInfo, weinitiated); connect(query, TQT_SIGNAL(sendFile(const TQString&)),this, TQT_SLOT(requestDccSend(const TQString&))); connect(this, TQT_SIGNAL(serverOnline(bool)), query, TQT_SLOT(serverOnline(bool))); // Append query to internal list m_queryList.append(query); m_queryNicks.insert(lcNickname, nickInfo); if (!weinitiated) static_cast(kapp)->notificationHandler()->query(query, nickname); } // try to get hostmask if there's none yet if (query->getNickInfo()->getHostmask().isEmpty()) requestUserhost(nickname); Q_ASSERT(query); return query; } void Server::closeQuery(const TQString &name) { class Query* query = getQueryByName(name); removeQuery(query); // Update NickInfo. If no longer on any lists, delete it altogether, but // only if not on the watch list. ISON replies will determine whether the NickInfo // is deleted altogether in that case. TQString lcNickname = name.lower(); m_queryNicks.remove(lcNickname); if (!isWatchedNick(name)) deleteNickIfUnlisted(name); } void Server::closeChannel(const TQString& name) { kdDebug() << "Server::closeChannel(" << name << ")" << endl; Channel* channelToClose = getChannelByName(name); if(channelToClose) { Konversation::OutputFilterResult result = getOutputFilter()->parse(getNickname(), Preferences::commandChar() + "PART", name); queue(result.toServer); } } void Server::requestChannelList() { m_inputFilter.setAutomaticRequest("LIST", TQString(), true); queue(TQString("LIST")); } void Server::requestWhois(const TQString& nickname) { m_inputFilter.setAutomaticRequest("WHOIS", nickname, true); queue("WHOIS "+nickname, LowPriority); } void Server::requestWho(const TQString& channel) { m_inputFilter.setAutomaticRequest("WHO", channel, true); queue("WHO "+channel, LowPriority); } void Server::requestUserhost(const TQString& nicks) { TQStringList nicksList = TQStringList::split(" ", nicks); for(TQStringList::ConstIterator it=nicksList.begin() ; it!=nicksList.end() ; ++it) m_inputFilter.setAutomaticRequest("USERHOST", *it, true); queue("USERHOST "+nicks, LowPriority); } void Server::requestTopic(const TQString& channel) { m_inputFilter.setAutomaticRequest("TOPIC", channel, true); queue("TOPIC "+channel, LowPriority); } void Server::resolveUserhost(const TQString& nickname) { m_inputFilter.setAutomaticRequest("WHOIS", nickname, true); m_inputFilter.setAutomaticRequest("DNS", nickname, true); queue("WHOIS "+nickname, LowPriority); //FIXME when is this really used? } void Server::requestBan(const TQStringList& users,const TQString& channel,const TQString& a_option) { TQString hostmask; TQString option=a_option.lower(); Channel* targetChannel=getChannelByName(channel); for(unsigned int index=0;indexgetNickByName(mask); // if we found the nick try to find their hostmask if(targetNick) { TQString hostmask=targetNick->getChannelNick()->getHostmask(); // if we found the hostmask, add it to the ban mask if(!hostmask.isEmpty()) { mask=targetNick->getChannelNick()->getNickname()+'!'+hostmask; // adapt ban mask to the option given if(option=="host") mask="*!*@*."+hostmask.section('.',1); else if(option=="domain") mask="*!*@"+hostmask.section('@',1); else if(option=="userhost") mask="*!"+hostmask.section('@',0,0)+"@*."+hostmask.section('.',1); else if(option=="userdomain") mask="*!"+hostmask.section('@',0,0)+'@'+hostmask.section('@',1); } } } Konversation::OutputFilterResult result = getOutputFilter()->execBan(mask,channel); queue(result.toServer); } } void Server::requestUnban(const TQString& mask,const TQString& channel) { Konversation::OutputFilterResult result = getOutputFilter()->execUnban(mask,channel); queue(result.toServer); } void Server::requestDccSend() { requestDccSend(TQString()); } void Server::sendURIs(const TQStrList& uris, const TQString& nick) { for (TQStrListIterator it(uris) ; *it; ++it) addDccSend(nick,KURL(*it)); } void Server::requestDccSend(const TQString &a_recipient) { TQString recipient(a_recipient); // if we don't have a recipient yet, let the user select one if(recipient.isEmpty()) { TQStringList nickList; Channel* lookChannel=m_channelList.first(); // fill nickList with all nicks we know about while (lookChannel) { TQPtrList nicks=lookChannel->getNickList(); Nick* lookNick=nicks.first(); while(lookNick) { if(!nickList.contains(lookNick->getChannelNick()->getNickname())) nickList.append(lookNick->getChannelNick()->getNickname()); lookNick=nicks.next(); } lookChannel=m_channelList.next(); } // add Queries as well, but don't insert duplicates class Query* lookQuery=m_queryList.first(); while(lookQuery) { if(!nickList.contains(lookQuery->getName())) nickList.append(lookQuery->getName()); lookQuery=m_queryList.next(); } recipient=DccRecipientDialog::getNickname(getViewContainer()->getWindow(),nickList); } // do we have a recipient *now*? if(!recipient.isEmpty()) { KURL::List fileURLs=KFileDialog::getOpenURLs( ":lastDccDir", TQString(), getViewContainer()->getWindow(), i18n("Select File(s) to Send to %1").arg(recipient) ); KURL::List::iterator it; for ( it = fileURLs.begin() ; it != fileURLs.end() ; ++it ) { addDccSend( recipient, *it ); } } } void Server::slotNewDccTransferItemQueued(DccTransfer* transfer) { if (transfer->getConnectionId() == connectionId() ) { kdDebug() << "Server::slotNewDccTranfserItemQueued(): connecting slots for " << transfer->getFileName() << " [" << transfer->getType() << "]" << endl; if ( transfer->getType() == DccTransfer::Receive ) { connect( transfer, TQT_SIGNAL( done( DccTransfer* ) ), this, TQT_SLOT( dccGetDone( DccTransfer* ) ) ); connect( transfer, TQT_SIGNAL( statusChanged( DccTransfer*, int, int ) ), this, TQT_SLOT( dccStatusChanged( DccTransfer*, int, int ) ) ); } else { connect( transfer, TQT_SIGNAL( done( DccTransfer* ) ), this, TQT_SLOT( dccSendDone( DccTransfer* ) ) ); connect( transfer, TQT_SIGNAL( statusChanged( DccTransfer*, int, int ) ), this, TQT_SLOT( dccStatusChanged( DccTransfer*, int, int ) ) ); } } } void Server::addDccSend(const TQString &recipient,KURL fileURL, const TQString &altFileName, uint fileSize) { if (!fileURL.isValid()) return; emit addDccPanel(); // We already checked that the file exists in output filter / requestDccSend() resp. DccTransferSend* newDcc = KonversationApplication::instance()->getDccTransferManager()->newUpload(); newDcc->setConnectionId( connectionId() ); newDcc->setPartnerNick( recipient ); newDcc->setFileURL( fileURL ); if ( !altFileName.isEmpty() ) newDcc->setFileName( altFileName ); if ( fileSize != 0 ) newDcc->setFileSize( fileSize ); if ( newDcc->queue() ) newDcc->start(); } void Server::addDccGet(const TQString &sourceNick, const TQStringList &dccArguments) { emit addDccPanel(); DccTransferRecv* newDcc = KonversationApplication::instance()->getDccTransferManager()->newDownload(); newDcc->setConnectionId( connectionId() ); newDcc->setPartnerNick( sourceNick ); newDcc->setPartnerIp( DccCommon::numericalIpToTextIp( dccArguments[1] ) ); newDcc->setPartnerPort( dccArguments[2] ); if ( dccArguments[2] == "0" && dccArguments.count() == 5) // Reverse DCC newDcc->setReverse( true, dccArguments[4] ); newDcc->setFileName( dccArguments[0] ); newDcc->setFileSize( dccArguments[3].isEmpty() ? 0 : dccArguments[3].toULong() ); if ( newDcc->queue() ) { TQString showfile = newDcc->getFileName(); if(showfile.startsWith("\"") && showfile.endsWith("\"")) showfile = showfile.mid(1, showfile.length() - 2); appendMessageToFrontmost( i18n( "DCC" ), i18n( "%1 offers to send you \"%2\" (%3)..." ) .arg( newDcc->getPartnerNick(), showfile, ( newDcc->getFileSize() == 0 ) ? i18n( "unknown size" ) : TDEIO::convertSize( newDcc->getFileSize() ) ) ); if(Preferences::dccAutoGet()) newDcc->start(); } } void Server::openDccChat(const TQString& nickname) { emit addDccChat(getNickname(),nickname,TQStringList(),true); } void Server::requestDccChat(const TQString& partnerNick, const TQString& numericalOwnIp, const TQString& ownPort) { queue(TQString("PRIVMSG %1 :\001DCC CHAT chat %2 %3\001").arg(partnerNick).arg(numericalOwnIp).arg(ownPort)); } void Server::dccSendRequest(const TQString &partner, const TQString &fileName, const TQString &address, const TQString &port, unsigned long size) { Konversation::OutputFilterResult result = getOutputFilter()->sendRequest(partner,fileName,address,port,size); queue(result.toServer); TQString showfile = fileName; if(showfile.startsWith("\"") && showfile.endsWith("\"")) showfile = showfile.mid(1, showfile.length() - 2); appendMessageToFrontmost( i18n( "DCC" ), i18n( "Asking %1 to accept upload of \"%2\" (%3)..." ) .arg( partner, showfile, ( size == 0 ) ? i18n( "unknown size" ) : TDEIO::convertSize( size ) ) ); } void Server::dccPassiveSendRequest(const TQString& recipient,const TQString& fileName,const TQString& address,unsigned long size,const TQString& token) { Konversation::OutputFilterResult result = getOutputFilter()->passiveSendRequest(recipient,fileName,address,size,token); queue(result.toServer); } void Server::dccResumeGetRequest(const TQString &sender, const TQString &fileName, const TQString &port, TDEIO::filesize_t startAt) { Konversation::OutputFilterResult result; if (fileName.contains(" ") > 0) result = getOutputFilter()->resumeRequest(sender,"\""+fileName+"\"",port,startAt); else result = getOutputFilter()->resumeRequest(sender,fileName,port,startAt); queue(result.toServer); } void Server::dccReverseSendAck(const TQString& partnerNick,const TQString& fileName,const TQString& ownAddress,const TQString& ownPort,unsigned long size,const TQString& reverseToken) { Konversation::OutputFilterResult result = getOutputFilter()->acceptPassiveSendRequest(partnerNick,fileName,ownAddress,ownPort,size,reverseToken); queue(result.toServer); } void Server::startReverseDccSendTransfer(const TQString& sourceNick,const TQStringList& dccArguments) { DccTransferManager* dtm = KonversationApplication::instance()->getDccTransferManager(); if ( dtm->startReverseSending( connectionId(), sourceNick, dccArguments[0], // filename DccCommon::numericalIpToTextIp( dccArguments[1] ), // partner IP dccArguments[2], // partner port dccArguments[3].toInt(), // filesize dccArguments[4] // Reverse DCC token ) == 0 ) { TQString showfile = dccArguments[0]; if(showfile.startsWith("\"") && showfile.endsWith("\"")) showfile = showfile.mid(1, showfile.length() - 2); // DTM could not find a matched item appendMessageToFrontmost( i18n( "Error" ), i18n( "%1 = file name, %2 = nickname", "Received invalid passive DCC send acceptance message for \"%1\" from %2." ) .arg( showfile, sourceNick ) ); } } void Server::resumeDccGetTransfer(const TQString &sourceNick, const TQStringList &dccArguments) { DccTransferManager* dtm = KonversationApplication::instance()->getDccTransferManager(); TQString fileName( dccArguments[0] ); TQString ownPort( dccArguments[1] ); unsigned long position = dccArguments[2].toULong(); DccTransferRecv* dccTransfer = dtm->resumeDownload( connectionId(), sourceNick, fileName, ownPort, position ); TQString showfile = fileName; if(showfile.startsWith("\"") && showfile.endsWith("\"")) showfile = showfile.mid(1, showfile.length() - 2); if ( dccTransfer ) { appendMessageToFrontmost( i18n( "DCC" ), i18n( "%1 = file name, %2 = nickname of sender, %3 = percentage of file size, %4 = file size", "Resuming download of \"%1\" from %2 starting at %3% of %4..." ) .arg( showfile, sourceNick, TQString::number( dccTransfer->getProgress() ), ( dccTransfer->getFileSize() == 0 ) ? i18n( "unknown size" ) : TDEIO::convertSize( dccTransfer->getFileSize() ) ) ); } else { appendMessageToFrontmost( i18n( "Error" ), i18n( "%1 = file name, %2 = nickname", "Received invalid resume acceptance message for \"%1\" from %2." ) .arg( showfile, sourceNick ) ); } } void Server::resumeDccSendTransfer(const TQString &sourceNick, const TQStringList &dccArguments) { DccTransferManager* dtm = KonversationApplication::instance()->getDccTransferManager(); TQString fileName( dccArguments[0] ); TQString ownPort( dccArguments[1] ); unsigned long position = dccArguments[2].toULong(); DccTransferSend* dccTransfer = dtm->resumeUpload( connectionId(), sourceNick, fileName, ownPort, position ); TQString showfile = fileName; if(showfile.startsWith("\"") && showfile.endsWith("\"")) showfile = showfile.mid(1, showfile.length() - 2); if ( dccTransfer ) { appendMessageToFrontmost( i18n( "DCC" ), i18n( "%1 = file name, %2 = nickname of recipient, %3 = percentage of file size, %4 = file size", "Resuming upload of \"%1\" to %2 starting at %3% of %4...") .arg( showfile, sourceNick, TQString::number(dccTransfer->getProgress()), ( dccTransfer->getFileSize() == 0 ) ? i18n( "unknown size" ) : TDEIO::convertSize( dccTransfer->getFileSize() ) ) ); // FIXME: this operation should be done by DccTransferManager Konversation::OutputFilterResult result = getOutputFilter()->acceptResumeRequest( sourceNick, fileName, ownPort, position ); queue( result.toServer ); } else { appendMessageToFrontmost( i18n( "Error" ), i18n( "%1 = file name, %2 = nickname", "Received invalid resume request for \"%1\" from %2." ) .arg( showfile, sourceNick ) ); } } void Server::dccGetDone(DccTransfer* item) { if (!item) return; TQString showfile = item->getFileName(); if(showfile.startsWith("\"") && showfile.endsWith("\"")) showfile = showfile.mid(1, showfile.length() - 2); if(item->getStatus()==DccTransfer::Done) appendMessageToFrontmost(i18n("DCC"),i18n("%1 = file name, %2 = nickname of sender", "Download of \"%1\" from %2 finished.").arg(showfile, item->getPartnerNick())); else if(item->getStatus()==DccTransfer::Failed) appendMessageToFrontmost(i18n("DCC"),i18n("%1 = file name, %2 = nickname of sender", "Download of \"%1\" from %2 failed. Reason: %3.").arg(showfile, item->getPartnerNick(), item->getStatusDetail())); } void Server::dccSendDone(DccTransfer* item) { if (!item) return; TQString showfile = item->getFileName(); if(showfile.startsWith("\"") && showfile.endsWith("\"")) showfile = showfile.mid(1, showfile.length() - 2); if(item->getStatus()==DccTransfer::Done) appendMessageToFrontmost(i18n("DCC"),i18n("%1 = file name, %2 = nickname of recipient", "Upload of \"%1\" to %2 finished.").arg(showfile, item->getPartnerNick())); else if(item->getStatus()==DccTransfer::Failed) appendMessageToFrontmost(i18n("DCC"),i18n("%1 = file name, %2 = nickname of recipient", "Upload of \"%1\" to %2 failed. Reason: %3.").arg(showfile, item->getPartnerNick(), item->getStatusDetail())); } void Server::dccStatusChanged(DccTransfer *item, int newStatus, int oldStatus) { if(!item) return; TQString showfile = item->getFileName(); if(showfile.startsWith("\"") && showfile.endsWith("\"")) showfile = showfile.mid(1, showfile.length() - 2); if ( item->getType() == DccTransfer::Send ) { // when resuming, a message about the receiver's acceptance has been shown already, so suppress this message if ( newStatus == DccTransfer::Transferring && oldStatus == DccTransfer::WaitingRemote && !item->isResumed() ) appendMessageToFrontmost( i18n( "DCC" ), i18n( "%1 = file name, %2 nickname of recipient", "Sending \"%1\" to %2...").arg( showfile, item->getPartnerNick() ) ); } else // type == Receive { if ( newStatus == DccTransfer::Transferring && !item->isResumed() ) { appendMessageToFrontmost( i18n( "DCC" ), i18n( "%1 = file name, %2 = file size, %3 = nickname of sender", "Downloading \"%1\" (%2) from %3...") .arg( showfile, ( item->getFileSize() == 0 ) ? i18n( "unknown size" ) : TDEIO::convertSize( item->getFileSize() ), item->getPartnerNick() ) ); } } } void Server::removeQuery(class Query* query) { // Traverse through list to find the query class Query* lookQuery = m_queryList.first(); while (lookQuery) { // Did we find our query? if (lookQuery == query) { // Remove it from the query list m_queryList.remove(lookQuery); // break out of the loop lookQuery = 0; } // else select next query else lookQuery = m_queryList.next(); } query->deleteLater(); } void Server::sendJoinCommand(const TQString& name, const TQString& password) { Konversation::OutputFilterResult result = getOutputFilter()->parse(getNickname(), Preferences::commandChar() + "JOIN " + name + ' ' + password, TQString()); queue(result.toServer); } void Server::joinChannel(const TQString& name, const TQString& hostmask) { // (re-)join channel, open a new panel if needed Channel* channel = getChannelByName(name); if (!channel) { channel=getViewContainer()->addChannel(this,name); Q_ASSERT(channel); channel->setIdentity(getIdentity()); channel->setNickname(getNickname()); channel->indicateAway(m_away); if (getServerGroup()) { Konversation::ChannelSettings channelSettings = getServerGroup()->channelByNameFromHistory(name); channel->setNotificationsEnabled(channelSettings.enableNotifications()); getServerGroup()->appendChannelHistory(channelSettings); } m_channelList.append(channel); connect(channel,TQT_SIGNAL (sendFile()),this,TQT_SLOT (requestDccSend()) ); connect(this, TQT_SIGNAL(nicknameChanged(const TQString&)), channel, TQT_SLOT(setNickname(const TQString&))); } // Move channel from unjoined (if present) to joined list and add our own nickname to the joined list. ChannelNickPtr channelNick = addNickToJoinedChannelsList(name, getNickname()); if ((channelNick->getHostmask() != hostmask ) && !hostmask.isEmpty()) { NickInfoPtr nickInfo = channelNick->getNickInfo(); nickInfo->setHostmask(hostmask); } channel->joinNickname(channelNick); } void Server::removeChannel(Channel* channel) { // Update NickInfo. removeJoinedChannel(channel->getName()); if (getServerGroup()) { Konversation::ChannelSettings channelSettings = getServerGroup()->channelByNameFromHistory(channel->getName()); channelSettings.setNotificationsEnabled(channel->notificationsEnabled()); getServerGroup()->appendChannelHistory(channelSettings); } m_channelList.removeRef(channel); } void Server::updateChannelMode(const TQString &updater, const TQString &channelName, char mode, bool plus, const TQString ¶meter) { Channel* channel=getChannelByName(channelName); if(channel) //Let the channel be verbose to the screen about the change, and update channelNick channel->updateMode(updater, mode, plus, parameter); // TODO: What is mode character for owner? // Answer from JOHNFLUX - I think that admin is the same as owner. Channel.h has owner as "a" // "q" is the likely answer.. UnrealIRCd and euIRCd use it. // TODO these need to become dynamic TQString userModes="vhoqa"; // voice halfop op owner admin int modePos = userModes.find(mode); if (modePos > 0) { ChannelNickPtr updateeNick = getChannelNick(channelName, parameter); if(!updateeNick) { /* if(parameter.isEmpty()) { kdDebug() << "in updateChannelMode, a nick with no-name has had their mode '" << mode << "' changed to (" <setMode(mode, plus); // Note that channel will be moved to joined list if necessary. addNickToJoinedChannelsList(channelName, parameter); } // Update channel ban list. if (mode == 'b') { if (plus) { TQDateTime when; addBan(channelName, TQString("%1 %2 %3").arg(parameter).arg(updater).arg(TQDateTime::currentDateTime().toTime_t())); } else { removeBan(channelName, parameter); } } } void Server::updateChannelModeWidgets(const TQString &channelName, char mode, const TQString ¶meter) { Channel* channel=getChannelByName(channelName); if(channel) channel->updateModeWidgets(mode,true,parameter); } void Server::updateChannelQuickButtons() { Channel* channel=m_channelList.first(); while (channel) { channel->updateQuickButtons(Preferences::quickButtonList()); channel = m_channelList.next(); } } Channel* Server::getChannelByName(const TQString& name) { // Convert wanted channel name to lowercase TQString wanted=name; wanted=wanted.lower(); // Traverse through list to find the channel named "name" Channel* lookChannel =m_channelList.first(); while (lookChannel) { if (lookChannel->getName().lower()==wanted) return lookChannel; lookChannel = m_channelList.next(); } // No channel by that name found? Return 0. Happens on first channel join return 0; } class Query* Server::getQueryByName(const TQString& name) { // Convert wanted query name to lowercase TQString wanted=name; wanted=wanted.lower(); // Traverse through list to find the query with "name" class Query* lookQuery=m_queryList.first(); while(lookQuery) { if(lookQuery->getName().lower()==wanted) return lookQuery; lookQuery=m_queryList.next(); } // No query by that name found? Must be a new query request. Return 0 return 0; } void Server::resetNickList(const TQString& channelName) { Channel* outChannel=getChannelByName(channelName); if(outChannel) outChannel->resetNickList(); } void Server::addPendingNickList(const TQString& channelName,const TQStringList& nickList) { Channel* outChannel=getChannelByName(channelName); if(outChannel) outChannel->addPendingNickList(nickList); } // Adds a nickname to the joinedChannels list. // Creates new NickInfo if necessary. // If needed, moves the channel from the unjoined list to the joined list. // Returns the NickInfo for the nickname. ChannelNickPtr Server::addNickToJoinedChannelsList(const TQString& channelName, const TQString& nickname) { bool doChannelJoinedSignal = false; bool doWatchedNickChangedSignal = false; bool doChannelMembersChangedSignal = false; TQString lcNickname = nickname.lower(); // Create NickInfo if not already created. NickInfoPtr nickInfo = getNickInfo(nickname); if (!nickInfo) { nickInfo = new NickInfo(nickname, this); m_allNicks.insert(lcNickname, nickInfo); doWatchedNickChangedSignal = isWatchedNick(nickname); } // if nickinfo already exists update nickname, in case we created the nickinfo based // on e.g. an incorrectly capitalized ISON request else nickInfo->setNickname(nickname); // Move the channel from unjoined list (if present) to joined list. TQString lcChannelName = channelName.lower(); ChannelNickMap *channel; if (m_unjoinedChannels.contains(lcChannelName)) { channel = m_unjoinedChannels[lcChannelName]; m_unjoinedChannels.remove(lcChannelName); m_joinedChannels.insert(lcChannelName, channel); doChannelJoinedSignal = true; } else { // Create a new list in the joined channels if not already present. if (!m_joinedChannels.contains(lcChannelName)) { channel = new ChannelNickMap; m_joinedChannels.insert(lcChannelName, channel); doChannelJoinedSignal = true; } else channel = m_joinedChannels[lcChannelName]; } // Add NickInfo to channel list if not already in the list. ChannelNickPtr channelNick; if (!channel->contains(lcNickname)) { channelNick = new ChannelNick(nickInfo, false, false, false, false, false); Q_ASSERT(channelNick); channel->insert(lcNickname, channelNick); doChannelMembersChangedSignal = true; } channelNick = (*channel)[lcNickname]; Q_ASSERT(channelNick); //Since we just added it if it didn't exist, it should be guaranteed to exist now if (doWatchedNickChangedSignal) emit watchedNickChanged(this, nickname, true); if (doChannelJoinedSignal) emit channelJoinedOrUnjoined(this, channelName, true); if (doChannelMembersChangedSignal) emit channelMembersChanged(this, channelName, true, false, nickname); return channelNick; } /** This function should _only_ be called from the ChannelNick class. * This function should also be the only one to emit this signal. * In this class, when channelNick is changed, it emits its own signal, and * calls this function itself. */ void Server::emitChannelNickChanged(const ChannelNickPtr channelNick) { emit channelNickChanged(this, channelNick); } /** This function should _only_ be called from the NickInfo class. * This function should also be the only one to emit this signal. * In this class, when nickInfo is changed, it emits its own signal, and * calls this function itself. */ void Server::emitNickInfoChanged(const NickInfoPtr nickInfo) { emit nickInfoChanged(this, nickInfo); } // Adds a nickname to the unjoinedChannels list. // Creates new NickInfo if necessary. // If needed, moves the channel from the joined list to the unjoined list. // If mode != 99 sets the mode for this nick in this channel. // Returns the NickInfo for the nickname. ChannelNickPtr Server::addNickToUnjoinedChannelsList(const TQString& channelName, const TQString& nickname) { bool doChannelUnjoinedSignal = false; bool doWatchedNickChangedSignal = false; bool doChannelMembersChangedSignal = false; TQString lcNickname = nickname.lower(); // Create NickInfo if not already created. NickInfoPtr nickInfo = getNickInfo(nickname); if (!nickInfo) { nickInfo = new NickInfo(nickname, this); m_allNicks.insert(lcNickname, nickInfo); doWatchedNickChangedSignal = isWatchedNick(nickname); } // Move the channel from joined list (if present) to unjoined list. TQString lcChannelName = channelName.lower(); ChannelNickMap *channel; if (m_joinedChannels.contains(lcChannelName)) { channel = m_joinedChannels[lcChannelName]; m_joinedChannels.remove(lcChannelName); m_unjoinedChannels.insert(lcChannelName, channel); doChannelUnjoinedSignal = true; } else { // Create a new list in the unjoined channels if not already present. if (!m_unjoinedChannels.contains(lcChannelName)) { channel = new ChannelNickMap; m_unjoinedChannels.insert(lcChannelName, channel); doChannelUnjoinedSignal = true; } else channel = m_unjoinedChannels[lcChannelName]; } // Add NickInfo to unjoinedChannels list if not already in the list. ChannelNickPtr channelNick; if (!channel->contains(lcNickname)) { channelNick = new ChannelNick(nickInfo, false, false, false, false, false); channel->insert(lcNickname, channelNick); doChannelMembersChangedSignal = true; } channelNick = (*channel)[lcNickname]; // Set the mode for the nick in this channel. if (doWatchedNickChangedSignal) emit watchedNickChanged(this, nickname, true); if (doChannelUnjoinedSignal) emit channelJoinedOrUnjoined(this, channelName, false); if (doChannelMembersChangedSignal) emit channelMembersChanged(this, channelName, false, false, nickname); return channelNick; } /** * If not already online, changes a nick to the online state by creating * a NickInfo for it and emits various signals and messages for it. * This method should only be called for nicks on the watch list. * @param nickname The nickname that is online. * @return Pointer to NickInfo for nick. */ NickInfoPtr Server::setWatchedNickOnline(const TQString& nickname) { NickInfoPtr nickInfo = getNickInfo(nickname); if (!nickInfo) { TQString lcNickname = nickname.lower(); nickInfo = new NickInfo(nickname, this); m_allNicks.insert(lcNickname, nickInfo); } emit watchedNickChanged(this, nickname, true); TDEABC::Addressee addressee = nickInfo->getAddressee(); if (!addressee.isEmpty()) Konversation::Addressbook::self()->emitContactPresenceChanged(addressee.uid()); appendMessageToFrontmost(i18n("Notify"),""+ i18n("%1 is online (%2).").arg(nickname).arg(getServerName())+"", getStatusView()); static_cast(kapp)->notificationHandler()->nickOnline(getStatusView(), nickname); nickInfo->setPrintedOnline(true); return nickInfo; } void Server::setWatchedNickOffline(const TQString& nickname, const NickInfoPtr nickInfo) { if (nickInfo) { TDEABC::Addressee addressee = nickInfo->getAddressee(); if (!addressee.isEmpty()) Konversation::Addressbook::self()->emitContactPresenceChanged(addressee.uid(), 1); } emit watchedNickChanged(this, nickname, false); appendMessageToFrontmost(i18n("Notify"), i18n("%1 went offline (%2).").arg(nickname).arg(getServerName()), getStatusView()); static_cast(kapp)->notificationHandler()->nickOffline(getStatusView(), nickname); } bool Server::setNickOffline(const TQString& nickname) { TQString lcNickname = nickname.lower(); NickInfoPtr nickInfo = getNickInfo(lcNickname); bool wasOnline = nickInfo->getPrintedOnline(); if (nickInfo && wasOnline) { // Delete from query list, if present. if (m_queryNicks.contains(lcNickname)) m_queryNicks.remove(lcNickname); // Delete the nickname from all channels (joined or unjoined). TQStringList nickChannels = getNickChannels(lcNickname); TQStringList::iterator itEnd = nickChannels.end(); for(TQStringList::iterator it = nickChannels.begin(); it != itEnd; ++it) { TQString channel = (*it); removeChannelNick(channel, lcNickname); } // Delete NickInfo. if (m_allNicks.contains(lcNickname)) m_allNicks.remove(lcNickname); // If the nick was in the watch list, emit various signals and messages. if (isWatchedNick(nickname)) setWatchedNickOffline(nickname, nickInfo); nickInfo->setPrintedOnline(false); } return (nickInfo != 0); } /** * If nickname is no longer on any channel list, or the query list, delete it altogether. * Call this routine only if the nick is not on the notify list or is on the notify * list but is known to be offline. * @param nickname The nickname to be deleted. Case insensitive. * @return True if the nickname is deleted. */ bool Server::deleteNickIfUnlisted(const TQString &nickname) { TQString lcNickname = nickname.lower(); // Don't delete our own nickinfo. if (lcNickname == loweredNickname()) return false; if (!m_queryNicks.contains(lcNickname)) { TQStringList nickChannels = getNickChannels(nickname); if (nickChannels.isEmpty()) { m_allNicks.remove(lcNickname); return true; } } return false; } /** * Remove nickname from a channel (on joined or unjoined lists). * @param channelName The channel name. Case insensitive. * @param nickname The nickname. Case insensitive. */ void Server::removeChannelNick(const TQString& channelName, const TQString& nickname) { bool doSignal = false; bool joined = false; TQString lcChannelName = channelName.lower(); TQString lcNickname = nickname.lower(); ChannelNickMap *channel; if (m_joinedChannels.contains(lcChannelName)) { channel = m_joinedChannels[lcChannelName]; if (channel->contains(lcNickname)) { channel->remove(lcNickname); doSignal = true; joined = true; // Note: Channel should not be empty because user's own nick should still be // in it, so do not need to delete empty channel here. } } else { if (m_unjoinedChannels.contains(lcChannelName)) { channel = m_unjoinedChannels[lcChannelName]; if (channel->contains(lcNickname)) { channel->remove(lcNickname); doSignal = true; joined = false; // If channel is now empty, delete it. // Caution: Any iterators across unjoinedChannels will be come invalid here. if (channel->isEmpty()) m_unjoinedChannels.remove(lcChannelName); } } } if (doSignal) emit channelMembersChanged(this, channelName, joined, true, nickname); } TQStringList Server::getWatchList() { // no nickinfo ISON for the time being return Preferences::notifyListByGroupName(getDisplayName()); if (m_serverISON) return m_serverISON->getWatchList(); else return TQStringList(); } TQString Server::getWatchListString() { return getWatchList().join(" "); } TQStringList Server::getISONList() { // no nickinfo ISON for the time being return Preferences::notifyListByGroupName(getDisplayName()); if (m_serverISON) return m_serverISON->getISONList(); else return TQStringList(); } TQString Server::getISONListString() { return getISONList().join(" "); } /** * Return true if the given nickname is on the watch list. */ bool Server::isWatchedNick(const TQString& nickname) { // Get watch list from preferences. TQString watchlist= ' ' + getWatchListString() + ' '; // Search case-insensitivly return (watchlist.find(' ' + nickname + ' ', 0, 0) != -1); } /** * Remove channel from the joined list, placing it in the unjoined list. * All the unwatched nicks are removed from the channel. If the channel becomes * empty, it is deleted. * @param channelName Name of the channel. Case sensitive. */ void Server::removeJoinedChannel(const TQString& channelName) { bool doSignal = false; TQStringList watchListLower = getWatchList(); TQString lcChannelName = channelName.lower(); // Move the channel nick list from the joined to unjoined lists. if (m_joinedChannels.contains(lcChannelName)) { doSignal = true; ChannelNickMap* channel = m_joinedChannels[lcChannelName]; m_joinedChannels.remove(lcChannelName); m_unjoinedChannels.insert(lcChannelName, channel); // Remove nicks not on the watch list. bool allDeleted = true; Q_ASSERT(channel); if(!channel) return; //already removed.. hmm ChannelNickMap::Iterator member; for ( member = channel->begin(); member != channel->end() ;) { TQString lcNickname = member.key(); if (watchListLower.find(lcNickname) == watchListLower.end()) { // Remove the unwatched nickname from the unjoined channel. channel->remove(member); // If the nick is no longer listed in any channels or query list, delete it altogether. deleteNickIfUnlisted(lcNickname); member = channel->begin(); } else { allDeleted = false; ++member; } } // If all were deleted, remove the channel from the unjoined list. if (allDeleted) { channel = m_unjoinedChannels[lcChannelName]; m_unjoinedChannels.remove(lcChannelName); delete channel; // recover memory! } } if (doSignal) emit channelJoinedOrUnjoined(this, channelName, false); } // Renames a nickname in all NickInfo lists. // Returns pointer to the NickInfo object or 0 if nick not found. void Server::renameNickInfo(NickInfoPtr nickInfo, const TQString& newname) { if (nickInfo) { // Get existing lowercase nickname and rename nickname in the NickInfo object. TQString lcNickname = nickInfo->loweredNickname(); nickInfo->setNickname(newname); nickInfo->setIdentified(false); TQString lcNewname = newname.lower(); // Rename the key in m_allNicks list. m_allNicks.remove(lcNickname); m_allNicks.insert(lcNewname, nickInfo); // Rename key in the joined and unjoined lists. TQStringList nickChannels = getNickChannels(lcNickname); TQStringList::iterator itEnd = nickChannels.end(); for(TQStringList::iterator it = nickChannels.begin(); it != itEnd; ++it) { const ChannelNickMap *channel = getChannelMembers(*it); Q_ASSERT(channel); ChannelNickPtr member = (*channel)[lcNickname]; Q_ASSERT(member); const_cast(channel)->remove(lcNickname); const_cast(channel)->insert(lcNewname, member); } // Rename key in Query list. if (m_queryNicks.contains(lcNickname)) { m_queryNicks.remove(lcNickname); m_queryNicks.insert(lcNewname, nickInfo); } } else { kdDebug() << "server::renameNickInfo() was called for newname='" << newname << "' but nickInfo is null" << endl; } } Channel* Server::nickJoinsChannel(const TQString &channelName, const TQString &nickname, const TQString &hostmask) { Channel* outChannel=getChannelByName(channelName); if(outChannel) { // Update NickInfo. ChannelNickPtr channelNick = addNickToJoinedChannelsList(channelName, nickname); NickInfoPtr nickInfo = channelNick->getNickInfo(); if ((nickInfo->getHostmask() != hostmask) && !hostmask.isEmpty()) { nickInfo->setHostmask(hostmask); } outChannel->joinNickname(channelNick); } return outChannel; } void Server::addHostmaskToNick(const TQString& sourceNick, const TQString& sourceHostmask) { // Update NickInfo. NickInfoPtr nickInfo=getNickInfo(sourceNick); if (nickInfo) { if ((nickInfo->getHostmask() != sourceHostmask) && !sourceHostmask.isEmpty()) { nickInfo->setHostmask(sourceHostmask); } } } Channel* Server::removeNickFromChannel(const TQString &channelName, const TQString &nickname, const TQString &reason, bool quit) { Channel* outChannel=getChannelByName(channelName); if(outChannel) { ChannelNickPtr channelNick = getChannelNick(channelName, nickname); if(channelNick) outChannel->removeNick(channelNick,reason,quit); } // Remove the nick from the channel. removeChannelNick(channelName, nickname); // If not listed in any channel, and not on query list, delete the NickInfo, // but only if not on the notify list. ISON replies will take care of deleting // the NickInfo, if on the notify list. if (!isWatchedNick(nickname)) { TQString nicky = nickname; deleteNickIfUnlisted(nicky); } return outChannel; } void Server::nickWasKickedFromChannel(const TQString &channelName, const TQString &nickname, const TQString &kicker, const TQString &reason) { Channel* outChannel=getChannelByName(channelName); if(outChannel) { ChannelNickPtr channelNick = getChannelNick(channelName, nickname); if(channelNick) { outChannel->kickNick(channelNick, kicker, reason); // Tell Nickinfo removeChannelNick(channelName,nickname); } } } void Server::removeNickFromServer(const TQString &nickname,const TQString &reason) { Channel* channel = m_channelList.first(); while (channel) { // Check if nick is in this channel or not. if(channel->getNickByName(nickname)) removeNickFromChannel(channel->getName(),nickname,reason,true); channel = m_channelList.next(); } Query* query=getQueryByName(nickname); if (query) query->quitNick(reason); // Delete the nick from all channels and then delete the nickinfo, // emitting signal if on the watch list. setNickOffline(nickname); } void Server::renameNick(const TQString &nickname, const TQString &newNick) { if(nickname.isEmpty() || newNick.isEmpty()) { kdDebug() << "server::renameNick called with empty strings! Trying to rename '" << nickname << "' to '" << newNick << "'" << endl; return; } // If this was our own nickchange, tell our server object about it if (nickname == getNickname()) { setNickname(newNick); // We may get a request from nickserv, so remove the auto-identify lock. m_autoIdentifyLock = false; } //Actually do the rename. NickInfoPtr nickInfo = getNickInfo(nickname); if(!nickInfo) { kdDebug() << "server::renameNick called for nickname '" << nickname << "' to '" << newNick << "' but getNickInfo('" << nickname << "') returned no results." << endl; } else { renameNickInfo(nickInfo, newNick); //The rest of the code below allows the channels to echo to the user to tell them that the nick has changed. // Rename the nick in every channel they are in Channel* channel=m_channelList.first(); while (channel) { // All we do is notify that the nick has been renamed.. we haven't actually renamed it yet // Note that NickPanel has already updated, so pass new nick to getNickByName. if (channel->getNickByName(newNick)) channel->nickRenamed(nickname, *nickInfo); channel = m_channelList.next(); } //Watched nicknames stuff if (isWatchedNick(nickname)) setWatchedNickOffline(nickname, 0); } // If we had a query with this nick, change that name, too } void Server::userhost(const TQString& nick,const TQString& hostmask,bool away,bool /* ircOp */) { addHostmaskToNick(nick, hostmask); // remember my IP for DCC things // myself if (m_ownIpByUserhost.isEmpty() && nick == getNickname()) { TQString myhost = hostmask.section('@', 1); // Use async lookup else you will be blocking GUI badly KNetwork::KResolver::resolveAsync(this,TQT_SLOT(gotOwnResolvedHostByUserhost(KResolverResults)),myhost,"0"); } NickInfoPtr nickInfo = getNickInfo(nick); if (nickInfo) { if (nickInfo->isAway() != away) { nickInfo->setAway(away); } } } void Server::gotOwnResolvedHostByUserhost(KResolverResults res) { if ( res.error() == KResolver::NoError && !res.isEmpty() ) m_ownIpByUserhost = res.first().address().nodeName(); else kdDebug() << "Server::gotOwnResolvedHostByUserhost(): Got error: " << ( int )res.error() << endl; } void Server::appendServerMessageToChannel(const TQString& channel,const TQString& type,const TQString& message) { Channel* outChannel = getChannelByName(channel); if (outChannel) outChannel->appendServerMessage(type,message); } void Server::appendCommandMessageToChannel(const TQString& channel,const TQString& command,const TQString& message, bool highlight) { Channel* outChannel = getChannelByName(channel); if (outChannel) { outChannel->appendCommandMessage(command,message,true,true,!highlight); } else { appendStatusMessage(command, TQString("%1 %2").arg(channel).arg(message)); } } void Server::appendStatusMessage(const TQString& type,const TQString& message) { getStatusView()->appendServerMessage(type,message); } void Server::appendMessageToFrontmost(const TQString& type,const TQString& message, bool parseURL) { getViewContainer()->appendToFrontmost(type, message, getStatusView(), parseURL); } void Server::setNickname(const TQString &newNickname) { m_nickname = newNickname; m_loweredNickname = newNickname.lower(); emit nicknameChanged(newNickname); } void Server::setChannelTopic(const TQString &channel, const TQString &newTopic) { Channel* outChannel = getChannelByName(channel); if(outChannel) { // encoding stuff is done in send() outChannel->setTopic(newTopic); } } // Overloaded void Server::setChannelTopic(const TQString& nickname, const TQString &channel, const TQString &newTopic) { Channel* outChannel = getChannelByName(channel); if(outChannel) { // encoding stuff is done in send() outChannel->setTopic(nickname,newTopic); } } void Server::setTopicAuthor(const TQString& channel, const TQString& author, TQDateTime time) { Channel* outChannel = getChannelByName(channel); if(outChannel) outChannel->setTopicAuthor(author, time); } void Server::endOfWho(const TQString& target) { Channel* channel = getChannelByName(target); if(channel) channel->scheduleAutoWho(); } bool Server::isNickname(const TQString &compare) const { return (m_nickname == compare); } TQString Server::getNickname() const { return m_nickname; } TQString Server::loweredNickname() const { return m_loweredNickname; } TQString Server::parseWildcards(const TQString& toParse, const TQString& sender, const TQString& channelName, const TQString& channelKey, const TQString& nick, const TQString& parameter) { return parseWildcards(toParse,sender,channelName,channelKey,TQStringList::split(' ',nick),parameter); } TQString Server::parseWildcards(const TQString& toParse, const TQString& sender, const TQString& channelName, const TQString& channelKey, const TQStringList& nickList, const TQString& /*parameter*/) { // TODO: parameter handling, since parameters are not functional yet // store the parsed version TQString out; // default separator TQString separator(" "); int index = 0, found = 0; TQChar toExpand; while ((found = toParse.find('%',index)) != -1) { // append part before the % out.append(toParse.mid(index,found-index)); index = found + 1; // skip the part before, including % if (index >= (int)toParse.length()) break; // % was the last char (not valid) toExpand = toParse.at(index++); if (toExpand == 's') { found = toParse.find('%',index); if (found == -1) // no other % (not valid) break; separator = toParse.mid(index,found-index); index = found + 1; // skip separator, including % } else if (toExpand == 'u') { out.append(nickList.join(separator)); } else if (toExpand == 'c') { if(!channelName.isEmpty()) out.append(channelName); } else if (toExpand == 'o') { out.append(sender); } else if (toExpand == 'k') { if(!channelKey.isEmpty()) out.append(channelKey); } else if (toExpand == 'K') { if(getConnectionSettings().server().password().isEmpty()) out.append(getConnectionSettings().server().password()); } else if (toExpand == 'n') { out.append("\n"); } else if (toExpand == 'p') { out.append("%"); } } // append last part out.append(toParse.mid(index,toParse.length()-index)); return out; } void Server::sendToAllChannels(const TQString &text) { // Send a message to all channels we are in Channel* channel = m_channelList.first(); while (channel) { channel->sendChannelText(text); channel = m_channelList.next(); } } void Server::invitation(const TQString& nick,const TQString& channel) { if(KMessageBox::questionYesNo(getViewContainer()->getWindow(), i18n("You were invited by %1 to join channel %2. " "Do you accept the invitation?").arg(nick).arg(channel), i18n("Invitation"), i18n("Join"), i18n("Ignore"), "Invitation")==KMessageBox::Yes) { sendJoinCommand(channel); } } void Server::scriptNotFound(const TQString& name) { appendMessageToFrontmost(i18n("DCOP"),i18n("Error: Could not find script \"%1\".").arg(name)); } void Server::scriptExecutionError(const TQString& name) { appendMessageToFrontmost(i18n("DCOP"),i18n("Error: Could not execute script \"%1\". Check file permissions.").arg(name)); } bool Server::isAChannel(const TQString &channel) const { return (getChannelTypes().contains(channel.at(0)) > 0); } void Server::addRawLog(bool show) { if (!m_rawLog) m_rawLog = getViewContainer()->addRawLog(this); connect(this, TQT_SIGNAL(serverOnline(bool)), m_rawLog, TQT_SLOT(serverOnline(bool))); // bring raw log to front since the main window does not do this for us if (show) emit showView(m_rawLog); } void Server::closeRawLog() { if (m_rawLog) delete m_rawLog; } ChannelListPanel* Server::addChannelListPanel() { if(!m_channelListPanel) { m_channelListPanel = getViewContainer()->addChannelListPanel(this); connect(m_channelListPanel, TQT_SIGNAL(refreshChannelList()), this, TQT_SLOT(requestChannelList())); connect(m_channelListPanel, TQT_SIGNAL(joinChannel(const TQString&)), this, TQT_SLOT(sendJoinCommand(const TQString&))); connect(this, TQT_SIGNAL(serverOnline(bool)), m_channelListPanel, TQT_SLOT(serverOnline(bool))); } return m_channelListPanel; } void Server::addToChannelList(const TQString& channel, int users, const TQString& topic) { addChannelListPanel(); m_channelListPanel->addToChannelList(channel, users, topic); } ChannelListPanel* Server::getChannelListPanel() const { return m_channelListPanel; } void Server::closeChannelListPanel() { if (m_channelListPanel) delete m_channelListPanel; } void Server::updateAutoJoin(Konversation::ChannelSettings channel) { if (!channel.name().isEmpty()) { setAutoJoin(true); setAutoJoinCommands(TQStringList("JOIN " + channel.name() + " " + channel.password())); return; } Konversation::ChannelList tmpList; if (m_channelList.isEmpty() && getServerGroup()) tmpList = getServerGroup()->channelList(); else { TQPtrListIterator it(m_channelList); Channel* channel; while ((channel = it.current()) != 0) { ++it; tmpList << channel->channelSettings(); } } if (!tmpList.isEmpty()) { setAutoJoin(true); TQStringList channels; TQStringList passwords; TQStringList joinCommands; uint length = 0; Konversation::ChannelList::iterator it; for (it = tmpList.begin(); it != tmpList.end(); ++it) { TQString channel = (*it).name();; TQString password = ((*it).password().isEmpty() ? "." : (*it).password()); int tempLen = channel.length(); length += getIdentity()->getCodec()->fromUnicode(channel, tempLen).length(); tempLen = password.length(); length += getIdentity()->getCodec()->fromUnicode(password, tempLen).length(); if (length + 6 < 512) // 6: "JOIN " plus separating space between chans and pws. { channels << channel; passwords << password; } else { if (passwords.last() == ".") passwords.pop_back(); joinCommands << "JOIN " + channels.join(",") + " " + passwords.join(","); channels.clear(); passwords.clear(); channels << channel; passwords << password; length = 0; tempLen = channel.length(); length += getIdentity()->getCodec()->fromUnicode(channel, tempLen).length(); tempLen = password.length(); length += getIdentity()->getCodec()->fromUnicode(password, tempLen).length(); } } if (passwords.last() == ".") passwords.pop_back(); joinCommands << "JOIN " + channels.join(",") + " " + passwords.join(","); setAutoJoinCommands(joinCommands); } else setAutoJoin(false); } ViewContainer* Server::getViewContainer() const { KonversationApplication* konvApp = static_cast(kapp); return konvApp->getMainWindow()->getViewContainer(); } bool Server::getUseSSL() const { SSLSocket* sslsocket = dynamic_cast(m_socket); return (sslsocket != 0); } TQString Server::getSSLInfo() const { SSLSocket* sslsocket = dynamic_cast(m_socket); if(sslsocket) return sslsocket->details(); return TQString(); } void Server::sendMultiServerCommand(const TQString& command, const TQString& parameter) { emit multiServerCommand(command, parameter); } void Server::executeMultiServerCommand(const TQString& command, const TQString& parameter) { if (command == "msg") sendToAllChannelsAndQueries(parameter); else sendToAllChannelsAndQueries(Preferences::commandChar() + command + ' ' + parameter); } void Server::sendToAllChannelsAndQueries(const TQString& text) { // Send a message to all channels we are in Channel* channel = m_channelList.first(); while (channel) { channel->sendChannelText(text); channel = m_channelList.next(); } // Send a message to all queries we are in class Query* query = m_queryList.first(); while (query) { query->sendQueryText(text); query = m_queryList.next(); } } bool Server::isSocketConnected() const { if (!m_socket) return false; return (m_socket->state() == KNetwork::KClientSocketBase::Connected); } void Server::updateConnectionState(Konversation::ConnectionState state) { if (state != m_connectionState) { m_connectionState = state; if (m_connectionState == Konversation::SSConnected) emit serverOnline(true); else if (m_connectionState != Konversation::SSConnecting) emit serverOnline(false); emit connectionStateChanged(this, state); } } void Server::reconnect() { if (isConnecting() || isSocketConnected()) quitServer(); // Use asynchronous invocation so that the broken() that the above // quitServer might cause is delivered before connectToIRCServer // sets SSConnecting and broken() announces a deliberate disconnect // due to the failure allegedly occuring during SSConnecting. TQTimer::singleShot(0, this, TQT_SLOT(connectToIRCServer())); } void Server::disconnect() { if (isSocketConnected()) quitServer(); } void Server::requestAway(const TQString& reason) { TQString awayReason = reason; IdentityPtr identity = getIdentity(); if (awayReason.isEmpty() || !identity) awayReason = i18n("Gone away for now"); setAwayReason(awayReason); queue("AWAY :" + awayReason); } void Server::requestUnaway() { queue("AWAY"); } void Server::setAway(bool away) { IdentityPtr identity = getIdentity(); if (away) { if (!m_away) startAwayTimer(); m_away = true; emit awayState(true); if (identity && !identity->getAwayNick().isEmpty() && identity->getAwayNick() != getNickname()) { m_nonAwayNick = getNickname(); queue("NICK " + getIdentity()->getAwayNick()); } appendMessageToFrontmost(i18n("Away"), i18n("You are now marked as being away.")); if (identity && identity->getShowAwayMessage()) { TQString message = identity->getAwayMessage(); sendToAllChannels(message.replace(TQRegExp("%s", false), m_awayReason)); } if (identity && identity->getInsertRememberLineOnAway()) emit awayInsertRememberLine(this); } else { m_awayReason = TQString(); emit awayState(false); if (!identity->getAwayNick().isEmpty() && !m_nonAwayNick.isEmpty()) { queue("NICK " + m_nonAwayNick); m_nonAwayNick = ""; } if (m_away) { appendMessageToFrontmost(i18n("Away"), i18n("You are no longer marked as being away.")); if (identity && identity->getShowAwayMessage()) { TQString message = identity->getReturnMessage(); sendToAllChannels(message.replace(TQRegExp("%t", false), awayTime())); } } else appendMessageToFrontmost(i18n("Away"), i18n("You are not marked as being away.")); m_away = false; } } TQString Server::awayTime() const { TQString retVal; if (m_away) { int diff = TQDateTime::currentDateTime().toTime_t() - m_awayTime; int num = diff / 3600; if (num < 10) retVal = '0' + TQString::number(num) + ':'; else retVal = TQString::number(num) + ':'; num = (diff % 3600) / 60; if (num < 10) retVal += '0'; retVal += TQString::number(num) + ':'; num = (diff % 3600) % 60; if (num < 10) retVal += '0'; retVal += TQString::number(num); } else retVal = "00:00:00"; return retVal; } void Server::startAwayTimer() { m_awayTime = TQDateTime::currentDateTime().toTime_t(); } TDEABC::Addressee Server::getOfflineNickAddressee(TQString& nickname) { if (m_serverISON) return m_serverISON->getOfflineNickAddressee(nickname); else return TDEABC::Addressee(); } void Server::enableIdentifyMsg(bool enabled) { m_identifyMsg = enabled; } bool Server::identifyMsgEnabled() { return m_identifyMsg; } void Server::addBan(const TQString &channel, const TQString &ban) { Channel* outChannel = getChannelByName(channel); if(outChannel) { outChannel->addBan(ban); } } void Server::removeBan(const TQString &channel, const TQString &ban) { Channel* outChannel = getChannelByName(channel); if(outChannel) { outChannel->removeBan(ban); } } void Server::sendPing() { //WHO ourselves once a minute in case the irc server has changed our //hostmask, such as what happens when a Freenode cloak is activated. //It might be more intelligent to only do this when there is text //in the inputbox. Kinda changes this into a "do minutely" //queue :-) TQStringList ql; ql << "PING LAG" + TQTime::currentTime().toString("hhmmss"); getInputFilter()->setAutomaticRequest("WHO", getNickname(), true); ql << "WHO " + getNickname(); queueList(ql, HighPriority); m_lagTime.start(); m_inputFilter.setLagMeasuring(true); m_pingResponseTimer.start(1000 /*1 sec*/); } void Server::pongReceived() { m_currentLag = m_lagTime.elapsed(); m_inputFilter.setLagMeasuring(false); m_pingResponseTimer.stop(); emit serverLag(this, m_currentLag); // Send another PING in 60 seconds TQTimer::singleShot(60000 /*60 sec*/, this, TQT_SLOT(sendPing())); } void Server::updateLongPongLag() { if (isSocketConnected()) { m_currentLag = m_lagTime.elapsed(); emit tooLongLag(this, m_currentLag); // kdDebug() << "Current lag: " << currentLag << endl; if (m_currentLag > (Preferences::maximumLagTime() * 1000)) m_socket->close(); } } void Server::updateEncoding() { if(getViewContainer() && getViewContainer()->getFrontView()) getViewContainer()->updateViewEncoding(getViewContainer()->getFrontView()); } #include "server.moc" // kate: space-indent on; tab-width 4; indent-width 4; mixed-indent off; replace-tabs on; // vim: set et sw=4 ts=4 cino=l1,cs,U1: