123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594 |
- /**
- * @file llimmgr.cpp
- * @brief Instant Messaging management
- *
- * $LicenseInfo:firstyear=2001&license=viewergpl$
- *
- * Copyright (c) 2001-2009, Linden Research, Inc.
- *
- * Second Life Viewer Source Code
- * The source code in this file ("Source Code") is provided by Linden Lab
- * to you under the terms of the GNU General Public License, version 2.0
- * ("GPL"), unless you have obtained a separate licensing agreement
- * ("Other License"), formally executed by you and Linden Lab. Terms of
- * the GPL can be found in doc/GPL-license.txt in this distribution, or
- * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
- *
- * There are special exceptions to the terms and conditions of the GPL as
- * it is applied to this Source Code. View the full text of the exception
- * in the file doc/FLOSS-exception.txt in this software distribution, or
- * online at
- * http://secondlifegrid.net/programs/open_source/licensing/flossexception
- *
- * By copying, modifying or distributing this software, you acknowledge
- * that you have read and understood your obligations described above,
- * and agree to abide by those obligations.
- *
- * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
- * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
- * COMPLETENESS OR PERFORMANCE.
- * $/LicenseInfo$
- */
- #include "llviewerprecompiledheaders.h"
- #include "boost/tokenizer.hpp"
- #include "llimmgr.h"
- #include "llcachename.h"
- #include "llcorehttputil.h"
- #include "llfloater.h"
- #include "llhttpnode.h"
- #include "llnotifications.h"
- #include "llsdserialize.h" // For LLSDSerialize::toPrettyXML()
- #include "llsdutil_math.h" // For ll_vector3_from_sd()
- #include "lltabcontainer.h"
- #include "lltrans.h"
- #include "lluistring.h"
- #include "llwindow.h"
- #include "llagent.h"
- #include "llappviewer.h" // For gFrameTimeSeconds and gDisconnected
- #include "llavatartracker.h"
- #include "llchat.h"
- #include "llfloaterchat.h"
- #include "llfloaterchatterbox.h"
- #include "llfloatergroupinfo.h"
- #include "llfloaterim.h"
- #include "llfloaternewim.h"
- #include "llinventorymodel.h"
- #include "llmutelist.h"
- #include "lloverlaybar.h"
- //MK
- #include "mkrlinterface.h"
- //mk
- #include "hbviewerautomation.h"
- #include "llviewercontrol.h"
- #include "llviewermenu.h"
- #include "llviewermessage.h"
- #include "llviewerobjectlist.h"
- #include "llviewerregion.h"
- #include "llvoavatarself.h"
- #include "llvoicechannel.h"
- LLIMMgr* gIMMgrp = NULL;
- // This name is used by (and reserved for) the menus: floater_im.xml,
- // floater_instant_message.xml, floater_instant_message_group.xml and
- // floater_instant_message_ad_hoc.xml. If you change it here, change it
- // there ! HB
- const std::string gIMFloaterName = "im session";
- typedef boost::tokenizer<boost::char_separator<char> > tok_t;
- static const boost::char_separator<char> sSeparators("|", "",
- boost::keep_empty_tokens);
- ///////////////////////////////////////////////////////////////////////////////
- // Friendship offer callback (was formerly in llviewermessage.cpp, but since
- // the OfferFriendship and OfferFriendshipNoMessage notifications are initiated
- // from here, it makes more sense to keep the corresponding callback here too).
- ///////////////////////////////////////////////////////////////////////////////
- bool accept_friendship_udp(const LLSD& payload)
- {
- LLMessageSystem* msg = gMessageSystemp;
- if (!msg) // Went offline ?
- {
- return false;
- }
- LL_DEBUGS("InstantMessaging") << "Accepting friendship offer via UDP messaging"
- << LL_ENDL;
- LLAvatarTracker::formFriendship(payload["from_id"]);
- const LLUUID& fid =
- gInventory.findCategoryUUIDForType(LLFolderType::FT_CALLINGCARD);
- // This will also trigger an onlinenotification if the user is online
- msg->newMessageFast(_PREHASH_AcceptFriendship);
- msg->nextBlockFast(_PREHASH_AgentData);
- msg->addUUIDFast(_PREHASH_AgentID, gAgentID);
- msg->addUUIDFast(_PREHASH_SessionID, gAgentSessionID);
- msg->nextBlockFast(_PREHASH_TransactionBlock);
- msg->addUUIDFast(_PREHASH_TransactionID, payload["session_id"]);
- msg->nextBlockFast(_PREHASH_FolderData);
- msg->addUUIDFast(_PREHASH_FolderID, fid);
- msg->sendReliable(LLHost(payload["sender"].asString()));
- return true;
- }
- bool decline_friendship_udp(const LLSD& payload)
- {
- LLMessageSystem* msg = gMessageSystemp;
- if (!msg) // Went offline ?
- {
- return false;
- }
- LL_DEBUGS("InstantMessaging") << "Declining friendship offer via UDP messaging"
- << LL_ENDL;
- // We no longer notify other viewers, but we DO still send the rejection to
- // the simulator to delete the pending userop.
- msg->newMessageFast(_PREHASH_DeclineFriendship);
- msg->nextBlockFast(_PREHASH_AgentData);
- msg->addUUIDFast(_PREHASH_AgentID, gAgentID);
- msg->addUUIDFast(_PREHASH_SessionID, gAgentSessionID);
- msg->nextBlockFast(_PREHASH_TransactionBlock);
- msg->addUUIDFast(_PREHASH_TransactionID, payload["session_id"]);
- msg->sendReliable(LLHost(payload["sender"].asString()));
- return true;
- }
- void accept_friendship_coro(std::string url, LLSD payload)
- {
- LL_DEBUGS("InstantMessaging") << "Accepting friendship offer via capability"
- << LL_ENDL;
- url += "?from=" + payload["from_id"].asString() + "&agent_name=\"" +
- LLURI::escape(gAgentAvatarp->getFullname(true)) + "\"";
- LLSD data;
- LLCoreHttpUtil::HttpCoroutineAdapter adapter("AcceptFriendshipOffer");
- LLSD result = adapter.postAndSuspend(url, data);
- LLCore::HttpStatus status =
- LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(result);
- if (!status || !result.has("success") || !result["success"].asBoolean())
- {
- llwarns << "Error accepting frienship offer via capability. Error: "
- << status.toString() << llendl;
- if (accept_friendship_udp(payload))
- {
- llinfos << "Sent frienship acceptance via legacy UDP messaging"
- << llendl;
- }
- else
- {
- llwarns << "Failed to send frienship acceptance via legacy UDP messaging"
- << llendl;
- }
- return;
- }
- LLAvatarTracker::formFriendship(payload["from_id"]);
- }
- void decline_friendship_coro(std::string url, LLSD payload)
- {
- LL_DEBUGS("InstantMessaging") << "Declining friendship offer via capability"
- << LL_ENDL;
- url += "?from=" + payload["from_id"].asString();
- LLCoreHttpUtil::HttpCoroutineAdapter adapter("DeclineFriendshipOffer");
- LLSD result = adapter.deleteAndSuspend(url);
- LLCore::HttpStatus status =
- LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(result);
- if (!status || !result.has("success") || !result["success"].asBoolean())
- {
- llwarns << "Error declining frienship offer via capability. Error: "
- << status.toString() << llendl;
- if (decline_friendship_udp(payload))
- {
- llinfos << "Sent frienship declining via legacy UDP messaging"
- << llendl;
- }
- else
- {
- llwarns << "Failed to send frienship declining via legacy UDP messaging"
- << llendl;
- }
- }
- }
- bool friendship_offer_callback(const LLSD& notification, const LLSD& response)
- {
- const LLSD& payload = notification["payload"];
- bool online = payload.has("online") && payload["online"].asBoolean();
- S32 option = LLNotification::getSelectedOption(notification, response);
- if (option == 0) // Accept
- {
- const std::string& url =
- gAgent.getRegionCapability("AcceptFriendship");
- if (url.empty() || online)
- {
- accept_friendship_udp(payload);
- return false;
- }
- gCoros.launch("acceptFriendshipOffer",
- boost::bind(&accept_friendship_coro, url, payload));
- }
- else if (option == 1) // Decline
- {
- const std::string& url =
- gAgent.getRegionCapability("DeclineFriendship");
- if (url.empty() || online)
- {
- decline_friendship_udp(payload);
- return false;
- }
- gCoros.launch("declineFriendshipOffer",
- boost::bind(&decline_friendship_coro, url, payload));
- }
- return false;
- }
- static LLNotificationFunctorRegistration friend_offer_cb_reg("OfferFriendship",
- friendship_offer_callback);
- static LLNotificationFunctorRegistration friend_offer_nm_cb_reg("OfferFriendshipNoMessage",
- friendship_offer_callback);
- ///////////////////////////////////////////////////////////////////////////////
- // LLIMMgrFriendObserver class
- // Bridge to suport knowing when the friends list has changed.
- ///////////////////////////////////////////////////////////////////////////////
- class LLIMMgrFriendObserver final : public LLFriendObserver
- {
- public:
- LLIMMgrFriendObserver()
- {
- }
- ~LLIMMgrFriendObserver() override
- {
- }
- void changed(U32 mask) override
- {
- if (gIMMgrp &&
- (mask & (LLFriendObserver::ADD | LLFriendObserver::REMOVE |
- LLFriendObserver::ONLINE)) != 0)
- {
- gIMMgrp->refresh();
- }
- }
- };
- ///////////////////////////////////////////////////////////////////////////////
- // LLIMMgr class
- ///////////////////////////////////////////////////////////////////////////////
- LLIMMgr::LLIMMgr()
- : mIMsReceived(0),
- mPrivateIMReceived(false)
- {
- llassert_always(gIMMgrp == NULL); // Only one instance allowed
- mFriendObserver = new LLIMMgrFriendObserver();
- gAvatarTracker.addObserver(mFriendObserver);
- // *HACK: use floater to initialize string constants from xml file then
- // delete it right away
- LLFloaterIM* dummy_floaterp = new LLFloaterIM();
- delete dummy_floaterp;
- mPendingInvitations = LLSD::emptyMap();
- mPendingAgentListUpdates = LLSD::emptyMap();
- gIMMgrp = this;
- }
- LLIMMgr::~LLIMMgr()
- {
- gAvatarTracker.removeObserver(mFriendObserver);
- delete mFriendObserver;
- gIMMgrp = NULL;
- }
- // NOTE: 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 (dialog == IM_SESSION_GROUP_START || dialog == IM_SESSION_INVITE)
- {
- // Slam group session_id to the group_id (other_participant_id)
- // or the provided session id for invites (which includes group
- // session invites).
- session_id = other_participant_id;
- }
- else if (dialog == IM_SESSION_CONFERENCE_START)
- {
- session_id.generate();
- }
- else
- {
- LLUUID agent_id = gAgentID;
- 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;
- }
- }
- return session_id;
- }
- //static
- void LLIMMgr::chatterBoxInvitationCoro(const std::string& url,
- LLUUID session_id,
- LLIMMgr::EInvitationType type)
- {
- LLSD data;
- data["method"] = "accept invitation";
- data["session-id"] = session_id;
- LLSD params;
- params["voice_server_type"] = gSavedSettings.getString("VoiceServerType");
- data["alt_params"] = params;
- LLCoreHttpUtil::HttpCoroutineAdapter adapter("ChatterBoxInvitation");
- LLSD result = adapter.postAndSuspend(url, data);
- if (!gIMMgrp) return; // Viewer is closing down !
- gIMMgrp->clearPendingAgentListUpdates(session_id);
- gIMMgrp->clearPendingInvitation(session_id);
- LLFloaterIMSession* floaterp =
- LLFloaterIMSession::findInstance(session_id);
- if (!floaterp)
- {
- llinfos << "Received a reply for closed session Id: " << session_id
- << ". Ignored." << llendl;
- return;
- }
- LLCore::HttpStatus status =
- LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(result);
- if (!status)
- {
- llwarns << "Failed to start session Id: " << session_id
- << ". Error: " << status.toString() << llendl;
- if (status == gStatusNotFound)
- {
- floaterp->showSessionStartError("does not exist");
- }
- return;
- }
- // We have 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 have 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.
- result.erase(LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS);
- floaterp->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 udpates we have
- // received in case of race conditions
- floaterp->updateSpeakersList(gIMMgrp->getPendingAgentListUpdates(session_id));
- if (type == LLIMMgr::INVITATION_TYPE_VOICE)
- {
- floaterp->requestAutoConnect();
- LLFloaterIMSession::onClickStartCall(floaterp);
- // Always open IM window when connecting to voice
- LLFloaterChatterBox::showInstance(true);
- }
- else if (type == LLIMMgr::INVITATION_TYPE_IMMEDIATE)
- {
- LLFloaterChatterBox::showInstance(true);
- }
- }
- //static
- bool LLIMMgr::inviteUserResponse(const LLSD& notification,
- const LLSD& response)
- {
- if (!gIMMgrp) return false;
- 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();
- std::string session_name = payload["session_name"].asString();
- S32 option = LLNotification::getSelectedOption(notification, response);
- switch (option)
- {
- case 0: // Accept
- {
- if (type == IM_SESSION_P2P_INVITE)
- {
- if (payload.has("voice_channel_info"))
- {
- LLSD& chan_info = payload["voice_channel_info"];
- if (chan_info.isMap() && chan_info.size() > 0)
- {
- chan_info["incoming"] = true;
- }
- }
- if (session_name.empty() && payload.has("caller_name"))
- {
- session_name = payload["caller_name"].asString();
- }
- // Create a normal IM session
- session_id =
- gIMMgrp->addP2PSession(session_name,
- payload["caller_id"].asUUID(),
- payload["voice_channel_info"]);
- LLFloaterIMSession* floaterp =
- LLFloaterIMSession::findInstance(session_id);
- if (floaterp)
- {
- floaterp->requestAutoConnect();
- LLFloaterIMSession::onClickStartCall(floaterp);
- // Always open IM window when connecting to voice
- LLFloaterChatterBox::showInstance(session_id);
- }
- gIMMgrp->clearPendingAgentListUpdates(session_id);
- gIMMgrp->clearPendingInvitation(session_id);
- }
- else
- {
- const std::string& url =
- gAgent.getRegionCapability("ChatSessionRequest");
- if (!url.empty())
- {
- if (inv_type == INVITATION_TYPE_VOICE &&
- payload.has("voice_channel_info"))
- {
- LLSD& chan_info = payload["voice_channel_info"];
- if (chan_info.isMap() && chan_info.size() > 0)
- {
- chan_info["incoming"] = true;
- }
- }
- gIMMgrp->addSession(session_name, type, session_id,
- payload["voice_channel_info"]);
- gCoros.launch("chatterBoxInvitationCoro",
- boost::bind(&LLIMMgr::chatterBoxInvitationCoro,
- url, session_id, inv_type));
- }
- }
- break;
- }
- // Mute (also implies ignore, so this falls through to the "ignore" case
- // below)
- case 2: // Mute
- {
- // Mute the sender of this invite
- if (!LLMuteList::isMuted(payload["caller_id"].asUUID()))
- {
- LLMute mute(payload["caller_id"].asUUID(),
- payload["caller_name"].asString(), LLMute::AGENT);
- LLMuteList::add(mute);
- }
- // FALLTHROUGH to decline
- }
- case 1: // Decline
- {
- static const std::string declined = "Invitation declined.";
- static const std::string failed =
- "Failed to send decline invitation message.";
- using namespace LLCoreHttpUtil;
- if (type == IM_SESSION_P2P_INVITE)
- {
- U32 server_type =
- gVoiceClient.getVoiceServerType(payload["voice_channel_info"]);
- if (server_type == LLVoiceClient::VIVOX_SERVER)
- {
- gVoiceClient.declineInvite(payload["voice_channel_info"]);
- }
- else if (server_type == LLVoiceClient::WEBRTC_SERVER)
- {
- const std::string& url =
- gAgent.getRegionCapability("ChatSessionRequest");
- if (!url.empty())
- {
- LLSD data;
- data["method"] = "decline p2p voice";
- data["session-id"] = session_id;
- HttpCoroutineAdapter::messageHttpPost(url, data, declined,
- failed);
- }
- }
- }
- else
- {
- const std::string& url =
- gAgent.getRegionCapability("ChatSessionRequest");
- if (!url.empty())
- {
- LLSD data;
- data["method"] = "decline invitation";
- data["session-id"] = session_id;
- HttpCoroutineAdapter::messageHttpPost(url, data, declined,
- failed);
- }
- }
- gIMMgrp->clearPendingAgentListUpdates(session_id);
- gIMMgrp->clearPendingInvitation(session_id);
- }
- }
- return false;
- }
- // Helper function
- void session_starter_helper(const LLUUID& temp_session_id,
- const LLUUID& other_participant_id,
- EInstantMessage im_type)
- {
- LLMessageSystem* msg = gMessageSystemp;
- msg->newMessageFast(_PREHASH_ImprovedInstantMessage);
- msg->nextBlockFast(_PREHASH_AgentData);
- msg->addUUIDFast(_PREHASH_AgentID, gAgentID);
- msg->addUUIDFast(_PREHASH_SessionID, gAgentSessionID);
- 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);
- // No timestamp necessary
- msg->addU32Fast(_PREHASH_Timestamp, NO_TIMESTAMP);
- std::string name;
- gAgent.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());
- }
- //static
- void LLIMMgr::startConferenceCoro(const std::string& url,
- LLUUID temp_session_id, LLUUID creator_id,
- LLUUID other_participant_id, LLSD agents)
- {
- LLSD data;
- data["method"] = "start conference";
- data["session-id"] = temp_session_id;
- data["params"] = agents;
- LLSD params;
- params["voice_server_type"] = gSavedSettings.getString("VoiceServerType");
- data["alt_params"] = params;
- LLCoreHttpUtil::HttpCoroutineAdapter adapter("StartConference");
- LLSD result = adapter.postAndSuspend(url, data);
- LLCore::HttpStatus status =
- LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(result);
- if (!status)
- {
- if (status == gStatusBadRequest)
- {
- startDeprecatedConference(temp_session_id, creator_id,
- other_participant_id, agents);
- }
- else
- {
- // Throw an error back to the client ?
- // In theory we should have just have these error strings 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.
- llwarns << "Failed to start conference: " << status.toString()
- << llendl;
- }
- }
- }
- //static
- void LLIMMgr::startP2PVoiceCoro(const std::string& url, LLUUID temp_session_id,
- LLUUID creator_id, LLUUID other_participant_id)
- {
- LLSD data;
- data["method"] = "start p2p voice";
- data["session-id"] = temp_session_id;
- data["params"] = other_participant_id;
- LLSD params;
- params["voice_server_type"] = gSavedSettings.getString("VoiceServerType");
- data["alt_params"] = params;
- LLCoreHttpUtil::HttpCoroutineAdapter adapter("startP2PVoiceCoro");
- LLSD result = adapter.postAndSuspend(url, data);
- LLCore::HttpStatus status =
- LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(result);
- if (!status)
- {
- llwarns << "Failed to start conference: " << status.toString()
- << llendl;
- if (status == gStatusBadRequest)
- {
- // Throw an error dialog and close the temp session's floater
- LLFloaterIMSession* floaterp =
- LLFloaterIMSession::findInstance(temp_session_id);
- if (floaterp)
- {
- floaterp->showSessionStartError("does not exist");
- }
- }
- }
- }
- // Returns true if any messages were sent, false otherwise. Is sort of
- // equivalent to "does the server need to do anything ?"
- //static
- bool LLIMMgr::sendStartSessionMessages(const LLUUID& temp_session_id,
- const LLUUID& other_participant_id,
- const uuid_vec_t& ids,
- EInstantMessage dialog, bool use_p2p)
- {
- if (dialog == IM_SESSION_GROUP_START)
- {
- session_starter_helper(temp_session_id, other_participant_id, dialog);
- gMessageSystemp->addBinaryDataFast(_PREHASH_BinaryBucket,
- EMPTY_BINARY_BUCKET,
- EMPTY_BINARY_BUCKET_SIZE);
- gAgent.sendReliableMessage();
- return true;
- }
- // We have a new way of starting conference calls now
- const std::string& url = gAgent.getRegionCapability("ChatSessionRequest");
- if (dialog == IM_SESSION_CONFERENCE_START)
- {
- LLSD agents;
- for (S32 i = 0, count = ids.size(); i < count; ++i)
- {
- agents.append(ids[i]);
- }
- if (url.empty())
- {
- startDeprecatedConference(temp_session_id, gAgentID,
- other_participant_id, agents);
- }
- else
- {
- gCoros.launch("startConferenceCoro",
- boost::bind(&LLIMMgr::startConferenceCoro, url,
- temp_session_id, gAgentID,
- other_participant_id, agents));
- }
- // We need to wait for the server reply; in case of ad-hoc chat, we
- // will get a new session Id.
- return true;
- }
- if (!use_p2p)
- {
- // Nothing to ask or tell to the server. HB
- return false;
- }
- if (url.empty())
- {
- llwarns << "Missing ChatSessionRequest capability: cannot open sesion."
- << llendl;
- }
- else
- {
- gCoros.launch("startP2PVoiceCoro",
- boost::bind(&LLIMMgr::startP2PVoiceCoro, url,
- temp_session_id, gAgentID,
- other_participant_id));
- }
- return true;
- }
- //static
- void LLIMMgr::startDeprecatedConference(const LLUUID& temp_session_id,
- const LLUUID& creator_id,
- const LLUUID& other_participant_id,
- const LLSD& agents_to_invite)
- {
- // This method is also called on return of coroutines, and the viewer could
- // be closing down when it happens...
- if (!gMessageSystemp)
- {
- return;
- }
- S32 count = agents_to_invite.size();
- if (count == 0)
- {
- return; // No one to invite...
- }
- S32 bucket_size = UUID_BYTES * count;
- U8* bucket = new U8[bucket_size];
- // *FIX: this could suffer from endian issues
- U8* pos = bucket;
- LLUUID agent_id;
- for (S32 i = 0; i < count; ++i)
- {
- 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);
- gMessageSystemp->addBinaryDataFast(_PREHASH_BinaryBucket, bucket,
- bucket_size);
- gAgent.sendReliableMessage();
- delete[] bucket;
- }
- // This is a helper function to determine what kind of IM session should be
- // used for the given agent.
- //static
- EInstantMessage LLIMMgr::defaultIMTypeForAgent(const LLUUID& agent_id)
- {
- EInstantMessage type = IM_NOTHING_SPECIAL;
- if (LLAvatarTracker::isAgentFriend(agent_id))
- {
- if (gAvatarTracker.isBuddyOnline(agent_id))
- {
- type = IM_SESSION_CONFERENCE_START;
- }
- }
- return type;
- }
- //static
- void LLIMMgr::toggle(void*)
- {
- // Hide the button and show the floater or vice versa.
- if (gIMMgrp)
- {
- gIMMgrp->setFloaterOpen(!gIMMgrp->getFloaterOpen());
- }
- }
- // Helper function
- static void get_extended_text_color(const LLUUID& session_id,
- const LLUUID& other_participant_id,
- const std::string& msg,
- LLColor4& color)
- {
- if (other_participant_id.notNull() &&
- gSavedSettings.getBool("HighlightOwnNameInIM"))
- {
- for (std::vector<LLGroupData>::iterator i = gAgent.mGroups.begin(),
- end = gAgent.mGroups.end();
- i != end; ++i)
- {
- if (i->mID == session_id)
- {
- if (LLFloaterChat::isOwnNameInText(msg))
- {
- color = gSavedSettings.getColor4("OwnNameChatColor");
- }
- break;
- }
- }
- }
- }
- // 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,
- const std::string& session_name,
- EInstantMessage dialog, U32 parent_estate_id,
- const LLUUID& region_id, const LLVector3& position,
- bool link_name)
- {
- LLUUID other_participant_id = target_id;
- bool private_im = from != SYSTEM_FROM &&
- !gAgent.isInGroup(session_id, true);
- // Replace interactive system message marker with correct from string value
- std::string from_name = from;
- if (from == INCOMING_IM)
- {
- from_name = SYSTEM_FROM;
- }
- else if (from == INTERACTIVE_SYSTEM_FROM)
- {
- from_name = SYSTEM_FROM;
- private_im = false;
- }
- // Do not process muted IMs
- if (LLMuteList::isMuted(other_participant_id, LLMute::flagTextChat) &&
- !LLMuteList::isLinden(from_name))
- {
- return;
- }
- if (session_id.notNull() &&
- LLMuteList::isMuted(session_id, LLMute::flagTextChat))
- {
- // Muted group
- return;
- }
- size_t i = session_name.find(" Conference");
- if (i != std::string::npos)
- {
- std::string initiator = session_name.substr(0, i);
- if (LLMuteList::isMuted(LLUUID::null, initiator, LLMute::flagTextChat,
- LLMute::AGENT))
- {
- // Conference initiated by a muted agent
- return;
- }
- }
- #if 1 // *TODO: check that this is still needed...
- // Not sure why...but if it is from ourselves we set the target_id to be
- // NULL
- if (other_participant_id == gAgentID)
- {
- other_participant_id.setNull();
- }
- #endif
- LL_DEBUGS("InstantMessaging") << "IM type: " << dialog
- << " - session name: " << session_name
- << " - From: " << from_name << LL_ENDL;
- LLUUID new_session_id = session_id;
- if (new_session_id.isNull())
- {
- // No session ID... Compute a new one
- new_session_id = computeSessionID(dialog, other_participant_id);
- }
- LLFloaterIMSession* floaterp =
- LLFloaterIMSession::findInstance(new_session_id);
- if (!floaterp)
- {
- floaterp = LLFloaterIMSession::findInstance(other_participant_id);
- if (floaterp)
- {
- llinfos << "Found the IM session " << session_id
- << " by participant " << other_participant_id << llendl;
- }
- }
- // Create IM window as necessary
- if (!floaterp)
- {
- LL_DEBUGS("InstantMessaging") << "Creating a new window" << LL_ENDL;
- std::string name = from_name;
- if (!session_name.empty() && session_name.size() > 1)
- {
- name = session_name;
- }
- if (LLAvatarName::sOmitResidentAsLastName)
- {
- name = LLCacheName::cleanFullName(name);
- from_name = LLCacheName::cleanFullName(from_name);
- }
- floaterp = createFloater(new_session_id, other_participant_id, name,
- dialog, false);
- // 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 << "*** parent estate: " << parent_estate_id
- << (parent_estate_id == 1 ? ", mainland" : "")
- << (parent_estate_id == 5 ? ", teen" : "");
- // 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;
- floaterp->addHistoryLine(bonus_info.str(),
- gSavedSettings.getColor4("SystemChatColor"));
- }
- if (private_im ||
- gSavedSettings.getBool("UISndNewIncomingPlayForGroup"))
- {
- make_ui_sound("UISndNewIncomingIMSession");
- }
- }
- // Now add message to floater
- bool is_from_system = target_id.isNull() || from_name == SYSTEM_FROM;
- LLColor4 color;
- if (is_from_system)
- {
- color = gSavedSettings.getColor4("SystemChatColor");
- }
- else
- {
- std::string new_line = std::string(msg);
- if (new_line.find(": ") == 0)
- {
- new_line = new_line.substr(2);
- }
- else
- {
- new_line = new_line.substr(1);
- }
- color = gSavedSettings.getColor("IMChatColor");
- get_extended_text_color(session_id, other_participant_id, new_line,
- color);
- }
- if (!link_name)
- {
- // No name to prepend, so just add the message normally
- floaterp->addHistoryLine(msg, color);
- }
- else
- {
- // Insert linked name to front of message
- floaterp->addHistoryLine(msg, color, true, other_participant_id,
- from_name);
- }
- LLFloaterChatterBox* chat_floaterp =
- LLFloaterChatterBox::getInstance(LLSD());
- if (!chat_floaterp->getVisible() && !floaterp->getVisible())
- {
- LL_DEBUGS("InstantMessaging") << "Adding the IM to the non-visible window"
- << LL_ENDL;
- // If the IM window is not open and the floater is not visible (i.e.
- // not torn off)
- LLFloater* old_active = chat_floaterp->getActiveFloater();
- // Select the newly added floater (or the floater with the new line
- // added to it). It should be there.
- chat_floaterp->selectFloater(floaterp);
- // There was a previously unseen IM, make that old tab flashing it is
- // assumed that the most recently unseen IM tab is the one current
- // selected/active
- if (old_active && mIMsReceived > 0)
- {
- chat_floaterp->setFloaterFlashing(old_active, true);
- }
- // Notify of a new IM (for the overlay bar button)
- // *BUG: in fact, this counts the number of sessions that received new,
- // unread IMs, and not the number of unread IMs... The floater code
- // above is apparently changing the getVisible() flags even though the
- // corresponding windows are not visible...
- ++mIMsReceived;
- if (private_im)
- {
- mPrivateIMReceived = true;
- }
- if (gOverlayBarp)
- {
- gOverlayBarp->setDirty();
- }
- LL_DEBUGS("InstantMessaging") << "Unread IMs: " << mIMsReceived
- << LL_ENDL;
- }
- }
- 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())
- {
- LLFloaterChat* chat_floaterp = LLFloaterChat::getInstance();
- message = chat_floaterp->getString(message_name);
- message.setArgs(args);
- LLChat chat(message);
- chat.mSourceType = CHAT_SOURCE_SYSTEM;
- chat_floaterp->addChatHistory(chat);
- return;
- }
- // Going to IM session
- LLFloaterIMSession* floaterp =
- LLFloaterIMSession::findInstance(session_id);
- if (floaterp)
- {
- message = floaterp->getString(message_name);
- message.setArgs(args);
- addMessage(session_id, LLUUID::null, SYSTEM_FROM, message.getString());
- }
- }
- // 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,
- const LLSD& voice_call_info)
- {
- LLUUID session_id = computeSessionID(dialog, other_participant_id);
- LLFloaterIMSession* floaterp =
- LLFloaterIMSession::findInstance(session_id);
- if (!floaterp)
- {
- uuid_vec_t ids;
- ids.emplace_back(other_participant_id);
- //MK
- if (gRLenabled && !gRLInterface.canStartIM(other_participant_id))
- {
- return LLUUID::null;
- }
- //mk
- floaterp = createFloater(session_id, other_participant_id, name, ids,
- voice_call_info, dialog, true);
- noteOfflineUsers(floaterp, ids);
- LLFloaterChatterBox::showInstance(session_id);
- // Only warn for regular IMs - not group IMs
- if (dialog == IM_NOTHING_SPECIAL)
- {
- noteMutedUsers(floaterp, ids);
- }
- else
- {
- snoozed_map_t::iterator it = mSnoozedSessions.find(session_id);
- if (it != mSnoozedSessions.end())
- {
- LL_DEBUGS("InstantMessaging") << "Removing session Id "
- << session_id
- << " from snoozes map."
- << LL_ENDL;
- mSnoozedSessions.erase(it);
- }
- }
- LLFloaterChatterBox::getInstance(LLSD())->showFloater(floaterp);
- }
- else
- {
- floaterp->open();
- }
- floaterp->setInputFocus(true);
- return floaterp->getSessionID();
- }
- // 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 uuid_vec_t& ids, const LLSD& voice_call_info)
- {
- if (ids.empty())
- {
- return LLUUID::null;
- }
- LLUUID session_id = computeSessionID(dialog, other_participant_id);
- LLFloaterIMSession* floaterp =
- LLFloaterIMSession::findInstance(session_id);
- if (!floaterp)
- {
- // On creation, use the first element of Ids as the
- // "other_participant_id"
- floaterp = createFloater(session_id, other_participant_id, name, ids,
- voice_call_info, dialog, true);
- if (!floaterp) return LLUUID::null; // Paranoia
- noteOfflineUsers(floaterp, ids);
- LLFloaterChatterBox::showInstance(session_id);
- // Only warn for regular IMs, not group IMs
- if (dialog == IM_NOTHING_SPECIAL)
- {
- noteMutedUsers(floaterp, ids);
- }
- }
- else
- {
- floaterp->open();
- }
- floaterp->setInputFocus(true);
- return floaterp->getSessionID();
- }
- void LLIMMgr::removeSession(const LLUUID& session_id,
- const LLUUID& other_participant_id,
- U32 snooze_duration)
- {
- if (session_id.notNull())
- {
- if (snooze_duration)
- {
- F32 unsnooze_after = gFrameTimeSeconds +
- (F32)snooze_duration * 60.f;
- LL_DEBUGS("InstantMessaging") << "Snoozing session Id: "
- << session_id << LL_ENDL;
- mSnoozedSessions[session_id] = unsnooze_after;
- }
- else // Close the session server-side
- {
- std::string name;
- gAgent.buildFullname(name);
- pack_instant_message(gAgentID, false, gAgentSessionID,
- other_participant_id, name,
- LLStringUtil::null, IM_ONLINE,
- IM_SESSION_LEAVE, session_id);
- gAgent.sendReliableMessage();
- }
- }
- LLFloaterIMSession* floaterp =
- LLFloaterIMSession::findInstance(session_id);
- if (floaterp)
- {
- LLFloaterChatterBox::getInstance(LLSD())->removeFloater(floaterp);
- clearPendingInvitation(session_id);
- clearPendingAgentListUpdates(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 LLSD& voice_channel_info)
- {
- if (caller_id == gAgentID)
- {
- return; // Ignore invites from ourself.
- }
- // Ignore invites from muted residents
- bool is_linden = LLMuteList::isLinden(caller_name);
- if (!is_linden && LLMuteList::isMuted(caller_id))
- {
- llinfos << "Ignoring session invite from fully muted resident: "
- << caller_name << llendl;
- return;
- }
- std::string notify_box_type;
- bool ad_hoc_invite = false;
- bool voice_invite = false;
- 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, true))
- {
- // Only really old school groups have voice invitations
- notify_box_type = "VoiceInviteGroup";
- voice_invite = true;
- }
- else if (inv_type == INVITATION_TYPE_VOICE)
- {
- // Else it is an ad-hoc and a voice ad-hoc
- notify_box_type = "VoiceInviteAdHoc";
- ad_hoc_invite = true;
- voice_invite = true;
- }
- else if (inv_type == INVITATION_TYPE_IMMEDIATE)
- {
- notify_box_type = "InviteAdHoc";
- ad_hoc_invite = true;
- }
- if (voice_invite && LLMuteList::isMuted(caller_id, LLMute::flagVoiceChat))
- {
- llinfos << "Ignoring voice session invite from voice-muted resident: "
- << caller_name << llendl;
- return;
- }
- 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["voice_channel_info"] = voice_channel_info;
- payload["notify_box_type"] = notify_box_type;
- 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
- gNotifications.forceResponse(LLNotification::Params("VoiceInviteP2P").payload(payload),
- 0);
- return;
- }
- if (type == IM_SESSION_P2P_INVITE || ad_hoc_invite)
- {
- // Is the inviter a friend ?
- if (gAvatarTracker.getBuddyInfo(caller_id) == NULL)
- {
- // If not, and we are ignoring voice invites from non-friends
- // then silently decline
- if (gSavedSettings.getBool("VoiceCallsFriendsOnly"))
- {
- // Invite is not from a friend, so decline
- gNotifications.forceResponse(LLNotification::Params("VoiceInviteP2P").payload(payload),
- 1);
- return;
- }
- }
- }
- if (!mPendingInvitations.has(session_id.asString()))
- {
- if (caller_name.empty())
- {
- if (gCacheNamep)
- {
- gCacheNamep->get(caller_id, false,
- boost::bind(&LLIMMgr::onInviteNameLookup,
- _1, _2, _3, payload));
- }
- }
- else
- {
- LLSD args;
- args["NAME"] = caller_name;
- args["GROUP"] = session_name;
- gNotifications.add(notify_box_type, args, payload,
- &inviteUserResponse);
- }
- mPendingInvitations[session_id.asString()] = LLSD();
- }
- }
- //static
- void LLIMMgr::onInviteNameLookup(const LLUUID& id,
- const std::string& full_name,
- bool is_group, LLSD payload)
- {
- std::string name = full_name;
- if (LLAvatarName::sOmitResidentAsLastName)
- {
- name = LLCacheName::cleanFullName(name);
- }
- payload["caller_name"] = name;
- payload["session_name"] = name;
- LLSD args;
- args["NAME"] = name;
- gNotifications.add(payload["notify_box_type"].asString(), args, payload,
- &inviteUserResponse);
- }
- void LLIMMgr::refresh()
- {
- static const EInstantMessage group_session = IM_SESSION_GROUP_START;
- static const EInstantMessage default_session = IM_NOTHING_SPECIAL;
- LLFloaterNewIM* floaterp =
- LLFloaterChatterBox::getInstance(LLSD())->getFloaterNewIM();
- if (!floaterp) return;
- S32 old_group_scroll_pos = floaterp->getGroupScrollPos();
- S32 old_agent_scroll_pos = floaterp->getAgentScrollPos();
- floaterp->clearAllTargets();
- // Add groups
- for (S32 i = 0, count = gAgent.mGroups.size(); i < count; ++i)
- {
- LLGroupData* group = &(gAgent.mGroups[i]);
- floaterp->addGroup(group->mID, (void*)(&group_session));
- }
- // Build a set of buddies in the current buddy list.
- LLCollectAllBuddies collector;
- gAvatarTracker.applyFunctor(collector);
- LLCollectAllBuddies::buddy_map_t::iterator it;
- LLCollectAllBuddies::buddy_map_t::iterator end;
- it = collector.mOnline.begin();
- end = collector.mOnline.end();
- for ( ; it != end; ++it)
- {
- floaterp->addAgent(it->second, (void*)(&default_session), true);
- }
- it = collector.mOffline.begin();
- end = collector.mOffline.end();
- for ( ; it != end; ++it)
- {
- floaterp->addAgent(it->second, (void*)(&default_session), false);
- }
- floaterp->setGroupScrollPos(old_group_scroll_pos);
- floaterp->setAgentScrollPos(old_agent_scroll_pos);
- }
- void LLIMMgr::setFloaterOpen(bool set_open)
- {
- if (set_open)
- {
- LLFloaterChatterBox::showInstance();
- LLFloaterChatterBox* floater_chatterbox = getFloater();
- LLFloater* floater_current = floater_chatterbox->getActiveFloater();
- LLFloater* floater_new_im = floater_chatterbox->getFloaterNewIM();
- bool active_is_im = floater_current &&
- (floater_current->getName() == gIMFloaterName ||
- floater_current == floater_new_im);
- LLFloater* floater_to_show = active_is_im ? floater_current : NULL;
- LLTabContainer* tabs =
- floater_chatterbox->getChild<LLTabContainer>("Preview Tabs");
- for (S32 i = 0; i < floater_chatterbox->getFloaterCount(); ++i)
- {
- LLPanel* panelp = tabs->getPanelByIndex(i);
- if (panelp->getName() == gIMFloaterName)
- {
- // This cast is safe here because in such tabs, only an
- // LLFloaterIMSessions can be called gIMFloaterName.
- LLFloaterIMSession* floaterp = (LLFloaterIMSession*)panelp;
- if (floaterp &&
- (!floater_to_show ||
- floater_chatterbox->isFloaterFlashing(floaterp)))
- {
- // The first floaterp or the flashing floaterp
- floater_to_show = floaterp;
- }
- }
- }
- if (floater_to_show)
- {
- floater_to_show->open();
- }
- else if (floater_chatterbox && floater_chatterbox->getFloaterNewIM())
- {
- floater_chatterbox->getFloaterNewIM()->open();
- }
- }
- else
- {
- LLFloaterChatterBox::hideInstance();
- }
- }
- bool LLIMMgr::getFloaterOpen()
- {
- return LLFloaterChatterBox::instanceVisible(LLSD());
- }
- LLFloaterChatterBox* LLIMMgr::getFloater()
- {
- return LLFloaterChatterBox::getInstance(LLSD());
- }
- void LLIMMgr::disconnectAllSessions()
- {
- LLFloaterIMSession::closeAllInstances();
- }
- void LLIMMgr::clearPendingInvitation(const LLUUID& session_id)
- {
- if (mPendingInvitations.has(session_id.asString()))
- {
- mPendingInvitations.erase(session_id.asString());
- }
- }
- LLSD LLIMMgr::getPendingAgentListUpdates(const LLUUID& session_id)
- {
- if (mPendingAgentListUpdates.has(session_id.asString()))
- {
- return mPendingAgentListUpdates[session_id.asString()];
- }
- 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::processAgentListUpdates(const LLUUID& session_id,
- const LLSD& body)
- {
- if (body.has("agent_updates"))
- {
- // WebRTC-specific agent updates on leaving a P2P channel.
- const LLSD& updates = body["agent_updates"];
- if (updates.isMap())
- {
- for (LLSD::map_const_iterator it = updates.beginMap(),
- end = updates.endMap();
- it != end; ++it)
- {
- const LLSD& agent_data = it->second;
- if (!agent_data.isMap())
- {
- continue;
- }
- // Ignore actual leaves as those will be handled separately.
- if (agent_data.has("transition") &&
- agent_data["transition"].asString() == "LEAVE")
- {
- continue;
- }
- LLUUID agent_id(it->first, false);
- if (agent_id == gAgentID || !agent_data.has("info"))
- {
- continue;
- }
- const LLSD& info = agent_data["info"];
- if (!info.isMap())
- {
- continue;
- }
- if (info.has("can_voice_chat") &&
- !info["can_voice_chat"].asBoolean())
- {
- LLVoiceChannel* channelp =
- LLVoiceChannel::getChannelByID(session_id);
- if (channelp && channelp->isP2P())
- {
- // It is an adhoc-style P2P channel, and the peer has
- // declined voice. Notify the user and shut down the
- // voice channel.
- gNotifications.add("P2PCallDeclined",
- channelp->getNotifyArgs());
- channelp->deactivate();
- break;
- }
- }
- }
- }
- }
- LLFloaterIMSession* floaterp =
- LLFloaterIMSession::findInstance(session_id);
- if (floaterp)
- {
- floaterp->updateSpeakersList(body);
- }
- else
- {
- // We do not have a floater yet: something went wrong and we are
- // probably receiving an update here before a start or an acceptance
- // of an invitation. Race condition.
- addPendingAgentListUpdates(session_id, body);
- }
- }
- // Creates a floater and updates internal representation for consistency.
- // Returns the pointer, caller (the class instance since it is a private
- // method) is not responsible for deleting the pointer. Add the floater to
- // this but do not select it.
- LLFloaterIMSession* LLIMMgr::createFloater(const LLUUID& session_id,
- const LLUUID& other_participant_id,
- const std::string& session_label,
- EInstantMessage dialog,
- bool user_initiated)
- {
- if (session_id.isNull())
- {
- llwarns << "Creating floater with null session Id" << llendl;
- }
- llinfos << "Created from " << other_participant_id << " in session "
- << session_id << llendl;
- LLFloaterIMSession* floaterp = new LLFloaterIMSession(session_label,
- session_id,
- other_participant_id,
- dialog);
- LLTabContainer::eInsertionPoint i_pt = user_initiated ?
- LLTabContainer::RIGHT_OF_CURRENT :
- LLTabContainer::END;
- LLFloaterChatterBox::getInstance(LLSD())->addFloater(floaterp, false,
- i_pt);
- return floaterp;
- }
- LLFloaterIMSession* LLIMMgr::createFloater(const LLUUID& session_id,
- const LLUUID& other_participant_id,
- const std::string& session_label,
- const uuid_vec_t& ids,
- const LLSD& voice_call_info,
- EInstantMessage dialog,
- bool user_initiated)
- {
- if (session_id.isNull())
- {
- llwarns << "Creating with null session Id !" << llendl;
- }
- llinfos << "Creating floater for " << other_participant_id
- << " in session " << session_id << llendl;
- LLFloaterIMSession* floaterp = new LLFloaterIMSession(session_label,
- session_id,
- other_participant_id,
- ids, voice_call_info,
- dialog);
- LLTabContainer::eInsertionPoint i_pt = user_initiated ?
- LLTabContainer::RIGHT_OF_CURRENT :
- LLTabContainer::END;
- LLFloaterChatterBox::getInstance(LLSD())->addFloater(floaterp, false,
- i_pt);
- return floaterp;
- }
- void LLIMMgr::noteOfflineUsers(LLFloaterIMSession* floaterp,
- const uuid_vec_t& ids)
- {
- S32 count = ids.size();
- if (!count)
- {
- floaterp->addHistoryLine(LLFloaterIM::sOnlyUserMessage,
- gSavedSettings.getColor4("SystemChatColor"));
- return;
- }
- const LLRelationship* info = NULL;
- LLAvatarTracker& at = gAvatarTracker;
- LLColor4 color = gSavedSettings.getColor4("SystemChatColor");
- for (S32 i = 0; i < count; ++i)
- {
- info = at.getBuddyInfo(ids[i]);
- std::string first, last;
- if (info && !info->isOnline() && gCacheNamep &&
- gCacheNamep->getName(ids[i], first, last))
- {
- LLUIString offline = LLFloaterIM::sOfflineMessage;
- offline.setArg("[FIRST]", first);
- offline.setArg("[LAST]", last);
- floaterp->addHistoryLine(offline, color);
- }
- }
- }
- void LLIMMgr::noteMutedUsers(LLFloaterIMSession* floaterp,
- const uuid_vec_t& ids)
- {
- S32 count = ids.size();
- if (count > 0)
- {
- for (S32 i = 0; i < count; ++i)
- {
- if (LLMuteList::isMuted(ids[i]))
- {
- LLUIString muted = LLFloaterIM::sMutedMessage;
- floaterp->addHistoryLine(muted);
- break;
- }
- }
- }
- }
- void LLIMMgr::processNewMessage(const LLUUID& from_id, bool from_group,
- const LLUUID& to_id, U8 offline,
- EInstantMessage dialog,
- const LLUUID& session_id, U32 timestamp,
- std::string name, std::string message,
- U32 parent_estate_id,const LLUUID& region_id,
- const LLVector3& position, U8* binary_bucket,
- S32 bucket_size, const LLHost& sender,
- const LLUUID& aux_id)
- {
- LLViewerRegion* regionp = gAgent.getRegion();
- if (!regionp)
- {
- return; // Viewer is likely disconnected or closing down !
- }
- // Make sure that we do not have an empty or all-whitespace name
- LLStringUtil::trim(name);
- if (name.empty())
- {
- name = LLTrans::getString("Unnamed");
- }
- bool is_busy = gAgent.getBusy();
- bool is_away = gAgent.getAFK() &&
- gSavedPerAccountSettings.getBool("BusyResponseWhenAway");
- bool auto_reply = gAgent.getAutoReply();
- bool is_muted = LLMuteList::isMuted(from_id, name, LLMute::flagTextChat);
- bool is_linden = LLMuteList::isLinden(name);
- bool is_owned_by_me = false;
- // session_id is probably the wrong thing...
- LLViewerObject* source = gObjectList.findObject(session_id);
- if (source)
- {
- is_owned_by_me = source->permYouOwner();
- }
- LLChat chat;
- chat.mMuted = is_muted && !is_linden;
- chat.mFromID = from_id;
- chat.mFromName = name;
- if (from_id.isNull() || name == SYSTEM_FROM)
- {
- chat.mSourceType = CHAT_SOURCE_SYSTEM;
- }
- else if (dialog == IM_FROM_TASK || dialog == IM_FROM_TASK_AS_ALERT)
- {
- chat.mSourceType = CHAT_SOURCE_OBJECT;
- // Keep track of the owner's Id for the source object.
- if (source && source->mOwnerID.isNull())
- {
- source->mOwnerID = from_id;
- }
- }
- else
- {
- chat.mSourceType = CHAT_SOURCE_AGENT;
- }
- std::string separator_string(": ");
- size_t message_offset = 0;
- // Handle IRC styled /me messages.
- std::string prefix = message.substr(0, 4);
- if (prefix == "/me " || prefix == "/me'")
- {
- separator_string = "";
- message_offset = 3;
- }
- LL_DEBUGS("InstantMessaging") << "IM type: " << dialog << " from: "
- << (is_owned_by_me ?
- "agent-owned object" :
- (source ?
- "other resident object" :
- (from_group ? "group" : "resident")))
- << LL_ENDL;
- std::string buffer;
- LLSD args;
- switch (dialog)
- {
- case IM_CONSOLE_AND_CHAT_HISTORY:
- {
- // These are used for system messages, hence do not need the name,
- // as it is always "Second Life". *TODO: translate
- //MK
- if (gRLenabled)
- {
- if (gRLInterface.mContainsShowloc)
- {
- // Hide every occurrence of the Region and Parcel names if
- // the location restriction is active
- message = gRLInterface.getCensoredLocation(message);
- }
- if (gRLInterface.mContainsShownames ||
- gRLInterface.mContainsShownametags)
- {
- // Censor object IMs but not avatar IMs
- message = gRLInterface.getCensoredMessage(message);
- }
- }
- //mk
- args["MESSAGE"] = message;
- // Note: don't put the message in the IM history, even though was
- // sent via the IM mechanism.
- gNotifications.add("SystemMessageTip", args);
- break;
- }
- case IM_NOTHING_SPECIAL:
- {
- // Do not show dialog, just do IM
- if (to_id.isNull() && !gAgent.isGodlike() && regionp &&
- regionp->isPrelude())
- {
- // Do not distract newbies in Prelude with global IMs
- break;
- }
- //MK
- else if (gRLenabled && !is_muted &&
- (message == "@version" || message == "@getblacklist" ||
- message == "@list" || message == "@stopim"))
- {
- bool close_session = false;
- std::string my_name;
- gAgent.buildFullname(my_name);
- std::string response;
- if (message == "@version")
- {
- // Return the version message
- response = gRLInterface.getVersion();
- }
- else if (message == "@getblacklist")
- {
- // Return the list of the blacklisted RLV commands
- response = RLInterface::sBlackList;
- }
- else if (message == "@list")
- {
- // Return the list of the RLV restrictions in force
- response = gRLInterface.getRlvRestrictions();
- }
- else if (gRLInterface.canStartIM(from_id))
- {
- response =
- "*** The other party is not under @startim restriction.";
- }
- // @stopim
- else
- {
- close_session = true;
- response =
- "*** Session has been ended for the other party.";
- }
- // The message may be very long, so we might need to chop it
- // into chunks of 1023 characters and send several IMs in a row
- // or else it will be truncated by the server.
- std::string chunk;
- while (response.length())
- {
- if (response.length() > 1023)
- {
- chunk = response.substr(0, 1023);
- // Try to break out at the end of a text line, if
- // possible...
- size_t i = chunk.rfind('\n');
- if (i > 1)
- {
- chunk = chunk.substr(0, i);
- }
- else
- {
- i = 1023;
- }
- response = response.substr(i);
- }
- else
- {
- chunk = response;
- response.clear();
- }
- pack_instant_message(gAgentID, false, gAgentSessionID,
- from_id, my_name.c_str(),
- chunk.c_str(), IM_ONLINE,
- IM_BUSY_AUTO_RESPONSE, session_id);
- gAgent.sendReliableMessage();
- }
- if (close_session)
- {
- LLFloaterIMSession* floaterp =
- LLFloaterIMSession::findInstance(session_id);
- if (floaterp)
- {
- LLChat chat("*** IM session with " + name +
- " has been ended remotely.");
- LLFloaterChat::addChat(chat, true, false);
- floaterp->close();
- }
- }
- // Remove the "XXX is typing..." label from the IM window
- processIMTypingCore(dialog, from_id, name, false);
- }
- //mk
- else if ((is_busy || is_away || auto_reply) &&
- //MK
- (!gRLenabled ||
- // Agent is not forbidden to receive IMs or the sender is
- // an exception => send Busy response
- gRLInterface.canReceiveIM(from_id)) &&
- //mk
- offline == IM_ONLINE && !is_linden && name != SYSTEM_FROM)
- {
- // Return a standard "busy" message, but only do it to online
- // IM (i.e. not other auto responses and not store-and-forward
- // IM)
- if (!LLFloaterIMSession::findInstance(session_id))
- {
- // There is no panel for this conversation (i.e. it is a
- // new IM conversation initiated by the other party)
- std::string my_name;
- gAgent.buildFullname(my_name);
- std::string response;
- if (is_away)
- {
- response = "Away mode auto-response: ";
- }
- else if (is_busy)
- {
- response = "Busy mode auto-response: ";
- }
- else
- {
- response = "Auto-response: ";
- }
- response +=
- gSavedPerAccountSettings.getText("BusyModeResponse");
- pack_instant_message(gAgentID, false, gAgentSessionID,
- from_id, my_name, response, IM_ONLINE,
- IM_BUSY_AUTO_RESPONSE, session_id);
- gAgent.sendReliableMessage();
- }
- // Now store incoming IM in chat history
- buffer = separator_string + message.substr(message_offset);
- llinfos << "IM_NOTHING_SPECIAL session_id(" << session_id
- << "), from_id(" << from_id << ")" << llendl;
- // Add to IM panel, but do not bother the user
- addMessage(session_id, from_id, name, buffer,
- LLStringUtil::null, dialog, parent_estate_id,
- region_id, position, true);
- // Pretend this is chat generated by self, so it does not show
- // up on screen
- chat.mText = "IM: " + name + separator_string +
- message.substr(message_offset);
- LLFloaterChat::addChat(chat, true, true);
- if (gAutomationp)
- {
- gAutomationp->onInstantMsg(session_id, from_id, name,
- chat.mText);
- }
- }
- else if (from_id.isNull())
- {
- // Messages from "Second Life" ID don't go to IM history
- // messages which should be routed to IM window come from a
- // user ID with name = SYSTEM_NAME:
- chat.mText = name + ": " + message;
- //MK
- if (gRLenabled)
- {
- if (gRLInterface.mContainsShowloc)
- {
- // Hide every occurrence of the Region and Parcel names if
- // the location restriction is active
- chat.mText = gRLInterface.getCensoredLocation(chat.mText);
- }
- if (gRLInterface.mContainsShownames)
- {
- // censor that message
- chat.mText = gRLInterface.getCensoredMessage(chat.mText);
- }
- }
- //mk
- LLFloaterChat::addChat(chat, false, false);
- if (gAutomationp)
- {
- gAutomationp->onReceivedChat(chat.mChatType, from_id, name,
- chat.mText);
- }
- }
- else if (to_id.isNull())
- {
- // Message to everyone from GOD
- args["NAME"] = name;
- args["MESSAGE"] = message;
- gNotifications.add("GodMessage", args);
- // Treat like a system message and put in chat history. Claim
- // to be from a local agent so it doesn't go into console.
- chat.mText = name + separator_string +
- message.substr(message_offset);
- bool local_agent = true;
- LLFloaterChat::addChat(chat, false, local_agent);
- if (gAutomationp)
- {
- gAutomationp->onReceivedChat(chat.mChatType, from_id, name,
- chat.mText);
- }
- }
- else
- {
- // Standard message, not from system
- std::string saved;
- if (offline == IM_OFFLINE)
- {
- saved = llformat("(Saved %s) ",
- formatted_time(timestamp).c_str());
- }
- buffer = separator_string + saved +
- message.substr(message_offset);
- //MK
- bool forbid = gRLenabled &&
- !gRLInterface.canReceiveIM(from_id);
- if (forbid)
- {
- // Agent is forbidden to receive IMs and the sender is no
- // exception
- buffer = separator_string + saved +
- "*** IM blocked by your viewer";
- // Tell the sender the avatar could not read them
- std::string my_name;
- gAgent.buildFullname(my_name);
- my_name += " using viewer " +
- gRLInterface.getVersion();
- std::string response = RLInterface::sRecvimMessage;
- pack_instant_message(gAgentID, false, gAgentSessionID,
- from_id, my_name.c_str(),
- response.c_str(), IM_ONLINE,
- IM_BUSY_AUTO_RESPONSE, session_id);
- gAgent.sendReliableMessage();
- }
- //mk
- llinfos << "IM_NOTHING_SPECIAL session_id(" << session_id
- << "), from_id(" << from_id << ")" << llendl;
- if (!is_muted || is_linden)
- {
- addMessage(session_id, from_id, name, buffer,
- LLStringUtil::null, dialog, parent_estate_id,
- region_id, position, true);
- if (gAutomationp)
- {
- gAutomationp->onInstantMsg(session_id, from_id, name,
- buffer);
- }
- //MK
- // When agent is not forbidden to receive IMs or the sender
- // is an exception, duplicate in chat box.
- if (!forbid)
- //mk
- {
- chat.mText = "IM: " + name + separator_string + saved +
- message.substr(message_offset);
- bool local_agent = false;
- LLFloaterChat::addChat(chat, true, local_agent);
- }
- }
- else
- {
- // Muted user, so do not start an IM session, just record
- // line in chat history. Pretend the chat is from a local
- // agent, so it will go into the history but not be shown
- // on screen.
- chat.mText = buffer;
- bool local_agent = true;
- LLFloaterChat::addChat(chat, true, local_agent);
- }
- }
- break;
- }
- case IM_TYPING_START:
- case IM_TYPING_STOP:
- {
- bool typing_start = dialog == IM_TYPING_START;
- bool ok = processIMTypingCore(dialog, from_id, name, typing_start);
- if (!ok && typing_start && (!is_muted || is_linden) &&
- // Do not announce when busy/away/auto-replying
- !is_busy && !is_away && !auto_reply &&
- gSavedSettings.getBool("IMOpenSessionOnIncoming") &&
- //MK
- (!gRLenabled || gRLInterface.canReceiveIM(from_id)))
- //mk
- {
- addMessage(computeSessionID(dialog, from_id), from_id,
- INCOMING_IM, LLTrans::getString("im_incoming"),
- name, IM_NOTHING_SPECIAL, parent_estate_id,
- region_id, position, false);
- }
- break;
- }
- case IM_MESSAGEBOX:
- {
- // This is a block, modeless dialog.
- //*TODO:translate
- //MK
- if (gRLenabled)
- {
- if (gRLInterface.mContainsShowloc)
- {
- // Hide every occurrence of the Region and Parcel names if
- // thelocation restriction is active
- message = gRLInterface.getCensoredLocation(message);
- }
- if (gRLInterface.mContainsShownames ||
- gRLInterface.mContainsShownametags)
- {
- message = gRLInterface.getCensoredMessage(message);
- }
- }
- //mk
- args["MESSAGE"] = message;
- gNotifications.add("SystemMessage", args);
- break;
- }
- case IM_GROUP_NOTICE:
- case IM_GROUP_NOTICE_REQUESTED:
- {
- llinfos << "Received IM_GROUP_NOTICE message." << llendl;
- U8 has_inventory = 0;
- 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
- from_group = true;
- group_id = aux_id;
- has_inventory = bucket_size > 1 ? 1 : 0;
- if (has_inventory)
- {
- std::string str_bucket =
- ll_safe_string((char*)binary_bucket, bucket_size);
- tok_t tokens(str_bucket, sSeparators);
- tok_t::iterator iter = tokens.begin();
- if (iter != tokens.end())
- {
- asset_type =
- (LLAssetType::EType)(atoi((iter++)->c_str()));
- if (++iter != tokens.end())
- {
- item_name.assign(iter->c_str());
- }
- }
- }
- }
- else
- {
- // Read the binary bucket 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 nul terminated item name.
- if (bucket_size <
- (S32)(sizeof(notice_bucket_header_t) + sizeof(U8)) ||
- binary_bucket[bucket_size - 1] != '\0')
- {
- llwarns << "Malformed group notice binary bucket"
- << llendl;
- break;
- }
- 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 there is inventory, give the user the inventory offer.
- LLOfferInfo* info = NULL;
- is_muted = LLMuteList::isMuted(LLUUID::null, name, 0,
- LLMute::AGENT);
- if (has_inventory && !is_muted)
- {
- info = new LLOfferInfo;
- info->mIM = IM_GROUP_NOTICE;
- info->mFromID = from_id;
- info->mFromObject = false;
- info->mFromGroup = from_group;
- info->mTransactionID = session_id;
- info->mType = (LLAssetType::EType)asset_type;
- info->mFolderID =
- gInventory.findCategoryUUIDForType(LLFolderType::assetTypeToFolderType(info->mType));
- info->mFromName = "A group member named " + name;
- info->mDesc = item_name;
- info->mHost = sender;
- // For requested notices, we do not want a chat decline message
- // logged (it would appear each time you select another group
- // notice).
- if (dialog == IM_GROUP_NOTICE_REQUESTED || is_muted)
- {
- info->mLogInChat = false;
- }
- }
- std::string str = message;
- // Tokenize the string. *TODO: Support escaped tokens ("||" -> "|")
- tok_t tokens(str, sSeparators);
- tok_t::iterator iter = tokens.begin();
- std::string subj(*iter++);
- std::string mes(*iter++);
- // Send the notification down the new path.
- // For requested notices, we do not want to send the popups.
- if (dialog != IM_GROUP_NOTICE_REQUESTED && !is_muted)
- {
- LLSD payload;
- payload["subject"] = subj;
- payload["message"] = mes;
- payload["sender_name"] = name;
- payload["group_id"] = group_id;
- payload["inventory_name"] = item_name;
- payload["inventory_offer"] = info ? info->asLLSD() : LLSD();
- LLSD args;
- args["SUBJECT"] = subj;
- args["MESSAGE"] = mes;
- gNotifications.add(LLNotification::Params("GroupNotice").substitutions(args).payload(payload).timestamp(timestamp));
- }
- // Also send down the old path for now.
- if (IM_GROUP_NOTICE_REQUESTED == dialog)
- {
- LLFloaterGroupInfo::showNotice(subj, mes, group_id,
- has_inventory, item_name, info);
- }
- break;
- }
- case IM_GROUP_INVITATION:
- {
- //if (!is_linden && (is_busy || is_muted))
- if (is_busy || is_muted)
- {
- busy_message(from_id);
- }
- else
- {
- llinfos << "Received IM_GROUP_INVITATION message." << llendl;
- //MK
- if (gRLenabled && gRLInterface.contains("setgroup"))
- {
- llinfos << "Invitation ignored due to RestrainedLove restrictions."
- << llendl;
- break;
- }
- //mk
- // 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 (bucket_size != sizeof(invite_bucket_t))
- {
- llwarns << "Malformed group invite binary bucket"
- << llendl;
- break;
- }
- 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;
- gNotifications.add("JoinGroup", args, payload);
- }
- break;
- }
- case IM_INVENTORY_OFFERED:
- case IM_TASK_INVENTORY_OFFERED:
- {
- // Someone has offered us some inventory.
- LLOfferInfo* info = new LLOfferInfo;
- if (dialog == IM_INVENTORY_OFFERED)
- {
- struct offer_agent_bucket_t
- {
- S8 asset_type;
- LLUUID object_id;
- }* bucketp;
- if (bucket_size != sizeof(offer_agent_bucket_t))
- {
- llwarns << "Malformed inventory offer from agent"
- << llendl;
- break;
- }
- bucketp = (struct offer_agent_bucket_t*)&binary_bucket[0];
- info->mType = (LLAssetType::EType)bucketp->asset_type;
- info->mObjectID = bucketp->object_id;
- info->mFromObject = false;
- }
- // IM_TASK_INVENTORY_OFFERED
- else
- {
- if (bucket_size == sizeof(S8))
- {
- 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.
- std::string str(reinterpret_cast<char*>(binary_bucket));
- std::string str_type = str.substr(0, str.find('|'));
- std::stringstream type_convert(str_type);
- S32 type;
- type_convert >> type;
- // We could try AT_UNKNOWN which would be more accurate,
- // but it would cause an auto decline.
- info->mType = (LLAssetType::EType)type;
- // Do not break in the case of a bad binary bucket. Go
- // ahead and show the accept/decline popup even though it
- // will not do anything.
- llwarns << "Malformed inventory offer from object, type might be: "
- << info->mType
- << ". The offer will likely be impossible to process."
- << llendl;
- }
- info->mObjectID.setNull();
- info->mFromObject = true;
- }
- // In the case of an offline message, the transaction Id is in
- // aux_id and the session_id is null
- info->mTransactionID = session_id.notNull() ? session_id : aux_id;
-
- info->mIM = dialog;
- info->mFromID = from_id;
- info->mFromGroup = from_group;
- //MK
- std::string folder_name(message);
- if (gRLenabled &&
- !gSavedSettings.getBool("RestrainedLoveForbidGiveToRLV") &&
- info->mType == LLAssetType::AT_CATEGORY &&
- gRLInterface.getRlvShare() &&
- folder_name.find(RL_RLV_REDIR_FOLDER_PREFIX) == 1)
- {
- info->mFolderID = gRLInterface.getRlvShare()->getUUID();
- }
- else
- //mk
- {
- info->mFolderID =
- gInventory.findCategoryUUIDForType(LLFolderType::assetTypeToFolderType(info->mType));
- }
- if (dialog == IM_TASK_INVENTORY_OFFERED)
- {
- info->mFromObject = true;
- }
- else
- {
- info->mFromObject = false;
- }
- info->mFromName = name;
- //MK
- if (gRLenabled && gRLInterface.mContainsShowloc)
- {
- // Hide every occurrence of the Region and Parcel names if the
- // location restriction is active
- message = gRLInterface.getCensoredLocation(message);
- }
- //mk
- info->mDesc = message;
- info->extractSLURL();
- info->mHost = sender;
- is_muted = LLMuteList::isMuted(from_id, name);
- if (is_muted)
- {
- static F32 last_notification = 0.f;
- // Do not spam with such messages...
- llinfos_once << "Declining inventory offer from muted object/agent: "
- << info->mFromName << llendl;
- if (gFrameTimeSeconds - last_notification > 30.f)
- {
- LLSD args;
- args["NAME"] = info->mFromName;
- gNotifications.add("MutedObjectOfferDeclined", args);
- last_notification = gFrameTimeSeconds;
- }
- // Same as closing window
- info->forceResponse(IOR_MUTED);
- }
- else if (is_busy && dialog != IM_TASK_INVENTORY_OFFERED &&
- gSavedSettings.getBool("RejectNewInventoryWhenBusy"))
- {
- // Until throttling is implemented, busy mode should reject
- // inventory instead of silently accepting it. SEE SL-39554
- info->forceResponse(IOR_BUSY);
- }
- else
- {
- info->inventoryOfferHandler();
- }
- break;
- }
- case IM_INVENTORY_ACCEPTED:
- {
- args["NAME"] = name;
- gNotifications.add("InventoryAccepted", args);
- break;
- }
- case IM_INVENTORY_DECLINED:
- {
- args["NAME"] = name;
- gNotifications.add("InventoryDeclined", args);
- break;
- }
- case IM_GROUP_VOTE:
- {
- llwarns << "Received deprecated IM event: IM_GROUP_VOTE" << llendl;
- break;
- }
- case IM_GROUP_ELECTION_DEPRECATED:
- {
- llwarns << "Received deprecated IM event: IM_GROUP_ELECTION_DEPRECATED"
- << llendl;
- break;
- }
- case IM_SESSION_SEND:
- {
- if (!is_linden && is_busy)
- {
- return;
- }
- LLFloaterIMSession* floaterp =
- LLFloaterIMSession::findInstance(session_id);
- // Only show messages if we have a session open (which should
- // happen after you get an "invitation"
- if (!floaterp)
- {
- // Check to see if this was a snoozed session, and whether the
- // snooze delay expired or not.
- snoozed_map_t::iterator it = mSnoozedSessions.find(session_id);
- if (it == mSnoozedSessions.end())
- {
- return; // Unexpected message for a closed session: ignore.
- }
- if (it->second > gFrameTimeSeconds)
- {
- LL_DEBUGS("InstantMessaging") << "Ignoring message for snoozed session Id: "
- << session_id << LL_ENDL;
- return;
- }
- mSnoozedSessions.erase(it);
- LL_DEBUGS("InstantMessaging") << "Restoring snoozed session Id: "
- << session_id << LL_ENDL;
- }
- //MK
- if (gRLenabled)
- {
- if (!gRLInterface.canReceiveIM(from_id))
- {
- // Agent is forbidden to receive IMs
- return;
- }
- // Group session ?
- if (floaterp && floaterp->isGroupSession() &&
- !gRLInterface.canSendGroupIM(floaterp->getSessionName()))
- {
- // Agent is forbidden to receive group IMs
- return;
- }
- }
- if (gRLenabled && !gRLInterface.canReceiveIM(from_id))
- {
- // Agent is forbidden to receive IMs
- return;
- }
- //mk
- // Standard message, not from system
- std::string saved;
- if (offline == IM_OFFLINE)
- {
- saved = llformat("(Saved %s) ", formatted_time(timestamp).c_str());
- }
- buffer = separator_string + saved + message.substr(message_offset);
- addMessage(session_id, from_id, name, buffer,
- ll_safe_string((char*)binary_bucket), IM_SESSION_INVITE,
- parent_estate_id, region_id, position, true);
- if (gAutomationp)
- {
- gAutomationp->onInstantMsg(session_id, from_id, name, buffer);
- }
- chat.mText = "IM: " + name + separator_string + saved +
- message.substr(message_offset);
- LLFloaterChat::addChat(chat, true, from_id == gAgentID);
- break;
- }
- case IM_FROM_TASK:
- {
- LL_DEBUGS("InstantMessaging") << "IM_FROM_TASK: owner: " << from_id
- << " - Object name: " << name
- << " - Object Id: " << session_id
- << LL_ENDL;
- if (from_id == gAgentID)
- {
- is_owned_by_me = true;
- }
- if ((is_busy && !is_owned_by_me) ||
- LLMuteList::isMuted(from_id, LLMute::flagTextChat) ||
- LLMuteList::isMuted(session_id, name, LLMute::flagTextChat))
- {
- return;
- }
- chat.mFromName = name;
- // Build a link to open the object IM info window.
- std::string location = ll_safe_string((char*)binary_bucket,
- bucket_size);
- LLSD query_string;
- query_string["owner"] = from_id;
- query_string["slurl"] = location.c_str();
- query_string["name"] = name;
- if (from_group)
- {
- query_string["groupowned"] = "true";
- }
- 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 is not the owner ID. If it is the owner
- // ID, it will overwrite the style that contains the link to
- // that owner's profile. This is not ideal: it will make one
- // 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 ^ gAgentSessionID;
- }
- std::ostringstream link;
- link << "secondlife:///app/objectim/" << session_id
- << LLURI::mapToQueryString(query_string);
- chat.mURL = link.str();
- //MK
- if (gRLenabled)
- {
- if (gRLInterface.mContainsShowloc)
- {
- // hide the url
- chat.mURL = "";
- // Hide every occurrence of the Region and Parcel names if
- // the/ location restriction is active
- message = gRLInterface.getCensoredLocation(message);
- }
- if (gRLInterface.mContainsShownames)
- {
- message = gRLInterface.getCensoredMessage(message);
- }
- }
- //mk
- chat.mText = name + separator_string +
- message.substr(message_offset);
- // Note: lie to LLFloaterChat::addChat(), pretending that this is
- // NOT an IM, because IMs from objects do not open IM sessions.
- // However, display it like a direct chat from object.
- chat.mChatType = CHAT_TYPE_DIRECT;
- chat.mOwnerID = from_id;
- if (is_owned_by_me &&
- HBViewerAutomation::checkLuaCommand(message, from_id, name))
- {
- return;
- }
- if (gAutomationp)
- {
- gAutomationp->onReceivedChat(chat.mChatType, from_id, name,
- chat.mText);
- }
- LLFloaterChat::addChat(chat, false, false);
- break;
- }
- case IM_FROM_TASK_AS_ALERT:
- {
- if (is_busy && !is_owned_by_me)
- {
- return;
- }
- // Construct a viewer alert for this message.
- //MK
- if (gRLenabled)
- {
- if (gRLInterface.mContainsShowloc)
- {
- // Hide every occurrence of the Region and Parcel names if
- // the location restriction is active
- message = gRLInterface.getCensoredLocation(message);
- }
- if (gRLInterface.mContainsShownames ||
- gRLInterface.mContainsShownametags)
- {
- // Censor object IMs but not avatar IMs
- message = gRLInterface.getCensoredMessage(message);
- }
- }
- //mk
- args["NAME"] = name;
- args["MESSAGE"] = message;
- gNotifications.add("ObjectMessage", args);
- break;
- }
- case IM_BUSY_AUTO_RESPONSE:
- {
- if (is_muted)
- {
- LL_DEBUGS("InstantMessaging") << "Ignoring busy response from "
- << from_id << LL_ENDL;
- return;
- }
- else
- {
- // *TODO: translate.
- buffer = llformat("%s (%s): %s", name.c_str(), "busy response",
- message.substr(message_offset).c_str());
- addMessage(session_id, from_id, name, buffer);
- }
- break;
- }
- case IM_LURE_USER:
- case IM_TELEPORT_REQUEST:
- {
- if (LLMuteList::isMuted(from_id, name))
- {
- return;
- }
- //MK
- bool auto_accept = false;
- if (gRLenabled)
- {
- std::string behav = dialog == IM_LURE_USER ? "accepttp"
- : "accepttprequest";
- auto_accept = gRLInterface.contains(behav) ||
- gRLInterface.contains(behav + ":" +
- from_id.asString());
- }
- //mk
- if (is_busy
- //MK
- // Even in busy mode, accept if we are forced to
- && !auto_accept)
- //mk
- {
- busy_message(from_id);
- }
- else
- {
- //MK
- if (gRLenabled && dialog == IM_LURE_USER)
- {
- if (gRLInterface.containsWithoutException("tplure",
- from_id.asString()) ||
- (gRLInterface.mContainsUnsit &&
- isAgentAvatarValid() && gAgentAvatarp->mIsSitting))
- {
- std::string response =
- "The Resident you invited is prevented from accepting teleport offers. Please try again later.";
- pack_instant_message(gAgentID, false, gAgentSessionID,
- from_id, SYSTEM_FROM,
- response.c_str(), IM_ONLINE,
- IM_BUSY_AUTO_RESPONSE);
- gAgent.sendReliableMessage();
- return;
- }
- }
- if (gRLenabled && dialog == IM_TELEPORT_REQUEST)
- {
- if (gRLInterface.containsWithoutException("tprequest",
- from_id.asString()))
- {
- std::string response =
- "The Resident you invited is prevented from accepting teleport requests. Please try again later.";
- pack_instant_message(gAgentID, false, gAgentSessionID,
- from_id, SYSTEM_FROM,
- response.c_str(), IM_ONLINE,
- IM_BUSY_AUTO_RESPONSE);
- gAgent.sendReliableMessage();
- return;
- }
- }
- if (gRLenabled &&
- (gRLInterface.mContainsShowloc ||
- !gRLInterface.canReceiveIM(from_id)))
- {
- message = "(Hidden)";
- }
- if (gRLenabled && dialog == IM_LURE_USER && auto_accept)
- {
- // accepttp => the viewer acts like it was teleported by a god
- gRLInterface.setAllowCancelTp(false);
- LLSD payload;
- payload["from_id"] = from_id;
- payload["lure_id"] = session_id;
- payload["godlike"] = true;
- // do not show a message box, because you're about to be
- // teleported.
- gNotifications.forceResponse(LLNotification::Params("TeleportOffered").payload(payload), 0);
- }
- else if (gRLenabled && dialog == IM_TELEPORT_REQUEST && auto_accept)
- {
- // accepttprequest => the viewer automatically sends the TP
- LLSD dummy_notification, dummy_response;
- dummy_notification["payload"]["ids"][0] = from_id;
- dummy_response["message"] = "Automatic teleport offer";
- send_lures(dummy_notification, dummy_response);
- }
- else
- //mk
- {
- LLSD args;
- // *TODO:translate -> [FIRST] [LAST] (maybe)
- args["NAME"] = name;
- args["MESSAGE"] = message;
- LLSD payload;
- payload["from_id"] = from_id;
- payload["lure_id"] = session_id;
- payload["godlike"] = false;
- if (dialog == IM_TELEPORT_REQUEST)
- {
- gNotifications.add("TeleportRequest", args, payload);
- }
- else
- {
- gNotifications.add("TeleportOffered", args, payload);
- }
- }
- }
- break;
- }
- case IM_GODLIKE_LURE_USER:
- {
- LLSD payload;
- payload["from_id"] = from_id;
- payload["lure_id"] = session_id;
- payload["godlike"] = true;
- // Do not show a message box, because you're about to be
- // teleported.
- gNotifications.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 (bucket_size <= 0)
- {
- llwarns << "bad bucket_size: " << bucket_size
- << " - aborting function." << llendl;
- return;
- }
- std::string url;
- url.assign((char*)binary_bucket, bucket_size - 1);
- args["MESSAGE"] = message;
- args["URL"] = url;
- LLSD payload;
- payload["url"] = url;
- gNotifications.add("GotoURL", args, payload);
- break;
- }
- case IM_FRIENDSHIP_OFFERED:
- {
- LLSD payload;
- payload["from_id"] = from_id;
- payload["session_id"] = session_id;;
- payload["online"] = offline == IM_ONLINE;
- payload["sender"] = sender.getIPandPort();
- if (is_busy)
- {
- busy_message(from_id);
- gNotifications.forceResponse(LLNotification::Params("OfferFriendship").payload(payload), 1);
- }
- else if (LLMuteList::isMuted(from_id, name))
- {
- gNotifications.forceResponse(LLNotification::Params("OfferFriendship").payload(payload), 1);
- }
- else
- {
- args["[NAME]"] = name;
- //MK
- if (gRLenabled && !gRLInterface.canReceiveIM(from_id))
- {
- message = "(Hidden)";
- }
- //mk
- if (message.empty())
- {
- // Support for frienship offers from clients before 07/2008
- gNotifications.add("OfferFriendshipNoMessage", args,
- payload);
- }
- else
- {
- args["[MESSAGE]"] = message;
- gNotifications.add("OfferFriendship", args, payload);
- }
- }
- 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 does not hurt for dupes.
- LLAvatarTracker::formFriendship(from_id);
- std::vector<std::string> strings;
- strings.emplace_back(from_id.asString());
- send_generic_message("requestonlinenotification", strings);
- args["NAME"] = name;
- gNotifications.add("FriendshipAccepted", args);
- break;
- }
- case IM_FRIENDSHIP_DECLINED_DEPRECATED:
- default:
- {
- llwarns << "Instant message calling for unknown dialog "
- << (S32)dialog << llendl;
- }
- }
- if (gWindowp && gWindowp->getMinimized())
- {
- F32 flash_time = gSavedSettings.getF32("TaskBarButtonFlashTime");
- if (flash_time > 0.f)
- {
- gWindowp->flashIcon(flash_time);
- }
- }
- }
- bool LLIMMgr::processIMTypingCore(EInstantMessage dialog,
- const LLUUID& from_id,
- const std::string& from_name, bool typing)
- {
- LLUUID session_id = computeSessionID(dialog, from_id);
- LLFloaterIMSession* floaterp =
- LLFloaterIMSession::findInstance(session_id);
- if (floaterp)
- {
- floaterp->processIMTyping(from_id, from_name, typing);
- return true;
- }
- return false;
- }
- void LLIMMgr::updateFloaterSessionID(const LLUUID& old_session_id,
- const LLUUID& new_session_id)
- {
- LLFloaterIMSession* floaterp =
- LLFloaterIMSession::findInstance(old_session_id);
- if (floaterp)
- {
- floaterp->sessionInitReplyReceived(new_session_id);
- }
- }
- // LLFloaterIMSession::sessionInitReplyReceived() above will call back this
- // method:
- //static
- void LLIMMgr::deliverMessage(const std::string& utf8_text,
- const LLUUID& im_session_id,
- const LLUUID& other_participant_id,
- EInstantMessage dialog)
- {
- std::string name;
- gAgent.buildFullname(name);
- const LLRelationship* info =
- gAvatarTracker.getBuddyInfo(other_participant_id);
- U8 offline = (!info || info->isOnline()) ? IM_ONLINE : IM_OFFLINE;
- // Send message normally. Default to IM_SESSION_SEND unless it is nothing
- // special, in which case it is probably an IM to everyone.
- U8 new_dialog = dialog;
- if (dialog != IM_NOTHING_SPECIAL)
- {
- new_dialog = IM_SESSION_SEND;
- }
- pack_instant_message(gAgentID, false, gAgentSessionID,
- other_participant_id, name.c_str(), utf8_text.c_str(),
- offline, (EInstantMessage)new_dialog, im_session_id);
- gAgent.sendReliableMessage();
- // If there is a mute list and this is not a group chat the target should
- // not be in our mute list for some message types. Auto-remove them if
- // present.
- switch (dialog)
- {
- #if 0 // Enabling this makes it impossible to mute permanently a resident who
- // initiated a group IM session (posting in the group chat would unmute
- // them)
- case IM_SESSION_INVITE:
- #endif
- case IM_NOTHING_SPECIAL:
- case IM_GROUP_INVITATION:
- case IM_INVENTORY_OFFERED:
- case IM_SESSION_P2P_INVITE:
- case IM_SESSION_CONFERENCE_START:
- case IM_SESSION_SEND: // Marginal: erring on the side of hearing.
- case IM_LURE_USER:
- case IM_GODLIKE_LURE_USER:
- case IM_FRIENDSHIP_OFFERED:
- LLMuteList::autoRemove(other_participant_id, LLMuteList::AR_IM);
- break;
- default: ; // do nothing
- }
- }
- //static
- bool LLIMMgr::requestOfflineMessages()
- {
- LLMessageSystem* msg = gMessageSystemp;
- if (!msg || gDisconnected)
- {
- return false;
- }
- if (!gAgent.regionCapabilitiesReceived())
- {
- return false;
- }
- if (!gSavedSettings.getBool("UseOfflineIMsCapability"))
- {
- return requestOfflineMessagesLegacy();
- }
- const std::string& cap_url = gAgent.getRegionCapability("ReadOfflineMsgs");
- if (cap_url.empty() ||
- // NOTE: Offline messages capability provides no session/transaction
- // Ids for message AcceptFriendship and IM_GROUP_INVITATION to work,
- // so make sure we have the necessary caps before using it.
- !gAgent.hasRegionCapability("AcceptFriendship") ||
- !gAgent.hasRegionCapability("AcceptGroupInvite"))
- {
- return requestOfflineMessagesLegacy();
- }
- LL_DEBUGS("InstantMessaging") << "Using capability for offline instant messages request"
- << LL_ENDL;
- gCoros.launch("requestOfflineMessagesCoro",
- boost::bind(&LLIMMgr::requestOfflineMessagesCoro, cap_url));
- return true;
- }
- //static
- bool LLIMMgr::requestOfflineMessagesLegacy()
- {
- LLMessageSystem* msg = gMessageSystemp;
- if (!msg)
- {
- return false;
- }
- LL_DEBUGS("InstantMessaging") << "Using UDP messaging for offline instant messages request"
- << LL_ENDL;
- msg->newMessageFast(_PREHASH_RetrieveInstantMessages);
- msg->nextBlockFast(_PREHASH_AgentData);
- msg->addUUIDFast(_PREHASH_AgentID, gAgentID);
- msg->addUUIDFast(_PREHASH_SessionID, gAgentSessionID);
- gAgent.sendReliableMessage();
- return true;
- }
- //static
- void LLIMMgr::requestOfflineMessagesCoro(const std::string& url)
- {
- LLCoreHttpUtil::HttpCoroutineAdapter adapter("requestOfflineMessages");
- LLSD result = adapter.getAndSuspend(url);
- LLViewerRegion* regionp = gAgent.getRegion();
- if (!regionp || !gIMMgrp)
- {
- return; // Viewer is likely disconnected or closing down !
- }
- LLCore::HttpStatus status =
- LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(result);
- if (!status)
- {
- llwarns << "Error requesting offline messages via capability. Error: "
- << status.toString() << llendl;
- if (requestOfflineMessagesLegacy())
- {
- llinfos << "Sent offline messages request via legacy UDP messaging"
- << llendl;
- }
- else
- {
- llwarns << "Failed to send offline messages request via legacy UDP messaging"
- << llendl;
- }
- return;
- }
- const LLSD& contents =
- result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS_CONTENT];
- if (!contents.size())
- {
- llinfos << "No contents received for offline messages via capability"
- << llendl;
- return;
- }
- LLSD messages;
- if (contents.isArray())
- {
- messages = *contents.beginArray();
- }
- else if (contents.has("messages"))
- {
- messages = contents["messages"];
- }
- else
- {
- llwarns << "Malformed contents received for offline messages via capability"
- << llendl;
- return;
- }
- if (!messages.isArray())
- {
- llwarns << "Malformed contents received for offline messages via capability"
- << llendl;
- return;
- }
- if (!messages.size())
- {
- // Nothing to process
- return;
- }
- std::vector<U8> data;
- LLVector3 position;
- std::string message, from_name;
- LLUUID session_id;
- LLHost sender = regionp->getHost();
- for (LLSD::array_iterator it = messages.beginArray(),
- end = messages.endArray();
- it != end; ++it)
- {
- const LLSD& message_data(*it);
- LL_DEBUGS("InstantMessaging") << "Processing offline message:\n";
- std::stringstream str;
- LLSDSerialize::toPrettyXML(message_data, str);
- LL_CONT << "\n" << str.str() << LL_ENDL;
- EInstantMessage dialog =
- (EInstantMessage)message_data["dialog"].asInteger();
- if (message_data.has("message"))
- {
- message = message_data["message"].asString();
- LL_DEBUGS("InstantMessaging") << "Found 'message'" << LL_ENDL;
- }
- else
- {
- message.clear();
- LL_DEBUGS("InstantMessaging") << "No message !" << LL_ENDL;
- }
- U32 parent_estate_id = 1; // 1 = Main land
- if (message_data.has("parent_estate_id"))
- {
- parent_estate_id = message_data["parent_estate_id"].asInteger();
- LL_DEBUGS("InstantMessaging") << "Found 'parent_estate_id': "
- << parent_estate_id << LL_ENDL;
- }
- if (message_data.has("position"))
- {
- position.setValue(message_data["position"]);
- LL_DEBUGS("InstantMessaging") << "Found 'position'" << LL_ENDL;
- }
- else if (message_data.has("local_x"))
- {
- position.set(message_data["local_x"].asReal(),
- message_data["local_y"].asReal(),
- message_data["local_z"].asReal());
- LL_DEBUGS("InstantMessaging") << "Found 'local_x/y/z'" << LL_ENDL;
- }
- else
- {
- position.clear();
- LL_DEBUGS("InstantMessaging") << "No position !" << LL_ENDL;
- }
- data.clear();
- if (message_data.has("binary_bucket"))
- {
- data = message_data["binary_bucket"].asBinary();
- LL_DEBUGS("InstantMessaging") << "Found 'binary_bucket'"
- << LL_ENDL;
- }
- else
- {
- data.push_back(0);
- }
- 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";
- }
- if (message_data.has("transaction-id"))
- {
- session_id = message_data["transaction-id"].asUUID();
- LL_DEBUGS("InstantMessaging") << "Found 'transaction-id': "
- << session_id << LL_ENDL;
- }
- // Fallbacks, in case LL changes this field name for something more
- // coherent (no dash is ever used in other names but underline is) or
- // meaningful (this actually is a session Id) in the future... HB
- else if (message_data.has("transaction_id"))
- {
- session_id = message_data["transaction_id"].asUUID();
- LL_DEBUGS("InstantMessaging") << "Found 'transaction_id': "
- << session_id << LL_ENDL;
- }
- else if (message_data.has("session_id"))
- {
- session_id = message_data["session_id"].asUUID();
- LL_DEBUGS("InstantMessaging") << "Found 'session_id': "
- << session_id << LL_ENDL;
- }
- else
- {
- session_id.setNull();
- LL_DEBUGS("InstantMessaging") << "No session/transaction id !"
- << LL_ENDL;
- }
- if (session_id.isNull() && dialog == IM_FROM_TASK)
- {
- session_id = message_data["asset_id"].asUUID();
- LL_DEBUGS("InstantMessaging") << "IM_FROM_TASK: using the asset Id for the session Id"
- << LL_ENDL;
- }
- U8 im_type = IM_OFFLINE;
- if (message_data.has("offline"))
- {
- im_type = (U8)message_data["offline"].asInteger();
- LL_DEBUGS("InstantMessaging") << "Found 'offline': "
- << (S32)im_type << LL_ENDL;
- }
- if (message_data.has("from_agent_name"))
- {
- from_name = message_data["from_agent_name"].asString();
- LL_DEBUGS("InstantMessaging") << "Found 'from_agent_name': "
- << from_name << LL_ENDL;
- }
- else if (message_data.has("from_name"))
- {
- from_name = message_data["from_name"].asString();
- LL_DEBUGS("InstantMessaging") << "Found 'from_name': "
- << from_name << LL_ENDL;
- }
- else
- {
- from_name.clear();
- LL_DEBUGS("InstantMessaging") << "No originator name !" << LL_ENDL;
- }
- gIMMgrp->processNewMessage(message_data["from_agent_id"].asUUID(),
- from_group,
- message_data["to_agent_id"].asUUID(),
- im_type, dialog, session_id,
- (U32)message_data["timestamp"].asInteger(),
- from_name, message, parent_estate_id,
- message_data["region_id"].asUUID(),
- position, data.data(), data.size(), sender,
- // Not necessarily an asset
- message_data["asset_id"].asUUID());
- }
- }
- class LLViewerChatterBoxSessionStartReply final : public LLHTTPNode
- {
- public:
- void describe(Description& desc) const override
- {
- 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__);
- }
- void post(ResponsePtr response, const LLSD& context,
- const LLSD& input) const override
- {
- LLSD body;
- LLUUID temp_session_id;
- LLUUID session_id;
- bool success;
- if (!gIMMgrp) return;
- body = input["body"];
- success = body["success"].asBoolean();
- temp_session_id = body["temp_session_id"].asUUID();
- if (success)
- {
- session_id = body["session_id"].asUUID();
- gIMMgrp->updateFloaterSessionID(temp_session_id, session_id);
- LLFloaterIMSession* floaterp =
- LLFloaterIMSession::findInstance(session_id);
- if (floaterp)
- {
- floaterp->setSpeakers(body);
- // Apply updates we have possibly received previously
- floaterp->updateSpeakersList(gIMMgrp->getPendingAgentListUpdates(session_id));
- if (body.has("session_info"))
- {
- floaterp->processSessionUpdate(body["session_info"]);
- }
- // Apply updates we have possibly received previously
- floaterp->updateSpeakersList(gIMMgrp->getPendingAgentListUpdates(session_id));
- }
- gIMMgrp->clearPendingAgentListUpdates(session_id);
- }
- else
- {
- // Throw an error dialog and close the temp session's floater
- LLFloaterIMSession* floaterp =
- LLFloaterIMSession::findInstance(temp_session_id);
- if (floaterp)
- {
- floaterp->showSessionStartError(body["error"].asString());
- }
- }
- gIMMgrp->clearPendingAgentListUpdates(session_id);
- }
- };
- class LLViewerChatterBoxSessionEventReply final : public LLHTTPNode
- {
- public:
- void describe(Description& desc) const override
- {
- 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__);
- }
- void post(ResponsePtr response, const LLSD& context,
- const LLSD& input) const override
- {
- 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
- LLFloaterIMSession* floaterp =
- LLFloaterIMSession::findInstance(session_id);
- if (floaterp)
- {
- floaterp->showSessionEventError(body["event"].asString(),
- body["error"].asString());
- }
- }
- }
- };
- class LLViewerForceCloseChatterBoxSession final : public LLHTTPNode
- {
- public:
- void post(ResponsePtr response, const LLSD& context,
- const LLSD& input) const override
- {
- LLUUID session_id = input["body"]["session_id"].asUUID();
- LLFloaterIMSession* floaterp =
- LLFloaterIMSession::findInstance(session_id);
- if (floaterp)
- {
- std::string reason = input["body"]["reason"].asString();
- floaterp->showSessionForceClose(reason);
- }
- }
- };
- class LLViewerChatterBoxSessionAgentListUpdates final : public LLHTTPNode
- {
- public:
- void post(ResponsePtr responder, const LLSD& context,
- const LLSD& input) const override
- {
- if (!gIMMgrp) return;
- const LLSD& body = input["body"];
- if (body.isMap())
- {
- LLUUID session_id = body["session_id"].asUUID();
- gIMMgrp->processAgentListUpdates(session_id, body);
- }
- }
- };
- class LLViewerChatterBoxSessionUpdate final : public LLHTTPNode
- {
- public:
- void post(ResponsePtr responder, const LLSD& context,
- const LLSD& input) const override
- {
- const LLSD& body = input["body"];
- if (!body.isMap())
- {
- return;
- }
- LLFloaterIMSession* floaterp =
- LLFloaterIMSession::findInstance(body["session_id"].asUUID());
- if (floaterp)
- {
- floaterp->processSessionUpdate(body["info"]);
- }
- }
- };
- class LLViewerChatterBoxInvitation final : public LLHTTPNode
- {
- public:
- void post(ResponsePtr response, const LLSD& context,
- const LLSD& input) const override
- {
- if (!gIMMgrp) return;
- // 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 its own function - jwolk
- std::string message = message_params["message"].asString();
- std::string name = message_params["from_name"].asString();
- if (LLAvatarName::sOmitResidentAsLastName)
- {
- name = LLCacheName::cleanFullName(name);
- }
- LLUUID from_id = message_params["from_id"].asUUID();
- LLUUID session_id = message_params["id"].asUUID();
- const LLSD::Binary& 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_busy = gAgent.getBusy();
- bool is_muted = LLMuteList::isMuted(from_id, name,
- LLMute::flagTextChat);
- bool is_linden = LLMuteList::isLinden(name);
- std::string separator_string(": ");
- size_t message_offset = 0;
- // Handle IRC styled /me messages.
- std::string prefix = message.substr(0, 4);
- if (prefix == "/me " || prefix == "/me'")
- {
- separator_string.clear();
- message_offset = 3;
- }
- LLChat chat;
- chat.mMuted = is_muted && !is_linden;
- chat.mFromID = from_id;
- chat.mFromName = name;
- if (!is_linden && (is_busy || is_muted))
- {
- return;
- }
- //MK
- if (gRLenabled && !gRLInterface.canReceiveIM(from_id))
- {
- return;
- }
- //mk
- // Standard message, not from system
- std::string saved;
- if (offline == IM_OFFLINE)
- {
- saved = llformat("(Saved %s) ",
- formatted_time(timestamp).c_str());
- }
- std::string buffer = separator_string + saved +
- message.substr(message_offset);
- bool is_this_agent = false;
- if (from_id == gAgentID)
- {
- is_this_agent = true;
- }
- // Do not process muted IMs
- if (!is_this_agent && !LLMuteList::isLinden(name) &&
- LLMuteList::isMuted(from_id, LLMute::flagTextChat))
- {
- // Muted agent
- return;
- }
- else if (session_id.notNull() &&
- LLMuteList::isMuted(session_id, LLMute::flagTextChat))
- {
- // Muted group
- return;
- }
- gIMMgrp->addMessage(session_id, from_id, name, buffer,
- 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);
- if (gAutomationp)
- {
- gAutomationp->onInstantMsg(session_id, from_id, name, buffer);
- if (!LLFloaterIMSession::findInstance(session_id))
- {
- // If the automation script OnInstantMsg() callback closed
- // the session as a result of this IM, abort now.
- return;
- }
- }
- chat.mText = "IM: " + name + separator_string + saved +
- message.substr(message_offset);
- LLFloaterChat::addChat(chat, true, is_this_agent);
- // OK, now we want to accept the invitation
- const std::string& url =
- gAgent.getRegionCapability("ChatSessionRequest");
- if (!url.empty())
- {
- gCoros.launch("chatterBoxInvitationCoro",
- boost::bind(&LLIMMgr::chatterBoxInvitationCoro,
- url, session_id,
- LLIMMgr::INVITATION_TYPE_INSTANT_MESSAGE));
- }
- }
- else if (input["body"].has("voice"))
- {
- if (!gVoiceClient.isVoiceWorking())
- {
- // Do not display voice invites unless the user has voice
- // enabled
- return;
- }
- const LLSD& voice = input["body"]["voice"];
- constexpr S32 P2P_SESSION = 2; // Note: WebRTC P2P voice only.
- bool p2p = voice.has("invitation_type") &&
- voice["invitation_type"].asInteger() == P2P_SESSION;
- gIMMgrp->inviteToSession(input["body"]["session_id"].asUUID(),
- input["body"]["session_name"].asString(),
- input["body"]["from_id"].asUUID(),
- input["body"]["from_name"].asString(),
- p2p ? IM_SESSION_P2P_INVITE
- : IM_SESSION_INVITE,
- LLIMMgr::INVITATION_TYPE_VOICE);
- }
- else if (input["body"].has("immediate"))
- {
- gIMMgrp->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<LLViewerChatterBoxSessionStartReply>
- gHTTPRegistrationMessageChatterboxsessionstartreply(
- "/message/ChatterBoxSessionStartReply");
- LLHTTPRegistration<LLViewerChatterBoxSessionEventReply>
- gHTTPRegistrationMessageChatterboxsessioneventreply(
- "/message/ChatterBoxSessionEventReply");
- LLHTTPRegistration<LLViewerForceCloseChatterBoxSession>
- gHTTPRegistrationMessageForceclosechatterboxsession(
- "/message/ForceCloseChatterBoxSession");
- LLHTTPRegistration<LLViewerChatterBoxSessionAgentListUpdates>
- gHTTPRegistrationMessageChatterboxsessionagentlistupdates(
- "/message/ChatterBoxSessionAgentListUpdates");
- LLHTTPRegistration<LLViewerChatterBoxSessionUpdate>
- gHTTPRegistrationMessageChatterBoxSessionUpdate(
- "/message/ChatterBoxSessionUpdate");
- LLHTTPRegistration<LLViewerChatterBoxInvitation>
- gHTTPRegistrationMessageChatterBoxInvitation(
- "/message/ChatterBoxInvitation");
|