diff options
Diffstat (limited to 'Swift/Controllers/Chat')
| -rw-r--r-- | Swift/Controllers/Chat/ChatController.cpp | 2 | ||||
| -rw-r--r-- | Swift/Controllers/Chat/MUCController.cpp | 4 |
2 files changed, 3 insertions, 3 deletions
diff --git a/Swift/Controllers/Chat/ChatController.cpp b/Swift/Controllers/Chat/ChatController.cpp index 65d65a6..2367761 100644 --- a/Swift/Controllers/Chat/ChatController.cpp +++ b/Swift/Controllers/Chat/ChatController.cpp @@ -1,514 +1,514 @@ /* * Copyright (c) 2010-2014 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ #include <Swift/Controllers/Chat/ChatController.h> #include <boost/bind.hpp> #include <boost/smart_ptr/make_shared.hpp> #include <stdio.h> #include <Swiften/Avatars/AvatarManager.h> #include <Swiften/Base/Algorithm.h> #include <Swiften/Base/DateTime.h> #include <Swiften/Base/foreach.h> #include <Swiften/Base/format.h> #include <Swiften/Base/Log.h> #include <Swiften/Chat/ChatStateNotifier.h> #include <Swiften/Chat/ChatStateTracker.h> #include <Swiften/Client/ClientBlockListManager.h> #include <Swiften/Client/NickResolver.h> #include <Swiften/Client/StanzaChannel.h> #include <Swiften/Disco/EntityCapsProvider.h> #include <Swiften/Elements/DeliveryReceipt.h> #include <Swiften/Elements/DeliveryReceiptRequest.h> #include <Swiften/Elements/Idle.h> #include <Swiften/FileTransfer/FileTransferManager.h> #include <Swift/Controllers/Intl.h> #include <Swift/Controllers/UIInterfaces/ChatWindowFactory.h> #include <Swift/Controllers/XMPPEvents/EventController.h> #include <Swift/Controllers/FileTransfer/FileTransferController.h> #include <Swift/Controllers/StatusUtil.h> #include <Swift/Controllers/UIEvents/UIEventStream.h> #include <Swift/Controllers/UIEvents/SendFileUIEvent.h> #include <Swift/Controllers/UIEvents/AcceptWhiteboardSessionUIEvent.h> #include <Swift/Controllers/UIEvents/CancelWhiteboardSessionUIEvent.h> #include <Swift/Controllers/UIEvents/ShowWhiteboardUIEvent.h> #include <Swift/Controllers/UIEvents/RequestChangeBlockStateUIEvent.h> #include <Swift/Controllers/UIEvents/InviteToMUCUIEvent.h> #include <Swift/Controllers/UIEvents/RequestInviteToMUCUIEvent.h> #include <Swift/Controllers/SettingConstants.h> #include <Swift/Controllers/Highlighter.h> #include <Swift/Controllers/Chat/ChatMessageParser.h> namespace Swift { /** * The controller does not gain ownership of the stanzaChannel, nor the factory. */ ChatController::ChatController(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &contact, NickResolver* nickResolver, PresenceOracle* presenceOracle, AvatarManager* avatarManager, bool isInMUC, bool useDelayForLatency, UIEventStream* eventStream, EventController* eventController, TimerFactory* timerFactory, EntityCapsProvider* entityCapsProvider, bool userWantsReceipts, SettingsProvider* settings, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager, ClientBlockListManager* clientBlockListManager, ChatMessageParser* chatMessageParser, AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider) : ChatControllerBase(self, stanzaChannel, iqRouter, chatWindowFactory, contact, presenceOracle, avatarManager, useDelayForLatency, eventStream, eventController, timerFactory, entityCapsProvider, historyController, mucRegistry, highlightManager, chatMessageParser, autoAcceptMUCInviteDecider), eventStream_(eventStream), userWantsReceipts_(userWantsReceipts), settings_(settings), clientBlockListManager_(clientBlockListManager) { isInMUC_ = isInMUC; lastWasPresence_ = false; chatStateNotifier_ = new ChatStateNotifier(stanzaChannel, contact, entityCapsProvider); chatStateTracker_ = new ChatStateTracker(); nickResolver_ = nickResolver; presenceOracle_->onPresenceChange.connect(boost::bind(&ChatController::handlePresenceChange, this, _1)); chatStateTracker_->onChatStateChange.connect(boost::bind(&ChatWindow::setContactChatState, chatWindow_, _1)); stanzaChannel_->onStanzaAcked.connect(boost::bind(&ChatController::handleStanzaAcked, this, _1)); nickResolver_->onNickChanged.connect(boost::bind(&ChatController::handleContactNickChanged, this, _1, _2)); std::string nick = nickResolver_->jidToNick(toJID_); chatWindow_->setName(nick); std::string startMessage; Presence::ref theirPresence; if (isInMUC) { startMessage = str(format(QT_TRANSLATE_NOOP("", "Starting chat with %1% in chatroom %2%")) % nick % contact.toBare().toString()); theirPresence = presenceOracle->getLastPresence(contact); } else { startMessage = str(format(QT_TRANSLATE_NOOP("", "Starting chat with %1% - %2%")) % nick % contact.toBare().toString()); theirPresence = contact.isBare() ? presenceOracle->getHighestPriorityPresence(contact.toBare()) : presenceOracle->getLastPresence(contact); } Idle::ref idle; if (theirPresence && (idle = theirPresence->getPayload<Idle>())) { startMessage += str(format(QT_TRANSLATE_NOOP("", ", who has been idle since %1%")) % dateTimeToLocalString(idle->getSince())); } startMessage += ": " + statusShowTypeToFriendlyName(theirPresence ? theirPresence->getShow() : StatusShow::None); if (theirPresence && !theirPresence->getStatus().empty()) { startMessage += " (" + theirPresence->getStatus() + ")"; } lastShownStatus_ = theirPresence ? theirPresence->getShow() : StatusShow::None; chatStateNotifier_->setContactIsOnline(theirPresence && theirPresence->getType() == Presence::Available); startMessage += "."; chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(startMessage), ChatWindow::DefaultDirection); chatWindow_->onUserTyping.connect(boost::bind(&ChatStateNotifier::setUserIsTyping, chatStateNotifier_)); chatWindow_->onUserCancelsTyping.connect(boost::bind(&ChatStateNotifier::userCancelledNewMessage, chatStateNotifier_)); chatWindow_->onFileTransferStart.connect(boost::bind(&ChatController::handleFileTransferStart, this, _1, _2)); chatWindow_->onFileTransferAccept.connect(boost::bind(&ChatController::handleFileTransferAccept, this, _1, _2)); chatWindow_->onFileTransferCancel.connect(boost::bind(&ChatController::handleFileTransferCancel, this, _1)); chatWindow_->onSendFileRequest.connect(boost::bind(&ChatController::handleSendFileRequest, this, _1)); chatWindow_->onWhiteboardSessionAccept.connect(boost::bind(&ChatController::handleWhiteboardSessionAccept, this)); chatWindow_->onWhiteboardSessionCancel.connect(boost::bind(&ChatController::handleWhiteboardSessionCancel, this)); chatWindow_->onWhiteboardWindowShow.connect(boost::bind(&ChatController::handleWhiteboardWindowShow, this)); chatWindow_->onBlockUserRequest.connect(boost::bind(&ChatController::handleBlockUserRequest, this)); chatWindow_->onUnblockUserRequest.connect(boost::bind(&ChatController::handleUnblockUserRequest, this)); chatWindow_->onInviteToChat.connect(boost::bind(&ChatController::handleInviteToChat, this, _1)); handleBareJIDCapsChanged(toJID_); settings_->onSettingChanged.connect(boost::bind(&ChatController::handleSettingChanged, this, _1)); eventStream_->onUIEvent.connect(boost::bind(&ChatController::handleUIEvent, this, _1)); } void ChatController::handleContactNickChanged(const JID& jid, const std::string& /*oldNick*/) { if (jid.toBare() == toJID_.toBare()) { chatWindow_->setName(nickResolver_->jidToNick(jid)); } } ChatController::~ChatController() { eventStream_->onUIEvent.disconnect(boost::bind(&ChatController::handleUIEvent, this, _1)); settings_->onSettingChanged.disconnect(boost::bind(&ChatController::handleSettingChanged, this, _1)); nickResolver_->onNickChanged.disconnect(boost::bind(&ChatController::handleContactNickChanged, this, _1, _2)); delete chatStateNotifier_; delete chatStateTracker_; } JID ChatController::getBaseJID() { return isInMUC_ ? toJID_ : ChatControllerBase::getBaseJID(); } void ChatController::cancelReplaces() { lastWasPresence_ = false; } void ChatController::handleBareJIDCapsChanged(const JID& /*jid*/) { DiscoInfo::ref disco = entityCapsProvider_->getCaps(toJID_); if (disco) { if (disco->hasFeature(DiscoInfo::MessageCorrectionFeature)) { chatWindow_->setCorrectionEnabled(ChatWindow::Yes); } else { chatWindow_->setCorrectionEnabled(ChatWindow::No); } if (disco->hasFeature(DiscoInfo::MessageDeliveryReceiptsFeature)) { contactSupportsReceipts_ = ChatWindow::Yes; } else { contactSupportsReceipts_ = ChatWindow::No; } if (FileTransferManager::isSupportedBy(disco)) { chatWindow_->setFileTransferEnabled(ChatWindow::Yes); } else { chatWindow_->setFileTransferEnabled(ChatWindow::No); } } else { SWIFT_LOG(debug) << "No disco info :(" << std::endl; chatWindow_->setCorrectionEnabled(ChatWindow::Maybe); contactSupportsReceipts_ = ChatWindow::Maybe; } checkForDisplayingDisplayReceiptsAlert(); } void ChatController::setToJID(const JID& jid) { chatStateNotifier_->setContact(jid); ChatControllerBase::setToJID(jid); Presence::ref presence; if (isInMUC_) { presence = presenceOracle_->getLastPresence(jid); } else { presence = jid.isBare() ? presenceOracle_->getHighestPriorityPresence(jid.toBare()) : presenceOracle_->getLastPresence(jid); } chatStateNotifier_->setContactIsOnline(presence && presence->getType() == Presence::Available); handleBareJIDCapsChanged(toJID_); } void ChatController::setAvailableServerFeatures(boost::shared_ptr<DiscoInfo> info) { ChatControllerBase::setAvailableServerFeatures(info); if (iqRouter_->isAvailable() && info->hasFeature(DiscoInfo::BlockingCommandFeature)) { boost::shared_ptr<BlockList> blockList = clientBlockListManager_->getBlockList(); blockingOnStateChangedConnection_ = blockList->onStateChanged.connect(boost::bind(&ChatController::handleBlockingStateChanged, this)); blockingOnItemAddedConnection_ = blockList->onItemAdded.connect(boost::bind(&ChatController::handleBlockingItemAdded, this, _1)); blockingOnItemRemovedConnection_ = blockList->onItemRemoved.connect(boost::bind(&ChatController::handleBlockingItemRemoved, this, _1)); handleBlockingStateChanged(); } } bool ChatController::isIncomingMessageFromMe(boost::shared_ptr<Message>) { return false; } void ChatController::preHandleIncomingMessage(boost::shared_ptr<MessageEvent> messageEvent) { if (messageEvent->isReadable()) { chatWindow_->flash(); lastWasPresence_ = false; } boost::shared_ptr<Message> message = messageEvent->getStanza(); JID from = message->getFrom(); if (!from.equals(toJID_, JID::WithResource)) { if (toJID_.equals(from, JID::WithoutResource) && toJID_.isBare()){ setToJID(from); } } chatStateTracker_->handleMessageReceived(message); chatStateNotifier_->receivedMessageFromContact(!!message->getPayload<ChatState>()); // handle XEP-0184 Message Receipts // incomming receipts if (boost::shared_ptr<DeliveryReceipt> receipt = message->getPayload<DeliveryReceipt>()) { SWIFT_LOG(debug) << "received receipt for id: " << receipt->getReceivedID() << std::endl; if (requestedReceipts_.find(receipt->getReceivedID()) != requestedReceipts_.end()) { chatWindow_->setMessageReceiptState(requestedReceipts_[receipt->getReceivedID()], ChatWindow::ReceiptReceived); requestedReceipts_.erase(receipt->getReceivedID()); } // incomming errors in response to send out receipts } else if (message->getPayload<DeliveryReceiptRequest>() && (message->getType() == Message::Error)) { if (requestedReceipts_.find(message->getID()) != requestedReceipts_.end()) { chatWindow_->setMessageReceiptState(requestedReceipts_[message->getID()], ChatWindow::ReceiptFailed); requestedReceipts_.erase(message->getID()); } // incoming receipt requests } else if (message->getPayload<DeliveryReceiptRequest>()) { if (receivingPresenceFromUs_) { boost::shared_ptr<Message> receiptMessage = boost::make_shared<Message>(); receiptMessage->setTo(toJID_); receiptMessage->addPayload(boost::make_shared<DeliveryReceipt>(message->getID())); stanzaChannel_->sendMessage(receiptMessage); } } } void ChatController::postHandleIncomingMessage(boost::shared_ptr<MessageEvent> messageEvent, const HighlightAction& highlight) { eventController_->handleIncomingEvent(messageEvent); if (!messageEvent->getConcluded()) { highlighter_->handleHighlightAction(highlight); } } void ChatController::preSendMessageRequest(boost::shared_ptr<Message> message) { chatStateNotifier_->addChatStateRequest(message); if (userWantsReceipts_ && (contactSupportsReceipts_ != ChatWindow::No) && message) { message->addPayload(boost::make_shared<DeliveryReceiptRequest>()); } } void ChatController::setContactIsReceivingPresence(bool isReceivingPresence) { receivingPresenceFromUs_ = isReceivingPresence; } void ChatController::handleSettingChanged(const std::string& settingPath) { if (settingPath == SettingConstants::REQUEST_DELIVERYRECEIPTS.getKey()) { userWantsReceipts_ = settings_->getSetting(SettingConstants::REQUEST_DELIVERYRECEIPTS); checkForDisplayingDisplayReceiptsAlert(); } } void ChatController::checkForDisplayingDisplayReceiptsAlert() { if (userWantsReceipts_ && (contactSupportsReceipts_ == ChatWindow::No)) { chatWindow_->setAlert(QT_TRANSLATE_NOOP("", "This chat doesn't support delivery receipts.")); } else if (userWantsReceipts_ && (contactSupportsReceipts_ == ChatWindow::Maybe)) { chatWindow_->setAlert(QT_TRANSLATE_NOOP("", "This chat may not support delivery receipts. You might not receive delivery receipts for the messages you sent.")); } else { chatWindow_->cancelAlert(); } } void ChatController::handleBlockingStateChanged() { boost::shared_ptr<BlockList> blockList = clientBlockListManager_->getBlockList(); if (blockList->getState() == BlockList::Available) { if (isInMUC_ ? blockList->isBlocked(toJID_) : blockList->isBlocked(toJID_.toBare())) { chatWindow_->setAlert(QT_TRANSLATE_NOOP("", "You've currently blocked this contact. To continue your conversation you have to unblock the contact first.")); chatWindow_->setInputEnabled(false); chatWindow_->setBlockingState(ChatWindow::IsBlocked); } else { chatWindow_->setBlockingState(ChatWindow::IsUnblocked); } } } void ChatController::handleBlockingItemAdded(const JID& jid) { if (toJID_ == (isInMUC_ ? jid: jid.toBare())) { chatWindow_->setAlert(QT_TRANSLATE_NOOP("", "You've currently blocked this contact. To continue your conversation you have to unblock the contact first.")); chatWindow_->setInputEnabled(false); chatWindow_->setBlockingState(ChatWindow::IsBlocked); } } void ChatController::handleBlockingItemRemoved(const JID& jid) { if (toJID_ == (isInMUC_ ? jid: jid.toBare())) { // FIXME: Support for different types of alerts. chatWindow_->cancelAlert(); chatWindow_->setInputEnabled(true); chatWindow_->setBlockingState(ChatWindow::IsUnblocked); } } void ChatController::handleBlockUserRequest() { if (isInMUC_) { eventStream_->send(boost::make_shared<RequestChangeBlockStateUIEvent>(RequestChangeBlockStateUIEvent::Blocked, toJID_)); } else { eventStream_->send(boost::make_shared<RequestChangeBlockStateUIEvent>(RequestChangeBlockStateUIEvent::Blocked, toJID_.toBare())); } } void ChatController::handleUnblockUserRequest() { if (isInMUC_) { eventStream_->send(boost::make_shared<RequestChangeBlockStateUIEvent>(RequestChangeBlockStateUIEvent::Unblocked, toJID_)); } else { eventStream_->send(boost::make_shared<RequestChangeBlockStateUIEvent>(RequestChangeBlockStateUIEvent::Unblocked, toJID_.toBare())); } } void ChatController::handleInviteToChat(const std::vector<JID>& droppedJIDs) { boost::shared_ptr<UIEvent> event(new RequestInviteToMUCUIEvent(toJID_.toBare(), droppedJIDs, RequestInviteToMUCUIEvent::Impromptu)); eventStream_->send(event); } void ChatController::handleUIEvent(boost::shared_ptr<UIEvent> event) { boost::shared_ptr<InviteToMUCUIEvent> inviteEvent = boost::dynamic_pointer_cast<InviteToMUCUIEvent>(event); if (inviteEvent && inviteEvent->getRoom() == toJID_.toBare()) { onConvertToMUC(detachChatWindow(), inviteEvent->getInvites(), inviteEvent->getReason()); } } void ChatController::postSendMessage(const std::string& body, boost::shared_ptr<Stanza> sentStanza) { boost::shared_ptr<Replace> replace = sentStanza->getPayload<Replace>(); if (replace) { eraseIf(unackedStanzas_, PairSecondEquals<boost::shared_ptr<Stanza>, std::string>(myLastMessageUIID_)); replaceMessage(body, myLastMessageUIID_, boost::posix_time::microsec_clock::universal_time(), HighlightAction()); } else { myLastMessageUIID_ = addMessage(body, QT_TRANSLATE_NOOP("", "me"), true, labelsEnabled_ ? chatWindow_->getSelectedSecurityLabel().getLabel() : boost::shared_ptr<SecurityLabel>(), avatarManager_->getAvatarPath(selfJID_), boost::posix_time::microsec_clock::universal_time(), HighlightAction()); } if (stanzaChannel_->getStreamManagementEnabled() && !myLastMessageUIID_.empty() ) { chatWindow_->setAckState(myLastMessageUIID_, ChatWindow::Pending); unackedStanzas_[sentStanza] = myLastMessageUIID_; } if (sentStanza->getPayload<DeliveryReceiptRequest>()) { requestedReceipts_[sentStanza->getID()] = myLastMessageUIID_; chatWindow_->setMessageReceiptState(myLastMessageUIID_, ChatWindow::ReceiptRequested); } lastWasPresence_ = false; chatStateNotifier_->userSentMessage(); } void ChatController::handleStanzaAcked(boost::shared_ptr<Stanza> stanza) { std::map<boost::shared_ptr<Stanza>, std::string>::iterator unackedStanza = unackedStanzas_.find(stanza); if (unackedStanza != unackedStanzas_.end()) { chatWindow_->setAckState(unackedStanza->second, ChatWindow::Received); unackedStanzas_.erase(unackedStanza); } } void ChatController::setOnline(bool online) { if (!online) { std::map<boost::shared_ptr<Stanza>, std::string>::iterator it = unackedStanzas_.begin(); for ( ; it != unackedStanzas_.end(); ++it) { chatWindow_->setAckState(it->second, ChatWindow::Failed); } unackedStanzas_.clear(); Presence::ref fakeOffline(new Presence()); fakeOffline->setFrom(toJID_); fakeOffline->setType(Presence::Unavailable); chatStateTracker_->handlePresenceChange(fakeOffline); } ChatControllerBase::setOnline(online); } void ChatController::handleNewFileTransferController(FileTransferController* ftc) { std::string nick = senderDisplayNameFromMessage(ftc->getOtherParty()); std::string ftID = ftc->setChatWindow(chatWindow_, nick); ftControllers[ftID] = ftc; } void ChatController::handleWhiteboardSessionRequest(bool senderIsSelf) { lastWbID_ = chatWindow_->addWhiteboardRequest(senderIsSelf); } void ChatController::handleWhiteboardStateChange(const ChatWindow::WhiteboardSessionState state) { chatWindow_->setWhiteboardSessionStatus(lastWbID_, state); } void ChatController::handleFileTransferCancel(std::string id) { SWIFT_LOG(debug) << "handleFileTransferCancel(" << id << ")" << std::endl; if (ftControllers.find(id) != ftControllers.end()) { ftControllers[id]->cancel(); } else { std::cerr << "unknown file transfer UI id" << std::endl; } } void ChatController::handleFileTransferStart(std::string id, std::string description) { SWIFT_LOG(debug) << "handleFileTransferStart(" << id << ", " << description << ")" << std::endl; if (ftControllers.find(id) != ftControllers.end()) { ftControllers[id]->start(description); } else { std::cerr << "unknown file transfer UI id" << std::endl; } } void ChatController::handleFileTransferAccept(std::string id, std::string filename) { SWIFT_LOG(debug) << "handleFileTransferAccept(" << id << ", " << filename << ")" << std::endl; if (ftControllers.find(id) != ftControllers.end()) { ftControllers[id]->accept(filename); } else { std::cerr << "unknown file transfer UI id" << std::endl; } } void ChatController::handleSendFileRequest(std::string filename) { SWIFT_LOG(debug) << "ChatController::handleSendFileRequest(" << filename << ")" << std::endl; eventStream_->send(boost::make_shared<SendFileUIEvent>(getToJID(), filename)); } void ChatController::handleWhiteboardSessionAccept() { eventStream_->send(boost::make_shared<AcceptWhiteboardSessionUIEvent>(toJID_)); } void ChatController::handleWhiteboardSessionCancel() { eventStream_->send(boost::make_shared<CancelWhiteboardSessionUIEvent>(toJID_)); } void ChatController::handleWhiteboardWindowShow() { eventStream_->send(boost::make_shared<ShowWhiteboardUIEvent>(toJID_)); } std::string ChatController::senderDisplayNameFromMessage(const JID& from) { return nickResolver_->jidToNick(from); } std::string ChatController::getStatusChangeString(boost::shared_ptr<Presence> presence) { std::string nick = senderDisplayNameFromMessage(presence->getFrom()); std::string response; if (!presence || presence->getType() == Presence::Unavailable || presence->getType() == Presence::Error) { response = QT_TRANSLATE_NOOP("", "%1% has gone offline"); } else if (presence->getType() == Presence::Available) { StatusShow::Type show = presence->getShow(); if (show == StatusShow::Online || show == StatusShow::FFC) { response = QT_TRANSLATE_NOOP("", "%1% has become available"); } else if (show == StatusShow::Away || show == StatusShow::XA) { response = QT_TRANSLATE_NOOP("", "%1% has gone away"); } else if (show == StatusShow::DND) { response = QT_TRANSLATE_NOOP("", "%1% is now busy"); } } Idle::ref idle; if ((idle = presence->getPayload<Idle>())) { response += str(format(QT_TRANSLATE_NOOP("", " and has been idle since %1%")) % dateTimeToLocalString(idle->getSince())); } if (!response.empty()) { response = str(format(response) % nick); } if (!presence->getStatus().empty()) { response += " (" + presence->getStatus() + ")"; } return response + "."; } void ChatController::handlePresenceChange(boost::shared_ptr<Presence> newPresence) { bool me = false; if (toJID_.isBare()) { newPresence = presenceOracle_->getHighestPriorityPresence(toJID_); if ((newPresence ? newPresence->getShow() : StatusShow::None) != lastShownStatus_) { me = true; } } else if (toJID_.equals(newPresence->getFrom(), JID::WithResource)) { me = true; } if (!me) { return; } if (!newPresence) { newPresence = boost::make_shared<Presence>(); newPresence->setType(Presence::Unavailable); } lastShownStatus_ = newPresence->getShow(); chatStateTracker_->handlePresenceChange(newPresence); chatStateNotifier_->setContactIsOnline(newPresence->getType() == Presence::Available); std::string newStatusChangeString = getStatusChangeString(newPresence); if (newStatusChangeString != lastStatusChangeString_) { if (lastWasPresence_) { - chatWindow_->replaceLastMessage(chatMessageParser_->parseMessageBody(newStatusChangeString)); + chatWindow_->replaceLastMessage(chatMessageParser_->parseMessageBody(newStatusChangeString), ChatWindow::UpdateTimestamp); } else { chatWindow_->addPresenceMessage(chatMessageParser_->parseMessageBody(newStatusChangeString), ChatWindow::DefaultDirection); } lastStatusChangeString_ = newStatusChangeString; lastWasPresence_ = true; } } boost::optional<boost::posix_time::ptime> ChatController::getMessageTimestamp(boost::shared_ptr<Message> message) const { return message->getTimestamp(); } void ChatController::logMessage(const std::string& message, const JID& fromJID, const JID& toJID, const boost::posix_time::ptime& timeStamp, bool /* isIncoming */) { HistoryMessage::Type type; if (mucRegistry_->isMUC(fromJID.toBare()) || mucRegistry_->isMUC(toJID.toBare())) { type = HistoryMessage::PrivateMessage; } else { type = HistoryMessage::Chat; } if (historyController_) { historyController_->addMessage(message, fromJID, toJID, type, timeStamp); } } ChatWindow* ChatController::detachChatWindow() { chatWindow_->onUserTyping.disconnect(boost::bind(&ChatStateNotifier::setUserIsTyping, chatStateNotifier_)); chatWindow_->onUserCancelsTyping.disconnect(boost::bind(&ChatStateNotifier::userCancelledNewMessage, chatStateNotifier_)); return ChatControllerBase::detachChatWindow(); } } diff --git a/Swift/Controllers/Chat/MUCController.cpp b/Swift/Controllers/Chat/MUCController.cpp index 6bc7067..d09bc3d 100644 --- a/Swift/Controllers/Chat/MUCController.cpp +++ b/Swift/Controllers/Chat/MUCController.cpp @@ -1,1041 +1,1041 @@ /* * Copyright (c) 2010-2014 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ #include <Swift/Controllers/Chat/MUCController.h> #include <boost/bind.hpp> #include <boost/regex.hpp> #include <boost/algorithm/string.hpp> #include <Swiften/Avatars/AvatarManager.h> #include <Swiften/Base/foreach.h> #include <Swiften/Base/foreach.h> #include <Swiften/Base/format.h> #include <Swiften/Base/Log.h> #include <Swiften/Client/StanzaChannel.h> #include <Swiften/Disco/EntityCapsProvider.h> #include <Swiften/Elements/Delay.h> #include <Swiften/MUC/MUC.h> #include <Swiften/Network/Timer.h> #include <Swiften/Network/TimerFactory.h> #include <Swiften/Roster/XMPPRoster.h> #include <SwifTools/TabComplete.h> #include <Swift/Controllers/Chat/ChatMessageParser.h> #include <Swift/Controllers/Highlighter.h> #include <Swift/Controllers/Intl.h> #include <Swift/Controllers/Roster/ContactRosterItem.h> #include <Swift/Controllers/Roster/GroupRosterItem.h> #include <Swift/Controllers/Roster/ItemOperations/SetAvatar.h> #include <Swift/Controllers/Roster/ItemOperations/SetPresence.h> #include <Swift/Controllers/Roster/ItemOperations/SetMUC.h> #include <Swift/Controllers/Roster/Roster.h> #include <Swift/Controllers/Roster/RosterVCardProvider.h> #include <Swift/Controllers/UIEvents/InviteToMUCUIEvent.h> #include <Swift/Controllers/UIEvents/RequestAddUserDialogUIEvent.h> #include <Swift/Controllers/UIEvents/RequestChatUIEvent.h> #include <Swift/Controllers/UIEvents/RequestInviteToMUCUIEvent.h> #include <Swift/Controllers/UIEvents/ShowProfileForRosterItemUIEvent.h> #include <Swift/Controllers/UIEvents/UIEventStream.h> #include <Swift/Controllers/UIInterfaces/ChatWindow.h> #include <Swift/Controllers/UIInterfaces/ChatWindowFactory.h> #include <Swift/Controllers/XMPPEvents/EventController.h> #define MUC_JOIN_WARNING_TIMEOUT_MILLISECONDS 60000 namespace Swift { /** * The controller does not gain ownership of the stanzaChannel, nor the factory. */ MUCController::MUCController ( const JID& self, MUC::ref muc, const boost::optional<std::string>& password, const std::string &nick, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, PresenceOracle* presenceOracle, AvatarManager* avatarManager, UIEventStream* uiEventStream, bool useDelayForLatency, TimerFactory* timerFactory, EventController* eventController, EntityCapsProvider* entityCapsProvider, XMPPRoster* roster, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager, ChatMessageParser* chatMessageParser, bool isImpromptu, AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider, VCardManager* vcardManager) : ChatControllerBase(self, stanzaChannel, iqRouter, chatWindowFactory, muc->getJID(), presenceOracle, avatarManager, useDelayForLatency, uiEventStream, eventController, timerFactory, entityCapsProvider, historyController, mucRegistry, highlightManager, chatMessageParser, autoAcceptMUCInviteDecider), muc_(muc), nick_(nick), desiredNick_(nick), password_(password), renameCounter_(0), isImpromptu_(isImpromptu), isImpromptuAlreadyConfigured_(false) { parting_ = true; joined_ = false; lastWasPresence_ = false; shouldJoinOnReconnect_ = true; doneGettingHistory_ = false; events_ = uiEventStream; xmppRoster_ = roster; roster_ = new Roster(false, true); rosterVCardProvider_ = new RosterVCardProvider(roster_, vcardManager, JID::WithResource); completer_ = new TabComplete(); chatWindow_->setRosterModel(roster_); chatWindow_->setTabComplete(completer_); chatWindow_->onClosed.connect(boost::bind(&MUCController::handleWindowClosed, this)); chatWindow_->onOccupantSelectionChanged.connect(boost::bind(&MUCController::handleWindowOccupantSelectionChanged, this, _1)); chatWindow_->onOccupantActionSelected.connect(boost::bind(&MUCController::handleActionRequestedOnOccupant, this, _1, _2)); chatWindow_->onChangeSubjectRequest.connect(boost::bind(&MUCController::handleChangeSubjectRequest, this, _1)); chatWindow_->onBookmarkRequest.connect(boost::bind(&MUCController::handleBookmarkRequest, this)); chatWindow_->onConfigureRequest.connect(boost::bind(&MUCController::handleConfigureRequest, this, _1)); chatWindow_->onConfigurationFormCancelled.connect(boost::bind(&MUCController::handleConfigurationCancelled, this)); chatWindow_->onDestroyRequest.connect(boost::bind(&MUCController::handleDestroyRoomRequest, this)); chatWindow_->onInviteToChat.connect(boost::bind(&MUCController::handleInvitePersonToThisMUCRequest, this, _1)); chatWindow_->onGetAffiliationsRequest.connect(boost::bind(&MUCController::handleGetAffiliationsRequest, this)); chatWindow_->onChangeAffiliationsRequest.connect(boost::bind(&MUCController::handleChangeAffiliationsRequest, this, _1)); muc_->onJoinComplete.connect(boost::bind(&MUCController::handleJoinComplete, this, _1)); muc_->onJoinFailed.connect(boost::bind(&MUCController::handleJoinFailed, this, _1)); muc_->onOccupantJoined.connect(boost::bind(&MUCController::handleOccupantJoined, this, _1)); muc_->onOccupantPresenceChange.connect(boost::bind(&MUCController::handleOccupantPresenceChange, this, _1)); muc_->onOccupantLeft.connect(boost::bind(&MUCController::handleOccupantLeft, this, _1, _2, _3)); muc_->onRoleChangeFailed.connect(boost::bind(&MUCController::handleOccupantRoleChangeFailed, this, _1, _2, _3)); muc_->onAffiliationListReceived.connect(boost::bind(&MUCController::handleAffiliationListReceived, this, _1, _2)); muc_->onConfigurationFailed.connect(boost::bind(&MUCController::handleConfigurationFailed, this, _1)); muc_->onConfigurationFormReceived.connect(boost::bind(&MUCController::handleConfigurationFormReceived, this, _1)); highlighter_->setMode(isImpromptu_ ? Highlighter::ChatMode : Highlighter::MUCMode); highlighter_->setNick(nick_); if (timerFactory) { loginCheckTimer_ = boost::shared_ptr<Timer>(timerFactory->createTimer(MUC_JOIN_WARNING_TIMEOUT_MILLISECONDS)); loginCheckTimer_->onTick.connect(boost::bind(&MUCController::handleJoinTimeoutTick, this)); loginCheckTimer_->start(); } if (isImpromptu) { muc_->onUnlocked.connect(boost::bind(&MUCController::handleRoomUnlocked, this)); chatWindow_->convertToMUC(ChatWindow::ImpromptuMUC); } else { muc_->onOccupantRoleChanged.connect(boost::bind(&MUCController::handleOccupantRoleChanged, this, _1, _2, _3)); muc_->onOccupantAffiliationChanged.connect(boost::bind(&MUCController::handleOccupantAffiliationChanged, this, _1, _2, _3)); chatWindow_->convertToMUC(ChatWindow::StandardMUC); chatWindow_->setName(muc->getJID().getNode()); } setOnline(true); if (avatarManager_ != NULL) { avatarChangedConnection_ = (avatarManager_->onAvatarChanged.connect(boost::bind(&MUCController::handleAvatarChanged, this, _1))); } handleBareJIDCapsChanged(muc->getJID()); eventStream_->onUIEvent.connect(boost::bind(&MUCController::handleUIEvent, this, _1)); } MUCController::~MUCController() { eventStream_->onUIEvent.disconnect(boost::bind(&MUCController::handleUIEvent, this, _1)); chatWindow_->setRosterModel(NULL); delete rosterVCardProvider_; delete roster_; if (loginCheckTimer_) { loginCheckTimer_->stop(); } chatWindow_->setTabComplete(NULL); delete completer_; } void MUCController::cancelReplaces() { lastWasPresence_ = false; } void MUCController::handleWindowOccupantSelectionChanged(ContactRosterItem* item) { std::vector<ChatWindow::OccupantAction> actions; if (item) { MUCOccupant::Affiliation affiliation = muc_->getOccupant(getNick()).getAffiliation(); MUCOccupant::Role role = muc_->getOccupant(getNick()).getRole(); if (role == MUCOccupant::Moderator && !isImpromptu_) { if (affiliation == MUCOccupant::Admin || affiliation == MUCOccupant::Owner) { actions.push_back(ChatWindow::Ban); } actions.push_back(ChatWindow::Kick); actions.push_back(ChatWindow::MakeModerator); actions.push_back(ChatWindow::MakeParticipant); actions.push_back(ChatWindow::MakeVisitor); } // Add contact is available only if the real JID is also available if (muc_->getOccupant(item->getJID().getResource()).getRealJID()) { actions.push_back(ChatWindow::AddContact); } actions.push_back(ChatWindow::ShowProfile); } chatWindow_->setAvailableOccupantActions(actions); } void MUCController::handleActionRequestedOnOccupant(ChatWindow::OccupantAction action, ContactRosterItem* item) { JID mucJID = item->getJID(); MUCOccupant occupant = muc_->getOccupant(mucJID.getResource()); JID realJID; if (occupant.getRealJID()) { realJID = occupant.getRealJID().get(); } switch (action) { case ChatWindow::Kick: muc_->kickOccupant(mucJID);break; case ChatWindow::Ban: muc_->changeAffiliation(realJID, MUCOccupant::Outcast);break; case ChatWindow::MakeModerator: muc_->changeOccupantRole(mucJID, MUCOccupant::Moderator);break; case ChatWindow::MakeParticipant: muc_->changeOccupantRole(mucJID, MUCOccupant::Participant);break; case ChatWindow::MakeVisitor: muc_->changeOccupantRole(mucJID, MUCOccupant::Visitor);break; case ChatWindow::AddContact: if (occupant.getRealJID()) events_->send(boost::make_shared<RequestAddUserDialogUIEvent>(realJID, occupant.getNick()));break; case ChatWindow::ShowProfile: events_->send(boost::make_shared<ShowProfileForRosterItemUIEvent>(mucJID));break; } } void MUCController::handleBareJIDCapsChanged(const JID& /*jid*/) { ChatWindow::Tristate support = ChatWindow::Yes; bool any = false; foreach (const std::string& nick, currentOccupants_) { DiscoInfo::ref disco = entityCapsProvider_->getCaps(toJID_.toBare().toString() + "/" + nick); if (disco && disco->hasFeature(DiscoInfo::MessageCorrectionFeature)) { any = true; } else { support = ChatWindow::Maybe; } } if (!any) { support = ChatWindow::No; } chatWindow_->setCorrectionEnabled(support); } /** * Join the MUC if not already in it. */ void MUCController::rejoin() { if (parting_) { joined_ = false; parting_ = false; if (password_) { muc_->setPassword(*password_); } //FIXME: check for received activity #ifdef SWIFT_EXPERIMENTAL_HISTORY if (lastActivity_ == boost::posix_time::not_a_date_time && historyController_) { lastActivity_ = historyController_->getLastTimeStampFromMUC(selfJID_, toJID_); } #endif if (lastActivity_ == boost::posix_time::not_a_date_time) { muc_->joinAs(nick_); } else { muc_->joinWithContextSince(nick_, lastActivity_); } } } bool MUCController::isJoined() { return joined_; } const std::string& MUCController::getNick() { return nick_; } const boost::optional<std::string> MUCController::getPassword() const { return password_; } bool MUCController::isImpromptu() const { return isImpromptu_; } std::map<std::string, JID> MUCController::getParticipantJIDs() const { std::map<std::string, JID> participants; typedef std::pair<std::string, MUCOccupant> MUCOccupantPair; std::map<std::string, MUCOccupant> occupants = muc_->getOccupants(); foreach(const MUCOccupantPair& occupant, occupants) { if (occupant.first != nick_) { participants[occupant.first] = occupant.second.getRealJID().is_initialized() ? occupant.second.getRealJID().get().toBare() : JID(); } } return participants; } void MUCController::sendInvites(const std::vector<JID>& jids, const std::string& reason) const { foreach (const JID& jid, jids) { muc_->invitePerson(jid, reason, isImpromptu_); } } void MUCController::handleJoinTimeoutTick() { receivedActivity(); chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(str(format(QT_TRANSLATE_NOOP("", "Room %1% is not responding. This operation may never complete.")) % toJID_.toString())), ChatWindow::DefaultDirection); } void MUCController::receivedActivity() { if (loginCheckTimer_) { loginCheckTimer_->stop(); } } #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wswitch-enum" void MUCController::handleJoinFailed(boost::shared_ptr<ErrorPayload> error) { receivedActivity(); std::string errorMessage = QT_TRANSLATE_NOOP("", "Unable to enter this room"); std::string rejoinNick; if (error) { switch (error->getCondition()) { case ErrorPayload::Conflict: rejoinNick = nick_ + "_"; errorMessage = str(format(QT_TRANSLATE_NOOP("", "Unable to enter this room as %1%, retrying as %2%")) % nick_ % rejoinNick); break; case ErrorPayload::JIDMalformed: errorMessage += ": "; errorMessage += QT_TRANSLATE_NOOP("", "No nickname specified"); break; case ErrorPayload::NotAuthorized: errorMessage += ": "; errorMessage += QT_TRANSLATE_NOOP("", "The correct room password is needed"); break; case ErrorPayload::RegistrationRequired: errorMessage += ": "; errorMessage += QT_TRANSLATE_NOOP("", "Only members may enter"); break; case ErrorPayload::Forbidden: errorMessage += ": "; errorMessage += QT_TRANSLATE_NOOP("", "You are banned from the room"); break; case ErrorPayload::ServiceUnavailable: errorMessage += ": "; errorMessage += QT_TRANSLATE_NOOP("", "The room is full"); break; case ErrorPayload::ItemNotFound: errorMessage += ": "; errorMessage += QT_TRANSLATE_NOOP("", "The room does not exist"); break; default: break; } } errorMessage = str(format(QT_TRANSLATE_NOOP("", "Couldn't join room: %1%.")) % errorMessage); chatWindow_->addErrorMessage(chatMessageParser_->parseMessageBody(errorMessage)); parting_ = true; if (!rejoinNick.empty() && renameCounter_ < 10) { renameCounter_++; setNick(rejoinNick); rejoin(); } } #pragma clang diagnostic pop void MUCController::handleJoinComplete(const std::string& nick) { receivedActivity(); renameCounter_ = 0; joined_ = true; std::string joinMessage; if (isImpromptu_) { joinMessage = str(format(QT_TRANSLATE_NOOP("", "You have joined the chat as %1%.")) % nick); } else { joinMessage = str(format(QT_TRANSLATE_NOOP("", "You have entered room %1% as %2%.")) % toJID_.toString() % nick); } setNick(nick); - chatWindow_->replaceLastMessage(chatMessageParser_->parseMessageBody(joinMessage)); + chatWindow_->replaceLastMessage(chatMessageParser_->parseMessageBody(joinMessage), ChatWindow::UpdateTimestamp); #ifdef SWIFT_EXPERIMENTAL_HISTORY addRecentLogs(); #endif clearPresenceQueue(); shouldJoinOnReconnect_ = true; setEnabled(true); if (isImpromptu_) { setAvailableRoomActions(MUCOccupant::NoAffiliation, MUCOccupant::Participant); } else { MUCOccupant occupant = muc_->getOccupant(nick); setAvailableRoomActions(occupant.getAffiliation(), occupant.getRole()); } onUserJoined(); if (isImpromptu_) { setImpromptuWindowTitle(); } } void MUCController::handleAvatarChanged(const JID& jid) { if (parting_ || !jid.equals(toJID_, JID::WithoutResource)) { return; } roster_->applyOnItems(SetAvatar(jid, avatarManager_->getAvatarPath(jid), JID::WithResource)); } void MUCController::handleWindowClosed() { parting_ = true; shouldJoinOnReconnect_ = false; muc_->part(); onUserLeft(); } void MUCController::handleOccupantJoined(const MUCOccupant& occupant) { if (nick_ != occupant.getNick()) { completer_->addWord(occupant.getNick()); } receivedActivity(); JID jid(nickToJID(occupant.getNick())); JID realJID; if (occupant.getRealJID()) { realJID = occupant.getRealJID().get(); } currentOccupants_.insert(occupant.getNick()); NickJoinPart event(occupant.getNick(), Join); appendToJoinParts(joinParts_, event); MUCOccupant::Role role = MUCOccupant::Participant; MUCOccupant::Affiliation affiliation = MUCOccupant::NoAffiliation; if (!isImpromptu_) { role = occupant.getRole(); affiliation = occupant.getAffiliation(); } std::string groupName(roleToGroupName(role)); roster_->addContact(jid, realJID, occupant.getNick(), groupName, avatarManager_->getAvatarPath(jid)); roster_->applyOnItems(SetMUC(jid, role, affiliation)); roster_->getGroup(groupName)->setManualSort(roleToSortName(role)); if (joined_) { std::string joinString; if (role != MUCOccupant::NoRole && role != MUCOccupant::Participant) { joinString = str(format(QT_TRANSLATE_NOOP("", "%1% has entered the %3% as a %2%.")) % occupant.getNick() % roleToFriendlyName(role) % (isImpromptu_ ? QT_TRANSLATE_NOOP("", "chat") : QT_TRANSLATE_NOOP("", "room"))); } else { joinString = str(format(QT_TRANSLATE_NOOP("", "%1% has entered the %2%.")) % occupant.getNick() % (isImpromptu_ ? QT_TRANSLATE_NOOP("", "chat") : QT_TRANSLATE_NOOP("", "room"))); } if (shouldUpdateJoinParts()) { updateJoinParts(); } else { addPresenceMessage(joinString); } if (isImpromptu_) { setImpromptuWindowTitle(); onActivity(""); } } if (avatarManager_ != NULL) { handleAvatarChanged(jid); } } void MUCController::addPresenceMessage(const std::string& message) { lastWasPresence_ = true; chatWindow_->addPresenceMessage(chatMessageParser_->parseMessageBody(message), ChatWindow::DefaultDirection); } void MUCController::setAvailableRoomActions(const MUCOccupant::Affiliation& affiliation, const MUCOccupant::Role& role) { std::vector<ChatWindow::RoomAction> actions; if (role <= MUCOccupant::Participant) { actions.push_back(ChatWindow::ChangeSubject); } if (affiliation == MUCOccupant::Owner) { actions.push_back(ChatWindow::Configure); } if (affiliation <= MUCOccupant::Admin) { actions.push_back(ChatWindow::Affiliations); } if (affiliation == MUCOccupant::Owner) { actions.push_back(ChatWindow::Destroy); } if (role <= MUCOccupant::Visitor) { actions.push_back(ChatWindow::Invite); } chatWindow_->setAvailableRoomActions(actions); } void MUCController::clearPresenceQueue() { lastWasPresence_ = false; joinParts_.clear(); } std::string MUCController::roleToFriendlyName(MUCOccupant::Role role) { switch (role) { case MUCOccupant::Moderator: return QT_TRANSLATE_NOOP("", "moderator"); case MUCOccupant::Participant: return QT_TRANSLATE_NOOP("", "participant"); case MUCOccupant::Visitor: return QT_TRANSLATE_NOOP("", "visitor"); case MUCOccupant::NoRole: return ""; } assert(false); return ""; } std::string MUCController::roleToSortName(MUCOccupant::Role role) { switch (role) { case MUCOccupant::Moderator: return "1"; case MUCOccupant::Participant: return "2"; case MUCOccupant::Visitor: return "3"; case MUCOccupant::NoRole: return "4"; } assert(false); return "5"; } JID MUCController::nickToJID(const std::string& nick) { return JID(toJID_.getNode(), toJID_.getDomain(), nick); } bool MUCController::messageTargetsMe(boost::shared_ptr<Message> message) { std::string stringRegexp(".*\\b" + boost::to_lower_copy(nick_) + "\\b.*"); boost::regex myRegexp(stringRegexp); return boost::regex_match(boost::to_lower_copy(message->getBody()), myRegexp); } void MUCController::preHandleIncomingMessage(boost::shared_ptr<MessageEvent> messageEvent) { if (messageEvent->getStanza()->getType() == Message::Groupchat) { lastActivity_ = boost::posix_time::microsec_clock::universal_time(); } clearPresenceQueue(); boost::shared_ptr<Message> message = messageEvent->getStanza(); if (joined_ && messageEvent->getStanza()->getFrom().getResource() != nick_ && messageTargetsMe(message) && !message->getPayload<Delay>() && messageEvent->isReadable()) { chatWindow_->flash(); } else { messageEvent->setTargetsMe(false); } if (messageEvent->isReadable() && isImpromptu_) { chatWindow_->flash(); /* behave like a regular char*/ } if (joined_) { std::string nick = message->getFrom().getResource(); if (nick != nick_ && currentOccupants_.find(nick) != currentOccupants_.end()) { completer_->addWord(nick); } } /*Buggy implementations never send the status code, so use an incoming message as a hint that joining's done (e.g. the old ejabberd on psi-im.org).*/ receivedActivity(); joined_ = true; if (message->hasSubject() && message->getBody().empty()) { chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(str(format(QT_TRANSLATE_NOOP("", "The room subject is now: %1%")) % message->getSubject())), ChatWindow::DefaultDirection);; chatWindow_->setSubject(message->getSubject()); doneGettingHistory_ = true; } if (!doneGettingHistory_ && !message->getPayload<Delay>()) { doneGettingHistory_ = true; } if (!doneGettingHistory_) { checkDuplicates(message); messageEvent->conclude(); } } void MUCController::postHandleIncomingMessage(boost::shared_ptr<MessageEvent> messageEvent, const HighlightAction& highlight) { boost::shared_ptr<Message> message = messageEvent->getStanza(); if (joined_ && messageEvent->getStanza()->getFrom().getResource() != nick_ && !message->getPayload<Delay>()) { if (messageTargetsMe(message) || isImpromptu_) { eventController_->handleIncomingEvent(messageEvent); if (!messageEvent->getConcluded()) { highlighter_->handleHighlightAction(highlight); } } } } void MUCController::handleOccupantRoleChanged(const std::string& nick, const MUCOccupant& occupant, const MUCOccupant::Role& oldRole) { clearPresenceQueue(); receivedActivity(); JID jid(nickToJID(nick)); roster_->removeContactFromGroup(jid, roleToGroupName(oldRole)); JID realJID; if (occupant.getRealJID()) { realJID = occupant.getRealJID().get(); } std::string group(roleToGroupName(occupant.getRole())); roster_->addContact(jid, realJID, nick, group, avatarManager_->getAvatarPath(jid)); roster_->getGroup(group)->setManualSort(roleToSortName(occupant.getRole())); roster_->applyOnItems(SetMUC(jid, occupant.getRole(), occupant.getAffiliation())); chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(str(format(QT_TRANSLATE_NOOP("", "%1% is now a %2%")) % nick % roleToFriendlyName(occupant.getRole()))), ChatWindow::DefaultDirection); if (nick == nick_) { setAvailableRoomActions(occupant.getAffiliation(), occupant.getRole()); } } void MUCController::handleOccupantAffiliationChanged(const std::string& nick, const MUCOccupant::Affiliation& affiliation, const MUCOccupant::Affiliation& /*oldAffiliation*/) { if (nick == nick_) { setAvailableRoomActions(affiliation, muc_->getOccupant(nick_).getRole()); } JID jid(nickToJID(nick)); MUCOccupant occupant = muc_->getOccupant(nick); roster_->applyOnItems(SetMUC(jid, occupant.getRole(), affiliation)); } std::string MUCController::roleToGroupName(MUCOccupant::Role role) { std::string result; switch (role) { case MUCOccupant::Moderator: result = QT_TRANSLATE_NOOP("", "Moderators"); break; case MUCOccupant::Participant: result = QT_TRANSLATE_NOOP("", "Participants"); break; case MUCOccupant::Visitor: result = QT_TRANSLATE_NOOP("", "Visitors"); break; case MUCOccupant::NoRole: result = QT_TRANSLATE_NOOP("", "Occupants"); break; } return result; } void MUCController::setOnline(bool online) { ChatControllerBase::setOnline(online); if (!online) { muc_->part(); parting_ = true; processUserPart(); } else { if (shouldJoinOnReconnect_) { renameCounter_ = 0; if (isImpromptu_) { chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(QT_TRANSLATE_NOOP("", "Trying to join chat")), ChatWindow::DefaultDirection); } else { chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(str(format(QT_TRANSLATE_NOOP("", "Trying to enter room %1%")) % toJID_.toString())), ChatWindow::DefaultDirection); } if (loginCheckTimer_) { loginCheckTimer_->start(); } setNick(desiredNick_); rejoin(); } } } void MUCController::processUserPart() { roster_->removeAll(); /* handleUserLeft won't throw a part back up unless this is called when it doesn't yet know we've left - which only happens on disconnect, so call with disconnect here so if the signal does bubble back up, it'll be with the right type.*/ muc_->handleUserLeft(MUC::Disconnect); setEnabled(false); } bool MUCController::shouldUpdateJoinParts() { return lastWasPresence_; } void MUCController::handleOccupantLeft(const MUCOccupant& occupant, MUC::LeavingType type, const std::string& reason) { NickJoinPart event(occupant.getNick(), Part); appendToJoinParts(joinParts_, event); currentOccupants_.erase(occupant.getNick()); completer_->removeWord(occupant.getNick()); std::string partMessage; bool clearAfter = false; if (occupant.getNick() != nick_) { std::string partType; switch (type) { case MUC::LeaveKick: clearPresenceQueue(); clearAfter = true; partType = " (kicked)"; break; case MUC::LeaveBan: clearPresenceQueue(); clearAfter = true; partType = " (banned)"; break; case MUC::LeaveNotMember: clearPresenceQueue(); clearAfter = true; partType = " (no longer a member)"; break; case MUC::LeaveDestroy: case MUC::Disconnect: case MUC::LeavePart: break; } if (isImpromptu_) { partMessage = str(format(QT_TRANSLATE_NOOP("", "%1% has left the chat%2%")) % occupant.getNick() % partType); } else { partMessage = str(format(QT_TRANSLATE_NOOP("", "%1% has left the room%2%")) % occupant.getNick() % partType); } } else if (isImpromptu_) { switch (type) { case MUC::LeaveKick: case MUC::LeaveBan: clearPresenceQueue(); clearAfter = true; partMessage = QT_TRANSLATE_NOOP("", "You have been removed from this chat"); break; case MUC::LeaveNotMember: clearPresenceQueue(); clearAfter = true; partMessage = QT_TRANSLATE_NOOP("", "You have been removed from this chat"); break; case MUC::LeaveDestroy: clearPresenceQueue(); clearAfter = true; partMessage = QT_TRANSLATE_NOOP("", "This chat has ended"); break; case MUC::Disconnect: case MUC::LeavePart: partMessage = QT_TRANSLATE_NOOP("", "You have left the chat"); } } else { switch (type) { case MUC::LeaveKick: clearPresenceQueue(); clearAfter = true; partMessage = QT_TRANSLATE_NOOP("", "You have been kicked out of the room"); break; case MUC::LeaveBan: clearPresenceQueue(); clearAfter = true; partMessage = QT_TRANSLATE_NOOP("", "You have been banned from the room"); break; case MUC::LeaveNotMember: clearPresenceQueue(); clearAfter = true; partMessage = QT_TRANSLATE_NOOP("", "You are no longer a member of the room and have been removed"); break; case MUC::LeaveDestroy: clearPresenceQueue(); clearAfter = true; partMessage = QT_TRANSLATE_NOOP("", "The room has been destroyed"); break; case MUC::Disconnect: case MUC::LeavePart: partMessage = QT_TRANSLATE_NOOP("", "You have left the room"); } } if (!reason.empty()) { partMessage += " (" + reason + ")"; } partMessage += "."; if (occupant.getNick() != nick_) { if (shouldUpdateJoinParts()) { updateJoinParts(); } else { addPresenceMessage(partMessage); } roster_->removeContact(JID(toJID_.getNode(), toJID_.getDomain(), occupant.getNick())); } else { addPresenceMessage(partMessage); parting_ = true; processUserPart(); } if (clearAfter) { clearPresenceQueue(); } if (isImpromptu_) { setImpromptuWindowTitle(); } } void MUCController::handleOccupantPresenceChange(boost::shared_ptr<Presence> presence) { receivedActivity(); roster_->applyOnItems(SetPresence(presence, JID::WithResource)); } bool MUCController::isIncomingMessageFromMe(boost::shared_ptr<Message> message) { JID from = message->getFrom(); return nick_ == from.getResource(); } std::string MUCController::senderDisplayNameFromMessage(const JID& from) { return from.getResource(); } void MUCController::preSendMessageRequest(boost::shared_ptr<Message> message) { message->setType(Swift::Message::Groupchat); } boost::optional<boost::posix_time::ptime> MUCController::getMessageTimestamp(boost::shared_ptr<Message> message) const { return message->getTimestampFrom(toJID_); } void MUCController::updateJoinParts() { - chatWindow_->replaceLastMessage(chatMessageParser_->parseMessageBody(generateJoinPartString(joinParts_, isImpromptu()))); + chatWindow_->replaceLastMessage(chatMessageParser_->parseMessageBody(generateJoinPartString(joinParts_, isImpromptu())), ChatWindow::UpdateTimestamp); } void MUCController::appendToJoinParts(std::vector<NickJoinPart>& joinParts, const NickJoinPart& newEvent) { std::vector<NickJoinPart>::iterator it = joinParts.begin(); bool matched = false; for (; it != joinParts.end(); ++it) { if ((*it).nick == newEvent.nick) { matched = true; JoinPart type = (*it).type; switch (newEvent.type) { case Join: type = (type == Part) ? PartThenJoin : Join; break; case Part: type = (type == Join) ? JoinThenPart : Part; break; case PartThenJoin: break; case JoinThenPart: break; } (*it).type = type; break; } } if (!matched) { joinParts.push_back(newEvent); } } std::string MUCController::concatenateListOfNames(const std::vector<NickJoinPart>& joinParts) { std::string result; for (size_t i = 0; i < joinParts.size(); i++) { if (i > 0) { if (i < joinParts.size() - 1) { result += ", "; } else { result += QT_TRANSLATE_NOOP("", " and "); } } NickJoinPart event = joinParts[i]; result += event.nick; } return result; } std::string MUCController::generateJoinPartString(const std::vector<NickJoinPart>& joinParts, bool isImpromptu) { std::vector<NickJoinPart> sorted[4]; std::string eventStrings[4]; foreach (NickJoinPart event, joinParts) { sorted[event.type].push_back(event); } std::string result; std::vector<JoinPart> populatedEvents; for (size_t i = 0; i < 4; i++) { std::string names = concatenateListOfNames(sorted[i]); if (!names.empty()) { std::string eventString; switch (i) { case Join: if (sorted[i].size() > 1) { eventString = (isImpromptu ? QT_TRANSLATE_NOOP("", "%1% have joined the chat") : QT_TRANSLATE_NOOP("", "%1% have entered the room")); } else { eventString = (isImpromptu ? QT_TRANSLATE_NOOP("", "%1% has joined the chat") : QT_TRANSLATE_NOOP("", "%1% has entered the room")); } break; case Part: if (sorted[i].size() > 1) { eventString = (isImpromptu ? QT_TRANSLATE_NOOP("", "%1% have left the chat") : QT_TRANSLATE_NOOP("", "%1% have left the room")); } else { eventString = (isImpromptu ? QT_TRANSLATE_NOOP("", "%1% have left the chat") : QT_TRANSLATE_NOOP("", "%1% has left the room")); } break; case JoinThenPart: if (sorted[i].size() > 1) { eventString = (isImpromptu ? QT_TRANSLATE_NOOP("", "%1% have joined then left the chat") : QT_TRANSLATE_NOOP("", "%1% have entered then left the room")); } else { eventString = (isImpromptu ? QT_TRANSLATE_NOOP("", "%1% has joined then left the chat") : QT_TRANSLATE_NOOP("", "%1% has entered then left the room")); } break; case PartThenJoin: if (sorted[i].size() > 1) { eventString = (isImpromptu ? QT_TRANSLATE_NOOP("", "%1% have left then returned to the chat") : QT_TRANSLATE_NOOP("", "%1% have left then returned to the room")); } else { eventString = (isImpromptu ? QT_TRANSLATE_NOOP("", "%1% has left then returned to the chat") : QT_TRANSLATE_NOOP("", "%1% has left then returned to the room")); } break; } populatedEvents.push_back(static_cast<JoinPart>(i)); eventStrings[i] = str(boost::format(eventString) % names); } } for (size_t i = 0; i < populatedEvents.size(); i++) { if (i > 0) { if (i < populatedEvents.size() - 1) { result += ", "; } else { result += QT_TRANSLATE_NOOP("", " and "); } } result += eventStrings[populatedEvents[i]]; } return result; } void MUCController::handleChangeSubjectRequest(const std::string& subject) { muc_->changeSubject(subject); } void MUCController::handleBookmarkRequest() { const JID jid = muc_->getJID(); MUCBookmark bookmark(jid, jid.toBare().toString()); bookmark.setPassword(password_); bookmark.setNick(nick_); chatWindow_->showBookmarkWindow(bookmark); } void MUCController::handleConfigureRequest(Form::ref form) { if (form) { muc_->configureRoom(form); } else { muc_->requestConfigurationForm(); } } void MUCController::handleConfigurationFailed(ErrorPayload::ref error) { std::string errorMessage = getErrorMessage(error); errorMessage = str(format(QT_TRANSLATE_NOOP("", "Room configuration failed: %1%.")) % errorMessage); chatWindow_->addErrorMessage(chatMessageParser_->parseMessageBody(errorMessage)); } void MUCController::handleOccupantRoleChangeFailed(ErrorPayload::ref error, const JID&, MUCOccupant::Role) { std::string errorMessage = getErrorMessage(error); errorMessage = str(format(QT_TRANSLATE_NOOP("", "Occupant role change failed: %1%.")) % errorMessage); chatWindow_->addErrorMessage(chatMessageParser_->parseMessageBody(errorMessage)); } void MUCController::configureAsImpromptuRoom(Form::ref form) { muc_->configureRoom(buildImpromptuRoomConfiguration(form)); isImpromptuAlreadyConfigured_ = true; onImpromptuConfigCompleted(); } void MUCController::handleConfigurationFormReceived(Form::ref form) { if (isImpromptu_) { if (!isImpromptuAlreadyConfigured_) { configureAsImpromptuRoom(form); } } else { chatWindow_->showRoomConfigurationForm(form); } } void MUCController::handleConfigurationCancelled() { muc_->cancelConfigureRoom(); } void MUCController::handleDestroyRoomRequest() { muc_->destroyRoom(); } void MUCController::handleInvitePersonToThisMUCRequest(const std::vector<JID>& jidsToInvite) { RequestInviteToMUCUIEvent::ImpromptuMode mode = isImpromptu_ ? RequestInviteToMUCUIEvent::Impromptu : RequestInviteToMUCUIEvent::NotImpromptu; boost::shared_ptr<UIEvent> event(new RequestInviteToMUCUIEvent(muc_->getJID(), jidsToInvite, mode)); eventStream_->send(event); } void MUCController::handleUIEvent(boost::shared_ptr<UIEvent> event) { boost::shared_ptr<InviteToMUCUIEvent> inviteEvent = boost::dynamic_pointer_cast<InviteToMUCUIEvent>(event); if (inviteEvent && inviteEvent->getRoom() == muc_->getJID()) { foreach (const JID& jid, inviteEvent->getInvites()) { muc_->invitePerson(jid, inviteEvent->getReason(), isImpromptu_); } } } void MUCController::handleGetAffiliationsRequest() { muc_->requestAffiliationList(MUCOccupant::Owner); muc_->requestAffiliationList(MUCOccupant::Admin); muc_->requestAffiliationList(MUCOccupant::Member); muc_->requestAffiliationList(MUCOccupant::Outcast); } typedef std::pair<MUCOccupant::Affiliation, JID> AffiliationChangePair; void MUCController::handleChangeAffiliationsRequest(const std::vector<std::pair<MUCOccupant::Affiliation, JID> >& changes) { std::set<JID> addedJIDs; foreach (const AffiliationChangePair& change, changes) { if (change.first != MUCOccupant::NoAffiliation) { addedJIDs.insert(change.second); } } foreach (const AffiliationChangePair& change, changes) { if (change.first != MUCOccupant::NoAffiliation || addedJIDs.find(change.second) == addedJIDs.end()) { muc_->changeAffiliation(change.second, change.first); } } } void MUCController::handleAffiliationListReceived(MUCOccupant::Affiliation affiliation, const std::vector<JID>& jids) { chatWindow_->setAffiliations(affiliation, jids); } void MUCController::logMessage(const std::string& message, const JID& fromJID, const JID& toJID, const boost::posix_time::ptime& timeStamp, bool isIncoming) { // log only incoming messages if (isIncoming && historyController_) { historyController_->addMessage(message, fromJID, toJID, HistoryMessage::Groupchat, timeStamp); } } void MUCController::addRecentLogs() { if (!historyController_) { return; } joinContext_ = historyController_->getMUCContext(selfJID_, toJID_, lastActivity_); foreach (const HistoryMessage& message, joinContext_) { bool senderIsSelf = nick_ == message.getFromJID().getResource(); // the chatWindow uses utc timestamps addMessage(message.getMessage(), senderDisplayNameFromMessage(message.getFromJID()), senderIsSelf, boost::shared_ptr<SecurityLabel>(new SecurityLabel()), avatarManager_->getAvatarPath(message.getFromJID()), message.getTime() - boost::posix_time::hours(message.getOffset()), HighlightAction()); } } void MUCController::checkDuplicates(boost::shared_ptr<Message> newMessage) { std::string body = newMessage->getBody(); JID jid = newMessage->getFrom(); boost::optional<boost::posix_time::ptime> time = newMessage->getTimestamp(); reverse_foreach (const HistoryMessage& message, joinContext_) { boost::posix_time::ptime messageTime = message.getTime() - boost::posix_time::hours(message.getOffset()); if (time && time < messageTime) { break; } if (time && time != messageTime) { continue; } if (message.getFromJID() != jid) { continue; } if (message.getMessage() != body) { continue; } // Mark the message as unreadable newMessage->setBody(""); } } void MUCController::setNick(const std::string& nick) { nick_ = nick; highlighter_->setNick(nick_); } Form::ref MUCController::buildImpromptuRoomConfiguration(Form::ref roomConfigurationForm) { Form::ref result = boost::make_shared<Form>(Form::SubmitType); std::string impromptuConfigs[] = { "muc#roomconfig_enablelogging", "muc#roomconfig_persistentroom", "muc#roomconfig_publicroom", "muc#roomconfig_whois"}; std::set<std::string> impromptuConfigsMissing(impromptuConfigs, impromptuConfigs + 4); foreach (boost::shared_ptr<FormField> field, roomConfigurationForm->getFields()) { boost::shared_ptr<FormField> resultField; if (field->getName() == "muc#roomconfig_enablelogging") { resultField = boost::make_shared<FormField>(FormField::BooleanType, "0"); } if (field->getName() == "muc#roomconfig_persistentroom") { resultField = boost::make_shared<FormField>(FormField::BooleanType, "0"); } if (field->getName() == "muc#roomconfig_publicroom") { resultField = boost::make_shared<FormField>(FormField::BooleanType, "0"); } if (field->getName() == "muc#roomconfig_whois") { resultField = boost::make_shared<FormField>(FormField::ListSingleType, "anyone"); } if (field->getName() == "FORM_TYPE") { resultField = boost::make_shared<FormField>(FormField::HiddenType, "http://jabber.org/protocol/muc#roomconfig"); } if (resultField) { impromptuConfigsMissing.erase(field->getName()); resultField->setName(field->getName()); result->addField(resultField); } } foreach (const std::string& config, impromptuConfigsMissing) { if (config == "muc#roomconfig_publicroom") { chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(QT_TRANSLATE_NOOP("", "This server doesn't support hiding your chat from other users.")), ChatWindow::DefaultDirection); } else if (config == "muc#roomconfig_whois") { chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(QT_TRANSLATE_NOOP("", "This server doesn't support sharing people's real identity in this chat.")), ChatWindow::DefaultDirection); } } return result; } void MUCController::setImpromptuWindowTitle() { std::string title; typedef std::pair<std::string, MUCOccupant> StringMUCOccupantPair; std::map<std::string, MUCOccupant> occupants = muc_->getOccupants(); if (occupants.size() <= 1) { title = QT_TRANSLATE_NOOP("", "Empty Chat"); } else { foreach (StringMUCOccupantPair pair, occupants) { if (pair.first != nick_) { title += (title.empty() ? "" : ", ") + pair.first; } } } chatWindow_->setName(title); } void MUCController::handleRoomUnlocked() { // Handle buggy MUC implementations where the joined room already exists and is unlocked. // Configure the room again in this case. if (!isImpromptuAlreadyConfigured_) { if (isImpromptu_ && (muc_->getOccupant(nick_).getAffiliation() == MUCOccupant::Owner)) { muc_->requestConfigurationForm(); } else if (isImpromptu_) { onImpromptuConfigCompleted(); } } } } |
Swift