diff --git a/src/irc/channel.cpp b/src/irc/channel.cpp index d8a98d66..22cd0dec 100644 --- a/src/irc/channel.cpp +++ b/src/irc/channel.cpp @@ -1,2905 +1,2908 @@ /* 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) 2004-2016 Peter Simonsson Copyright (C) 2006-2008 Eike Hein */ #include "channel.h" #include "channeloptionsdialog.h" #include "application.h" #include "server.h" #include "nick.h" #include "nicklistview.h" #include "quickbutton.h" #include "modebutton.h" #include "ircinput.h" #include "ircviewbox.h" #include "ircview.h" #include "awaylabel.h" #include "topiclabel.h" #include "topichistorymodel.h" #include "notificationhandler.h" #include "viewcontainer.h" #include #include #include #include #include #include #include #include #include #include #include #define DELAYED_SORT_TRIGGER 10 using namespace Konversation; bool nickTimestampLessThan(const Nick* nick1, const Nick* nick2) { if(nick2->getChannelNick()->timeStamp() == nick1->getChannelNick()->timeStamp()) { return QString::compare(nick1->getChannelNick()->loweredNickname(), nick2->getChannelNick()->loweredNickname()) < 0; } return nick1->getChannelNick()->timeStamp() < nick2->getChannelNick()->timeStamp(); } bool nickLessThan(const Nick* nick1, const Nick* nick2) { return nick1->getChannelNick()->loweredNickname() < nick2->getChannelNick()->loweredNickname(); } using Konversation::ChannelOptionsDialog; Channel::Channel(QWidget* parent, const QString& _name) : ChatWindow(parent) { // init variables //HACK I needed the channel name at time of setServer, but setName needs m_server.. // This effectively assigns the name twice, but none of the other logic has been moved or updated. name=_name; m_ownChannelNick = nullptr; m_optionsDialog = nullptr; m_delayedSortTimer = nullptr; m_delayedSortTrigger = 0; m_processedNicksCount = 0; m_processedOpsCount = 0; m_initialNamesReceived = false; nicks = 0; ops = 0; completionPosition = 0; m_nicknameListViewTextChanged = 0; m_joined = false; quickButtonsChanged = false; quickButtonsState = false; modeButtonsChanged = false; modeButtonsState = false; awayChanged = false; awayState = false; splittersInitialized = false; topicSplitterHidden = false; channelSplitterHidden = false; setType(ChatWindow::Channel); m_isTopLevelView = false; setChannelEncodingSupported(true); // Build some size policies for the widgets QSizePolicy hfixed = QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred); QSizePolicy hmodest = QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); QSizePolicy vfixed = QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); QSizePolicy greedy = QSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); m_vertSplitter = new QSplitter(Qt::Vertical, this); QWidget* topicWidget = new QWidget(m_vertSplitter); m_vertSplitter->setStretchFactor(m_vertSplitter->indexOf(topicWidget), 0); QGridLayout* topicLayout = new QGridLayout(topicWidget); topicLayout->setContentsMargins(0, 0, 0, 0); topicLayout->setSpacing(0); m_topicButton = new QToolButton(topicWidget); m_topicButton->setIcon(QIcon::fromTheme(QStringLiteral("document-edit"))); m_topicButton->setToolTip(i18n("Edit Channel Settings")); m_topicButton->setAutoRaise(true); connect(m_topicButton, &QAbstractButton::clicked, this, &Channel::showOptionsDialog); topicLine = new Konversation::TopicLabel(topicWidget); topicLine->setContextMenuOptions(IrcContextMenus::ShowChannelActions | IrcContextMenus::ShowLogAction, true); topicLine->setChannelName(getName()); topicLine->setWordWrap(true); topicLine->setWhatsThis(i18n("

Every channel on IRC has a topic associated with it. This is simply a message that everybody can see.

If you are an operator, or the channel mode 'T' has not been set, then you can change the topic by clicking the Edit Channel Properties button to the left of the topic. You can also view the history of topics there.

")); connect(topicLine, &TopicLabel::setStatusBarTempText, this, &ChatWindow::setStatusBarTempText); connect(topicLine, &TopicLabel::clearStatusBarTempText, this, &ChatWindow::clearStatusBarTempText); m_topicHistory = new TopicHistoryModel(this); connect(m_topicHistory, SIGNAL(currentTopicChanged(QString)), topicLine, SLOT(setText(QString))); topicLayout->addWidget(m_topicButton, 0, 0); topicLayout->addWidget(topicLine, 0, 1, -1, 1); // The box holding the channel modes modeBox = new QFrame(topicWidget); QHBoxLayout* modeBoxLayout = new QHBoxLayout(modeBox); modeBoxLayout->setContentsMargins(0, 0, 0, 0); modeBox->hide(); modeBox->setSizePolicy(hfixed); modeT = new ModeButton(QStringLiteral("T"),modeBox,0); modeBoxLayout->addWidget(modeT); modeN = new ModeButton(QStringLiteral("N"),modeBox,1); modeBoxLayout->addWidget(modeN); modeS = new ModeButton(QStringLiteral("S"),modeBox,2); modeBoxLayout->addWidget(modeS); modeI = new ModeButton(QStringLiteral("I"),modeBox,3); modeBoxLayout->addWidget(modeI); modeP = new ModeButton(QStringLiteral("P"),modeBox,4); modeBoxLayout->addWidget(modeP); modeM = new ModeButton(QStringLiteral("M"),modeBox,5); modeBoxLayout->addWidget(modeM); modeK = new ModeButton(QStringLiteral("K"),modeBox,6); modeBoxLayout->addWidget(modeK); modeL = new ModeButton(QStringLiteral("L"),modeBox,7); modeBoxLayout->addWidget(modeL); modeT->setWhatsThis(ChannelOptionsDialog::whatsThisForMode('T')); modeN->setWhatsThis(ChannelOptionsDialog::whatsThisForMode('N')); modeS->setWhatsThis(ChannelOptionsDialog::whatsThisForMode('S')); modeI->setWhatsThis(ChannelOptionsDialog::whatsThisForMode('I')); modeP->setWhatsThis(ChannelOptionsDialog::whatsThisForMode('P')); modeM->setWhatsThis(ChannelOptionsDialog::whatsThisForMode('M')); modeK->setWhatsThis(ChannelOptionsDialog::whatsThisForMode('K')); modeL->setWhatsThis(ChannelOptionsDialog::whatsThisForMode('L')); connect(modeT, &ModeButton::modeClicked, this, &Channel::modeButtonClicked); connect(modeN, &ModeButton::modeClicked, this, &Channel::modeButtonClicked); connect(modeS, &ModeButton::modeClicked, this, &Channel::modeButtonClicked); connect(modeI, &ModeButton::modeClicked, this, &Channel::modeButtonClicked); connect(modeP, &ModeButton::modeClicked, this, &Channel::modeButtonClicked); connect(modeM, &ModeButton::modeClicked, this, &Channel::modeButtonClicked); connect(modeK, &ModeButton::modeClicked, this, &Channel::modeButtonClicked); connect(modeL, &ModeButton::modeClicked, this, &Channel::modeButtonClicked); connect(modeT, &ModeButton::modeClicked, this, &Channel::modeButtonClicked); limit=new KLineEdit(modeBox); modeBoxLayout->addWidget(limit); limit->setToolTip(i18n("Maximum users allowed in channel")); limit->setWhatsThis(i18n("

This is the channel user limit - the maximum number of users that can be in the channel at a time. If you are an operator, you can set this. The channel mode Topic (button to left) will automatically be set if set this.

")); connect(limit,SIGNAL (returnPressed()),this,SLOT (channelLimitChanged()) ); connect(limit,&QLineEdit::editingFinished, this, &Channel::channelLimitChanged ); topicLayout->addWidget(modeBox, 0, 2); topicLayout->setRowStretch(1, 10); topicLayout->setColumnStretch(1, 10); showTopic(Preferences::self()->showTopic()); showModeButtons(Preferences::self()->showModeButtons()); // (this) The main Box, holding the channel view/topic and the input line m_horizSplitter = new QSplitter(m_vertSplitter); m_vertSplitter->setStretchFactor(m_vertSplitter->indexOf(m_horizSplitter), 1); // Server will be set later in setServer() IRCViewBox* ircViewBox = new IRCViewBox(m_horizSplitter); m_horizSplitter->setStretchFactor(m_horizSplitter->indexOf(ircViewBox), 1); setTextView(ircViewBox->ircView()); ircViewBox->ircView()->setContextMenuOptions(IrcContextMenus::ShowChannelActions, true); // The box that holds the Nick List and the quick action buttons nickListButtons = new QFrame(m_horizSplitter); m_horizSplitter->setStretchFactor(m_horizSplitter->indexOf(nickListButtons), 0); QVBoxLayout* nickListButtonsLayout = new QVBoxLayout(nickListButtons); nickListButtonsLayout->setSpacing(0); nickListButtonsLayout->setContentsMargins(0, 0, 0, 0); nicknameListView=new NickListView(nickListButtons, this); nickListButtons->layout()->addWidget(nicknameListView); nicknameListView->installEventFilter(this); // initialize buttons grid, will be set up in updateQuickButtons m_buttonsGrid = nullptr; // The box holding the Nickname button and Channel input commandLineBox = new QFrame(this); QHBoxLayout* commandLineLayout = new QHBoxLayout(commandLineBox); commandLineBox->setLayout(commandLineLayout); commandLineLayout->setContentsMargins(0, 0, 0, 0); commandLineLayout->setSpacing(spacing()); nicknameCombobox = new KComboBox(commandLineBox); nicknameCombobox->setEditable(true); nicknameCombobox->setSizeAdjustPolicy(KComboBox::AdjustToContents); KLineEdit* nicknameComboboxLineEdit = qobject_cast(nicknameCombobox->lineEdit()); if (nicknameComboboxLineEdit) nicknameComboboxLineEdit->setClearButtonEnabled(false); nicknameCombobox->setWhatsThis(i18n("

This shows your current nick, and any alternatives you have set up. If you select or type in a different nickname, then a request will be sent to the IRC server to change your nick. If the server allows it, the new nickname will be selected. If you type in a new nickname, you need to press 'Enter' at the end.

You can edit the alternative nicknames from the Identities option in the Settings menu.

