+
+void CoreNetwork::serverCapRemoved(const QString &capability)
+{
+ // This may be called multiple times in certain situations.
+
+ // Handle special cases here
+ if (capability == IrcCap::AWAY_NOTIFY) {
+ // away-notify disabled, enable autoWho according to configuration
+ setAutoWhoEnabled(networkConfig()->autoWhoEnabled());
+ }
+}
+
+void CoreNetwork::queueCap(const QString &capability)
+{
+ // IRCv3 specs all use lowercase capability names
+ QString _capLowercase = capability.toLower();
+ if (!_capsQueued.contains(_capLowercase)) {
+ _capsQueued.append(_capLowercase);
+ }
+}
+
+QString CoreNetwork::takeQueuedCap()
+{
+ if (!_capsQueued.empty()) {
+ return _capsQueued.takeFirst();
+ } else {
+ return QString();
+ }
+}
+
+void CoreNetwork::beginCapNegotiation()
+{
+ // Don't begin negotiation if no capabilities are queued to request
+ if (!capNegotiationInProgress())
+ return;
+
+ _capNegotiationActive = true;
+ displayMsg(Message::Server, BufferInfo::StatusBuffer, "",
+ tr("Ready to negotiate (found: %1)").arg(caps().join(", ")));
+ displayMsg(Message::Server, BufferInfo::StatusBuffer, "",
+ tr("Negotiating capabilities (requesting: %1)...").arg(_capsQueued.join(", ")));
+ sendNextCap();
+}
+
+void CoreNetwork::sendNextCap()
+{
+ if (capNegotiationInProgress()) {
+ // Request the next capability and remove it from the list
+ // Handle one at a time so one capability failing won't NAK all of 'em
+ putRawLine(serverEncode(QString("CAP REQ :%1").arg(takeQueuedCap())));
+ } else {
+ // No pending desired capabilities, capability negotiation finished
+ // If SASL requested but not available, print a warning
+ if (networkInfo().useSasl && !capEnabled(IrcCap::SASL))
+ displayMsg(Message::Error, BufferInfo::StatusBuffer, "",
+ tr("SASL authentication currently not supported by server"));
+
+ if (_capNegotiationActive) {
+ displayMsg(Message::Server, BufferInfo::StatusBuffer, "",
+ tr("Capability negotiation finished (enabled: %1)").arg(capsEnabled().join(", ")));
+ _capNegotiationActive = false;
+ }
+
+ // If nick registration is already complete, CAP END is not required
+ if (!_capInitialNegotiationEnded) {
+ putRawLine(serverEncode(QString("CAP END")));
+ _capInitialNegotiationEnded = true;
+ }
+ }
+}
+
+/******** AutoWHO ********/
+
+void CoreNetwork::startAutoWhoCycle()
+{
+ if (!_autoWhoQueue.isEmpty()) {
+ _autoWhoCycleTimer.stop();
+ return;
+ }
+ _autoWhoQueue = channels();
+}
+
+void CoreNetwork::queueAutoWhoOneshot(const QString &channelOrNick)
+{
+ // Prepend so these new channels/nicks are the first to be checked
+ // Don't allow duplicates
+ if (!_autoWhoQueue.contains(channelOrNick.toLower())) {
+ _autoWhoQueue.prepend(channelOrNick.toLower());
+ }
+ if (capEnabled(IrcCap::AWAY_NOTIFY)) {
+ // When away-notify is active, the timer's stopped. Start a new cycle to who this channel.
+ setAutoWhoEnabled(true);
+ }
+}
+
+
+void CoreNetwork::setAutoWhoDelay(int delay)
+{
+ _autoWhoTimer.setInterval(delay * 1000);
+}
+
+
+void CoreNetwork::setAutoWhoInterval(int interval)
+{
+ _autoWhoCycleTimer.setInterval(interval * 1000);
+}
+
+
+void CoreNetwork::setAutoWhoEnabled(bool enabled)
+{
+ if (enabled && isConnected() && !_autoWhoTimer.isActive())
+ _autoWhoTimer.start();
+ else if (!enabled) {
+ _autoWhoTimer.stop();
+ _autoWhoCycleTimer.stop();
+ }
+}
+
+
+void CoreNetwork::sendAutoWho()
+{
+ // Don't send autowho if there are still some pending
+ if (_autoWhoPending.count())
+ return;
+
+ while (!_autoWhoQueue.isEmpty()) {
+ QString chanOrNick = _autoWhoQueue.takeFirst();
+ // Check if it's a known channel or nick
+ IrcChannel *ircchan = ircChannel(chanOrNick);
+ IrcUser *ircuser = ircUser(chanOrNick);
+ if (ircchan) {
+ // Apply channel limiting rules
+ // If using away-notify, don't impose channel size limits in order to capture away
+ // state of everyone. Auto-who won't run on a timer so network impact is minimal.
+ if (networkConfig()->autoWhoNickLimit() > 0
+ && ircchan->ircUsers().count() >= networkConfig()->autoWhoNickLimit()
+ && !capEnabled(IrcCap::AWAY_NOTIFY))
+ continue;
+ _autoWhoPending[chanOrNick.toLower()]++;
+ } else if (ircuser) {
+ // Checking a nick, add it to the pending list
+ _autoWhoPending[ircuser->nick().toLower()]++;
+ } else {
+ // Not a channel or a nick, skip it
+ qDebug() << "Skipping who polling of unknown channel or nick" << chanOrNick;
+ continue;
+ }
+ if (supports("WHOX")) {
+ // Use WHO extended to poll away users and/or user accounts
+ // See http://faerion.sourceforge.net/doc/irc/whox.var
+ // And https://github.com/hexchat/hexchat/blob/c874a9525c9b66f1d5ddcf6c4107d046eba7e2c5/src/common/proto-irc.c#L750
+ putRawLine(serverEncode(QString("WHO %1 %%chtsunfra,%2")
+ .arg(serverEncode(chanOrNick), QString::number(IrcCap::ACCOUNT_NOTIFY_WHOX_NUM))));
+ } else {
+ putRawLine(serverEncode(QString("WHO %1").arg(chanOrNick)));
+ }
+ break;
+ }
+
+ if (_autoWhoQueue.isEmpty() && networkConfig()->autoWhoEnabled() && !_autoWhoCycleTimer.isActive()
+ && !capEnabled(IrcCap::AWAY_NOTIFY)) {
+ // Timer was stopped, means a new cycle is due immediately
+ // Don't run a new cycle if using away-notify; server will notify as appropriate
+ _autoWhoCycleTimer.start();
+ startAutoWhoCycle();
+ } else if (capEnabled(IrcCap::AWAY_NOTIFY) && _autoWhoCycleTimer.isActive()) {
+ // Don't run another who cycle if away-notify is enabled
+ _autoWhoCycleTimer.stop();
+ }
+}
+
+
+#ifdef HAVE_SSL
+void CoreNetwork::sslErrors(const QList<QSslError> &sslErrors)
+{
+ Q_UNUSED(sslErrors)
+ socket.ignoreSslErrors();
+ // TODO errorhandling
+}
+
+