/** * @file LLIMProcessing.cpp * @brief Container for Instant Messaging * * $LicenseInfo:firstyear=2001&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2018, 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 "llimprocessing.h" #include "llagent.h" #include "llappviewer.h" #include "llavatarnamecache.h" #include "llfirstuse.h" #include "llfloaterreg.h" // [FS communication UI] //#include "llfloaterimnearbychat.h" #include "fsfloaternearbychat.h" // [FS communication UI] #include "llimview.h" #include "llinventoryobserver.h" #include "llinventorymodel.h" #include "llmutelist.h" #include "llnotifications.h" #include "llnotificationsutil.h" #include "llnotificationmanager.h" #include "llpanelgroup.h" #include "llregex.h" #include "llregionhandle.h" #include "llsdserialize.h" #include "llslurl.h" #include "llstring.h" #include "lltoastnotifypanel.h" #include "lltrans.h" #include "llviewergenericmessage.h" #include "llviewerobjectlist.h" #include "llviewermessage.h" #include "llviewerwindow.h" #include "llviewerregion.h" #include "llvoavatarself.h" #include "llworld.h" #include "boost/lexical_cast.hpp" // [RLVa:KB] - Checked: 2010-03-09 (RLVa-1.2.0a) #include "rlvactions.h" #include "rlvhandler.h" #include "rlvinventory.h" #include "rlvui.h" // [/RLVa:KB] // Firestorm includes #include "exogroupmutelist.h" #include "fscommon.h" #include "fsdata.h" #include "fskeywords.h" #include "llagentui.h" #include "llavataractions.h" #include "llgiveinventory.h" #include "lllandmarkactions.h" #include "llviewernetwork.h" #include "sound_ids.h" #include "NACLantispam.h" #if LL_MSVC // disable boost::lexical_cast warning #pragma warning (disable:4702) #endif extern void on_new_message(const LLSD& msg); // Strip out "Resident" for display, but only if the message came from a user // (rather than a script) static std::string clean_name_from_im(const std::string& name, EInstantMessage type) { switch (type) { case IM_NOTHING_SPECIAL: case IM_MESSAGEBOX: case IM_GROUP_INVITATION: case IM_INVENTORY_OFFERED: case IM_INVENTORY_ACCEPTED: case IM_INVENTORY_DECLINED: case IM_GROUP_VOTE: case IM_GROUP_MESSAGE_DEPRECATED: //IM_TASK_INVENTORY_OFFERED //IM_TASK_INVENTORY_ACCEPTED //IM_TASK_INVENTORY_DECLINED case IM_NEW_USER_DEFAULT: case IM_SESSION_INVITE: case IM_SESSION_P2P_INVITE: case IM_SESSION_GROUP_START: case IM_SESSION_CONFERENCE_START: case IM_SESSION_SEND: case IM_SESSION_LEAVE: //IM_FROM_TASK case IM_DO_NOT_DISTURB_AUTO_RESPONSE: case IM_CONSOLE_AND_CHAT_HISTORY: case IM_LURE_USER: case IM_LURE_ACCEPTED: case IM_LURE_DECLINED: case IM_GODLIKE_LURE_USER: case IM_TELEPORT_REQUEST: case IM_GROUP_ELECTION_DEPRECATED: //IM_GOTO_URL //IM_FROM_TASK_AS_ALERT case IM_GROUP_NOTICE: case IM_GROUP_NOTICE_INVENTORY_ACCEPTED: case IM_GROUP_NOTICE_INVENTORY_DECLINED: case IM_GROUP_INVITATION_ACCEPT: case IM_GROUP_INVITATION_DECLINE: case IM_GROUP_NOTICE_REQUESTED: case IM_FRIENDSHIP_OFFERED: case IM_FRIENDSHIP_ACCEPTED: case IM_FRIENDSHIP_DECLINED_DEPRECATED: //IM_TYPING_START //IM_TYPING_STOP return LLCacheName::cleanFullName(name); default: return name; } } static std::string clean_name_from_task_im(const std::string& msg, BOOL from_group) { boost::smatch match; static const boost::regex returned_exp( "(.*been returned to your inventory lost and found folder by )(.+)( (from|near).*)"); if (ll_regex_match(msg, match, returned_exp)) { // match objects are 1-based for groups std::string final = match[1].str(); std::string name = match[2].str(); // Don't try to clean up group names if (!from_group) { final += LLCacheName::buildUsername(name); } final += match[3].str(); return final; } return msg; } const std::string NOT_ONLINE_MSG("User not online - message will be stored and delivered later."); const std::string NOT_ONLINE_INVENTORY("User not online - inventory has been saved."); void translate_if_needed(std::string& message) { if (message == NOT_ONLINE_MSG) { message = LLTrans::getString("not_online_msg"); } else if (message == NOT_ONLINE_INVENTORY) { message = LLTrans::getString("not_online_inventory"); } } class LLPostponedIMSystemTipNotification : public LLPostponedNotification { protected: /* virtual */ void modifyNotificationParams() { LLSD payload = mParams.payload; // FIRE-29943: Item shared messaged logging to wrong IM logfile if user is offline //payload["SESSION_NAME"] = mName; LLAvatarName av_name; // This should work since modifyNotificationParams() is invoked after we already // retrieved the avatar name if (mFromId.notNull() && LLAvatarNameCache::instance().getName(mFromId, &av_name)) { // LLHandlerUtil::logToIM() will transform this into the correct filename payload["SESSION_NAME"] = av_name.getLegacyName(); } else { payload["SESSION_NAME"] = mName; } // mParams.payload = payload; } }; class LLPostponedOfferNotification : public LLPostponedNotification { protected: /* virtual */ void modifyNotificationParams() { LLSD substitutions = mParams.substitutions; substitutions["NAME"] = mName; mParams.substitutions = substitutions; } }; void inventory_offer_handler(LLOfferInfo* info) { // If muted, don't even go through the messaging stuff. Just curtail the offer here. // Passing in a null UUID handles the case of where you have muted one of your own objects by_name. // The solution for STORM-1297 seems to handle the cases where the object is owned by someone else. if (LLMuteList::getInstance()->isMuted(info->mFromID, info->mFromName) || LLMuteList::getInstance()->isMuted(LLUUID::null, info->mFromName)) { info->forceResponse(IOR_MUTE); return; } bool bAutoAccept(false); // Avoid the Accept/Discard dialog if the user so desires. JC // Auto-accept any kind of inventory (FIRE-4128) //if (gSavedSettings.getBOOL("AutoAcceptNewInventory") // && (info->mType == LLAssetType::AT_NOTECARD // || info->mType == LLAssetType::AT_LANDMARK // || info->mType == LLAssetType::AT_TEXTURE)) // if (gSavedSettings.getBOOL("AutoAcceptNewInventory")) // Auto-accept any kind of inventory (FIRE-4128) // [RLVa:KB] // Don't auto-accept give-to-RLV inventory offers if ( (gSavedSettings.getBOOL("AutoAcceptNewInventory")) && ( (!rlv_handler_t::isEnabled()) || (!RlvInventory::instance().isGiveToRLVOffer(*info)) ) ) // [/RLVa:KB] { // For certain types, just accept the items into the inventory, // and possibly open them on receipt depending upon "ShowNewInventory". bAutoAccept = true; } // Strip any SLURL from the message display. (DEV-2754) std::string msg = info->mDesc; int indx = msg.find(" ( http://slurl.com/secondlife/"); if (indx == std::string::npos) { // try to find new slurl host indx = msg.find(" ( http://maps.secondlife.com/secondlife/"); } if (indx >= 0) { LLStringUtil::truncate(msg, indx); } LLSD args; args["[OBJECTNAME]"] = msg; LLSD payload; // must protect against a NULL return from lookupHumanReadable() std::string typestr = ll_safe_string(LLAssetType::lookupHumanReadable(info->mType)); if (!typestr.empty()) { // human readable matches string name from strings.xml // lets get asset type localized name args["OBJECTTYPE"] = LLTrans::getString(typestr); } else { LL_WARNS("Messaging") << "LLAssetType::lookupHumanReadable() returned NULL - probably bad asset type: " << info->mType << LL_ENDL; args["OBJECTTYPE"] = ""; // This seems safest, rather than propagating bogosity LL_WARNS("Messaging") << "Forcing an inventory-decline for probably-bad asset type." << LL_ENDL; info->forceResponse(IOR_DECLINE); return; } // If mObjectID is null then generate the object_id based on msg to prevent // multiple creation of chiclets for same object. LLUUID object_id = info->mObjectID; if (object_id.isNull()) object_id.generate(msg); payload["from_id"] = info->mFromID; // Needed by LLScriptFloaterManager to bind original notification with // faked for toast one. payload["object_id"] = object_id; // Flag indicating that this notification is faked for toast. payload["give_inventory_notification"] = FALSE; args["OBJECTFROMNAME"] = info->mFromName; args["NAME"] = info->mFromName; if (info->mFromGroup) { args["NAME_SLURL"] = LLSLURL("group", info->mFromID, "about").getSLURLString(); } else { // [SL:KB] - Patch: UI-Notifications | Checked: 2011-04-11 (Catznip-2.5.0a) | Added: Catznip-2.5.0a args["NAME_LABEL"] = LLSLURL("agent", info->mFromID, "completename").getSLURLString(); // [/SL:KB] args["NAME_SLURL"] = LLSLURL("agent", info->mFromID, "about").getSLURLString(); } std::string verb = "select?name=" + LLURI::escape(msg); args["ITEM_SLURL"] = LLSLURL("inventory", info->mObjectID, verb.c_str()).getSLURLString(); LLNotification::Params p; // Object -> Agent Inventory Offer if (info->mFromObject && !bAutoAccept) { // [RLVa:KB] - Checked: RLVa-1.2.2 // Only filter if the object owner is a nearby agent if ( (RlvActions::isRlvEnabled()) && (!RlvActions::canShowName(RlvActions::SNC_DEFAULT, info->mFromID)) && (RlvUtil::isNearbyAgent(info->mFromID)) ) { payload["rlv_shownames"] = TRUE; args["NAME_SLURL"] = LLSLURL("agent", info->mFromID, "rlvanonym").getSLURLString(); } // [/RLVa:KB] // Inventory Slurls don't currently work for non agent transfers, so only display the object name. args["ITEM_SLURL"] = msg; // Note: sets inventory_task_offer_callback as the callback p.substitutions(args).payload(payload).functor.responder(LLNotificationResponderPtr(info)); info->mPersist = true; // Offers from your own objects need a special notification template. p.name = info->mFromID == gAgentID ? "OwnObjectGiveItem" : "ObjectGiveItem"; // Pop up inv offer chiclet and let the user accept (keep), or reject (and silently delete) the inventory. LLPostponedNotification::add(p, info->mFromID, info->mFromGroup == TRUE); } else // Agent -> Agent Inventory Offer { // [RLVa:KB] - Checked: RLVa-2.0.1 // Only filter if the offer is from a nearby agent and if there's no open IM session (doesn't necessarily have to be focused) bool fRlvCanShowName = (!RlvActions::isRlvEnabled()) || (RlvActions::canShowName(RlvActions::SNC_DEFAULT, info->mFromID)) || (!RlvUtil::isNearbyAgent(info->mFromID)) || (RlvUIEnabler::hasOpenIM(info->mFromID)) || (RlvUIEnabler::hasOpenProfile(info->mFromID)); if (!fRlvCanShowName) { payload["rlv_shownames"] = TRUE; LLAvatarName av_name; if (LLAvatarNameCache::get(info->mFromID, &av_name)) { args["NAME"] = RlvStrings::getAnonym(av_name); } else { args["NAME"] = RlvStrings::getAnonym(info->mFromName); } args["NAME_SLURL"] = LLSLURL("agent", info->mFromID, "rlvanonym").getSLURLString(); } // [/RLVa:KB] p.responder = info; // Note: sets inventory_offer_callback as the callback // *TODO fix memory leak // inventory_offer_callback() is not invoked if user received notification and // closes viewer(without responding the notification) p.substitutions(args).payload(payload).functor.responder(LLNotificationResponderPtr(info)); info->mPersist = true; // FIRE-3832: Silent accept/decline of inventory offers //p.name = "UserGiveItem"; p.name = (gSavedSettings.getBOOL("FSUseLegacyInventoryAcceptMessages") ? "UserGiveItemLegacy" : "UserGiveItem"); // p.offer_from_agent = true; // Prefetch the item into your local inventory. LLInventoryFetchItemsObserver* fetch_item = new LLInventoryFetchItemsObserver(info->mObjectID); fetch_item->startFetch(); if(fetch_item->isFinished()) { fetch_item->done(); } else { gInventory.addObserver(fetch_item); } // In viewer 2 we're now auto receiving inventory offers and messaging as such (not sending reject messages). // Optional V1-like inventory accept messages //info->send_auto_receive_response(); // Also needs to be send on auto-accept so the item gets into the inventory! if (bAutoAccept || !gSavedSettings.getBOOL("FSUseLegacyInventoryAcceptMessages")) { info->send_auto_receive_response(); } // Optional V1-like inventory accept messages if (gAgent.isDoNotDisturb()) { send_do_not_disturb_message(gMessageSystem, info->mFromID); } if (!bAutoAccept) // if we auto accept, do not pester the user { // Inform user that there is a script floater via toast system payload["give_inventory_notification"] = TRUE; p.payload = payload; LLPostponedNotification::add(p, info->mFromID, false); } // FIRE-19540: Log auto-accepted inventory to nearby chat else if (gSavedSettings.getBOOL("FSLogAutoAcceptInventoryToChat")) { std::string message_type; LLStringUtil::format_map_t chat_args; chat_args["OBJECT_TYPE"] = (!typestr.empty() ? LLTrans::getString(typestr) : ""); chat_args["DESC"] = msg; if (info->mFromObject) { std::string str_pos; std::string::size_type idx_start = info->mDesc.rfind(" ( http://"); std::string::size_type idx_end = info->mDesc.find(" )", idx_start); if (idx_start != std::string::npos && idx_end != std::string::npos) { LLSLURL url_pos(info->mDesc.substr(idx_start + 3, idx_end - (idx_start + 3))); str_pos = "&slurl=" + LLURI::escape(url_pos.getLocationString()); } chat_args["OBJECT_NAME"] = "[" + LLSLURL("objectim", info->mObjectID, "").getSLURLString() + "?name=" + LLURI::escape(info->mFromName) + "&owner=" + info->mFromID.asString() + (info->mFromGroup ? "&groupowned=true" : "") + str_pos + " " + info->mFromName + "]"; message_type = "InvOfferAutoAcceptObject"; } else { bool fRlvCanShowName = (!RlvActions::isRlvEnabled()) || (RlvActions::canShowName(RlvActions::SNC_DEFAULT, info->mFromID)) || (!RlvUtil::isNearbyAgent(info->mFromID)) || (RlvUIEnabler::hasOpenIM(info->mFromID)) || (RlvUIEnabler::hasOpenProfile(info->mFromID)); std::string name_slurl = LLSLURL("agent", info->mFromID, (fRlvCanShowName ? "inspect" : "rlvanonym")).getSLURLString(); chat_args["USER_NAME"] = name_slurl; message_type = "InvOfferAutoAcceptUser"; } report_to_nearby_chat(LLTrans::getString(message_type, chat_args)); } // // Show offered inventory also if auto-accept is enabled (FIRE-5101) if (bAutoAccept && gSavedSettings.getBOOL("ShowNewInventory")) { LLViewerInventoryCategory* catp = NULL; catp = (LLViewerInventoryCategory*)gInventory.getCategory(info->mObjectID); LLViewerInventoryItem* itemp = NULL; if(!catp) { itemp = (LLViewerInventoryItem*)gInventory.getItem(info->mObjectID); } LLOpenAgentOffer* open_agent_offer = new LLOpenAgentOffer(info->mObjectID, info->mFromName, false); open_agent_offer->startFetch(); if(catp || (itemp && itemp->isFinished())) { open_agent_offer->done(); } else { gInventory.addObserver(open_agent_offer); } } // Show offered inventory also if auto-accept is enabled (FIRE-5101) } LLFirstUse::newInventory(); } // Callback for name resolution of a god/estate message static void god_message_name_cb(const LLAvatarName& av_name, LLChat chat, std::string message) { LLSD args; args["NAME"] = av_name.getCompleteName(); args["MESSAGE"] = message; LLNotificationsUtil::add("GodMessage", args); // Treat like a system message and put in chat history. chat.mSourceType = CHAT_SOURCE_SYSTEM; chat.mText = message; // [FS communication UI] //LLFloaterIMNearbyChat* nearby_chat = LLFloaterReg::getTypedInstance("nearby_chat"); FSFloaterNearbyChat* nearby_chat = FSFloaterNearbyChat::getInstance(); // [FS communication UI] if (nearby_chat) { nearby_chat->addMessage(chat); } } // FIRE-505: Group name not shown in notification well static void notification_group_name_cb(const std::string& group_name, const std::string& sender, const std::string& subject, const std::string& message, const LLSD& payload, U32 timestamp) { LLAvatarName av_name; av_name.fromString(sender); LLSD args; args["SENDER"] = av_name.getUserNameForDisplay(); args["GROUP"] = group_name; args["SUBJECT"] = subject; args["MESSAGE"] = message; LLDate notice_date = LLDate(timestamp).notNull() ? LLDate(timestamp) : LLDate::now(); LLNotifications::instance().add(LLNotification::Params("GroupNotice").substitutions(args).payload(payload).time_stamp(notice_date)); make_ui_sound("UISndGroupNotice"); // Group notice sound } // // FIRE-6786: Always show teleport location in teleport offer static void teleport_region_info_cb(const std::string& slurl, LLSD args, const LLSD& payload, const LLUUID& from_id, const LLUUID& session_id, bool can_user_access_dst_region, bool does_user_require_maturity_increase) { if (gRlvHandler.hasBehaviour(RLV_BHVR_SHOWLOC)) { args["POS_SLURL"] = RlvStrings::getString(RlvStringKeys::Hidden::Generic); } else { args["POS_SLURL"] = slurl; } LLNotification::Params params; if (!can_user_access_dst_region) { params.name = "TeleportOffered_MaturityBlocked_SLUrl"; send_simple_im(from_id, LLTrans::getString("TeleportMaturityExceeded"), IM_NOTHING_SPECIAL, session_id); send_simple_im(from_id, LLStringUtil::null, IM_LURE_DECLINED, session_id); } else if (does_user_require_maturity_increase) { params.name = "TeleportOffered_MaturityExceeded_SLUrl"; } else { params.name = "TeleportOffered_SLUrl"; params.functor.name = "TeleportOffered"; } params.substitutions = args; params.payload = payload; LLPostponedNotification::add(params, from_id, false); LLWindow* viewer_window = gViewerWindow->getWindow(); static LLCachedControl sFlashIcon(gSavedSettings, "FSFlashOnMessage"); if (viewer_window && sFlashIcon) { viewer_window->flashIcon(5.f); } } // static bool parse_lure_bucket(const std::string& bucket, U64& region_handle, LLVector3& pos, LLVector3& look_at, U8& region_access) { // tokenize the bucket typedef boost::tokenizer > tokenizer; boost::char_separator sep("|", "", boost::keep_empty_tokens); tokenizer tokens(bucket, sep); tokenizer::iterator iter = tokens.begin(); S32 gx, gy, rx, ry, rz, lx, ly, lz; try { gx = boost::lexical_cast((*(iter)).c_str()); gy = boost::lexical_cast((*(++iter)).c_str()); rx = boost::lexical_cast((*(++iter)).c_str()); ry = boost::lexical_cast((*(++iter)).c_str()); rz = boost::lexical_cast((*(++iter)).c_str()); lx = boost::lexical_cast((*(++iter)).c_str()); ly = boost::lexical_cast((*(++iter)).c_str()); lz = boost::lexical_cast((*(++iter)).c_str()); } catch (boost::bad_lexical_cast&) { LL_WARNS("parse_lure_bucket") << "Couldn't parse lure bucket." << LL_ENDL; return false; } // Grab region access region_access = SIM_ACCESS_MIN; if (++iter != tokens.end()) { std::string access_str((*iter).c_str()); LLStringUtil::trim(access_str); if (access_str == "A") { region_access = SIM_ACCESS_ADULT; } else if (access_str == "M") { region_access = SIM_ACCESS_MATURE; } else if (access_str == "PG") { region_access = SIM_ACCESS_PG; } } pos.setVec((F32)rx, (F32)ry, (F32)rz); look_at.setVec((F32)lx, (F32)ly, (F32)lz); region_handle = to_region_handle(gx, gy); return true; } static void notification_display_name_callback(const LLUUID& id, const LLAvatarName& av_name, const std::string& name, LLSD& substitutions, const LLSD& payload) { substitutions["NAME"] = av_name.getDisplayName(); LLNotificationsUtil::add(name, substitutions, payload); } void LLIMProcessing::processNewMessage(LLUUID from_id, BOOL from_group, LLUUID to_id, U8 offline, EInstantMessage dialog, // U8 LLUUID session_id, U32 timestamp, std::string agentName, std::string message, U32 parent_estate_id, LLUUID region_id, LLVector3 position, U8 *binary_bucket, S32 binary_bucket_size, LLHost &sender, LLUUID aux_id) { LLChat chat; std::string buffer; std::string name = agentName; // NaCl - Antispam Registry if (dialog != IM_TYPING_START && dialog != IM_TYPING_STOP && // Typing notifications !(dialog == IM_NOTHING_SPECIAL && offline == IM_OFFLINE && from_id.notNull() && to_id.notNull()) && // Saved offline IMs !(dialog == IM_FROM_TASK && offline == IM_OFFLINE) // Saved offline IMs from objects ) { if (NACLAntiSpamRegistry::instance().checkQueue(ANTISPAM_QUEUE_IM, from_id, ANTISPAM_SOURCE_AGENT)) { return; } } // NaCl End // make sure that we don't have an empty or all-whitespace name LLStringUtil::trim(name); if (name.empty()) { name = LLTrans::getString("Unnamed"); } // Preserve the unaltered name for use in group notice mute checking. std::string original_name = name; // IDEVO convert new-style "Resident" names for display name = clean_name_from_im(name, dialog); BOOL is_do_not_disturb = gAgent.isDoNotDisturb(); BOOL is_autorespond = gAgent.getAutorespond(); BOOL is_autorespond_nonfriends = gAgent.getAutorespondNonFriends(); // FIRE-1245: Option to block/reject teleport offers BOOL is_rejecting_tp_offers = gAgent.getRejectTeleportOffers(); static LLCachedControl FSDontRejectTeleportOffersFromFriends(gSavedPerAccountSettings, "FSDontRejectTeleportOffersFromFriends"); // BOOL is_rejecting_group_invites = gAgent.getRejectAllGroupInvites(); // Option to block/reject all group invites BOOL is_rejecting_friendship_requests = gAgent.getRejectFriendshipRequests(); // FIRE-15233: Automatic friendship request refusal BOOL is_autorespond_muted = gSavedPerAccountSettings.getBOOL("FSSendMutedAvatarResponse"); BOOL is_muted = LLMuteList::getInstance()->isMuted(from_id, name, LLMute::flagTextChat) // object IMs contain sender object id in session_id (STORM-1209) || (dialog == IM_FROM_TASK && LLMuteList::getInstance()->isMuted(session_id)); BOOL is_owned_by_me = FALSE; BOOL is_friend = (LLAvatarTracker::instance().getBuddyInfo(from_id) == NULL) ? false : true; static LLCachedControl accept_im_from_only_friend(gSavedPerAccountSettings, "VoiceCallsFriendsOnly"); //BOOL is_linden = chat.mSourceType != CHAT_SOURCE_OBJECT && // LLMuteList::isLinden(name); <:FS:TM> Bear compile fix - is_linden not referenced // FIRE-10500: Autoresponse for (Away) static LLCachedControl FSSendAwayAvatarResponse(gSavedPerAccountSettings, "FSSendAwayAvatarResponse"); BOOL is_afk = gAgent.getAFK(); // chat.mMuted = is_muted; chat.mFromID = from_id; chat.mFromName = name; chat.mSourceType = (from_id.isNull() || (name == std::string(SYSTEM_FROM))) ? CHAT_SOURCE_SYSTEM : CHAT_SOURCE_AGENT; if (chat.mSourceType == CHAT_SOURCE_SYSTEM) { // Translate server message if required (MAINT-6109) translate_if_needed(message); } LLViewerObject *source = gObjectList.findObject(session_id); //Session ID is probably the wrong thing. if (source) { is_owned_by_me = source->permYouOwner(); } // NaCl - Newline flood protection static LLCachedControl useAntiSpam(gSavedSettings, "UseAntiSpam"); if (useAntiSpam && dialog != IM_GROUP_INVITATION) { bool doCheck = true; if (from_id.isNull() || gAgentID == from_id) { doCheck = false; } if (doCheck && is_owned_by_me) { doCheck = false; } if (doCheck && NACLAntiSpamRegistry::instance().checkNewlineFlood(ANTISPAM_QUEUE_IM, from_id, message)) { return; } } // NaCl End std::string separator_string(": "); LLSD args; LLSD payload; LLNotification::Params params; switch (dialog) { case IM_CONSOLE_AND_CHAT_HISTORY: args["MESSAGE"] = message; payload["from_id"] = from_id; params.name = "IMSystemMessageTip"; params.substitutions = args; params.payload = payload; LLPostponedNotification::add(params, from_id, false); break; case IM_NOTHING_SPECIAL: // p2p IM // Don't show dialog, just do IM if (!gAgent.isGodlike() && gAgent.getRegion()->isPrelude() && to_id.isNull()) { // do nothing -- don't distract newbies in // Prelude with global IMs } // [RLVa:KB] - Checked: RLVa-2.1.0 else if ( (RlvActions::isRlvEnabled()) && (offline == IM_ONLINE) && (!is_muted) && ((!accept_im_from_only_friend) || (is_friend)) && (message.length() > 3) && (RLV_CMD_PREFIX == message[0]) && (RlvHandler::instance().processIMQuery(from_id, message)) ) { // Eat the message and do nothing } // [/RLVa:KB] // else if (offline == IM_ONLINE // && is_do_not_disturb // && from_id.notNull() //not a system message // && to_id.notNull()) //not global message // [RLVa:KB] - Checked: 2010-11-30 (RLVa-1.3.0) // Only send the busy reponse if either the sender is not // muted OR the sender is muted and we explicitely want // to inform him about that fact. else if (offline == IM_ONLINE && (!accept_im_from_only_friend || is_friend) // is friend or accept IMs from friend only disabled && ((is_do_not_disturb && (!is_muted || (is_muted && !is_autorespond_muted))) || // do not disturb (is_autorespond && !is_muted) || // autorespond everyone (is_autorespond_nonfriends && !is_friend && !is_muted) || // autorespond friends only (is_afk && FSSendAwayAvatarResponse && !is_muted)) // away && from_id.notNull() //not a system message && to_id.notNull() //not global message && RlvActions::canReceiveIM(from_id)) // [/RLVa:KB] { // Log autoresponse notification after initial message bool has_session = true; // Old "do not disturb" message behavior: only send once if session not open // Session id will be null if avatar answers from offline IM via email std::string response; if (!gIMMgr->hasSession(session_id) && session_id.notNull()) { // // Log autoresponse notification after initial message has_session = false; // FS autoresponse feature std::string my_name; LLAgentUI::buildFullname(my_name); if (is_do_not_disturb) { response = gSavedPerAccountSettings.getString("DoNotDisturbModeResponse"); } else if (is_autorespond_nonfriends && !is_friend) { response = gSavedPerAccountSettings.getString("FSAutorespondNonFriendsResponse"); } else if (is_autorespond) { response = gSavedPerAccountSettings.getString("FSAutorespondModeResponse"); } // FIRE-10500: Autoresponse for (Away) 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(), from_id, my_name, response, IM_ONLINE, IM_DO_NOT_DISTURB_AUTO_RESPONSE, session_id); gAgent.sendReliableMessage(); // FS autoresponse feature // Old "do not disturb" message behavior: only send once if session not open } // // checkfor and process reqinfo if (has_session) { message = FSData::getInstance()->processRequestForInfo(from_id,message,name,session_id); } // // now store incoming IM in chat history buffer = message; LL_DEBUGS("Messaging") << "session_id( " << session_id << " ), from_id( " << from_id << " )" << LL_ENDL; // FIRE-10178: Keyword Alerts in group IM do not work unless the group is in the foreground (notification on receipt of IM) chat.mText = buffer; bool keyword_alert_performed = false; if (FSKeywords::getInstance()->chatContainsKeyword(chat, false)) { FSKeywords::notify(chat); keyword_alert_performed = true; } // // add to IM panel, but do not bother the user gIMMgr->addMessage( session_id, from_id, name, buffer, IM_OFFLINE == offline, LLStringUtil::null, dialog, parent_estate_id, region_id, position, false, timestamp, false, keyword_alert_performed); // Old "do not disturb" message behavior: only send once if session not open //if (!gIMMgr->isDNDMessageSend(session_id)) //{ // // return a standard "do not disturb" message, but only do it to online IM // // (i.e. not other auto responses and not store-and-forward IM) // send_do_not_disturb_message(msg, from_id, session_id); // gIMMgr->setDNDMessageSent(session_id, true); //} // if (!has_session) { // Fire-5389 - "Autoresponse Sent" message added to Firestorm as was in Phoenix 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, name, IM_NOTHING_SPECIAL, parent_estate_id, region_id, position, false, 0, 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, name, IM_NOTHING_SPECIAL, parent_estate_id, region_id, position, false, 0, true); LLGiveInventory::doGiveInventoryItem(from_id, item, session_id); } } // } } else if (from_id.isNull()) { LLSD args; args["MESSAGE"] = message; LLNotificationsUtil::add("SystemMessage", args); } else if (to_id.isNull()) { // Message to everyone from GOD, look up the fullname since // server always slams name to legacy names LLAvatarNameCache::get(from_id, boost::bind(god_message_name_cb, _2, chat, message)); } else { // 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); } buffer = saved + message; LL_DEBUGS("Messaging") << "session_id( " << session_id << " ), from_id( " << from_id << " )" << LL_ENDL; bool mute_im = is_muted; if (accept_im_from_only_friend && !is_friend) { if (!gIMMgr->isNonFriendSessionNotified(session_id)) { // Disable this - doesn't make sense it will be skipped by LLIMMgr::addMessage() anyway //std::string message = LLTrans::getString("IM_unblock_only_groups_friends"); //gIMMgr->addMessage(session_id, from_id, name, message, IM_OFFLINE == offline); // gIMMgr->addNotifiedNonFriendSessionID(session_id); } mute_im = true; } // [RLVa:KB] - Checked: 2010-11-30 (RLVa-1.3.0) // Don't block offline IMs, or IMs from Lindens if ( (rlv_handler_t::isEnabled()) && (offline != IM_OFFLINE) && (!RlvActions::canReceiveIM(from_id)) && (!LLMuteList::getInstance()->isLinden(original_name) )) { if (!mute_im) RlvUtil::sendBusyMessage(from_id, RlvStrings::getString(RlvStringKeys::Blocked::RecvImRemote), session_id); message = RlvStrings::getString(RlvStringKeys::Blocked::RecvIm); } // [/RLVa:KB] if (!mute_im) { // checkfor and process reqinfo message = FSData::getInstance()->processRequestForInfo(from_id, message, name, session_id); // FIRE-10178: Keyword Alerts in group IM do not work unless the group is in the foreground (notification on receipt of IM) chat.mText = message; bool keyword_alert_performed = false; if (FSKeywords::getInstance()->chatContainsKeyword(chat, false)) { FSKeywords::notify(chat); keyword_alert_performed = true; } // buffer = saved + message; bool region_message = false; if (region_id.isNull()) { LLViewerRegion* regionp = LLWorld::instance().getRegionFromID(from_id); if (regionp) { region_message = true; } } gIMMgr->addMessage( session_id, from_id, name, buffer, IM_OFFLINE == offline, LLStringUtil::null, dialog, parent_estate_id, region_id, position, region_message, timestamp, false, keyword_alert_performed); } else { /* EXT-5099 */ static LLCachedControl fsSendMutedAvatarResponse(gSavedPerAccountSettings, "FSSendMutedAvatarResponse"); if (fsSendMutedAvatarResponse && (!accept_im_from_only_friend || is_friend)) { std::string my_name; LLAgentUI::buildFullname(my_name); std::string response = gSavedPerAccountSettings.getString("FSMutedAvatarResponse"); pack_instant_message( gMessageSystem, gAgent.getID(), FALSE, gAgent.getSessionID(), from_id, my_name, response, IM_ONLINE, IM_DO_NOT_DISTURB_AUTO_RESPONSE, session_id); gAgent.sendReliableMessage(); } // Don't flash for muted IMs return; } } break; case IM_TYPING_START: { gIMMgr->processIMTypingStart(from_id, dialog); } break; case IM_TYPING_STOP: { gIMMgr->processIMTypingStop(from_id, dialog); } break; case IM_MESSAGEBOX: { // This is a block, modeless dialog. args["MESSAGE"] = message; LLNotificationsUtil::add("SystemMessageTip", args); } break; case IM_GROUP_NOTICE: case IM_GROUP_NOTICE_REQUESTED: { LL_INFOS("Messaging") << "Received IM_GROUP_NOTICE message." << LL_ENDL; LLUUID agent_id; U8 has_inventory; U8 asset_type = 0; LLUUID group_id; std::string item_name; if (aux_id.notNull()) { // aux_id contains group id, binary bucket contains name and asset type group_id = aux_id; has_inventory = binary_bucket_size > 1 ? TRUE : FALSE; from_group = TRUE; // inaccurate value correction if (has_inventory) { std::string str_bucket = ll_safe_string((char*)binary_bucket, binary_bucket_size); typedef boost::tokenizer > tokenizer; boost::char_separator sep("|", "", boost::keep_empty_tokens); tokenizer tokens(str_bucket, sep); tokenizer::iterator iter = tokens.begin(); asset_type = (LLAssetType::EType)(atoi((*(iter++)).c_str())); iter++; // wearable type if applicable, otherwise asset type item_name = std::string((*(iter++)).c_str()); // Note There is more elements in 'tokens' ... for (int i = 0; i < 6; i++) { LL_WARNS() << *(iter++) << LL_ENDL; iter++; } } } else { // All info is in binary bucket, read it for more information. struct notice_bucket_header_t { U8 has_inventory; U8 asset_type; LLUUID group_id; }; struct notice_bucket_full_t { struct notice_bucket_header_t header; U8 item_name[DB_INV_ITEM_NAME_BUF_SIZE]; }*notice_bin_bucket; // Make sure the binary bucket is big enough to hold the header // and a null terminated item name. if ((binary_bucket_size < (S32)((sizeof(notice_bucket_header_t) + sizeof(U8)))) || (binary_bucket[binary_bucket_size - 1] != '\0')) { LL_WARNS("Messaging") << "Malformed group notice binary bucket" << LL_ENDL; // Don't flash task icon //break; return; } notice_bin_bucket = (struct notice_bucket_full_t*) &binary_bucket[0]; has_inventory = notice_bin_bucket->header.has_inventory; asset_type = notice_bin_bucket->header.asset_type; group_id = notice_bin_bucket->header.group_id; item_name = ll_safe_string((const char*)notice_bin_bucket->item_name); } if (group_id != from_id) { agent_id = from_id; } else { S32 index = original_name.find(" Resident"); if (index != std::string::npos) { original_name = original_name.substr(0, index); } // The group notice packet does not have an AgentID. Obtain one from the name cache. // If last name is "Resident" strip it out so the cache name lookup works. std::string legacy_name = gCacheName->buildLegacyName(original_name); agent_id = LLAvatarNameCache::getInstance()->findIdByName(legacy_name); if (agent_id.isNull()) { LL_WARNS("Messaging") << "buildLegacyName returned null while processing " << original_name << LL_ENDL; } } if (agent_id.notNull() && LLMuteList::getInstance()->isMuted(agent_id)) { // Don't flash task icon //break; return; } // If there is inventory, give the user the inventory offer. LLOfferInfo* info = NULL; if (has_inventory) { info = new LLOfferInfo(); info->mIM = dialog; info->mFromID = from_id; info->mFromGroup = from_group; info->mTransactionID = session_id; info->mType = (LLAssetType::EType) asset_type; info->mFolderID = gInventory.findCategoryUUIDForType(LLFolderType::assetTypeToFolderType(info->mType)); std::string from_name; // FIRE-17714: Make group notice attachment confirmation localizable //from_name += "A group member named "; //from_name += name; LLStringUtil::format_map_t args; args["NAME"] = name; from_name += LLTrans::getString("InvOfferGroupNoticeName", args); // FIRE-29677 / SL-13720 workaround if (LLUUID::validate(name)) { LLStringUtil::format_map_t args; args["NAME"] = "%s"; std::string placeholder_from_name = LLTrans::getString("InvOfferGroupNoticeName", args); from_name = name + "|" + placeholder_from_name; } // info->mFromName = from_name; info->mDesc = item_name; info->mHost = sender; } std::string str(message); // Tokenize the string. // TODO: Support escaped tokens ("||" -> "|") typedef boost::tokenizer > tokenizer; boost::char_separator sep("|", "", boost::keep_empty_tokens); tokenizer tokens(str, sep); tokenizer::iterator iter = tokens.begin(); std::string subj(*iter++); std::string mes(*iter++); // Send the notification down the new path. // For requested notices, we don't want to send the popups. if (dialog != IM_GROUP_NOTICE_REQUESTED) { payload["subject"] = subj; payload["message"] = mes; payload["sender_name"] = name; payload["sender_id"] = agent_id; payload["group_id"] = group_id; payload["inventory_name"] = item_name; payload["received_time"] = LLDate::now(); if (info && info->asLLSD()) { payload["inventory_offer"] = info->asLLSD(); } // FIRE-505: Group name not shown in notification well //LLSD args; //args["SUBJECT"] = subj; //args["MESSAGE"] = mes; //LLDate notice_date = LLDate(timestamp).notNull() ? LLDate(timestamp) : LLDate::now(); //LLNotifications::instance().add(LLNotification::Params("GroupNotice").substitutions(args).payload(payload).time_stamp(notice_date)); //make_ui_sound("UISndGroupNotice"); // Group notice sound if (group_id.isNull()) { LL_WARNS() << "Received group notice with null id!" << LL_ENDL; } gCacheName->get(group_id, true, boost::bind(¬ification_group_name_cb, _2, name, subj, mes, payload, timestamp)); // } // Also send down the old path for now. if (IM_GROUP_NOTICE_REQUESTED == dialog) { LLPanelGroup::showNotice(subj, mes, group_id, has_inventory, item_name, info); } else { delete info; } } break; case IM_GROUP_INVITATION: { // FIRE-20385: Don't show group invitation for groups agent is already a member of if (gAgent.isInGroup(from_id) && !gSavedSettings.getBOOL("FSShowJoinedGroupInvitations")) { LL_INFOS("Messaging") << "Received group invitation for group " << from_id << " but we are already a member" << LL_ENDL; return; } // if (!is_muted) { // group is not blocked, but we still need to check agent that sent the invitation // and we have no agent's id // Note: server sends username "first.last". // Fix broken mute check for avatars without last name: // Names without last names are stored with capital first letter // in the mute list, but they arrive here completely in lower case. // So we need to change the first letter to upper case in order to // make the mute check actually work. //is_muted |= LLMuteList::getInstance()->isMuted(name); std::string check_name(name); if (!check_name.empty() && check_name.find('.') == std::string::npos) { check_name[0] = toupper(check_name[0]); } is_muted |= LLMuteList::getInstance()->isMuted(check_name); // } if (is_do_not_disturb || is_muted) { send_do_not_disturb_message(gMessageSystem, from_id); } if (!is_muted) { LL_INFOS("Messaging") << "Received IM_GROUP_INVITATION message." << LL_ENDL; // Read the binary bucket for more information. struct invite_bucket_t { S32 membership_fee; LLUUID role_id; }*invite_bucket; // Make sure the binary bucket is the correct size. if (binary_bucket_size != sizeof(invite_bucket_t)) { LL_WARNS("Messaging") << "Malformed group invite binary bucket" << LL_ENDL; // Don't flash task icon //break; return; } invite_bucket = (struct invite_bucket_t*) &binary_bucket[0]; S32 membership_fee = ntohl(invite_bucket->membership_fee); LLSD payload; payload["transaction_id"] = session_id; payload["group_id"] = from_group ? from_id : aux_id; payload["name"] = name; payload["message"] = message; payload["fee"] = membership_fee; payload["use_offline_cap"] = session_id.isNull() && (offline == IM_OFFLINE); LLSD args; args["MESSAGE"] = message; // we shouldn't pass callback functor since it is registered in LLFunctorRegistration // Option to block/reject all group invites // LLNotificationsUtil::add("JoinGroup", args, payload); if (is_rejecting_group_invites) { LL_INFOS("Messaging") << "Group invite automatically rejected because of the user setting..." << LL_ENDL; return; } else { make_ui_sound("UISndGroupInvitation"); // Group invitation sound LLNotificationsUtil::add("JoinGroup", args, payload); } // } // Don't flash task icon for group messages from muted senders else { return; } // } break; case IM_INVENTORY_OFFERED: case IM_TASK_INVENTORY_OFFERED: // Someone has offered us some inventory. { LLOfferInfo* info = new LLOfferInfo; if (IM_INVENTORY_OFFERED == dialog) { struct offer_agent_bucket_t { S8 asset_type; LLUUID object_id; }*bucketp; if (sizeof(offer_agent_bucket_t) != binary_bucket_size) { LL_WARNS("Messaging") << "Malformed inventory offer from agent" << LL_ENDL; delete info; // Don't flash task icon //break; return; } bucketp = (struct offer_agent_bucket_t*) &binary_bucket[0]; info->mType = (LLAssetType::EType) bucketp->asset_type; info->mObjectID = bucketp->object_id; info->mFromObject = FALSE; } else // IM_TASK_INVENTORY_OFFERED { if (sizeof(S8) == binary_bucket_size) { info->mType = (LLAssetType::EType) binary_bucket[0]; } else { /*RIDER*/ // The previous version of the protocol returned the wrong binary bucket... we // still might be able to figure out the type... even though the offer is not retrievable. // Should be safe to remove once DRTSIM-451 fully deploys std::string str_bucket(reinterpret_cast(binary_bucket)); std::string str_type(str_bucket.substr(0, str_bucket.find('|'))); std::stringstream type_convert(str_type); S32 type; type_convert >> type; // We could try AT_UNKNOWN which would be more accurate, but that causes an auto decline info->mType = static_cast(type); // Don't break in the case of a bad binary bucket. Go ahead and show the // accept/decline popup even though it will not do anything. LL_WARNS("Messaging") << "Malformed inventory offer from object, type might be " << info->mType << LL_ENDL; } info->mObjectID = LLUUID::null; info->mFromObject = TRUE; } info->mIM = dialog; info->mFromID = from_id; info->mFromGroup = from_group; info->mFolderID = gInventory.findCategoryUUIDForType(LLFolderType::assetTypeToFolderType(info->mType)); info->mTransactionID = session_id.notNull() ? session_id : aux_id; info->mFromName = name; info->mDesc = message; info->mHost = sender; //if (((is_do_not_disturb && !is_owned_by_me) || is_muted)) if (is_muted) { // Prefetch the offered item so that it can be discarded by the appropriate observer. (EXT-4331) if (IM_INVENTORY_OFFERED == dialog) { LLInventoryFetchItemsObserver* fetch_item = new LLInventoryFetchItemsObserver(info->mObjectID); fetch_item->startFetch(); delete fetch_item; // Same as closing window info->forceResponse(IOR_DECLINE); } else { info->forceResponse(IOR_MUTE); } } // old logic: busy mode must not affect interaction with objects (STORM-565) // new logic: inventory offers from in-world objects should be auto-declined (CHUI-519) else if (is_do_not_disturb && dialog == IM_TASK_INVENTORY_OFFERED) { // Until throttling is implemented, do not disturb mode should reject inventory instead of silently // accepting it. SEE SL-39554 info->forceResponse(IOR_DECLINE); } else { inventory_offer_handler(info); } } break; case IM_INVENTORY_ACCEPTED: { // args["NAME"] = LLSLURL("agent", from_id, "completename").getSLURLString();; // args["ORIGINAL_NAME"] = original_name; // [RLVa:KB] - Checked: RLVa-1.2.2 // Only anonymize the name if the agent is nearby, there isn't an open IM session to them and their profile isn't open LLAvatarName av_name; bool fRlvCanShowName = (!RlvActions::isRlvEnabled()) || (RlvActions::canShowName(RlvActions::SNC_DEFAULT, from_id)) || (!RlvUtil::isNearbyAgent(from_id)) || (RlvUIEnabler::hasOpenProfile(from_id)) || (RlvUIEnabler::hasOpenIM(from_id)); args["NAME"] = LLSLURL("agent", from_id, (fRlvCanShowName) ? "completename" : "rlvanonym").getSLURLString();; args["ORIGINAL_NAME"] = fRlvCanShowName ? original_name : (LLAvatarNameCache::get(from_id, &av_name) ? RlvStrings::getAnonym(av_name) : RlvStrings::getAnonym(original_name)); // [/RLVa:KB] LLSD payload; payload["from_id"] = from_id; // Passing the "SESSION_NAME" to use it for IM notification logging // in LLTipHandler::processNotification(). See STORM-941. payload["SESSION_NAME"] = name; LLNotificationsUtil::add("InventoryAccepted", args, payload); break; } case IM_INVENTORY_DECLINED: { // args["NAME"] = LLSLURL("agent", from_id, "completename").getSLURLString();; // [RLVa:KB] - Checked: RLVa-1.2.2 // Only anonymize the name if the agent is nearby, there isn't an open IM session to them and their profile isn't open bool fRlvCanShowName = (!RlvActions::isRlvEnabled()) || (RlvActions::canShowName(RlvActions::SNC_DEFAULT, from_id)) || (!RlvUtil::isNearbyAgent(from_id)) || (RlvUIEnabler::hasOpenProfile(from_id)) || (RlvUIEnabler::hasOpenIM(from_id)); args["NAME"] = LLSLURL("agent", from_id, (fRlvCanShowName) ? "completename" : "rlvanonym").getSLURLString();; // [/RLVa:KB] LLSD payload; payload["from_id"] = from_id; LLNotificationsUtil::add("InventoryDeclined", args, payload); break; } // TODO: _DEPRECATED suffix as part of vote removal - DEV-24856 case IM_GROUP_VOTE: { LL_WARNS("Messaging") << "Received IM: IM_GROUP_VOTE_DEPRECATED" << LL_ENDL; } break; case IM_GROUP_ELECTION_DEPRECATED: { LL_WARNS("Messaging") << "Received IM: IM_GROUP_ELECTION_DEPRECATED" << LL_ENDL; } break; case IM_FROM_TASK: { if (is_do_not_disturb && !is_owned_by_me) { return; } // FIRE-6406: Feature to disable Object Return notification static LLCachedControl FSDisableReturnObjectNotification(gSavedSettings, "FSDisableReturnObjectNotification"); if (FSDisableReturnObjectNotification) { if (message.find("been returned to your inventory") != -1) { return; } } // // Build a link to open the object IM info window. std::string location = ll_safe_string((char*)binary_bucket, binary_bucket_size - 1); if (session_id.notNull()) { chat.mFromID = session_id; } else { // This message originated on a region without the updated code for task id and slurl information. // We just need a unique ID for this object that isn't the owner ID. // If it is the owner ID it will overwrite the style that contains the link to that owner's profile. // This isn't ideal - it will make 1 style for all objects owned by the the same person/group. // This works because the only thing we can really do in this case is show the owner name and link to their profile. chat.mFromID = from_id ^ gAgent.getSessionID(); } chat.mSourceType = CHAT_SOURCE_OBJECT; chat.mChatType = CHAT_TYPE_IM; // To conclude that the source type of message is CHAT_SOURCE_SYSTEM it's not // enough to check only from name (i.e. fromName = "Second Life"). For example // source type of messages from objects called "Second Life" should not be CHAT_SOURCE_SYSTEM. bool chat_from_system = (SYSTEM_FROM == name) && region_id.isNull() && position.isNull(); if (chat_from_system) { // System's UUID is NULL (fixes EXT-4766) chat.mFromID = LLUUID::null; chat.mSourceType = CHAT_SOURCE_SYSTEM; } // IDEVO Some messages have embedded resident names message = clean_name_from_task_im(message, from_group); LLSD query_string; query_string["owner"] = from_id; // [RLVa:KB] - Checked: RLVa-1.2.0 if (RlvActions::isRlvEnabled()) { // NOTE: the chat message itself will be filtered in LLNearbyChatHandler::processChat() if ( (!RlvActions::canShowName(RlvActions::SNC_DEFAULT)) && (!from_group) && (RlvUtil::isNearbyAgent(from_id)) ) { query_string["rlv_shownames"] = TRUE; RlvUtil::filterNames(name); chat.mFromName = name; } if (!RlvActions::canShowLocation()) { std::string::size_type idxPos = location.find('/'); if ( (std::string::npos != idxPos) && (RlvUtil::isNearbyRegion(location.substr(0, idxPos))) ) location = RlvStrings::getString(RlvStringKeys::Hidden::Region); } } // [/RLVa:KB] query_string["slurl"] = location; query_string["name"] = name; if (from_group) { query_string["groupowned"] = "true"; } // chat.mURL = LLSLURL("objectim", session_id, "").getSLURLString(); // [SL:KB] - Checked: 2010-11-02 (RLVa-1.2.2a) | Added: RLVa-1.2.2a chat.mURL = LLSLURL("objectim", session_id, LLURI::mapToQueryString(query_string)).getSLURLString(); // [/SL:KB] chat.mText = message; // FIRE-10178: Keyword Alerts in group IM do not work unless the group is in the foreground (notification on receipt of Task IM) if (FSKeywords::getInstance()->chatContainsKeyword(chat, true)) { FSKeywords::notify(chat); } // // Note: lie to Nearby Chat, pretending that this is NOT an IM, because // IMs from obejcts don't open IM sessions. // [FS communication UI] //LLFloaterIMNearbyChat* nearby_chat = LLFloaterReg::getTypedInstance("nearby_chat"); FSFloaterNearbyChat* nearby_chat = FSFloaterNearbyChat::getInstance(); // [FS communication UI] if (!chat_from_system && nearby_chat) { chat.mOwnerID = from_id; LLSD args; args["slurl"] = location; // Look for IRC-style emotes here so object name formatting is correct // Consolidate IRC /me prefix checks //std::string prefix = message.substr(0, 4); //if (prefix == "/me " || prefix == "/me'") if (is_irc_me_prefix(message)) // { chat.mChatStyle = CHAT_STYLE_IRC; } LLNotificationsUI::LLNotificationManager::instance().onChat(chat, args); if (message != "") { LLSD msg_notify; msg_notify["session_id"] = LLUUID(); msg_notify["from_id"] = chat.mFromID; msg_notify["source_type"] = chat.mSourceType; on_new_message(msg_notify); } } //Object IMs send with from name: 'Second Life' need to be displayed also in notification toasts (EXT-1590) if (!chat_from_system) break; LLSD substitutions; substitutions["NAME"] = name; substitutions["MSG"] = message; LLSD payload; payload["object_id"] = session_id; payload["owner_id"] = from_id; payload["from_id"] = from_id; payload["slurl"] = location; payload["name"] = name; if (from_group) { payload["group_owned"] = "true"; } LLNotificationsUtil::add("ServerObjectMessage", substitutions, payload); } break; case IM_SESSION_SEND: // ad-hoc or group IMs // Only show messages if we have a session open (which // should happen after you get an "invitation" // [SL:KB] - Patch: Chat-GroupSnooze | Checked: 2012-06-16 (Catznip-3.3) //if ( !gIMMgr->hasSession(session_id) ) if (!gIMMgr->hasSession(session_id) && (!gAgent.isInGroup(session_id) || LLAvatarActions::isBlocked(from_id) || (!exoGroupMuteList::instance().restoreDeferredGroupChat(session_id) && (!gIMMgr->checkSnoozeExpiration(session_id) || !gIMMgr->restoreSnoozedSession(session_id)) ))) // [/SL:KB] { return; } else if (offline == IM_ONLINE && is_do_not_disturb) { // return a standard "do not disturb" message, but only do it to online IM // (i.e. not other auto responses and not store-and-forward IM) if (!gIMMgr->hasSession(session_id)) { // if there is not a panel for this conversation (i.e. it is a new IM conversation // initiated by the other party) then... send_do_not_disturb_message(gMessageSystem, from_id, session_id); } // now store incoming IM in chat history buffer = message; LL_DEBUGS("Messaging") << "message in dnd; session_id( " << session_id << " ), from_id( " << from_id << " )" << LL_ENDL; // add to IM panel, but do not bother the user gIMMgr->addMessage( session_id, from_id, name, buffer, IM_OFFLINE == offline, ll_safe_string((char*)binary_bucket), IM_SESSION_INVITE, parent_estate_id, region_id, position, false, // is_region_msg timestamp); } else { // FIRE-10178: Keyword Alerts in group IM do not work unless the group is in the foreground (notification on receipt of IM) chat.mText = message; bool keyword_alert_performed = false; if (FSKeywords::getInstance()->chatContainsKeyword(chat, false)) { FSKeywords::notify(chat); keyword_alert_performed = true; } // // standard message, not from system std::string saved; if (offline == IM_OFFLINE) { saved = llformat("(Saved %s) ", formatted_time(timestamp).c_str()); } buffer = saved + message; LL_DEBUGS("Messaging") << "standard message session_id( " << session_id << " ), from_id( " << from_id << " )" << LL_ENDL; gIMMgr->addMessage( session_id, from_id, name, buffer, (IM_OFFLINE == offline), ll_safe_string((char*)binary_bucket), // session name IM_SESSION_INVITE, parent_estate_id, region_id, position, false, timestamp, false, keyword_alert_performed); } break; case IM_FROM_TASK_AS_ALERT: if (is_do_not_disturb && !is_owned_by_me) { return; } { // Construct a viewer alert for this message. args["NAME"] = name; args["MESSAGE"] = message; LLNotificationsUtil::add("ObjectMessage", args); } break; case IM_DO_NOT_DISTURB_AUTO_RESPONSE: if (is_muted) { LL_DEBUGS("Messaging") << "Ignoring do-not-disturb response from " << from_id << LL_ENDL; return; } else { // FIRE-12908: Add busy response indicator back to busy messages //gIMMgr->addMessage(session_id, from_id, name, message); buffer = llformat("(%s): %s", LLTrans::getString("BusyResponse").c_str(), message.c_str()); gIMMgr->addMessage(session_id, from_id, name, buffer); // } break; case IM_LURE_USER: case IM_TELEPORT_REQUEST: { // [RLVa:KB] - Checked: RLVa-1.4.9 // If we auto-accept the offer/request then this will override DnD status (but we'll still let the other party know later) bool fRlvAutoAccept = (rlv_handler_t::isEnabled()) && ( ((IM_LURE_USER == dialog) && (RlvActions::autoAcceptTeleportOffer(from_id))) || ((IM_TELEPORT_REQUEST == dialog) && (RlvActions::autoAcceptTeleportRequest(from_id))) ); // [/RLVa:KB] if (is_muted) { return; } // FIRE-1245: Option to block/reject teleport offers //else if (gSavedPerAccountSettings.getBOOL("VoiceCallsFriendsOnly") && (LLAvatarTracker::instance().getBuddyInfo(from_id) == NULL)) //{ // return; //} else if ( (is_rejecting_tp_offers && (!FSDontRejectTeleportOffersFromFriends || (FSDontRejectTeleportOffersFromFriends && !is_friend))) && (!fRlvAutoAccept) ) { send_rejecting_tp_offers_message(gMessageSystem, from_id); } // else { // FS autoresponse feature //if (is_do_not_disturb) //{ // send_do_not_disturb_message(gMessageSystem, from_id); //} // [RLVa:KB] - Checked: RLVa-1.4.9 if (!fRlvAutoAccept) // [/RLVa:KB] { std::string my_name; std::string response; LLAgentUI::buildFullname(my_name); if (is_do_not_disturb) { 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"); } if (!response.empty()) { pack_instant_message( gMessageSystem, gAgentID, FALSE, gAgentSessionID, from_id, my_name, response, IM_ONLINE, IM_DO_NOT_DISTURB_AUTO_RESPONSE, LLUUID::null); gAgent.sendReliableMessage(); } } // FS autoresponse feature LLVector3 pos, look_at; U64 region_handle(0); U8 region_access(SIM_ACCESS_MIN); std::string region_info = ll_safe_string((char*)binary_bucket, binary_bucket_size); std::string region_access_str = LLStringUtil::null; std::string region_access_icn = LLStringUtil::null; std::string region_access_lc = LLStringUtil::null; bool canUserAccessDstRegion = true; bool doesUserRequireMaturityIncrease = false; // Do not parse the (empty) lure bucket for TELEPORT_REQUEST if (IM_TELEPORT_REQUEST != dialog && parse_lure_bucket(region_info, region_handle, pos, look_at, region_access)) { region_access_str = LLViewerRegion::accessToString(region_access); region_access_icn = LLViewerRegion::getAccessIcon(region_access); region_access_lc = region_access_str; LLStringUtil::toLower(region_access_lc); if (!gAgent.isGodlike()) { switch (region_access) { case SIM_ACCESS_MIN: case SIM_ACCESS_PG: break; case SIM_ACCESS_MATURE: if (gAgent.isTeen()) { canUserAccessDstRegion = false; } else if (gAgent.prefersPG()) { doesUserRequireMaturityIncrease = true; } break; case SIM_ACCESS_ADULT: if (!gAgent.isAdult()) { canUserAccessDstRegion = false; } else if (!gAgent.prefersAdult()) { doesUserRequireMaturityIncrease = true; } break; default: llassert(0); break; } } } // [RLVa:KB] - Checked: RLVa-1.4.9 if (rlv_handler_t::isEnabled()) { if ( ((IM_LURE_USER == dialog) && (!RlvActions::canAcceptTpOffer(from_id))) || ((IM_TELEPORT_REQUEST == dialog) && (!RlvActions::canAcceptTpRequest(from_id))) ) { RlvUtil::sendBusyMessage(from_id, RlvStrings::getString(RlvStringKeys::Blocked::TpLureRequestRemote)); if (is_do_not_disturb) send_do_not_disturb_message(gMessageSystem, from_id); return; } // Censor message if: 1) restricted from receiving IMs from the sender, or 2) teleport offer/request and @showloc=n restricted if ( (!RlvActions::canReceiveIM(from_id)) || ((gRlvHandler.hasBehaviour(RLV_BHVR_SHOWLOC)) && (IM_LURE_USER == dialog || IM_TELEPORT_REQUEST == dialog)) ) { message = RlvStrings::getString(RlvStringKeys::Hidden::Generic); } } // [/RLVa:KB] LLSD args; // *TODO: Translate -> [FIRST] [LAST] (maybe) // [SL:KB] - Patch: UI-Notifications | Checked: 2011-04-11 (Catznip-2.5.0a) | Added: Catznip-2.5.0a args["NAME_LABEL"] = LLSLURL("agent", from_id, "completename").getSLURLString(); // [/SL:KB] args["NAME_SLURL"] = LLSLURL("agent", from_id, "about").getSLURLString(); args["MESSAGE"] = message; args["MATURITY_STR"] = region_access_str; args["MATURITY_ICON"] = region_access_icn; args["REGION_CONTENT_MATURITY"] = region_access_lc; LLSD payload; payload["from_id"] = from_id; payload["lure_id"] = session_id; payload["godlike"] = FALSE; payload["region_maturity"] = region_access; // FIRE-6786: Always show teleport location in teleport offer if (dialog == IM_LURE_USER && (!rlv_handler_t::isEnabled() || !fRlvAutoAccept) && LLGridManager::instance().isInSecondLife()) { LLVector3d pos_global = from_region_handle(region_handle); pos_global += LLVector3d(pos); LLLandmarkActions::getSLURLfromPosGlobal(pos_global, boost::bind(&teleport_region_info_cb, _1, args, payload, from_id, session_id, canUserAccessDstRegion, doesUserRequireMaturityIncrease)); return; } // if (!canUserAccessDstRegion) { LLNotification::Params params("TeleportOffered_MaturityBlocked"); params.substitutions = args; params.payload = payload; LLPostponedNotification::add(params, from_id, false); send_simple_im(from_id, LLTrans::getString("TeleportMaturityExceeded"), IM_NOTHING_SPECIAL, session_id); send_simple_im(from_id, LLStringUtil::null, IM_LURE_DECLINED, session_id); } else if (doesUserRequireMaturityIncrease) { LLNotification::Params params("TeleportOffered_MaturityExceeded"); params.substitutions = args; params.payload = payload; LLPostponedNotification::add(params, from_id, false); } else { LLNotification::Params params; if (IM_LURE_USER == dialog) { params.name = "TeleportOffered"; params.functor.name = "TeleportOffered"; } else if (IM_TELEPORT_REQUEST == dialog) { params.name = "TeleportRequest"; params.functor.name = "TeleportRequest"; } params.substitutions = args; params.payload = payload; // [RLVa:KB] - Checked: RLVa-1.4.9 if (fRlvAutoAccept) { if (IM_LURE_USER == dialog) gRlvHandler.setCanCancelTp(false); if (is_do_not_disturb) send_do_not_disturb_message(gMessageSystem, from_id); LLNotifications::instance().forceResponse(LLNotification::Params(params.name).payload(payload), 0); } else { LLPostponedNotification::add(params, from_id, false); } // [/RLVa:KB] // LLPostponedNotification::add(params, from_id, false); } } } break; case IM_GODLIKE_LURE_USER: { LLVector3 pos, look_at; U64 region_handle(0); U8 region_access(SIM_ACCESS_MIN); std::string region_info = ll_safe_string((char*)binary_bucket, binary_bucket_size); std::string region_access_str = LLStringUtil::null; std::string region_access_icn = LLStringUtil::null; std::string region_access_lc = LLStringUtil::null; bool canUserAccessDstRegion = true; bool doesUserRequireMaturityIncrease = false; if (parse_lure_bucket(region_info, region_handle, pos, look_at, region_access)) { region_access_str = LLViewerRegion::accessToString(region_access); region_access_icn = LLViewerRegion::getAccessIcon(region_access); region_access_lc = region_access_str; LLStringUtil::toLower(region_access_lc); if (!gAgent.isGodlike()) { switch (region_access) { case SIM_ACCESS_MIN: case SIM_ACCESS_PG: break; case SIM_ACCESS_MATURE: if (gAgent.isTeen()) { canUserAccessDstRegion = false; } else if (gAgent.prefersPG()) { doesUserRequireMaturityIncrease = true; } break; case SIM_ACCESS_ADULT: if (!gAgent.isAdult()) { canUserAccessDstRegion = false; } else if (!gAgent.prefersAdult()) { doesUserRequireMaturityIncrease = true; } break; default: llassert(0); break; } } } LLSD args; // *TODO: Translate -> [FIRST] [LAST] (maybe) args["NAME_SLURL"] = LLSLURL("agent", from_id, "about").getSLURLString(); args["MESSAGE"] = message; args["MATURITY_STR"] = region_access_str; args["MATURITY_ICON"] = region_access_icn; args["REGION_CONTENT_MATURITY"] = region_access_lc; LLSD payload; payload["from_id"] = from_id; payload["lure_id"] = session_id; payload["godlike"] = TRUE; payload["region_maturity"] = region_access; if (!canUserAccessDstRegion) { LLNotification::Params params("TeleportOffered_MaturityBlocked"); params.substitutions = args; params.payload = payload; LLPostponedNotification::add(params, from_id, false); send_simple_im(from_id, LLTrans::getString("TeleportMaturityExceeded"), IM_NOTHING_SPECIAL, session_id); send_simple_im(from_id, LLStringUtil::null, IM_LURE_DECLINED, session_id); } else if (doesUserRequireMaturityIncrease) { LLNotification::Params params("TeleportOffered_MaturityExceeded"); params.substitutions = args; params.payload = payload; LLPostponedNotification::add(params, from_id, false); } else { // do not show a message box, because you're about to be // teleported. LLNotifications::instance().forceResponse(LLNotification::Params("TeleportOffered").payload(payload), 0); } } break; case IM_GOTO_URL: { LLSD args; // n.b. this is for URLs sent by the system, not for // URLs sent by scripts (i.e. llLoadURL) if (binary_bucket_size <= 0) { LL_WARNS("Messaging") << "bad binary_bucket_size: " << binary_bucket_size << " - aborting function." << LL_ENDL; return; } std::string url; url.assign((char*)binary_bucket, binary_bucket_size - 1); args["MESSAGE"] = message; args["URL"] = url; LLSD payload; payload["url"] = url; LLNotificationsUtil::add("GotoURL", args, payload); } break; case IM_FRIENDSHIP_OFFERED: { // FIRE-15233: Automatic friendship request refusal if (is_rejecting_friendship_requests) { send_rejecting_friendship_requests_message(gMessageSystem, from_id); return; } // LLSD payload; payload["from_id"] = from_id; payload["session_id"] = session_id; payload["online"] = (offline == IM_ONLINE); payload["sender"] = sender.getIPandPort(); bool add_notification = true; for (auto& panel : LLToastNotifyPanel::instance_snapshot()) { const std::string& notification_name = panel.getNotificationName(); if (notification_name == "OfferFriendship" && panel.isControlPanelEnabled()) { add_notification = false; break; } } if (is_muted && add_notification) { LLNotifications::instance().forceResponse(LLNotification::Params("OfferFriendship").payload(payload), 1); } else { if (is_do_not_disturb) { send_do_not_disturb_message(gMessageSystem, from_id); } // [SL:KB] - Patch: UI-Notifications | Checked: 2011-04-11 (Catznip-2.5.0a) | Added: Catznip-2.5.0a args["NAME_LABEL"] = LLSLURL("agent", from_id, "completename").getSLURLString(); // [/SL:KB] args["NAME_SLURL"] = LLSLURL("agent", from_id, "about").getSLURLString(); if (add_notification) { if (message.empty()) { //support for frienship offers from clients before July 2008 LLNotificationsUtil::add("OfferFriendshipNoMessage", args, payload); make_ui_sound("UISndFriendshipOffer"); // Friendship offer sound } else { args["[MESSAGE]"] = message; LLNotification::Params params("OfferFriendship"); params.substitutions = args; params.payload = payload; LLPostponedNotification::add(params, from_id, false); make_ui_sound("UISndFriendshipOffer"); // Friendship offer sound } } } } break; case IM_FRIENDSHIP_ACCEPTED: { // In the case of an offline IM, the formFriendship() may be extraneous // as the database should already include the relationship. But it // doesn't hurt for dupes. LLAvatarTracker::formFriendship(from_id); std::vector strings; strings.push_back(from_id.asString()); send_generic_message("requestonlinenotification", strings); args["NAME"] = name; LLSD payload; payload["from_id"] = from_id; LLAvatarNameCache::get(from_id, boost::bind(¬ification_display_name_callback, _1, _2, "FriendshipAccepted", args, payload)); } break; case IM_FRIENDSHIP_DECLINED_DEPRECATED: default: LL_WARNS("Messaging") << "Instant message calling for unknown dialog " << (S32)dialog << LL_ENDL; break; } LLWindow* viewer_window = gViewerWindow->getWindow(); // Make osx dashboard icon bounce when window isn't in focus //if (viewer_window && viewer_window->getMinimized()) static LLCachedControl sFlashIcon(gSavedSettings, "FSFlashOnMessage"); static LLCachedControl sFSFlashOnObjectIM(gSavedSettings, "FSFlashOnObjectIM"); if (viewer_window && dialog != IM_TYPING_START && dialog != IM_TYPING_STOP && sFlashIcon && (sFSFlashOnObjectIM || (chat.mChatType != CHAT_TYPE_IM)) && !is_muted) { viewer_window->flashIcon(5.f); } } void LLIMProcessing::requestOfflineMessages() { static BOOL requested = FALSE; if (!requested && gMessageSystem && !gDisconnected && LLMuteList::getInstance()->isLoaded() && isAgentAvatarValid() && gAgent.getRegion() && gAgent.getRegion()->capabilitiesReceived()) { std::string cap_url = gAgent.getRegionCapability("ReadOfflineMsgs"); // Optional legacy offline messages if (!gSavedSettings.getBOOL("FSUseReadOfflineMsgsCap")) { cap_url = ""; } // // Auto-accepted inventory items may require the avatar object // to build a correct name. Likewise, inventory offers from // muted avatars require the mute list to properly mute. if (cap_url.empty() || gAgent.getRegionCapability("AcceptFriendship").empty() || gAgent.getRegionCapability("AcceptGroupInvite").empty()) { // Offline messages capability provides no session/transaction ids for message AcceptFriendship and IM_GROUP_INVITATION to work // So make sure we have the caps before using it. requestOfflineMessagesLegacy(); } else { LLCoros::instance().launch("LLIMProcessing::requestOfflineMessagesCoro", boost::bind(&LLIMProcessing::requestOfflineMessagesCoro, cap_url)); } requested = TRUE; } } void LLIMProcessing::requestOfflineMessagesCoro(std::string url) { LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("requestOfflineMessagesCoro", httpPolicy)); LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); LLSD result = httpAdapter->getAndSuspend(httpRequest, url); LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); if (!status) // success = httpResults["success"].asBoolean(); { LL_WARNS("Messaging") << "Error requesting offline messages via capability " << url << ", Status: " << status.toString() << "\nFalling back to legacy method." << LL_ENDL; requestOfflineMessagesLegacy(); return; } LLSD contents = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS_CONTENT]; if (!contents.size()) { LL_WARNS("Messaging") << "No contents received for offline messages via capability " << url << LL_ENDL; return; } // Todo: once dirtsim-369 releases, remove one of the map/array options LLSD messages; if (contents.isArray()) { messages = *contents.beginArray(); } else if (contents.has("messages")) { messages = contents["messages"]; } else { LL_WARNS("Messaging") << "Invalid offline message content received via capability " << url << LL_ENDL; return; } if (!messages.isArray()) { LL_WARNS("Messaging") << "Invalid offline message content received via capability " << url << LL_ENDL; return; } if (messages.size() == 0) { // Nothing to process return; } if (!gAgent.getRegion()) { LL_WARNS("Messaging") << "Region null while attempting to load messages." << LL_ENDL; return; } LL_INFOS("Messaging") << "Processing offline messages." << LL_ENDL; LLHost sender = gAgent.getRegionHost(); LLSD::array_iterator i = messages.beginArray(); LLSD::array_iterator iEnd = messages.endArray(); for (; i != iEnd; ++i) { const LLSD &message_data(*i); /* RIDER: Many fields in this message are using a '_' rather than the standard '-'. This * should be changed but would require tight coordination with the simulator. */ LLVector3 position; if (message_data.has("position")) { position.setValue(message_data["position"]); } else { position.set(message_data["local_x"].asReal(), message_data["local_y"].asReal(), message_data["local_z"].asReal()); } std::vector bin_bucket; if (message_data.has("binary_bucket")) { bin_bucket = message_data["binary_bucket"].asBinary(); } else { bin_bucket.push_back(0); } // Todo: once drtsim-451 releases, remove the string option BOOL from_group; if (message_data["from_group"].isInteger()) { from_group = message_data["from_group"].asInteger(); } else { from_group = message_data["from_group"].asString() == "Y"; } LLIMProcessing::processNewMessage( message_data["from_agent_id"].asUUID(), from_group, message_data["to_agent_id"].asUUID(), message_data.has("offline") ? static_cast(message_data["offline"].asInteger()) : IM_OFFLINE, static_cast(message_data["dialog"].asInteger()), message_data["transaction-id"].asUUID(), static_cast(message_data["timestamp"].asInteger()), message_data["from_agent_name"].asString(), message_data["message"].asString(), static_cast((message_data.has("parent_estate_id")) ? message_data["parent_estate_id"].asInteger() : 1), // 1 - IMMainland message_data["region_id"].asUUID(), position, bin_bucket.data(), bin_bucket.size(), sender, message_data["asset_id"].asUUID()); } } void LLIMProcessing::requestOfflineMessagesLegacy() { LL_INFOS("Messaging") << "Requesting offline messages (Legacy)." << LL_ENDL; LLMessageSystem* msg = gMessageSystem; msg->newMessageFast(_PREHASH_RetrieveInstantMessages); msg->nextBlockFast(_PREHASH_AgentData); msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); gAgent.sendReliableMessage(); }