")); awayLabel = new AwayLabel(commandLineBox); awayLabel->hide(); cipherLabel = new QLabel(commandLineBox); cipherLabel->hide(); cipherLabel->setPixmap(KIconLoader::global()->loadIcon(QStringLiteral("document-encrypt"), KIconLoader::Toolbar)); m_inputBar = new IRCInput(commandLineBox); commandLineLayout->addWidget(nicknameCombobox); commandLineLayout->addWidget(awayLabel); commandLineLayout->addWidget(cipherLabel); commandLineLayout->addWidget(m_inputBar); getTextView()->installEventFilter(m_inputBar); topicLine->installEventFilter(m_inputBar); m_inputBar->installEventFilter(this); // Set the widgets size policies m_topicButton->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed)); topicLine->setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum)); commandLineBox->setSizePolicy(vfixed); limit->setMaximumSize(40,100); limit->setSizePolicy(hfixed); modeT->setMaximumSize(20,100); modeT->setSizePolicy(hfixed); modeN->setMaximumSize(20,100); modeN->setSizePolicy(hfixed); modeS->setMaximumSize(20,100); modeS->setSizePolicy(hfixed); modeI->setMaximumSize(20,100); modeI->setSizePolicy(hfixed); modeP->setMaximumSize(20,100); modeP->setSizePolicy(hfixed); modeM->setMaximumSize(20,100); modeM->setSizePolicy(hfixed); modeK->setMaximumSize(20,100); modeK->setSizePolicy(hfixed); modeL->setMaximumSize(20,100); modeL->setSizePolicy(hfixed); getTextView()->setSizePolicy(greedy); nicknameListView->setSizePolicy(hmodest); connect(m_inputBar,&IRCInput::submit,this,&Channel::channelTextEntered ); connect(m_inputBar,&IRCInput::envelopeCommand,this,&Channel::channelPassthroughCommand ); connect(m_inputBar,&IRCInput::nickCompletion,this,&Channel::completeNick ); connect(m_inputBar,&IRCInput::endCompletion,this,&Channel::endCompleteNick ); connect(m_inputBar,&IRCInput::textPasted,this,&Channel::textPasted ); connect(getTextView(), SIGNAL(textPasted(bool)), m_inputBar, SLOT(paste(bool))); connect(getTextView(),SIGNAL (gotFocus()),m_inputBar,SLOT (setFocus()) ); connect(getTextView(),&IRCView::sendFile,this,&Channel::sendFileMenu ); connect(getTextView(),SIGNAL (autoText(QString)),this,SLOT (sendText(QString)) ); connect(nicknameListView,&QTreeWidget::itemDoubleClicked,this,&Channel::doubleClickCommand ); connect(nicknameCombobox,SIGNAL (activated(int)),this,SLOT(nicknameComboboxChanged())); if(nicknameCombobox->lineEdit()) connect(nicknameCombobox->lineEdit(), &QLineEdit::returnPressed,this,&Channel::nicknameComboboxChanged); connect(&userhostTimer,&QTimer::timeout,this,&Channel::autoUserhost); m_whoTimer.setSingleShot(true); connect(&m_whoTimer, &QTimer::timeout, this, &Channel::autoWho); connect(Application::instance(), &Application::appearanceChanged, this, &Channel::updateAutoWho); // every 5 minutes decrease everyone's activity by 1 unit m_fadeActivityTimer.start(5*60*1000); connect(&m_fadeActivityTimer, &QTimer::timeout, this, &Channel::fadeActivity); updateAppearance(); #ifdef HAVE_QCA2 m_cipher = nullptr; #endif // Setup delayed sort timer m_delayedSortTimer = new QTimer(this); m_delayedSortTimer->setSingleShot(true); connect(m_delayedSortTimer, &QTimer::timeout, this, &Channel::delayedSortNickList); } //FIXME there is some logic in setLogfileName that needs to be split out and called here if the server display name gets changed void Channel::setServer(Server* server) { if (m_server != server) { connect(server, &Server::connectionStateChanged, this, &Channel::connectionStateChanged); connect(server, SIGNAL(nickInfoChanged()), this, SLOT(updateNickInfos())); connect(server, &Server::channelNickChanged, this, &Channel::updateChannelNicks); } ChatWindow::setServer(server); if (!server->getKeyForRecipient(getName()).isEmpty()) cipherLabel->show(); topicLine->setServer(server); refreshModeButtons(); nicknameCombobox->setModel(m_server->nickListModel()); connect(awayLabel, &AwayLabel::unaway, m_server, &Server::requestUnaway); connect(awayLabel, &AwayLabel::awayMessageChanged, m_server, &Server::requestAway); } void Channel::connectionStateChanged(Server* server, Konversation::ConnectionState state) { if (server == m_server) { if (state != Konversation::SSConnected) { m_joined = false; ViewContainer* viewContainer = Application::instance()->getMainWindow()->getViewContainer(); //HACK the way the notification priorities work sucks, this forces the tab text color to gray right now. if (viewContainer->getFrontView() == this || m_currentTabNotify == Konversation::tnfNone || (!Preferences::self()->tabNotificationsEvents() && m_currentTabNotify == Konversation::tnfControl)) { viewContainer->unsetViewNotification(this); } } } } void Channel::setEncryptedOutput(bool e) { #ifdef HAVE_QCA2 if (e) { cipherLabel->show(); if (!getCipher()->setKey(m_server->getKeyForRecipient(getName()))) return; m_topicHistory->setCipher(getCipher()); topicLine->setText(m_topicHistory->currentTopic()); } else { cipherLabel->hide(); m_topicHistory->clearCipher(); topicLine->setText(m_topicHistory->currentTopic()); } #else Q_UNUSED(e) #endif } Channel::~Channel() { qDebug() << "(" << getName() << ")"; // Purge nickname list purgeNicks(); qDebug() << "Nicks purged."; // Unlink this channel from channel list m_server->removeChannel(this); qDebug() << "Channel removed."; if (m_recreationScheduled) { QMetaObject::invokeMethod(m_server, "sendJoinCommand", Qt::QueuedConnection, Q_ARG(QString, getName()), Q_ARG(QString, getPassword())); } } bool Channel::rejoinable() { if (getServer() && getServer()->isConnected()) return !m_joined; return false; } void Channel::rejoin() { if (rejoinable()) m_server->sendJoinCommand(getName(), getPassword()); } bool Channel::log() { return ChatWindow::log() && !Preferences::self()->privateOnly(); } ChannelNickPtr Channel::getOwnChannelNick() const { return m_ownChannelNick; } ChannelNickPtr Channel::getChannelNick(const QString &ircnick) const { return m_server->getChannelNick(getName(), ircnick); } void Channel::purgeNicks() { m_ownChannelNick = nullptr; // Purge nickname list qDeleteAll(nicknameList); nicknameList.clear(); m_nicknameNickHash.clear(); // Execute this otherwise it may crash trying to access // deleted nicks nicknameListView->executeDelayedItemsLayout(); // clear stats counter nicks=0; ops=0; } void Channel::showOptionsDialog() { if (!m_optionsDialog) m_optionsDialog = new Konversation::ChannelOptionsDialog(this); m_optionsDialog->show(); } void Channel::textPasted(const QString& text) { if(m_server) { QStringList multiline = text.split(QLatin1Char('\n'), QString::SkipEmptyParts); for(int index=0;indexcommandChar()); // make sure that lines starting with command char get escaped if(line.startsWith(cChar)) line=cChar+line; sendText(line); } } } // Will be connected to NickListView::doubleClicked() void Channel::doubleClickCommand(QTreeWidgetItem *item, int column) { Q_UNUSED(column) if(item) { nicknameListView->clearSelection(); item->setSelected(true); // TODO: put the quick button code in another function to make reusal more legitimate quickButtonClicked(Preferences::self()->channelDoubleClickAction()); } } void Channel::completeNick() { int pos, oldPos; QTextCursor cursor = m_inputBar->textCursor(); pos = cursor.position(); oldPos = m_inputBar->getOldCursorPosition(); QString line=m_inputBar->toPlainText(); QString newLine; // Check if completion position is out of range if(completionPosition >= nicknameList.count()) completionPosition = 0; // Check, which completion mode is active char mode = m_inputBar->getCompletionMode(); if(mode == 'c') { line.remove(oldPos, pos - oldPos); pos = oldPos; } // If the cursor is at beginning of line, insert last completion if the nick is still around if(pos == 0 && !m_inputBar->lastCompletion().isEmpty() && nicknameList.containsNick(m_inputBar->lastCompletion())) { QString addStart(Preferences::self()->nickCompleteSuffixStart()); newLine = m_inputBar->lastCompletion() + addStart; // New cursor position is behind nickname pos = newLine.length(); // Add rest of the line newLine += line; } else { // remember old cursor position in input field m_inputBar->setOldCursorPosition(pos); // remember old cursor position locally oldPos = pos; // step back to []{}-_^`\| or start of line QString regexpStr(QStringLiteral("[^A-Z0-9a-z\\_\\[\\]\\{\\}\\-\\^\\`\\\\\\|")); if(!Preferences::self()->prefixCharacter().isEmpty()) regexpStr += "\\" + Preferences::self()->prefixCharacter(); regexpStr += QLatin1Char(']'); QRegExp tmp(regexpStr); pos = tmp.lastIndexIn(line, pos - 1); if (pos < 0) pos = 0; else pos++; // copy search pattern (lowercase) QString pattern = line.mid(pos, oldPos - pos); // copy line to newLine-buffer newLine = line; // did we find any pattern? if(!pattern.isEmpty()) { bool complete = false; QString foundNick; // try to find matching nickname in list of names if(Preferences::self()->nickCompletionMode() == 1 || Preferences::self()->nickCompletionMode() == 2) { // Shell like completion QStringList found; foundNick = nicknameList.completeNick(pattern, complete, found, (Preferences::self()->nickCompletionMode() == 2), Preferences::self()->nickCompletionCaseSensitive()); if(!complete && !found.isEmpty()) { if(Preferences::self()->nickCompletionMode() == 1) { QString nicksFound = found.join(QLatin1Char(' ')); appendServerMessage(i18n("Completion"), i18n("Possible completions: %1.", nicksFound)); } else { m_inputBar->showCompletionList(found); } } } // Cycle completion else if(Preferences::self()->nickCompletionMode() == 0 && !nicknameList.isEmpty()) { if(mode == '\0') { uint timeStamp = 0; int listPosition = 0; foreach (Nick* nick, nicknameList) { if(nick->getChannelNick()->getNickname().startsWith(pattern, Preferences::self()->nickCompletionCaseSensitive() ? Qt::CaseSensitive : Qt::CaseInsensitive) && (nick->getChannelNick()->timeStamp() > timeStamp)) { timeStamp = nick->getChannelNick()->timeStamp(); completionPosition = listPosition; } ++listPosition; } } // remember old nick completion position int oldCompletionPosition = completionPosition; complete = true; QString prefixCharacter = Preferences::self()->prefixCharacter(); do { QString lookNick = nicknameList.at(completionPosition)->getChannelNick()->getNickname(); if(!prefixCharacter.isEmpty() && lookNick.contains(prefixCharacter)) { lookNick = lookNick.section( prefixCharacter,1 ); } if(lookNick.startsWith(pattern, Preferences::self()->nickCompletionCaseSensitive() ? Qt::CaseSensitive : Qt::CaseInsensitive)) { foundNick = lookNick; } // increment search position completionPosition++; // wrap around if(completionPosition == nicknameList.count()) { completionPosition = 0; } // the search ends when we either find a suitable nick or we end up at the // first search position } while((completionPosition != oldCompletionPosition) && foundNick.isEmpty()); } // did we find a suitable nick? if(!foundNick.isEmpty()) { // set channel nicks completion mode m_inputBar->setCompletionMode('c'); // remove pattern from line newLine.remove(pos, pattern.length()); // did we find the nick in the middle of the line? if(pos && complete) { m_inputBar->setLastCompletion(foundNick); QString addMiddle = Preferences::self()->nickCompleteSuffixMiddle(); newLine.insert(pos, foundNick + addMiddle); pos = pos + foundNick.length() + addMiddle.length(); } // no, it was at the beginning else if(complete) { m_inputBar->setLastCompletion(foundNick); QString addStart = Preferences::self()->nickCompleteSuffixStart(); newLine.insert(pos, foundNick + addStart); pos = pos + foundNick.length() + addStart.length(); } // the nick wasn't complete else { newLine.insert(pos, foundNick); pos = pos + foundNick.length(); } } // no pattern found, so restore old cursor position else pos = oldPos; } } // Set new text and cursor position m_inputBar->setText(newLine); cursor.setPosition(pos); m_inputBar->setTextCursor(cursor); } // make sure to step back one position when completion ends so the user starts // with the last complete they made void Channel::endCompleteNick() { if(completionPosition) completionPosition--; else completionPosition=nicknameList.count()-1; } void Channel::setName(const QString& newName) { ChatWindow::setName(newName); setLogfileName(newName.toLower()); } bool Channel::autoJoin() { if (!m_server->getServerGroup()) return false; Konversation::ChannelList channelList = m_server->getServerGroup()->channelList(); return channelList.contains(channelSettings()); } void Channel::setAutoJoin(bool autojoin) { if (autojoin && !(autoJoin())) { Konversation::ChannelSettings before; QList channelList = m_server->getChannelList(); if (channelList.count() > 1) { QMap channelMap; int index = -1; int ownIndex = m_server->getViewContainer()->getViewIndex(this); foreach (Channel* channel, channelList) { index = m_server->getViewContainer()->getViewIndex(channel); if (index && index > ownIndex) channelMap.insert(index, channel); } if (channelMap.count()) { QMap::Iterator it2; Channel* channel; for (it2 = channelMap.begin(); it2 != channelMap.end(); ++it2) { channel = it2.value(); if (channel->autoJoin()) { before = channel->channelSettings(); break; } } } } if (m_server->getServerGroup()) m_server->getServerGroup()->addChannel(channelSettings(), before); } else { if (m_server->getServerGroup()) m_server->getServerGroup()->removeChannel(channelSettings()); } } QString Channel::getPassword() { QString password; for (QStringList::const_iterator it = m_modeList.constBegin(); it != m_modeList.constEnd(); ++it) { if ((*it)[0] == QLatin1Char('k')) password = (*it).mid(1); } if (password.isEmpty() && m_server->getServerGroup()) { Konversation::ChannelList channelSettingsList = m_server->getServerGroup()->channelList(); Konversation::ChannelSettings channelSettings(getName()); int index = channelSettingsList.indexOf(channelSettings); if(index >= 0) password = channelSettingsList.at(index).password(); } return password; } const Konversation::ChannelSettings Channel::channelSettings() { Konversation::ChannelSettings channel; channel.setName(getName()); channel.setPassword(getPassword()); channel.setNotificationsEnabled(notificationsEnabled()); return channel; } void Channel::sendFileMenu() { emit sendFile(); } void Channel::channelTextEntered() { QString line = m_inputBar->toPlainText(); m_inputBar->clear(); if (!line.isEmpty()) sendText(sterilizeUnicode(line)); } void Channel::channelPassthroughCommand() { QString commandChar = Preferences::self()->commandChar(); QString line = m_inputBar->toPlainText(); m_inputBar->clear(); if(!line.isEmpty()) { // Prepend commandChar on Ctrl+Enter to bypass outputfilter command recognition if (line.startsWith(commandChar)) { line = commandChar + line; } sendText(sterilizeUnicode(line)); } } void Channel::sendText(const QString& sendLine) { // create a work copy QString outputAll(sendLine); // replace aliases and wildcards OutputFilter::replaceAliases(outputAll, this); // Send all strings, one after another QStringList outList = outputAll.split(QRegExp(QStringLiteral("[\r\n]+")), QString::SkipEmptyParts); for(int index=0;indexgetOutputFilter()->parse(m_server->getNickname(), output, getName(), this); // Is there something we need to display for ourselves? if(!result.output.isEmpty()) { if(result.type == Konversation::Action) appendAction(m_server->getNickname(), result.output); else if(result.type == Konversation::Command) appendCommandMessage(result.typeString, result.output); else if(result.type == Konversation::Program) appendServerMessage(result.typeString, result.output); else if(result.type == Konversation::PrivateMessage) msgHelper(result.typeString, result.output); else append(m_server->getNickname(), result.output); } else if (result.outputList.count()) { if (result.type == Konversation::Message) { QStringListIterator it(result.outputList); while (it.hasNext()) append(m_server->getNickname(), it.next()); } else if (result.type == Konversation::Action) { for (int i = 0; i < result.outputList.count(); ++i) { if (i == 0) appendAction(m_server->getNickname(), result.outputList.at(i)); else append(m_server->getNickname(), result.outputList.at(i)); } } } // Send anything else to the server if (!result.toServerList.empty()) m_server->queueList(result.toServerList); else m_server->queue(result.toServer); } } void Channel::setNickname(const QString& newNickname) { nicknameCombobox->setCurrentIndex(nicknameCombobox->findText(newNickname)); } QStringList Channel::getSelectedNickList() { QStringList selectedNicks; foreach (Nick* nick, nicknameList) { if (nick->isSelected()) selectedNicks << nick->getChannelNick()->getNickname(); } return selectedNicks; } void Channel::channelLimitChanged() { unsigned int lim=limit->text().toUInt(); modeButtonClicked(7,lim>0); } void Channel::modeButtonClicked(int id, bool on) { char mode[]={'t','n','s','i','p','m','k','l'}; QString command(QStringLiteral("MODE %1 %2%3 %4")); QString args = getPassword(); if (mode[id] == 'k') { if (args.isEmpty()) { QPointer dlg = new KPasswordDialog(this); dlg->setPrompt(i18n("Channel Password")); if (dlg->exec() && !dlg->password().isEmpty()) { args = dlg->password(); } delete dlg; } } else if(mode[id]=='l') { if(limit->text().isEmpty() && on) { bool ok=false; // ask user how many nicks should be the limit args=QInputDialog::getText(this, i18n("Channel User Limit"), i18n("Enter the new user limit for the channel:"), QLineEdit::Normal, limit->text(), // will be always "" but what the hell ;) &ok); // leave this function if user cancels if(!ok) return; } else if(on) args=limit->text(); } // put together the mode command and send it to the server queue m_server->queue(command.arg(getName()).arg((on) ? QStringLiteral("+") : QStringLiteral("-")).arg(mode[id]).arg(args)); } void Channel::quickButtonClicked(const QString &buttonText) { // parse wildcards (toParse,nickname,channelName,nickList,queryName,parameter) QString out=m_server->parseWildcards(buttonText,m_server->getNickname(),getName(),getPassword(),getSelectedNickList(), m_inputBar->toPlainText()); // are there any newlines in the definition? if (out.contains(QLatin1Char('\n'))) sendText(out); // single line without newline needs to be copied into input line else m_inputBar->setText(out, true); } void Channel::addNickname(ChannelNickPtr channelnick) { QString nickname = channelnick->loweredNickname(); Nick* nick=nullptr; foreach (Nick* lookNick, nicknameList) { if(lookNick->getChannelNick()->loweredNickname() == nickname) { nick = lookNick; break; } } if (nick == nullptr) { fastAddNickname(channelnick); if(channelnick->isAnyTypeOfOp()) { adjustOps(1); } adjustNicks(1); requestNickListSort(); } // TODO (re)-investigate why it was thought unusual to add an already added nick // -- see bug 333969 } // Use with caution! Does not check for duplicates or may not // sort if delayed sorting is in effect. void Channel::fastAddNickname(ChannelNickPtr channelnick, Nick *nick) { Q_ASSERT(channelnick); if(!channelnick) return; if (!nick || !nick->treeWidget()) { // Deal with nicknameListView now (creating nick if necessary) NickListView::NoSorting noSorting(nicknameListView); int index = nicknameListView->topLevelItemCount(); // Append nick to the lists if (nick) { nicknameListView->addTopLevelItem(nick); } else { nick = new Nick(nicknameListView, this, channelnick); m_nicknameListViewTextChanged |= 0xFF; // new nick, text changed. } if (!m_delayedSortTimer->isActive()) { // Find its right place and insert where it belongs int newindex = nicknameListView->findLowerBound(*nick); if (newindex != index) { if (newindex >= index) newindex--; nicknameListView->takeTopLevelItem(index); nicknameListView->insertTopLevelItem(newindex, nick); } } // Otherwise it will be sorted by delayed sort. } // Now deal with nicknameList if (m_delayedSortTimer->isActive()) { // nicks get sorted later nicknameList.append(nick); } else { NickList::iterator it = std::lower_bound(nicknameList.begin(), nicknameList.end(), nick, nickLessThan); nicknameList.insert(it, nick); } m_nicknameNickHash.insert (channelnick->loweredNickname(), nick); } /* Determines whether Nick/Part/Join event should be shown or skipped based on user settings. */ bool Channel::shouldShowEvent(ChannelNickPtr channelNick) { if (Preferences::self()->hideUnimportantEvents()) { if (channelNick && Preferences::self()->hideUnimportantEventsExcludeActive()) { uint activityThreshold = 3600; if (Preferences::self()->hideUnimportantEventsExcludeActiveThreshold() == 0) // last 10 minutes activityThreshold = 600; else if (Preferences::self()->hideUnimportantEventsExcludeActiveThreshold() == 1) // last hour activityThreshold = 3600; else if (Preferences::self()->hideUnimportantEventsExcludeActiveThreshold() == 2) // last day activityThreshold = 86400; else if (Preferences::self()->hideUnimportantEventsExcludeActiveThreshold() == 3) // last week activityThreshold = 604800; if (m_server->isWatchedNick(channelNick->getNickname())) return true; // nick is on our watched list, so we probably want to see the event else if (channelNick->timeStamp()+activityThreshold > QDateTime::currentDateTime().toTime_t()) return true; // the nick has spoken within activity threshold else return false; } else return false; // if hideUnimportantEventsExcludeActive is off, we hide all events } else return true; // if hideUnimportantEvents is off we don't care and just show the event } void Channel::nickRenamed(const QString &oldNick, const NickInfo& nickInfo, const QHash &messageTags) { QString newNick = nickInfo.getNickname(); Nick *nick = getNickByName(oldNick); bool displayCommandMessage; if (Preferences::self()->hideUnimportantEventsExcludeActive() && m_server->isWatchedNick(oldNick)) displayCommandMessage = true; // this is for displaying watched people NICK events both ways (watched->unwatched and unwatched->watched) else if (nick) displayCommandMessage = shouldShowEvent(nick->getChannelNick()); else displayCommandMessage = shouldShowEvent(ChannelNickPtr()); // passing null pointer /* Did we change our nick name? */ if(newNick == m_server->getNickname()) /* Check newNick because m_server->getNickname() is already updated to new nick */ { setNickname(newNick); if (displayCommandMessage) appendCommandMessage(i18n("Nick"),i18n("You are now known as %1.", newNick), messageTags, true, true); } else if (displayCommandMessage) { /* No, must've been someone else */ appendCommandMessage(i18n("Nick"),i18n("%1 is now known as %2.", oldNick, newNick), messageTags); } if (nick) { m_nicknameNickHash.remove(oldNick.toLower()); m_nicknameNickHash.insert(newNick.toLower(), nick); repositionNick(nick); } } void Channel::joinNickname(ChannelNickPtr channelNick, const QHash &messageTags) { bool displayCommandMessage = shouldShowEvent(channelNick); if(channelNick->getNickname() == m_server->getNickname()) { m_joined = true; emit joined(this); if (displayCommandMessage) appendCommandMessage(i18nc("Message type", "Join"), i18nc("%1 = our hostmask, %2 = channel", "You (%1) have joined the channel %2.", channelNick->getHostmask(), getName()), messageTags, false, true); // Prepare for impending NAMES. purgeNicks(); nicknameListView->setUpdatesEnabled(false); m_ownChannelNick = channelNick; refreshModeButtons(); setActive(true); ViewContainer* viewContainer = Application::instance()->getMainWindow()->getViewContainer(); //HACK the way the notification priorities work sucks, this forces the tab text color to ungray right now. if (viewContainer->getFrontView() == this || m_currentTabNotify == Konversation::tnfNone || (!Preferences::self()->tabNotificationsEvents() && m_currentTabNotify == Konversation::tnfControl)) { Application::instance()->getMainWindow()->getViewContainer()->unsetViewNotification(this); } Application::instance()->notificationHandler()->channelJoin(this,getName()); } else { QString nick = channelNick->getNickname(); QString hostname = channelNick->getHostmask(); if (displayCommandMessage) appendCommandMessage(i18nc("Message type", "Join"), i18nc("%1 is the nick joining and %2 the hostmask of that nick", "%1 (%2) has joined this channel.", nick, hostname), messageTags, false); addNickname(channelNick); } } void Channel::removeNick(ChannelNickPtr channelNick, const QString &reason, bool quit, const QHash &messageTags) { bool displayCommandMessage = shouldShowEvent(channelNick); QString displayReason = reason; if(!displayReason.isEmpty()) { // if the reason contains text markup characters, play it safe and reset all if (hasIRCMarkups(displayReason)) displayReason += QStringLiteral("\017"); } if(channelNick->getNickname() == m_server->getNickname()) { if (displayCommandMessage) { //If in the future we can leave a channel, but not close the window, refreshModeButtons() has to be called. if (quit) { if (displayReason.isEmpty()) appendCommandMessage(i18nc("Message type", "Quit"), i18n("You (%1) have left this server.", channelNick->getHostmask()), messageTags); else appendCommandMessage(i18nc("Message type", "Quit"), i18nc("%1 = our hostmask, %2 = reason", "You (%1) have left this server (%2).", channelNick->getHostmask(), displayReason), messageTags, false); } else { if (displayReason.isEmpty()) appendCommandMessage(i18nc("Message type", "Part"), i18n("You have left channel %1.", getName()), messageTags); else appendCommandMessage(i18nc("Message type", "Part"), i18nc("%1 = our hostmask, %2 = channel, %3 = reason", "You (%1) have left channel %2 (%3).", channelNick->getHostmask(), getName(), displayReason), messageTags, false); } } delete this; } else { if (displayCommandMessage) { if (quit) { if (displayReason.isEmpty()) appendCommandMessage(i18nc("Message type", "Quit"), i18n("%1 (%2) has left this server.", channelNick->getNickname(), channelNick->getHostmask()), messageTags, false); else appendCommandMessage(i18nc("Message type", "Quit"), i18nc("%1 = nick, %2 = hostname, %3 = reason", "%1 (%2) has left this server (%3).", channelNick->getNickname(), channelNick->getHostmask(), displayReason), messageTags, false); } else { if (displayReason.isEmpty()) appendCommandMessage(i18nc("Message type", "Part"), i18n("%1 (%2) has left this channel.", channelNick->getNickname(), channelNick->getHostmask()), messageTags, false); else appendCommandMessage(i18nc("Message type", "Part"), i18nc("%1 = nick, %2 = hostmask, %3 = reason", "%1 (%2) has left this channel (%3).", channelNick->getNickname(), channelNick->getHostmask(), displayReason), messageTags, false); } } if(channelNick->isAnyTypeOfOp()) { adjustOps(-1); } adjustNicks(-1); Nick* nick = getNickByName(channelNick->loweredNickname()); if(nick) { nicknameList.removeOne(nick); m_nicknameNickHash.remove(channelNick->loweredNickname()); delete nick; // Execute this otherwise it may crash trying to access deleted nick nicknameListView->executeDelayedItemsLayout(); } else { qWarning() << "Nickname " << channelNick->getNickname() << " not found!"<< endl; } } } void Channel::flushNickQueue() { processQueuedNicks(true); } void Channel::kickNick(ChannelNickPtr channelNick, const QString &kicker, const QString &reason, const QHash &messageTags) { QString displayReason = reason; if(!displayReason.isEmpty()) { // if the reason contains text markup characters, play it safe and reset all if (hasIRCMarkups(displayReason)) displayReason += QStringLiteral("\017"); } if(channelNick->getNickname() == m_server->getNickname()) { if(kicker == m_server->getNickname()) { if (displayReason.isEmpty()) appendCommandMessage(i18n("Kick"), i18n("You have kicked yourself from channel %1.", getName()), messageTags); else appendCommandMessage(i18n("Kick"), i18nc("%1 adds the channel and %2 the reason", "You have kicked yourself from channel %1 (%2).", getName(), displayReason), messageTags); } else { if (displayReason.isEmpty()) { appendCommandMessage(i18n("Kick"), i18nc("%1 adds the channel, %2 adds the kicker", "You have been kicked from channel %1 by %2.", getName(), kicker), messageTags); } else { appendCommandMessage(i18n("Kick"), i18nc("%1 adds the channel, %2 the kicker and %3 the reason", "You have been kicked from channel %1 by %2 (%3).", getName(), kicker, displayReason), messageTags); } Application::instance()->notificationHandler()->kick(this,getName(), kicker); } m_joined=false; setActive(false); //HACK the way the notification priorities work sucks, this forces the tab text color to gray right now. if (m_currentTabNotify == Konversation::tnfNone || (!Preferences::self()->tabNotificationsEvents() && m_currentTabNotify == Konversation::tnfControl)) Application::instance()->getMainWindow()->getViewContainer()->unsetViewNotification(this); return; } else { if(kicker == m_server->getNickname()) { if (displayReason.isEmpty()) appendCommandMessage(i18n("Kick"), i18n("You have kicked %1 from the channel.", channelNick->getNickname()), messageTags); else appendCommandMessage(i18n("Kick"), i18nc("%1 adds the kicked nick and %2 the reason", "You have kicked %1 from the channel (%2).", channelNick->getNickname(), displayReason), messageTags); } else { if (displayReason.isEmpty()) { appendCommandMessage(i18n("Kick"), i18nc("%1 adds the kicked nick, %2 adds the kicker", "%1 has been kicked from the channel by %2.", channelNick->getNickname(), kicker), messageTags); } else { appendCommandMessage(i18n("Kick"), i18nc("%1 adds the kicked nick, %2 the kicker and %3 the reason", "%1 has been kicked from the channel by %2 (%3).", channelNick->getNickname(), kicker, displayReason), messageTags); } } if(channelNick->isAnyTypeOfOp()) adjustOps(-1); adjustNicks(-1); Nick* nick = getNickByName(channelNick->loweredNickname()); if(nick == nullptr) { qWarning() << "Nickname " << channelNick->getNickname() << " not found!"<< endl; } else { nicknameList.removeOne(nick); m_nicknameNickHash.remove(channelNick->loweredNickname()); delete nick; } } } Nick* Channel::getNickByName(const QString &lookname) const { QString lcLookname(lookname.toLower()); return m_nicknameNickHash.value(lcLookname); } void Channel::adjustNicks(int value) { if((nicks == 0) && (value <= 0)) { return; } nicks += value; if(nicks < 0) { nicks = 0; } emitUpdateInfo(); } void Channel::adjustOps(int value) { if((ops == 0) && (value <= 0)) { return; } ops += value; if(ops < 0) { ops = 0; } emitUpdateInfo(); } void Channel::emitUpdateInfo() { QString info = getName() + QStringLiteral(" - "); info += i18np("%1 nick", "%1 nicks", numberOfNicks()); info += i18np(" (%1 op)", " (%1 ops)", numberOfOps()); emit updateInfo(info); } QString Channel::getTopic() { return m_topicHistory->currentTopic(); } void Channel::setTopic(const QString& text, const QHash &messageTags) { QString cleanTopic = text; // If the reason contains text markup characters, play it safe and reset all. if (!cleanTopic.isEmpty() && hasIRCMarkups(cleanTopic)) cleanTopic += QStringLiteral("\017"); appendCommandMessage(i18n("Topic"), i18n("The channel topic is \"%1\".", cleanTopic), messageTags); m_topicHistory->appendTopic(replaceIRCMarkups(Konversation::removeIrcMarkup(text))); } void Channel::setTopic(const QString& nickname, const QString& text, const QHash &messageTags) { QString cleanTopic = text; // If the reason contains text markup characters, play it safe and reset all. if (!cleanTopic.isEmpty() && hasIRCMarkups(cleanTopic)) cleanTopic += QStringLiteral("\017"); if (nickname == m_server->getNickname()) appendCommandMessage(i18n("Topic"), i18n("You set the channel topic to \"%1\".", cleanTopic), messageTags); else appendCommandMessage(i18n("Topic"), i18n("%1 sets the channel topic to \"%2\".", nickname, cleanTopic), messageTags); m_topicHistory->appendTopic(replaceIRCMarkups(Konversation::removeIrcMarkup(text)), nickname); } void Channel::setTopicAuthor(const QString& author, QDateTime time) { if (time.isNull() || !time.isValid()) time = QDateTime::currentDateTime(); m_topicHistory->setCurrentTopicMetadata(author, time); } void Channel::updateMode(const QString& sourceNick, char mode, bool plus, const QString ¶meter, const QHash &messageTags) { // Note for future expansion: // m_server->getChannelNick(getName(), sourceNick); // may not return a valid channelNickPtr if the mode is updated by // the server. // --johnflux, 9 September 2004 // Note: nick repositioning in the nicknameListView should be // triggered by nickinfo / channelnick signals QString message; ChannelNickPtr parameterChannelNick = m_server->getChannelNick(getName(), parameter); bool fromMe = false; bool toMe = false; // HACK right now Server only keeps type A modes bool banTypeThang = m_server->banAddressListModes().contains(QLatin1Char(mode)); // remember if this nick had any type of op. bool wasAnyOp = false; if (parameterChannelNick) { addNickname(parameterChannelNick); wasAnyOp = parameterChannelNick->isAnyTypeOfOp(); } if (sourceNick.toLower() == m_server->loweredNickname()) fromMe = true; if (parameter.toLower() == m_server->loweredNickname()) toMe = true; switch (mode) { case 'q': if (banTypeThang) { if (plus) { if (fromMe) message = i18n("You set a quiet on %1.", parameter); else message = i18n("%1 sets a quiet on %2.", sourceNick, parameter); } else { if (fromMe) message = i18n("You remove the quiet on %1.", parameter); else message = i18n("%1 removes the quiet on %2.", sourceNick, parameter); } } else { if (plus) { if (fromMe) { if (toMe) message = i18n("You give channel owner privileges to yourself."); else message = i18n("You give channel owner privileges to %1.", parameter); } else { if (toMe) message = i18n("%1 gives channel owner privileges to you.", sourceNick); else message = i18n("%1 gives channel owner privileges to %2.", sourceNick, parameter); } } else { if (fromMe) { if (toMe) message = i18n("You take channel owner privileges from yourself."); else message = i18n("You take channel owner privileges from %1.", parameter); } else { if (toMe) message = i18n("%1 takes channel owner privileges from you.", sourceNick); else message = i18n("%1 takes channel owner privileges from %2.", sourceNick, parameter); } } if (parameterChannelNick) { parameterChannelNick->setOwner(plus); emitUpdateInfo(); } } break; case 'a': if (plus) { if (fromMe) { if (toMe) message = i18n("You give channel admin privileges to yourself."); else message = i18n("You give channel admin privileges to %1.", parameter); } else { if (toMe) message = i18n("%1 gives channel admin privileges to you.", sourceNick); else message = i18n("%1 gives channel admin privileges to %2.", sourceNick, parameter); } } else { if (fromMe) { if (toMe) message = i18n("You take channel admin privileges from yourself."); else message = i18n("You take channel admin privileges from %1.", parameter); } else { if (toMe) message = i18n("%1 takes channel admin privileges from you.", sourceNick); else message = i18n("%1 takes channel admin privileges from %2.", sourceNick, parameter); } } if (parameterChannelNick) { parameterChannelNick->setAdmin(plus); emitUpdateInfo(); } break; case 'o': if (plus) { if (fromMe) { if (toMe) message = i18n("You give channel operator privileges to yourself."); else message = i18n("You give channel operator privileges to %1.", parameter); } else { if (toMe) message = i18n("%1 gives channel operator privileges to you.", sourceNick); else message = i18n("%1 gives channel operator privileges to %2.", sourceNick, parameter); } } else { if (fromMe) { if (toMe) message = i18n("You take channel operator privileges from yourself."); else message = i18n("You take channel operator privileges from %1.", parameter); } else { if (toMe) message = i18n("%1 takes channel operator privileges from you.", sourceNick); else message = i18n("%1 takes channel operator privileges from %2.", sourceNick, parameter); } } if (parameterChannelNick) { parameterChannelNick->setOp(plus); emitUpdateInfo(); } break; case 'h': if (plus) { if (fromMe) { if (toMe) message = i18n("You give channel halfop privileges to yourself."); else message = i18n("You give channel halfop privileges to %1.", parameter); } else { if (toMe) message = i18n("%1 gives channel halfop privileges to you.", sourceNick); else message = i18n("%1 gives channel halfop privileges to %2.", sourceNick, parameter); } } else { if (fromMe) { if (toMe) message = i18n("You take channel halfop privileges from yourself."); else message = i18n("You take channel halfop privileges from %1.", parameter); } else { if (toMe) message = i18n("%1 takes channel halfop privileges from you.", sourceNick); else message = i18n("%1 takes channel halfop privileges from %2.", sourceNick, parameter); } } if (parameterChannelNick) { parameterChannelNick->setHalfOp(plus); emitUpdateInfo(); } break; //case 'O': break; case 'v': if (plus) { if (fromMe) { if (toMe) message = i18n("You give yourself permission to talk."); else message = i18n("You give %1 permission to talk.", parameter); } else { if (toMe) message = i18n("%1 gives you permission to talk.", sourceNick); else message = i18n("%1 gives %2 permission to talk.", sourceNick, parameter); } } else { if (fromMe) { if (toMe) message = i18n("You take the permission to talk from yourself."); else message = i18n("You take the permission to talk from %1.", parameter); } else { if (toMe) message = i18n("%1 takes the permission to talk from you.", sourceNick); else message = i18n("%1 takes the permission to talk from %2.", sourceNick, parameter); } } if (parameterChannelNick) { parameterChannelNick->setVoice(plus); } break; case 'c': if (plus) { if (fromMe) message = i18n("You set the channel mode to 'no colors allowed'."); else message = i18n("%1 sets the channel mode to 'no colors allowed'.", sourceNick); } else { if (fromMe) message = i18n("You set the channel mode to 'allow color codes'."); else message = i18n("%1 sets the channel mode to 'allow color codes'.", sourceNick); } break; case 'i': if (plus) { if (fromMe) message = i18n("You set the channel mode to 'invite only'."); else message = i18n("%1 sets the channel mode to 'invite only'.", sourceNick); } else { if (fromMe) message = i18n("You remove the 'invite only' mode from the channel."); else message = i18n("%1 removes the 'invite only' mode from the channel.", sourceNick); } modeI->setDown(plus); break; case 'm': if (plus) { if (fromMe) message = i18n("You set the channel mode to 'moderated'."); else message = i18n("%1 sets the channel mode to 'moderated'.", sourceNick); } else { if (fromMe) message = i18n("You set the channel mode to 'unmoderated'."); else message = i18n("%1 sets the channel mode to 'unmoderated'.", sourceNick); } modeM->setDown(plus); break; case 'n': if (plus) { if (fromMe) message = i18n("You set the channel mode to 'no messages from outside'."); else message = i18n("%1 sets the channel mode to 'no messages from outside'.", sourceNick); } else { if (fromMe) message = i18n("You set the channel mode to 'allow messages from outside'."); else message = i18n("%1 sets the channel mode to 'allow messages from outside'.", sourceNick); } modeN->setDown(plus); break; case 'p': if (plus) { if (fromMe) message = i18n("You set the channel mode to 'private'."); else message = i18n("%1 sets the channel mode to 'private'.", sourceNick); } else { if (fromMe) message = i18n("You set the channel mode to 'public'."); else message = i18n("%1 sets the channel mode to 'public'.", sourceNick); } modeP->setDown(plus); if (plus) modeS->setDown(false); break; case 's': if (plus) { if (fromMe) message = i18n("You set the channel mode to 'secret'."); else message = i18n("%1 sets the channel mode to 'secret'.", sourceNick); } else { if (fromMe) message = i18n("You set the channel mode to 'visible'."); else message = i18n("%1 sets the channel mode to 'visible'.", sourceNick); } modeS->setDown(plus); if (plus) modeP->setDown(false); break; //case 'r': break; case 't': if (plus) { if (fromMe) message = i18n("You switch on 'topic protection'."); else message = i18n("%1 switches on 'topic protection'.", sourceNick); } else { if (fromMe) message = i18n("You switch off 'topic protection'."); else message = i18n("%1 switches off 'topic protection'.", sourceNick); } modeT->setDown(plus); break; case 'k': if (plus) { if (fromMe) message = i18n("You set the channel key to '%1'.", parameter); else message = i18n("%1 sets the channel key to '%2'.", sourceNick, parameter); } else { if (fromMe) message = i18n("You remove the channel key."); else message = i18n("%1 removes the channel key.", sourceNick); } modeK->setDown(plus); break; case 'l': if (plus) { if (fromMe) message = i18np("You set the channel limit to 1 nick.", "You set the channel limit to %1 nicks.", parameter); else message = i18np("%2 sets the channel limit to 1 nick.", "%2 sets the channel limit to %1 nicks.", parameter, sourceNick); } else { if (fromMe) message = i18n("You remove the channel limit."); else message = i18n("%1 removes the channel limit.", sourceNick); } modeL->setDown(plus); if (plus) limit->setText(parameter); else limit->clear(); break; case 'b': if (plus) { if (fromMe) message = i18n("You set a ban on %1.", parameter); else message = i18n("%1 sets a ban on %2.", sourceNick, parameter); } else { if (fromMe) message = i18n("You remove the ban on %1.", parameter); else message = i18n("%1 removes the ban on %2.", sourceNick, parameter); } break; case 'e': if (plus) { if (fromMe) message = i18n("You set a ban exception on %1.", parameter); else message = i18n("%1 sets a ban exception on %2.", sourceNick, parameter); } else { if (fromMe) message = i18n("You remove the ban exception on %1.", parameter); else message = i18n("%1 removes the ban exception on %2.", sourceNick, parameter); } break; case 'I': if (plus) { if (fromMe) message = i18n("You set invitation mask %1.", parameter); else message = i18n("%1 sets invitation mask %2.", sourceNick, parameter); } else { if (fromMe) message = i18n("You remove the invitation mask %1.", parameter); else message = i18n("%1 removes the invitation mask %2.", sourceNick, parameter); } break; default: if (plus) { if (Konversation::getChannelModesHash().contains(QLatin1Char(mode))) { if (fromMe) message = i18n("You set the channel mode '%1'.", Konversation::getChannelModesHash().value(QLatin1Char(mode))); else message= i18n("%1 sets the channel mode '%2'.", sourceNick, Konversation::getChannelModesHash().value(QLatin1Char(mode))); } else { if (fromMe) message = i18n("You set channel mode +%1", QLatin1Char(mode)); else message = i18n("%1 sets channel mode +%2", sourceNick, QLatin1Char(mode)); } } else { if (Konversation::getChannelModesHash().contains(QLatin1Char(mode))) { if (fromMe) message = i18n("You remove the channel mode '%1'.", Konversation::getChannelModesHash().value(QLatin1Char(mode))); else message= i18n("%1 removes the channel mode '%2'.", sourceNick, Konversation::getChannelModesHash().value(QLatin1Char(mode))); } else { if (fromMe) message = i18n("You set channel mode -%1", QLatin1Char(mode)); else message = i18n("%1 sets channel mode -%2", sourceNick, QLatin1Char(mode)); } } } // check if this nick's anyOp-status has changed and adjust ops accordingly if (parameterChannelNick) { if (wasAnyOp && (!parameterChannelNick->isAnyTypeOfOp())) adjustOps(-1); else if (!wasAnyOp && parameterChannelNick->isAnyTypeOfOp()) adjustOps(1); } if (!message.isEmpty() && !Preferences::self()->useLiteralModes()) { appendCommandMessage(i18n("Mode"), message, messageTags); } updateModeWidgets(mode, plus, parameter); } void Channel::clearModeList() { QString k; // Keep channel password in the backing store, for rejoins. for (QStringList::const_iterator it = m_modeList.constBegin(); it != m_modeList.constEnd(); ++it) { if ((*it)[0] == QLatin1Char('k')) k = (*it); } m_modeList.clear(); if (!k.isEmpty()) m_modeList << k; modeT->setOn(0); modeT->setDown(0); modeN->setOn(0); modeN->setDown(0); modeS->setOn(0); modeS->setDown(0); modeI->setOn(0); modeI->setDown(0); modeP->setOn(0); modeP->setDown(0); modeM->setOn(0); modeM->setDown(0); modeK->setOn(0); modeK->setDown(0); modeL->setOn(0); modeL->setDown(0); limit->clear(); emit modesChanged(); } void Channel::updateModeWidgets(char mode, bool plus, const QString ¶meter) { ModeButton* widget=nullptr; if(mode=='t') widget=modeT; else if(mode=='n') widget=modeN; else if(mode=='s') widget=modeS; else if(mode=='i') widget=modeI; else if(mode=='p') widget=modeP; else if(mode=='m') widget=modeM; else if(mode=='k') widget=modeK; else if(mode=='l') { widget=modeL; if(plus) limit->setText(parameter); else limit->clear(); } if(widget) widget->setOn(plus); if(plus) { m_modeList.append(QString(QLatin1Char(mode) + parameter)); } else { QStringList removable = m_modeList.filter(QRegExp(QString(QStringLiteral("^%1.*")).arg(mode))); foreach(const QString &mode, removable) { m_modeList.removeOne(mode); } } emit modesChanged(); } void Channel::updateQuickButtons() { delete m_buttonsGrid; m_buttonsGrid = nullptr; // the grid that holds the quick action buttons m_buttonsGrid = new QWidget (nickListButtons); //Q3Grid(2, nickListButtons); nickListButtons->layout()->addWidget(m_buttonsGrid); m_buttonsGrid->hide(); QGridLayout* layout = new QGridLayout (m_buttonsGrid); layout->setContentsMargins(0, 0, 0, 0); int col = 0; int row = 0; const QStringList &newButtonList = Preferences::quickButtonList(); // add new quick buttons for(int index=0;indexaddWidget (quickButton, row, col); row += col; connect(quickButton, SIGNAL(clicked(QString)), this, SLOT(quickButtonClicked(QString))); // Get the button definition QString buttonText=newButtonList[index]; // Extract button label QString buttonLabel=buttonText.section(QLatin1Char(','),0,0); // Extract button definition buttonText=buttonText.section(QLatin1Char(','),1); quickButton->setText(buttonLabel); quickButton->setDefinition(buttonText); // Add tool tips QString toolTip=buttonText.replace(QLatin1Char('&'),QStringLiteral("&")). replace(QLatin1Char('<'),QStringLiteral("<")). replace(QLatin1Char('>'),QStringLiteral(">")); quickButton->setToolTip(toolTip); quickButton->show(); } // for // set hide() or show() on grid showQuickButtons(Preferences::self()->showQuickButtons()); } void Channel::showQuickButtons(bool show) { // Qt does not redraw the buttons properly when they are not on screen // while getting hidden, so we remember the "soon to be" state here. if(isHidden() || !m_buttonsGrid) { quickButtonsChanged=true; quickButtonsState=show; } else { if(show) m_buttonsGrid->show(); else m_buttonsGrid->hide(); } } void Channel::showModeButtons(bool show) { // Qt does not redraw the buttons properly when they are not on screen // while getting hidden, so we remember the "soon to be" state here. if(isHidden()) { modeButtonsChanged=true; modeButtonsState=show; } else { if(show) { topicSplitterHidden = false; modeBox->show(); modeBox->parentWidget()->show(); } else { modeBox->hide(); if(topicLine->isHidden()) { topicSplitterHidden = true; modeBox->parentWidget()->hide(); } } } } void Channel::indicateAway(bool show) { // Qt does not redraw the label properly when they are not on screen // while getting hidden, so we remember the "soon to be" state here. if(isHidden()) { awayChanged=true; awayState=show; } else { if(show) awayLabel->show(); else awayLabel->hide(); } } void Channel::showEvent(QShowEvent*) { // If the show quick/mode button settings have changed, apply the changes now if(quickButtonsChanged) { quickButtonsChanged=false; showQuickButtons(quickButtonsState); } if(modeButtonsChanged) { modeButtonsChanged=false; showModeButtons(modeButtonsState); } if(awayChanged) { awayChanged=false; indicateAway(awayState); } syncSplitters(); } void Channel::syncSplitters() { QList vertSizes = Preferences::self()->topicSplitterSizes(); QList horizSizes = Preferences::self()->channelSplitterSizes(); if (vertSizes.isEmpty()) { vertSizes << m_topicButton->height() << (height() - m_topicButton->height()); Preferences::self()->setTopicSplitterSizes(vertSizes); } if (horizSizes.isEmpty()) { // An approximation of a common NICKLEN plus the width of the icon, // tested with 8pt and 10pt DejaVu Sans and Droid Sans. int listWidth = fontMetrics().averageCharWidth() * 17 + 20; horizSizes << (width() - listWidth) << listWidth; Preferences::self()->setChannelSplitterSizes(horizSizes); } m_vertSplitter->setSizes(vertSizes); m_horizSplitter->setSizes(horizSizes); splittersInitialized = true; } void Channel::updateAppearance() { QPalette palette; if (Preferences::self()->inputFieldsBackgroundColor()) { palette.setColor(QPalette::Text, Preferences::self()->color(Preferences::ChannelMessage)); palette.setColor(QPalette::Base, Preferences::self()->color(Preferences::TextViewBackground)); palette.setColor(QPalette::AlternateBase, Preferences::self()->color(Preferences::AlternateBackground)); } limit->setPalette(palette); topicLine->setPalette(QPalette()); if (Preferences::self()->customTextFont()) { topicLine->setFont(Preferences::self()->textFont()); m_inputBar->setFont(Preferences::self()->textFont()); nicknameCombobox->setFont(Preferences::self()->textFont()); limit->setFont(Preferences::self()->textFont()); } else { topicLine->setFont(QFontDatabase::systemFont(QFontDatabase::GeneralFont)); m_inputBar->setFont(QFontDatabase::systemFont(QFontDatabase::GeneralFont)); nicknameCombobox->setFont(QFontDatabase::systemFont(QFontDatabase::GeneralFont)); limit->setFont(QFontDatabase::systemFont(QFontDatabase::GeneralFont)); } nicknameListView->resort(); nicknameListView->setPalette(palette); nicknameListView->setAlternatingRowColors(Preferences::self()->inputFieldsBackgroundColor()); if (Preferences::self()->customListFont()) nicknameListView->setFont(Preferences::self()->listFont()); else nicknameListView->setFont(QFontDatabase::systemFont(QFontDatabase::GeneralFont)); nicknameListView->refresh(); showModeButtons(Preferences::self()->showModeButtons()); showNicknameList(Preferences::self()->showNickList()); showNicknameBox(Preferences::self()->showNicknameBox()); showTopic(Preferences::self()->showTopic()); setAutoUserhost(Preferences::self()->autoUserhost()); QMetaObject::invokeMethod(this, "updateQuickButtons", Qt::QueuedConnection); // Nick sorting settings might have changed. Trigger timer if (m_delayedSortTimer) { m_delayedSortTrigger = DELAYED_SORT_TRIGGER + 1; m_delayedSortTimer->start(500 + qrand()/2000); } ChatWindow::updateAppearance(); } void Channel::nicknameComboboxChanged() { QString newNick=nicknameCombobox->currentText(); oldNick=m_server->getNickname(); if (oldNick != newNick) { nicknameCombobox->setCurrentIndex(nicknameCombobox->findText(oldNick)); changeNickname(newNick); // return focus to input line m_inputBar->setFocus(); } } void Channel::changeNickname(const QString& newNickname) { if (!newNickname.isEmpty()) m_server->queue(QStringLiteral("NICK ")+newNickname); } void Channel::queueNicks(const QStringList& nicknameList) { if (nicknameList.isEmpty()) return; m_nickQueue.append(nicknameList); processQueuedNicks(); } void Channel::endOfNames() { if (!m_initialNamesReceived) { m_initialNamesReceived = true; if (m_server->capabilities() & Server::AwayNotify && !Preferences::self()->autoWhoContinuousEnabled()) { // Do one who request to get the initial away state for the channel QMetaObject::invokeMethod(m_server, "requestWho", Qt::QueuedConnection, Q_ARG(QString, getName())); } scheduleAutoWho(); } } void Channel::childAdjustFocus() { m_inputBar->setFocus(); refreshModeButtons(); } void Channel::refreshModeButtons() { bool enable = true; if(getOwnChannelNick()) { enable=getOwnChannelNick()->isAnyTypeOfOp(); } // if not channel nick, then enable is true - fall back to assuming they are op //don't disable the mode buttons since you can't then tell if they are enabled or not. //needs to be fixed somehow /* modeT->setEnabled(enable); modeN->setEnabled(enable); modeS->setEnabled(enable); modeI->setEnabled(enable); modeP->setEnabled(enable); modeM->setEnabled(enable); modeK->setEnabled(enable); modeL->setEnabled(enable);*/ limit->setEnabled(enable); // Tooltips for the ModeButtons QString opOnly; if(!enable) opOnly = i18n("You have to be an operator to change this."); modeT->setToolTip(i18n("Topic can be changed by channel operator only. %1", opOnly)); modeN->setToolTip(i18n("No messages to channel from clients on the outside. %1", opOnly)); modeS->setToolTip(i18n("Secret channel. %1", opOnly)); modeI->setToolTip(i18n("Invite only channel. %1", opOnly)); modeP->setToolTip(i18n("Private channel. %1", opOnly)); modeM->setToolTip(i18n("Moderated channel. %1", opOnly)); modeK->setToolTip(i18n("Protect channel with a password.")); modeL->setToolTip(i18n("Set user limit to channel.")); } void Channel::nicknameListViewTextChanged(int textChangedFlags) { m_nicknameListViewTextChanged |= textChangedFlags; } void Channel::autoUserhost() { if(Preferences::self()->autoUserhost() && !Preferences::self()->autoWhoContinuousEnabled()) { int limit = 5; QString nickString; foreach (Nick* nick, getNickList()) { if(nick->getChannelNick()->getHostmask().isEmpty()) { if(limit--) nickString = nickString + nick->getChannelNick()->getNickname() + QLatin1Char(' '); else break; } } if(!nickString.isEmpty()) m_server->requestUserhost(nickString); } if(!nicknameList.isEmpty()) { resizeNicknameListViewColumns(); } } void Channel::setAutoUserhost(bool state) { nicknameListView->setColumnHidden(Nick::HostmaskColumn, !state); if (state) { nicknameListView->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); // Cannot use QHeaderView::ResizeToContents here because it is slow // and it gets triggered by setSortingEnabled(). Using timed resize // instead, see Channel::autoUserhost() above. nicknameListView->header()->setSectionResizeMode(Nick::NicknameColumn, QHeaderView::Fixed); nicknameListView->header()->setSectionResizeMode(Nick::HostmaskColumn, QHeaderView::Fixed); userhostTimer.start(10000); m_nicknameListViewTextChanged |= 0xFF; // ResizeColumnsToContents QTimer::singleShot(0, this, &Channel::autoUserhost); // resize columns ASAP } else { nicknameListView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); nicknameListView->header()->setSectionResizeMode(Nick::NicknameColumn, QHeaderView::Stretch); userhostTimer.stop(); } } void Channel::scheduleAutoWho(int msec) { // The first auto-who is scheduled by ENDOFNAMES in InputFilter, which means // the first auto-who occurs one interval after it. This has two desirable // consequences specifically related to the startup phase: auto-who dispatch // doesn't occur at the same time for all channels that are auto-joined, and // it gives some breathing room to process the NAMES replies for all channels // first before getting started on WHO. // Subsequent auto-whos are scheduled by ENDOFWHO in InputFilter. However, // autoWho() might refuse to actually do the request if the number of nicks // in the channel exceeds the threshold, and will instead schedule another // attempt later. Thus scheduling an auto-who does not guarantee it will be // performed. // If this is called mid-interval (e.g. due to the ENDOFWHO from a manual WHO) // it will reset the interval to avoid cutting it short. + if (m_server->whoRequestsDisabled()) + return; + if (m_whoTimer.isActive()) m_whoTimer.stop(); if (Preferences::self()->autoWhoContinuousEnabled()) { if (msec > 0) m_whoTimer.start(msec); else m_whoTimer.start(Preferences::self()->autoWhoContinuousInterval() * 1000); } } void Channel::autoWho() { // Try again later if there are too many nicks or we're already processing a WHO request. if ((nicks > Preferences::self()->autoWhoNicksLimit()) || m_server->getInputFilter()->isWhoRequestUnderProcess(getName())) { scheduleAutoWho(); return; } m_server->requestWho(getName()); } void Channel::updateAutoWho() { if (!Preferences::self()->autoWhoContinuousEnabled()) m_whoTimer.stop(); else if (Preferences::self()->autoWhoContinuousEnabled() && !m_whoTimer.isActive()) autoWho(); else if (m_whoTimer.isActive()) { // The below tries to meet user expectations on an interval settings change, // making two assumptions: // - If the new interval is lower than the old one, the user may be impatient // and desires an information update. // - If the new interval is longer than the old one, the user may be trying to // avoid Konversation producing too much traffic in a given timeframe, and // wants it to stop doing so sooner rather than later. // Both require rescheduling the next auto-who request. int interval = Preferences::self()->autoWhoContinuousInterval() * 1000; if (interval != m_whoTimer.interval()) { if (m_whoTimerStarted.elapsed() >= interval) { // If the time since the last auto-who request is longer than (or // equal to) the new interval setting, it follows that the new // setting is lower than the old setting. In this case issue a new // request immediately, which is the closest we can come to acting // as if the new setting had been active all along, short of tra- // velling back in time to change history. This handles the impa- // tient user. // FIXME: Adjust algorithm when time machine becomes available. m_whoTimer.stop(); autoWho(); } else { // If on the other hand the elapsed time is shorter than the new // interval setting, the new setting could be either shorter or // _longer_ than the old setting. Happily, this time we can actually // behave as if the new setting had been active all along, by sched- // uling the next request to happen in the new interval time minus // the already elapsed time, meeting user expecations for both cases // originally laid out. scheduleAutoWho(interval - m_whoTimerStarted.elapsed()); } } } } void Channel::fadeActivity() { foreach (Nick *nick, nicknameList) { nick->getChannelNick()->lessActive(); } } bool Channel::canBeFrontView() { return true; } bool Channel::searchView() { return true; } bool Channel::closeYourself(bool confirm) { int result=KMessageBox::Continue; if (confirm) result = KMessageBox::warningContinueCancel(this, i18n("Do you want to leave %1?", getName()), i18n("Leave Channel"), KGuiItem(i18n("Leave")), KStandardGuiItem::cancel(), QStringLiteral("QuitChannelTab")); if (result==KMessageBox::Continue) { m_server->closeChannel(getName()); m_server->removeChannel(this); deleteLater(); return true; } else m_recreationScheduled = false; return false; } void Channel::serverOnline(bool online) { setActive(online); } //Used to disable functions when not connected, does not necessarily mean the server is offline void Channel::setActive(bool active) { if (active) nicknameCombobox->setEnabled(true); else { m_initialNamesReceived = false; purgeNicks(); nicknameCombobox->setEnabled(false); topicLine->clear(); clearModeList(); clearBanList(); m_whoTimer.stop(); } } void Channel::showTopic(bool show) { if(show) { topicSplitterHidden = false; topicLine->show(); m_topicButton->show(); topicLine->parentWidget()->show(); } else { topicLine->hide(); m_topicButton->hide(); if(modeBox->isHidden()) { topicSplitterHidden = true; topicLine->parentWidget()->hide(); } } } void Channel::processQueuedNicks(bool flush) { // This pops nicks from the front of a queue added to by incoming NAMES // messages and adds them to the channel nicklist, calling itself via // the event loop until the last invocation finds the queue empty and // adjusts the nicks/ops counters and requests a nicklist sort, but only // if previous invocations actually processed any nicks. The latter is // an optimization for the common case of processing being kicked off by // flushNickQueue(), which is done e.g. before a nick rename or part to // make sure the channel is up to date and will usually find an empty // queue. This is also the use case for the 'flush' parameter, which if // true causes the recursion to block in a tight loop instead of queueing // via the event loop. if (m_nickQueue.isEmpty()) { if (m_processedNicksCount) { adjustNicks(m_processedNicksCount); adjustOps(m_processedOpsCount); m_processedNicksCount = 0; m_processedOpsCount = 0; sortNickList(); nicknameListView->setUpdatesEnabled(true); if (Preferences::self()->autoUserhost()) resizeNicknameListViewColumns(); } } else { QString nickname; while (nickname.isEmpty() && !m_nickQueue.isEmpty()) nickname = m_nickQueue.takeFirst(); QString userHost; if(m_server->capabilities() & Server::UserHostInNames) { int index = nickname.indexOf(QLatin1Char('!')); if(index >= 0) { userHost = nickname.mid(index + 1); nickname.truncate(index); } } bool admin = false; bool owner = false; bool op = false; bool halfop = false; bool voice = false; // Remove possible mode characters from nickname and store the resulting mode. m_server->mangleNicknameWithModes(nickname, admin, owner, op, halfop, voice); // TODO: Make these an enumeration in KApplication or somewhere, we can use them as well. unsigned int mode = (admin ? 16 : 0) + (owner ? 8 : 0) + (op ? 4 : 0) + (halfop ? 2 : 0) + (voice ? 1 : 0); // Check if nick is already in the nicklist. if (!nickname.isEmpty() && !getNickByName(nickname)) { ChannelNickPtr nick = m_server->addNickToJoinedChannelsList(getName(), nickname); Q_ASSERT(nick); nick->setMode(mode); if(!userHost.isEmpty()) { nick->getNickInfo()->setHostmask(userHost); } fastAddNickname(nick); ++m_processedNicksCount; if (nick->isAdmin() || nick->isOwner() || nick->isOp() || nick->isHalfOp()) ++m_processedOpsCount; } QMetaObject::invokeMethod(this, "processQueuedNicks", flush ? Qt::DirectConnection : Qt::QueuedConnection, Q_ARG(bool, flush)); } } void Channel::setChannelEncoding(const QString& encoding) // virtual { if(m_server->getServerGroup()) Preferences::setChannelEncoding(m_server->getServerGroup()->id(), getName(), encoding); else Preferences::setChannelEncoding(m_server->getDisplayName(), getName(), encoding); } QString Channel::getChannelEncoding() // virtual { if(m_server->getServerGroup()) return Preferences::channelEncoding(m_server->getServerGroup()->id(), getName()); return Preferences::channelEncoding(m_server->getDisplayName(), getName()); } QString Channel::getChannelEncodingDefaultDesc() // virtual { return i18n("Identity Default ( %1 )", getServer()->getIdentity()->getCodecName()); } void Channel::showNicknameBox(bool show) { if(show) { nicknameCombobox->show(); } else { nicknameCombobox->hide(); } } void Channel::showNicknameList(bool show) { if (show) { channelSplitterHidden = false; nickListButtons->show(); } else { channelSplitterHidden = true; nickListButtons->hide(); } } void Channel::requestNickListSort() { m_delayedSortTrigger++; if (m_delayedSortTrigger == DELAYED_SORT_TRIGGER && !m_delayedSortTimer->isActive()) { nicknameListView->fastSetSortingEnabled(false); m_delayedSortTimer->start(1000); } } void Channel::delayedSortNickList() { sortNickList(true); } void Channel::sortNickList(bool delayed) { if (!delayed || m_delayedSortTrigger > DELAYED_SORT_TRIGGER) { std::sort(nicknameList.begin(), nicknameList.end(), nickLessThan); nicknameListView->resort(); } if (!nicknameListView->isSortingEnabled()) nicknameListView->fastSetSortingEnabled(true); m_delayedSortTrigger = 0; m_delayedSortTimer->stop(); } void Channel::repositionNick(Nick *nick) { int index = nicknameList.indexOf(nick); if (index > -1) { // Trigger nick reposition in the nicklist including // field updates nick->refresh(); // Readd nick to the nicknameList nicknameList.removeAt(index); fastAddNickname(nick->getChannelNick(), nick); } else { qWarning() << "Nickname " << nick->getChannelNick()->getNickname() << " not found!"<< endl; } } bool Channel::eventFilter(QObject* watched, QEvent* e) { if((watched == nicknameListView) && (e->type() == QEvent::Resize) && splittersInitialized && isVisible()) { if (!topicSplitterHidden && !channelSplitterHidden) { Preferences::self()->setChannelSplitterSizes(m_horizSplitter->sizes()); Preferences::self()->setTopicSplitterSizes(m_vertSplitter->sizes()); } if (!topicSplitterHidden && channelSplitterHidden) { Preferences::self()->setTopicSplitterSizes(m_vertSplitter->sizes()); } if (!channelSplitterHidden && topicSplitterHidden) { Preferences::self()->setChannelSplitterSizes(m_horizSplitter->sizes()); } } return ChatWindow::eventFilter(watched, e); } void Channel::addBan(const QString& ban) { for ( QStringList::iterator it = m_BanList.begin(); it != m_BanList.end(); ++it ) { if ((*it).section(QLatin1Char(' '), 0, 0) == ban.section(QLatin1Char(' '), 0, 0)) { // Ban is already in list. it = m_BanList.erase(it); emit banRemoved(ban.section(QLatin1Char(' '), 0, 0)); if (it == m_BanList.end()) break; } } m_BanList.prepend(ban); emit banAdded(ban); } void Channel::removeBan(const QString& ban) { foreach(const QString &string, m_BanList) { if (string.section(QLatin1Char(' '), 0, 0) == ban) { m_BanList.removeOne(string); emit banRemoved(ban); } } } void Channel::clearBanList() { m_BanList.clear(); emit banListCleared(); } void Channel::append(const QString& nickname, const QString& message, const QHash &messageTags, const QString& label) { if(nickname != getServer()->getNickname()) { Nick* nick = getNickByName(nickname); if(nick) { nick->getChannelNick()->setTimeStamp(QDateTime::currentDateTime().toTime_t()); } } ChatWindow::append(nickname, message, messageTags, label); nickActive(nickname); } void Channel::appendAction(const QString& nickname, const QString& message, const QHash &messageTags) { if(nickname != getServer()->getNickname()) { Nick* nick = getNickByName(nickname); if(nick) { nick->getChannelNick()->setTimeStamp(QDateTime::currentDateTime().toTime_t()); } } ChatWindow::appendAction(nickname, message, messageTags); nickActive(nickname); } void Channel::nickActive(const QString& nickname) //FIXME reported to crash, can't reproduce { ChannelNickPtr channelnick=getChannelNick(nickname); //XXX Would be nice to know why it can be null here... if (channelnick) { channelnick->moreActive(); if (Preferences::self()->sortByActivity()) { Nick* nick = getNickByName(nickname); if (nick) { nick->repositionMe(); } } } } #ifdef HAVE_QCA2 Konversation::Cipher* Channel::getCipher() { if(!m_cipher) m_cipher = new Konversation::Cipher(); return m_cipher; } #endif void Channel::updateNickInfos() { foreach(Nick* nick, nicknameList) { if(nick->getChannelNick()->getNickInfo()->isChanged()) { nick->refresh(); } } } void Channel::updateChannelNicks(const QString& channel) { if(channel != name.toLower()) return; foreach(Nick* nick, nicknameList) { if(nick->getChannelNick()->isChanged()) { nick->refresh(); if(nick->getChannelNick() == m_ownChannelNick) { refreshModeButtons(); } } } } void Channel::resizeNicknameListViewColumns() { // Resize columns if needed (on regular basis) if (m_nicknameListViewTextChanged & (1 << Nick::NicknameColumn)) nicknameListView->resizeColumnToContents(Nick::NicknameColumn); if (m_nicknameListViewTextChanged & (1 << Nick::HostmaskColumn)) nicknameListView->resizeColumnToContents(Nick::HostmaskColumn); m_nicknameListViewTextChanged = 0; } // // NickList // NickList::NickList() : QList() { } QString NickList::completeNick(const QString& pattern, bool& complete, QStringList& found, bool skipNonAlfaNum, bool caseSensitive) { found.clear(); QString prefix(QLatin1Char('^')); QString newNick; QString prefixCharacter = Preferences::self()->prefixCharacter(); NickList foundNicks; if((pattern.contains(QRegExp(QStringLiteral("^(\\d|\\w)")))) && skipNonAlfaNum) { prefix = QStringLiteral("^([^\\d\\w]|[\\_]){0,}"); } QRegExp regexp(prefix + QRegExp::escape(pattern)); regexp.setCaseSensitivity(caseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive); foreach (Nick* nick, *this) { newNick = nick->getChannelNick()->getNickname(); if(!prefix.isEmpty() && newNick.contains(prefixCharacter)) { newNick = newNick.section( prefixCharacter,1 ); } if(newNick.contains(regexp)) { foundNicks.append(nick); } } std::sort(foundNicks.begin(), foundNicks.end(), nickTimestampLessThan); foreach (Nick *nick, foundNicks) { found.append(nick->getChannelNick()->getNickname()); } if(found.count() > 1) { bool ok = true; int patternLength = pattern.length(); QString firstNick = found[0]; int firstNickLength = firstNick.length(); int foundCount = found.count(); while(ok && ((patternLength) < firstNickLength)) { ++patternLength; QStringList tmp = found.filter(firstNick.left(patternLength), caseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive); if(tmp.count() != foundCount) { ok = false; --patternLength; } } complete = false; return firstNick.left(patternLength); } else if(found.count() == 1) { complete = true; return found[0]; } return QString(); } bool NickList::containsNick(const QString& nickname) { foreach (Nick* nick, *this) { if (nick->getChannelNick()->getNickname()==nickname) return true; } return false; } // 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: diff --git a/src/irc/server.cpp b/src/irc/server.cpp index 97d879e1..036670b8 100644 --- a/src/irc/server.cpp +++ b/src/irc/server.cpp @@ -1,4425 +1,4443 @@ // -*- 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-2016 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 "application.h" #include "connectionmanager.h" #include "dcccommon.h" #include "dccfiledialog.h" #include "transfermanager.h" #include "transfersend.h" #include "transferrecv.h" #include "chat.h" #include "recipientdialog.h" #include "nick.h" #include "irccharsets.h" #include "viewcontainer.h" #include "rawlog.h" #include "channellistpanel.h" #include "scriptlauncher.h" #include "serverison.h" #include "notificationhandler.h" #include "awaymanager.h" #include "ircinput.h" #include #include #include #include #include #include #include #include #include #include #include #include using namespace Konversation; int Server::m_availableConnectionId = 0; Server::Server(QObject* parent, ConnectionSettings& settings) : QObject(parent) { m_connectionId = m_availableConnectionId; m_availableConnectionId++; setConnectionSettings(settings); m_connectionState = Konversation::SSNeverConnected; m_recreationScheduled = false; m_delayedConnectTimer = new QTimer(this); m_delayedConnectTimer->setSingleShot(true); connect(m_delayedConnectTimer, &QTimer::timeout, this, &Server::connectToIRCServer); m_reconnectImmediately = false; for (int i=0; i <= Application::instance()->countOfQueues(); i++) { //QList r=Preferences::queueRate(i); IRCQueue *q=new IRCQueue(this, Application::instance()->staticrates[i]); //FIXME these are supposed to be in the rc m_queues.append(q); } m_processingIncoming = false; m_identifyMsg = false; m_capRequested = 0; m_capAnswered = 0; m_capEndDelayed = false; m_autoJoin = false; m_capabilities = NoCapabilies; + m_whoRequestsDisabled = false; initCapablityNames(); m_nickIndices.clear(); m_nickIndices.append(0); m_nickListModel = new QStringListModel(this); m_currentLag = -1; m_rawLog = nullptr; m_channelListPanel = nullptr; m_serverISON = nullptr; m_away = false; m_socket = nullptr; m_prevISONList = QStringList(); m_bytesReceived = 0; m_encodedBytesSent=0; m_bytesSent=0; m_linesSent=0; // TODO fold these into a QMAP, and these need to be reset to RFC values if this server object is reused. m_serverNickPrefixModes = QStringLiteral("ovh"); m_serverNickPrefixes = QStringLiteral("@+%"); m_banAddressListModes = QLatin1Char('b'); // {RFC-1459, draft-brocklesby-irc-isupport} -> pick one m_channelPrefixes = QStringLiteral("#&"); m_modesCount = 3; m_sslErrorLock = false; m_topicLength = -1; setObjectName(QLatin1String("server_") + m_connectionSettings.name()); setNickname(m_connectionSettings.initialNick()); obtainNickInfo(getNickname()); m_statusView = getViewContainer()->addStatusView(this); if (Preferences::self()->rawLog()) addRawLog(false); m_inputFilter.setServer(this); m_outputFilter = new Konversation::OutputFilter(this); // For /msg query completion m_completeQueryPosition = 0; updateAutoJoin(m_connectionSettings.oneShotChannelList()); if (!getIdentity()->getShellCommand().isEmpty()) QTimer::singleShot(0, this, &Server::doPreShellCommand); else QTimer::singleShot(0, this, &Server::connectToIRCServer); initTimers(); if (getIdentity()->getShellCommand().isEmpty()) connectSignals(); // TODO FIXME this disappeared in a merge, ensure it should have updateConnectionState(Konversation::SSNeverConnected); m_nickInfoChangedTimer = new QTimer(this); m_nickInfoChangedTimer->setSingleShot(true); m_nickInfoChangedTimer->setInterval(3000); connect(m_nickInfoChangedTimer, &QTimer::timeout, this, &Server::sendNickInfoChangedSignals); m_channelNickChangedTimer = new QTimer(this); m_channelNickChangedTimer->setSingleShot(true); m_channelNickChangedTimer->setInterval(1000); connect(m_channelNickChangedTimer, &QTimer::timeout, this, &Server::sendChannelNickChangedSignals); } Server::~Server() { //send queued messages qDebug() << "Server::~Server(" << getServerName() << ")"; // Delete helper object. delete m_serverISON; m_serverISON = nullptr; // clear nicks online emit nicksNowOnline(this,QStringList(),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(); } delete m_statusView; closeRawLog(); closeChannelListPanel(); if (m_recreationScheduled) { Konversation::ChannelList channelList; foreach (Channel* channel, m_channelList) { channelList << channel->channelSettings(); } m_connectionSettings.setOneShotChannelList(channelList); } qDeleteAll(m_channelList); m_channelList.clear(); m_loweredChannelNameHash.clear(); qDeleteAll(m_queryList); m_queryList.clear(); purgeData(); //Delete the queues qDeleteAll(m_queues); emit destroyed(m_connectionId); if (m_recreationScheduled) { qRegisterMetaType("ConnectionSettings"); qRegisterMetaType("Konversation::ConnectionFlag"); Application* konvApp = Application::instance(); QMetaObject::invokeMethod(konvApp->getConnectionManager(), "connectTo", Qt::QueuedConnection, Q_ARG(Konversation::ConnectionFlag, Konversation::CreateNewConnection), Q_ARG(ConnectionSettings, m_connectionSettings)); } qDebug() << "~Server done"; } void Server::purgeData() { // Delete all the NickInfos and ChannelNick structures. m_allNicks.clear(); ChannelMembershipMap::ConstIterator it; for ( it = m_joinedChannels.constBegin(); it != m_joinedChannels.constEnd(); ++it ) delete it.value(); m_joinedChannels.clear(); for ( it = m_unjoinedChannels.constBegin(); it != m_unjoinedChannels.constEnd(); ++it ) delete it.value(); m_unjoinedChannels.clear(); m_queryNicks.clear(); delete m_serverISON; m_serverISON = nullptr; } //... so called to match the ChatWindow derivatives. bool Server::closeYourself(bool askForConfirmation) { m_statusView->closeYourself(askForConfirmation); return true; } void Server::cycle() { m_recreationScheduled = true; m_statusView->closeYourself(); } void Server::doPreShellCommand() { KShell::Errors e; QStringList command = KShell::splitArgs(getIdentity()->getShellCommand(), KShell::TildeExpand, &e); if (e != KShell::NoError) { //FIXME The flow needs to be refactored, add a finally-like method that does the ready-to-connect stuff // "The pre-connect shell command could not be understood!"); preShellCommandExited(m_preShellCommand.exitCode(), m_preShellCommand.exitStatus()); } else { // FIXME add i18n, and in preShellCommandExited and preShellCommandError getStatusView()->appendServerMessage(i18n("Info"), i18nc("The command mentioned is executed in a shell prior to connecting.", "Running pre-connect shell command...")); connect(&m_preShellCommand, SIGNAL(finished(int,QProcess::ExitStatus)), SLOT(preShellCommandExited(int,QProcess::ExitStatus))); connect(&m_preShellCommand, SIGNAL(error(QProcess::ProcessError)), SLOT(preShellCommandError(QProcess::ProcessError))); m_preShellCommand.setProgram(command); m_preShellCommand.start(); // NOTE: isConnecting is tested in connectToIRCServer so there's no guard here if (m_preShellCommand.state() == QProcess::NotRunning) preShellCommandExited(m_preShellCommand.exitCode(), m_preShellCommand.exitStatus()); } } void Server::initTimers() { m_notifyTimer.setObjectName(QStringLiteral("notify_timer")); m_notifyTimer.setSingleShot(true); m_incomingTimer.setObjectName(QStringLiteral("incoming_timer")); m_pingSendTimer.setSingleShot(true); } void Server::connectSignals() { // Timers connect(&m_incomingTimer, &QTimer::timeout, this, &Server::processIncomingData); connect(&m_notifyTimer, &QTimer::timeout, this, &Server::notifyTimeout); connect(&m_pingResponseTimer, &QTimer::timeout, this, &Server::updateLongPongLag); connect(&m_pingSendTimer, &QTimer::timeout, this, &Server::sendPing); // OutputFilter connect(getOutputFilter(), SIGNAL(requestDccSend()), this,SLOT(requestDccSend()), Qt::QueuedConnection); connect(getOutputFilter(), SIGNAL(requestDccSend(QString)), this, SLOT(requestDccSend(QString)), Qt::QueuedConnection); connect(getOutputFilter(), &OutputFilter::multiServerCommand, this, &Server::sendMultiServerCommand); connect(getOutputFilter(), &OutputFilter::reconnectServer, this, &Server::reconnectServer); connect(getOutputFilter(), &OutputFilter::disconnectServer, this, &Server::disconnectServer); connect(getOutputFilter(), &OutputFilter::quitServer, this, &Server::quitServer); connect(getOutputFilter(), SIGNAL(openDccSend(QString,QUrl)), this, SLOT(addDccSend(QString,QUrl)), Qt::QueuedConnection); connect(getOutputFilter(), &OutputFilter::openDccChat, this, &Server::openDccChat, Qt::QueuedConnection); connect(getOutputFilter(), &OutputFilter::openDccWBoard, this, &Server::openDccWBoard, Qt::QueuedConnection); connect(getOutputFilter(), &OutputFilter::acceptDccGet, this, &Server::acceptDccGet); connect(getOutputFilter(), &OutputFilter::sendToAllChannels, this, &Server::sendToAllChannels); connect(getOutputFilter(), &OutputFilter::banUsers, this, &Server::requestBan); connect(getOutputFilter(), &OutputFilter::unbanUsers, this, &Server::requestUnban); connect(getOutputFilter(), &OutputFilter::openRawLog, this, &Server::addRawLog); connect(getOutputFilter(), &OutputFilter::closeRawLog, this, &Server::closeRawLog); connect(getOutputFilter(), &OutputFilter::encodingChanged, this, &Server::updateEncoding); Application* konvApp = Application::instance(); connect(getOutputFilter(), SIGNAL(connectTo(Konversation::ConnectionFlag,QString,QString,QString,QString,QString,bool)), konvApp->getConnectionManager(), SLOT(connectTo(Konversation::ConnectionFlag,QString,QString,QString,QString,QString,bool))); connect(konvApp->getDccTransferManager(), &DCC::TransferManager::newDccTransferQueued, this, &Server::slotNewDccTransferItemQueued); // ViewContainer connect(this, &Server::showView, getViewContainer(), &ViewContainer::showView); connect(this, &Server::addDccPanel, getViewContainer(), &ViewContainer::addDccPanel); connect(this, SIGNAL(addDccChat(Konversation::DCC::Chat*)), getViewContainer(), SLOT(addDccChat(Konversation::DCC::Chat*)), Qt::QueuedConnection); connect(this, &Server::serverLag, getViewContainer(), &ViewContainer::updateStatusBarLagLabel); connect(this, &Server::tooLongLag, getViewContainer(), &ViewContainer::setStatusBarLagLabelTooLongLag); connect(this, &Server::resetLag, getViewContainer(), &ViewContainer::resetStatusBarLagLabel); connect(getOutputFilter(), &OutputFilter::showView, getViewContainer(), &ViewContainer::showView); connect(getOutputFilter(), &OutputFilter::openKonsolePanel, getViewContainer(), &ViewContainer::addKonsolePanel); connect(getOutputFilter(), &OutputFilter::openChannelList, this, &Server::requestOpenChannelListPanel); connect(getOutputFilter(), &OutputFilter::closeDccPanel, getViewContainer(), &ViewContainer::closeDccPanel); connect(getOutputFilter(), &OutputFilter::addDccPanel, getViewContainer(), &ViewContainer::addDccPanel); // Inputfilter - queued connections should be used for slots that have blocking UI connect(&m_inputFilter, SIGNAL(addDccChat(QString,QStringList)), this, SLOT(addDccChat(QString,QStringList)), Qt::QueuedConnection); connect(&m_inputFilter, &InputFilter::rejectDccChat, this, &Server::rejectDccChat); connect(&m_inputFilter, &InputFilter::startReverseDccChat, this, &Server::startReverseDccChat); connect(&m_inputFilter, &InputFilter::welcome, this, &Server::capCheckIgnored); connect(&m_inputFilter, &InputFilter::welcome, this, &Server::connectionEstablished); connect(&m_inputFilter, &InputFilter::notifyResponse, this, &Server::notifyResponse); connect(&m_inputFilter, &InputFilter::startReverseDccSendTransfer, this, &Server::startReverseDccSendTransfer); connect(&m_inputFilter, &InputFilter::addDccGet, this, &Server::addDccGet, Qt::QueuedConnection); connect(&m_inputFilter, &InputFilter::resumeDccGetTransfer, this, &Server::resumeDccGetTransfer); connect(&m_inputFilter, &InputFilter::resumeDccSendTransfer, this, &Server::resumeDccSendTransfer); connect(&m_inputFilter, &InputFilter::rejectDccSendTransfer, this, &Server::rejectDccSendTransfer); connect(&m_inputFilter, &InputFilter::userhost, this, &Server::userhost ); connect(&m_inputFilter, &InputFilter::topicAuthor, this, &Server::setTopicAuthor ); connect(&m_inputFilter, &InputFilter::endOfWho, this, &Server::endOfWho ); connect(&m_inputFilter, &InputFilter::endOfNames, this, &Server::endOfNames ); connect(&m_inputFilter, &InputFilter::invitation, this,&Server::invitation ); connect(&m_inputFilter, &InputFilter::addToChannelList, this, &Server::addToChannelList); // Status View connect(this, SIGNAL(serverOnline(bool)), getStatusView(), SLOT(serverOnline(bool))); // Scripts connect(getOutputFilter(), &OutputFilter::launchScript, konvApp->getScriptLauncher(), &ScriptLauncher::launchScript); connect(konvApp->getScriptLauncher(), &ScriptLauncher::scriptNotFound, this, &Server::scriptNotFound); connect(konvApp->getScriptLauncher(), &ScriptLauncher::scriptExecutionError, this, &Server::scriptExecutionError); connect(Preferences::self(), &Preferences::notifyListStarted, this, &Server::notifyListStarted, Qt::QueuedConnection); } 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(int exitCode, QProcess::ExitStatus exitStatus) { Q_UNUSED(exitCode); if (exitStatus == QProcess::NormalExit) getStatusView()->appendServerMessage(i18n("Info"), i18n("Pre-shell command executed successfully!")); else { QString errorText = i18nc("An error message from KDE or Qt is appended.", "There was a problem while executing the command: ") % m_preShellCommand.errorString(); getStatusView()->appendServerMessage(i18n("Warning"), errorText); } connectToIRCServer(); connectSignals(); } void Server::preShellCommandError(QProcess::ProcessError error) { Q_UNUSED(error); QString errorText = i18nc("An error message from KDE or Qt is appended.", "There was a problem while executing the command: ") % m_preShellCommand.errorString(); getStatusView()->appendServerMessage(i18n("Warning"), errorText); connectToIRCServer(); connectSignals(); } void Server::connectToIRCServer() { if (!isConnected() && !isConnecting()) { if (m_sslErrorLock) { qDebug() << "Refusing to connect while SSL lock from previous connection attempt is being held."; return; } // Reenable check when it works reliably for all backends // if(Solid::Networking::status() != Solid::Networking::Connected) // { // updateConnectionState(Konversation::SSInvoluntarilyDisconnected); // return; // } updateConnectionState(Konversation::SSConnecting); m_ownIpByUserhost.clear(); resetQueues(); // This is needed to support server groups with mixed SSL and nonSSL servers delete m_socket; m_socket = nullptr; if (m_referenceNicklist != getIdentity()->getNicknameList()) m_nickListModel->setStringList(getIdentity()->getNicknameList()); resetNickSelection(); m_socket = new QSslSocket(); m_socket->setObjectName(QStringLiteral("serverSocket")); connect(m_socket, QOverload::of(&QAbstractSocket::error), this, &Server::broken); connect(m_socket, &QIODevice::readyRead, this, &Server::incoming); connect(m_socket, &QAbstractSocket::disconnected, this, &Server::closed); connect(m_socket, &QAbstractSocket::hostFound, this, &Server::hostFound); getStatusView()->appendServerMessage(i18n("Info"),i18n("Looking for server %1 (port %2)...", getConnectionSettings().server().host(), QString::number(getConnectionSettings().server().port()))); if(getConnectionSettings().server().bypassProxy()) { m_socket->setProxy(QNetworkProxy::NoProxy); } // connect() will do a async lookup too if (getConnectionSettings().server().SSLEnabled() || getIdentity()->getAuthType() == QLatin1String("saslexternal") || getIdentity()->getAuthType() == QLatin1String("pemclientcert")) { connect(m_socket, &QSslSocket::encrypted, this, &Server::socketConnected); connect(m_socket, QOverload &>::of(&QSslSocket::sslErrors), this, &Server::sslError); if (getIdentity()->getAuthType() == QLatin1String("saslexternal") || getIdentity()->getAuthType() == QLatin1String("pemclientcert")) { m_socket->setLocalCertificate(getIdentity()->getPemClientCertFile().toLocalFile()); m_socket->setPrivateKey(getIdentity()->getPemClientCertFile().toLocalFile()); } m_socket->setProtocol(QSsl::SecureProtocols); // QIODevice::Unbuffered, see m_socket->connectToHost() call below m_socket->connectToHostEncrypted(getConnectionSettings().server().host(), getConnectionSettings().server().port(), (QIODevice::ReadWrite | QIODevice::Unbuffered)); } else { connect(m_socket, &QAbstractSocket::connected, this, &Server::socketConnected); // From KTcpSocket::connectToHost(): // There are enough layers of buffers between us and the network, and there is a quirk // in QIODevice that can make it try to readData() twice per read() call if buffered and // reaData() does not deliver enough data the first time. Like when the other side is // simply not sending any more data... // This can *apparently* lead to long delays sometimes which stalls applications. // Do not want. m_socket->connectToHost(getConnectionSettings().server().host(), getConnectionSettings().server().port(), (QIODevice::ReadWrite | QIODevice::Unbuffered)); } // set up the connection details setPrefixes(m_serverNickPrefixModes, m_serverNickPrefixes); // reset InputFilter (auto request info, /WHO request info) m_inputFilter.reset(); } else qDebug() << "connectToIRCServer() called while already connected: This should never happen. (" << (isConnecting() << 1) + isConnected() << ')'; } void Server::connectToIRCServerIn(uint delay) { m_delayedConnectTimer->setInterval(delay * 1000); m_delayedConnectTimer->start(); updateConnectionState(Konversation::SSScheduledToConnect); } void Server::showSSLDialog() { //TODO /* SSLSocket* sslsocket = dynamic_cast(m_socket); if (sslsocket) sslsocket->showInfoDialog(); */ } void Server::rebuildTargetPrefixMatcher() { m_targetMatcher.setPattern(QLatin1String("^([") + getServerNickPrefixes() + QLatin1String("]*)([") + getChannelTypes() + QLatin1String("])(.+)")); } // set available channel types according to 005 RPL_ISUPPORT void Server::setChannelTypes(const QString &pre) { m_channelPrefixes = pre; rebuildTargetPrefixMatcher(); if (getConnectionSettings().reconnectCount() == 0) { updateAutoJoin(m_connectionSettings.oneShotChannelList()); } else { updateAutoJoin(); } } QString Server::getChannelTypes() const { return m_channelPrefixes; } // set max number of channel modes with parameter according to 005 RPL_ISUPPORT void Server::setModesCount(int count) { m_modesCount = count; } int Server::getModesCount() { return m_modesCount; } // set user mode prefixes according to non-standard 005-Reply (see inputfilter.cpp) void Server::setPrefixes(const QString &modes, const QString& prefixes) { // NOTE: serverModes is QString::null, if server did not supply the // modes which relates to the network's nick-prefixes m_serverNickPrefixModes = modes; m_serverNickPrefixes = prefixes; rebuildTargetPrefixMatcher(); } QString Server::getServerNickPrefixes() const { return m_serverNickPrefixes; } void Server::setChanModes(const QString& modes) { QStringList abcd = modes.split(QLatin1Char(',')); m_banAddressListModes = abcd.value(0); } // return a nickname without possible mode character at the beginning void Server::mangleNicknameWithModes(QString& 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.indexOf(nickname[0])) != -1) { if(nickname.isEmpty()) return; nickname.remove(0, 1); // cut off the prefix bool recognisedMode = false; // determine, whether status is like op or like voice while(modeIndex < m_serverNickPrefixes.length() && !recognisedMode) { switch(m_serverNickPrefixes[modeIndex].toLatin1()) { case '*': // admin (EUIRC) { isAdmin = true; recognisedMode = true; break; } case '&': // admin (unrealircd) { isAdmin = true; recognisedMode = true; break; } case '!': // channel owner (RFC2811) { isOwner = true; recognisedMode = true; break; } case '~': // channel owner (unrealircd) { isOwner = true; recognisedMode = true; break; } case '@': // channel operator (RFC1459) { isOp = true; recognisedMode = true; break; } case '%': // halfop { isHalfop = true; recognisedMode = true; break; } case '+': // voiced (RFC1459) { hasVoice = true; recognisedMode = true; break; } default: { ++modeIndex; break; } } //switch to recognise the mode. } // loop through the modes to find one recognised } // loop through the name } void Server::hostFound() { getStatusView()->appendServerMessage(i18n("Info"),i18n("Server found, connecting...")); } void Server::socketConnected() { emit sslConnected(this); getConnectionSettings().setReconnectCount(0); requestAvailableCapabilies(); QStringList ql; if (getIdentity() && getIdentity()->getAuthType() == QLatin1String("serverpw") && !getIdentity()->getAuthPassword().isEmpty()) { ql << QStringLiteral("PASS ") + getIdentity()->getAuthPassword(); } else if (!getConnectionSettings().server().password().isEmpty()) ql << QStringLiteral("PASS ") + getConnectionSettings().server().password(); ql << QStringLiteral("NICK ") + getNickname(); ql << QStringLiteral("USER ") + getIdentity()->getIdent() + QStringLiteral(" 8 * :") /* 8 = +i; 4 = +w */ + getIdentity()->getRealName(); queueList(ql, HighPriority); connect(this, &Server::nicknameChanged, getStatusView(), &StatusPanel::setNickname); setNickname(getNickname()); } void Server::requestAvailableCapabilies () { m_capRequested = 0; m_capAnswered = 0; m_capEndDelayed = false; m_capabilities = NoCapabilies; getStatusView()->appendServerMessage(i18n("Info"),i18n("Negotiating capabilities with server...")); m_inputFilter.setAutomaticRequest(QStringLiteral("CAP LS"), QString(), true); queue(QStringLiteral("CAP LS 302"), HighPriority); } void Server::capInitiateNegotiation(const QString &availableCaps) { QStringList requestCaps; QStringList capsList = availableCaps.split (QChar(' '), QString::SkipEmptyParts); QStringList nameValue; foreach(const QString &cap, capsList) { nameValue = cap.split(QChar('=')); if(nameValue[0] == QLatin1String("sasl")) { QString authCommand; if (getIdentity()) { // A username is optional SASL EXTERNAL and a client cert substitutes // for the password. if (getIdentity()->getAuthType() == QLatin1String("saslexternal")) { authCommand = QStringLiteral("EXTERNAL"); // PLAIN on the other hand requires both. } else if (getIdentity()->getAuthType() == QLatin1String("saslplain") && !getIdentity()->getSaslAccount().isEmpty() && !getIdentity()->getAuthPassword().isEmpty()) { authCommand = QStringLiteral("PLAIN"); } } if(!authCommand.isEmpty()) { QSet supportedSaslTypes; if(nameValue.count() > 1) supportedSaslTypes = QSet::fromList(nameValue[1].split(QChar(','))); if(!supportedSaslTypes.isEmpty() && !supportedSaslTypes.contains(authCommand)) getStatusView()->appendServerMessage(i18n("Error"), i18n("Server does not support %1 as SASL authentication mechanism, skipping SASL authentication.", authCommand)); else requestCaps.append (QStringLiteral("sasl")); } } else if(m_capabilityNames.contains(nameValue[0])) { requestCaps.append (nameValue[0]); } + + // HACK: twitch.tv's IRC server doesn't handle WHO so + // let's disable all WHO requests for servers that has + // twitch.tv capabilities + if(nameValue[0].startsWith("twitch.tv")) + { + m_whoRequestsDisabled = true; + } } if(!requestCaps.isEmpty()) { QString capsString = requestCaps.join(QLatin1Char(' ')); getStatusView()->appendServerMessage(i18n("Info"),i18n("Requesting capabilities: %1", capsString)); queue(QStringLiteral("CAP REQ :") + capsString, HighPriority); m_capRequested++; } } void Server::capReply() { m_capAnswered++; } void Server::capEndNegotiation() { if(m_capRequested == m_capAnswered) { getStatusView()->appendServerMessage(i18n("Info"),i18n("Closing capabilities negotiation.")); queue(QStringLiteral("CAP END"), HighPriority); } } void Server::capCheckIgnored() { if (m_capRequested && !m_capAnswered) getStatusView()->appendServerMessage(i18n("Error"), i18n("Capabilities negotiation failed: Appears not supported by server.")); } void Server::capAcknowledged(const QString& name, Server::CapModifiers modifiers) { if (name == QLatin1String("sasl") && modifiers == Server::NoModifiers) { const QString &authCommand = (getIdentity()->getAuthType() == QLatin1String("saslexternal")) ? QStringLiteral("EXTERNAL") : QStringLiteral("PLAIN"); getStatusView()->appendServerMessage(i18n("Info"), i18n("SASL capability acknowledged by server, attempting SASL %1 authentication...", authCommand)); sendAuthenticate(authCommand); m_capEndDelayed = true; } m_capabilities |= m_capabilityNames.value(name); } void Server::capDenied(const QString& name) { if (name == QLatin1String("sasl")) getStatusView()->appendServerMessage(i18n("Error"), i18n("SASL capability denied or not supported by server.")); } void Server::registerWithServices() { if (!getIdentity()) return; NickInfoPtr nickInfo = getNickInfo(getNickname()); if (nickInfo && nickInfo->isIdentified()) return; if (getIdentity()->getAuthType() == QLatin1String("nickserv")) { if (!getIdentity()->getNickservNickname().isEmpty() && !getIdentity()->getNickservCommand().isEmpty() && !getIdentity()->getAuthPassword().isEmpty()) { queue(QStringLiteral("PRIVMSG ")+getIdentity()->getNickservNickname()+QStringLiteral(" :")+getIdentity()->getNickservCommand()+QLatin1Char(' ')+getIdentity()->getAuthPassword(), HighPriority); } } else if (getIdentity()->getAuthType() == QLatin1String("saslplain")) { QString authString = getIdentity()->getSaslAccount(); authString.append(QChar(QChar::Null)); authString.append(getIdentity()->getSaslAccount()); authString.append(QChar(QChar::Null)); authString.append(getIdentity()->getAuthPassword()); sendAuthenticate(QLatin1String(authString.toLatin1().toBase64())); } else if (getIdentity()->getAuthType() == QLatin1String("saslexternal")) { QString authString = getIdentity()->getSaslAccount(); // An account name is optional with SASL EXTERNAL. if (!authString.isEmpty()) { authString.append(QChar(QChar::Null)); authString.append(getIdentity()->getSaslAccount()); } sendAuthenticate(QLatin1String(authString.toLatin1().toBase64())); } } void Server::sendAuthenticate(const QString& message) { m_lastAuthenticateCommand = message; if (message.isEmpty()) { queue(QStringLiteral("AUTHENTICATE +"), HighPriority); } else { queue(QStringLiteral("AUTHENTICATE ") + message, HighPriority); } } void Server::broken(QAbstractSocket::SocketError error) { Q_UNUSED(error); qDebug() << "Connection broken with state" << m_connectionState << "and error:" << m_socket->errorString(); m_socket->blockSignals(true); resetQueues(); m_notifyTimer.stop(); m_pingSendTimer.stop(); m_pingResponseTimer.stop(); m_inputFilter.setLagMeasuring(false); m_currentLag = -1; purgeData(); emit resetLag(this); emit nicksNowOnline(this, QStringList(), true); m_prevISONList.clear(); updateAutoJoin(); if (m_sslErrorLock) { // We got disconnected while dealing with an SSL error, e.g. due to the // user taking their time on dealing with the error dialog. Since auto- // reconnecting makes no sense in this situation, let's pass it off as a // deliberate disconnect. sslError() will either kick off a reconnect, or // reaffirm this. getStatusView()->appendServerMessage(i18n("SSL Connection Error"), i18n("Connection to server %1 (port %2) lost while waiting for user response to an SSL error. " "Will automatically reconnect if error is ignored.", getConnectionSettings().server().host(), QString::number(getConnectionSettings().server().port()))); updateConnectionState(SSDeliberatelyDisconnected); } else if (getConnectionState() == Konversation::SSDeliberatelyDisconnected) { if (m_reconnectImmediately) { m_reconnectImmediately = false; QMetaObject::invokeMethod(this, "connectToIRCServer", Qt::QueuedConnection); } } else { Application::instance()->notificationHandler()->connectionFailure(getStatusView(), getServerName()); QString error = i18n("Connection to server %1 (port %2) lost: %3.", getConnectionSettings().server().host(), QString::number(getConnectionSettings().server().port()), m_socket->errorString()); getStatusView()->appendServerMessage(i18n("Error"), error); updateConnectionState(Konversation::SSInvoluntarilyDisconnected); } // HACK Only show one nick change dialog at connection time. // This hack is a bit nasty as it assumes that the only QInputDialog // child of the statusview will be the nick change dialog. if (getStatusView()) { QInputDialog* nickChangeDialog = getStatusView()->findChild(); if (nickChangeDialog) nickChangeDialog->reject(); } } void Server::sslError(const QList &errors) { // We have to explicitly grab the socket we got the error from, // lest we might end up calling ignoreSslErrors() on a different // socket later if m_socket has started pointing at something // else. QPointer socket = qobject_cast(QObject::sender()); m_sslErrorLock = true; KSslErrorUiData uiData(socket); bool ignoreSslErrors = KIO::SslUi::askIgnoreSslErrors(uiData, KIO::SslUi::RecallAndStoreRules); m_sslErrorLock = false; // The dialog-based user interaction above may take an undefined amount // of time, and anything could happen to the socket in that span of time. // If it was destroyed, let's not do anything and bail out. if (!socket) { qDebug() << "Socket was destroyed while waiting for user interaction."; return; } // Ask the user if he wants to ignore the errors. if (ignoreSslErrors) { // Show a warning in the chat window that the SSL certificate failed the authenticity check. QString error = i18n("The SSL certificate for the server %1 (port %2) failed the authenticity check.", getConnectionSettings().server().host(), QString::number(getConnectionSettings().server().port())); getStatusView()->appendServerMessage(i18n("SSL Connection Warning"), error); // We may have gotten disconnected while waiting for user response and have // to reconnect. if (isConnecting()) { // The user has chosen to ignore SSL errors. socket->ignoreSslErrors(); } else { // QueuedConnection is vital here, otherwise we're deleting the socket // in a slot connected to one of its signals (connectToIRCServer deletes // any old socket) and crash. QMetaObject::invokeMethod(this, "connectToIRCServer", Qt::QueuedConnection); } } else { // Don't auto-reconnect if the user chose to ignore the SSL errors -- // treat it as a deliberate disconnect. updateConnectionState(Konversation::SSDeliberatelyDisconnected); QString errorReason; for (int i = 0; i < errors.size(); ++i) { errorReason += errors.at(i).errorString() + QLatin1Char(' '); } QString error = i18n("Could not connect to %1 (port %2) using SSL encryption. Either the server does not support SSL (did you use the correct port?) or you rejected the certificate. %3", getConnectionSettings().server().host(), QString::number(getConnectionSettings().server().port()), errorReason); getStatusView()->appendServerMessage(i18n("SSL Connection Error"), error); emit sslInitFailure(); } } // Will be called from InputFilter as soon as the Welcome message was received void Server::connectionEstablished(const QString& 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()) QHostInfo::lookupHost(ownHost, this, SLOT(gotOwnResolvedHostByWelcome(QHostInfo))); updateConnectionState(Konversation::SSConnected); // Make a helper object to build ISON (notify) list. // TODO: Give the object a kick to get it started? delete m_serverISON; m_serverISON = new ServerISON(this); // get first notify very early startNotifyTimer(1000); // Register with services if (getIdentity() && getIdentity()->getAuthType() == QLatin1String("nickserv")) registerWithServices(); // get own ip by userhost requestUserhost(getNickname()); // Start the PINGPONG match m_pingSendTimer.start(1000 /*1 sec*/); // 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); } } //FIXME operator[] inserts an empty T& so each destination might just as well have its own key storage QByteArray Server::getKeyForRecipient(const QString& recipient) const { return m_keyHash[recipient.toLower()]; } void Server::setKeyForRecipient(const QString& recipient, const QByteArray& key) { m_keyHash[recipient.toLower()] = key; } void Server::gotOwnResolvedHostByWelcome(const QHostInfo& res) { if (res.error() == QHostInfo::NoError && !res.addresses().isEmpty()) m_ownIpByWelcome = res.addresses().first().toString(); else qDebug() << "Got error: " << res.errorString(); } bool Server::isSocketConnected() const { if (!m_socket) return false; return (m_socket->state() == QAbstractSocket::ConnectedState); } 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::reconnectServer(const QString& quitMessage) { if (isConnecting() || isSocketConnected()) { m_reconnectImmediately = true; quitServer(quitMessage); } else QMetaObject::invokeMethod(this, "connectToIRCServer", Qt::QueuedConnection); } void Server::disconnectServer(const QString& quitMessage) { getConnectionSettings().setReconnectCount(0); if (isScheduledToConnect()) { m_delayedConnectTimer->stop(); getStatusView()->appendServerMessage(i18n("Info"), i18n("Delayed connect aborted.")); } if (isSocketConnected()) quitServer(quitMessage); } void Server::quitServer(const QString& quitMessage) { // 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); if (!m_socket) return; QString toServer = QStringLiteral("QUIT :"); if (quitMessage.isEmpty()) toServer += getIdentity()->getQuitReason(); else toServer += quitMessage; queue(toServer, HighPriority); flushQueues(); m_socket->flush(); // Close the socket to allow a dead connection to be reconnected before the socket timeout. m_socket->close(); getStatusView()->appendServerMessage(i18n("Info"), i18n("Disconnected from %1 (port %2).", getConnectionSettings().server().host(), QString::number(getConnectionSettings().server().port()))); } void Server::notifyAction(const QString& nick) { QString out(Preferences::self()->notifyDoubleClickAction()); OutputFilter::replaceAliases(out); // parse wildcards (toParse,nickname,channelName,nickList,parameter) out = parseWildcards(out, getNickname(), QString(), QString(), nick, QString()); // Send all strings, one after another QStringList outList = out.split(QLatin1Char('\n'), QString::SkipEmptyParts); for (int index=0; indexparse(getNickname(),outList[index],QString()); queue(result.toServer); } // endfor } void Server::notifyResponse(const QString& nicksOnline) { bool nicksOnlineChanged = false; QStringList actualList = nicksOnline.split(QLatin1Char(' '), QString::SkipEmptyParts); QString lcActual = QLatin1Char(' ') + nicksOnline + QLatin1Char(' '); QString lcPrevISON = QLatin1Char(' ') + (m_prevISONList.join(QLatin1Char(' '))) + QLatin1Char(' '); QStringList::iterator it; //Are any nicks gone offline for (it = m_prevISONList.begin(); it != m_prevISONList.end(); ++it) { if (!lcActual.contains(QLatin1Char(' ') + (*it) + QLatin1Char(' '), Qt::CaseInsensitive)) { setNickOffline(*it); nicksOnlineChanged = true; } } //Are any nicks gone online for (it = actualList.begin(); it != actualList.end(); ++it) { if (!lcPrevISON.contains(QLatin1Char(' ') + (*it) + QLatin1Char(' '), Qt::CaseInsensitive)) { 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::notifyListStarted(int serverGroupId) { if (getServerGroup()) if (getServerGroup()->id() == serverGroupId) startNotifyTimer(1000); } void Server::startNotifyTimer(int msec) { // make sure the timer gets started properly in case we have reconnected m_notifyTimer.stop(); if (Preferences::self()->useNotify()) { if (msec == 0) msec = Preferences::self()->notifyDelay() * 1000; m_notifyTimer.start(msec); } } void Server::notifyTimeout() { // Notify delay time is over, send ISON request if desired if (Preferences::self()->useNotify()) { // But only if there actually are nicks in the notify list QString list = getISONListString(); if (!list.isEmpty()) queue(QStringLiteral("ISON ") + list, LowPriority); } } void Server::autoCommandsAndChannels() { if (getServerGroup() && !getServerGroup()->connectCommands().isEmpty()) { QString connectCommands = getServerGroup()->connectCommands(); if (!getNickname().isEmpty()) connectCommands.replace(QStringLiteral("%nick"), getNickname()); QStringList connectCommandsList = connectCommands.split(QLatin1Char(';'), QString::SkipEmptyParts); QStringList::iterator iter; for (iter = connectCommandsList.begin(); iter != connectCommandsList.end(); ++iter) { QString output(*iter); output = output.simplified(); OutputFilter::replaceAliases(output); Konversation::OutputFilterResult result = getOutputFilter()->parse(getNickname(),output,QString()); queue(result.toServer); } } if (getAutoJoin()) { for ( QStringList::Iterator it = m_autoJoinCommands.begin(); it != m_autoJoinCommands.end(); ++it ) queue((*it)); } if (!m_connectionSettings.oneShotChannelList().isEmpty()) { QStringList oneShotJoin = generateJoinCommand(m_connectionSettings.oneShotChannelList()); for ( QStringList::Iterator it = oneShotJoin.begin(); it != oneShotJoin.end(); ++it ) { queue((*it)); } m_connectionSettings.clearOneShotChannelList(); } } /** 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(); for (int i = 0; i < m_referenceNicklist.length(); ++i) { // Pointless to include the nick we're already going to use. if (m_referenceNicklist.at(i) != getNickname()) { m_nickIndices.append(i); } } } QString Server::getNextNickname() { //if the identity changed underneath us (likely impossible), start over if (m_referenceNicklist != getIdentity()->getNicknameList()) resetNickSelection(); QString newNick; if (!m_nickIndices.isEmpty()) { newNick = getIdentity()->getNickname(m_nickIndices.takeFirst()); } else { QString inputText = i18n("No nicknames from the \"%1\" identity were accepted by the connection \"%2\".\nPlease enter a new one or press Cancel to disconnect:", getIdentity()->getName(), getDisplayName()); newNick = QInputDialog::getText(getStatusView(), i18n("Nickname error"), inputText); } return newNick; } void Server::processIncomingData() { m_incomingTimer.stop(); if (!m_inputBuffer.isEmpty() && !m_processingIncoming) { m_processingIncoming = true; QString front(m_inputBuffer.front()); m_inputBuffer.pop_front(); 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); //if (len <= 0 && getConnectionSettings().server().SSLEnabled()) // return; // split buffer to lines QList bufferLines; while (m_socket->canReadLine()) { QByteArray line(m_socket->readLine()); //remove \n blowfish doesn't like it int i = line.size()-1; while (i >= 0 && (line[i]=='\n' || line[i]=='\r')) // since euIRC gets away with sending just \r, bet someone sends \n\r? { i--; } line.truncate(i+1); if (line.size() > 0) bufferLines.append(line); } while(!bufferLines.isEmpty()) { // Pre parsing is needed in case encryption/decryption is needed // BEGIN set channel encoding if specified QString senderNick; bool isServerMessage = false; QString channelKey; QTextCodec* codec = getIdentity()->getCodec(); QByteArray first = bufferLines.first(); bufferLines.removeFirst(); QStringList lineSplit = codec->toUnicode(first).split(QLatin1Char(' '), QString::SkipEmptyParts); if (lineSplit.count() >= 1) { if (lineSplit[0][0] == QLatin1Char(':')) // does this message have a prefix? { if(!lineSplit[0].contains(QLatin1Char('!'))) // is this a server(global) message? isServerMessage = true; else senderNick = lineSplit[0].mid(1, lineSplit[0].indexOf(QLatin1Char('!'))-1); lineSplit.removeFirst(); // remove prefix } } if (lineSplit.isEmpty()) continue; // BEGIN pre-parse to know where the message belongs to QString command = lineSplit[0].toLower(); if( isServerMessage ) { if( lineSplit.count() >= 3 ) { if( command == QLatin1String("332") ) // RPL_TOPIC channelKey = lineSplit[2]; if( command == QLatin1String("372") ) // RPL_MOTD channelKey = QStringLiteral(":server"); } } else // NOT a global message { if( lineSplit.count() >= 2 ) { // query if( ( command == QLatin1String("privmsg") || command == QLatin1String("notice") ) && lineSplit[1] == getNickname() ) { channelKey = senderNick; } // channel message else if( command == QLatin1String("privmsg") || command == QLatin1String("notice") || command == QLatin1String("join") || command == QLatin1String("kick") || command == QLatin1String("part") || command == QLatin1String("topic") ) { channelKey = lineSplit[1]; } } } // END pre-parse to know where the message belongs to // Decrypt if necessary //send to raw log before decryption if (m_rawLog) m_rawLog->appendRaw(RawLog::Inbound, first); #ifdef HAVE_QCA2 QByteArray cKey = getKeyForRecipient(channelKey); if(!cKey.isEmpty()) { if(command == QLatin1String("privmsg")) { //only send encrypted text to decrypter int index = first.indexOf(":",first.indexOf(":")+1); if(this->identifyMsgEnabled()) // Workaround braindead Freenode prefixing messages with + ++index; QByteArray backup = first.mid(0,index+1); if(getChannelByName(channelKey) && getChannelByName(channelKey)->getCipher()->setKey(cKey)) first = getChannelByName(channelKey)->getCipher()->decrypt(first.mid(index+1)); else if(getQueryByName(channelKey) && getQueryByName(channelKey)->getCipher()->setKey(cKey)) first = getQueryByName(channelKey)->getCipher()->decrypt(first.mid(index+1)); first.prepend(backup); } else if(command == QLatin1String("332") || command == QLatin1String("topic")) { //only send encrypted text to decrypter int index = first.indexOf(":",first.indexOf(":")+1); QByteArray backup = first.mid(0,index+1); if(getChannelByName(channelKey) && getChannelByName(channelKey)->getCipher()->setKey(cKey)) first = getChannelByName(channelKey)->getCipher()->decryptTopic(first.mid(index+1)); else if(getQueryByName(channelKey) && getQueryByName(channelKey)->getCipher()->setKey(cKey)) first = getQueryByName(channelKey)->getCipher()->decryptTopic(first.mid(index+1)); first.prepend(backup); } } #endif bool isUtf8 = Konversation::isUtf8(first); QString encoded; if (isUtf8) encoded = QString::fromUtf8(first); else { // check setting QString channelEncoding; if( !channelKey.isEmpty() ) { if(getServerGroup()) channelEncoding = Preferences::channelEncoding(getServerGroup()->id(), channelKey); else 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 (codec->mibEnum() == 106) codec = QTextCodec::codecForMib( 4 /* iso-8859-1 */ ); encoded = codec->toUnicode(first); } // Qt uses 0xFDD0 and 0xFDD1 to mark the beginning and end of text frames. Remove // these here to avoid fatal errors encountered in QText* and the event loop pro- // cessing. sterilizeUnicode(encoded); if (!encoded.isEmpty()) m_inputBuffer << encoded; //FIXME: This has nothing to do with bytes, and it's not raw received bytes either. Bogus number. //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 QString& command, const QString& dest) { NickInfoPtr 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 QStringList outcmds = QString(QStringLiteral("WHO QUIT PRIVMSG NOTICE KICK PART TOPIC")).split(QChar(QLatin1Char(' '))); int Server::_send_internal(QString outputLine) { QStringList outputLineSplit = outputLine.split(QLatin1Char(' '), QString::SkipEmptyParts); int outboundCommand = -1; if (!outputLineSplit.isEmpty()) { //Lets cache the uppercase command so we don't miss or reiterate too much outboundCommand = outcmds.indexOf(outputLineSplit[0].toUpper()); } if (outputLine.at(outputLine.length()-1) == QLatin1Char('\n')) { qDebug() << "found \\n on " << outboundCommand; outputLine.resize(outputLine.length()-1); } // remember the first arg of /WHO to identify responses if (outboundCommand == 0 && outputLineSplit.count() >= 2) //"WHO" m_inputFilter.addWhoRequest(outputLineSplit[1]); else if (outboundCommand == 1) //"QUIT" updateConnectionState(Konversation::SSDeliberatelyDisconnected); // set channel encoding if specified QString channelCodecName; //[ PRIVMSG | NOTICE | KICK | PART | TOPIC ] target :message if (outputLineSplit.count() > 2 && outboundCommand > 1) { if(getServerGroup()) // if we're connecting via a servergroup channelCodecName=Preferences::channelEncoding(getServerGroup()->id(), outputLineSplit[1]); else //if we're connecting to a server manually channelCodecName=Preferences::channelEncoding(getDisplayName(), outputLineSplit[1]); } QTextCodec* codec = nullptr; 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 Qt 3. // ex.: JIS7, eucJP, SJIS //int outlen=-1; //leaving this done twice for now, I'm uncertain of the implications of not encoding other commands QByteArray encoded = outputLine.toUtf8(); if(codec) encoded = codec->fromUnicode(outputLine); #ifdef HAVE_QCA2 QString cipherKey; if (outputLineSplit.count() > 2 && outboundCommand > 1) cipherKey = getKeyForRecipient(outputLineSplit.at(1)); if (!cipherKey.isEmpty()) { int colon = outputLine.indexOf(':'); if (colon > -1) { colon++; QString pay(outputLine.mid(colon)); //only encode the actual user text, IRCD *should* desire only ASCII 31 < x < 127 for protocol elements QByteArray payload = pay.toUtf8(); QByteArray dest; if (codec) { payload=codec->fromUnicode(pay); //apparently channel name isn't a protocol element... dest = codec->fromUnicode(outputLineSplit.at(1)); } else { dest = outputLineSplit.at(1).toLatin1(); } 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.at(2).startsWith(QLatin1String(":\x01")) && outputLineSplit.at(2) != ":\x01""ACTION") doit = false; } if (doit) { QString target = outputLineSplit.at(1); if(getChannelByName(target) && getChannelByName(target)->getCipher()->setKey(cipherKey.toLocal8Bit())) getChannelByName(target)->getCipher()->encrypt(payload); else if(getQueryByName(target) && getQueryByName(target)->getCipher()->setKey(cipherKey.toLocal8Bit())) getQueryByName(target)->getCipher()->encrypt(payload); encoded = outputLineSplit.at(0).toLatin1(); qDebug() << payload << "\n" << payload.data(); //two lines because the compiler insists on using the wrong operator+ encoded += ' ' + dest + " :" + payload; } } } } #endif if (m_rawLog) m_rawLog->appendRaw(RawLog::Outbound, encoded); encoded += '\n'; qint64 sout = m_socket->write(encoded); return sout; } void Server::toServer(QString&s, IRCQueue* q) { int sizesent = _send_internal(s); emit sentStat(s.length(), sizesent, q); //tell the queues what we sent collectStats(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 <= Application::instance()->countOfQueues()) return true; return false; } bool Server::queue(const QString& line, QueuePriority priority) { if (!line.isEmpty() && validQueue(priority)) { IRCQueue& out=*m_queues[priority]; out.enqueue(line); return true; } return false; } bool Server::queueList(const QStringList& buffer, QueuePriority priority) { if (buffer.isEmpty() || !validQueue(priority)) return false; IRCQueue& out=*(m_queues[priority]); for(int i=0;icountOfQueues(); i++) m_queues[i]->reset(); } //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 <= Application::instance()->countOfQueues(); 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::dbusRaw(const QString& command) { if(command.startsWith(Preferences::self()->commandChar())) { queue(command.section(Preferences::self()->commandChar(), 1)); } else queue(command); } void Server::dbusSay(const QString& target,const QString& command) { if(isAChannel(target)) { Channel* channel=getChannelByName(target); if(channel) channel->sendText(command); } else { Query* query = getQueryByName(target); if(query==nullptr) { NickInfoPtr nickinfo = obtainNickInfo(target); query=addQuery(nickinfo, true); } if(query) { if(!command.isEmpty()) query->sendText(command); else { query->adjustFocus(); getViewContainer()->getWindow()->show(); KWindowSystem::demandAttention(getViewContainer()->getWindow()->winId()); KWindowSystem::activateWindow(getViewContainer()->getWindow()->winId()); } } } } void Server::dbusInfo(const QString& string) { appendMessageToFrontmost(i18n("D-Bus"),string); } void Server::ctcpReply(const QString &receiver,const QString &text) { queue(QStringLiteral("NOTICE ")+receiver+QStringLiteral(" :")+QLatin1Char('\x01')+text+QLatin1Char('\x01')); } // Given a nickname, returns NickInfo object. 0 if not found. NickInfoPtr Server::getNickInfo(const QString& nickname) { QString lcNickname(nickname.toLower()); if (m_allNicks.contains(lcNickname)) { NickInfoPtr nickinfo = m_allNicks[lcNickname]; Q_ASSERT(nickinfo); return nickinfo; } else return NickInfoPtr(); //! TODO FIXME null null null } // 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 QString& nickname) { NickInfoPtr nickInfo = getNickInfo(nickname); if (!nickInfo) { nickInfo = new NickInfo(nickname, this); m_allNicks.insert(QString(nickname.toLower()), 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 QString& channelName) const { QString lcChannelName = channelName.toLower(); if (m_joinedChannels.contains(lcChannelName)) return m_joinedChannels[lcChannelName]; else return nullptr; } // 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 QString& channelName) const { QString lcChannelName = channelName.toLower(); if (m_unjoinedChannels.contains(lcChannelName)) return m_unjoinedChannels[lcChannelName]; else return nullptr; } // 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 QString& 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 QString& channelName, const QString& nickname) { QString lcNickname = nickname.toLower(); const ChannelNickMap *channelNickMap = getChannelMembers(channelName); if (channelNickMap) { if (channelNickMap->contains(lcNickname)) return (*channelNickMap)[lcNickname]; else return ChannelNickPtr(); //! TODO FIXME null null null } else { return ChannelNickPtr(); //! TODO FIXME null null null } } // 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 QString& channelName, const QString& nickname, unsigned int mode) { QString lcNickname = nickname.toLower(); // If already on a list, update mode. ChannelNickPtr channelNick = getChannelNick(channelName, lcNickname); if (!channelNick) { // If the nick is on the watch list, add channel and nick to unjoinedChannels list. if (getWatchList().contains(lcNickname, Qt::CaseInsensitive)) { channelNick = addNickToUnjoinedChannelsList(channelName, nickname); channelNick->setMode(mode); } else return ChannelNickPtr(); //! TODO FIXME null null null } if (mode != 99) channelNick->setMode(mode); return channelNick; } // Returns a list of all the joined channels that a nick is in. QStringList Server::getNickJoinedChannels(const QString& nickname) { QString lcNickname = nickname.toLower(); QStringList channellist; ChannelMembershipMap::ConstIterator channel; for( channel = m_joinedChannels.constBegin(); channel != m_joinedChannels.constEnd(); ++channel ) { if (channel.value()->contains(lcNickname)) channellist.append(channel.key()); } return channellist; } // Returns a list of all the channels (joined or unjoined) that a nick is in. QStringList Server::getNickChannels(const QString& nickname) { QString lcNickname = nickname.toLower(); QStringList channellist; ChannelMembershipMap::ConstIterator channel; for( channel = m_joinedChannels.constBegin(); channel != m_joinedChannels.constEnd(); ++channel ) { if (channel.value()->contains(lcNickname)) channellist.append(channel.key()); } for( channel = m_unjoinedChannels.constBegin(); channel != m_unjoinedChannels.constEnd(); ++channel ) { if (channel.value()->contains(lcNickname)) channellist.append(channel.key()); } return channellist; } QStringList Server::getSharedChannels(const QString& nickname) { QString lcNickname = nickname.toLower(); QStringList channellist; ChannelMembershipMap::ConstIterator channel; for( channel = m_joinedChannels.constBegin(); channel != m_joinedChannels.constEnd(); ++channel ) { if (channel.value()->contains(lcNickname)) channellist.append(channel.key()); } return channellist; } bool Server::isNickOnline(const QString &nickname) { NickInfoPtr nickInfo = getNickInfo(nickname); return (nickInfo != nullptr); } QString Server::getOwnIpByNetworkInterface() { return m_socket->localAddress().toString(); } QString Server::getOwnIpByServerMessage() { if(!m_ownIpByWelcome.isEmpty()) return m_ownIpByWelcome; else if(!m_ownIpByUserhost.isEmpty()) return m_ownIpByUserhost; else return QString(); } Query* Server::addQuery(const NickInfoPtr & nickInfo, bool weinitiated) { QString nickname = nickInfo->getNickname(); // Only create new query object if there isn't already one with the same name Query* query = getQueryByName(nickname); if (!query) { QString lcNickname = nickname.toLower(); query = getViewContainer()->addQuery(this, nickInfo, weinitiated); query->indicateAway(m_away); connect(query, SIGNAL(sendFile(QString)),this, SLOT(requestDccSend(QString))); connect(this, SIGNAL(serverOnline(bool)), query, SLOT(serverOnline(bool))); // Append query to internal list m_queryList.append(query); m_queryNicks.insert(lcNickname, nickInfo); if (!weinitiated) Application::instance()->notificationHandler()->query(query, nickname); } else if (weinitiated) { emit showView(query); } // 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 QString &name) { 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. QString lcNickname = name.toLower(); m_queryNicks.remove(lcNickname); if (!isWatchedNick(name)) deleteNickIfUnlisted(name); } void Server::closeChannel(const QString& name) { qDebug() << "Server::closeChannel(" << name << ")"; Channel* channelToClose = getChannelByName(name); if (channelToClose && channelToClose->joined()) { Konversation::OutputFilterResult result = getOutputFilter()->parse(getNickname(), Preferences::self()->commandChar() + QStringLiteral("PART"), name); queue(result.toServer); } } void Server::requestChannelList() { m_inputFilter.setAutomaticRequest(QStringLiteral("LIST"), QString(), true); queue(QStringLiteral("LIST")); } void Server::requestWhois(const QString& nickname) { m_inputFilter.setAutomaticRequest(QStringLiteral("WHOIS"), nickname, true); queue(QStringLiteral("WHOIS ")+nickname, LowPriority); } void Server::requestWho(const QString& channel) { + if(m_whoRequestsDisabled) + return; + m_inputFilter.setAutomaticRequest(QStringLiteral("WHO"), channel, true); QString command(QStringLiteral("WHO ") + channel); if (capabilities() & WHOX && capabilities() & ExtendedJoin) { // Request the account as well as the usual info. // See http://faerion.sourceforge.net/doc/irc/whox.var // for more info. command += QStringLiteral(" nuhsa%cuhsnfdra"); } queue(command, LowPriority); } void Server::requestUserhost(const QString& nicks) { + if(m_whoRequestsDisabled) + return; + const QStringList nicksList = nicks.split(QLatin1Char(' '), QString::SkipEmptyParts); for(QStringList::ConstIterator it=nicksList.constBegin() ; it!=nicksList.constEnd() ; ++it) m_inputFilter.setAutomaticRequest(QStringLiteral("USERHOST"), *it, true); queue(QStringLiteral("USERHOST ")+nicks, LowPriority); } void Server::requestTopic(const QString& channel) { m_inputFilter.setAutomaticRequest(QStringLiteral("TOPIC"), channel, true); queue(QStringLiteral("TOPIC ")+channel, LowPriority); } void Server::resolveUserhost(const QString& nickname) { m_inputFilter.setAutomaticRequest(QStringLiteral("WHOIS"), nickname, true); m_inputFilter.setAutomaticRequest(QStringLiteral("DNS"), nickname, true); queue(QStringLiteral("WHOIS ")+nickname, LowPriority); //FIXME when is this really used? } void Server::requestBan(const QStringList& users,const QString& channel,const QString& a_option) { QString option=a_option.toLower(); Channel* targetChannel=getChannelByName(channel); for(int index=0;indexgetNickByName(mask); // if we found the nick try to find their hostmask if(targetNick) { QString hostmask=targetNick->getChannelNick()->getHostmask(); // if we found the hostmask, add it to the ban mask if(!hostmask.isEmpty()) { mask=targetNick->getChannelNick()->getNickname()+QLatin1Char('!')+hostmask; // adapt ban mask to the option given if(option==QStringLiteral("host")) mask=QLatin1String("*!*@*.")+hostmask.section(QLatin1Char('.'),1); else if(option==QStringLiteral("domain")) mask=QLatin1String("*!*@")+hostmask.section(QLatin1Char('@'),1); else if(option==QStringLiteral("userhost")) mask=QLatin1String("*!")+hostmask.section(QLatin1Char('@'),0,0)+QLatin1String("@*.")+hostmask.section(QLatin1Char('.'),1); else if(option==QStringLiteral("userdomain")) mask=QLatin1String("*!")+hostmask.section(QLatin1Char('@'),0,0)+QLatin1Char('@')+hostmask.section(QLatin1Char('@'),1); } } } Konversation::OutputFilterResult result = getOutputFilter()->execBan(mask,channel); queue(result.toServer); } } void Server::requestUnban(const QString& mask,const QString& channel) { Konversation::OutputFilterResult result = getOutputFilter()->execUnban(mask,channel); queue(result.toServer); } void Server::requestDccSend() { requestDccSend(QString()); } void Server::sendURIs(const QList& uris, const QString& nick) { foreach(const QUrl &uri, uris) addDccSend(nick, uri); } void Server::requestDccSend(const QString &a_recipient) { QString recipient(a_recipient); // if we don't have a recipient yet, let the user select one if (recipient.isEmpty()) { recipient = recipientNick(); } // do we have a recipient *now*? if(!recipient.isEmpty()) { QPointer dlg = new DccFileDialog (getViewContainer()->getWindow()); //DccFileDialog fileDialog(KUrl(), QString(), getViewContainer()->getWindow()); QList fileURLs = dlg->getOpenUrls( QUrl(), QString(), i18n("Select File(s) to Send to %1", recipient) ); QList::const_iterator it; for ( it = fileURLs.constBegin() ; it != fileURLs.constEnd() ; ++it ) { addDccSend( recipient, *it, dlg->passiveSend()); } delete dlg; } } void Server::slotNewDccTransferItemQueued(DCC::Transfer* transfer) { if (transfer->getConnectionId() == connectionId() ) { qDebug() << "connecting slots for " << transfer->getFileName() << " [" << transfer->getType() << "]"; if ( transfer->getType() == DCC::Transfer::Receive ) { connect( transfer, &DCC::Transfer::done, this, &Server::dccGetDone ); connect( transfer, &DCC::Transfer::statusChanged, this, &Server::dccStatusChanged ); } else { connect( transfer, &DCC::Transfer::done, this, &Server::dccSendDone ); connect( transfer, &DCC::Transfer::statusChanged, this, &Server::dccStatusChanged ); } } } void Server::addDccSend(const QString &recipient, const QUrl &fileURL, bool passive, const QString &altFileName, quint64 fileSize) { if (!fileURL.isValid()) { return; } // We already checked that the file exists in output filter / requestDccSend() resp. DCC::TransferSend* newDcc = Application::instance()->getDccTransferManager()->newUpload(); newDcc->setConnectionId(connectionId()); newDcc->setPartnerNick(recipient); newDcc->setFileURL(fileURL); newDcc->setReverse(passive); if (!altFileName.isEmpty()) newDcc->setFileName(altFileName); if (fileSize != 0) newDcc->setFileSize(fileSize); emit addDccPanel(); if (newDcc->queue()) newDcc->start(); } QString Server::recoverDccFileName(const QStringList & dccArguments, int offset) const { QString fileName; if(dccArguments.count() > offset + 1) { qDebug() << "recover filename"; const int argumentOffsetSize = dccArguments.size() - offset; for (int i = 0; i < argumentOffsetSize; ++i) { fileName += dccArguments.at(i); //if not last element, append a space if (i < (argumentOffsetSize - 1)) { fileName += QLatin1Char(' '); } } } else { fileName = dccArguments.at(0); } return cleanDccFileName(fileName); } QString Server::cleanDccFileName(const QString& filename) const { QString cleanFileName = filename; //we want a clean filename to get rid of the mass """filename""" //NOTE: if a filename starts really with a ", it is escaped -> \" (2 chars) // but most clients don't support that and just replace it with a _ while (cleanFileName.startsWith(QLatin1Char('\"')) && cleanFileName.endsWith(QLatin1Char('\"'))) { cleanFileName = cleanFileName.mid(1, cleanFileName.length() - 2); } return cleanFileName; } quint16 Server::stringToPort(const QString &port, bool *ok) { bool toUintOk = false; uint uPort32 = port.toUInt(&toUintOk); // ports over 65535 are invalid, someone sends us bad data if (!toUintOk || uPort32 > USHRT_MAX) { if (ok) { *ok = false; } } else { if (ok) { *ok = true; } } return (quint16)uPort32; } QString Server::recipientNick() const { QStringList nickList; // fill nickList with all nicks we know about foreach (Channel* lookChannel, m_channelList) { foreach (Nick* lookNick, lookChannel->getNickList()) { if (!nickList.contains(lookNick->getChannelNick()->getNickname())) nickList.append(lookNick->getChannelNick()->getNickname()); } } // add Queries as well, but don't insert duplicates foreach (Query* lookQuery, m_queryList) { if(!nickList.contains(lookQuery->getName())) nickList.append(lookQuery->getName()); } QStringListModel model; model.setStringList(nickList); return DCC::RecipientDialog::getNickname(getViewContainer()->getWindow(), &model); } void Server::addDccGet(const QString &sourceNick, const QStringList &dccArguments) { //filename ip port filesize [token] QString ip; quint16 port; QString fileName; quint64 fileSize; QString token; const int argumentSize = dccArguments.count(); bool ok = true; if (dccArguments.at(argumentSize - 3) == QLatin1Char('0')) //port==0, for passive send, ip can't be 0 { //filename ip port(0) filesize token fileName = recoverDccFileName(dccArguments, 4); //ip port filesize token ip = DCC::DccCommon::numericalIpToTextIp( dccArguments.at(argumentSize - 4) ); //-1 index, -1 token, -1 port, -1 filesize port = 0; fileSize = dccArguments.at(argumentSize - 2).toULongLong(); //-1 index, -1 token token = dccArguments.at(argumentSize - 1); //-1 index } else { //filename ip port filesize ip = DCC::DccCommon::numericalIpToTextIp( dccArguments.at(argumentSize - 3) ); //-1 index, -1 filesize fileName = recoverDccFileName(dccArguments, 3); //ip port filesize fileSize = dccArguments.at(argumentSize - 1).toULongLong(); //-1 index port = stringToPort(dccArguments.at(argumentSize - 2), &ok); //-1 index, -1 filesize } if (!ok) { appendMessageToFrontmost(i18n("Error"), i18nc("%1=nickname","Received invalid DCC SEND request from %1.", sourceNick)); return; } DCC::TransferRecv* newDcc = Application::instance()->getDccTransferManager()->newDownload(); newDcc->setConnectionId(connectionId()); newDcc->setPartnerNick(sourceNick); newDcc->setPartnerIp(ip); newDcc->setPartnerPort(port); newDcc->setFileName(fileName); newDcc->setFileSize(fileSize); // Reverse DCC if (!token.isEmpty()) { newDcc->setReverse(true, token); } qDebug() << "ip: " << ip; qDebug() << "port: " << port; qDebug() << "filename: " << fileName; qDebug() << "filesize: " << fileSize; qDebug() << "token: " << token; //emit after data was set emit addDccPanel(); if ( newDcc->queue() ) { appendMessageToFrontmost( i18n( "DCC" ), i18n( "%1 offers to send you \"%2\" (%3)...", newDcc->getPartnerNick(), fileName, ( newDcc->getFileSize() == 0 ) ? i18n( "unknown size" ) : KIO::convertSize( newDcc->getFileSize() ) ) ); if (Preferences::self()->dccAutoGet()) newDcc->start(); } } void Server::addDccChat(const QString& sourceNick, const QStringList& dccArguments) { //chat ip port [token] QString ip; quint16 port = 0; QString token; bool reverse = false; const int argumentSize = dccArguments.count(); bool ok = true; QString extension; extension = dccArguments.at(0); ip = DCC::DccCommon::numericalIpToTextIp(dccArguments.at(1)); if (argumentSize == 3) { //extension ip port port = stringToPort(dccArguments.at(2), &ok); } else if (argumentSize == 4) { //extension ip port(0) token token = dccArguments.at(3); reverse = true; } if (!ok) { appendMessageToFrontmost(i18n("Error"), i18nc("%1=nickname","Received invalid DCC CHAT request from %1.", sourceNick)); return; } DCC::Chat* newChat = Application::instance()->getDccTransferManager()->newChat(); newChat->setConnectionId(connectionId()); newChat->setPartnerNick(sourceNick); newChat->setOwnNick(getNickname()); qDebug() << "ip: " << ip; qDebug() << "port: " << port; qDebug() << "token: " << token; qDebug() << "extension: " << extension; newChat->setPartnerIp(ip); newChat->setPartnerPort(port); newChat->setReverse(reverse, token); newChat->setSelfOpened(false); newChat->setExtension(extension); emit addDccChat(newChat); newChat->start(); } void Server::openDccChat(const QString& nickname) { qDebug(); QString recipient(nickname); // if we don't have a recipient yet, let the user select one if (recipient.isEmpty()) { recipient = recipientNick(); } // do we have a recipient *now*? if (!recipient.isEmpty()) { DCC::Chat* newChat = Application::instance()->getDccTransferManager()->newChat(); newChat->setConnectionId(connectionId()); newChat->setPartnerNick(recipient); newChat->setOwnNick(getNickname()); newChat->setSelfOpened(true); emit addDccChat(newChat); newChat->start(); } } void Server::openDccWBoard(const QString& nickname) { qDebug(); QString recipient(nickname); // if we don't have a recipient yet, let the user select one if (recipient.isEmpty()) { recipient = recipientNick(); } // do we have a recipient *now*? if (!recipient.isEmpty()) { DCC::Chat* newChat = Application::instance()->getDccTransferManager()->newChat(); newChat->setConnectionId(connectionId()); newChat->setPartnerNick(recipient); newChat->setOwnNick(getNickname()); // Set extension before emiting addDccChat newChat->setExtension(DCC::Chat::Whiteboard); newChat->setSelfOpened(true); emit addDccChat(newChat); newChat->start(); } } void Server::requestDccChat(const QString& partnerNick, const QString& extension, const QString& numericalOwnIp, quint16 ownPort) { Konversation::OutputFilterResult result = getOutputFilter()->requestDccChat(partnerNick, extension, numericalOwnIp,ownPort); queue(result.toServer); } void Server::acceptDccGet(const QString& nick, const QString& file) { Application::instance()->getDccTransferManager()->acceptDccGet(m_connectionId, nick, file); } void Server::dccSendRequest(const QString &partner, const QString &fileName, const QString &address, quint16 port, quint64 size) { Konversation::OutputFilterResult result = getOutputFilter()->sendRequest(partner,fileName,address,port,size); queue(result.toServer); appendMessageToFrontmost( i18n( "DCC" ), i18n( "Asking %1 to accept upload of \"%2\" (%3)...", partner, cleanDccFileName(fileName), ( size == 0 ) ? i18n( "unknown size" ) : KIO::convertSize( size ) ) ); } void Server::dccPassiveSendRequest(const QString& recipient,const QString& fileName,const QString& address,quint64 size,const QString& token) { Konversation::OutputFilterResult result = getOutputFilter()->passiveSendRequest(recipient,fileName,address,size,token); queue(result.toServer); appendMessageToFrontmost( i18n( "DCC" ), i18n( "Asking %1 to accept passive upload of \"%2\" (%3)...", recipient, cleanDccFileName(fileName), ( size == 0 ) ? i18n( "unknown size" ) : KIO::convertSize( size ) ) ); } void Server::dccPassiveChatRequest(const QString& recipient, const QString& extension, const QString& address, const QString& token) { Konversation::OutputFilterResult result = getOutputFilter()->passiveChatRequest(recipient, extension, address, token); queue(result.toServer); appendMessageToFrontmost(i18n("DCC"), i18nc("%1=name, %2=dcc extension, chat or wboard for example","Asking %1 to accept %2...", recipient, extension)); } void Server::dccPassiveResumeGetRequest(const QString& sender,const QString& fileName,quint16 port,KIO::filesize_t startAt,const QString &token) { Konversation::OutputFilterResult result = getOutputFilter()->resumePassiveRequest(sender,fileName,port,startAt,token);; queue(result.toServer); } void Server::dccResumeGetRequest(const QString &sender, const QString &fileName, quint16 port, KIO::filesize_t startAt) { Konversation::OutputFilterResult result = getOutputFilter()->resumeRequest(sender,fileName,port,startAt);; queue(result.toServer); } void Server::dccReverseSendAck(const QString& partnerNick,const QString& fileName,const QString& ownAddress,quint16 ownPort,quint64 size,const QString& reverseToken) { Konversation::OutputFilterResult result = getOutputFilter()->acceptPassiveSendRequest(partnerNick,fileName,ownAddress,ownPort,size,reverseToken); queue(result.toServer); } void Server::dccReverseChatAck(const QString& partnerNick, const QString& extension, const QString& ownAddress, quint16 ownPort, const QString& reverseToken) { Konversation::OutputFilterResult result = getOutputFilter()->acceptPassiveChatRequest(partnerNick, extension, ownAddress, ownPort, reverseToken); queue(result.toServer); } void Server::dccRejectSend(const QString& partnerNick, const QString& fileName) { Konversation::OutputFilterResult result = getOutputFilter()->rejectDccSend(partnerNick,fileName); queue(result.toServer); } void Server::dccRejectChat(const QString& partnerNick, const QString& extension) { Konversation::OutputFilterResult result = getOutputFilter()->rejectDccChat(partnerNick, extension); queue(result.toServer); } void Server::startReverseDccChat(const QString &sourceNick, const QStringList &dccArguments) { qDebug(); DCC::TransferManager* dtm = Application::instance()->getDccTransferManager(); bool ok = true; QString partnerIP = DCC::DccCommon::numericalIpToTextIp(dccArguments.at(1)); quint16 port = stringToPort(dccArguments.at(2), &ok); QString token = dccArguments.at(3); qDebug() << "ip: " << partnerIP; qDebug() << "port: " << port; qDebug() << "token: " << token; if (!ok || dtm->startReverseChat(connectionId(), sourceNick, partnerIP, port, token) == nullptr) { // DTM could not find a matched item appendMessageToFrontmost(i18n("Error"), i18nc("%1 = nickname", "Received invalid passive DCC chat acceptance message from %1.", sourceNick)); } } void Server::startReverseDccSendTransfer(const QString& sourceNick,const QStringList& dccArguments) { qDebug(); DCC::TransferManager* dtm = Application::instance()->getDccTransferManager(); bool ok = true; const int argumentSize = dccArguments.size(); QString partnerIP = DCC::DccCommon::numericalIpToTextIp( dccArguments.at(argumentSize - 4) ); //dccArguments[1] ) ); quint16 port = stringToPort(dccArguments.at(argumentSize - 3), &ok); QString token = dccArguments.at(argumentSize - 1); quint64 fileSize = dccArguments.at(argumentSize - 2).toULongLong(); QString fileName = recoverDccFileName(dccArguments, 4); //ip port filesize token qDebug() << "ip: " << partnerIP; qDebug() << "port: " << port; qDebug() << "filename: " << fileName; qDebug() << "filesize: " << fileSize; qDebug() << "token: " << token; if (!ok || dtm->startReverseSending(connectionId(), sourceNick, fileName, // filename partnerIP, // partner IP port, // partner port fileSize, // filesize token // Reverse DCC token ) == nullptr) { // DTM could not find a matched item appendMessageToFrontmost(i18n("Error"), i18nc("%1 = file name, %2 = nickname", "Received invalid passive DCC send acceptance message for \"%1\" from %2.", fileName, sourceNick)); } } void Server::resumeDccGetTransfer(const QString &sourceNick, const QStringList &dccArguments) { DCC::TransferManager* dtm = Application::instance()->getDccTransferManager(); //filename port position [token] QString fileName; quint64 position; quint16 ownPort; bool ok = true; const int argumentSize = dccArguments.count(); if (dccArguments.at(argumentSize - 3) == QLatin1Char('0')) //-1 index, -1 token, -1 pos { fileName = recoverDccFileName(dccArguments, 3); //port position token ownPort = 0; position = dccArguments.at(argumentSize - 2).toULongLong(); //-1 index, -1 token } else { fileName = recoverDccFileName(dccArguments, 2); //port position ownPort = stringToPort(dccArguments.at(argumentSize - 2), &ok); //-1 index, -1 pos position = dccArguments.at(argumentSize - 1).toULongLong(); //-1 index } //do we need the token here? DCC::TransferRecv* dccTransfer = nullptr; if (ok) { dccTransfer = dtm->resumeDownload(connectionId(), sourceNick, fileName, ownPort, position); } if (dccTransfer) { appendMessageToFrontmost(i18n("DCC"), i18nc("%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...", fileName, sourceNick, QString::number( dccTransfer->getProgress()), (dccTransfer->getFileSize() == 0) ? i18n("unknown size") : KIO::convertSize(dccTransfer->getFileSize()))); } else { appendMessageToFrontmost(i18n("Error"), i18nc("%1 = file name, %2 = nickname", "Received invalid resume acceptance message for \"%1\" from %2.", fileName, sourceNick)); } } void Server::resumeDccSendTransfer(const QString &sourceNick, const QStringList &dccArguments) { DCC::TransferManager* dtm = Application::instance()->getDccTransferManager(); bool passiv = false; QString fileName; quint64 position; QString token; quint16 ownPort; bool ok = true; const int argumentSize = dccArguments.count(); //filename port filepos [token] if (dccArguments.at( argumentSize - 3) == QLatin1Char('0')) { //filename port filepos token passiv = true; ownPort = 0; token = dccArguments.at( argumentSize - 1); // -1 index position = dccArguments.at( argumentSize - 2).toULongLong(); // -1 index, -1 token fileName = recoverDccFileName(dccArguments, 3); //port filepos token } else { //filename port filepos ownPort = stringToPort(dccArguments.at(argumentSize - 2), &ok); //-1 index, -1 filesize position = dccArguments.at( argumentSize - 1).toULongLong(); // -1 index fileName = recoverDccFileName(dccArguments, 2); //port filepos } DCC::TransferSend* dccTransfer = nullptr; if (ok) { dccTransfer = dtm->resumeUpload(connectionId(), sourceNick, fileName, ownPort, position); } if (dccTransfer) { appendMessageToFrontmost(i18n("DCC"), i18nc("%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...", fileName, sourceNick, QString::number(dccTransfer->getProgress()), (dccTransfer->getFileSize() == 0) ? i18n("unknown size") : KIO::convertSize(dccTransfer->getFileSize()))); // fileName can't have " here if (fileName.contains(QLatin1Char(' '))) fileName = QLatin1Char('\"')+fileName+QLatin1Char('\"'); // FIXME: this operation should be done by TransferManager Konversation::OutputFilterResult result; if (passiv) result = getOutputFilter()->acceptPassiveResumeRequest( sourceNick, fileName, ownPort, position, token ); else result = getOutputFilter()->acceptResumeRequest( sourceNick, fileName, ownPort, position ); queue( result.toServer ); } else { appendMessageToFrontmost(i18n("Error"), i18nc("%1 = file name, %2 = nickname", "Received invalid resume request for \"%1\" from %2.", fileName, sourceNick)); } } void Server::rejectDccSendTransfer(const QString &sourceNick, const QStringList &dccArguments) { DCC::TransferManager* dtm = Application::instance()->getDccTransferManager(); //filename QString fileName = recoverDccFileName(dccArguments, 0); DCC::TransferSend* dccTransfer = dtm->rejectSend(connectionId(), sourceNick, fileName); if (!dccTransfer) { appendMessageToFrontmost(i18n("Error"), i18nc("%1 = file name, %2 = nickname", "Received invalid reject request for \"%1\" from %2.", fileName, sourceNick)); } } void Server::rejectDccChat(const QString& sourceNick) { DCC::TransferManager* dtm = Application::instance()->getDccTransferManager(); DCC::Chat* dccChat = dtm->rejectChat(connectionId(), sourceNick); if (!dccChat) { appendMessageToFrontmost(i18n("Error"), i18nc("%1 = nickname", "Received invalid reject request from %1.", sourceNick)); } } void Server::dccGetDone(DCC::Transfer* item) { if (!item) return; if(item->getStatus() == DCC::Transfer::Done) { appendMessageToFrontmost(i18n("DCC"), i18nc("%1 = file name, %2 = nickname of sender", "Download of \"%1\" from %2 finished.", item->getFileName(), item->getPartnerNick())); } else if(item->getStatus() == DCC::Transfer::Failed) { appendMessageToFrontmost(i18n("DCC"), i18nc("%1 = file name, %2 = nickname of sender", "Download of \"%1\" from %2 failed. Reason: %3.", item->getFileName(), item->getPartnerNick(), item->getStatusDetail())); } } void Server::dccSendDone(DCC::Transfer* item) { if (!item) return; if(item->getStatus() == DCC::Transfer::Done) appendMessageToFrontmost(i18n("DCC"), i18nc("%1 = file name, %2 = nickname of recipient", "Upload of \"%1\" to %2 finished.", item->getFileName(), item->getPartnerNick())); else if(item->getStatus() == DCC::Transfer::Failed) appendMessageToFrontmost(i18n("DCC"), i18nc("%1 = file name, %2 = nickname of recipient", "Upload of \"%1\" to %2 failed. Reason: %3.", item->getFileName(), item->getPartnerNick(), item->getStatusDetail())); } void Server::dccStatusChanged(DCC::Transfer *item, int newStatus, int oldStatus) { if(!item) return; if ( item->getType() == DCC::Transfer::Send ) { // when resuming, a message about the receiver's acceptance has been shown already, so suppress this message if ( newStatus == DCC::Transfer::Transferring && oldStatus == DCC::Transfer::WaitingRemote && !item->isResumed() ) appendMessageToFrontmost( i18n( "DCC" ), i18nc( "%1 = file name, %2 nickname of recipient", "Sending \"%1\" to %2...", item->getFileName(), item->getPartnerNick() ) ); } else // type == Receive { if ( newStatus == DCC::Transfer::Transferring && !item->isResumed() ) { appendMessageToFrontmost( i18n( "DCC" ), i18nc( "%1 = file name, %2 = file size, %3 = nickname of sender", "Downloading \"%1\" (%2) from %3...", item->getFileName(), ( item->getFileSize() == 0 ) ? i18n( "unknown size" ) : KIO::convertSize( item->getFileSize() ), item->getPartnerNick() ) ); } } } void Server::removeQuery(Query* query) { m_queryList.removeOne(query); query->deleteLater(); } void Server::sendJoinCommand(const QString& name, const QString& password) { Konversation::OutputFilterResult result = getOutputFilter()->parse(getNickname(), Preferences::self()->commandChar() + QStringLiteral("JOIN ") + name + QLatin1Char(' ') + password, QString()); queue(result.toServer); } Channel* Server::joinChannel(const QString& name, const QString& hostmask, const QHash &messageTags) { // (re-)join channel, open a new panel if needed Channel* channel = getChannelByName(name); if (!channel) { channel=getViewContainer()->addChannel(this,name); Q_ASSERT(channel); 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); m_loweredChannelNameHash.insert(channel->getName().toLower(), channel); connect(channel,SIGNAL (sendFile()),this,SLOT (requestDccSend()) ); connect(this, &Server::nicknameChanged, channel, &Channel::setNickname); } // 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, messageTags); return channel; } 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.removeOne(channel); m_loweredChannelNameHash.remove(channel->getName().toLower()); if (!isConnected()) updateAutoJoin(); } void Server::updateChannelMode(const QString &updater, const QString &channelName, char mode, bool plus, const QString ¶meter, const QHash &messageTags) { 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, messageTags); // 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 QString userModes=QStringLiteral("vhoqa"); // voice halfop op owner admin int modePos = userModes.indexOf(QLatin1Char(mode)); if (modePos > 0) { ChannelNickPtr updateeNick = getChannelNick(channelName, parameter); if(!updateeNick) { /* if(parameter.isEmpty()) { qDebug() << "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) { addBan(channelName, QString(QStringLiteral("%1 %2 %3")).arg(parameter).arg(updater).arg(QDateTime::currentDateTime().toTime_t())); } else { removeBan(channelName, parameter); } } } void Server::updateChannelModeWidgets(const QString &channelName, char mode, const QString ¶meter) { Channel* channel=getChannelByName(channelName); if(channel) channel->updateModeWidgets(mode,true,parameter); } Channel* Server::getChannelByName(const QString& name) { if (name.isEmpty()) { return nullptr; } // Convert wanted channel name to lowercase QString wanted = name.toLower(); QRegularExpressionMatch p = m_targetMatcher.match(wanted); int index = p.capturedStart(2); if (index >= 0) { wanted = wanted.mid(index); if (m_loweredChannelNameHash.contains(wanted)) return m_loweredChannelNameHash.value(wanted); } return nullptr; } Query* Server::getQueryByName(const QString& name) { // Convert wanted query name to lowercase QString wanted = name.toLower(); // Traverse through list to find the query with "name" foreach (Query* lookQuery, m_queryList) { if(lookQuery->getName().toLower()==wanted) return lookQuery; } // No query by that name found? Must be a new query request. Return 0 return nullptr; } ChatWindow* Server::getChannelOrQueryByName(const QString& name) { ChatWindow* window = getChannelByName(name); if (!window) window = getQueryByName(name); return window; } void Server::queueNicks(const QString& channelName, const QStringList& nicknameList) { Channel* channel = getChannelByName(channelName); if (channel) channel->queueNicks(nicknameList); } // 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 QString& channelName, const QString& nickname) { bool doChannelJoinedSignal = false; bool doWatchedNickChangedSignal = false; bool doChannelMembersChangedSignal = false; QString lcNickname(nickname.toLower()); // 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. QString lcChannelName = channelName.toLower(); 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, lcChannelName); 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; } // 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 QString& channelName, const QString& nickname) { bool doChannelUnjoinedSignal = false; bool doWatchedNickChangedSignal = false; bool doChannelMembersChangedSignal = false; QString lcNickname(nickname.toLower()); // 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. QString lcChannelName = channelName.toLower(); 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, lcChannelName); 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 QString& nickname) { NickInfoPtr nickInfo = getNickInfo(nickname); if (!nickInfo) { QString lcNickname(nickname.toLower()); nickInfo = new NickInfo(nickname, this); m_allNicks.insert(lcNickname, nickInfo); } emit watchedNickChanged(this, nickname, true); appendMessageToFrontmost(i18nc("Message type", "Notify"), i18n("%1 is online (%2).", nickname, getServerName()), QHash(), getStatusView()); Application::instance()->notificationHandler()->nickOnline(getStatusView(), nickname); nickInfo->setPrintedOnline(true); return nickInfo; } void Server::setWatchedNickOffline(const QString& nickname, const NickInfoPtr &nickInfo) { Q_UNUSED(nickInfo) emit watchedNickChanged(this, nickname, false); appendMessageToFrontmost(i18nc("Message type", "Notify"), i18n("%1 went offline (%2).", nickname, getServerName()), QHash(), getStatusView()); Application::instance()->notificationHandler()->nickOffline(getStatusView(), nickname); } bool Server::setNickOffline(const QString& nickname) { QString lcNickname(nickname.toLower()); NickInfoPtr nickInfo = getNickInfo(lcNickname); bool wasOnline = nickInfo ? nickInfo->getPrintedOnline() : false; if (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). QStringList nickChannels = getNickChannels(lcNickname); QStringList::iterator itEnd = nickChannels.end(); for(QStringList::iterator it = nickChannels.begin(); it != itEnd; ++it) { QString 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 != nullptr); } /** * 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 QString &nickname) { QString lcNickname(nickname.toLower()); // Don't delete our own nickinfo. if (lcNickname == loweredNickname()) return false; if (!m_queryNicks.contains(lcNickname)) { QStringList 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 QString& channelName, const QString& nickname) { bool doSignal = false; bool joined = false; QString lcChannelName = channelName.toLower(); QString lcNickname = nickname.toLower(); 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 { qDebug() << "Error: Tried to remove nickname=" << nickname << " from joined channel=" << channelName; } } 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); } else { qDebug() << "Error: Tried to remove nickname=" << nickname << " from unjoined channel=" << channelName; } } } if (doSignal) emit channelMembersChanged(this, channelName, joined, true, nickname); } QStringList Server::getWatchList() { // no nickinfo ISON for the time being if (getServerGroup()) return Preferences::notifyListByGroupId(getServerGroup()->id()); else return QStringList(); if (m_serverISON) return m_serverISON->getWatchList(); else return QStringList(); } QStringList Server::getISONList() { // no nickinfo ISON for the time being if (getServerGroup()) return Preferences::notifyListByGroupId(getServerGroup()->id()); else return QStringList(); if (m_serverISON) return m_serverISON->getISONList(); else return QStringList(); } QString Server::getISONListString() { return getISONList().join(QLatin1Char(' ')); } /** * Return true if the given nickname is on the watch list. */ bool Server::isWatchedNick(const QString& nickname) { // no nickinfo ISON for the time being if (getServerGroup()) return Preferences::isNotify(getServerGroup()->id(), nickname); else return false; // ###### ERROR: not reached return getWatchList().contains(nickname, Qt::CaseInsensitive); } /** * 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 QString& channelName) { bool doSignal = false; QStringList watchListLower = getWatchList(); QString lcChannelName = channelName.toLower(); // 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() ;) { QString lcNickname = member.key(); if (!watchListLower.contains(lcNickname)) { // Remove the unwatched nickname from the unjoined channel. channel->erase(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 QString& newname) { if (nickInfo) { // Get existing lowercase nickname and rename nickname in the NickInfo object. QString lcNickname(nickInfo->loweredNickname()); nickInfo->setNickname(newname); QString lcNewname(newname.toLower()); // Rename the key in m_allNicks list. m_allNicks.remove(lcNickname); m_allNicks.insert(lcNewname, nickInfo); // Rename key in the joined and unjoined lists. QStringList nickChannels = getNickChannels(lcNickname); QStringList::iterator itEnd = nickChannels.end(); for(QStringList::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 { qDebug() << "was called for newname='" << newname << "' but nickInfo is null"; } } Channel* Server::nickJoinsChannel(const QString &channelName, const QString &nickname, const QString &hostmask, const QString &account, const QString &realName, const QHash &messageTags) { 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); } if (!account.isEmpty()) { nickInfo->setAccount(account); } if (!realName.isEmpty()) { nickInfo->setRealName(realName); } outChannel->joinNickname(channelNick, messageTags); } return outChannel; } void Server::addHostmaskToNick(const QString& sourceNick, const QString& sourceHostmask) { // Update NickInfo. NickInfoPtr nickInfo = getNickInfo(sourceNick); if (nickInfo) { if ((nickInfo->getHostmask() != sourceHostmask) && !sourceHostmask.isEmpty()) { nickInfo->setHostmask(sourceHostmask); } } } Channel* Server::removeNickFromChannel(const QString &channelName, const QString &nickname, const QString &reason, const QHash &messageTags, bool quit) { Channel* outChannel = getChannelByName(channelName); if(outChannel) { outChannel->flushNickQueue(); ChannelNickPtr channelNick = getChannelNick(channelName, nickname); if(channelNick) { outChannel->removeNick(channelNick,reason,quit, messageTags); } } // 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)) { QString nicky = nickname; deleteNickIfUnlisted(nicky); } return outChannel; } void Server::nickWasKickedFromChannel(const QString &channelName, const QString &nickname, const QString &kicker, const QString &reason, const QHash &messageTags) { Channel* outChannel = getChannelByName(channelName); if(outChannel) { outChannel->flushNickQueue(); ChannelNickPtr channelNick = getChannelNick(channelName, nickname); if(channelNick) { outChannel->kickNick(channelNick, kicker, reason, messageTags); // Tell Nickinfo removeChannelNick(channelName,nickname); } } } void Server::removeNickFromServer(const QString &nickname,const QString &reason, const QHash &messageTags) { foreach (Channel* channel, m_channelList) { channel->flushNickQueue(); // Check if nick is in this channel or not. if(channel->getNickByName(nickname)) removeNickFromChannel(channel->getName(), nickname, reason, messageTags, true); } Query* query = getQueryByName(nickname); if (query) query->quitNick(reason, messageTags); // Delete the nick from all channels and then delete the nickinfo, // emitting signal if on the watch list. setNickOffline(nickname); } void Server::renameNick(const QString &nickname, const QString &newNick, const QHash &messageTags) { if(nickname.isEmpty() || newNick.isEmpty()) { qDebug() << "called with empty strings! Trying to rename '" << nickname << "' to '" << newNick << "'"; return; } // If this was our own nickchange, tell our server object about it if (nickname == getNickname()) setNickname(newNick); //Actually do the rename. NickInfoPtr nickInfo = getNickInfo(nickname); if(!nickInfo) { qDebug() << "called for nickname '" << nickname << "' to '" << newNick << "' but getNickInfo('" << nickname << "') returned no results."; } 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 foreach (Channel* channel, m_channelList) { channel->flushNickQueue(); // All we do is notify that the nick has been renamed.. we haven't actually renamed it yet if (channel->getNickByName(nickname)) channel->nickRenamed(nickname, *nickInfo, messageTags); } //Watched nicknames stuff if (isWatchedNick(nickname)) setWatchedNickOffline(nickname, NickInfoPtr()); } // We had an encrypt conversation with the user that changed his nick, lets copy the key to the new nick and remove the old nick #ifdef HAVE_QCA2 QByteArray userKey = getKeyForRecipient(nickname); if (!userKey.isEmpty()) { setKeyForRecipient(newNick, userKey); m_keyHash.remove(nickname.toLower()); } #endif } void Server::userhost(const QString& nick,const QString& hostmask,bool away,bool /* ircOp */) { addHostmaskToNick(nick, hostmask); // remember my IP for DCC things // myself if (m_ownIpByUserhost.isEmpty() && nick == getNickname()) { QString myhost = hostmask.section(QLatin1Char('@'), 1); // Use async lookup else you will be blocking GUI badly QHostInfo::lookupHost(myhost, this, SLOT(gotOwnResolvedHostByUserhost(QHostInfo))); } NickInfoPtr nickInfo = getNickInfo(nick); if (nickInfo) { if (nickInfo->isAway() != away) { nickInfo->setAway(away); } } } void Server::gotOwnResolvedHostByUserhost(const QHostInfo& res) { if ( res.error() == QHostInfo::NoError && !res.addresses().isEmpty() ) m_ownIpByUserhost = res.addresses().first().toString(); else qDebug() << "Got error: " << res.errorString(); } void Server::appendServerMessageToChannel(const QString& channel,const QString& type,const QString& message, const QHash &messageTags) { Channel* outChannel = getChannelByName(channel); if (outChannel) outChannel->appendServerMessage(type, message, messageTags); } void Server::appendCommandMessageToChannel(const QString& channel, const QString& command, const QString& message, const QHash &messageTags, bool highlight, bool parseURL) { Channel* outChannel = getChannelByName(channel); if (outChannel) { outChannel->appendCommandMessage(command, message, messageTags, parseURL, !highlight); } else { appendStatusMessage(command, QString(QStringLiteral("%1 %2")).arg(channel).arg(message), messageTags); } } void Server::appendStatusMessage(const QString& type, const QString& message, const QHash &messageTags) { getStatusView()->appendServerMessage(type, message, messageTags); } void Server::appendMessageToFrontmost(const QString& type, const QString& message, const QHash &messageTags, bool parseURL) { getViewContainer()->appendToFrontmost(type, message, getStatusView(), messageTags, parseURL); } void Server::setNickname(const QString &newNickname) { m_nickname = newNickname; m_loweredNickname = newNickname.toLower(); if (!m_nickListModel->stringList().contains(newNickname)) { m_nickListModel->insertRows(m_nickListModel->rowCount(), 1); m_nickListModel->setData(m_nickListModel->index(m_nickListModel->rowCount() -1 , 0), newNickname, Qt::DisplayRole); } emit nicknameChanged(newNickname); } void Server::setChannelTopic(const QString &channel, const QString &newTopic, const QHash &messageTags) { Channel* outChannel = getChannelByName(channel); if(outChannel) { // encoding stuff is done in send() outChannel->setTopic(newTopic, messageTags); } } // Overloaded void Server::setChannelTopic(const QString& nickname, const QString &channel, const QString &newTopic, const QHash &messageTags) { Channel* outChannel = getChannelByName(channel); if(outChannel) { // encoding stuff is done in send() outChannel->setTopic(nickname, newTopic, messageTags); } } void Server::setTopicAuthor(const QString& channel, const QString& author, const QDateTime &time) { Channel* outChannel = getChannelByName(channel); if(outChannel) outChannel->setTopicAuthor(author, time); } void Server::endOfWho(const QString& target) { Channel* channel = getChannelByName(target); if(channel) channel->scheduleAutoWho(); } void Server::endOfNames(const QString& target) { Channel* channel = getChannelByName(target); if(channel) channel->endOfNames(); } bool Server::isNickname(const QString &compare) const { return (m_nickname == compare); } QString Server::getNickname() const { return m_nickname; } QString Server::loweredNickname() const { return m_loweredNickname; } QString Server::parseWildcards(const QString& toParse, ChatWindow* context, const QStringList &nicks) { QString inputLineText; if (context && context->getInputBar()) inputLineText = context->getInputBar()->toPlainText(); if (!context) return parseWildcards(toParse, getNickname(), QString(), QString(), QString(), QString()); else if (context->getType() == ChatWindow::Channel) { Channel* channel = qobject_cast(context); return parseWildcards(toParse, getNickname(), context->getName(), channel->getPassword(), nicks.count() ? nicks : channel->getSelectedNickList(), inputLineText); } else if (context->getType() == ChatWindow::Query) return parseWildcards(toParse, getNickname(), context->getName(), QString(), context->getName(), inputLineText); return parseWildcards(toParse, getNickname(), context->getName(), QString(), QString(), inputLineText); } QString Server::parseWildcards(const QString& toParse, const QString& sender, const QString& channelName, const QString& channelKey, const QString& nick, const QString& inputLineText) { return parseWildcards(toParse, sender, channelName, channelKey, nick.split(QLatin1Char(' '), QString::SkipEmptyParts), inputLineText); } QString Server::parseWildcards(const QString& toParse, const QString& sender, const QString& channelName, const QString& channelKey, const QStringList& nickList, const QString& inputLineText ) { // store the parsed version QString out; // default separator QString separator(QStringLiteral(" ")); int index = 0, found = 0; QChar toExpand; while ((found = toParse.indexOf(QLatin1Char('%'), index)) != -1) { // append part before the % out.append(toParse.midRef(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 == QLatin1Char('s')) { found = toParse.indexOf(QLatin1Char('%'), index); if (found == -1) // no other % (not valid) break; separator = toParse.mid(index,found-index); index = found + 1; // skip separator, including % } else if (toExpand == QLatin1Char('u')) { out.append(nickList.join(separator)); } else if (toExpand == QLatin1Char('c')) { if(!channelName.isEmpty()) out.append(channelName); } else if (toExpand == QLatin1Char('o')) { out.append(sender); } else if (toExpand == QLatin1Char('k')) { if(!channelKey.isEmpty()) out.append(channelKey); } else if (toExpand == QLatin1Char('K')) { if(getConnectionSettings().server().password().isEmpty()) out.append(getConnectionSettings().server().password()); } else if (toExpand == QLatin1Char('n')) { out.append(QLatin1Char('\n')); } else if (toExpand == QLatin1Char('p')) { out.append(QLatin1Char('%')); } else if (toExpand == QLatin1Char('i')) { out.append(inputLineText); } } // append last part out.append(toParse.midRef(index,toParse.length()-index)); return out; } void Server::sendToAllChannels(const QString &text) { // Send a message to all channels we are in foreach (Channel* channel, m_channelList) { channel->sendText(text); } } void Server::invitation(const QString& nick,const QString& channel) { if(!m_inviteDialog) { QDialogButtonBox::StandardButton buttonCode = QDialogButtonBox::Cancel; if(!InviteDialog::shouldBeShown(buttonCode)) { if (buttonCode == QDialogButtonBox::Ok) sendJoinCommand(channel); return; } m_inviteDialog = new InviteDialog (getViewContainer()->getWindow()); connect(m_inviteDialog, SIGNAL(joinChannelsRequested(QString)), this, SLOT(sendJoinCommand(QString))); } m_inviteDialog->show(); m_inviteDialog->raise(); m_inviteDialog->addInvite(nick, channel); } void Server::scriptNotFound(const QString& name) { appendMessageToFrontmost(i18n("D-Bus"),i18n("Error: Could not find script \"%1\".", name)); } void Server::scriptExecutionError(const QString& name) { appendMessageToFrontmost(i18n("D-Bus"),i18n("Error: Could not execute script \"%1\". Check file permissions.", name)); } bool Server::isAChannel(const QString &channel) const { if (channel.isEmpty()) return false; QRegularExpressionMatch x = m_targetMatcher.match(channel); int index = x.capturedStart(2); return (index >= 0 && getChannelTypes().contains(channel.at(index)) > 0); } void Server::addRawLog(bool show) { if (!m_rawLog) m_rawLog = getViewContainer()->addRawLog(this); connect(this, SIGNAL(serverOnline(bool)), m_rawLog, 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() { delete m_rawLog; } void Server::requestOpenChannelListPanel(const QString& filter) { getViewContainer()->openChannelList(this, filter, true); } ChannelListPanel* Server::addChannelListPanel() { if(!m_channelListPanel) { m_channelListPanel = getViewContainer()->addChannelListPanel(this); connect(&m_inputFilter, &InputFilter::endOfChannelList, m_channelListPanel.data(), &ChannelListPanel::endOfChannelList); connect(m_channelListPanel.data(), &ChannelListPanel::refreshChannelList, this, &Server::requestChannelList); connect(m_channelListPanel, SIGNAL(joinChannel(QString)), this, SLOT(sendJoinCommand(QString))); connect(this, SIGNAL(serverOnline(bool)), m_channelListPanel, SLOT(serverOnline(bool))); } return m_channelListPanel; } void Server::addToChannelList(const QString& channel, int users, const QString& topic) { addChannelListPanel(); m_channelListPanel->addToChannelList(channel, users, topic); } ChannelListPanel* Server::getChannelListPanel() const { return m_channelListPanel; } void Server::closeChannelListPanel() { delete m_channelListPanel; } void Server::updateAutoJoin(Konversation::ChannelList channels) { Konversation::ChannelList tmpList; if (!channels.isEmpty()) { foreach (const ChannelSettings& cs, channels) { tmpList << cs; } } else if (m_channelList.isEmpty() && getServerGroup()) tmpList = getServerGroup()->channelList(); else { foreach (Channel* channel, m_channelList) { tmpList << channel->channelSettings(); } } if (!tmpList.isEmpty()) { setAutoJoinCommands(generateJoinCommand(tmpList)); setAutoJoin(!m_autoJoinCommands.isEmpty()); } else { m_autoJoinCommands.clear(); setAutoJoin(false); } } QStringList Server::generateJoinCommand(const Konversation::ChannelList &tmpList) { QStringList channels; QStringList passwords; QStringList joinCommands; uint length = 0; Konversation::ChannelList::const_iterator it; for (it = tmpList.constBegin(); it != tmpList.constEnd(); ++it) { QString channel = (*it).name(); // Only add the channel to the JOIN command if it has a valid channel name. if (isAChannel(channel)) { QString password = ((*it).password().isEmpty() ? QStringLiteral(".") : (*it).password()); uint currentLength = getIdentity()->getCodec()->fromUnicode(channel).length(); currentLength += getIdentity()->getCodec()->fromUnicode(password).length(); //channels.count() and passwords.count() account for the commas if (length + currentLength + 6 + channels.count() + passwords.count() >= 512) // 6: "JOIN " plus separating space between chans and pws. { while (!passwords.isEmpty() && passwords.last() == QLatin1Char('.')) passwords.pop_back(); joinCommands << QStringLiteral("JOIN ") + channels.join(QLatin1Char(',')) + QLatin1Char(' ') + passwords.join(QLatin1Char(',')); channels.clear(); passwords.clear(); length = 0; } length += currentLength; channels << channel; passwords << password; } } while (!passwords.isEmpty() && passwords.last() == QLatin1Char('.')) passwords.pop_back(); // Even if the given tmpList contained entries they might have been filtered // out by the isAChannel() check. if (!channels.isEmpty()) { joinCommands << QStringLiteral("JOIN ") + channels.join(QLatin1Char(',')) + QLatin1Char(' ') + passwords.join(QLatin1Char(',')); } return joinCommands; } ViewContainer* Server::getViewContainer() const { Application* konvApp = Application::instance(); return konvApp->getMainWindow()->getViewContainer(); } bool Server::getUseSSL() const { if ( m_socket ) return ( m_socket->mode() != QSslSocket::UnencryptedMode ); else return false; } QString Server::getSSLInfo() const { // SSLSocket* sslsocket = dynamic_cast(m_socket); // if(sslsocket) // return sslsocket->details(); return QString(); } void Server::sendMultiServerCommand(const QString& command, const QString& parameter) { emit multiServerCommand(command, parameter); } void Server::executeMultiServerCommand(const QString& command, const QString& parameter) { if (command == QLatin1String("msg")) sendToAllChannelsAndQueries(parameter); else sendToAllChannelsAndQueries(Preferences::self()->commandChar() + command + QLatin1Char(' ') + parameter); } void Server::sendToAllChannelsAndQueries(const QString& text) { // Send a message to all channels we are in foreach (Channel* channel, m_channelList) { channel->sendText(text); } // Send a message to all queries we are in foreach (Query* query, m_queryList) { query->sendText(text); } } void Server::requestAway(const QString& reason) { QString awayReason = reason; IdentityPtr identity = getIdentity(); if (awayReason.isEmpty() && identity) awayReason = identity->getAwayMessage(); // Fallback in case the identity has no away message set. if (awayReason.isEmpty()) awayReason = i18n("Gone away for now"); setAwayReason(awayReason); queue(QStringLiteral("AWAY :") + awayReason); } void Server::requestUnaway() { queue(QStringLiteral("AWAY")); } void Server::setAway(bool away, const QHash &messageTags) { IdentityPtr identity = getIdentity(); if (away) { if (!m_away) startAwayTimer(); m_away = true; emit awayState(true); if (identity && !identity->getAwayNickname().isEmpty() && identity->getAwayNickname() != getNickname()) { m_nonAwayNick = getNickname(); queue(QStringLiteral("NICK ") + getIdentity()->getAwayNickname()); } if (!m_awayReason.isEmpty()) appendMessageToFrontmost(i18n("Away"), i18n("You are now marked as being away (reason: %1).",m_awayReason), messageTags); else appendMessageToFrontmost(i18n("Away"), i18n("You are now marked as being away."), messageTags); if (identity && identity->getRunAwayCommands()) { QString message = identity->getAwayCommand(); sendToAllChannels(message.replace(QRegExp(QStringLiteral("%s"), Qt::CaseInsensitive), m_awayReason)); } if (identity && identity->getInsertRememberLineOnAway()) emit awayInsertRememberLine(this); } else { m_awayReason.clear(); emit awayState(false); if (!identity->getAwayNickname().isEmpty() && !m_nonAwayNick.isEmpty()) { queue(QStringLiteral("NICK ") + m_nonAwayNick); m_nonAwayNick.clear(); } if (m_away) { appendMessageToFrontmost(i18n("Away"), i18n("You are no longer marked as being away."), messageTags); if (identity && identity->getRunAwayCommands()) { QString message = identity->getReturnCommand(); sendToAllChannels(message.replace(QRegExp(QStringLiteral("%t"), Qt::CaseInsensitive), awayTime())); } } else appendMessageToFrontmost(i18n("Away"), i18n("You are not marked as being away."), messageTags); m_away = false; } } QString Server::awayTime() const { QString retVal; if (m_away) { int diff = QDateTime::currentDateTime().toTime_t() - m_awayTime; int num = diff / 3600; if (num < 10) retVal = QLatin1Char('0') + QString::number(num) + QLatin1Char(':'); else retVal = QString::number(num) + QLatin1Char(':'); num = (diff % 3600) / 60; if (num < 10) retVal += QLatin1Char('0'); retVal += QString::number(num) + QLatin1Char(':'); num = (diff % 3600) % 60; if (num < 10) retVal += QLatin1Char('0'); retVal += QString::number(num); } else retVal = QStringLiteral("00:00:00"); return retVal; } void Server::startAwayTimer() { m_awayTime = QDateTime::currentDateTime().toTime_t(); } void Server::enableIdentifyMsg(bool enabled) { m_identifyMsg = enabled; } bool Server::identifyMsgEnabled() { return m_identifyMsg; } void Server::addBan(const QString &channel, const QString &ban) { Channel* outChannel = getChannelByName(channel); if(outChannel) { outChannel->addBan(ban); } } void Server::removeBan(const QString &channel, const QString &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 :-) QStringList ql; ql << QStringLiteral("PING LAG") + QTime::currentTime().toString(QStringLiteral("hhmmss")); - getInputFilter()->setAutomaticRequest(QStringLiteral("WHO"), getNickname(), true); - ql << QStringLiteral("WHO ") + getNickname(); + if(!m_whoRequestsDisabled) + { + getInputFilter()->setAutomaticRequest(QStringLiteral("WHO"), getNickname(), true); + ql << QStringLiteral("WHO ") + getNickname(); + } queueList(ql, HighPriority); m_lagTime.start(); m_inputFilter.setLagMeasuring(true); m_pingResponseTimer.start(1000 /*1 sec*/); } void Server::pongReceived() { // ignore unrequested PONGs if (m_pingSendTimer.isActive()) return; m_currentLag = m_lagTime.elapsed(); m_inputFilter.setLagMeasuring(false); m_pingResponseTimer.stop(); emit serverLag(this, m_currentLag); // Send another PING in 60 seconds m_pingSendTimer.start(60000 /*60 sec*/); } void Server::updateLongPongLag() { if (isSocketConnected()) { m_currentLag = m_lagTime.elapsed(); emit tooLongLag(this, m_currentLag); // qDebug() << "Current lag: " << currentLag; if (m_currentLag > (Preferences::self()->maximumLagTime() * 1000)) m_socket->close(); } } void Server::updateEncoding() { if(getViewContainer() && getViewContainer()->getFrontView()) getViewContainer()->updateViewEncoding(getViewContainer()->getFrontView()); } #ifdef HAVE_QCA2 void Server::initKeyExchange(const QString &receiver) { Query* query; if (getQueryByName(receiver)) { query = getQueryByName(receiver); } else { NickInfoPtr nickinfo = obtainNickInfo(receiver); query = addQuery(nickinfo, true); } Konversation::Cipher* cipher = query->getCipher(); QByteArray pubKey = cipher->initKeyExchange(); if(pubKey.isEmpty()) { appendMessageToFrontmost(i18n("Error"), i18n("Failed to initiate key exchange with %1.", receiver)); } else { queue("NOTICE "+receiver+" :DH1080_INIT "+pubKey); } } void Server::parseInitKeyX(const QString &sender, const QString &remoteKey) { if (!Konversation::Cipher::isFeatureAvailable(Konversation::Cipher::DH)) { appendMessageToFrontmost(i18n("Error"), i18n("Unable to perform key exchange with %1.", sender) + ' ' + Konversation::Cipher::runtimeError()); return; } //TODO ask the user to accept without blocking Query* query; if (getQueryByName(sender)) { query = getQueryByName(sender); } else { NickInfoPtr nickinfo = obtainNickInfo(sender); query = addQuery(nickinfo, false); } Konversation::Cipher* cipher = query->getCipher(); QByteArray pubKey = cipher->parseInitKeyX(remoteKey.toLocal8Bit()); if (pubKey.isEmpty()) { appendMessageToFrontmost(i18n("Error"), i18n("Failed to parse the DH1080_INIT of %1. Key exchange failed.", sender)); } else { setKeyForRecipient(sender, cipher->key()); query->setEncryptedOutput(true); appendMessageToFrontmost(i18n("Notice"), i18n("Your key is set and your messages will now be encrypted, sending DH1080_FINISH to %1.", sender)); queue("NOTICE "+sender+" :DH1080_FINISH "+pubKey); } } void Server::parseFinishKeyX(const QString &sender, const QString &remoteKey) { Query* query; if (getQueryByName(sender)) { query = getQueryByName(sender); } else return; if (!Konversation::Cipher::isFeatureAvailable(Konversation::Cipher::DH)) { appendMessageToFrontmost(i18n("Error"), i18n("Unable to complete key exchange with %1.", sender) + ' ' + Konversation::Cipher::runtimeError()); return; } Konversation::Cipher* cipher = query->getCipher(); if (cipher->parseFinishKeyX(remoteKey.toLocal8Bit())) { setKeyForRecipient(sender,cipher->key()); query->setEncryptedOutput(true); appendMessageToFrontmost(i18n("Notice"), i18n("Successfully parsed DH1080_FINISH sent by %1. Your key is set and your messages will now be encrypted.", sender)); } else { appendMessageToFrontmost(i18n("Error"), i18n("Failed to parse DH1080_FINISH sent by %1. Key exchange failed.", sender)); } } #endif QAbstractItemModel* Server::nickListModel() const { return m_nickListModel; } void Server::startNickInfoChangedTimer() { if(!m_nickInfoChangedTimer->isActive()) m_nickInfoChangedTimer->start(); } void Server::sendNickInfoChangedSignals() { emit nickInfoChanged(); foreach(NickInfoPtr nickInfo, m_allNicks) { if(nickInfo->isChanged()) { emit nickInfoChanged(this, nickInfo); nickInfo->setChanged(false); } } } void Server::startChannelNickChangedTimer(const QString& channel) { if(!m_channelNickChangedTimer->isActive()) m_channelNickChangedTimer->start(); m_changedChannels.append(channel); } void Server::sendChannelNickChangedSignals() { foreach(const QString& channel, m_changedChannels) { if (m_joinedChannels.contains (channel)) { emit channelNickChanged(channel); foreach(ChannelNickPtr nick, (*m_joinedChannels[channel])) { if(nick->isChanged()) { nick->setChanged(false); } } } } m_changedChannels.clear(); } void Server::involuntaryQuit() { if((m_connectionState == Konversation::SSConnected || m_connectionState == Konversation::SSConnecting) && (m_socket->peerAddress() != QHostAddress(QHostAddress::LocalHost) && m_socket->peerAddress() != QHostAddress(QHostAddress::LocalHostIPv6))) { quitServer(); updateConnectionState(Konversation::SSInvoluntarilyDisconnected); } } void Server::reconnectInvoluntary() { if(m_connectionState == Konversation::SSInvoluntarilyDisconnected) reconnectServer(); } void Server::initCapablityNames() { m_capabilityNames.insert(QStringLiteral("away-notify"), AwayNotify); m_capabilityNames.insert(QStringLiteral("extended-join"), ExtendedJoin); m_capabilityNames.insert(QStringLiteral("server-time"), ServerTime); m_capabilityNames.insert(QStringLiteral("znc.in/server-time-iso"), ServerTime); m_capabilityNames.insert(QStringLiteral("userhost-in-names"), UserHostInNames); m_capabilityNames.insert(QStringLiteral("sasl"), SASL); m_capabilityNames.insert(QStringLiteral("multi-prefix"), MultiPrefix); m_capabilityNames.insert(QStringLiteral("account-notify"), AccountNotify); m_capabilityNames.insert(QStringLiteral("znc.in/self-message"), SelfMessage); } // 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: diff --git a/src/irc/server.h b/src/irc/server.h index d856681e..c2f98dfc 100644 --- a/src/irc/server.h +++ b/src/irc/server.h @@ -1,883 +1,886 @@ /* 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-2016 Peter Simonsson Copyright (C) 2006-2008 Eli J. MacKenzie Copyright (C) 2005-2008 Eike Hein */ #ifndef SERVER_H #define SERVER_H #include "common.h" #include "channelnick.h" #include "inputfilter.h" #include "outputfilter.h" #include "nickinfo.h" #include "serversettings.h" #include "servergroupsettings.h" #include "connectionsettings.h" #include "statuspanel.h" #include "invitedialog.h" #include #ifdef HAVE_QCA2 #include "cipher.h" #endif #include #include #include #include #include #include #include class QAbstractItemModel; class QStringListModel; class Channel; class Query; class Identity; class RawLog; class ChannelListPanel; class ServerISON; class ChatWindow; class ViewContainer; class IRCQueue; namespace Konversation { namespace DCC { class Transfer; class Chat; } } class Server : public QObject { Q_OBJECT friend class IRCQueue; friend class QueueTuner; public: enum QueuePriority { LowPriority, /// &messageTags); void renameNick(const QString &nickname, const QString &newNick, const QHash &messageTags); Channel* removeNickFromChannel(const QString &channelName, const QString &nickname, const QString &reason, const QHash &messageTags, bool quit=false); void nickWasKickedFromChannel(const QString &channelName, const QString &nickname, const QString &kicker, const QString &reason, const QHash &messageTags); void removeNickFromServer(const QString &nickname, const QString &reason, const QHash &messageTags); void setChannelTypes(const QString &types); QString getChannelTypes() const; void setModesCount(int count); int getModesCount(); // extended user modes support void setChanModes(const QString&); //grab modes types from RPL_ISUPPORT CHANMODES QString banAddressListModes() { return m_banAddressListModes; } // aka "TYPE A" modes http://tools.ietf.org/html/draft-brocklesby-irc-isupport-03#section-3.3 void setPrefixes(const QString &modes, const QString& prefixes); QString getServerNickPrefixes() const; void mangleNicknameWithModes(QString &nickname,bool& isAdmin,bool& isOwner,bool &isOp, bool& isHalfop,bool &hasVoice); bool isAChannel(const QString &channel) const; bool isNickname(const QString& compare) const; QString getNickname() const; QString loweredNickname() const; QString getNextNickname(); InputFilter* getInputFilter() { return &m_inputFilter; } Konversation::OutputFilter* getOutputFilter() { return m_outputFilter; } Channel* joinChannel(const QString& name, const QString& hostmask, const QHash &messageTags); void removeChannel(Channel* channel); void appendServerMessageToChannel(const QString& channel, const QString& type, const QString& message, const QHash &messageTags); void appendCommandMessageToChannel(const QString& channel, const QString& command, const QString& message, const QHash &messageTags, bool highlight = true, bool parseURL = true); void appendStatusMessage(const QString& type,const QString& message, const QHash &messageTags); void appendMessageToFrontmost(const QString& type,const QString& message, const QHash &messageTags = QHash(), bool parseURL = true); int getPreLength(const QString& command, const QString& dest); void dbusRaw(const QString& command); void dbusSay(const QString& target,const QString& command); void dbusInfo(const QString& string); void ctcpReply(const QString& receiver, const QString& text); void setChannelTopic(const QString& channel, const QString& topic, const QHash &messageTags); // Overloaded void setChannelTopic(const QString& nickname, const QString& channel, const QString& topic, const QHash &messageTags); void updateChannelMode(const QString& nick, const QString& channel, char mode, bool plus, const QString& parameter, const QHash &messageTags); void updateChannelModeWidgets(const QString& channel, char mode, const QString& parameter); Channel* getChannelByName(const QString& name); Query* getQueryByName(const QString& name); ChatWindow* getChannelOrQueryByName(const QString& name); QString parseWildcards(const QString& toParse, ChatWindow* context = 0, const QStringList &nicks = QStringList()); QString parseWildcards(const QString& toParse, const QString& nickname, const QString& channelName, const QString &channelKey, const QStringList &nickList, const QString& inputLineText); QString parseWildcards(const QString& toParse, const QString& nickname, const QString& channelName, const QString &channelKey, const QString& nick, const QString& inputLineText); void autoCommandsAndChannels(); void sendURIs(const QList& uris, const QString& nick); void notifyAction(const QString& nick); ChannelListPanel* getChannelListPanel() const; StatusPanel* getStatusView() const { return m_statusView; } virtual bool closeYourself(bool askForConfirmation=true); QString getOwnIpByNetworkInterface(); QString getOwnIpByServerMessage(); bool isAway() { return m_away; } void setAway(bool away, const QHash &messageTags); QString awayTime() const; void setAwayReason(const QString& reason) { m_awayReason = reason; } /** * Returns true if the given nickname is known to be online. * @param nickname The nickname. Case insensitive. * @return True if the nickname is known to be online by the server. * Note that a nick that is not in any of the joined channels and is not on the * notify list, and has not initiated a query with you, may well be online, * but server doesn't know if it is or not, in which case False is returned. */ bool isNickOnline(const QString &nickname); /** Given a nickname, returns NickInfo object. * @param nickname The desired nickname. Case insensitive. * @return Pointer to the nickinfo for this nickname if one exists. * 0 if not known to be online. * * A NickInfo pointer will only be returned if the nickname is known to the Konvi * Server object. A nick will be known if: * - It is in one of the server's channels user has joined. * - It is on the notify list and is known to be online. * - The nick initiated a query with the user. * A NickInfo is destroyed when it is offline. */ NickInfoPtr getNickInfo(const QString& nickname); /** Given a nickname, returns an existing NickInfo object, or creates a new NickInfo object. * Guaranteed to return a nickinfo. * @param nickname The desired nickname. Case sensitive. * @return Pointer to the found or created NickInfo object. */ NickInfoPtr obtainNickInfo(const QString& nickname); /** Returns a list of all the NickInfos that are online and known to the server. * Caller should not modify the list. * A nick will be known if: * - It is in one of the server's channels user has joined. * - It is on the notify list and is known to be online. * - The nick initiated a query with the user. * * @return A QMap of NickInfoPtrs indexed by lowercase nickname. */ const NickInfoMap* getAllNicks(); /** Returns the list of members for a channel in the joinedChannels list. * A joinedChannel is one that you are in, as opposed to a channel that you aren't in, * but one of your watched nicks is in. * Code that calls this must not modify the list. * @param channelName Name of desired channel. Case insensitive. * @return A map of all the nicks in the channel. * 0 if channel is not in the joinedChannels list. */ const ChannelNickMap *getJoinedChannelMembers(const QString& channelName) const; /** Returns the list of members for a channel in the unjoinedChannels list. * An unjoinedChannel is a channel you aren't in. As such, this is only going to return * nicks that you know are in that channel because a /whois has been done against them. * This could be done automatically if they are on the watch list. * Code that calls this must not modify the list. * @param channelName Name of desired channel. Case insensitive. * @return A map of only the nicks that we know that are in the channel. * 0 if channel is not in the unjoinedChannels list. */ const ChannelNickMap *getUnjoinedChannelMembers(const QString& channelName) const; /** Searches the Joined and Unjoined lists for the given channel and returns the member list. * Code that calls this must not modify the list. * @param channelName Name of desired channel. Case insensitive. * @return A map of nicks in that channel. 0 if channel is not in either list. * * @see getJoinedChannelMembers(const QString& channelName) * @see getUnjoinedChannelMembers(const QString& channelName) */ const ChannelNickMap *getChannelMembers(const QString& channelName) const; /** Returns a list of all the joined channels that a nick is in. * @param nickname The desired nickname. Case insensitive. * @return A list of joined channels the nick is in. Empty if none. */ QStringList getNickJoinedChannels(const QString& nickname); /** Returns a list of all the channels (joined or unjoined) that a nick is in. * @param nickname The desired nickname. Case insensitive. * @return A list of channels the nick is in. Empty if none. * * A nick will not appear in the Unjoined channels list unless a WHOIS * has been performed on it. */ QStringList getNickChannels(const QString& nickname); /** Returns a list of all the channels we're in that nickname is also in. * @param nickname The desired nickname. Case insensitive. * @return A list of channels the nick is in that we're also in. Empty if none. */ QStringList getSharedChannels(const QString& nickname); /** Returns pointer to the ChannelNick (mode and pointer to NickInfo) for a * given channel and nickname. * @param channelName The desired channel name. Case insensitive. * @param nickname The desired nickname. Case insensitive. * @return Pointer to ChannelNick structure containing a pointer * to the NickInfo and the mode of the nick in the channel. * 0 if not found. */ ChannelNickPtr getChannelNick(const QString& channelName, const QString& nickname); /** 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. * @param channelName The channel name. Case sensitive. * @param nickname The nickname. Case sensitive. * @param mode Bit mask containing the modes the nick has in the channel, * or 99 if not known. See channelnick.cpp for bit definitions. */ ChannelNickPtr setChannelNick(const QString& channelName, const QString& nickname, unsigned int mode = 99); /** * Returns a QList of all channels */ const QList& getChannelList() const { return m_channelList; } /** * Returns a lower case list of all the nicks on the user watch list. */ QStringList getWatchList(); /** * Return true if the given nickname is on the watch list. */ bool isWatchedNick(const QString& nickname); /** * Returns a list of all the nicks on the watch list that are not in joined * channels. ISON command is sent for these nicks. */ QStringList getISONList(); QString getISONListString(); ViewContainer* getViewContainer() const; /** 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. * If needed, moves the nickname from the Offline to Online lists. * If mode != 99 sets the mode for this nick in this channel. * @param channelName The channel name. Case sensitive. * @param nickname The nickname. Case sensitive. * @return The NickInfo for the nickname. */ ChannelNickPtr addNickToJoinedChannelsList(const QString& channelName, const QString& nickname); void setAllowedChannelModes(const QString& modes) { m_allowedChannelModes = modes; } QString allowedChannelModes() const { return m_allowedChannelModes; } void setTopicLength(int topicLength) { m_topicLength = topicLength; } int topicLength() const { return m_topicLength; } void registerWithServices(); // Blowfish stuff QByteArray getKeyForRecipient(const QString& recipient) const; void setKeyForRecipient(const QString& recipient, const QByteArray& key); bool identifyMsg() const { return m_identifyMsg; } QString getLastAuthenticateCommand() const { return m_lastAuthenticateCommand; } ChannelListPanel* addChannelListPanel(); // invoked by DCC::TransferSend void dccSendRequest(const QString& recipient,const QString& fileName,const QString& address,quint16 port,quint64 size); void dccPassiveSendRequest(const QString& recipient,const QString& fileName,const QString& address,quint64 size,const QString& token); // invoked by DCC::TransferRecv void dccPassiveResumeGetRequest(const QString& sender,const QString& fileName,quint16 port,KIO::filesize_t startAt,const QString &token); void dccResumeGetRequest(const QString& sender,const QString& fileName,quint16 port,KIO::filesize_t startAt); void dccReverseSendAck(const QString& partnerNick,const QString& fileName,const QString& ownAddress,quint16 ownPort,quint64 size,const QString& reverseToken); void dccRejectSend(const QString& partnerNick, const QString& fileName); // invoked by DCC::Chat void dccRejectChat(const QString& partnerNick, const QString& extension); void dccPassiveChatRequest(const QString& recipient, const QString& extension, const QString& address, const QString& token); void dccReverseChatAck(const QString& partnerNick, const QString& extension, const QString& ownAddress, quint16 ownPort, const QString& reverseToken); bool capEndDelayed() const { return m_capEndDelayed; } void setHasWHOX(bool state) { m_capabilities.setFlag(WHOX, state); } CapabilityFlags capabilities() const { return m_capabilities; } + bool whoRequestsDisabled() const { return m_whoRequestsDisabled; } // IRCQueueManager bool validQueue(QueuePriority priority); ///< is this queue index valid? void resetQueues(); ///< Tell all of the queues to reset /** Forces the queued data to be sent in sequence of age, without pause. This could flood you off but since you're quitting, we probably don't care. This is done here instead of in the queues themselves so we can interleave the queues without having to zip the queues together. If you want to quit the server normally without sending, reset the queues first. */ void flushQueues(); //These are really only here to limit where ircqueue.h is included Q_SIGNALS: void destroyed(int connectionId); void nicknameChanged(const QString&); void serverLag(Server* server,int msec); /// will be connected to KonversationMainWindow::updateLag() void tooLongLag(Server* server, int msec);/// will be connected to KonversationMainWindow::updateLag() void resetLag(Server* server); ///< will be emitted when new 303 came in void nicksNowOnline(Server* server,const QStringList& list,bool changed); void awayState(bool away); /// will be connected to any user input panel; void multiServerCommand(const QString& command, const QString& parameter); /** * Emitted when the server gains/loses connection. * Will be connected to all server dependant tabs. */ void serverOnline(bool state); /** * Emitted every time something gets sent. * * @param bytes The count of bytes sent to the server, before re-encoding. * @param encodedBytes The count of bytes sent to the server after re-encoding. */ void sentStat(int bytes, int encodedBytes, IRCQueue *whichQueue); //Note that these signals haven't been implemented yet. /// Fires when the information in a NickInfo object changes. void nickInfoChanged(Server* server, const NickInfoPtr nickInfo); /// Emitted once if one or more NickInfo has been changed. void nickInfoChanged(); /// Emitted once if one or more ChannelNick has been changed in @p channel. void channelNickChanged(const QString& channel); /// Fires when a nick leaves or joins a channel. Based on joined flag, receiver could /// call getJoinedChannelMembers or getUnjoinedChannelMembers, or just /// getChannelMembers to get a list of all the nicks now in the channel. /// parted indicates whether the nick joined or left the channel. void channelMembersChanged(Server* server, const QString& channelName, bool joined, bool parted, const QString& nickname); /// Fires when a channel is moved to/from the Joinied/Unjoined lists. /// joined indicates which list it is now on. Note that if joined is False, it is /// possible the channel does not exist in any list anymore. void channelJoinedOrUnjoined(Server* server, const QString& channelName, bool joined); /// Fires when a nick on the watch list goes online or offline. void watchedNickChanged(Server* server, const QString& nickname, bool online); ///Fires when the user switches his state to away and has enabled "Insert Remember Line on away" in his identity. void awayInsertRememberLine(Server* server); void sslInitFailure(); void sslConnected(Server* server); void connectionStateChanged(Server* server, Konversation::ConnectionState state); void showView(ChatWindow* view); void addDccPanel(); void addDccChat(Konversation::DCC::Chat *chat); public Q_SLOTS: void connectToIRCServer(); void connectToIRCServerIn(uint delay); /** Adds line to queue if non-empty. */ bool queue(const QString& line, QueuePriority priority=StandardPriority); //TODO this should be an overload, not a separate name. ambiguous cases need QString() around the cstring bool queueList(const QStringList& buffer, QueuePriority priority=StandardPriority); void setNickname(const QString &newNickname); /** This is called when we want to open a new query, or focus an existing one. * @param nickInfo The nickinfo we want to open the query to. Must exist. * @param weinitiated This is whether we initiated this - did we do /query, or somebody else sending us a message. * @return A pointer to a new or already-existing query. Guaranteed to be non-null */ Query *addQuery(const NickInfoPtr & nickInfo, bool weinitiated); void closeQuery(const QString &name); void closeChannel(const QString &name); void reconnectServer(const QString& quitMessage = QString()); void disconnectServer(const QString& quitMessage = QString()); void quitServer(const QString& quitMessage = QString()); void openDccChat(const QString& nickname); void openDccWBoard(const QString& nickname); void requestDccChat(const QString& partnerNick, const QString& extension, const QString& numericalOwnIp, quint16 ownPort); void acceptDccGet(const QString& nick, const QString& file); void requestBan(const QStringList& users,const QString& channel,const QString& option); void requestUnban(const QString& mask,const QString& channel); void addDccSend(const QString &recipient, const QUrl &fileURL, bool passive = Preferences::self()->dccPassiveSend(), const QString &altFileName = QString(), quint64 fileSize = 0); void removeQuery(Query *query); void notifyListStarted(int serverGroupId); void startNotifyTimer(int msec=0); void notifyTimeout(); void sendJoinCommand(const QString& channelName, const QString& password = QString()); void requestAway(const QString& reason = QString()); void requestUnaway(); void requestChannelList(); void requestWhois(const QString& nickname); void requestWho(const QString& channel); void requestUserhost(const QString& nicks); void requestTopic(const QString& channel); void resolveUserhost(const QString& nickname); void addRawLog(bool show); void closeRawLog(); void addToChannelList(const QString& channel, int users, const QString& topic); void closeChannelListPanel(); void sendMultiServerCommand(const QString& command, const QString& parameter); void executeMultiServerCommand(const QString& command, const QString& parameter); void showSSLDialog(); void sendToAllChannels(const QString& text); void sendToAllChannelsAndQueries(const QString& text); void enableIdentifyMsg(bool enabled); bool identifyMsgEnabled(); void addBan(const QString &channel, const QString &ban); void removeBan(const QString &channel, const QString &ban); /// Called when we received a PONG from the server void pongReceived(); #ifdef HAVE_QCA2 void initKeyExchange(const QString &receiver); void parseInitKeyX(const QString &sender, const QString &pubKey); void parseFinishKeyX(const QString &sender, const QString &pubKey); #endif /// Start the NickInfo changed timer if it isn't started already void startNickInfoChangedTimer(); /// Start the ChannelNick changed timer if it isn't started already void startChannelNickChangedTimer(const QString& channel); /// Called when the system wants to close the connection due to network going down etc. void involuntaryQuit(); /// Will only reconnect if the connection state is involuntary disconnected. void reconnectInvoluntary(); void requestAvailableCapabilies (); void capInitiateNegotiation(const QString &availableCaps); void capReply(); void capEndNegotiation(); void capCheckIgnored(); void capAcknowledged(const QString& name, CapModifiers modifiers); void capDenied(const QString& name); void sendAuthenticate(const QString& message); protected Q_SLOTS: void hostFound(); void preShellCommandExited(int exitCode, QProcess::ExitStatus exitStatus); void preShellCommandError(QProcess::ProcessError eror); void socketConnected(); void startAwayTimer(); void incoming(); void processIncomingData(); /// Sends the QString to the socket. No longer has any internal concept of queueing void toServer(QString&, IRCQueue *); /// Because KBufferedSocket has no closed(int) signal we use this slot to call broken(0) void closed(); void broken(QAbstractSocket::SocketError error); /** This is connected to the SSLSocket failed. * @param reason The reason why this failed. This is already translated, ready to show the user. */ void sslError(const QList&); void connectionEstablished(const QString& ownHost); void notifyResponse(const QString& nicksOnline); void slotNewDccTransferItemQueued(Konversation::DCC::Transfer* transfer); void startReverseDccSendTransfer(const QString& sourceNick,const QStringList& dccArguments); void startReverseDccChat(const QString &sourceNick, const QStringList &dccArgument); void addDccGet(const QString& sourceNick,const QStringList& dccArguments); void requestDccSend(); // -> to outputFilter, dccPanel // -> to outputFilter void requestDccSend(const QString& recipient); // -> to inputFilter void resumeDccGetTransfer(const QString& sourceNick,const QStringList& dccArguments); // -> to inputFilter void resumeDccSendTransfer(const QString& sourceNick,const QStringList& dccArguments); void rejectDccSendTransfer(const QString& sourceNick,const QStringList& dccArguments); void dccGetDone(Konversation::DCC::Transfer* item); void dccSendDone(Konversation::DCC::Transfer* item); void dccStatusChanged(Konversation::DCC::Transfer* item, int newStatus, int oldStatus); void addDccChat(const QString& sourceNick,const QStringList& arguments); void rejectDccChat(const QString& sourceNick); void scriptNotFound(const QString& name); void scriptExecutionError(const QString& name); void userhost(const QString& nick,const QString& hostmask,bool away,bool ircOp); void setTopicAuthor(const QString& channel,const QString& author, const QDateTime &t); void endOfWho(const QString& target); void endOfNames(const QString& target); void invitation(const QString& nick,const QString& channel); void gotOwnResolvedHostByWelcome(const QHostInfo& res); void gotOwnResolvedHostByUserhost(const QHostInfo& res); /// Send a PING to the server so we can meassure the lag void sendPing(); /// Updates GUI when the lag gets high void updateLongPongLag(); /// Update the encoding shown in the mainwindow's actions void updateEncoding(); /** Called when the NickInfo changed timer times out. * Emits the nickInfoChanged() signal for all changed NickInfos */ void sendNickInfoChangedSignals(); /** Called when the ChannelNick changed timer times out. * Emits the channelNickChanged() signal for each channel with changed nicks. */ void sendChannelNickChangedSignals(); void requestOpenChannelListPanel(const QString& filter); private Q_SLOTS: /** Called in the server constructor if the preferences are set to run a command on a new server instance. * This sets up the kprocess, runs it, and connects the signals to call preShellCommandExited when done. */ void doPreShellCommand(); protected: // constants static const int BUFFER_LEN=513; /// Initialize the timers void initTimers(); /// Connect to the signals used in this class. void connectSignals(); int _send_internal(QString outputline); ///< Guts of old send, isn't a slot. /** 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 needed, moves the nickname from the Offline to the Online list. * If mode != 99 sets the mode for this nick in this channel. * @param channelName The channel name. Case sensitive. * @param nickname The nickname. Case sensitive. * @return The NickInfo for the nickname. */ ChannelNickPtr addNickToUnjoinedChannelsList(const QString& channelName, const QString& nickname); /** * 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 setWatchedNickOnline(const QString& nickname); /** * Display offline notification for a certain nickname. The function doesn't change NickInfo objects. * @param nickname The nickname that is offline * @param nickInfo Pointer to NickInfo for nick */ void setWatchedNickOffline(const QString& nickname, const NickInfoPtr &nickInfo); /** * 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 deleteNickIfUnlisted(const QString &nickname); /** * If not already offline, changes a nick to the offline state. * Removes it from all channels on the joined and unjoined lists. * If the nick is in the watch list, and went offline, emits a signal, * posts a Notify message, and posts a KNotify. * If the nick goes offline, the NickInfo is deleted. * * @param nickname The nickname. Case sensitive. * @return True if the nick was online. */ bool setNickOffline(const QString& nickname); /** Remove nickname from a channel (on joined or unjoined lists). * @param channelName The channel name. Case insensitive. * @param nickname The nickname. Case insensitive. */ void removeChannelNick(const QString& channelName, const QString& nickname); /** Remove channel from the joined list. * Nicknames in the channel are added to the unjoined list if they are in the watch list. * @param channelName The channel name. Case insensitive. */ void removeJoinedChannel(const QString& channelName); /** Renames a nickname in all NickInfo lists. * @param nickInfo Pointer to existing NickInfo object. * @param newname New nickname for the nick. Case sensitive. */ void renameNickInfo(NickInfoPtr nickInfo, const QString& newname); bool getAutoJoin() const; void setAutoJoin(bool on); QStringList getAutoJoinCommands() const { return m_autoJoinCommands; } void setAutoJoinCommands(const QStringList& commands) { m_autoJoinCommands = commands; } unsigned int m_completeQueryPosition; QList m_nickIndices; QStringList m_referenceNicklist; QStringListModel* m_nickListModel; // TODO roll these into a QMap. QString m_serverNickPrefixes; ///< Nickname prefixes used by the server to indicate a mode QString m_serverNickPrefixModes; ///< if supplied: mode flags related to nickname prefixes QRegularExpression m_targetMatcher; ///< a character set composed of m_serverNickPrefixes and m_channelPrefixes void rebuildTargetPrefixMatcher(); ///< updates the regexp when prefixes change QString m_banAddressListModes; // "TYPE A" modes from RPL_ISUPPORT CHANMODES=A,B,C,D QString m_channelPrefixes; // prefixes that indicate channel names. defaults to RFC1459 "#&" int m_modesCount; // Maximum number of channel modes with parameter allowed per MODE command. bool m_autoJoin; QStringList m_autoJoinCommands; QSslSocket *m_socket; QTimer m_incomingTimer; QTimer m_notifyTimer; QStringList m_notifyCache; // List of users found with ISON int m_checkTime; // Time elapsed while waiting for server 303 response int m_currentLag; QStringList m_inputBuffer; QList m_queues; // Stats used in QueueTuner int m_bytesSent, m_encodedBytesSent, m_linesSent, m_bytesReceived; QString m_nickname; QString m_loweredNickname; QString m_ownIpByUserhost; // RPL_USERHOST QString m_ownIpByWelcome; // RPL_WELCOME QList m_channelList; QHash m_loweredChannelNameHash; QList m_queryList; InputFilter m_inputFilter; Konversation::OutputFilter* m_outputFilter; QPointer m_statusView; QPointer m_rawLog; QPointer m_channelListPanel; bool m_away; QString m_awayReason; QString m_nonAwayNick; int m_awayTime; Konversation::ConnectionState m_connectionState; void updateConnectionState(Konversation::ConnectionState state); bool isSocketConnected() const; KProcess m_preShellCommand; private: void purgeData(); /// Recovers the filename from the dccArguments list from pos 0 to size-offset-1 /// joining with a space and cleans the filename using cleanDccFileName. /// The filename only needs to be recovered if it contains a space, in case /// it does not, the cleaned string at pos 0 is returned. /// "offset" states how many fixed arguments the dcc command has, where the /// filename is variable. For example "filename ip port filesize", offset is 3. inline QString recoverDccFileName(const QStringList& dccArguments, int offset) const; /// Cleans the filename from extra '"'. We just remove '"' if it is the first /// and last char, if the filename really contains a '"' it comes as two chars, /// escaped "\"", and is not affected. /// Some clients return the filename with multiple '"' around the filename /// but all we want is the plain filename. inline QString cleanDccFileName(const QString& filename) const; /// Checks if the port is in a valid range inline quint16 stringToPort(const QString &port, bool *ok = 0); /// Creates a list of known users and returns the one chosen by the user inline QString recipientNick() const; void collectStats(int bytes, int encodedBytes); void initCapablityNames(); /// Helper object to construct ISON (notify) list. ServerISON* m_serverISON; /// All nicks known to this server. Note this is NOT a list of all nicks on the server. /// Any nick appearing in this list is online, but may not necessarily appear in /// any of the joined or unjoined channel lists because a WHOIS has not yet been /// performed on the nick. NickInfoMap m_allNicks; /// List of membership lists for joined channels. A "joined" channel is a channel /// that user has joined, i.e., a tab appears for the channel in the main window. ChannelMembershipMap m_joinedChannels; /// List of membership lists for unjoined channels. These come from WHOIS responses. /// Note that this is NOT a list of all channels on the server, just those we are /// interested in because of nicks in the Nick Watch List. ChannelMembershipMap m_unjoinedChannels; /// List of nicks in Queries. NickInfoMap m_queryNicks; QString m_allowedChannelModes; int m_topicLength; // Blowfish key map QHash m_keyHash; bool m_identifyMsg; bool m_sslErrorLock; /// Used to lock incomingTimer while processing message. bool m_processingIncoming; /// Measures the lag between PING and PONG QTime m_lagTime; /// Updates the gui when the lag gets too high QTimer m_pingResponseTimer; /// Wait before sending the next PING QTimer m_pingSendTimer; /// Previous ISON reply of the server, needed for comparison with the next reply QStringList m_prevISONList; int m_capRequested; int m_capAnswered; bool m_capEndDelayed; QString m_lastAuthenticateCommand; ConnectionSettings m_connectionSettings; /// Used by ConnectionManager to schedule a reconnect; stopped by /disconnect /// and /quit. QTimer* m_delayedConnectTimer; bool m_reconnectImmediately; static int m_availableConnectionId; int m_connectionId; QPointer m_inviteDialog; QTimer* m_nickInfoChangedTimer; QTimer* m_channelNickChangedTimer; QStringList m_changedChannels; bool m_recreationScheduled; CapabilityFlags m_capabilities; QHash m_capabilityNames; + + bool m_whoRequestsDisabled; }; Q_DECLARE_OPERATORS_FOR_FLAGS(Server::CapModifiers) Q_DECLARE_OPERATORS_FOR_FLAGS(Server::CapabilityFlags) #endif