FriendsModule.cs 42 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143
  1. /*
  2. * Copyright (c) Contributors, http://opensimulator.org/
  3. * See CONTRIBUTORS.TXT for a full list of copyright holders.
  4. *
  5. * Redistribution and use in source and binary forms, with or without
  6. * modification, are permitted provided that the following conditions are met:
  7. * * Redistributions of source code must retain the above copyright
  8. * notice, this list of conditions and the following disclaimer.
  9. * * Redistributions in binary form must reproduce the above copyright
  10. * notice, this list of conditions and the following disclaimer in the
  11. * documentation and/or other materials provided with the distribution.
  12. * * Neither the name of the OpenSimulator Project nor the
  13. * names of its contributors may be used to endorse or promote products
  14. * derived from this software without specific prior written permission.
  15. *
  16. * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
  17. * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  18. * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  19. * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
  20. * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  21. * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  22. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  23. * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  24. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  25. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  26. */
  27. using System;
  28. using System.Collections.Generic;
  29. using System.Reflection;
  30. using log4net;
  31. using Nini.Config;
  32. using OpenMetaverse;
  33. using Mono.Addins;
  34. using OpenSim.Framework;
  35. using OpenSim.Framework.Servers.HttpServer;
  36. using OpenSim.Framework.Servers;
  37. using OpenSim.Region.Framework.Interfaces;
  38. using OpenSim.Region.Framework.Scenes;
  39. using OpenSim.Services.Interfaces;
  40. using OpenSim.Services.Connectors.Friends;
  41. using OpenSim.Server.Base;
  42. using FriendInfo = OpenSim.Services.Interfaces.FriendInfo;
  43. using PresenceInfo = OpenSim.Services.Interfaces.PresenceInfo;
  44. using GridRegion = OpenSim.Services.Interfaces.GridRegion;
  45. namespace OpenSim.Region.CoreModules.Avatar.Friends
  46. {
  47. [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "FriendsModule")]
  48. public class FriendsModule : ISharedRegionModule, IFriendsModule
  49. {
  50. private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
  51. protected bool m_Enabled = false;
  52. protected class UserFriendData
  53. {
  54. public UUID PrincipalID;
  55. public FriendInfo[] Friends;
  56. public int Refcount;
  57. public bool IsFriend(string friend)
  58. {
  59. foreach (FriendInfo fi in Friends)
  60. {
  61. if (fi.Friend == friend)
  62. return true;
  63. }
  64. return false;
  65. }
  66. }
  67. protected static readonly FriendInfo[] EMPTY_FRIENDS = Array.Empty<FriendInfo>();
  68. protected List<Scene> m_Scenes = new();
  69. protected IPresenceService m_PresenceService = null;
  70. protected IFriendsService m_FriendsService = null;
  71. protected FriendsSimConnector m_FriendsSimConnector;
  72. /// <summary>
  73. /// Cache friends lists for users.
  74. /// </summary>
  75. /// <remarks>
  76. /// This is a complex and error-prone thing to do. At the moment, we assume that the efficiency gained in
  77. /// permissions checks outweighs the disadvantages of that complexity.
  78. /// </remarks>
  79. protected Dictionary<UUID, UserFriendData> m_Friends = new();
  80. protected Dictionary<UUID, HashSet<UUID>> m_OnlineFriendsCache = new();
  81. /// <summary>
  82. /// Maintain a record of clients that need to notify about their online status. This only
  83. /// needs to be done on login. Subsequent online/offline friend changes are sent by a different mechanism.
  84. /// </summary>
  85. protected HashSet<UUID> m_NeedsToNotifyStatus = new();
  86. /// <summary>
  87. /// Maintain a record of viewers that need to be sent notifications for friends that are online. This only
  88. /// needs to be done on login. Subsequent online/offline friend changes are sent by a different mechanism.
  89. /// </summary>
  90. protected HashSet<UUID> m_NeedsListOfOnlineFriends = new();
  91. protected IPresenceService PresenceService
  92. {
  93. get
  94. {
  95. if (m_PresenceService is null)
  96. {
  97. if (m_Scenes.Count > 0)
  98. m_PresenceService = m_Scenes[0].RequestModuleInterface<IPresenceService>();
  99. }
  100. return m_PresenceService;
  101. }
  102. }
  103. public IFriendsService FriendsService
  104. {
  105. get
  106. {
  107. if (m_FriendsService is null)
  108. {
  109. if (m_Scenes.Count > 0)
  110. m_FriendsService = m_Scenes[0].RequestModuleInterface<IFriendsService>();
  111. }
  112. return m_FriendsService;
  113. }
  114. }
  115. protected IGridService GridService
  116. {
  117. get { return m_Scenes[0].GridService; }
  118. }
  119. public IUserAccountService UserAccountService
  120. {
  121. get { return m_Scenes[0].UserAccountService; }
  122. }
  123. public IScene Scene
  124. {
  125. get
  126. {
  127. if (m_Scenes.Count > 0)
  128. return m_Scenes[0];
  129. else
  130. return null;
  131. }
  132. }
  133. #region ISharedRegionModule
  134. public void Initialise(IConfigSource config)
  135. {
  136. IConfig moduleConfig = config.Configs["Modules"];
  137. if (moduleConfig != null)
  138. {
  139. string name = moduleConfig.GetString("FriendsModule", "FriendsModule");
  140. if (name == Name)
  141. {
  142. InitModule(config);
  143. m_Enabled = true;
  144. m_log.DebugFormat("[FRIENDS MODULE]: {0} enabled.", Name);
  145. }
  146. }
  147. }
  148. protected virtual void InitModule(IConfigSource config)
  149. {
  150. IConfig friendsConfig = config.Configs["Friends"];
  151. if (friendsConfig != null)
  152. {
  153. int mPort = friendsConfig.GetInt("Port", 0);
  154. string connector = friendsConfig.GetString("Connector", String.Empty);
  155. Object[] args = new Object[] { config };
  156. m_FriendsService = ServerUtils.LoadPlugin<IFriendsService>(connector, args);
  157. m_FriendsSimConnector = new FriendsSimConnector();
  158. // Instantiate the request handler
  159. IHttpServer server = MainServer.GetHttpServer((uint)mPort);
  160. server?.AddSimpleStreamHandler(new FriendsSimpleRequestHandler(this));
  161. }
  162. if (m_FriendsService is null)
  163. {
  164. m_log.Error("[FRIENDS]: No Connector defined in section Friends, or failed to load, cannot continue");
  165. throw new Exception("Connector load error");
  166. }
  167. }
  168. public void PostInitialise()
  169. {
  170. }
  171. public void Close()
  172. {
  173. }
  174. public virtual void AddRegion(Scene scene)
  175. {
  176. if (!m_Enabled)
  177. return;
  178. //m_log.DebugFormat("[FRIENDS MODULE]: AddRegion on {0}", Name);
  179. m_Scenes.Add(scene);
  180. scene.RegisterModuleInterface<IFriendsModule>(this);
  181. scene.EventManager.OnNewClient += OnNewClient;
  182. scene.EventManager.OnClientClosed += OnClientClosed;
  183. scene.EventManager.OnMakeRootAgent += OnMakeRootAgent;
  184. scene.EventManager.OnClientLogin += OnClientLogin;
  185. }
  186. public virtual void RegionLoaded(Scene scene) {}
  187. public void RemoveRegion(Scene scene)
  188. {
  189. if (!m_Enabled)
  190. return;
  191. m_Scenes.Remove(scene);
  192. }
  193. public virtual string Name
  194. {
  195. get { return "FriendsModule"; }
  196. }
  197. public Type ReplaceableInterface
  198. {
  199. get { return null; }
  200. }
  201. #endregion
  202. public virtual int GetRightsGrantedByFriend(UUID principalID, UUID friendID)
  203. {
  204. FriendInfo[] friends = GetFriendsFromCache(principalID);
  205. if (friends.Length > 0)
  206. {
  207. FriendInfo finfo = GetFriend(friends, friendID);
  208. if (finfo is not null && finfo.TheirFlags != -1)
  209. {
  210. return finfo.TheirFlags;
  211. }
  212. }
  213. return 0;
  214. }
  215. public bool IsFriend(UUID principalID, UUID friendID)
  216. {
  217. FriendInfo[] friends = GetFriendsFromCache(principalID);
  218. if (friends.Length > 0)
  219. {
  220. FriendInfo finfo = GetFriend(friends, friendID);
  221. return (finfo is not null && finfo.TheirFlags != -1);
  222. }
  223. return false;
  224. }
  225. public bool IsFriendOnline(UUID userID, UUID friendID)
  226. {
  227. if(m_OnlineFriendsCache.TryGetValue(userID, out HashSet<UUID> friends))
  228. return friends.Contains(friendID);
  229. return false;
  230. }
  231. public void CacheFriendsOnline(UUID userID, List<UUID> friendsOnline, bool online)
  232. {
  233. if (!m_OnlineFriendsCache.TryGetValue(userID, out HashSet<UUID> friends))
  234. {
  235. friends = new HashSet<UUID>();
  236. m_OnlineFriendsCache[userID] = friends;
  237. }
  238. if (online)
  239. {
  240. foreach (UUID friendID in friendsOnline)
  241. friends.Add(friendID);
  242. }
  243. else
  244. {
  245. foreach (UUID friendID in friendsOnline)
  246. friends.Remove(friendID);
  247. }
  248. }
  249. public virtual void CacheFriendOnline(UUID userID, UUID friendID, bool online)
  250. {
  251. if (!m_OnlineFriendsCache.TryGetValue(userID, out HashSet<UUID> friends))
  252. {
  253. friends = new HashSet<UUID>();
  254. m_OnlineFriendsCache[userID] = friends;
  255. }
  256. if (online)
  257. friends.Add(friendID);
  258. else
  259. friends.Remove(friendID);
  260. }
  261. public virtual List<UUID> GetCachedFriendsOnline(UUID userID)
  262. {
  263. if (m_OnlineFriendsCache.TryGetValue(userID, out HashSet<UUID> friends))
  264. {
  265. List<UUID> friendslst = new List<UUID>(friends.Count);
  266. foreach(UUID id in friends)
  267. friendslst.Add(id);
  268. return friendslst;
  269. }
  270. else
  271. return null;
  272. }
  273. private void OnMakeRootAgent(ScenePresence sp)
  274. {
  275. if(sp.m_gotCrossUpdate)
  276. return;
  277. RecacheFriends(sp.ControllingClient);
  278. lock (m_NeedsToNotifyStatus)
  279. {
  280. if (m_NeedsToNotifyStatus.Remove(sp.UUID))
  281. {
  282. // Inform the friends that this user is online. This can only be done once the client is a Root Agent.
  283. StatusChange(sp.UUID, true);
  284. }
  285. }
  286. }
  287. private void OnNewClient(IClientAPI client)
  288. {
  289. client.OnInstantMessage += OnInstantMessage;
  290. if (client is INPC)
  291. return;
  292. client.OnApproveFriendRequest += OnApproveFriendRequest;
  293. client.OnDenyFriendRequest += OnDenyFriendRequest;
  294. client.OnTerminateFriendship += RemoveFriendship;
  295. client.OnGrantUserRights += GrantRights;
  296. client.OnFindAgent += FindFriend;
  297. // We need to cache information for child agents as well as root agents so that friend edit/move/delete
  298. // permissions will work across borders where both regions are on different simulators.
  299. //
  300. // Do not do this asynchronously. If we do, then subsequent code can outrace CacheFriends() and
  301. // return misleading results from the still empty friends cache.
  302. // If we absolutely need to do this asynchronously, then a signalling mechanism is needed so that calls
  303. // to GetFriends() will wait until CacheFriends() completes. Locks are insufficient.
  304. CacheFriends(client);
  305. }
  306. /// <summary>
  307. /// Cache the friends list or increment the refcount for the existing friends list.
  308. /// </summary>
  309. /// <param name="client">
  310. /// </param>
  311. /// <returns>
  312. /// Returns true if the list was fetched, false if it wasn't
  313. /// </returns>
  314. protected virtual bool CacheFriends(IClientAPI client)
  315. {
  316. UUID agentID = client.AgentId;
  317. lock (m_Friends)
  318. {
  319. if (m_Friends.TryGetValue(agentID, out UserFriendData friendsData))
  320. {
  321. friendsData.Refcount++;
  322. return false;
  323. }
  324. else
  325. {
  326. friendsData = new UserFriendData
  327. {
  328. PrincipalID = agentID,
  329. Friends = GetFriendsFromService(client),
  330. Refcount = 1
  331. };
  332. m_Friends[agentID] = friendsData;
  333. return true;
  334. }
  335. }
  336. }
  337. private void OnClientClosed(UUID agentID, Scene scene)
  338. {
  339. ScenePresence sp = scene.GetScenePresence(agentID);
  340. if (sp != null && !sp.IsChildAgent)
  341. {
  342. // do this for root agents closing out
  343. StatusChange(agentID, false);
  344. }
  345. lock (m_Friends)
  346. {
  347. m_OnlineFriendsCache.Remove(agentID);
  348. if (m_Friends.TryGetValue(agentID, out UserFriendData friendsData))
  349. {
  350. friendsData.Refcount--;
  351. if (friendsData.Refcount <= 0)
  352. m_Friends.Remove(agentID);
  353. }
  354. }
  355. }
  356. private void OnClientLogin(IClientAPI client)
  357. {
  358. UUID agentID = client.AgentId;
  359. //m_log.DebugFormat("[XXX]: OnClientLogin!");
  360. // Register that we need to send this user's status to friends. This can only be done
  361. // once the client becomes a Root Agent, because as part of sending out the presence
  362. // we also get back the presence of the HG friends, and we need to send that to the
  363. // client, but that can only be done when the client is a Root Agent.
  364. lock (m_NeedsToNotifyStatus)
  365. m_NeedsToNotifyStatus.Add(agentID);
  366. // Register that we need to send the list of online friends to this user
  367. lock (m_NeedsListOfOnlineFriends)
  368. m_NeedsListOfOnlineFriends.Add(agentID);
  369. }
  370. public void IsNowRoot(ScenePresence sp)
  371. {
  372. OnMakeRootAgent(sp);
  373. }
  374. public virtual bool SendFriendsOnlineIfNeeded(IClientAPI client)
  375. {
  376. if (client is null)
  377. return false;
  378. // Check if the online friends list is needed
  379. lock (m_NeedsListOfOnlineFriends)
  380. {
  381. if (!m_NeedsListOfOnlineFriends.Remove(client.AgentId))
  382. return false;
  383. }
  384. // Send the friends online
  385. List<UUID> online = GetOnlineFriends(client.AgentId);
  386. if (online.Count > 0)
  387. client.SendAgentOnline(online.ToArray());
  388. // Send outstanding friendship offers
  389. List<string> outstanding = new();
  390. FriendInfo[] friends = GetFriendsFromCache(client.AgentId);
  391. foreach (FriendInfo fi in friends)
  392. {
  393. if (fi.TheirFlags == -1)
  394. outstanding.Add(fi.Friend);
  395. }
  396. GridInstantMessage im = new(client.Scene, UUID.Zero, string.Empty, client.AgentId, (byte)InstantMessageDialog.FriendshipOffered,
  397. "Will you be my friend?", true, Vector3.Zero);
  398. foreach (string fid in outstanding)
  399. {
  400. if (!GetAgentInfo(client.Scene.RegionInfo.ScopeID, fid, out UUID fromAgentID, out string firstname, out string lastname))
  401. {
  402. m_log.DebugFormat("[FRIENDS MODULE]: skipping malformed friend {0}", fid);
  403. continue;
  404. }
  405. im.offline = 0;
  406. im.fromAgentID = fromAgentID.Guid;
  407. im.fromAgentName = firstname + " " + lastname;
  408. im.imSessionID = im.fromAgentID;
  409. im.message = FriendshipMessage(fid);
  410. LocalFriendshipOffered(client.AgentId, im);
  411. }
  412. return true;
  413. }
  414. protected virtual string FriendshipMessage(string friendID)
  415. {
  416. return "Will you be my friend?";
  417. }
  418. protected virtual bool GetAgentInfo(UUID scopeID, string fid, out UUID agentID, out string first, out string last)
  419. {
  420. first = "Unknown"; last = "UserFMGAI";
  421. if (!UUID.TryParse(fid, out agentID))
  422. return false;
  423. UserAccount account = m_Scenes[0].UserAccountService.GetUserAccount(scopeID, agentID);
  424. if (account != null)
  425. {
  426. first = account.FirstName;
  427. last = account.LastName;
  428. }
  429. return true;
  430. }
  431. List<UUID> GetOnlineFriends(UUID userID)
  432. {
  433. List<UUID> online = new();
  434. FriendInfo[] friends = GetFriendsFromCache(userID);
  435. if(friends.Length == 0)
  436. return online;
  437. List<string> friendList = new(friends.Length);
  438. foreach (FriendInfo fi in friends)
  439. {
  440. if (((fi.TheirFlags & (int)FriendRights.CanSeeOnline) != 0) && (fi.TheirFlags != -1))
  441. friendList.Add(fi.Friend);
  442. }
  443. if (friendList.Count > 0)
  444. GetOnlineFriends(userID, friendList, online);
  445. //m_log.DebugFormat(
  446. // "[FRIENDS MODULE]: User {0} has {1} friends online", userID, online.Count);
  447. return online;
  448. }
  449. protected virtual void GetOnlineFriends(UUID userID, List<string> friendList, List<UUID> online)
  450. {
  451. //m_log.DebugFormat(
  452. // "[FRIENDS MODULE]: Looking for online presence of {0} users for {1}", friendList.Count, userID);
  453. PresenceInfo[] presence = PresenceService.GetAgents(friendList.ToArray());
  454. if(presence.Length == 0)
  455. return;
  456. if (!m_OnlineFriendsCache.TryGetValue(userID, out HashSet<UUID> friends))
  457. {
  458. friends = new HashSet<UUID>();
  459. m_OnlineFriendsCache[userID] = friends;
  460. }
  461. foreach (PresenceInfo pi in presence)
  462. {
  463. if (UUID.TryParse(pi.UserID, out UUID presenceID))
  464. {
  465. online.Add(presenceID);
  466. friends.Add(presenceID);
  467. }
  468. }
  469. }
  470. /// <summary>
  471. /// Find the client for a ID
  472. /// </summary>
  473. public IClientAPI LocateClientObject(UUID agentID)
  474. {
  475. lock (m_Scenes)
  476. {
  477. foreach (Scene scene in m_Scenes)
  478. {
  479. ScenePresence presence = scene.GetScenePresence(agentID);
  480. if (presence is not null && !presence.IsDeleted && !presence.IsChildAgent)
  481. return presence.ControllingClient;
  482. }
  483. }
  484. return null;
  485. }
  486. /// <summary>
  487. /// Caller beware! Call this only for root agents.
  488. /// </summary>
  489. /// <param name="agentID"></param>
  490. /// <param name="online"></param>
  491. private void StatusChange(UUID agentID, bool online)
  492. {
  493. FriendInfo[] friends = GetFriendsFromCache(agentID);
  494. if (friends.Length == 0)
  495. return;
  496. List<FriendInfo> friendList = new(friends.Length);
  497. foreach (FriendInfo fi in friends)
  498. {
  499. if (fi.TheirFlags != -1 && (fi.MyFlags & (int)FriendRights.CanSeeOnline) != 0)
  500. friendList.Add(fi);
  501. }
  502. if(friendList.Count > 0)
  503. {
  504. Util.FireAndForget(
  505. delegate
  506. {
  507. //m_log.DebugFormat(
  508. // "[FRIENDS MODULE]: Notifying {0} friends of {1} of online status {2}",
  509. // friendList.Count, agentID, online);
  510. // Notify about this user status
  511. StatusNotify(friendList, agentID, online);
  512. }, null, "FriendsModule.StatusChange"
  513. );
  514. }
  515. }
  516. protected virtual void StatusNotify(List<FriendInfo> friendList, UUID userID, bool online)
  517. {
  518. //m_log.DebugFormat("[FRIENDS]: Entering StatusNotify for {0}", userID);
  519. List<string> remoteFriendStringIds = new(friendList.Count);
  520. foreach (FriendInfo friend in friendList)
  521. {
  522. if (UUID.TryParse(friend.Friend, out UUID friendUuid))
  523. {
  524. if (LocalStatusNotification(userID, friendUuid, online))
  525. continue;
  526. remoteFriendStringIds.Add(friend.Friend);
  527. }
  528. else
  529. {
  530. m_log.WarnFormat("[FRIENDS]: Error parsing friend ID {0}", friend.Friend);
  531. }
  532. }
  533. if (remoteFriendStringIds.Count == 0)
  534. return;
  535. // We do this regrouping so that we can efficiently send a single request rather than one for each
  536. // friend in what may be a very large friends list.
  537. PresenceInfo[] friendSessions = PresenceService.GetAgents(remoteFriendStringIds.ToArray());
  538. if(friendSessions is null)
  539. return;
  540. foreach (PresenceInfo friendSession in friendSessions)
  541. {
  542. // let's guard against sessions-gone-bad
  543. if (friendSession is not null && friendSession.RegionID.IsNotZero())
  544. {
  545. //m_log.DebugFormat("[FRIENDS]: Get region {0}", friendSession.RegionID);
  546. GridRegion region = GridService.GetRegionByUUID(m_Scenes[0].RegionInfo.ScopeID, friendSession.RegionID);
  547. if (region is not null)
  548. {
  549. m_FriendsSimConnector.StatusNotify(region, userID, friendSession.UserID, online);
  550. }
  551. }
  552. //else
  553. // m_log.DebugFormat("[FRIENDS]: friend session is null or the region is UUID.Zero");
  554. }
  555. }
  556. protected virtual void OnInstantMessage(IClientAPI client, GridInstantMessage im)
  557. {
  558. if ((InstantMessageDialog)im.dialog == InstantMessageDialog.FriendshipOffered)
  559. {
  560. // we got a friendship offer
  561. UUID principalID = new(im.fromAgentID);
  562. UUID friendID = new(im.toAgentID);
  563. m_log.DebugFormat("[FRIENDS]: {0} ({1}) offered friendship to {2} ({3})", principalID, client.FirstName + client.LastName, friendID, im.fromAgentName);
  564. // Check that the friendship doesn't exist yet
  565. FriendInfo[] finfos = GetFriendsFromCache(principalID);
  566. if (finfos is not null)
  567. {
  568. FriendInfo f = GetFriend(finfos, friendID);
  569. if (f is not null)
  570. {
  571. client.SendAgentAlertMessage("This person is already your friend. Please delete it first if you want to reestablish the friendship.", false);
  572. return;
  573. }
  574. }
  575. // This user wants to be friends with the other user.
  576. // Let's add the relation backwards, in case the other is not online
  577. StoreBackwards(friendID, principalID);
  578. // Now let's ask the other user to be friends with this user
  579. ForwardFriendshipOffer(principalID, friendID, im);
  580. }
  581. }
  582. protected virtual bool ForwardFriendshipOffer(UUID agentID, UUID friendID, GridInstantMessage im)
  583. {
  584. // !!!!!!!! This is a hack so that we don't have to keep state (transactionID/imSessionID)
  585. // We stick this agent's ID as imSession, so that it's directly available on the receiving end
  586. im.imSessionID = im.fromAgentID;
  587. im.fromAgentName = GetFriendshipRequesterName(agentID);
  588. // Try the local sim
  589. if (LocalFriendshipOffered(friendID, im))
  590. return true;
  591. // The prospective friend is not here [as root]. Let's forward.
  592. PresenceInfo[] friendSessions = PresenceService.GetAgents(new string[] { friendID.ToString() });
  593. if (friendSessions is not null && friendSessions.Length > 0)
  594. {
  595. PresenceInfo friendSession = friendSessions[0];
  596. if (friendSession is not null)
  597. {
  598. GridRegion region = GridService.GetRegionByUUID(m_Scenes[0].RegionInfo.ScopeID, friendSession.RegionID);
  599. if(region is not null)
  600. {
  601. m_FriendsSimConnector.FriendshipOffered(region, agentID, friendID, im.message);
  602. return true;
  603. }
  604. }
  605. }
  606. // If the prospective friend is not online, he'll get the message upon login.
  607. return false;
  608. }
  609. protected virtual string GetFriendshipRequesterName(UUID agentID)
  610. {
  611. UserAccount account = UserAccountService.GetUserAccount(UUID.Zero, agentID);
  612. return (account is null) ? "Unknown" : account.FirstName + " " + account.LastName;
  613. }
  614. protected virtual void OnApproveFriendRequest(IClientAPI client, UUID friendID, List<UUID> callingCardFolders)
  615. {
  616. m_log.DebugFormat("[FRIENDS]: {0} accepted friendship from {1}", client.AgentId, friendID);
  617. AddFriendship(client, friendID);
  618. }
  619. public void AddFriendship(IClientAPI client, UUID friendID)
  620. {
  621. StoreFriendships(client.AgentId, friendID);
  622. ICallingCardModule ccm = client.Scene.RequestModuleInterface<ICallingCardModule>();
  623. ccm?.CreateCallingCard(client.AgentId, friendID, UUID.Zero);
  624. // Update the local cache.
  625. RecacheFriends(client);
  626. //
  627. // Notify the friend
  628. //
  629. // Try Local
  630. if (LocalFriendshipApproved(client.AgentId, client.Name, friendID))
  631. {
  632. client.SendAgentOnline(new UUID[] { friendID });
  633. return;
  634. }
  635. // The friend is not here
  636. PresenceInfo[] friendSessions = PresenceService.GetAgents(new string[] { friendID.ToString() });
  637. if (friendSessions is not null && friendSessions.Length > 0)
  638. {
  639. PresenceInfo friendSession = friendSessions[0];
  640. if (friendSession is not null)
  641. {
  642. GridRegion region = GridService.GetRegionByUUID(m_Scenes[0].RegionInfo.ScopeID, friendSession.RegionID);
  643. m_FriendsSimConnector.FriendshipApproved(region, client.AgentId, client.Name, friendID);
  644. client.SendAgentOnline(new UUID[] { friendID });
  645. }
  646. }
  647. }
  648. private void OnDenyFriendRequest(IClientAPI client, UUID friendID, List<UUID> callingCardFolders)
  649. {
  650. m_log.DebugFormat("[FRIENDS]: {0} denied friendship to {1}", client.AgentId, friendID);
  651. DeleteFriendship(client.AgentId, friendID);
  652. //
  653. // Notify the friend
  654. //
  655. // Try local
  656. if (LocalFriendshipDenied(client.AgentId, client.Name, friendID))
  657. return;
  658. PresenceInfo[] friendSessions = PresenceService.GetAgents(new string[] { friendID.ToString() });
  659. if (friendSessions is not null && friendSessions.Length > 0)
  660. {
  661. PresenceInfo friendSession = friendSessions[0];
  662. if (friendSession is not null)
  663. {
  664. GridRegion region = GridService.GetRegionByUUID(m_Scenes[0].RegionInfo.ScopeID, friendSession.RegionID);
  665. if (region is not null)
  666. m_FriendsSimConnector.FriendshipDenied(region, client.AgentId, client.Name, friendID);
  667. else
  668. m_log.WarnFormat("[FRIENDS]: Could not find region {0} in locating {1}", friendSession.RegionID, friendID);
  669. }
  670. }
  671. }
  672. public void RemoveFriendship(IClientAPI client, UUID exfriendID)
  673. {
  674. if (!DeleteFriendship(client.AgentId, exfriendID))
  675. client.SendAlertMessage("Unable to terminate friendship on this sim.");
  676. // Update local cache
  677. RecacheFriends(client);
  678. client.SendTerminateFriend(exfriendID);
  679. //
  680. // Notify the friend
  681. //
  682. // Try local
  683. if (LocalFriendshipTerminated(client.AgentId, exfriendID))
  684. return;
  685. PresenceInfo[] friendSessions = PresenceService.GetAgents(new string[] { exfriendID.ToString() });
  686. if (friendSessions is not null && friendSessions.Length > 0)
  687. {
  688. PresenceInfo friendSession = friendSessions[0];
  689. if (friendSession is not null)
  690. {
  691. GridRegion region = GridService.GetRegionByUUID(m_Scenes[0].RegionInfo.ScopeID, friendSession.RegionID);
  692. m_FriendsSimConnector.FriendshipTerminated(region, client.AgentId, exfriendID);
  693. }
  694. }
  695. }
  696. public void FindFriend(IClientAPI remoteClient,UUID HunterID ,UUID PreyID)
  697. {
  698. UUID requester = remoteClient.AgentId;
  699. if(requester != HunterID) // only allow client agent to be the hunter (?)
  700. return;
  701. FriendInfo[] friends = GetFriendsFromCache(requester);
  702. if (friends.Length == 0)
  703. return;
  704. FriendInfo friend = GetFriend(friends, PreyID);
  705. if (friend is null)
  706. return;
  707. if(friend.TheirFlags == -1 || (friend.TheirFlags & (int)FriendRights.CanSeeOnMap) == 0)
  708. return;
  709. Scene hunterScene = (Scene)remoteClient.Scene;
  710. if(hunterScene is null)
  711. return;
  712. // check local
  713. double px;
  714. double py;
  715. if(hunterScene.TryGetScenePresence(PreyID, out ScenePresence sp))
  716. {
  717. if(sp == null)
  718. return;
  719. px = hunterScene.RegionInfo.WorldLocX + sp.AbsolutePosition.X;
  720. py = hunterScene.RegionInfo.WorldLocY + sp.AbsolutePosition.Y;
  721. remoteClient.SendFindAgent(HunterID, PreyID, px, py);
  722. return;
  723. }
  724. PresenceInfo[] friendSessions = PresenceService.GetAgents(new string[] { PreyID.ToString() });
  725. if (friendSessions is null || friendSessions.Length == 0)
  726. return;
  727. PresenceInfo friendSession = friendSessions[0];
  728. if (friendSession is null)
  729. return;
  730. GridRegion region = GridService.GetRegionByUUID(hunterScene.RegionInfo.ScopeID, friendSession.RegionID);
  731. if(region is null)
  732. return;
  733. // we don't have presence location so point to a standard region center for now
  734. px = region.RegionLocX + 128.0;
  735. py = region.RegionLocY + 128.0;
  736. remoteClient.SendFindAgent(HunterID, PreyID, px, py);
  737. }
  738. public void GrantRights(IClientAPI remoteClient, UUID friendID, int rights)
  739. {
  740. UUID requester = remoteClient.AgentId;
  741. m_log.DebugFormat(
  742. "[FRIENDS MODULE]: User {0} changing rights to {1} for friend {2}",
  743. requester, rights, friendID);
  744. FriendInfo[] friends = GetFriendsFromCache(requester);
  745. if (friends.Length == 0)
  746. return;
  747. // Let's find the friend in this user's friend list
  748. FriendInfo friend = GetFriend(friends, friendID);
  749. if (friend is not null) // Found it
  750. {
  751. // Store it on service
  752. if (!StoreRights(requester, friendID, rights))
  753. {
  754. remoteClient.SendAlertMessage("Unable to grant rights.");
  755. return;
  756. }
  757. // Store it in the local cache
  758. int myFlags = friend.MyFlags;
  759. friend.MyFlags = rights;
  760. // Always send this back to the original client
  761. remoteClient.SendChangeUserRights(requester, friendID, rights);
  762. //
  763. // Notify the friend
  764. //
  765. // Try local
  766. if (LocalGrantRights(requester, friendID, myFlags, rights))
  767. return;
  768. PresenceInfo[] friendSessions = PresenceService.GetAgents(new string[] { friendID.ToString() });
  769. if (friendSessions is not null && friendSessions.Length > 0)
  770. {
  771. PresenceInfo friendSession = friendSessions[0];
  772. if (friendSession is not null)
  773. {
  774. GridRegion region = GridService.GetRegionByUUID(m_Scenes[0].RegionInfo.ScopeID, friendSession.RegionID);
  775. // TODO: You might want to send the delta to save the lookup
  776. // on the other end!!
  777. m_FriendsSimConnector.GrantRights(region, requester, friendID, myFlags, rights);
  778. }
  779. }
  780. }
  781. else
  782. {
  783. m_log.DebugFormat("[FRIENDS MODULE]: friend {0} not found for {1}", friendID, requester);
  784. }
  785. }
  786. protected virtual FriendInfo GetFriend(FriendInfo[] friends, UUID friendID)
  787. {
  788. foreach (FriendInfo fi in friends)
  789. {
  790. if (fi.Friend == friendID.ToString())
  791. return fi;
  792. }
  793. return null;
  794. }
  795. #region Local
  796. public virtual bool LocalFriendshipOffered(UUID toID, GridInstantMessage im)
  797. {
  798. IClientAPI friendClient = LocateClientObject(toID);
  799. if (friendClient is not null)
  800. {
  801. // the prospective friend in this sim as root agent
  802. friendClient.SendInstantMessage(im);
  803. // we're done
  804. return true;
  805. }
  806. return false;
  807. }
  808. public bool LocalFriendshipApproved(UUID userID, string userName, UUID friendID)
  809. {
  810. IClientAPI friendClient = LocateClientObject(friendID);
  811. if (friendClient is not null)
  812. {
  813. // the prospective friend in this sim as root agent
  814. GridInstantMessage im = new(Scene, userID, userName, friendID,
  815. (byte)OpenMetaverse.InstantMessageDialog.FriendshipAccepted, userID.ToString(), false, Vector3.Zero);
  816. friendClient.SendInstantMessage(im);
  817. ICallingCardModule ccm = friendClient.Scene.RequestModuleInterface<ICallingCardModule>();
  818. ccm?.CreateCallingCard(friendID, userID, UUID.Zero);
  819. // Update the local cache
  820. RecacheFriends(friendClient);
  821. // we're done
  822. return true;
  823. }
  824. return false;
  825. }
  826. public bool LocalFriendshipDenied(UUID userID, string userName, UUID friendID)
  827. {
  828. IClientAPI friendClient = LocateClientObject(friendID);
  829. if (friendClient is not null)
  830. {
  831. // the prospective friend in this sim as root agent
  832. GridInstantMessage im = new(Scene, userID, userName, friendID,
  833. (byte)OpenMetaverse.InstantMessageDialog.FriendshipDeclined, userID.ToString(), false, Vector3.Zero);
  834. friendClient.SendInstantMessage(im);
  835. // we're done
  836. return true;
  837. }
  838. return false;
  839. }
  840. public bool LocalFriendshipTerminated(UUID userID, UUID exfriendID)
  841. {
  842. IClientAPI friendClient = LocateClientObject(exfriendID);
  843. if (friendClient is not null)
  844. {
  845. // the friend in this sim as root agent
  846. friendClient.SendTerminateFriend(userID);
  847. // update local cache
  848. RecacheFriends(friendClient);
  849. // we're done
  850. return true;
  851. }
  852. return false;
  853. }
  854. public bool LocalGrantRights(UUID userID, UUID friendID, int oldRights, int newRights)
  855. {
  856. IClientAPI friendClient = LocateClientObject(friendID);
  857. if (friendClient is not null)
  858. {
  859. int changedRights = newRights ^ oldRights;
  860. bool onlineBitChanged = (changedRights & (int)FriendRights.CanSeeOnline) != 0;
  861. if (onlineBitChanged)
  862. {
  863. if ((newRights & (int)FriendRights.CanSeeOnline) == 1)
  864. friendClient.SendAgentOnline(new UUID[] { userID });
  865. else
  866. friendClient.SendAgentOffline(new UUID[] { userID });
  867. }
  868. if(changedRights != 0)
  869. friendClient.SendChangeUserRights(userID, friendID, newRights);
  870. // Update local cache
  871. UpdateLocalCache(userID, friendID, newRights);
  872. return true;
  873. }
  874. return false;
  875. }
  876. public bool LocalStatusNotification(UUID userID, UUID friendID, bool online)
  877. {
  878. //m_log.DebugFormat("[FRIENDS]: Local Status Notify {0} that user {1} is {2}", friendID, userID, online);
  879. IClientAPI friendClient = LocateClientObject(friendID);
  880. if (friendClient is not null)
  881. {
  882. CacheFriendOnline(friendID, userID, online);
  883. // the friend in this sim as root agent
  884. if (online)
  885. friendClient.SendAgentOnline(new UUID[] { userID });
  886. else
  887. friendClient.SendAgentOffline(new UUID[] { userID });
  888. // we're done
  889. return true;
  890. }
  891. return false;
  892. }
  893. #endregion
  894. #region Get / Set friends in several flavours
  895. public FriendInfo[] GetFriendsFromCache(UUID userID)
  896. {
  897. lock (m_Friends)
  898. {
  899. if (m_Friends.TryGetValue(userID, out UserFriendData friendsData))
  900. return friendsData.Friends;
  901. }
  902. return EMPTY_FRIENDS;
  903. }
  904. /// <summary>
  905. /// Update local cache only
  906. /// </summary>
  907. /// <param name="userID"></param>
  908. /// <param name="friendID"></param>
  909. /// <param name="rights"></param>
  910. protected void UpdateLocalCache(UUID userID, UUID friendID, int rights)
  911. {
  912. // Update local cache
  913. lock (m_Friends)
  914. {
  915. FriendInfo[] friends = GetFriendsFromCache(friendID);
  916. if(friends.Length > 0)
  917. {
  918. FriendInfo finfo = GetFriend(friends, userID);
  919. if(finfo is not null)
  920. finfo.TheirFlags = rights;
  921. }
  922. }
  923. }
  924. public virtual FriendInfo[] GetFriendsFromService(IClientAPI client)
  925. {
  926. return FriendsService.GetFriends(client.AgentId);
  927. }
  928. protected void RecacheFriends(IClientAPI client)
  929. {
  930. // FIXME: Ideally, we want to avoid doing this here since it sits the EventManager.OnMakeRootAgent event
  931. // is on the critical path for transferring an avatar from one region to another.
  932. lock (m_Friends)
  933. {
  934. if (m_Friends.TryGetValue(client.AgentId, out UserFriendData friendsData))
  935. friendsData.Friends = GetFriendsFromService(client);
  936. }
  937. }
  938. public bool AreFriendsCached(UUID userID)
  939. {
  940. lock (m_Friends)
  941. return m_Friends.ContainsKey(userID);
  942. }
  943. protected virtual bool StoreRights(UUID agentID, UUID friendID, int rights)
  944. {
  945. FriendsService.StoreFriend(agentID.ToString(), friendID.ToString(), rights);
  946. return true;
  947. }
  948. protected virtual void StoreBackwards(UUID friendID, UUID agentID)
  949. {
  950. FriendsService.StoreFriend(friendID.ToString(), agentID.ToString(), 0);
  951. }
  952. protected virtual void StoreFriendships(UUID agentID, UUID friendID)
  953. {
  954. FriendsService.StoreFriend(agentID.ToString(), friendID.ToString(), (int)FriendRights.CanSeeOnline);
  955. FriendsService.StoreFriend(friendID.ToString(), agentID.ToString(), (int)FriendRights.CanSeeOnline);
  956. }
  957. protected virtual bool DeleteFriendship(UUID agentID, UUID exfriendID)
  958. {
  959. FriendsService.Delete(agentID, exfriendID.ToString());
  960. FriendsService.Delete(exfriendID, agentID.ToString());
  961. return true;
  962. }
  963. #endregion
  964. }
  965. }