/** * @file LLIMMgr.cpp * @brief Container for Instant Messaging * * $LicenseInfo:firstyear=2001&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2010, Linden Research, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License only. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA * $/LicenseInfo$ */ #include "llviewerprecompiledheaders.h" #include "llimview.h" #include "llavatarnamecache.h" // IDEVO #include "llavataractions.h" #include "llfloaterconversationlog.h" #include "llfloaterreg.h" #include "llfontgl.h" #include "llgl.h" #include "llrect.h" #include "llerror.h" #include "llbutton.h" #include "llsdutil_math.h" #include "llstring.h" #include "lltextutil.h" #include "lltrans.h" #include "lluictrlfactory.h" #include "llfloaterimsessiontab.h" #include "llagent.h" #include "llagentui.h" #include "llappviewer.h" #include "llavatariconctrl.h" #include "llcallingcard.h" #include "llchat.h" // [FS communication UI] //#include "llfloaterimsession.h" //#include "llfloaterimcontainer.h" #include "fsfloaterim.h" #include "fsfloaterimcontainer.h" // [FS communication UI] #include "llgroupiconctrl.h" #include "llmd5.h" #include "llmutelist.h" #include "llrecentpeople.h" #include "llviewermessage.h" #include "llviewerwindow.h" #include "llnotifications.h" #include "llnotificationsutil.h" // [FS communication UI] //#include "llfloaterimnearbychat.h" #include "fsfloaternearbychat.h" // [FS communication UI] #include "llspeakers.h" //for LLIMSpeakerMgr #include "lltextbox.h" #include "lltoolbarview.h" #include "llviewercontrol.h" #include "llviewerparcelmgr.h" #include "llconversationlog.h" #include "message.h" #include "llviewerregion.h" #include "llcorehttputil.h" // [RLVa:KB] - Checked: 2013-05-10 (RLVa-1.4.9) #include "rlvactions.h" #include "rlvcommon.h" // [/RLVa:KB] #include "exogroupmutelist.h" #include "fsconsoleutils.h" #include "fscommon.h" #include "fsfloaternearbychat.h" #include "llslurl.h" #ifdef OPENSIM #include "llviewernetwork.h" #endif // OPENSIM #include "llgiveinventory.h" #include "llinventoryfunctions.h" const static std::string ADHOC_NAME_SUFFIX(" Conference"); const static std::string NEARBY_P2P_BY_OTHER("nearby_P2P_by_other"); const static std::string NEARBY_P2P_BY_AGENT("nearby_P2P_by_agent"); /** Timeout of outgoing session initialization (in seconds) */ const static U32 SESSION_INITIALIZATION_TIMEOUT = 30; void startConfrenceCoro(std::string url, LLUUID tempSessionId, LLUUID creatorId, LLUUID otherParticipantId, LLSD agents); void chatterBoxInvitationCoro(std::string url, LLUUID sessionId, LLIMMgr::EInvitationType invitationType); void start_deprecated_conference_chat(const LLUUID& temp_session_id, const LLUUID& creator_id, const LLUUID& other_participant_id, const LLSD& agents_to_invite); const LLUUID LLOutgoingCallDialog::OCD_KEY = LLUUID("7CF78E11-0CFE-498D-ADB9-1417BF03DDB4"); // // Globals // LLIMMgr* gIMMgr = NULL; BOOL LLSessionTimeoutTimer::tick() { if (mSessionId.isNull()) return TRUE; LLIMModel::LLIMSession* session = LLIMModel::getInstance()->findIMSession(mSessionId); if (session && !session->mSessionInitialized) { gIMMgr->showSessionStartError("session_initialization_timed_out_error", mSessionId); } return TRUE; } void notify_of_message(const LLSD& msg, bool is_dnd_msg); void process_dnd_im(const LLSD& notification) { // [FS communication UI] CHUI will call this after returning // from DnD mode to highlight missed IMs in their // conversations floater; we don't need this as our IM tabs // will already be highlighted. return; // [FS communication UI] // Hide unreachable code to prevent it from breaking compiles #if 0 LLSD data = notification["substitutions"]; LLUUID sessionID = data["SESSION_ID"].asUUID(); LLUUID fromID = data["FROM_ID"].asUUID(); //re-create the IM session if needed //(when coming out of DND mode upon app restart) if(!gIMMgr->hasSession(sessionID)) { //reconstruct session using data from the notification std::string name = data["FROM"]; LLAvatarName av_name; if (LLAvatarNameCache::get(data["FROM_ID"], &av_name)) { name = av_name.getDisplayName(); } LLIMModel::getInstance()->newSession(sessionID, name, IM_NOTHING_SPECIAL, fromID, false, false); //will need slight refactor to retrieve whether offline message or not (assume online for now) } notify_of_message(data, true); #endif // } static void on_avatar_name_cache_toast(const LLUUID& agent_id, const LLAvatarName& av_name, LLSD msg) { LLSD args; args["MESSAGE"] = msg["message"]; args["TIME"] = msg["time"]; // *TODO: Can this ever be an object name or group name? args["FROM"] = av_name.getCompleteName(); args["FROM_ID"] = msg["from_id"]; args["SESSION_ID"] = msg["session_id"]; args["SESSION_TYPE"] = msg["session_type"]; // [FS communication UI] //LLNotificationsUtil::add("IMToast", args, args, boost::bind(&LLFloaterIMContainer::showConversation, LLFloaterIMContainer::getInstance(), msg["session_id"].asUUID())); if (gSavedSettings.getS32("NotificationToastLifeTime") > 0 || gSavedSettings.getS32("ToastFadingTime") > 0) // Ansa: only create toast if it should be visible at all { LLNotificationsUtil::add("IMToast", args, LLSD(), boost::bind(&FSFloaterIM::show, msg["session_id"].asUUID())); } // [FS communication UI] } void notify_of_message(const LLSD& msg, bool is_dnd_msg) { // [CHUI Merge] Commented out for now. Need to see if/how we can/want to wire it up #if 0 std::string user_preferences; LLUUID participant_id = msg[is_dnd_msg ? "FROM_ID" : "from_id"].asUUID(); LLUUID session_id = msg[is_dnd_msg ? "SESSION_ID" : "session_id"].asUUID(); LLIMModel::LLIMSession* session = LLIMModel::instance().findIMSession(session_id); // do not show notification which goes from agent if (gAgent.getID() == participant_id) { return; } // determine state of conversations floater enum {CLOSED, NOT_ON_TOP, ON_TOP, ON_TOP_AND_ITEM_IS_SELECTED} conversations_floater_status; LLFloaterIMContainer* im_box = LLFloaterReg::getTypedInstance("im_container"); LLFloaterIMSessionTab* session_floater = LLFloaterIMSessionTab::getConversation(session_id); bool store_dnd_message = false; // flag storage of a dnd message bool is_session_focused = session_floater->isTornOff() && session_floater->hasFocus(); if (!LLFloater::isVisible(im_box) || im_box->isMinimized()) { conversations_floater_status = CLOSED; } else if (!im_box->hasFocus() && !(session_floater && LLFloater::isVisible(session_floater) && !session_floater->isMinimized() && session_floater->hasFocus())) { conversations_floater_status = NOT_ON_TOP; } else if (im_box->getSelectedSession() != session_id) { conversations_floater_status = ON_TOP; } else { conversations_floater_status = ON_TOP_AND_ITEM_IS_SELECTED; } // determine user prefs for this session if (session_id.isNull()) { if (msg["source_type"].asInteger() == CHAT_SOURCE_OBJECT) { user_preferences = gSavedSettings.getString("NotificationObjectIMOptions"); if (!gAgent.isDoNotDisturb() && (gSavedSettings.getBOOL("PlaySoundObjectIM") == TRUE)) { make_ui_sound("UISndNewIncomingIMSession"); } } else { user_preferences = gSavedSettings.getString("NotificationNearbyChatOptions"); if (!gAgent.isDoNotDisturb() && (gSavedSettings.getBOOL("PlaySoundNearbyChatIM") == TRUE)) { make_ui_sound("UISndNewIncomingIMSession"); } } } else if(session->isP2PSessionType()) { if (LLAvatarTracker::instance().isBuddy(participant_id)) { user_preferences = gSavedSettings.getString("NotificationFriendIMOptions"); if (!gAgent.isDoNotDisturb() && (gSavedSettings.getBOOL("PlaySoundFriendIM") == TRUE)) { make_ui_sound("UISndNewIncomingIMSession"); } } else { user_preferences = gSavedSettings.getString("NotificationNonFriendIMOptions"); if (!gAgent.isDoNotDisturb() && (gSavedSettings.getBOOL("PlaySoundNonFriendIM") == TRUE)) { make_ui_sound("UISndNewIncomingIMSession"); } } } else if(session->isAdHocSessionType()) { user_preferences = gSavedSettings.getString("NotificationConferenceIMOptions"); if (!gAgent.isDoNotDisturb() && (gSavedSettings.getBOOL("PlaySoundConferenceIM") == TRUE)) { make_ui_sound("UISndNewIncomingIMSession"); } } else if(session->isGroupSessionType()) { user_preferences = gSavedSettings.getString("NotificationGroupChatOptions"); if (!gAgent.isDoNotDisturb() && (gSavedSettings.getBOOL("PlaySoundGroupChatIM") == TRUE)) { make_ui_sound("UISndNewIncomingIMSession"); } } // actions: // 0. nothing - exit if (("noaction" == user_preferences || ON_TOP_AND_ITEM_IS_SELECTED == conversations_floater_status) && session_floater->isMessagePaneExpanded()) { return; } // 1. open floater and [optional] surface it if ("openconversations" == user_preferences && (CLOSED == conversations_floater_status || NOT_ON_TOP == conversations_floater_status)) { if(!gAgent.isDoNotDisturb()) { if(!LLAppViewer::instance()->quitRequested() && !LLFloater::isVisible(im_box)) { // Open conversations floater LLFloaterReg::showInstance("im_container"); } im_box->collapseMessagesPane(false); if (session_floater) { if (session_floater->getHost()) { if (NULL != im_box && im_box->isMinimized()) { LLFloater::onClickMinimize(im_box); } } else { if (session_floater->isMinimized()) { LLFloater::onClickMinimize(session_floater); } } } } else { store_dnd_message = true; } } // 2. Flash line item if ("openconversations" == user_preferences || ON_TOP == conversations_floater_status || ("toast" == user_preferences && ON_TOP != conversations_floater_status) || ("flash" == user_preferences && (CLOSED == conversations_floater_status || NOT_ON_TOP == conversations_floater_status)) || is_dnd_msg) { if(!LLMuteList::getInstance()->isMuted(participant_id)) { if(gAgent.isDoNotDisturb()) { store_dnd_message = true; } else { if (is_dnd_msg && (ON_TOP == conversations_floater_status || NOT_ON_TOP == conversations_floater_status || CLOSED == conversations_floater_status)) { im_box->highlightConversationItemWidget(session_id, true); } else { im_box->flashConversationItemWidget(session_id, true); } } } } // 3. Flash FUI button if (("toast" == user_preferences || "flash" == user_preferences) && (CLOSED == conversations_floater_status || NOT_ON_TOP == conversations_floater_status) && !is_session_focused && !is_dnd_msg) //prevent flashing FUI button because the conversation floater will have already opened { if(!LLMuteList::getInstance()->isMuted(participant_id)) { if(!gAgent.isDoNotDisturb()) { gToolBarView->flashCommand(LLCommandId("chat"), true, im_box->isMinimized()); } else { store_dnd_message = true; } } } // 4. Toast if ((("toast" == user_preferences) && (ON_TOP_AND_ITEM_IS_SELECTED != conversations_floater_status) && (!session_floater->isTornOff() || !LLFloater::isVisible(session_floater))) || !session_floater->isMessagePaneExpanded()) { //Show IM toasts (upper right toasts) // Skip toasting for system messages and for nearby chat if(session_id.notNull() && participant_id.notNull()) { if(!is_dnd_msg) { if(gAgent.isDoNotDisturb()) { store_dnd_message = true; } else { LLAvatarNameCache::get(participant_id, boost::bind(&on_avatar_name_cache_toast, _1, _2, msg)); } } } } if (store_dnd_message) { // If in DND mode, allow notification to be stored so upon DND exit // the user will be notified with some limitations (see 'is_dnd_msg' flag checks) if(session_id.notNull() && participant_id.notNull() && !session_floater->isShown()) { LLAvatarNameCache::get(participant_id, boost::bind(&on_avatar_name_cache_toast, _1, _2, msg)); } } #endif // [CHUI Merge] // [FS communication UI] Use old toast handling code for now LLUUID participant_id = msg["from_id"].asUUID(); LLUUID session_id = msg["session_id"].asUUID(); // do not show toast in busy mode or it goes from agent if (gAgent.isDoNotDisturb() || gAgent.getID() == participant_id) { return; } // Ansa: CHUI routes nearby chat through here with session id = null uuid! if (session_id.isNull()) { FSFloaterIMContainer* im_container = FSFloaterIMContainer::getInstance(); FSFloaterNearbyChat* nearby_chat_instance = FSFloaterNearbyChat::findInstance(); if (!im_container->getVisible() && nearby_chat_instance && im_container->hasFloater(nearby_chat_instance) && gSavedSettings.getBOOL("FSNotifyNearbyChatFlash")) { im_container->addFlashingSession(session_id); gToolBarView->flashCommand(LLCommandId("chat"), true, im_container->isMinimized()); } return; } // Don't toast if the message is an announcement if (msg["is_announcement"].asBoolean()) { return; } // Don't toast if the message is an announcement // Skip toasting for system messages if (participant_id.isNull()) { return; } FSFloaterIMContainer* im_container = FSFloaterIMContainer::getInstance(); FSFloaterIM* im_instance = FSFloaterIM::findInstance(session_id); if (!im_container->getVisible() && im_instance && im_container->hasFloater(im_instance) && gSavedSettings.getBOOL("FSNotifyIMFlash")) { im_container->addFlashingSession(session_id); gToolBarView->flashCommand(LLCommandId("chat"), true, im_container->isMinimized()); } // (Group-)IMs in chat console if (FSConsoleUtils::ProcessInstantMessage(session_id, participant_id, msg["message"].asString())) { return; } // (Group-)IMs in chat console // check whether incoming IM belongs to an active session or not if (LLIMModel::getInstance()->getActiveSessionID().notNull() && LLIMModel::getInstance()->getActiveSessionID() == session_id) { return; } // *NOTE Skip toasting if the user disable it in preferences/debug settings ~Alexandrea LLIMModel::LLIMSession* session = LLIMModel::instance().findIMSession(session_id); if (!gSavedSettings.getBOOL("EnableGroupChatPopups") && session->isGroupSessionType()) { return; } if (!gSavedSettings.getBOOL("EnableIMChatPopups") && !session->isGroupSessionType()) { return; } // Skip toasting if we have open window of IM with this session id FSFloaterIM* open_im_floater = FSFloaterIM::findInstance(session_id); if (open_im_floater && open_im_floater->getVisible()) { return; } LLAvatarNameCache::get(participant_id, boost::bind(&on_avatar_name_cache_toast, _1, _2, msg)); // [FS communication UI] } // [FS communication UI] Re-added to not toast if our IM floater is active void LLIMModel::setActiveSessionID(const LLUUID& session_id) { // check if such an ID really exists if (!findIMSession(session_id)) { LL_WARNS() << "Trying to set as active a non-existent session!" << LL_ENDL; return; } mActiveSessionID = session_id; } // [FS communication UI] void on_new_message(const LLSD& msg) { notify_of_message(msg, false); } void startConfrenceCoro(std::string url, LLUUID tempSessionId, LLUUID creatorId, LLUUID otherParticipantId, LLSD agents) { LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("TwitterConnect", httpPolicy)); LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); LLSD postData; postData["method"] = "start conference"; postData["session-id"] = tempSessionId; postData["params"] = agents; LLSD result = httpAdapter->postAndSuspend(httpRequest, url, postData); LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); if (!status) { LL_WARNS("LLIMModel") << "Failed to start conference" << LL_ENDL; //try an "old school" way. // *TODO: What about other error status codes? 4xx 5xx? if (status == LLCore::HttpStatus(HTTP_BAD_REQUEST)) { start_deprecated_conference_chat( tempSessionId, creatorId, otherParticipantId, agents); } //else throw an error back to the client? //in theory we should have just have these error strings //etc. set up in this file as opposed to the IMMgr, //but the error string were unneeded here previously //and it is not worth the effort switching over all //the possible different language translations } } void chatterBoxInvitationCoro(std::string url, LLUUID sessionId, LLIMMgr::EInvitationType invitationType) { LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("TwitterConnect", httpPolicy)); LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); LLSD postData; postData["method"] = "accept invitation"; postData["session-id"] = sessionId; LLSD result = httpAdapter->postAndSuspend(httpRequest, url, postData); LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); if (!gIMMgr) { LL_WARNS("") << "Global IM Manager is NULL" << LL_ENDL; return; } if (!status) { LL_WARNS("LLIMModel") << "Bad HTTP response in chatterBoxInvitationCoro" << LL_ENDL; //throw something back to the viewer here? gIMMgr->clearPendingAgentListUpdates(sessionId); gIMMgr->clearPendingInvitation(sessionId); if (status == LLCore::HttpStatus(HTTP_NOT_FOUND)) { static const std::string error_string("session_does_not_exist_error"); gIMMgr->showSessionStartError(error_string, sessionId); } return; } result.erase(LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS); LLIMSpeakerMgr* speakerMgr = LLIMModel::getInstance()->getSpeakerManager(sessionId); if (speakerMgr) { //we've accepted our invitation //and received a list of agents that were //currently in the session when the reply was sent //to us. Now, it is possible that there were some agents //to slip in/out between when that message was sent to us //and now. //the agent list updates we've received have been //accurate from the time we were added to the session //but unfortunately, our base that we are receiving here //may not be the most up to date. It was accurate at //some point in time though. speakerMgr->setSpeakers(result); //we now have our base of users in the session //that was accurate at some point, but maybe not now //so now we apply all of the updates we've received //in case of race conditions speakerMgr->updateSpeakers(gIMMgr->getPendingAgentListUpdates(sessionId)); } if (LLIMMgr::INVITATION_TYPE_VOICE == invitationType) { gIMMgr->startCall(sessionId, LLVoiceChannel::INCOMING_CALL); } if ((invitationType == LLIMMgr::INVITATION_TYPE_VOICE || invitationType == LLIMMgr::INVITATION_TYPE_IMMEDIATE) && LLIMModel::getInstance()->findIMSession(sessionId)) { // TODO remove in 2010, for voice calls we do not open an IM window //LLFloaterIMSession::show(mSessionID); } gIMMgr->clearPendingAgentListUpdates(sessionId); gIMMgr->clearPendingInvitation(sessionId); } LLIMModel::LLIMModel() { // [FS communication UI] //addNewMsgCallback(boost::bind(&LLFloaterIMSession::newIMCallback, _1)); addNewMsgCallback(boost::bind(&FSFloaterIM::newIMCallback, _1)); // [FS communication UI] addNewMsgCallback(boost::bind(&on_new_message, _1)); LLCallDialogManager::instance(); } LLIMModel::LLIMSession::LLIMSession(const LLUUID& session_id, const std::string& name, const EInstantMessage& type, const LLUUID& other_participant_id, const uuid_vec_t& ids, bool voice, bool has_offline_msg) : mSessionID(session_id), mName(name), mType(type), mHasOfflineMessage(has_offline_msg), // [SL:KB] - Patch: Chat-GroupSnooze | Checked: 2012-08-01 (Catznip-3.3) mCloseAction(CLOSE_DEFAULT), mSnoozeTime(-1), // [/SL:KB] mParticipantUnreadMessageCount(0), mNumUnread(0), mOtherParticipantID(other_participant_id), mInitialTargetIDs(ids), mVoiceChannel(NULL), mSpeakers(NULL), mSessionInitialized(false), mCallBackEnabled(true), mTextIMPossible(true), mOtherParticipantIsAvatar(true), mStartCallOnInitialize(false), mStartedAsIMCall(voice), mIsDNDsend(false), mAvatarNameCacheConnection() { // set P2P type by default mSessionType = P2P_SESSION; if (IM_NOTHING_SPECIAL == mType || IM_SESSION_P2P_INVITE == mType) { mVoiceChannel = new LLVoiceChannelP2P(session_id, name, other_participant_id); mOtherParticipantIsAvatar = LLVoiceClient::getInstance()->isParticipantAvatar(mSessionID); // check if it was AVALINE call if (!mOtherParticipantIsAvatar) { mSessionType = AVALINE_SESSION; } } else { mVoiceChannel = new LLVoiceChannelGroup(session_id, name); // determine whether it is group or conference session if (gAgent.isInGroup(mSessionID)) { mSessionType = GROUP_SESSION; } else { mSessionType = ADHOC_SESSION; } } if(mVoiceChannel) { mVoiceChannelStateChangeConnection = mVoiceChannel->setStateChangedCallback(boost::bind(&LLIMSession::onVoiceChannelStateChanged, this, _1, _2, _3)); } mSpeakers = new LLIMSpeakerMgr(mVoiceChannel); // All participants will be added to the list of people we've recently interacted with. // we need to add only _active_ speakers...so comment this. // may delete this later on cleanup //mSpeakers->addListener(&LLRecentPeople::instance(), "add"); //we need to wait for session initialization for outgoing ad-hoc and group chat session //correct session id for initiated ad-hoc chat will be received from the server if (!LLIMModel::getInstance()->sendStartSession(mSessionID, mOtherParticipantID, mInitialTargetIDs, mType)) { //we don't need to wait for any responses //so we're already initialized mSessionInitialized = true; } else { //tick returns TRUE - timer will be deleted after the tick new LLSessionTimeoutTimer(mSessionID, SESSION_INITIALIZATION_TIMEOUT); } if (IM_NOTHING_SPECIAL == mType) { mCallBackEnabled = LLVoiceClient::getInstance()->isSessionCallBackPossible(mSessionID); mTextIMPossible = LLVoiceClient::getInstance()->isSessionTextIMPossible(mSessionID); } buildHistoryFileName(); loadHistory(); // Localizing name of ad-hoc session. STORM-153 // Changing name should happen here- after the history file was created, so that // history files have consistent (English) names in different locales. if (isAdHocSessionType() && IM_SESSION_INVITE == mType) { mAvatarNameCacheConnection = LLAvatarNameCache::get(mOtherParticipantID,boost::bind(&LLIMModel::LLIMSession::onAdHocNameCache,this, _2)); } } void LLIMModel::LLIMSession::onAdHocNameCache(const LLAvatarName& av_name) { mAvatarNameCacheConnection.disconnect(); if (!av_name.isValidName()) { S32 separator_index = mName.rfind(" "); std::string name = mName.substr(0, separator_index); ++separator_index; std::string conference_word = mName.substr(separator_index, mName.length()); // additional check that session name is what we expected if ("Conference" == conference_word) { LLStringUtil::format_map_t args; args["[AGENT_NAME]"] = name; LLTrans::findString(mName, "conference-title-incoming", args); } } else { LLStringUtil::format_map_t args; args["[AGENT_NAME]"] = av_name.getCompleteName(); LLTrans::findString(mName, "conference-title-incoming", args); } } void LLIMModel::LLIMSession::onVoiceChannelStateChanged(const LLVoiceChannel::EState& old_state, const LLVoiceChannel::EState& new_state, const LLVoiceChannel::EDirection& direction) { std::string you_joined_call = LLTrans::getString("you_joined_call"); std::string you_started_call = LLTrans::getString("you_started_call"); std::string other_avatar_name = ""; LLAvatarName av_name; std::string message; switch(mSessionType) { case AVALINE_SESSION: // no text notifications break; case P2P_SESSION: LLAvatarNameCache::get(mOtherParticipantID, &av_name); other_avatar_name = av_name.getUserName(); if(direction == LLVoiceChannel::INCOMING_CALL) { switch(new_state) { case LLVoiceChannel::STATE_CALL_STARTED : { LLStringUtil::format_map_t string_args; string_args["[NAME]"] = other_avatar_name; message = LLTrans::getString("name_started_call", string_args); LLIMModel::getInstance()->addMessage(mSessionID, SYSTEM_FROM, LLUUID::null, message); break; } case LLVoiceChannel::STATE_CONNECTED : LLIMModel::getInstance()->addMessage(mSessionID, SYSTEM_FROM, LLUUID::null, you_joined_call); default: break; } } else // outgoing call { switch(new_state) { case LLVoiceChannel::STATE_CALL_STARTED : LLIMModel::getInstance()->addMessage(mSessionID, SYSTEM_FROM, LLUUID::null, you_started_call); break; case LLVoiceChannel::STATE_CONNECTED : message = LLTrans::getString("answered_call"); LLIMModel::getInstance()->addMessage(mSessionID, SYSTEM_FROM, LLUUID::null, message); default: break; } } break; case GROUP_SESSION: case ADHOC_SESSION: if(direction == LLVoiceChannel::INCOMING_CALL) { switch(new_state) { case LLVoiceChannel::STATE_CONNECTED : LLIMModel::getInstance()->addMessage(mSessionID, SYSTEM_FROM, LLUUID::null, you_joined_call); default: break; } } else // outgoing call { switch(new_state) { case LLVoiceChannel::STATE_CALL_STARTED : LLIMModel::getInstance()->addMessage(mSessionID, SYSTEM_FROM, LLUUID::null, you_started_call); break; default: break; } } default: break; } // Update speakers list when connected if (LLVoiceChannel::STATE_CONNECTED == new_state) { mSpeakers->update(true); } } LLIMModel::LLIMSession::~LLIMSession() { if (mAvatarNameCacheConnection.connected()) { mAvatarNameCacheConnection.disconnect(); } delete mSpeakers; mSpeakers = NULL; // End the text IM session if necessary if(LLVoiceClient::getInstance() && mOtherParticipantID.notNull()) { switch(mType) { case IM_NOTHING_SPECIAL: case IM_SESSION_P2P_INVITE: LLVoiceClient::getInstance()->endUserIMSession(mOtherParticipantID); break; default: // Appease the linux compiler break; } } mVoiceChannelStateChangeConnection.disconnect(); // HAVE to do this here -- if it happens in the LLVoiceChannel destructor it will call the wrong version (since the object's partially deconstructed at that point). mVoiceChannel->deactivate(); delete mVoiceChannel; mVoiceChannel = NULL; } void LLIMModel::LLIMSession::sessionInitReplyReceived(const LLUUID& new_session_id) { mSessionInitialized = true; if (new_session_id != mSessionID) { mSessionID = new_session_id; mVoiceChannel->updateSessionID(new_session_id); } } void LLIMModel::LLIMSession::addMessage(const std::string& from, const LLUUID& from_id, const std::string& utf8_text, const std::string& time, const bool is_history) { LLSD message; message["from"] = from; message["from_id"] = from_id; message["message"] = utf8_text; message["time"] = time; message["index"] = (LLSD::Integer)mMsgs.size(); message["is_history"] = is_history; mMsgs.push_front(message); if (mSpeakers && from_id.notNull()) { mSpeakers->speakerChatted(from_id); mSpeakers->setSpeakerTyping(from_id, FALSE); } } void LLIMModel::LLIMSession::addMessagesFromHistory(const std::list& history) { std::list::const_iterator it = history.begin(); while (it != history.end()) { const LLSD& msg = *it; std::string from = msg[LL_IM_FROM]; LLUUID from_id; if (msg[LL_IM_FROM_ID].isDefined()) { from_id = msg[LL_IM_FROM_ID].asUUID(); } else { // convert it to a legacy name if we have a complete name std::string legacy_name = gCacheName->buildLegacyName(from); from_id = LLAvatarNameCache::getInstance()->findIdByName(legacy_name); } std::string timestamp = msg[LL_IM_TIME]; std::string text = msg[LL_IM_TEXT]; addMessage(from, from_id, text, timestamp, true); it++; } } void LLIMModel::LLIMSession::chatFromLogFile(LLLogChat::ELogLineType type, const LLSD& msg, void* userdata) { if (!userdata) return; LLIMSession* self = (LLIMSession*) userdata; if (type == LLLogChat::LOG_LINE) { self->addMessage("", LLSD(), msg["message"].asString(), "", true); } else if (type == LLLogChat::LOG_LLSD) { self->addMessage(msg["from"].asString(), msg["from_id"].asUUID(), msg["message"].asString(), msg["time"].asString(), true); } } void LLIMModel::LLIMSession::loadHistory() { mMsgs.clear(); if ( gSavedPerAccountSettings.getBOOL("LogShowHistory") ) { std::list chat_history; //involves parsing of a chat history LLLogChat::loadChatHistory(mHistoryFileName, chat_history, LLSD(), isGroupChat()); addMessagesFromHistory(chat_history); } } LLIMModel::LLIMSession* LLIMModel::findIMSession(const LLUUID& session_id) const { return get_if_there(mId2SessionMap, session_id, (LLIMModel::LLIMSession*) NULL); } //*TODO consider switching to using std::set instead of std::list for holding LLUUIDs across the whole code LLIMModel::LLIMSession* LLIMModel::findAdHocIMSession(const uuid_vec_t& ids) { S32 num = ids.size(); if (!num) return NULL; if (mId2SessionMap.empty()) return NULL; std::map::const_iterator it = mId2SessionMap.begin(); for (; it != mId2SessionMap.end(); ++it) { LLIMSession* session = (*it).second; if (!session->isAdHoc()) continue; if (session->mInitialTargetIDs.size() != num) continue; std::list tmp_list(session->mInitialTargetIDs.begin(), session->mInitialTargetIDs.end()); uuid_vec_t::const_iterator iter = ids.begin(); while (iter != ids.end()) { tmp_list.remove(*iter); ++iter; if (tmp_list.empty()) { break; } } if (tmp_list.empty() && iter == ids.end()) { return session; } } return NULL; } bool LLIMModel::LLIMSession::isOutgoingAdHoc() const { return IM_SESSION_CONFERENCE_START == mType; } bool LLIMModel::LLIMSession::isAdHoc() { return IM_SESSION_CONFERENCE_START == mType || (IM_SESSION_INVITE == mType && !gAgent.isInGroup(mSessionID)); } bool LLIMModel::LLIMSession::isP2P() { return IM_NOTHING_SPECIAL == mType; } bool LLIMModel::LLIMSession::isGroupChat() { return IM_SESSION_GROUP_START == mType || (IM_SESSION_INVITE == mType && gAgent.isInGroup(mSessionID)); } bool LLIMModel::LLIMSession::isOtherParticipantAvaline() { return !mOtherParticipantIsAvatar; } LLUUID LLIMModel::LLIMSession::generateOutgoingAdHocHash() const { LLUUID hash = LLUUID::null; if (mInitialTargetIDs.size()) { std::set sorted_uuids(mInitialTargetIDs.begin(), mInitialTargetIDs.end()); hash = generateHash(sorted_uuids); } return hash; } void LLIMModel::LLIMSession::buildHistoryFileName() { mHistoryFileName = mName; //ad-hoc requires sophisticated chat history saving schemes if (isAdHoc()) { /* in case of outgoing ad-hoc sessions we need to make specilized names * if this naming system is ever changed then the filtering definitions in * lllogchat.cpp need to be change acordingly so that the filtering for the * date stamp code introduced in STORM-102 will work properly and not add * a date stamp to the Ad-hoc conferences. */ if (mInitialTargetIDs.size()) { std::set sorted_uuids(mInitialTargetIDs.begin(), mInitialTargetIDs.end()); mHistoryFileName = mName + " hash" + generateHash(sorted_uuids).asString(); } else { //in case of incoming ad-hoc sessions mHistoryFileName = mName + " " + LLLogChat::timestamp(true) + " " + mSessionID.asString().substr(0, 4); } } else if (isP2P()) // look up username to use as the log name { LLAvatarName av_name; // For outgoing sessions we already have a cached name // so no need for a callback in LLAvatarNameCache::get() if (LLAvatarNameCache::get(mOtherParticipantID, &av_name)) { // [Legacy IM logfile names] //mHistoryFileName = LLCacheName::buildUsername(av_name.getUserName()); if (gSavedSettings.getBOOL("UseLegacyIMLogNames")) { std::string user_name = av_name.getUserName(); mHistoryFileName = user_name.substr(0, user_name.find(" Resident"));; } else { mHistoryFileName = LLCacheName::buildUsername(av_name.getUserName()); } // [Legacy IM logfile names] } else { // Incoming P2P sessions include a name that we can use to build a history file name // [Legacy IM logfile names] //mHistoryFileName = LLCacheName::buildUsername(mName); if (gSavedSettings.getBOOL("UseLegacyIMLogNames")) { mHistoryFileName = mName.substr(0, mName.find(" Resident"));; } else { mHistoryFileName = LLCacheName::buildUsername(mName); } // [Legacy IM logfile names] } } else if (isGroupChat()) { mHistoryFileName = mName + GROUP_CHAT_SUFFIX; } } //static LLUUID LLIMModel::LLIMSession::generateHash(const std::set& sorted_uuids) { LLMD5 md5_uuid; std::set::const_iterator it = sorted_uuids.begin(); while (it != sorted_uuids.end()) { md5_uuid.update((unsigned char*)(*it).mData, 16); it++; } md5_uuid.finalize(); LLUUID participants_md5_hash; md5_uuid.raw_digest((unsigned char*) participants_md5_hash.mData); return participants_md5_hash; } void LLIMModel::processSessionInitializedReply(const LLUUID& old_session_id, const LLUUID& new_session_id) { LLIMSession* session = findIMSession(old_session_id); if (session) { session->sessionInitReplyReceived(new_session_id); if (old_session_id != new_session_id) { mId2SessionMap.erase(old_session_id); mId2SessionMap[new_session_id] = session; } // [FS communication UI] //LLFloaterIMSession* im_floater = LLFloaterIMSession::findInstance(old_session_id); FSFloaterIM* im_floater = FSFloaterIM::findInstance(old_session_id); // [FS communication UI] if (im_floater) { im_floater->sessionInitReplyReceived(new_session_id); } if (old_session_id != new_session_id) { gIMMgr->notifyObserverSessionIDUpdated(old_session_id, new_session_id); } // auto-start the call on session initialization? if (session->mStartCallOnInitialize) { gIMMgr->startCall(new_session_id); } } } void LLIMModel::testMessages() { LLUUID bot1_id("d0426ec6-6535-4c11-a5d9-526bb0c654d9"); LLUUID bot1_session_id; std::string from = "IM Tester"; bot1_session_id = LLIMMgr::computeSessionID(IM_NOTHING_SPECIAL, bot1_id); newSession(bot1_session_id, from, IM_NOTHING_SPECIAL, bot1_id); addMessage(bot1_session_id, from, bot1_id, "Test Message: Hi from testerbot land!"); LLUUID bot2_id; std::string firstname[] = {"Roflcopter", "Joe"}; std::string lastname[] = {"Linden", "Tester", "Resident", "Schmoe"}; S32 rand1 = ll_rand(sizeof firstname)/(sizeof firstname[0]); S32 rand2 = ll_rand(sizeof lastname)/(sizeof lastname[0]); from = firstname[rand1] + " " + lastname[rand2]; bot2_id.generate(from); LLUUID bot2_session_id = LLIMMgr::computeSessionID(IM_NOTHING_SPECIAL, bot2_id); newSession(bot2_session_id, from, IM_NOTHING_SPECIAL, bot2_id); addMessage(bot2_session_id, from, bot2_id, "Test Message: Hello there, I have a question. Can I bother you for a second? "); addMessage(bot2_session_id, from, bot2_id, "Test Message: OMGWTFBBQ."); } //session name should not be empty bool LLIMModel::newSession(const LLUUID& session_id, const std::string& name, const EInstantMessage& type, const LLUUID& other_participant_id, const uuid_vec_t& ids, bool voice, bool has_offline_msg) { if (name.empty()) { LL_WARNS() << "Attempt to create a new session with empty name; id = " << session_id << LL_ENDL; return false; } if (findIMSession(session_id)) { LL_WARNS() << "IM Session " << session_id << " already exists" << LL_ENDL; return false; } LLIMSession* session = new LLIMSession(session_id, name, type, other_participant_id, ids, voice, has_offline_msg); mId2SessionMap[session_id] = session; // When notifying observer, name of session is used instead of "name", because they may not be the // same if it is an adhoc session (in this case name is localized in LLIMSession constructor). std::string session_name = LLIMModel::getInstance()->getName(session_id); LLIMMgr::getInstance()->notifyObserverSessionAdded(session_id, session_name, other_participant_id,has_offline_msg); return true; } bool LLIMModel::newSession(const LLUUID& session_id, const std::string& name, const EInstantMessage& type, const LLUUID& other_participant_id, bool voice, bool has_offline_msg) { uuid_vec_t ids; ids.push_back(other_participant_id); return newSession(session_id, name, type, other_participant_id, ids, voice, has_offline_msg); } bool LLIMModel::clearSession(const LLUUID& session_id) { if (mId2SessionMap.find(session_id) == mId2SessionMap.end()) return false; delete (mId2SessionMap[session_id]); mId2SessionMap.erase(session_id); return true; } void LLIMModel::getMessages(const LLUUID& session_id, std::list& messages, int start_index, const bool sendNoUnreadMsgs) { getMessagesSilently(session_id, messages, start_index); if (sendNoUnreadMsgs) { sendNoUnreadMessages(session_id); } } void LLIMModel::getMessagesSilently(const LLUUID& session_id, std::list& messages, int start_index) { LLIMSession* session = findIMSession(session_id); if (!session) { LL_WARNS() << "session " << session_id << "does not exist " << LL_ENDL; return; } int i = session->mMsgs.size() - start_index; for (std::list::iterator iter = session->mMsgs.begin(); iter != session->mMsgs.end() && i > 0; iter++) { LLSD msg; msg = *iter; messages.push_back(*iter); i--; } } void LLIMModel::sendNoUnreadMessages(const LLUUID& session_id) { LLIMSession* session = findIMSession(session_id); if (!session) { LL_WARNS() << "session " << session_id << "does not exist " << LL_ENDL; return; } session->mNumUnread = 0; session->mParticipantUnreadMessageCount = 0; LLSD arg; arg["session_id"] = session_id; arg["num_unread"] = 0; arg["participant_unread"] = session->mParticipantUnreadMessageCount; mNoUnreadMsgsSignal(arg); } // Added is_announcement parameter //bool LLIMModel::addToHistory(const LLUUID& session_id, const std::string& from, const LLUUID& from_id, const std::string& utf8_text) { bool LLIMModel::addToHistory(const LLUUID& session_id, const std::string& from, const LLUUID& from_id, const std::string& utf8_text, bool is_announcement /* = false */) { LLIMSession* session = findIMSession(session_id); if (!session) { LL_WARNS() << "session " << session_id << "does not exist " << LL_ENDL; return false; } // Forward IM to nearby chat if wanted std::string timestr = LLLogChat::timestamp(false); session->addMessage(from, from_id, utf8_text, timestr); //might want to add date separately static LLCachedControl show_im_in_chat(gSavedSettings, "FSShowIMInChatHistory"); if (show_im_in_chat && !is_announcement) { LLChat chat; chat.mChatStyle = CHAT_STYLE_NORMAL; // FS:LO FIRE-5230 - Chat Console Improvement: Replacing the "IM" in front of group chat messages with the actual group name chat.mChatType = CHAT_TYPE_IM; chat.mFromID = from_id; //chat.mFromName = from; static LLCachedControl group_name_length(gSavedSettings, "FSShowGroupNameLength"); if(group_name_length != 0 && session->isGroupSessionType()) { chat.mChatType = CHAT_TYPE_IM_GROUP; chat.mFromNameGroup = "[" + session->mName.substr(0, group_name_length) + "] "; chat.mFromName = from; } else { chat.mChatType = CHAT_TYPE_IM; chat.mFromName = from; } // FS:LO FIRE-5230 - Chat Console Improvement: Replacing the "IM" in front of group chat messages with the actual group name chat.mSourceType = CHAT_SOURCE_AGENT; chat.mText = utf8_text; chat.mTimeStr = timestr; // [FS communication UI] //LLFloaterNearbyChat* nearby_chat = LLFloaterReg::getTypedInstance("nearby_chat", LLSD()); FSFloaterNearbyChat* nearby_chat = LLFloaterReg::getTypedInstance("fs_nearby_chat", LLSD()); // [FS communication UI] nearby_chat->addMessage(chat, true, LLSD()); } // return true; } bool LLIMModel::logToFile(const std::string& file_name, const std::string& from, const LLUUID& from_id, const std::string& utf8_text) { if (gSavedPerAccountSettings.getS32("KeepConversationLogTranscripts") > 1) { std::string from_name = from; LLAvatarName av_name; if (!from_id.isNull() && LLAvatarNameCache::get(from_id, &av_name) && !av_name.isDisplayNameDefault()) { from_name = av_name.getCompleteName(); } LLLogChat::saveHistory(file_name, from_name, from_id, utf8_text); LLConversationLog::instance().cache(); // update the conversation log too return true; } else { return false; } } bool LLIMModel::proccessOnlineOfflineNotification( const LLUUID& session_id, const std::string& utf8_text) { // Add system message to history return addMessage(session_id, SYSTEM_FROM, LLUUID::null, utf8_text); } // Added is_announcement parameter //bool LLIMModel::addMessage(const LLUUID& session_id, const std::string& from, const LLUUID& from_id, // const std::string& utf8_text, bool log2file /* = true */) { bool LLIMModel::addMessage(const LLUUID& session_id, const std::string& from, const LLUUID& from_id, const std::string& utf8_text, bool log2file /* = true */, bool is_announcement /* = false */, bool keyword_alert_performed /* = false */) { LLIMSession* session = addMessageSilently(session_id, from, from_id, utf8_text, log2file, is_announcement); if (!session) return false; //good place to add some1 to recent list //other places may be called from message history. if( !from_id.isNull() && ( session->isP2PSessionType() || session->isAdHocSessionType() ) ) LLRecentPeople::instance().add(from_id); // notify listeners LLSD arg; arg["session_id"] = session_id; arg["num_unread"] = session->mNumUnread; arg["participant_unread"] = session->mParticipantUnreadMessageCount; arg["message"] = utf8_text; arg["from"] = from; arg["from_id"] = from_id; arg["time"] = LLLogChat::timestamp(false); arg["session_type"] = session->mSessionType; arg["is_announcement"] = is_announcement; // Indicator if it's an announcement arg["keyword_alert_performed"] = keyword_alert_performed; // Pass info if keyword alert has been performed mNewMsgSignal(arg); return true; } // Added is_announcement parameter //LLIMModel::LLIMSession* LLIMModel::addMessageSilently(const LLUUID& session_id, const std::string& from, const LLUUID& from_id, // const std::string& utf8_text, bool log2file /* = true */) LLIMModel::LLIMSession* LLIMModel::addMessageSilently(const LLUUID& session_id, const std::string& from, const LLUUID& from_id, const std::string& utf8_text, bool log2file /* = true */, bool is_announcement /* = false */) { LLIMSession* session = findIMSession(session_id); if (!session) { // Don't spam the log with one of these every time // someone logs on or off //LL_WARNS() << "session " << session_id << "does not exist " << LL_ENDL; return NULL; } // replace interactive system message marker with correct from string value std::string from_name = from; if (INTERACTIVE_SYSTEM_FROM == from) { from_name = SYSTEM_FROM; } addToHistory(session_id, from_name, from_id, utf8_text, is_announcement); if (log2file && !is_announcement) { logToFile(getHistoryFileName(session_id), from_name, from_id, utf8_text); } session->mNumUnread++; //update count of unread messages from real participant if (!(from_id.isNull() || from_id == gAgentID || SYSTEM_FROM == from) // we should increment counter for interactive system messages() || INTERACTIVE_SYSTEM_FROM == from) { ++(session->mParticipantUnreadMessageCount); } return session; } const std::string LLIMModel::getName(const LLUUID& session_id) const { LLIMSession* session = findIMSession(session_id); if (!session) { LL_WARNS() << "session " << session_id << "does not exist " << LL_ENDL; return LLTrans::getString("no_session_message"); } return session->mName; } const S32 LLIMModel::getNumUnread(const LLUUID& session_id) const { LLIMSession* session = findIMSession(session_id); if (!session) { LL_WARNS() << "session " << session_id << "does not exist " << LL_ENDL; return -1; } return session->mNumUnread; } const LLUUID& LLIMModel::getOtherParticipantID(const LLUUID& session_id) const { LLIMSession* session = findIMSession(session_id); if (!session) { LL_WARNS() << "session " << session_id << " does not exist " << LL_ENDL; return LLUUID::null; } return session->mOtherParticipantID; } EInstantMessage LLIMModel::getType(const LLUUID& session_id) const { LLIMSession* session = findIMSession(session_id); if (!session) { LL_WARNS() << "session " << session_id << "does not exist " << LL_ENDL; return IM_COUNT; } return session->mType; } LLVoiceChannel* LLIMModel::getVoiceChannel( const LLUUID& session_id ) const { LLIMSession* session = findIMSession(session_id); if (!session) { LL_WARNS() << "session " << session_id << "does not exist " << LL_ENDL; return NULL; } return session->mVoiceChannel; } LLIMSpeakerMgr* LLIMModel::getSpeakerManager( const LLUUID& session_id ) const { LLIMSession* session = findIMSession(session_id); if (!session) { LL_WARNS() << "session " << session_id << " does not exist " << LL_ENDL; return NULL; } return session->mSpeakers; } const std::string& LLIMModel::getHistoryFileName(const LLUUID& session_id) const { LLIMSession* session = findIMSession(session_id); if (!session) { LL_WARNS() << "session " << session_id << " does not exist " << LL_ENDL; return LLStringUtil::null; } return session->mHistoryFileName; } // TODO get rid of other participant ID void LLIMModel::sendTypingState(LLUUID session_id, LLUUID other_participant_id, BOOL typing) { static LLCachedControl fsSendTypingState(gSavedSettings, "FSSendTypingState"); if (!fsSendTypingState) { return; } std::string name; LLAgentUI::buildFullname(name); pack_instant_message( gMessageSystem, gAgent.getID(), FALSE, gAgent.getSessionID(), other_participant_id, name, std::string("typing"), IM_ONLINE, (typing ? IM_TYPING_START : IM_TYPING_STOP), session_id); gAgent.sendReliableMessage(); } void LLIMModel::sendLeaveSession(const LLUUID& session_id, const LLUUID& other_participant_id) { if(session_id.notNull()) { std::string name; LLAgentUI::buildFullname(name); pack_instant_message( gMessageSystem, gAgent.getID(), FALSE, gAgent.getSessionID(), other_participant_id, name, LLStringUtil::null, IM_ONLINE, IM_SESSION_LEAVE, session_id); gAgent.sendReliableMessage(); } } // *TODO this method is better be moved to the LLIMMgr // FIRE-787: break up too long chat lines into multiple messages // This code is broken out for proper handling of multiple IMs after splitting. void deliverMessage(const std::string& utf8_text, const LLUUID& im_session_id, const LLUUID& other_participant_id, EInstantMessage dialog) { std::string name; bool sent = false; LLAgentUI::buildFullname(name); const LLRelationship* info = NULL; info = LLAvatarTracker::instance().getBuddyInfo(other_participant_id); U8 offline = (!info || info->isOnline()) ? IM_ONLINE : IM_OFFLINE; // Old call to send messages to SLim client, no longer supported. //if((offline == IM_OFFLINE) && (LLVoiceClient::getInstance()->isOnlineSIP(other_participant_id))) //{ // // User is online through the OOW connector, but not with a regular viewer. Try to send the message via SLVoice. // sent = LLVoiceClient::getInstance()->sendTextMessage(other_participant_id, utf8_text); //} if(!sent) { // Send message normally. // default to IM_SESSION_SEND unless it's nothing special - in // which case it's probably an IM to everyone. U8 new_dialog = dialog; if ( dialog != IM_NOTHING_SPECIAL ) { new_dialog = IM_SESSION_SEND; } pack_instant_message( gMessageSystem, gAgent.getID(), FALSE, gAgent.getSessionID(), other_participant_id, name.c_str(), utf8_text.c_str(), offline, (EInstantMessage)new_dialog, im_session_id); gAgent.sendReliableMessage(); } bool is_group_chat = false; LLIMModel::LLIMSession* session = LLIMModel::getInstance()->findIMSession(im_session_id); if(session) { is_group_chat = session->isGroupSessionType(); } // If there is a mute list and this is not a group chat... if ( LLMuteList::getInstance() && !is_group_chat) { // ... the target should not be in our mute list for some message types. // Auto-remove them if present. switch( dialog ) { case IM_NOTHING_SPECIAL: case IM_GROUP_INVITATION: case IM_INVENTORY_OFFERED: case IM_SESSION_INVITE: case IM_SESSION_P2P_INVITE: case IM_SESSION_CONFERENCE_START: case IM_SESSION_SEND: // This one is marginal - erring on the side of hearing. case IM_LURE_USER: case IM_GODLIKE_LURE_USER: case IM_FRIENDSHIP_OFFERED: LLMuteList::getInstance()->autoRemove(other_participant_id, LLMuteList::AR_IM); break; default: ; // do nothing } } } // FIRE-787 void LLIMModel::sendMessage(const std::string& utf8_text, const LLUUID& im_session_id, const LLUUID& other_participant_id, EInstantMessage dialog) { // FIRE-787: break up too long chat lines into multiple messages U32 split = MAX_MSG_BUF_SIZE - 1; U32 pos = 0; U32 total = utf8_text.length(); while(pos < total) { U32 next_split = split; if (pos + next_split > total) { // just send the rest of the message next_split = total - pos; } else { // first, try to split at a space while((U8(utf8_text[pos + next_split]) != ' ') && (next_split > 0)) { --next_split; } if (next_split == 0) { next_split = split; // no space found, split somewhere not in the middle of UTF-8 while((U8(utf8_text[pos + next_split]) >= 0x80) && (U8(utf8_text[pos + next_split]) < 0xC0) && (next_split > 0)) { --next_split; } } if(next_split == 0) { next_split = split; LL_WARNS("Splitting") << "utf-8 couldn't be split correctly" << LL_ENDL; } } std::string send = utf8_text.substr(pos, next_split); pos += next_split; // *FIXME: Queue messages and wait for server deliverMessage(send, im_session_id, other_participant_id, dialog); } // FIRE-787 if((dialog == IM_NOTHING_SPECIAL) && (other_participant_id.notNull())) { // Do we have to replace the /me's here? std::string from; LLAgentUI::buildFullname(from); LLIMModel::getInstance()->addMessage(im_session_id, from, gAgentID, utf8_text); //local echo for the legacy communicate panel std::string history_echo; LLAgentUI::buildFullname(history_echo); history_echo += ": " + utf8_text; LLIMSpeakerMgr* speaker_mgr = LLIMModel::getInstance()->getSpeakerManager(im_session_id); if (speaker_mgr) { speaker_mgr->speakerChatted(gAgentID); speaker_mgr->setSpeakerTyping(gAgentID, FALSE); } } // Add the recipient to the recent people list. bool is_not_group_id = LLGroupMgr::getInstance()->getGroupData(other_participant_id) == NULL; if (is_not_group_id) { LLIMModel::LLIMSession* session = LLIMModel::getInstance()->findIMSession(im_session_id); // Re-added; required because of FIRE-787 if( session == 0)//??? shouldn't really happen { LLRecentPeople::instance().add(other_participant_id); return; } // IM_SESSION_INVITE means that this is an Ad-hoc incoming chat // (it can be also Group chat but it is checked above) // In this case mInitialTargetIDs contains Ad-hoc session ID and it should not be added // to Recent People to prevent showing of an item with (?? ?)(?? ?), sans the spaces. See EXT-8246. // Concrete participants will be added into this list once they sent message in chat. if (IM_SESSION_INVITE == dialog) return; if (IM_SESSION_CONFERENCE_START == dialog) // outgoing ad-hoc session { // Add only online members of conference to recent list (EXT-8658) addSpeakersToRecent(im_session_id); } else // outgoing P2P session { // Add the recepient of the session. if (!session->mInitialTargetIDs.empty()) { LLRecentPeople::instance().add(*(session->mInitialTargetIDs.begin())); } } } } void LLIMModel::addSpeakersToRecent(const LLUUID& im_session_id) { LLIMSpeakerMgr* speaker_mgr = LLIMModel::getInstance()->getSpeakerManager(im_session_id); LLSpeakerMgr::speaker_list_t speaker_list; if(speaker_mgr != NULL) { speaker_mgr->getSpeakerList(&speaker_list, true); } for(LLSpeakerMgr::speaker_list_t::iterator it = speaker_list.begin(); it != speaker_list.end(); it++) { const LLPointer& speakerp = *it; LLRecentPeople::instance().add(speakerp->mID); } } void session_starter_helper( const LLUUID& temp_session_id, const LLUUID& other_participant_id, EInstantMessage im_type) { LLMessageSystem *msg = gMessageSystem; msg->newMessageFast(_PREHASH_ImprovedInstantMessage); msg->nextBlockFast(_PREHASH_AgentData); msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); msg->nextBlockFast(_PREHASH_MessageBlock); msg->addBOOLFast(_PREHASH_FromGroup, FALSE); msg->addUUIDFast(_PREHASH_ToAgentID, other_participant_id); msg->addU8Fast(_PREHASH_Offline, IM_ONLINE); msg->addU8Fast(_PREHASH_Dialog, im_type); msg->addUUIDFast(_PREHASH_ID, temp_session_id); msg->addU32Fast(_PREHASH_Timestamp, NO_TIMESTAMP); // no timestamp necessary std::string name; LLAgentUI::buildFullname(name); msg->addStringFast(_PREHASH_FromAgentName, name); msg->addStringFast(_PREHASH_Message, LLStringUtil::null); msg->addU32Fast(_PREHASH_ParentEstateID, 0); msg->addUUIDFast(_PREHASH_RegionID, LLUUID::null); msg->addVector3Fast(_PREHASH_Position, gAgent.getPositionAgent()); } void start_deprecated_conference_chat( const LLUUID& temp_session_id, const LLUUID& creator_id, const LLUUID& other_participant_id, const LLSD& agents_to_invite) { U8* bucket; U8* pos; S32 count; S32 bucket_size; // *FIX: this could suffer from endian issues count = agents_to_invite.size(); bucket_size = UUID_BYTES * count; bucket = new U8[bucket_size]; pos = bucket; for(S32 i = 0; i < count; ++i) { LLUUID agent_id = agents_to_invite[i].asUUID(); memcpy(pos, &agent_id, UUID_BYTES); pos += UUID_BYTES; } session_starter_helper( temp_session_id, other_participant_id, IM_SESSION_CONFERENCE_START); gMessageSystem->addBinaryDataFast( _PREHASH_BinaryBucket, bucket, bucket_size); gAgent.sendReliableMessage(); delete[] bucket; } // Returns true if any messages were sent, false otherwise. // Is sort of equivalent to "does the server need to do anything?" bool LLIMModel::sendStartSession( const LLUUID& temp_session_id, const LLUUID& other_participant_id, const uuid_vec_t& ids, EInstantMessage dialog) { if ( dialog == IM_SESSION_GROUP_START ) { session_starter_helper( temp_session_id, other_participant_id, dialog); gMessageSystem->addBinaryDataFast( _PREHASH_BinaryBucket, EMPTY_BINARY_BUCKET, EMPTY_BINARY_BUCKET_SIZE); gAgent.sendReliableMessage(); return true; } else if ( dialog == IM_SESSION_CONFERENCE_START ) { LLSD agents; for (int i = 0; i < (S32) ids.size(); i++) { agents.append(ids[i]); } //we have a new way of starting conference calls now LLViewerRegion* region = gAgent.getRegion(); if (region) { std::string url = region->getCapability( "ChatSessionRequest"); LLCoros::instance().launch("startConfrenceCoro", boost::bind(&startConfrenceCoro, url, temp_session_id, gAgent.getID(), other_participant_id, agents)); } else { start_deprecated_conference_chat( temp_session_id, gAgent.getID(), other_participant_id, agents); } //we also need to wait for reply from the server in case of ad-hoc chat (we'll get new session id) return true; } return false; } // the other_participant_id is either an agent_id, a group_id, or an inventory // folder item_id (collection of calling cards) // static LLUUID LLIMMgr::computeSessionID( EInstantMessage dialog, const LLUUID& other_participant_id) { LLUUID session_id; if (IM_SESSION_GROUP_START == dialog) { // slam group session_id to the group_id (other_participant_id) session_id = other_participant_id; } else if (IM_SESSION_CONFERENCE_START == dialog) { session_id.generate(); } else if (IM_SESSION_INVITE == dialog) { // use provided session id for invites session_id = other_participant_id; } else { LLUUID agent_id = gAgent.getID(); if (other_participant_id == agent_id) { // if we try to send an IM to ourselves then the XOR would be null // so we just make the session_id the same as the agent_id session_id = agent_id; } else { // peer-to-peer or peer-to-asset session_id is the XOR session_id = other_participant_id ^ agent_id; } } if (gAgent.isInGroup(session_id) && (session_id != other_participant_id)) { LL_WARNS() << "Group session id different from group id: IM type = " << dialog << ", session id = " << session_id << ", group id = " << other_participant_id << LL_ENDL; } return session_id; } void LLIMMgr::showSessionStartError( const std::string& error_string, const LLUUID session_id) { if (!hasSession(session_id)) return; LLSD args; args["REASON"] = LLTrans::getString(error_string); args["RECIPIENT"] = LLIMModel::getInstance()->getName(session_id); LLSD payload; payload["session_id"] = session_id; LLNotificationsUtil::add( "ChatterBoxSessionStartError", args, payload, LLIMMgr::onConfirmForceCloseError); } void LLIMMgr::showSessionEventError( const std::string& event_string, const std::string& error_string, const LLUUID session_id) { LLSD args; LLStringUtil::format_map_t event_args; event_args["RECIPIENT"] = LLIMModel::getInstance()->getName(session_id); args["REASON"] = LLTrans::getString(error_string); args["EVENT"] = LLTrans::getString(event_string, event_args); LLNotificationsUtil::add( "ChatterBoxSessionEventError", args); } void LLIMMgr::showSessionForceClose( const std::string& reason_string, const LLUUID session_id) { if (!hasSession(session_id)) return; LLSD args; args["NAME"] = LLIMModel::getInstance()->getName(session_id); args["REASON"] = LLTrans::getString(reason_string); LLSD payload; payload["session_id"] = session_id; LLNotificationsUtil::add( "ForceCloseChatterBoxSession", args, payload, LLIMMgr::onConfirmForceCloseError); } //static bool LLIMMgr::onConfirmForceCloseError( const LLSD& notification, const LLSD& response) { //only 1 option really LLUUID session_id = notification["payload"]["session_id"]; // [FS communication UI] //LLFloater* floater = LLFloaterIMSession::findInstance(session_id); LLFloater* floater = FSFloaterIM::findInstance(session_id); // [FS communication UI] if ( floater ) { floater->closeFloater(FALSE); } return false; } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Class LLCallDialogManager //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ LLCallDialogManager::LLCallDialogManager(): mPreviousSessionlName(""), mPreviousSessionType(LLIMModel::LLIMSession::P2P_SESSION), mCurrentSessionlName(""), mSession(NULL), mOldState(LLVoiceChannel::STATE_READY) { } LLCallDialogManager::~LLCallDialogManager() { } void LLCallDialogManager::initSingleton() { LLVoiceChannel::setCurrentVoiceChannelChangedCallback(LLCallDialogManager::onVoiceChannelChanged); } // static void LLCallDialogManager::onVoiceChannelChanged(const LLUUID &session_id) { LLCallDialogManager::getInstance()->onVoiceChannelChangedInt(session_id); } void LLCallDialogManager::onVoiceChannelChangedInt(const LLUUID &session_id) { LLIMModel::LLIMSession* session = LLIMModel::getInstance()->findIMSession(session_id); if(!session) { mPreviousSessionlName = mCurrentSessionlName; mCurrentSessionlName = ""; // Empty string results in "Nearby Voice Chat" after substitution return; } if (mSession) { // store previous session type to process Avaline calls in dialogs mPreviousSessionType = mSession->mSessionType; } mSession = session; static boost::signals2::connection prev_channel_state_changed_connection; // disconnect previously connected callback to avoid have invalid sSession in onVoiceChannelStateChanged() prev_channel_state_changed_connection.disconnect(); prev_channel_state_changed_connection = mSession->mVoiceChannel->setStateChangedCallback(boost::bind(LLCallDialogManager::onVoiceChannelStateChanged, _1, _2, _3, _4)); if(mCurrentSessionlName != session->mName) { mPreviousSessionlName = mCurrentSessionlName; mCurrentSessionlName = session->mName; } if (LLVoiceChannel::getCurrentVoiceChannel()->getState() == LLVoiceChannel::STATE_CALL_STARTED && LLVoiceChannel::getCurrentVoiceChannel()->getCallDirection() == LLVoiceChannel::OUTGOING_CALL) { //*TODO get rid of duplicated code LLSD mCallDialogPayload; mCallDialogPayload["session_id"] = mSession->mSessionID; mCallDialogPayload["session_name"] = mSession->mName; mCallDialogPayload["other_user_id"] = mSession->mOtherParticipantID; mCallDialogPayload["old_channel_name"] = mPreviousSessionlName; mCallDialogPayload["old_session_type"] = mPreviousSessionType; mCallDialogPayload["state"] = LLVoiceChannel::STATE_CALL_STARTED; mCallDialogPayload["disconnected_channel_name"] = mSession->mName; mCallDialogPayload["session_type"] = mSession->mSessionType; LLOutgoingCallDialog* ocd = LLFloaterReg::getTypedInstance("outgoing_call", LLOutgoingCallDialog::OCD_KEY); if(ocd) { ocd->show(mCallDialogPayload); } } } // static void LLCallDialogManager::onVoiceChannelStateChanged(const LLVoiceChannel::EState& old_state, const LLVoiceChannel::EState& new_state, const LLVoiceChannel::EDirection& direction, bool ended_by_agent) { LLCallDialogManager::getInstance()->onVoiceChannelStateChangedInt(old_state, new_state, direction, ended_by_agent); } void LLCallDialogManager::onVoiceChannelStateChangedInt(const LLVoiceChannel::EState& old_state, const LLVoiceChannel::EState& new_state, const LLVoiceChannel::EDirection& direction, bool ended_by_agent) { LLSD mCallDialogPayload; LLOutgoingCallDialog* ocd = NULL; if(mOldState == new_state) { return; } mOldState = new_state; mCallDialogPayload["session_id"] = mSession->mSessionID; mCallDialogPayload["session_name"] = mSession->mName; mCallDialogPayload["other_user_id"] = mSession->mOtherParticipantID; mCallDialogPayload["old_channel_name"] = mPreviousSessionlName; mCallDialogPayload["old_session_type"] = mPreviousSessionType; mCallDialogPayload["state"] = new_state; mCallDialogPayload["disconnected_channel_name"] = mSession->mName; mCallDialogPayload["session_type"] = mSession->mSessionType; mCallDialogPayload["ended_by_agent"] = ended_by_agent; switch(new_state) { case LLVoiceChannel::STATE_CALL_STARTED : // do not show "Calling to..." if it is incoming call if(direction == LLVoiceChannel::INCOMING_CALL) { return; } break; case LLVoiceChannel::STATE_HUNG_UP: // this state is coming before session is changed, so, put it into payload map mCallDialogPayload["old_session_type"] = mSession->mSessionType; break; case LLVoiceChannel::STATE_CONNECTED : ocd = LLFloaterReg::findTypedInstance("outgoing_call", LLOutgoingCallDialog::OCD_KEY); if (ocd) { ocd->closeFloater(); } return; default: break; } ocd = LLFloaterReg::getTypedInstance("outgoing_call", LLOutgoingCallDialog::OCD_KEY); if(ocd) { ocd->show(mCallDialogPayload); } } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Class LLCallDialog //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ LLCallDialog::LLCallDialog(const LLSD& payload) : LLDockableFloater(NULL, false, payload), mPayload(payload), mLifetime(DEFAULT_LIFETIME) { setAutoFocus(FALSE); // force docked state since this floater doesn't save it between recreations setDocked(true); } LLCallDialog::~LLCallDialog() { LLUI::getInstance()->removePopup(this); } BOOL LLCallDialog::postBuild() { if (!LLDockableFloater::postBuild() || !gToolBarView) return FALSE; dockToToolbarButton("speak"); return TRUE; } void LLCallDialog::dockToToolbarButton(const std::string& toolbarButtonName) { LLDockControl::DocAt dock_pos = getDockControlPos(toolbarButtonName); LLView *anchor_panel = gToolBarView->findChildView(toolbarButtonName); setUseTongue(anchor_panel); setDockControl(new LLDockControl(anchor_panel, this, getDockTongue(dock_pos), dock_pos)); } LLDockControl::DocAt LLCallDialog::getDockControlPos(const std::string& toolbarButtonName) { LLCommandId command_id(toolbarButtonName); S32 toolbar_loc = gToolBarView->hasCommand(command_id); LLDockControl::DocAt doc_at = LLDockControl::TOP; switch (toolbar_loc) { case LLToolBarEnums::TOOLBAR_LEFT: doc_at = LLDockControl::RIGHT; break; case LLToolBarEnums::TOOLBAR_RIGHT: doc_at = LLDockControl::LEFT; break; } return doc_at; } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Class LLOutgoingCallDialog //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ LLOutgoingCallDialog::LLOutgoingCallDialog(const LLSD& payload) : LLCallDialog(payload) { LLOutgoingCallDialog* instance = LLFloaterReg::findTypedInstance("outgoing_call", LLOutgoingCallDialog::OCD_KEY); if(instance && instance->getVisible()) { instance->onCancel(instance); } } void LLCallDialog::draw() { if (lifetimeHasExpired()) { onLifetimeExpired(); } if (getDockControl() != NULL) { LLDockableFloater::draw(); } } // virtual void LLCallDialog::onOpen(const LLSD& key) { LLDockableFloater::onOpen(key); // it should be over the all floaters. EXT-5116 LLUI::getInstance()->addPopup(this); } void LLCallDialog::setIcon(const LLSD& session_id, const LLSD& participant_id) { // *NOTE: 12/28/2009: check avaline calls: LLVoiceClient::isParticipantAvatar returns false for them bool participant_is_avatar = LLVoiceClient::getInstance()->isParticipantAvatar(session_id); bool is_group = participant_is_avatar && gAgent.isInGroup(session_id); LLAvatarIconCtrl* avatar_icon = getChild("avatar_icon"); LLGroupIconCtrl* group_icon = getChild("group_icon"); avatar_icon->setVisible(!is_group); group_icon->setVisible(is_group); if (is_group) { group_icon->setValue(session_id); } else if (participant_is_avatar) { avatar_icon->setValue(participant_id); } else { avatar_icon->setValue("Avaline_Icon"); avatar_icon->setToolTip(std::string("")); } } bool LLCallDialog::lifetimeHasExpired() { if (mLifetimeTimer.getStarted()) { F32 elapsed_time = mLifetimeTimer.getElapsedTimeF32(); if (elapsed_time > mLifetime) { return true; } } return false; } void LLCallDialog::onLifetimeExpired() { mLifetimeTimer.stop(); closeFloater(); } void LLOutgoingCallDialog::show(const LLSD& key) { mPayload = key; //will be false only if voice in parcel is disabled and channel we leave is nearby(checked further) bool show_oldchannel = LLViewerParcelMgr::getInstance()->allowAgentVoice(); // hide all text at first hideAllText(); // init notification's lifetime std::istringstream ss( getString("lifetime") ); if (!(ss >> mLifetime)) { mLifetime = DEFAULT_LIFETIME; } // customize text strings // tell the user which voice channel they are leaving if (!mPayload["old_channel_name"].asString().empty()) { bool was_avaline_call = LLIMModel::LLIMSession::AVALINE_SESSION == mPayload["old_session_type"].asInteger(); std::string old_caller_name = mPayload["old_channel_name"].asString(); if (was_avaline_call) { old_caller_name = LLTextUtil::formatPhoneNumber(old_caller_name); } getChild("leaving")->setTextArg("[CURRENT_CHAT]", old_caller_name); show_oldchannel = true; } else { getChild("leaving")->setTextArg("[CURRENT_CHAT]", getString("localchat")); } if (!mPayload["disconnected_channel_name"].asString().empty()) { std::string channel_name = mPayload["disconnected_channel_name"].asString(); if (LLIMModel::LLIMSession::AVALINE_SESSION == mPayload["session_type"].asInteger()) { channel_name = LLTextUtil::formatPhoneNumber(channel_name); } getChild("nearby")->setTextArg("[VOICE_CHANNEL_NAME]", channel_name); // skipping "You will now be reconnected to nearby" in notification when call is ended by disabling voice, // so no reconnection to nearby chat happens (EXT-4397) bool voice_works = LLVoiceClient::getInstance()->voiceEnabled() && LLVoiceClient::getInstance()->isVoiceWorking(); std::string reconnect_nearby = voice_works ? LLTrans::getString("reconnect_nearby") : std::string(); getChild("nearby")->setTextArg("[RECONNECT_NEARBY]", reconnect_nearby); const std::string& nearby_str = mPayload["ended_by_agent"] ? NEARBY_P2P_BY_AGENT : NEARBY_P2P_BY_OTHER; getChild(nearby_str)->setTextArg("[RECONNECT_NEARBY]", reconnect_nearby); } std::string callee_name = mPayload["session_name"].asString(); LLUUID session_id = mPayload["session_id"].asUUID(); bool is_avatar = LLVoiceClient::getInstance()->isParticipantAvatar(session_id); if (callee_name == "anonymous") { callee_name = getString("anonymous"); } else if (!is_avatar) { callee_name = LLTextUtil::formatPhoneNumber(callee_name); } LLSD callee_id = mPayload["other_user_id"]; // Beautification: Since you know who you called, just show display name std::string title = callee_name; std::string final_callee_name = callee_name; if (mPayload["session_type"].asInteger() == LLIMModel::LLIMSession::P2P_SESSION) { LLAvatarName av_name; if (LLAvatarNameCache::get(callee_id, &av_name)) { final_callee_name = av_name.getDisplayName(); title = av_name.getCompleteName(); } } getChild("calling")->setTextArg("[CALLEE_NAME]", final_callee_name); getChild("connecting")->setTextArg("[CALLEE_NAME]", final_callee_name); setTitle(title); // for outgoing group calls callee_id == group id == session id setIcon(callee_id, callee_id); // stop timer by default mLifetimeTimer.stop(); // show only necessary strings and controls switch(mPayload["state"].asInteger()) { case LLVoiceChannel::STATE_CALL_STARTED : getChild("calling")->setVisible(true); getChild("Cancel")->setVisible(true); if(show_oldchannel) { getChild("leaving")->setVisible(true); } break; // STATE_READY is here to show appropriate text for ad-hoc and group calls when floater is shown(EXT-6893) case LLVoiceChannel::STATE_READY : case LLVoiceChannel::STATE_RINGING : if(show_oldchannel) { getChild("leaving")->setVisible(true); } getChild("connecting")->setVisible(true); break; case LLVoiceChannel::STATE_ERROR : getChild("noanswer")->setVisible(true); getChild("Cancel")->setVisible(false); setCanClose(true); mLifetimeTimer.start(); break; case LLVoiceChannel::STATE_HUNG_UP : if (mPayload["session_type"].asInteger() == LLIMModel::LLIMSession::P2P_SESSION) { const std::string& nearby_str = mPayload["ended_by_agent"] ? NEARBY_P2P_BY_AGENT : NEARBY_P2P_BY_OTHER; getChild(nearby_str)->setVisible(true); } else { getChild("nearby")->setVisible(true); } getChild("Cancel")->setVisible(false); setCanClose(true); mLifetimeTimer.start(); } openFloater(LLOutgoingCallDialog::OCD_KEY); } void LLOutgoingCallDialog::hideAllText() { getChild("calling")->setVisible(false); getChild("leaving")->setVisible(false); getChild("connecting")->setVisible(false); getChild("nearby_P2P_by_other")->setVisible(false); getChild("nearby_P2P_by_agent")->setVisible(false); getChild("nearby")->setVisible(false); getChild("noanswer")->setVisible(false); } //static void LLOutgoingCallDialog::onCancel(void* user_data) { LLOutgoingCallDialog* self = (LLOutgoingCallDialog*)user_data; if (!gIMMgr) return; LLUUID session_id = self->mPayload["session_id"].asUUID(); gIMMgr->endCall(session_id); self->closeFloater(); } BOOL LLOutgoingCallDialog::postBuild() { BOOL success = LLCallDialog::postBuild(); childSetAction("Cancel", onCancel, this); setCanDrag(FALSE); return success; } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Class LLIncomingCallDialog //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ LLIncomingCallDialog::LLIncomingCallDialog(const LLSD& payload) : LLCallDialog(payload), mAvatarNameCacheConnection() { } void LLIncomingCallDialog::onLifetimeExpired() { std::string session_handle = mPayload["session_handle"].asString(); if (LLVoiceClient::getInstance()->isValidChannel(session_handle)) { // restart notification's timer if call is still valid mLifetimeTimer.start(); } else { // close invitation if call is already not valid mLifetimeTimer.stop(); LLUUID session_id = mPayload["session_id"].asUUID(); gIMMgr->clearPendingAgentListUpdates(session_id); gIMMgr->clearPendingInvitation(session_id); closeFloater(); } } BOOL LLIncomingCallDialog::postBuild() { LLCallDialog::postBuild(); LLUUID session_id = mPayload["session_id"].asUUID(); LLSD caller_id = mPayload["caller_id"]; std::string caller_name = mPayload["caller_name"].asString(); // init notification's lifetime std::istringstream ss( getString("lifetime") ); if (!(ss >> mLifetime)) { mLifetime = DEFAULT_LIFETIME; } std::string call_type; if (gAgent.isInGroup(session_id)) { LLStringUtil::format_map_t args; LLGroupData data; if (gAgent.getGroupData(session_id, data)) { args["[GROUP]"] = data.mName; call_type = getString(mPayload["notify_box_type"], args); } } else { call_type = getString(mPayload["notify_box_type"]); } // check to see if this is an Avaline call bool is_avatar = LLVoiceClient::getInstance()->isParticipantAvatar(session_id); if (caller_name == "anonymous") { caller_name = getString("anonymous"); setCallerName(caller_name, caller_name, call_type); } else if (!is_avatar) { caller_name = LLTextUtil::formatPhoneNumber(caller_name); setCallerName(caller_name, caller_name, call_type); } else { // Get the full name information if (mAvatarNameCacheConnection.connected()) { mAvatarNameCacheConnection.disconnect(); } mAvatarNameCacheConnection = LLAvatarNameCache::get(caller_id, boost::bind(&LLIncomingCallDialog::onAvatarNameCache, this, _1, _2, call_type)); } setIcon(session_id, caller_id); childSetAction("Accept", onAccept, this); childSetAction("Reject", onReject, this); childSetAction("Start IM", onStartIM, this); setDefaultBtn("Accept"); std::string notify_box_type = mPayload["notify_box_type"].asString(); if(notify_box_type != "VoiceInviteGroup" && notify_box_type != "VoiceInviteAdHoc") { // starting notification's timer for P2P and AVALINE invitations mLifetimeTimer.start(); } else { mLifetimeTimer.stop(); } //it's not possible to connect to existing Ad-Hoc/Group chat through incoming ad-hoc call //and no IM for avaline getChildView("Start IM")->setVisible( is_avatar && notify_box_type != "VoiceInviteAdHoc" && notify_box_type != "VoiceInviteGroup"); setCanDrag(FALSE); return TRUE; } void LLIncomingCallDialog::setCallerName(const std::string& ui_title, const std::string& ui_label, const std::string& call_type) { // call_type may be a string like " is calling." LLUICtrl* caller_name_widget = getChild("caller name"); caller_name_widget->setValue(ui_label + " " + call_type); } void LLIncomingCallDialog::onAvatarNameCache(const LLUUID& agent_id, const LLAvatarName& av_name, const std::string& call_type) { mAvatarNameCacheConnection.disconnect(); std::string title = av_name.getCompleteName(); setCallerName(title, av_name.getCompleteName(), call_type); } void LLIncomingCallDialog::onOpen(const LLSD& key) { LLCallDialog::onOpen(key); // FIRE-7556: Configurable User Interface sounds; This is controlled in llui.cpp //if (gSavedSettings.getBOOL("PlaySoundIncomingVoiceCall")) //{ // // play a sound for incoming voice call if respective property is set // make_ui_sound("UISndStartIM"); //} make_ui_sound("UISndIncomingVoiceCall"); // LLStringUtil::format_map_t args; LLGroupData data; // if it's a group call, retrieve group name to use it in question if (gAgent.getGroupData(key["session_id"].asUUID(), data)) { args["[GROUP]"] = data.mName; } } //static void LLIncomingCallDialog::onAccept(void* user_data) { LLIncomingCallDialog* self = (LLIncomingCallDialog*)user_data; processCallResponse(0, self->mPayload); self->closeFloater(); } //static void LLIncomingCallDialog::onReject(void* user_data) { LLIncomingCallDialog* self = (LLIncomingCallDialog*)user_data; processCallResponse(1, self->mPayload); self->closeFloater(); } //static void LLIncomingCallDialog::onStartIM(void* user_data) { LLIncomingCallDialog* self = (LLIncomingCallDialog*)user_data; processCallResponse(2, self->mPayload); self->closeFloater(); } // static void LLIncomingCallDialog::processCallResponse(S32 response, const LLSD &payload) { if (!gIMMgr || gDisconnected) return; LLUUID session_id = payload["session_id"].asUUID(); LLUUID caller_id = payload["caller_id"].asUUID(); std::string session_name = payload["session_name"].asString(); EInstantMessage type = (EInstantMessage)payload["type"].asInteger(); LLIMMgr::EInvitationType inv_type = (LLIMMgr::EInvitationType)payload["inv_type"].asInteger(); bool voice = true; switch(response) { case 2: // start IM: just don't start the voice chat { voice = false; /* FALLTHROUGH */ } case 0: // accept { if (type == IM_SESSION_P2P_INVITE) { // create a normal IM session session_id = gIMMgr->addP2PSession( session_name, caller_id, payload["session_handle"].asString(), payload["session_uri"].asString()); if (voice) { gIMMgr->startCall(session_id, LLVoiceChannel::INCOMING_CALL); } else { LLAvatarActions::startIM(caller_id); } gIMMgr->clearPendingAgentListUpdates(session_id); gIMMgr->clearPendingInvitation(session_id); } else { //session name should not be empty, but it can contain spaces so we don't trim std::string correct_session_name = session_name; if (session_name.empty()) { LL_WARNS() << "Received an empty session name from a server" << LL_ENDL; switch(type){ case IM_SESSION_CONFERENCE_START: case IM_SESSION_GROUP_START: case IM_SESSION_INVITE: if (gAgent.isInGroup(session_id)) { LLGroupData data; if (!gAgent.getGroupData(session_id, data)) break; correct_session_name = data.mName; } else { // *NOTE: really should be using callbacks here LLAvatarName av_name; if (LLAvatarNameCache::get(caller_id, &av_name)) { correct_session_name = av_name.getCompleteName(); correct_session_name.append(ADHOC_NAME_SUFFIX); } } LL_INFOS() << "Corrected session name is " << correct_session_name << LL_ENDL; break; default: LL_WARNS() << "Received an empty session name from a server and failed to generate a new proper session name" << LL_ENDL; break; } } LLUUID new_session_id = gIMMgr->addSession(correct_session_name, type, session_id, true); std::string url = gAgent.getRegion()->getCapability( "ChatSessionRequest"); if (voice) { LLCoros::instance().launch("chatterBoxInvitationCoro", boost::bind(&chatterBoxInvitationCoro, url, session_id, inv_type)); // send notification message to the corresponding chat if (payload["notify_box_type"].asString() == "VoiceInviteGroup" || payload["notify_box_type"].asString() == "VoiceInviteAdHoc") { LLStringUtil::format_map_t string_args; string_args["[NAME]"] = payload["caller_name"].asString(); std::string message = LLTrans::getString("name_started_call", string_args); LLIMModel::getInstance()->addMessageSilently(session_id, SYSTEM_FROM, LLUUID::null, message); } } } if (voice) { break; } } case 1: // decline { if (type == IM_SESSION_P2P_INVITE) { if(LLVoiceClient::getInstance()) { std::string s = payload["session_handle"].asString(); LLVoiceClient::getInstance()->declineInvite(s); } } else { std::string url = gAgent.getRegion()->getCapability( "ChatSessionRequest"); LLSD data; data["method"] = "decline invitation"; data["session-id"] = session_id; LLCoreHttpUtil::HttpCoroutineAdapter::messageHttpPost(url, data, "Invitation declined", "Invitation decline failed."); } } gIMMgr->clearPendingAgentListUpdates(session_id); gIMMgr->clearPendingInvitation(session_id); } } bool inviteUserResponse(const LLSD& notification, const LLSD& response) { if (!gIMMgr || gDisconnected) return false; const LLSD& payload = notification["payload"]; LLUUID session_id = payload["session_id"].asUUID(); EInstantMessage type = (EInstantMessage)payload["type"].asInteger(); LLIMMgr::EInvitationType inv_type = (LLIMMgr::EInvitationType)payload["inv_type"].asInteger(); S32 option = LLNotificationsUtil::getSelectedOption(notification, response); switch(option) { case 0: // accept { if (type == IM_SESSION_P2P_INVITE) { // create a normal IM session session_id = gIMMgr->addP2PSession( payload["session_name"].asString(), payload["caller_id"].asUUID(), payload["session_handle"].asString(), payload["session_uri"].asString()); gIMMgr->startCall(session_id); gIMMgr->clearPendingAgentListUpdates(session_id); gIMMgr->clearPendingInvitation(session_id); } else { LLUUID new_session_id = gIMMgr->addSession( payload["session_name"].asString(), type, session_id, true); std::string url = gAgent.getRegion()->getCapability( "ChatSessionRequest"); LLCoros::instance().launch("chatterBoxInvitationCoro", boost::bind(&chatterBoxInvitationCoro, url, session_id, inv_type)); } } break; case 2: // mute (also implies ignore, so this falls through to the "ignore" case below) { // mute the sender of this invite if (!LLMuteList::getInstance()->isMuted(payload["caller_id"].asUUID())) { LLMute mute(payload["caller_id"].asUUID(), payload["caller_name"].asString(), LLMute::AGENT); LLMuteList::getInstance()->add(mute); } } /* FALLTHROUGH */ case 1: // decline { if (type == IM_SESSION_P2P_INVITE) { std::string s = payload["session_handle"].asString(); LLVoiceClient::getInstance()->declineInvite(s); } else { std::string url = gAgent.getRegion()->getCapability( "ChatSessionRequest"); LLSD data; data["method"] = "decline invitation"; data["session-id"] = session_id; LLCoreHttpUtil::HttpCoroutineAdapter::messageHttpPost(url, data, "Invitation declined.", "Invitation decline failed."); } } gIMMgr->clearPendingAgentListUpdates(session_id); gIMMgr->clearPendingInvitation(session_id); break; } return false; } // // Member Functions // LLIMMgr::LLIMMgr() { mPendingInvitations = LLSD::emptyMap(); mPendingAgentListUpdates = LLSD::emptyMap(); // [FS communication UI] //LLIMModel::getInstance()->addNewMsgCallback(boost::bind(&LLFloaterIMSession::sRemoveTypingIndicator, _1)); LLIMModel::getInstance()->addNewMsgCallback(boost::bind(&FSFloaterIM::sRemoveTypingIndicator, _1)); // [FS communication UI] } // Add a message to a session. void LLIMMgr::addMessage( const LLUUID& session_id, const LLUUID& target_id, const std::string& from, const std::string& msg, bool is_offline_msg, const std::string& session_name, EInstantMessage dialog, U32 parent_estate_id, const LLUUID& region_id, const LLVector3& position, bool link_name, // If this is true, then we insert the name and link it to a profile bool is_announcement, // Special parameter indicating announcements bool keyword_alert_performed) // Pass info if keyword alert has been performed { LLUUID other_participant_id = target_id; LLUUID new_session_id = session_id; if (new_session_id.isNull()) { //no session ID...compute new one new_session_id = computeSessionID(dialog, other_participant_id); } //*NOTE session_name is empty in case of incoming P2P sessions std::string fixed_session_name = from; bool name_is_setted = false; if(!session_name.empty() && session_name.size()>1) { fixed_session_name = session_name; name_is_setted = true; } bool skip_message = false; bool from_linden = LLMuteList::getInstance()->isLinden(from); // FIRE-14564: VoiceCallFriendsOnly prevents receiving of //if (gSavedPerAccountSettings.getBOOL("VoiceCallsFriendsOnly") && !from_linden) if (gSavedPerAccountSettings.getBOOL("VoiceCallsFriendsOnly") && !from_linden && (dialog == IM_NOTHING_SPECIAL || (dialog == IM_SESSION_INVITE && !gAgent.isInGroup(new_session_id))) ) // { // Evaluate if we need to skip this message when that setting is true (default is false) skip_message = (LLAvatarTracker::instance().getBuddyInfo(other_participant_id) == NULL); // Skip non friends... skip_message &= !(other_participant_id == gAgentID); // You are your best friend... Don't skip yourself } bool new_session = !hasSession(new_session_id); // Configurable IM sounds static LLCachedControl PlayModeUISndNewIncomingIMSession(gSavedSettings, "PlayModeUISndNewIncomingIMSession"); static LLCachedControl PlayModeUISndNewIncomingGroupIMSession(gSavedSettings, "PlayModeUISndNewIncomingGroupIMSession"); static LLCachedControl PlayModeUISndNewIncomingConfIMSession(gSavedSettings, "PlayModeUISndNewIncomingConfIMSession"); bool do_not_disturb = gAgent.isDoNotDisturb(); bool is_group_chat = false; if (dialog != IM_NOTHING_SPECIAL) { is_group_chat = gAgent.isInGroup(new_session_id); } // Configurable IM sounds if (new_session) { LLAvatarName av_name; if (LLAvatarNameCache::get(other_participant_id, &av_name) && !name_is_setted) { fixed_session_name = av_name.getDisplayName(); } // Clear muted group chat early to prevent contacts floater // (re-)gaining focus; the server already knows the correct // session id, so we can leave it! if (is_group_chat && !exoGroupMuteList::instance().isLoaded()) { LL_INFOS() << "Received group chat from " << fixed_session_name << " (" << new_session_id.asString() << ") before mute list has been loaded - skipping message" << LL_ENDL; exoGroupMuteList::instance().addDeferredGroupChat(new_session_id); return; } if (exoGroupMuteList::instance().isMuted(new_session_id)) { LL_INFOS() << "Muting group chat from " << new_session_id.asString() << ": " << fixed_session_name << LL_ENDL; if (gSavedSettings.getBOOL("FSReportMutedGroupChat")) { LLStringUtil::format_map_t args; args["NAME"] = LLSLURL("group", new_session_id, "about").getSLURLString(); report_to_nearby_chat(LLTrans::getString("GroupChatMuteNotice", args)); } clearPendingInvitation(new_session_id); clearPendingAgentListUpdates(new_session_id); LLIMModel::getInstance()->sendLeaveSession(new_session_id, other_participant_id); return; } // // FIRE-13613: First group IM received that was initiated by a muted // resident leads to leaving the group chat session if (IM_NOTHING_SPECIAL != dialog && IM_SESSION_P2P_INVITE != dialog && gAgent.isInGroup(new_session_id) && LLMuteList::getInstance()->isMuted(other_participant_id) && !from_linden) { LL_INFOS() << "Ignoring group chat from " << fixed_session_name << " (" << new_session_id.asString() << ") initiated by muted resident." << LL_ENDL; exoGroupMuteList::instance().addDeferredGroupChat(new_session_id); return; } // LLIMModel::getInstance()->newSession(new_session_id, fixed_session_name, dialog, other_participant_id, false, is_offline_msg); LLIMModel::LLIMSession* session = LLIMModel::instance().findIMSession(new_session_id); if (session) { skip_message &= !session->isGroupSessionType(); // Do not skip group chats... if (skip_message) { gIMMgr->leaveSession(new_session_id); } // When we get a new IM, and if you are a god, display a bit // of information about the source. This is to help liaisons // when answering questions. if (gAgent.isGodlike()) { // *TODO:translate (low priority, god ability) std::ostringstream bonus_info; bonus_info << LLTrans::getString("***") + " " + LLTrans::getString("IMParentEstate") + ":" + " " << parent_estate_id << ((parent_estate_id == 1) ? "," + LLTrans::getString("IMMainland") : "") << ((parent_estate_id == 5) ? "," + LLTrans::getString("IMTeen") : ""); // once we have web-services (or something) which returns // information about a region id, we can print this out // and even have it link to map-teleport or something. //<< "*** region_id: " << region_id << std::endl //<< "*** position: " << position << std::endl; LLIMModel::instance().addMessage(new_session_id, from, other_participant_id, bonus_info.str()); } // Logically it would make more sense to reject the session sooner, in another area of the // code, but the session has to be established inside the server before it can be left. if (LLMuteList::getInstance()->isMuted(other_participant_id, LLMute::flagTextChat) && !from_linden) { LL_WARNS() << "Leaving IM session from initiating muted resident " << from << LL_ENDL; if (!gIMMgr->leaveSession(new_session_id)) { LL_INFOS() << "Session " << new_session_id << " does not exist." << LL_ENDL; } return; } // Configurable IM sounds // //Play sound for new conversations // if (!skip_message & !gAgent.isDoNotDisturb() && (gSavedSettings.getBOOL("PlaySoundNewConversation") == TRUE)) // Option to automatically ignore and leave all conference (ad-hoc) chats static LLCachedControl ignoreAdHocSessions(gSavedSettings, "FSIgnoreAdHocSessions"); if (dialog != IM_NOTHING_SPECIAL && !is_group_chat && ignoreAdHocSessions && !from_linden) { static LLCachedControl dontIgnoreAdHocFromFriends(gSavedSettings, "FSDontIgnoreAdHocFromFriends"); if (!dontIgnoreAdHocFromFriends || (dontIgnoreAdHocFromFriends && LLAvatarTracker::instance().getBuddyInfo(other_participant_id) == NULL)) { static LLCachedControl reportIgnoredAdHocSession(gSavedSettings, "FSReportIgnoredAdHocSession"); // [FIRE-21385] Add inviter name/UUID to ad-hoc ignored messages LLSD args; args["AVATAR_NAME"] = LLSLURL("agent", other_participant_id, "about").getSLURLString(); // LL_INFOS() << "Ignoring conference (ad-hoc) chat from " << new_session_id.asString() << LL_ENDL; LL_INFOS() << "Ignoring conference (ad-hoc) chat from " << args["AVATAR_NAME"] << LL_ENDL; if (!gIMMgr->leaveSession(new_session_id)) { LL_WARNS() << "Ad-hoc session " << new_session_id.asString() << " does not exist." << LL_ENDL; } else if (reportIgnoredAdHocSession) { // report_to_nearby_chat(LLTrans::getString("IgnoredAdHocSession")); report_to_nearby_chat(LLTrans::getString("IgnoredAdHocSession", args)); } // return; } } // if(!do_not_disturb && PlayModeUISndNewIncomingIMSession != 0 && dialog == IM_NOTHING_SPECIAL) { make_ui_sound("UISndNewIncomingIMSession"); } else if(!do_not_disturb && PlayModeUISndNewIncomingGroupIMSession != 0 && dialog != IM_NOTHING_SPECIAL && is_group_chat) { make_ui_sound("UISndNewIncomingGroupIMSession"); } else if(!do_not_disturb && PlayModeUISndNewIncomingConfIMSession != 0 && dialog != IM_NOTHING_SPECIAL && !is_group_chat) { make_ui_sound("UISndNewIncomingConfIMSession"); } } else { // Failed to create a session, most likely due to empty name (name cache failed?) LL_WARNS() << "Failed to create IM session " << fixed_session_name << LL_ENDL; } } else if(!do_not_disturb && PlayModeUISndNewIncomingIMSession == 2 && dialog == IM_NOTHING_SPECIAL) { make_ui_sound("UISndNewIncomingIMSession"); } else if(!do_not_disturb && PlayModeUISndNewIncomingGroupIMSession == 2 && dialog != IM_NOTHING_SPECIAL && is_group_chat) { make_ui_sound("UISndNewIncomingGroupIMSession"); } else if(!do_not_disturb && PlayModeUISndNewIncomingConfIMSession == 2 && dialog != IM_NOTHING_SPECIAL && !is_group_chat) { make_ui_sound("UISndNewIncomingConfIMSession"); // } // IM Sounds only for sessions not in focus else if(!do_not_disturb && PlayModeUISndNewIncomingIMSession == 3 && dialog == IM_NOTHING_SPECIAL) { // [FS communication UI] //LLIMFloater* im_floater = LLIMFloater::findInstance(session_id); FSFloaterIM* im_floater = FSFloaterIM::findInstance(session_id); // [FS communication UI] if (im_floater && !im_floater->hasFocus()) { make_ui_sound("UISndNewIncomingIMSession"); } } else if(!do_not_disturb && PlayModeUISndNewIncomingGroupIMSession == 3 && dialog != IM_NOTHING_SPECIAL && is_group_chat) { // [FS communication UI] //LLIMFloater* im_floater = LLIMFloater::findInstance(session_id); FSFloaterIM* im_floater = FSFloaterIM::findInstance(session_id); // [FS communication UI] if (im_floater && !im_floater->hasFocus()) { make_ui_sound("UISndNewIncomingGroupIMSession"); } } else if(!do_not_disturb && PlayModeUISndNewIncomingConfIMSession == 3 && dialog != IM_NOTHING_SPECIAL && !is_group_chat) { // [FS communication UI] //LLIMFloater* im_floater = LLIMFloater::findInstance(session_id); FSFloaterIM* im_floater = FSFloaterIM::findInstance(session_id); // [FS communication UI] if (im_floater && !im_floater->hasFocus()) { make_ui_sound("UISndNewIncomingConfIMSession"); } } // if (!LLMuteList::getInstance()->isMuted(other_participant_id, LLMute::flagTextChat) && !skip_message) { // Added is_announcement parameter //LLIMModel::instance().addMessage(new_session_id, from, other_participant_id, msg); LLIMModel::instance().addMessage(new_session_id, from, other_participant_id, msg, true, is_announcement, keyword_alert_performed); } // Open conversation floater if offline messages are present // Only open it when the user opts to do so... //if (is_offline_msg && !skip_message) if (is_offline_msg && gSavedSettings.getBOOL("FSOpenIMContainerOnOfflineMessage")) { // LLFloaterReg::showInstance("im_container"); // LLFloaterReg::getTypedInstance("im_container")-> // flashConversationItemWidget(new_session_id, true); LLFloaterReg::showInstance("fs_im_container"); // } } // FIRE-15138: Sharing inventory item sometimes doesn't obey UseLegacyIMLogNames setting void add_system_message_name_cb(LLAvatarName avatar_name, const std::string& message) { std::string session_name = avatar_name.getLegacyName(); // [Legacy IM logfile names] if (gSavedSettings.getBOOL("UseLegacyIMLogNames")) { session_name = session_name.substr(0, session_name.find(" Resident"));; } else { session_name = LLCacheName::buildUsername(session_name); } // [Legacy IM logfile names] LLIMModel::instance().logToFile(session_name, SYSTEM_FROM, LLUUID::null, message); } // void LLIMMgr::addSystemMessage(const LLUUID& session_id, const std::string& message_name, const LLSD& args) { LLUIString message; // null session id means near me (chat history) if (session_id.isNull()) { message = LLTrans::getString(message_name); message.setArgs(args); LLChat chat(message); chat.mSourceType = CHAT_SOURCE_SYSTEM; // [FS communication UI] //LLFloaterIMNearbyChat* nearby_chat = LLFloaterReg::findTypedInstance("nearby_chat"); FSFloaterNearbyChat* nearby_chat = FSFloaterNearbyChat::getInstance(); // [FS communication UI] if (nearby_chat) { nearby_chat->addMessage(chat); } } else // going to IM session { message = LLTrans::getString(message_name + "-im"); message.setArgs(args); if (hasSession(session_id)) { gIMMgr->addMessage(session_id, LLUUID::null, SYSTEM_FROM, message.getString()); } // log message to file else { // FIRE-15138: Sharing inventory item sometimes doesn't obey UseLegacyIMLogNames setting //LLAvatarName av_name; //// since we select user to share item with - his name is already in cache //LLAvatarNameCache::get(args["user_id"], &av_name); //std::string session_name = LLCacheName::buildUsername(av_name.getUserName()); //LLIMModel::instance().logToFile(session_name, SYSTEM_FROM, LLUUID::null, message.getString()); LLAvatarNameCache::get(args["user_id"].asUUID(), boost::bind(&add_system_message_name_cb, _2, message.getString())); // } } } S32 LLIMMgr::getNumberOfUnreadIM() { std::map::iterator it; S32 num = 0; for(it = LLIMModel::getInstance()->mId2SessionMap.begin(); it != LLIMModel::getInstance()->mId2SessionMap.end(); ++it) { num += (*it).second->mNumUnread; } return num; } S32 LLIMMgr::getNumberOfUnreadParticipantMessages() { std::map::iterator it; S32 num = 0; for(it = LLIMModel::getInstance()->mId2SessionMap.begin(); it != LLIMModel::getInstance()->mId2SessionMap.end(); ++it) { num += (*it).second->mParticipantUnreadMessageCount; } return num; } void LLIMMgr::autoStartCallOnStartup(const LLUUID& session_id) { LLIMModel::LLIMSession *session = LLIMModel::getInstance()->findIMSession(session_id); if (!session) return; if (session->mSessionInitialized) { startCall(session_id); } else { session->mStartCallOnInitialize = true; } } LLUUID LLIMMgr::addP2PSession(const std::string& name, const LLUUID& other_participant_id, const std::string& voice_session_handle, const std::string& caller_uri) { LLUUID session_id = addSession(name, IM_NOTHING_SPECIAL, other_participant_id, true); LLIMSpeakerMgr* speaker_mgr = LLIMModel::getInstance()->getSpeakerManager(session_id); if (speaker_mgr) { LLVoiceChannelP2P* voice_channel = dynamic_cast(speaker_mgr->getVoiceChannel()); if (voice_channel) { voice_channel->setSessionHandle(voice_session_handle, caller_uri); } } return session_id; } // This adds a session to the talk view. The name is the local name of // the session, dialog specifies the type of session. If the session // exists, it is brought forward. Specifying id = NULL results in an // im session to everyone. Returns the uuid of the session. LLUUID LLIMMgr::addSession( const std::string& name, EInstantMessage dialog, const LLUUID& other_participant_id, bool voice) { std::vector ids; ids.push_back(other_participant_id); LLUUID session_id = addSession(name, dialog, other_participant_id, ids, voice); return session_id; } // Adds a session using the given session_id. If the session already exists // the dialog type is assumed correct. Returns the uuid of the session. LLUUID LLIMMgr::addSession( const std::string& name, EInstantMessage dialog, const LLUUID& other_participant_id, const std::vector& ids, bool voice, const LLUUID& floater_id) { if (ids.empty()) { return LLUUID::null; } if (name.empty()) { LL_WARNS() << "Session name cannot be null!" << LL_ENDL; return LLUUID::null; } LLUUID session_id = computeSessionID(dialog,other_participant_id); if (floater_id.notNull()) { // [FS communications UI] // LLFloaterIMSession* im_floater = LLFloaterIMSession::findInstance(floater_id); FSFloaterIM* im_floater = FSFloaterIM::findInstance(session_id); // if (im_floater) { // The IM floater should be initialized with a new session_id // so that it is found by that id when creating a chiclet in LLFloaterIMSession::onIMChicletCreated, // and a new floater is not created. im_floater->initIMSession(session_id); im_floater->reloadMessages(); } } bool new_session = (LLIMModel::getInstance()->findIMSession(session_id) == NULL); //works only for outgoing ad-hoc sessions if (new_session && IM_SESSION_CONFERENCE_START == dialog && ids.size()) { LLIMModel::LLIMSession* ad_hoc_found = LLIMModel::getInstance()->findAdHocIMSession(ids); if (ad_hoc_found) { new_session = false; session_id = ad_hoc_found->mSessionID; } } //Notify observers that a session was added if (new_session) { LLIMModel::getInstance()->newSession(session_id, name, dialog, other_participant_id, ids, voice); } //Notifies observers that the session was already added else { std::string session_name = LLIMModel::getInstance()->getName(session_id); LLIMMgr::getInstance()->notifyObserverSessionActivated(session_id, session_name, other_participant_id); } //we don't need to show notes about online/offline, mute/unmute users' statuses for existing sessions if (!new_session) return session_id; LL_INFOS() << "LLIMMgr::addSession, new session added, name = " << name << ", session id = " << session_id << LL_ENDL; //Per Plan's suggestion commented "explicit offline status warning" out to make Dessie happier (see EXT-3609) //*TODO After February 2010 remove this commented out line if no one will be missing that warning //noteOfflineUsers(session_id, floater, ids); // Only warn for regular IMs - not group IMs if( dialog == IM_NOTHING_SPECIAL ) { noteMutedUsers(session_id, ids); } notifyObserverSessionVoiceOrIMStarted(session_id); return session_id; } bool LLIMMgr::leaveSession(const LLUUID& session_id) { LLIMModel::LLIMSession* im_session = LLIMModel::getInstance()->findIMSession(session_id); if (!im_session) return false; // [SL:KB] - Patch: Chat-GroupSnooze | Checked: 2012-06-16 (Catznip-3.3) // Only group sessions can be snoozed if ( (im_session->isGroupSessionType()) && (LLIMModel::LLIMSession::CLOSE_SNOOZE == im_session->mCloseAction) ) { static LLCachedControl s_nSnoozeTime(gSavedSettings, "GroupSnoozeTime", 900); snoozed_sessions_t::iterator itSession = mSnoozedSessions.find(session_id); F64 expirationTime = LLTimer::getTotalSeconds() + F64(s_nSnoozeTime); if (im_session->mSnoozeTime > -1) { expirationTime = LLTimer::getTotalSeconds() + F64(im_session->mSnoozeTime); im_session->mSnoozeTime = -1; } if (mSnoozedSessions.end() != itSession) itSession->second = expirationTime; else mSnoozedSessions.insert(std::pair(session_id, expirationTime)); } else { LLIMModel::getInstance()->sendLeaveSession(session_id, im_session->mOtherParticipantID); } // [/SL:KB] // LLIMModel::getInstance()->sendLeaveSession(session_id, im_session->mOtherParticipantID); gIMMgr->removeSession(session_id); return true; } // Removes data associated with a particular session specified by session_id void LLIMMgr::removeSession(const LLUUID& session_id) { llassert_always(hasSession(session_id)); clearPendingInvitation(session_id); clearPendingAgentListUpdates(session_id); LLIMModel::getInstance()->clearSession(session_id); LL_INFOS() << "LLIMMgr::removeSession, session removed, session id = " << session_id << LL_ENDL; notifyObserverSessionRemoved(session_id); } void LLIMMgr::inviteToSession( const LLUUID& session_id, const std::string& session_name, const LLUUID& caller_id, const std::string& caller_name, EInstantMessage type, EInvitationType inv_type, const std::string& session_handle, const std::string& session_uri) { std::string notify_box_type; // voice invite question is different from default only for group call (EXT-7118) std::string question_type = "VoiceInviteQuestionDefault"; BOOL voice_invite = FALSE; bool is_linden = LLMuteList::getInstance()->isLinden(caller_name); if(type == IM_SESSION_P2P_INVITE) { //P2P is different...they only have voice invitations notify_box_type = "VoiceInviteP2P"; voice_invite = TRUE; } else if ( gAgent.isInGroup(session_id) ) { //only really old school groups have voice invitations notify_box_type = "VoiceInviteGroup"; question_type = "VoiceInviteQuestionGroup"; voice_invite = TRUE; } else if ( inv_type == INVITATION_TYPE_VOICE ) { //else it's an ad-hoc //and a voice ad-hoc notify_box_type = "VoiceInviteAdHoc"; voice_invite = TRUE; } else if ( inv_type == INVITATION_TYPE_IMMEDIATE ) { notify_box_type = "InviteAdHoc"; } LLSD payload; payload["session_id"] = session_id; payload["session_name"] = session_name; payload["caller_id"] = caller_id; payload["caller_name"] = caller_name; payload["type"] = type; payload["inv_type"] = inv_type; payload["session_handle"] = session_handle; payload["session_uri"] = session_uri; payload["notify_box_type"] = notify_box_type; payload["question_type"] = question_type; //ignore invites from muted residents if (!is_linden) { if (LLMuteList::getInstance()->isMuted(caller_id, LLMute::flagVoiceChat) && voice_invite && "VoiceInviteQuestionDefault" == question_type) { LL_INFOS() << "Rejecting voice call from initiating muted resident " << caller_name << LL_ENDL; LLIncomingCallDialog::processCallResponse(1, payload); return; } else if (LLMuteList::getInstance()->isMuted(caller_id, LLMute::flagAll & ~LLMute::flagVoiceChat) && !voice_invite) { LL_INFOS() << "Rejecting session invite from initiating muted resident " << caller_name << LL_ENDL; return; } } LLVoiceChannel* channelp = LLVoiceChannel::getChannelByID(session_id); if (channelp && channelp->callStarted()) { // you have already started a call to the other user, so just accept the invite LLIncomingCallDialog::processCallResponse(0, payload); return; } if (voice_invite) { bool isRejectGroupCall = (gSavedSettings.getBOOL("VoiceCallsRejectGroup") && (notify_box_type == "VoiceInviteGroup")); bool isRejectNonFriendCall = (gSavedPerAccountSettings.getBOOL("VoiceCallsFriendsOnly") && (LLAvatarTracker::instance().getBuddyInfo(caller_id) == NULL)); // FIRE-6522: Options to automatically decline all group and personal voice chat requests // if (isRejectGroupCall || isRejectNonFriendCall || gAgent.isDoNotDisturb()) bool isRejectAdHocCall = (gSavedSettings.getBOOL("VoiceCallsRejectAdHoc") && (notify_box_type == "VoiceInviteAdHoc")); bool isRejectP2PCall = (gSavedSettings.getBOOL("VoiceCallsRejectP2P") && (notify_box_type == "VoiceInviteP2P")); if (isRejectGroupCall || isRejectNonFriendCall || gAgent.isDoNotDisturb() || isRejectAdHocCall || isRejectP2PCall) // { // FIRE-6522 // if (gAgent.isDoNotDisturb() && !isRejectGroupCall && !isRejectNonFriendCall) if (gAgent.isDoNotDisturb() && !isRejectGroupCall && !isRejectNonFriendCall && !isRejectAdHocCall && !isRejectP2PCall) // { if (!hasSession(session_id) && (type == IM_SESSION_P2P_INVITE)) { std::string fixed_session_name = caller_name; if(!session_name.empty() && session_name.size()>1) { fixed_session_name = session_name; } else { LLAvatarName av_name; if (LLAvatarNameCache::get(caller_id, &av_name)) { fixed_session_name = av_name.getDisplayName(); } } LLIMModel::getInstance()->newSession(session_id, fixed_session_name, IM_NOTHING_SPECIAL, caller_id, false, false); } LLSD args; addSystemMessage(session_id, "you_auto_rejected_call", args); send_do_not_disturb_message(gMessageSystem, caller_id, session_id); } // silently decline the call LLIncomingCallDialog::processCallResponse(1, payload); return; } } if ( !mPendingInvitations.has(session_id.asString()) ) { if (caller_name.empty()) { LLAvatarNameCache::get(caller_id, boost::bind(&LLIMMgr::onInviteNameLookup, payload, _1, _2)); } else { LLFloaterReg::showInstance("incoming_call", payload, FALSE); } // Add the caller to the Recent List here (at this point // "incoming_call" floater is shown and the recipient can // reject the call), because even if a recipient will reject // the call, the caller should be added to the recent list // anyway. STORM-507. if(type == IM_SESSION_P2P_INVITE) LLRecentPeople::instance().add(caller_id); mPendingInvitations[session_id.asString()] = LLSD(); } } void LLIMMgr::onInviteNameLookup(LLSD payload, const LLUUID& id, const LLAvatarName& av_name) { payload["caller_name"] = av_name.getUserName(); payload["session_name"] = payload["caller_name"].asString(); std::string notify_box_type = payload["notify_box_type"].asString(); LLFloaterReg::showInstance("incoming_call", payload, FALSE); } //*TODO disconnects all sessions void LLIMMgr::disconnectAllSessions() { //*TODO disconnects all IM sessions } BOOL LLIMMgr::hasSession(const LLUUID& session_id) { return LLIMModel::getInstance()->findIMSession(session_id) != NULL; } // [SL:KB] - Patch: Chat-GroupSnooze | Checked: 2012-06-16 (Catznip-3.3) bool LLIMMgr::checkSnoozeExpiration(const LLUUID& session_id) const { snoozed_sessions_t::const_iterator itSession = mSnoozedSessions.find(session_id); return (mSnoozedSessions.end() != itSession) && (itSession->second <= LLTimer::getTotalSeconds()); } bool LLIMMgr::isSnoozedSession(const LLUUID& session_id) const { return (mSnoozedSessions.end() != mSnoozedSessions.find(session_id)); } bool LLIMMgr::restoreSnoozedSession(const LLUUID& session_id) { snoozed_sessions_t::iterator itSession = mSnoozedSessions.find(session_id); if (mSnoozedSessions.end() != itSession) { mSnoozedSessions.erase(itSession); LLGroupData groupData; if (gAgent.getGroupData(session_id, groupData)) { gIMMgr->addSession(groupData.mName, IM_SESSION_INVITE, session_id); uuid_vec_t ids; LLIMModel::sendStartSession(session_id, session_id, ids, IM_SESSION_GROUP_START); if (!gAgent.isDoNotDisturb() && gSavedSettings.getU32("PlayModeUISndNewIncomingGroupIMSession") != 0) { make_ui_sound("UISndNewIncomingGroupIMSession"); } return true; } } return false; } // [/SL:KB] void LLIMMgr::clearPendingInvitation(const LLUUID& session_id) { if ( mPendingInvitations.has(session_id.asString()) ) { mPendingInvitations.erase(session_id.asString()); } } void LLIMMgr::processAgentListUpdates(const LLUUID& session_id, const LLSD& body) { // [FS communication UI] //LLFloaterIMSession* im_floater = LLFloaterIMSession::findInstance(session_id); FSFloaterIM* im_floater = FSFloaterIM::findInstance(session_id); // [FS communication UI] if ( im_floater ) { im_floater->processAgentListUpdates(body); } LLIMSpeakerMgr* speaker_mgr = LLIMModel::getInstance()->getSpeakerManager(session_id); if (speaker_mgr) { speaker_mgr->updateSpeakers(body); // also the same call is added into LLVoiceClient::participantUpdatedEvent because // sometimes it is called AFTER LLViewerChatterBoxSessionAgentListUpdates::post() // when moderation state changed too late. See EXT-3544. speaker_mgr->update(true); } else { //we don't have a speaker manager yet..something went wrong //we are probably receiving an update here before //a start or an acceptance of an invitation. Race condition. gIMMgr->addPendingAgentListUpdates( session_id, body); } } LLSD LLIMMgr::getPendingAgentListUpdates(const LLUUID& session_id) { if ( mPendingAgentListUpdates.has(session_id.asString()) ) { return mPendingAgentListUpdates[session_id.asString()]; } else { return LLSD(); } } void LLIMMgr::addPendingAgentListUpdates( const LLUUID& session_id, const LLSD& updates) { LLSD::map_const_iterator iter; if ( !mPendingAgentListUpdates.has(session_id.asString()) ) { //this is a new agent list update for this session mPendingAgentListUpdates[session_id.asString()] = LLSD::emptyMap(); } if ( updates.has("agent_updates") && updates["agent_updates"].isMap() && updates.has("updates") && updates["updates"].isMap() ) { //new school update LLSD update_types = LLSD::emptyArray(); LLSD::array_iterator array_iter; update_types.append("agent_updates"); update_types.append("updates"); for ( array_iter = update_types.beginArray(); array_iter != update_types.endArray(); ++array_iter) { //we only want to include the last update for a given agent for ( iter = updates[array_iter->asString()].beginMap(); iter != updates[array_iter->asString()].endMap(); ++iter) { mPendingAgentListUpdates[session_id.asString()][array_iter->asString()][iter->first] = iter->second; } } } else if ( updates.has("updates") && updates["updates"].isMap() ) { //old school update where the SD contained just mappings //of agent_id -> "LEAVE"/"ENTER" //only want to keep last update for each agent for ( iter = updates["updates"].beginMap(); iter != updates["updates"].endMap(); ++iter) { mPendingAgentListUpdates[session_id.asString()]["updates"][iter->first] = iter->second; } } } void LLIMMgr::clearPendingAgentListUpdates(const LLUUID& session_id) { if ( mPendingAgentListUpdates.has(session_id.asString()) ) { mPendingAgentListUpdates.erase(session_id.asString()); } } void LLIMMgr::notifyObserverSessionAdded(const LLUUID& session_id, const std::string& name, const LLUUID& other_participant_id, bool has_offline_msg) { for (session_observers_list_t::iterator it = mSessionObservers.begin(); it != mSessionObservers.end(); it++) { (*it)->sessionAdded(session_id, name, other_participant_id, has_offline_msg); } } void LLIMMgr::notifyObserverSessionActivated(const LLUUID& session_id, const std::string& name, const LLUUID& other_participant_id) { for (session_observers_list_t::iterator it = mSessionObservers.begin(); it != mSessionObservers.end(); it++) { (*it)->sessionActivated(session_id, name, other_participant_id); } } void LLIMMgr::notifyObserverSessionVoiceOrIMStarted(const LLUUID& session_id) { for (session_observers_list_t::iterator it = mSessionObservers.begin(); it != mSessionObservers.end(); it++) { (*it)->sessionVoiceOrIMStarted(session_id); } } void LLIMMgr::notifyObserverSessionRemoved(const LLUUID& session_id) { for (session_observers_list_t::iterator it = mSessionObservers.begin(); it != mSessionObservers.end(); it++) { (*it)->sessionRemoved(session_id); } } void LLIMMgr::notifyObserverSessionIDUpdated( const LLUUID& old_session_id, const LLUUID& new_session_id ) { for (session_observers_list_t::iterator it = mSessionObservers.begin(); it != mSessionObservers.end(); it++) { (*it)->sessionIDUpdated(old_session_id, new_session_id); } } void LLIMMgr::addSessionObserver(LLIMSessionObserver *observer) { mSessionObservers.push_back(observer); } void LLIMMgr::removeSessionObserver(LLIMSessionObserver *observer) { mSessionObservers.remove(observer); } bool LLIMMgr::startCall(const LLUUID& session_id, LLVoiceChannel::EDirection direction) { LLVoiceChannel* voice_channel = LLIMModel::getInstance()->getVoiceChannel(session_id); if (!voice_channel) return false; voice_channel->setCallDirection(direction); voice_channel->activate(); return true; } bool LLIMMgr::endCall(const LLUUID& session_id) { LLVoiceChannel* voice_channel = LLIMModel::getInstance()->getVoiceChannel(session_id); if (!voice_channel) return false; voice_channel->deactivate(); LLIMModel::LLIMSession* im_session = LLIMModel::getInstance()->findIMSession(session_id); if (im_session) { // need to update speakers' state im_session->mSpeakers->update(FALSE); } return true; } bool LLIMMgr::isVoiceCall(const LLUUID& session_id) { LLIMModel::LLIMSession* im_session = LLIMModel::getInstance()->findIMSession(session_id); if (!im_session) return false; return im_session->mStartedAsIMCall; } void LLIMMgr::updateDNDMessageStatus() { if (LLIMModel::getInstance()->mId2SessionMap.empty()) return; std::map::const_iterator it = LLIMModel::getInstance()->mId2SessionMap.begin(); for (; it != LLIMModel::getInstance()->mId2SessionMap.end(); ++it) { LLIMModel::LLIMSession* session = (*it).second; if (session->isP2P()) { setDNDMessageSent(session->mSessionID,false); } } } bool LLIMMgr::isDNDMessageSend(const LLUUID& session_id) { LLIMModel::LLIMSession* im_session = LLIMModel::getInstance()->findIMSession(session_id); if (!im_session) return false; return im_session->mIsDNDsend; } void LLIMMgr::setDNDMessageSent(const LLUUID& session_id, bool is_send) { LLIMModel::LLIMSession* im_session = LLIMModel::getInstance()->findIMSession(session_id); if (!im_session) return; im_session->mIsDNDsend = is_send; } void LLIMMgr::addNotifiedNonFriendSessionID(const LLUUID& session_id) { mNotifiedNonFriendSessions.insert(session_id); } bool LLIMMgr::isNonFriendSessionNotified(const LLUUID& session_id) { return mNotifiedNonFriendSessions.end() != mNotifiedNonFriendSessions.find(session_id); } void LLIMMgr::noteOfflineUsers( const LLUUID& session_id, const std::vector& ids) { S32 count = ids.size(); if(count == 0) { const std::string& only_user = LLTrans::getString("only_user_message"); LLIMModel::getInstance()->addMessage(session_id, SYSTEM_FROM, LLUUID::null, only_user); } else { const LLRelationship* info = NULL; LLAvatarTracker& at = LLAvatarTracker::instance(); LLIMModel& im_model = LLIMModel::instance(); for(S32 i = 0; i < count; ++i) { info = at.getBuddyInfo(ids.at(i)); LLAvatarName av_name; if (info && !info->isOnline() && LLAvatarNameCache::get(ids.at(i), &av_name)) { LLUIString offline = LLTrans::getString("offline_message"); // Use display name only because this user is your friend // Ansariel: No please! Take preference settings into account! if ((gSavedSettings.getBOOL("NameTagShowUsernames")) && (gSavedSettings.getBOOL("UseDisplayNames"))) { offline.setArg("[NAME]", av_name.getCompleteName()); } else if (gSavedSettings.getBOOL("UseDisplayNames")) { offline.setArg("[NAME]", av_name.getDisplayName()); } else { offline.setArg("[NAME]", av_name.getUserNameForDisplay()); } im_model.proccessOnlineOfflineNotification(session_id, offline); } } } } void LLIMMgr::noteMutedUsers(const LLUUID& session_id, const std::vector& ids) { // Don't do this if we don't have a mute list. LLMuteList *ml = LLMuteList::getInstance(); if( !ml ) { return; } S32 count = ids.size(); if(count > 0) { LLIMModel* im_model = LLIMModel::getInstance(); for(S32 i = 0; i < count; ++i) { if( ml->isMuted(ids.at(i)) ) { LLUIString muted = LLTrans::getString("muted_message"); im_model->addMessage(session_id, SYSTEM_FROM, LLUUID::null, muted); break; } } } } void LLIMMgr::processIMTypingStart(const LLUUID& from_id, const EInstantMessage im_type) { processIMTypingCore(from_id, im_type, TRUE); } void LLIMMgr::processIMTypingStop(const LLUUID& from_id, const EInstantMessage im_type) { processIMTypingCore(from_id, im_type, FALSE); } // Announce incoming IMs void typingNameCallback(const LLUUID& av_id, const LLAvatarName& av_name, const LLUUID& session_id) { LLStringUtil::format_map_t args; args["[NAME]"] = av_name.getCompleteName(); BOOL is_muted = LLMuteList::getInstance()->isMuted(av_id, av_name.getCompleteName(), LLMute::flagTextChat); bool is_friend = (LLAvatarTracker::instance().getBuddyInfo(av_id) == NULL) ? false : true; static LLCachedControl VoiceCallsFriendsOnly(gSavedPerAccountSettings, "VoiceCallsFriendsOnly"); if (!is_muted && ( (VoiceCallsFriendsOnly && is_friend) || !VoiceCallsFriendsOnly )) { gIMMgr->addMessage( session_id, av_id, SYSTEM_FROM, // Use system name instead of NULL Growl notifier acts funny with NULL here. LLTrans::getString("IM_announce_incoming", args), false, LLStringUtil::null, IM_NOTHING_SPECIAL, 0, LLUUID::null, LLVector3::zero, false, true ); } // Send busy and auto-response messages now or they won't be send // later because a session has already been created by showing the // incoming IM announcement. // The logic was originally copied from process_improved_im() in llviewermessage.cpp bool is_busy = gAgent.isDoNotDisturb(); BOOL is_autorespond = gAgent.getAutorespond(); BOOL is_autorespond_nonfriends = gAgent.getAutorespondNonFriends(); BOOL is_autorespond_muted = gSavedPerAccountSettings.getBOOL("FSSendMutedAvatarResponse"); BOOL is_linden = LLMuteList::getInstance()->isLinden(av_name.getAccountName()); static LLCachedControl FSSendAwayAvatarResponse(gSavedPerAccountSettings, "FSSendAwayAvatarResponse"); BOOL is_afk = gAgent.getAFK(); if (RlvActions::canReceiveIM(av_id) && !is_linden && (!VoiceCallsFriendsOnly || is_friend) && ((is_busy && (!is_muted || (is_muted && !is_autorespond_muted))) || (is_autorespond && !is_muted) || (is_autorespond_nonfriends && !is_friend && !is_muted) || (FSSendAwayAvatarResponse && is_afk && !is_muted)) ) { std::string my_name; std::string response; LLAgentUI::buildFullname(my_name); if (is_busy) { response = gSavedPerAccountSettings.getString("DoNotDisturbModeResponse"); } else if (is_autorespond_nonfriends && !is_friend) { response = gSavedPerAccountSettings.getString("FSAutorespondNonFriendsResponse"); } else if (is_autorespond) { response = gSavedPerAccountSettings.getString("FSAutorespondModeResponse"); } else if (is_afk && FSSendAwayAvatarResponse) { response = gSavedPerAccountSettings.getString("FSAwayAvatarResponse"); } else { LL_WARNS() << "Unknown auto-response mode" << LL_ENDL; } pack_instant_message( gMessageSystem, gAgent.getID(), FALSE, gAgent.getSessionID(), av_id, my_name, response, IM_ONLINE, IM_DO_NOT_DISTURB_AUTO_RESPONSE, session_id); gAgent.sendReliableMessage(); LLStringUtil::format_map_t args; args["MESSAGE"] = response; gIMMgr->addMessage( session_id, gAgentID, LLStringUtil::null, // Pass null value so no name gets prepended LLTrans::getString("IM_autoresponse_sent", args), false, LLStringUtil::null, IM_NOTHING_SPECIAL, 0, LLUUID::null, LLVector3::zero, false, true ); // Send inventory item on autoresponse LLUUID item_id(gSavedPerAccountSettings.getString("FSAutoresponseItemUUID")); if (item_id.notNull()) { LLInventoryItem* item = dynamic_cast(gInventory.getItem(item_id)); if (item) { gIMMgr->addMessage( session_id, gAgentID, LLStringUtil::null, // Pass null value so no name gets prepended LLTrans::getString("IM_autoresponse_item_sent", LLSD().with("[ITEM_NAME]", item->getName())), false, LLStringUtil::null, IM_NOTHING_SPECIAL, 0, LLUUID::null, LLVector3::zero, false, true); LLGiveInventory::doGiveInventoryItem(av_id, item, session_id); } } } } // void LLIMMgr::processIMTypingCore(const LLUUID& from_id, const EInstantMessage im_type, BOOL typing) { LLUUID session_id = computeSessionID(im_type, from_id); // Announce incoming IMs static LLCachedControl announceIncomingIM(gSavedSettings, "FSAnnounceIncomingIM"); if (typing && !gIMMgr->hasSession(session_id) && announceIncomingIM) { LLAvatarNameCache::get(from_id, boost::bind(&typingNameCallback, _1, _2, session_id)); } // // [FS communication UI] //LLFloaterIMSession* im_floater = LLFloaterIMSession::findInstance(session_id); FSFloaterIM* im_floater = FSFloaterIM::findInstance(session_id); // [FS communication UI] if ( im_floater ) { im_floater->processIMTyping(from_id, typing); } } class LLViewerChatterBoxSessionStartReply : public LLHTTPNode { public: virtual void describe(Description& desc) const { desc.shortInfo("Used for receiving a reply to a request to initialize an ChatterBox session"); desc.postAPI(); desc.input( "{\"client_session_id\": UUID, \"session_id\": UUID, \"success\" boolean, \"reason\": string"); desc.source(__FILE__, __LINE__); } virtual void post(ResponsePtr response, const LLSD& context, const LLSD& input) const { LLSD body; LLUUID temp_session_id; LLUUID session_id; bool success; body = input["body"]; success = body["success"].asBoolean(); temp_session_id = body["temp_session_id"].asUUID(); if ( success ) { session_id = body["session_id"].asUUID(); LLIMModel::getInstance()->processSessionInitializedReply(temp_session_id, session_id); LLIMSpeakerMgr* speaker_mgr = LLIMModel::getInstance()->getSpeakerManager(session_id); if (speaker_mgr) { speaker_mgr->setSpeakers(body); speaker_mgr->updateSpeakers(gIMMgr->getPendingAgentListUpdates(session_id)); } // [FS communication UI] //LLFloaterIMSession* im_floater = LLFloaterIMSession::findInstance(session_id); FSFloaterIM* im_floater = FSFloaterIM::findInstance(session_id); // [FS communication UI] if ( im_floater ) { if ( body.has("session_info") ) { im_floater->processSessionUpdate(body["session_info"]); } } gIMMgr->clearPendingAgentListUpdates(session_id); } else { //throw an error dialog and close the temp session's floater gIMMgr->showSessionStartError(body["error"].asString(), temp_session_id); } gIMMgr->clearPendingAgentListUpdates(session_id); } }; class LLViewerChatterBoxSessionEventReply : public LLHTTPNode { public: virtual void describe(Description& desc) const { desc.shortInfo("Used for receiving a reply to a ChatterBox session event"); desc.postAPI(); desc.input( "{\"event\": string, \"reason\": string, \"success\": boolean, \"session_id\": UUID"); desc.source(__FILE__, __LINE__); } virtual void post(ResponsePtr response, const LLSD& context, const LLSD& input) const { LLUUID session_id; bool success; LLSD body = input["body"]; success = body["success"].asBoolean(); session_id = body["session_id"].asUUID(); if ( !success ) { //throw an error dialog gIMMgr->showSessionEventError( body["event"].asString(), body["error"].asString(), session_id); } } }; class LLViewerForceCloseChatterBoxSession: public LLHTTPNode { public: virtual void post(ResponsePtr response, const LLSD& context, const LLSD& input) const { LLUUID session_id; std::string reason; session_id = input["body"]["session_id"].asUUID(); reason = input["body"]["reason"].asString(); gIMMgr->showSessionForceClose(reason, session_id); } }; class LLViewerChatterBoxSessionAgentListUpdates : public LLHTTPNode { public: virtual void post( ResponsePtr responder, const LLSD& context, const LLSD& input) const { const LLUUID& session_id = input["body"]["session_id"].asUUID(); gIMMgr->processAgentListUpdates(session_id, input["body"]); } }; class LLViewerChatterBoxSessionUpdate : public LLHTTPNode { public: virtual void post( ResponsePtr responder, const LLSD& context, const LLSD& input) const { LLUUID session_id = input["body"]["session_id"].asUUID(); // [FS communication UI] //LLFloaterIMSession* im_floater = LLFloaterIMSession::findInstance(session_id); FSFloaterIM* im_floater = FSFloaterIM::findInstance(session_id); // [FS communication UI] if ( im_floater ) { im_floater->processSessionUpdate(input["body"]["info"]); } LLIMSpeakerMgr* im_mgr = LLIMModel::getInstance()->getSpeakerManager(session_id); if (im_mgr) { im_mgr->processSessionUpdate(input["body"]["info"]); } } }; class LLViewerChatterBoxInvitation : public LLHTTPNode { public: virtual void post( ResponsePtr response, const LLSD& context, const LLSD& input) const { //for backwards compatiblity reasons...we need to still //check for 'text' or 'voice' invitations...bleh if ( input["body"].has("instantmessage") ) { LLSD message_params = input["body"]["instantmessage"]["message_params"]; //do something here to have the IM invite behave //just like a normal IM //this is just replicated code from process_improved_im //and should really go in it's own function -jwolk std::string message = message_params["message"].asString(); std::string name = message_params["from_name"].asString(); LLUUID from_id = message_params["from_id"].asUUID(); LLUUID session_id = message_params["id"].asUUID(); std::vector bin_bucket = message_params["data"]["binary_bucket"].asBinary(); U8 offline = (U8)message_params["offline"].asInteger(); time_t timestamp = (time_t) message_params["timestamp"].asInteger(); BOOL is_do_not_disturb = gAgent.isDoNotDisturb(); //don't return if user is muted b/c proper way to ignore a muted user who //initiated an adhoc/group conference is to create then leave the session (see STORM-1731) if (is_do_not_disturb) { return; } // [RLVa:KB] - Checked: 2010-11-30 (RLVa-1.3.0) if ( (RlvActions::hasBehaviour(RLV_BHVR_RECVIM)) || (RlvActions::hasBehaviour(RLV_BHVR_RECVIMFROM)) ) { if (gAgent.isInGroup(session_id)) // Group chat: don't accept the invite if not an exception { if (!RlvActions::canReceiveIM(session_id)) return; } else if (!RlvActions::canReceiveIM(from_id)) // Conference chat: don't block; censor if not an exception { message = RlvStrings::getString(RlvStringKeys::Blocked::RecvIm); } } // [/RLVa:KB] // Mute group chat port from Phoenix if (from_id != gAgentID) // FIRE-14222: OpenSim routes agent's chat through here - don't mute it! { BOOL FSMuteAllGroups = gSavedSettings.getBOOL("FSMuteAllGroups"); BOOL FSMuteGroupWhenNoticesDisabled = gSavedSettings.getBOOL("FSMuteGroupWhenNoticesDisabled"); LLGroupData group_data; if (gAgent.getGroupData(session_id, group_data)) { if (FSMuteAllGroups || (FSMuteGroupWhenNoticesDisabled && !group_data.mAcceptNotices)) { LL_INFOS() << "Muting group chat: " << group_data.mName << LL_ENDL; if (gSavedSettings.getBOOL("FSReportMutedGroupChat")) { LLStringUtil::format_map_t args; args["NAME"] = LLSLURL("group", session_id, "about").getSLURLString(); report_to_nearby_chat(LLTrans::getString("GroupChatMuteNotice", args)); } //KC: make sure we leave the group chat at the server end as well std::string aname; gAgent.buildFullname(aname); pack_instant_message( gMessageSystem, gAgentID, FALSE, gAgentSessionID, from_id, aname, LLStringUtil::null, IM_ONLINE, IM_SESSION_LEAVE, session_id); gAgent.sendReliableMessage(); gIMMgr->leaveSession(session_id); return; } } // Groupdata debug else { LL_INFOS("Agent_GroupData") << "GROUPDEBUG: Group chat mute: No agent group data for group " << session_id.asString() << LL_ENDL; } // } // Mute group chat port from Phoenix // standard message, not from system std::string saved; if(offline == IM_OFFLINE) { LLStringUtil::format_map_t args; args["[LONG_TIMESTAMP]"] = formatted_time(timestamp); saved = LLTrans::getString("Saved_message", args); } std::string buffer = saved + message; // FIRE-9762 - Don't bail here on OpenSim, we'll need to echo local posts #ifdef OPENSIM bool is_opensim = LLGridManager::getInstance()->isInOpenSim(); if (!is_opensim && from_id == gAgentID) #else // OPENSIM if(from_id == gAgentID) #endif // OPENSIM // { return; } gIMMgr->addMessage( session_id, from_id, name, buffer, IM_OFFLINE == offline, std::string((char*)&bin_bucket[0]), IM_SESSION_INVITE, message_params["parent_estate_id"].asInteger(), message_params["region_id"].asUUID(), ll_vector3_from_sd(message_params["position"]), true); // FIRE-9762 - OK, return here if we must! #ifdef OPENSIM if (is_opensim && from_id == gAgentID) { return; } #endif // OPENSIM // if (LLMuteList::getInstance()->isMuted(from_id, name, LLMute::flagTextChat)) { return; } //K now we want to accept the invitation std::string url = gAgent.getRegionCapability("ChatSessionRequest"); if ( url != "" ) { LLCoros::instance().launch("chatterBoxInvitationCoro", boost::bind(&chatterBoxInvitationCoro, url, session_id, LLIMMgr::INVITATION_TYPE_INSTANT_MESSAGE)); } } //end if invitation has instant message else if ( input["body"].has("voice") ) { if(!LLVoiceClient::getInstance()->voiceEnabled() || !LLVoiceClient::getInstance()->isVoiceWorking()) { // Don't display voice invites unless the user has voice enabled. return; } gIMMgr->inviteToSession( input["body"]["session_id"].asUUID(), input["body"]["session_name"].asString(), input["body"]["from_id"].asUUID(), input["body"]["from_name"].asString(), IM_SESSION_INVITE, LLIMMgr::INVITATION_TYPE_VOICE); } else if ( input["body"].has("immediate") ) { gIMMgr->inviteToSession( input["body"]["session_id"].asUUID(), input["body"]["session_name"].asString(), input["body"]["from_id"].asUUID(), input["body"]["from_name"].asString(), IM_SESSION_INVITE, LLIMMgr::INVITATION_TYPE_IMMEDIATE); } } }; LLHTTPRegistration gHTTPRegistrationMessageChatterboxsessionstartreply( "/message/ChatterBoxSessionStartReply"); LLHTTPRegistration gHTTPRegistrationMessageChatterboxsessioneventreply( "/message/ChatterBoxSessionEventReply"); LLHTTPRegistration gHTTPRegistrationMessageForceclosechatterboxsession( "/message/ForceCloseChatterBoxSession"); LLHTTPRegistration gHTTPRegistrationMessageChatterboxsessionagentlistupdates( "/message/ChatterBoxSessionAgentListUpdates"); LLHTTPRegistration gHTTPRegistrationMessageChatterBoxSessionUpdate( "/message/ChatterBoxSessionUpdate"); LLHTTPRegistration gHTTPRegistrationMessageChatterBoxInvitation( "/message/ChatterBoxInvitation");