UserProfileModule.cs 72 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894
  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.Collections.Concurrent;
  30. using System.Globalization;
  31. using System.Linq;
  32. using System.Reflection;
  33. using System.Threading;
  34. using OpenMetaverse;
  35. using OpenMetaverse.StructuredData;
  36. using log4net;
  37. using Nini.Config;
  38. using OpenSim.Framework;
  39. using OpenSim.Region.Framework.Interfaces;
  40. using OpenSim.Region.Framework.Scenes;
  41. using OpenSim.Services.Interfaces;
  42. using Mono.Addins;
  43. using OpenSim.Services.Connectors.Hypergrid;
  44. using OpenSim.Framework.Servers.HttpServer;
  45. using OpenSim.Services.UserProfilesService;
  46. using GridRegion = OpenSim.Services.Interfaces.GridRegion;
  47. using OpenSim.Region.CoreModules.Avatar.Friends;
  48. namespace OpenSim.Region.CoreModules.Avatar.UserProfiles
  49. {
  50. [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "UserProfilesModule")]
  51. public class UserProfileModule : IProfileModule, INonSharedRegionModule
  52. {
  53. const double PROFILECACHEEXPIRE = 300;
  54. /// <summary>
  55. /// Logging
  56. /// </summary>
  57. static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
  58. // The pair of Dictionaries are used to handle the switching of classified ads
  59. // by maintaining a cache of classified id to creator id mappings and an interest
  60. // count. The entries are removed when the interest count reaches 0.
  61. readonly Dictionary<UUID, UUID> m_classifiedCache = new();
  62. readonly Dictionary<UUID, int> m_classifiedInterest = new();
  63. readonly ExpiringCacheOS<UUID, UserProfileCacheEntry> m_profilesCache = new(60000);
  64. IGroupsModule m_groupsModule = null;
  65. private readonly JsonRpcRequestManager rpc = new();
  66. private bool m_allowUserProfileWebURLs = true;
  67. struct AsyncPropsRequest
  68. {
  69. public IClientAPI client;
  70. public ScenePresence presence;
  71. public UUID agent;
  72. public int reqtype;
  73. }
  74. private readonly ConcurrentStack<AsyncPropsRequest> m_asyncRequests = new();
  75. private readonly object m_asyncRequestsLock = new();
  76. private bool m_asyncRequestsRunning = false;
  77. private void ProcessRequests()
  78. {
  79. lock(m_asyncRequestsLock)
  80. {
  81. while (m_asyncRequests.TryPop(out AsyncPropsRequest req))
  82. {
  83. try
  84. {
  85. IClientAPI client = req.client;
  86. if(!client.IsActive)
  87. continue;
  88. if(req.reqtype == 0)
  89. {
  90. ScenePresence p = req.presence;
  91. bool ok = true;
  92. bool foreign = GetUserProfileServerURI(req.agent, out string serverURI);
  93. if(serverURI.Length == 0)
  94. ok = false;
  95. Byte[] membershipType = new Byte[1];
  96. string born = string.Empty;
  97. uint flags = 0x00;
  98. if (ok && GetUserAccountData(req.agent, out UserAccount acc))
  99. {
  100. flags = (uint)(acc.UserFlags & 0xff);
  101. if (acc.UserTitle.Length == 0)
  102. membershipType[0] = (byte)((acc.UserFlags & 0x0f00) >> 8);
  103. else
  104. membershipType = Utils.StringToBytes(acc.UserTitle);
  105. int val_born = acc.Created;
  106. if (val_born != 0)
  107. born = Util.ToDateTime(val_born).ToString("M/d/yyyy", CultureInfo.InvariantCulture);
  108. }
  109. else
  110. ok = false;
  111. UserProfileProperties props = new() { UserId = req.agent };
  112. if (ok)
  113. ok = GetProfileData(ref props, foreign, serverURI, out string result);
  114. if (!ok)
  115. props.AboutText = "Profile not available at this time. User may still be unknown to this grid";
  116. if (!m_allowUserProfileWebURLs)
  117. props.WebUrl = "";
  118. GroupMembershipData[] agentGroups = null;
  119. if(ok && m_groupsModule is not null)
  120. agentGroups = m_groupsModule.GetMembershipData(req.agent);
  121. HashSet<IClientAPI> clients;
  122. lock (m_profilesCache)
  123. {
  124. if (!m_profilesCache.TryGetValue(props.UserId, out UserProfileCacheEntry uce) || uce is null)
  125. uce = new UserProfileCacheEntry();
  126. uce.props = props;
  127. uce.born = born;
  128. uce.membershipType = membershipType;
  129. uce.flags = flags;
  130. clients = uce.ClientsWaitingProps;
  131. uce.ClientsWaitingProps = null;
  132. uce.avatarGroups = agentGroups;
  133. m_profilesCache.AddOrUpdate(props.UserId, uce, PROFILECACHEEXPIRE);
  134. }
  135. if (IsFriendOnline(req.client, req.agent))
  136. flags |= (uint)ProfileFlags.Online;
  137. else
  138. flags &= (uint)~ProfileFlags.Online;
  139. if (clients is null)
  140. {
  141. client.SendAvatarProperties(props.UserId, props.AboutText, born, membershipType, props.FirstLifeText, flags,
  142. props.FirstLifeImageId, props.ImageId, props.WebUrl, props.PartnerId);
  143. client.SendAvatarInterestsReply(props.UserId, (uint)props.WantToMask, props.WantToText,
  144. (uint)props.SkillsMask, props.SkillsText, props.Language);
  145. if (agentGroups is not null)
  146. client.SendAvatarGroupsReply(req.agent, agentGroups);
  147. }
  148. else
  149. {
  150. if (!clients.Contains(client) && client.IsActive)
  151. {
  152. client.SendAvatarProperties(props.UserId, props.AboutText, born, membershipType, props.FirstLifeText, flags,
  153. props.FirstLifeImageId, props.ImageId, props.WebUrl, props.PartnerId);
  154. client.SendAvatarInterestsReply(props.UserId, (uint)props.WantToMask, props.WantToText,
  155. (uint)props.SkillsMask, props.SkillsText, props.Language);
  156. if (agentGroups is not null)
  157. client.SendAvatarGroupsReply(req.agent, agentGroups);
  158. }
  159. foreach (IClientAPI cli in clients)
  160. {
  161. if (!cli.IsActive)
  162. continue;
  163. cli.SendAvatarProperties(props.UserId, props.AboutText, born, membershipType, props.FirstLifeText, flags,
  164. props.FirstLifeImageId, props.ImageId, props.WebUrl, props.PartnerId);
  165. cli.SendAvatarInterestsReply(props.UserId, (uint)props.WantToMask, props.WantToText,
  166. (uint)props.SkillsMask, props.SkillsText, props.Language);
  167. if (agentGroups is not null)
  168. cli.SendAvatarGroupsReply(req.agent, agentGroups);
  169. }
  170. }
  171. }
  172. }
  173. catch (Exception e)
  174. {
  175. m_log.ErrorFormat("[UserProfileModule]: Process fail {0} : {1}", e.Message, e.StackTrace);
  176. }
  177. }
  178. m_asyncRequestsRunning = false;
  179. }
  180. }
  181. public Scene Scene
  182. {
  183. get; private set;
  184. }
  185. /// <summary>
  186. /// Gets or sets the ConfigSource.
  187. /// </summary>
  188. /// <value>
  189. /// The configuration
  190. /// </value>
  191. public IConfigSource Config
  192. {
  193. get;
  194. set;
  195. }
  196. /// <summary>
  197. /// Gets or sets the URI to the profile server.
  198. /// </summary>
  199. /// <value>
  200. /// The profile server URI.
  201. /// </value>
  202. public string ProfileServerUri
  203. {
  204. get;
  205. set;
  206. }
  207. IProfileModule ProfileModule
  208. {
  209. get; set;
  210. }
  211. IUserManagement UserManagementModule
  212. {
  213. get; set;
  214. }
  215. /// <summary>
  216. /// Gets or sets a value indicating whether this
  217. /// <see cref="OpenSim.Region.Coremodules.UserProfiles.UserProfileModule"/> is enabled.
  218. /// </summary>
  219. /// <value>
  220. /// <c>true</c> if enabled; otherwise, <c>false</c>.
  221. /// </value>
  222. public bool Enabled
  223. {
  224. get;
  225. set;
  226. }
  227. private GridInfo m_thisGridInfo;
  228. #region IRegionModuleBase implementation
  229. /// <summary>
  230. /// This is called to initialize the region module. For shared modules, this is called exactly once, after
  231. /// creating the single (shared) instance. For non-shared modules, this is called once on each instance, after
  232. /// the instace for the region has been created.
  233. /// </summary>
  234. /// <param name='source'>
  235. /// Source.
  236. /// </param>
  237. public void Initialise(IConfigSource source)
  238. {
  239. Config = source;
  240. ReplaceableInterface = typeof(IProfileModule);
  241. IConfig profileConfig = Config.Configs["UserProfiles"];
  242. if (profileConfig is null)
  243. {
  244. //m_log.Debug("[PROFILES]: UserProfiles disabled, no configuration");
  245. Enabled = false;
  246. return;
  247. }
  248. // If we find ProfileURL then we configure for FULL support
  249. // else we setup for BASIC support
  250. ProfileServerUri = profileConfig.GetString("ProfileServiceURL", "");
  251. if (string.IsNullOrEmpty(ProfileServerUri))
  252. {
  253. Enabled = false;
  254. return;
  255. }
  256. OSHTTPURI tmp = new(ProfileServerUri, true);
  257. if (!tmp.IsResolvedHost)
  258. {
  259. m_log.ErrorFormat("[UserProfileModule: {0}", tmp.IsValidHost ? "Could not resolve ProfileServiceURL" : "ProfileServiceURL is a invalid host");
  260. throw new Exception("UserProfileModule init error");
  261. }
  262. ProfileServerUri = tmp.URI;
  263. m_allowUserProfileWebURLs = profileConfig.GetBoolean("AllowUserProfileWebURLs", m_allowUserProfileWebURLs);
  264. m_log.Debug("[UserProfileModule]: Full Profiles Enabled");
  265. ReplaceableInterface = null;
  266. Enabled = true;
  267. }
  268. /// <summary>
  269. /// Adds the region.
  270. /// </summary>
  271. /// <param name='scene'>
  272. /// Scene.
  273. /// </param>
  274. public void AddRegion(Scene scene)
  275. {
  276. if(!Enabled)
  277. return;
  278. Scene = scene;
  279. m_thisGridInfo ??= scene.SceneGridInfo;
  280. Scene.RegisterModuleInterface<IProfileModule>(this);
  281. Scene.EventManager.OnNewClient += OnNewClient;
  282. Scene.EventManager.OnClientClosed += OnClientClosed;
  283. UserManagementModule = Scene.RequestModuleInterface<IUserManagement>();
  284. }
  285. /// <summary>
  286. /// Removes the region.
  287. /// </summary>
  288. /// <param name='scene'>
  289. /// Scene.
  290. /// </param>
  291. public void RemoveRegion(Scene scene)
  292. {
  293. if(!Enabled)
  294. return;
  295. m_profilesCache.Clear();
  296. m_classifiedCache.Clear();
  297. m_classifiedInterest.Clear();
  298. }
  299. /// <summary>
  300. /// This will be called once for every scene loaded. In a shared module this will be multiple times in one
  301. /// instance, while a nonshared module instance will only be called once. This method is called after AddRegion
  302. /// has been called in all modules for that scene, providing an opportunity to request another module's
  303. /// interface, or hook an event from another module.
  304. /// </summary>
  305. /// <param name='scene'>
  306. /// Scene.
  307. /// </param>
  308. public void RegionLoaded(Scene scene)
  309. {
  310. if(!Enabled)
  311. return;
  312. m_groupsModule = Scene.RequestModuleInterface<IGroupsModule>();
  313. }
  314. /// <summary>
  315. /// If this returns non-null, it is the type of an interface that this module intends to register. This will
  316. /// cause the loader to defer loading of this module until all other modules have been loaded. If no other
  317. /// module has registered the interface by then, this module will be activated, else it will remain inactive,
  318. /// letting the other module take over. This should return non-null ONLY in modules that are intended to be
  319. /// easily replaceable, e.g. stub implementations that the developer expects to be replaced by third party
  320. /// provided modules.
  321. /// </summary>
  322. /// <value>
  323. /// The replaceable interface.
  324. /// </value>
  325. public Type ReplaceableInterface
  326. {
  327. get; private set;
  328. }
  329. /// <summary>
  330. /// Called as the instance is closed.
  331. /// </summary>
  332. public void Close()
  333. {
  334. m_thisGridInfo = null;
  335. }
  336. /// <value>
  337. /// The name of the module
  338. /// </value>
  339. /// <summary>
  340. /// Gets the module name.
  341. /// </summary>
  342. public string Name
  343. {
  344. get { return "UserProfileModule"; }
  345. }
  346. #endregion IRegionModuleBase implementation
  347. #region Region Event Handlers
  348. /// <summary>
  349. /// Raises the new client event.
  350. /// </summary>
  351. /// <param name='client'>
  352. /// Client.
  353. /// </param>
  354. void OnNewClient(IClientAPI client)
  355. {
  356. //Profile
  357. client.OnRequestAvatarProperties += RequestAvatarProperties;
  358. client.OnUpdateAvatarProperties += AvatarPropertiesUpdate;
  359. client.OnAvatarInterestUpdate += AvatarInterestsUpdate;
  360. // Classifieds
  361. client.AddGenericPacketHandler("avatarclassifiedsrequest", ClassifiedsRequest);
  362. client.OnClassifiedInfoUpdate += ClassifiedInfoUpdate;
  363. client.OnClassifiedInfoRequest += ClassifiedInfoRequest;
  364. client.OnClassifiedDelete += ClassifiedDelete;
  365. // Picks
  366. client.AddGenericPacketHandler("avatarpicksrequest", PicksRequest);
  367. client.AddGenericPacketHandler("pickinforequest", PickInfoRequest);
  368. client.OnPickInfoUpdate += PickInfoUpdate;
  369. client.OnPickDelete += PickDelete;
  370. // Notes
  371. client.AddGenericPacketHandler("avatarnotesrequest", NotesRequest);
  372. client.OnAvatarNotesUpdate += NotesUpdate;
  373. // Preferences
  374. client.OnUserInfoRequest += UserPreferencesRequest;
  375. client.OnUpdateUserInfo += UpdateUserPreferences;
  376. }
  377. void OnClientClosed(UUID AgentId, Scene scene)
  378. {
  379. ScenePresence sp = scene.GetScenePresence(AgentId);
  380. IClientAPI client = sp.ControllingClient;
  381. if (client is null)
  382. return;
  383. //Profile
  384. client.OnRequestAvatarProperties -= RequestAvatarProperties;
  385. client.OnUpdateAvatarProperties -= AvatarPropertiesUpdate;
  386. client.OnAvatarInterestUpdate -= AvatarInterestsUpdate;
  387. // Classifieds
  388. client.OnClassifiedInfoUpdate -= ClassifiedInfoUpdate;
  389. client.OnClassifiedInfoRequest -= ClassifiedInfoRequest;
  390. client.OnClassifiedDelete -= ClassifiedDelete;
  391. // Picks
  392. client.OnPickInfoUpdate -= PickInfoUpdate;
  393. client.OnPickDelete -= PickDelete;
  394. // Notes
  395. client.OnAvatarNotesUpdate -= NotesUpdate;
  396. // Preferences
  397. client.OnUserInfoRequest -= UserPreferencesRequest;
  398. client.OnUpdateUserInfo -= UpdateUserPreferences;
  399. }
  400. #endregion Region Event Handlers
  401. #region Classified
  402. ///
  403. /// <summary>
  404. /// Handles the avatar classifieds request.
  405. /// </summary>
  406. /// <param name='sender'>
  407. /// Sender.
  408. /// </param>
  409. /// <param name='method'>
  410. /// Method.
  411. /// </param>
  412. /// <param name='args'>
  413. /// Arguments.
  414. /// </param>
  415. public void ClassifiedsRequest(Object sender, string method, List<String> args)
  416. {
  417. if (sender is not IClientAPI remoteClient)
  418. return;
  419. Dictionary<UUID, string> classifieds = new();
  420. if (!UUID.TryParse(args[0], out UUID targetID) || targetID.IsZero())
  421. return;
  422. if (targetID.Equals(Constants.m_MrOpenSimID))
  423. {
  424. remoteClient.SendAvatarClassifiedReply(targetID, classifieds);
  425. return;
  426. }
  427. ScenePresence p = FindPresence(targetID);
  428. if (p is not null && p.IsNPC)
  429. {
  430. remoteClient.SendAvatarClassifiedReply(targetID, classifieds);
  431. return;
  432. }
  433. lock(m_profilesCache)
  434. {
  435. if(m_profilesCache.TryGetValue(targetID, out UserProfileCacheEntry uce) && uce is not null)
  436. {
  437. if(uce.classifiedsLists is not null)
  438. {
  439. foreach(KeyValuePair<UUID,string> kvp in uce.classifiedsLists)
  440. {
  441. UUID kvpkey = kvp.Key;
  442. classifieds[kvpkey] = kvp.Value;
  443. lock (m_classifiedCache)
  444. {
  445. if (!m_classifiedCache.ContainsKey(kvpkey))
  446. {
  447. m_classifiedCache.Add(kvpkey,targetID);
  448. m_classifiedInterest.Add(kvpkey, 0);
  449. }
  450. m_classifiedInterest[kvpkey]++;
  451. }
  452. }
  453. remoteClient.SendAvatarClassifiedReply(targetID, uce.classifiedsLists);
  454. return;
  455. }
  456. }
  457. }
  458. GetUserProfileServerURI(targetID, out string serverURI);
  459. if(string.IsNullOrWhiteSpace(serverURI))
  460. {
  461. remoteClient.SendAvatarClassifiedReply(targetID, classifieds);
  462. return;
  463. }
  464. OSDMap parameters= new()
  465. {
  466. {"creatorId", OSD.FromUUID(targetID)}
  467. };
  468. OSD osdtmp = parameters;
  469. if(!rpc.JsonRpcRequest(ref osdtmp, "avatarclassifiedsrequest", serverURI, UUID.Random().ToString()))
  470. {
  471. remoteClient.SendAvatarClassifiedReply(targetID, classifieds);
  472. return;
  473. }
  474. parameters = (OSDMap)osdtmp;
  475. if(!parameters.TryGetValue("result", out osdtmp) || osdtmp is not OSDArray)
  476. {
  477. remoteClient.SendAvatarClassifiedReply(targetID, classifieds);
  478. return;
  479. }
  480. OSDArray list = (OSDArray)osdtmp;
  481. foreach(OSD map in list)
  482. {
  483. OSDMap m = (OSDMap)map;
  484. UUID cid = m["classifieduuid"].AsUUID();
  485. string name = m["name"].AsString();
  486. classifieds[cid] = name;
  487. lock (m_classifiedCache)
  488. {
  489. if (!m_classifiedCache.ContainsKey(cid))
  490. {
  491. m_classifiedCache.Add(cid,targetID);
  492. m_classifiedInterest.Add(cid, 0);
  493. }
  494. m_classifiedInterest[cid]++;
  495. }
  496. }
  497. lock(m_profilesCache)
  498. {
  499. if(!m_profilesCache.TryGetValue(targetID, out UserProfileCacheEntry uce) || uce is null)
  500. uce = new UserProfileCacheEntry();
  501. uce.classifiedsLists = classifieds;
  502. m_profilesCache.AddOrUpdate(targetID, uce, PROFILECACHEEXPIRE);
  503. }
  504. remoteClient.SendAvatarClassifiedReply(targetID, classifieds);
  505. }
  506. public void ClassifiedInfoRequest(UUID queryClassifiedID, IClientAPI remoteClient)
  507. {
  508. UUID target = remoteClient.AgentId;
  509. UserClassifiedAdd ad = new() { ClassifiedId = queryClassifiedID };
  510. lock (m_classifiedCache)
  511. {
  512. if (m_classifiedCache.ContainsKey(queryClassifiedID))
  513. {
  514. target = m_classifiedCache[queryClassifiedID];
  515. m_classifiedInterest[queryClassifiedID]--;
  516. if (m_classifiedInterest[queryClassifiedID] == 0)
  517. {
  518. m_classifiedInterest.Remove(queryClassifiedID);
  519. m_classifiedCache.Remove(queryClassifiedID);
  520. }
  521. }
  522. }
  523. UserProfileCacheEntry uce = null;
  524. lock(m_profilesCache)
  525. {
  526. if(m_profilesCache.TryGetValue(target, out uce) && uce is not null)
  527. {
  528. if(uce.classifieds is not null && uce.classifieds.ContainsKey(queryClassifiedID))
  529. {
  530. ad = uce.classifieds[queryClassifiedID];
  531. if(Vector3.TryParse(ad.GlobalPos, out Vector3 gPos))
  532. {
  533. remoteClient.SendClassifiedInfoReply(ad.ClassifiedId, ad.CreatorId, (uint)ad.CreationDate,
  534. (uint)ad.ExpirationDate, (uint)ad.Category, ad.Name, ad.Description,
  535. ad.ParcelId, (uint)ad.ParentEstate, ad.SnapshotId, ad.SimName,
  536. gPos, ad.ParcelName, ad.Flags, ad.Price);
  537. }
  538. return;
  539. }
  540. }
  541. }
  542. bool foreign = GetUserProfileServerURI(target, out string serverURI);
  543. if(string.IsNullOrWhiteSpace(serverURI))
  544. {
  545. return;
  546. }
  547. object Adobject = ad;
  548. if(!rpc.JsonRpcRequest(ref Adobject, "classifieds_info_query", serverURI, UUID.Random().ToString()))
  549. {
  550. remoteClient.SendAgentAlertMessage(
  551. "Error getting classified info", false);
  552. return;
  553. }
  554. ad = (UserClassifiedAdd) Adobject;
  555. if(ad.CreatorId.IsZero())
  556. return;
  557. if(foreign)
  558. cacheForeignImage(target, ad.SnapshotId);
  559. lock(m_profilesCache)
  560. {
  561. if(!m_profilesCache.TryGetValue(target, out uce) || uce is null)
  562. uce = new UserProfileCacheEntry();
  563. uce.classifieds ??= new Dictionary<UUID, UserClassifiedAdd>();
  564. uce.classifieds[ad.ClassifiedId] = ad;
  565. m_profilesCache.AddOrUpdate(target, uce, PROFILECACHEEXPIRE);
  566. }
  567. if(Vector3.TryParse(ad.GlobalPos, out Vector3 globalPos))
  568. remoteClient.SendClassifiedInfoReply(ad.ClassifiedId, ad.CreatorId, (uint)ad.CreationDate, (uint)ad.ExpirationDate,
  569. (uint)ad.Category, ad.Name, ad.Description, ad.ParcelId, (uint)ad.ParentEstate,
  570. ad.SnapshotId, ad.SimName, globalPos, ad.ParcelName, ad.Flags, ad.Price);
  571. }
  572. /// <summary>
  573. /// Classifieds info update.
  574. /// </summary>
  575. /// <param name='queryclassifiedID'>
  576. /// Queryclassified I.
  577. /// </param>
  578. /// <param name='queryCategory'>
  579. /// Query category.
  580. /// </param>
  581. /// <param name='queryName'>
  582. /// Query name.
  583. /// </param>
  584. /// <param name='queryDescription'>
  585. /// Query description.
  586. /// </param>
  587. /// <param name='queryParcelID'>
  588. /// Query parcel I.
  589. /// </param>
  590. /// <param name='queryParentEstate'>
  591. /// Query parent estate.
  592. /// </param>
  593. /// <param name='querySnapshotID'>
  594. /// Query snapshot I.
  595. /// </param>
  596. /// <param name='queryGlobalPos'>
  597. /// Query global position.
  598. /// </param>
  599. /// <param name='queryclassifiedFlags'>
  600. /// Queryclassified flags.
  601. /// </param>
  602. /// <param name='queryclassifiedPrice'>
  603. /// Queryclassified price.
  604. /// </param>
  605. /// <param name='remoteClient'>
  606. /// Remote client.
  607. /// </param>
  608. public void ClassifiedInfoUpdate(UUID queryclassifiedID, uint queryCategory, string queryName, string queryDescription, UUID queryParcelID,
  609. uint queryParentEstate, UUID querySnapshotID, Vector3 queryGlobalPos, byte queryclassifiedFlags,
  610. int queryclassifiedPrice, IClientAPI remoteClient)
  611. {
  612. Scene s = (Scene)remoteClient.Scene;
  613. Vector3 pos = remoteClient.SceneAgent.AbsolutePosition;
  614. ILandObject land = s.LandChannel.GetLandObject(pos.X, pos.Y);
  615. UUID creatorId = remoteClient.AgentId;
  616. ScenePresence p = FindPresence(creatorId);
  617. UserProfileCacheEntry uce;
  618. lock (m_profilesCache)
  619. m_profilesCache.TryGetValue(remoteClient.AgentId, out uce);
  620. bool foreign = GetUserProfileServerURI(remoteClient.AgentId, out string serverURI);
  621. if(string.IsNullOrWhiteSpace(serverURI))
  622. return;
  623. if(foreign)
  624. {
  625. remoteClient.SendAgentAlertMessage("Please change classifieds on your home grid", true);
  626. if(uce is not null && uce.classifiedsLists is not null)
  627. remoteClient.SendAvatarClassifiedReply(remoteClient.AgentId, uce.classifiedsLists);
  628. return;
  629. }
  630. OSDMap parameters = new () { {"creatorId", OSD.FromUUID(creatorId)} };
  631. OSD osdtmp = parameters;
  632. if (!rpc.JsonRpcRequest(ref osdtmp, "avatarclassifiedsrequest", serverURI, UUID.Random().ToString()))
  633. {
  634. remoteClient.SendAgentAlertMessage("Error fetching classifieds", false);
  635. return;
  636. }
  637. parameters = (OSDMap)osdtmp;
  638. OSDArray list = (OSDArray)parameters["result"];
  639. bool exists = list.Cast<OSDMap>().Where(map => map.ContainsKey("classifieduuid"))
  640. .Any(map => map["classifieduuid"].AsUUID().Equals(queryclassifiedID));
  641. IMoneyModule money = null;
  642. if (!exists)
  643. {
  644. money = s.RequestModuleInterface<IMoneyModule>();
  645. if (money is not null)
  646. {
  647. if (!money.AmountCovered(remoteClient.AgentId, queryclassifiedPrice))
  648. {
  649. remoteClient.SendAgentAlertMessage("You do not have enough money to create this classified.", false);
  650. if(uce is not null && uce.classifiedsLists is not null)
  651. remoteClient.SendAvatarClassifiedReply(remoteClient.AgentId, uce.classifiedsLists);
  652. return;
  653. }
  654. }
  655. }
  656. UserClassifiedAdd ad = new()
  657. {
  658. ParcelName = land == null ? string.Empty : land.LandData.Name,
  659. CreatorId = remoteClient.AgentId,
  660. ClassifiedId = queryclassifiedID,
  661. Category = Convert.ToInt32(queryCategory),
  662. Name = queryName,
  663. Description = queryDescription,
  664. ParentEstate = Convert.ToInt32(queryParentEstate),
  665. SnapshotId = querySnapshotID,
  666. SimName = remoteClient.Scene.RegionInfo.RegionName,
  667. GlobalPos = queryGlobalPos.ToString(),
  668. Flags = queryclassifiedFlags,
  669. Price = queryclassifiedPrice,
  670. ParcelId = p.currentParcelUUID
  671. };
  672. object Ad = ad;
  673. if(!rpc.JsonRpcRequest(ref Ad, "classified_update", serverURI, UUID.Random().ToString()))
  674. {
  675. remoteClient.SendAgentAlertMessage("Error updating classified", false);
  676. if(uce is not null && uce.classifiedsLists is not null)
  677. remoteClient.SendAvatarClassifiedReply(remoteClient.AgentId, uce.classifiedsLists);
  678. return;
  679. }
  680. // only charge if it worked
  681. money?.ApplyCharge(remoteClient.AgentId, queryclassifiedPrice, MoneyTransactionType.ClassifiedCharge);
  682. // just flush cache for now
  683. lock(m_profilesCache)
  684. {
  685. if(m_profilesCache.TryGetValue(remoteClient.AgentId, out uce) && uce is not null)
  686. {
  687. uce.classifieds = null;
  688. uce.classifiedsLists = null;
  689. }
  690. }
  691. }
  692. /// <summary>
  693. /// Classifieds delete.
  694. /// </summary>
  695. /// <param name='queryClassifiedID'>
  696. /// Query classified I.
  697. /// </param>
  698. /// <param name='remoteClient'>
  699. /// Remote client.
  700. /// </param>
  701. public void ClassifiedDelete(UUID queryClassifiedID, IClientAPI remoteClient)
  702. {
  703. bool foreign = GetUserProfileServerURI(remoteClient.AgentId, out string serverURI);
  704. if(string.IsNullOrWhiteSpace(serverURI))
  705. return;
  706. if(foreign)
  707. {
  708. remoteClient.SendAgentAlertMessage("Please change classifieds on your home grid", true);
  709. return;
  710. }
  711. if (!UUID.TryParse(queryClassifiedID.ToString(), out UUID classifiedId))
  712. return;
  713. OSD Params = new OSDMap() {{ "classifiedId", OSD.FromUUID(classifiedId) }};
  714. if(!rpc.JsonRpcRequest(ref Params, "classified_delete", serverURI, UUID.Random().ToString()))
  715. {
  716. remoteClient.SendAgentAlertMessage(
  717. "Error deleting classified", false);
  718. return;
  719. }
  720. // flush cache
  721. lock(m_profilesCache)
  722. {
  723. if(m_profilesCache.TryGetValue(remoteClient.AgentId, out UserProfileCacheEntry uce) && uce is not null)
  724. {
  725. uce.classifieds = null;
  726. uce.classifiedsLists = null;
  727. }
  728. }
  729. }
  730. #endregion Classified
  731. #region Picks
  732. /// <summary>
  733. /// Handles the avatar picks request.
  734. /// </summary>
  735. /// <param name='sender'>
  736. /// Sender.
  737. /// </param>
  738. /// <param name='method'>
  739. /// Method.
  740. /// </param>
  741. /// <param name='args'>
  742. /// Arguments.
  743. /// </param>
  744. public void PicksRequest(Object sender, string method, List<String> args)
  745. {
  746. if (sender is not IClientAPI remoteClient)
  747. return;
  748. if(!UUID.TryParse(args[0], out UUID targetId))
  749. return;
  750. Dictionary<UUID, string> picks = new();
  751. if (targetId.Equals(Constants.m_MrOpenSimID))
  752. {
  753. remoteClient.SendAvatarPicksReply(targetId, picks);
  754. return;
  755. }
  756. ScenePresence p = FindPresence(targetId);
  757. if (p is not null && p.IsNPC)
  758. {
  759. remoteClient.SendAvatarPicksReply(targetId, picks);
  760. return;
  761. }
  762. UserProfileCacheEntry uce = null;
  763. lock(m_profilesCache)
  764. {
  765. if(m_profilesCache.TryGetValue(targetId, out uce) && uce is not null)
  766. {
  767. if(uce.picksList is not null)
  768. {
  769. remoteClient.SendAvatarPicksReply(targetId, uce.picksList);
  770. return;
  771. }
  772. }
  773. }
  774. GetUserProfileServerURI(targetId, out string serverURI);
  775. if(string.IsNullOrWhiteSpace(serverURI))
  776. {
  777. remoteClient.SendAvatarPicksReply(targetId, picks);
  778. return;
  779. }
  780. OSDMap parameters= new()
  781. {
  782. {"creatorId", OSD.FromUUID(targetId)}
  783. };
  784. OSD osdtmp = parameters;
  785. if(!rpc.JsonRpcRequest(ref osdtmp, "avatarpicksrequest", serverURI, UUID.Random().ToString()))
  786. {
  787. remoteClient.SendAvatarPicksReply(targetId, picks);
  788. return;
  789. }
  790. parameters = (OSDMap)osdtmp;
  791. if(!parameters.TryGetValue("result", out osdtmp) || osdtmp is not OSDArray)
  792. {
  793. remoteClient.SendAvatarPicksReply(targetId, picks);
  794. return;
  795. }
  796. OSDArray list = (OSDArray)osdtmp;
  797. foreach(OSD map in list)
  798. {
  799. OSDMap m = (OSDMap)map;
  800. UUID cid = m["pickuuid"].AsUUID();
  801. string name = m["name"].AsString();
  802. picks[cid] = name;
  803. }
  804. lock(m_profilesCache)
  805. {
  806. if(!m_profilesCache.TryGetValue(targetId, out uce) || uce is null)
  807. uce = new UserProfileCacheEntry();
  808. uce.picksList = picks;
  809. m_profilesCache.AddOrUpdate(targetId, uce, PROFILECACHEEXPIRE);
  810. }
  811. remoteClient.SendAvatarPicksReply(targetId, picks);
  812. }
  813. /// <summary>
  814. /// Handles the pick info request.
  815. /// </summary>
  816. /// <param name='sender'>
  817. /// Sender.
  818. /// </param>
  819. /// <param name='method'>
  820. /// Method.
  821. /// </param>
  822. /// <param name='args'>
  823. /// Arguments.
  824. /// </param>
  825. public void PickInfoRequest(Object sender, string method, List<String> args)
  826. {
  827. if (sender is not IClientAPI)
  828. return;
  829. UserProfilePick pick = new();
  830. if(!UUID.TryParse(args [0], out UUID targetID))
  831. return;
  832. pick.CreatorId = targetID;
  833. if(!UUID.TryParse (args [1], out pick.PickId))
  834. return;
  835. IClientAPI remoteClient = (IClientAPI)sender;
  836. UserProfileCacheEntry uce = null;
  837. lock(m_profilesCache)
  838. {
  839. if(m_profilesCache.TryGetValue(targetID, out uce) && uce is not null)
  840. {
  841. if(uce.picks is not null && uce.picks.ContainsKey(pick.PickId))
  842. {
  843. pick = uce.picks[pick.PickId];
  844. if(Vector3.TryParse(pick.GlobalPos, out Vector3 gPos))
  845. remoteClient.SendPickInfoReply(pick.PickId,pick.CreatorId,pick.TopPick,pick.ParcelId,pick.Name,
  846. pick.Desc,pick.SnapshotId,pick.ParcelName,pick.OriginalName,pick.SimName,
  847. gPos,pick.SortOrder,pick.Enabled);
  848. return;
  849. }
  850. }
  851. }
  852. bool foreign = GetUserProfileServerURI (targetID, out string serverURI);
  853. if(string.IsNullOrWhiteSpace(serverURI))
  854. {
  855. return;
  856. }
  857. object Pick = (object)pick;
  858. if (!rpc.JsonRpcRequest (ref Pick, "pickinforequest", serverURI, UUID.Random ().ToString ())) {
  859. remoteClient.SendAgentAlertMessage ("Error selecting pick", false);
  860. return;
  861. }
  862. pick = (UserProfilePick)Pick;
  863. if(foreign)
  864. cacheForeignImage(targetID, pick.SnapshotId);
  865. if(!Vector3.TryParse(pick.GlobalPos, out Vector3 globalPos))
  866. return;
  867. if (m_thisGridInfo.IsLocalGrid(pick.Gatekeeper, true) == 0)
  868. {
  869. // Setup the illusion
  870. string region = string.Format("{0} {1}",pick.Gatekeeper,pick.SimName);
  871. GridRegion target = Scene.GridService.GetRegionByName(Scene.RegionInfo.ScopeID, region);
  872. if(target is null)
  873. {
  874. // This is a unreachable region
  875. }
  876. else
  877. {
  878. // we have a proxy on map
  879. if (Util.ParseFakeParcelID(pick.ParcelId, out ulong _, out uint oriX, out uint oriY))
  880. {
  881. pick.ParcelId = Util.BuildFakeParcelID(target.RegionHandle, oriX, oriY);
  882. globalPos.X = target.RegionLocX + oriX;
  883. globalPos.Y = target.RegionLocY + oriY;
  884. pick.GlobalPos = globalPos.ToString();
  885. }
  886. else
  887. {
  888. // this is a fail on large regions
  889. uint gtmp = (uint)globalPos.X >> 8;
  890. globalPos.X -= (gtmp << 8);
  891. gtmp = (uint)globalPos.Y >> 8;
  892. globalPos.Y -= (gtmp << 8);
  893. pick.ParcelId = Util.BuildFakeParcelID(target.RegionHandle, (uint)globalPos.X, (uint)globalPos.Y);
  894. globalPos.X += target.RegionLocX;
  895. globalPos.Y += target.RegionLocY;
  896. pick.GlobalPos = globalPos.ToString();
  897. }
  898. }
  899. }
  900. //m_log.DebugFormat("[PROFILES]: PickInfoRequest: {0} : {1}", pick.Name.ToString(), pick.SnapshotId.ToString());
  901. lock(m_profilesCache)
  902. {
  903. if(!m_profilesCache.TryGetValue(targetID, out uce) || uce is null)
  904. uce = new UserProfileCacheEntry();
  905. uce.picks ??= new Dictionary<UUID, UserProfilePick>();
  906. uce.picks[pick.PickId] = pick;
  907. m_profilesCache.AddOrUpdate(targetID, uce, PROFILECACHEEXPIRE);
  908. }
  909. // Pull the rabbit out of the hat
  910. remoteClient.SendPickInfoReply(pick.PickId,pick.CreatorId,pick.TopPick,pick.ParcelId,pick.Name,
  911. pick.Desc,pick.SnapshotId,pick.ParcelName,pick.OriginalName,pick.SimName,
  912. globalPos,pick.SortOrder,pick.Enabled);
  913. }
  914. /// <summary>
  915. /// Updates the userpicks
  916. /// </summary>
  917. /// <param name='remoteClient'>
  918. /// Remote client.
  919. /// </param>
  920. /// <param name='pickID'>
  921. /// Pick I.
  922. /// </param>
  923. /// <param name='creatorID'>
  924. /// the creator of the pick
  925. /// </param>
  926. /// <param name='topPick'>
  927. /// Top pick.
  928. /// </param>
  929. /// <param name='name'>
  930. /// Name.
  931. /// </param>
  932. /// <param name='desc'>
  933. /// Desc.
  934. /// </param>
  935. /// <param name='snapshotID'>
  936. /// Snapshot I.
  937. /// </param>
  938. /// <param name='sortOrder'>
  939. /// Sort order.
  940. /// </param>
  941. /// <param name='enabled'>
  942. /// Enabled.
  943. /// </param>
  944. public void PickInfoUpdate(IClientAPI remoteClient, UUID pickID, UUID creatorID, bool topPick, string name, string desc, UUID snapshotID, int sortOrder, bool enabled)
  945. {
  946. //m_log.DebugFormat("[PROFILES]: Start PickInfoUpdate Name: {0} PickId: {1} SnapshotId: {2}", name, pickID.ToString(), snapshotID.ToString());
  947. UserProfilePick pick = new();
  948. GetUserProfileServerURI(remoteClient.AgentId, out string serverURI);
  949. if(string.IsNullOrWhiteSpace(serverURI))
  950. return;
  951. ScenePresence p = FindPresence(remoteClient.AgentId);
  952. Vector3 avaPos = p.AbsolutePosition;
  953. // Getting the global position for the Avatar
  954. Vector3 posGlobal = new(remoteClient.Scene.RegionInfo.WorldLocX + avaPos.X,
  955. remoteClient.Scene.RegionInfo.WorldLocY + avaPos.Y,
  956. avaPos.Z);
  957. string landParcelName = "My Parcel";
  958. // to locate parcels we use a fake id that encodes the region handle
  959. // since we do not have a global locator
  960. // this fails on HG
  961. UUID landParcelID = Util.BuildFakeParcelID(remoteClient.Scene.RegionInfo.RegionHandle, (uint)avaPos.X, (uint)avaPos.Y);
  962. ILandObject land = p.Scene.LandChannel.GetLandObject(avaPos.X, avaPos.Y);
  963. if (land is not null)
  964. {
  965. // If land found, use parcel uuid from here because the value from SP will be blank if the avatar hasnt moved
  966. landParcelName = land.LandData.Name;
  967. }
  968. else
  969. {
  970. m_log.WarnFormat(
  971. "[PROFILES]: PickInfoUpdate found no parcel info at {0},{1} in {2}",
  972. avaPos.X, avaPos.Y, p.Scene.Name);
  973. }
  974. pick.PickId = pickID;
  975. pick.CreatorId = creatorID;
  976. pick.TopPick = topPick;
  977. pick.Name = name;
  978. pick.Desc = desc;
  979. pick.ParcelId = landParcelID;
  980. pick.SnapshotId = snapshotID;
  981. pick.ParcelName = landParcelName;
  982. pick.SimName = remoteClient.Scene.RegionInfo.RegionName;
  983. pick.Gatekeeper = m_thisGridInfo.GateKeeperURLNoEndSlash;
  984. pick.GlobalPos = posGlobal.ToString();
  985. pick.SortOrder = sortOrder;
  986. pick.Enabled = enabled;
  987. object Pick = (object)pick;
  988. if(!rpc.JsonRpcRequest(ref Pick, "picks_update", serverURI, UUID.Random().ToString()))
  989. {
  990. remoteClient.SendAgentAlertMessage(
  991. "Error updating pick", false);
  992. return;
  993. }
  994. UserProfileCacheEntry uce = null;
  995. lock(m_profilesCache)
  996. {
  997. if(!m_profilesCache.TryGetValue(remoteClient.AgentId, out uce) || uce is null)
  998. uce = new UserProfileCacheEntry();
  999. uce.picks ??= new Dictionary<UUID, UserProfilePick>();
  1000. uce.picksList ??= new Dictionary<UUID, string>();
  1001. uce.picks[pick.PickId] = pick;
  1002. uce.picksList[pick.PickId] = pick.Name;
  1003. m_profilesCache.AddOrUpdate(remoteClient.AgentId, uce, PROFILECACHEEXPIRE);
  1004. }
  1005. remoteClient.SendAvatarPicksReply(remoteClient.AgentId, uce.picksList);
  1006. remoteClient.SendPickInfoReply(pick.PickId,pick.CreatorId,pick.TopPick,pick.ParcelId,pick.Name,
  1007. pick.Desc,pick.SnapshotId,pick.ParcelName,pick.OriginalName,pick.SimName,
  1008. posGlobal,pick.SortOrder,pick.Enabled);
  1009. //m_log.DebugFormat("[PROFILES]: Finish PickInfoUpdate {0} {1}", pick.Name, pick.PickId.ToString());
  1010. }
  1011. /// <summary>
  1012. /// Delete a Pick
  1013. /// </summary>
  1014. /// <param name='remoteClient'>
  1015. /// Remote client.
  1016. /// </param>
  1017. /// <param name='queryPickID'>
  1018. /// Query pick I.
  1019. /// </param>
  1020. public void PickDelete(IClientAPI remoteClient, UUID queryPickID)
  1021. {
  1022. GetUserProfileServerURI(remoteClient.AgentId, out string serverURI);
  1023. if(string.IsNullOrWhiteSpace(serverURI))
  1024. {
  1025. return;
  1026. }
  1027. OSDMap parameters = new() { { "pickId", OSD.FromUUID(queryPickID) } };
  1028. OSD Params = (OSD)parameters;
  1029. if(!rpc.JsonRpcRequest(ref Params, "picks_delete", serverURI, UUID.Random().ToString()))
  1030. {
  1031. remoteClient.SendAgentAlertMessage(
  1032. "Error picks delete", false);
  1033. return;
  1034. }
  1035. UserProfileCacheEntry uce = null;
  1036. lock(m_profilesCache)
  1037. {
  1038. if(m_profilesCache.TryGetValue(remoteClient.AgentId, out uce) && uce is not null)
  1039. {
  1040. uce.picks?.Remove(queryPickID);
  1041. uce.picksList?.Remove(queryPickID);
  1042. m_profilesCache.AddOrUpdate(remoteClient.AgentId, uce, PROFILECACHEEXPIRE);
  1043. }
  1044. }
  1045. if(uce is not null && uce.picksList is not null)
  1046. remoteClient.SendAvatarPicksReply(remoteClient.AgentId, uce.picksList);
  1047. else
  1048. remoteClient.SendAvatarPicksReply(remoteClient.AgentId, new Dictionary<UUID, string>());
  1049. }
  1050. #endregion Picks
  1051. #region Notes
  1052. /// <summary>
  1053. /// Handles the avatar notes request.
  1054. /// </summary>
  1055. /// <param name='sender'>
  1056. /// Sender.
  1057. /// </param>
  1058. /// <param name='method'>
  1059. /// Method.
  1060. /// </param>
  1061. /// <param name='args'>
  1062. /// Arguments.
  1063. /// </param>
  1064. public void NotesRequest(Object sender, string method, List<String> args)
  1065. {
  1066. if (sender is not IClientAPI remoteClient)
  1067. return;
  1068. UserProfileNotes note = new();
  1069. if (!UUID.TryParse(args[0], out note.TargetId))
  1070. return;
  1071. note.UserId = remoteClient.AgentId;
  1072. GetUserProfileServerURI(remoteClient.AgentId, out string serverURI);
  1073. if(string.IsNullOrWhiteSpace(serverURI))
  1074. {
  1075. remoteClient.SendAvatarNotesReply(note.TargetId, note.Notes);
  1076. return;
  1077. }
  1078. object Note = (object)note;
  1079. if(!rpc.JsonRpcRequest(ref Note, "avatarnotesrequest", serverURI, UUID.Random().ToString()))
  1080. {
  1081. remoteClient.SendAvatarNotesReply(note.TargetId, note.Notes);
  1082. return;
  1083. }
  1084. note = (UserProfileNotes) Note;
  1085. remoteClient.SendAvatarNotesReply(note.TargetId, note.Notes);
  1086. }
  1087. /// <summary>
  1088. /// Avatars the notes update.
  1089. /// </summary>
  1090. /// <param name='remoteClient'>
  1091. /// Remote client.
  1092. /// </param>
  1093. /// <param name='queryTargetID'>
  1094. /// Query target I.
  1095. /// </param>
  1096. /// <param name='queryNotes'>
  1097. /// Query notes.
  1098. /// </param>
  1099. public void NotesUpdate(IClientAPI remoteClient, UUID queryTargetID, string queryNotes)
  1100. {
  1101. if (queryTargetID.Equals(Constants.m_MrOpenSimID))
  1102. return;
  1103. ScenePresence p = FindPresence(queryTargetID);
  1104. if (p is not null && p.IsNPC)
  1105. {
  1106. remoteClient.SendAgentAlertMessage("Notes for NPCs not available", false);
  1107. return;
  1108. }
  1109. UserProfileNotes note = new()
  1110. {
  1111. UserId = remoteClient.AgentId,
  1112. TargetId = queryTargetID,
  1113. Notes = queryNotes
  1114. };
  1115. GetUserProfileServerURI(remoteClient.AgentId, out string serverURI);
  1116. if(string.IsNullOrWhiteSpace(serverURI))
  1117. return;
  1118. object Note = note;
  1119. if(!rpc.JsonRpcRequest(ref Note, "avatar_notes_update", serverURI, UUID.Random().ToString()))
  1120. {
  1121. remoteClient.SendAgentAlertMessage(
  1122. "Error updating note", false);
  1123. return;
  1124. }
  1125. }
  1126. #endregion Notes
  1127. #region User Preferences
  1128. /// <summary>
  1129. /// Updates the user preferences.
  1130. /// </summary>
  1131. /// <param name='imViaEmail'>
  1132. /// Im via email.
  1133. /// </param>
  1134. /// <param name='visible'>
  1135. /// Visible.
  1136. /// </param>
  1137. /// <param name='remoteClient'>
  1138. /// Remote client.
  1139. /// </param>
  1140. public void UpdateUserPreferences(bool imViaEmail, bool visible, IClientAPI remoteClient)
  1141. {
  1142. UserPreferences pref = new()
  1143. {
  1144. UserId = remoteClient.AgentId,
  1145. IMViaEmail = imViaEmail,
  1146. Visible = visible
  1147. };
  1148. _ = GetUserProfileServerURI(remoteClient.AgentId, out string serverURI);
  1149. if(string.IsNullOrWhiteSpace(serverURI))
  1150. return;
  1151. object Pref = pref;
  1152. if(!rpc.JsonRpcRequest(ref Pref, "user_preferences_update", serverURI, UUID.Random().ToString()))
  1153. {
  1154. m_log.InfoFormat("[PROFILES]: UserPreferences update error");
  1155. remoteClient.SendAgentAlertMessage("Error updating preferences", false);
  1156. return;
  1157. }
  1158. }
  1159. /// <summary>
  1160. /// Users the preferences request.
  1161. /// </summary>
  1162. /// <param name='remoteClient'>
  1163. /// Remote client.
  1164. /// </param>
  1165. public void UserPreferencesRequest(IClientAPI remoteClient)
  1166. {
  1167. GetUserProfileServerURI(remoteClient.AgentId, out string serverURI);
  1168. if(string.IsNullOrWhiteSpace(serverURI))
  1169. return;
  1170. UserPreferences pref = new() { UserId = remoteClient.AgentId };
  1171. object Pref = (object)pref;
  1172. if(!rpc.JsonRpcRequest(ref Pref, "user_preferences_request", serverURI, UUID.Random().ToString()))
  1173. {
  1174. //m_log.InfoFormat("[PROFILES]: UserPreferences request error");
  1175. //remoteClient.SendAgentAlertMessage("Error requesting preferences", false);
  1176. return;
  1177. }
  1178. pref = (UserPreferences) Pref;
  1179. remoteClient.SendUserInfoReply(pref.IMViaEmail, pref.Visible, pref.EMail);
  1180. }
  1181. #endregion User Preferences
  1182. #region Avatar Properties
  1183. /// <summary>
  1184. /// Update the avatars interests .
  1185. /// </summary>
  1186. /// <param name='remoteClient'>
  1187. /// Remote client.
  1188. /// </param>
  1189. /// <param name='wantmask'>
  1190. /// Wantmask.
  1191. /// </param>
  1192. /// <param name='wanttext'>
  1193. /// Wanttext.
  1194. /// </param>
  1195. /// <param name='skillsmask'>
  1196. /// Skillsmask.
  1197. /// </param>
  1198. /// <param name='skillstext'>
  1199. /// Skillstext.
  1200. /// </param>
  1201. /// <param name='languages'>
  1202. /// Languages.
  1203. /// </param>
  1204. public void AvatarInterestsUpdate(IClientAPI remoteClient, uint wantmask, string wanttext, uint skillsmask, string skillstext, string languages)
  1205. {
  1206. GetUserProfileServerURI(remoteClient.AgentId, out string serverURI);
  1207. if (string.IsNullOrWhiteSpace(serverURI))
  1208. return;
  1209. object Param = new UserProfileProperties()
  1210. {
  1211. UserId = remoteClient.AgentId,
  1212. WantToMask = (int)wantmask,
  1213. WantToText = wanttext,
  1214. SkillsMask = (int)skillsmask,
  1215. SkillsText = skillstext,
  1216. Language = languages
  1217. };
  1218. if(!rpc.JsonRpcRequest(ref Param, "avatar_interests_update", serverURI, UUID.Random().ToString()))
  1219. {
  1220. remoteClient.SendAgentAlertMessage("Error updating interests", false);
  1221. return;
  1222. }
  1223. // flush cache
  1224. lock(m_profilesCache)
  1225. {
  1226. if(m_profilesCache.TryGetValue(remoteClient.AgentId, out UserProfileCacheEntry uce) && uce is not null)
  1227. {
  1228. uce.props = null;
  1229. uce.ClientsWaitingProps = null;
  1230. }
  1231. }
  1232. RequestAvatarProperties(remoteClient, remoteClient.AgentId);
  1233. }
  1234. public void RequestAvatarProperties(IClientAPI remoteClient, UUID avatarID)
  1235. {
  1236. if (avatarID.IsZero())
  1237. {
  1238. // Looking for a reason that some viewers are sending null Id's
  1239. m_log.Debug("[PROFILES]: got request of null ID");
  1240. return;
  1241. }
  1242. if (avatarID.Equals(Constants.m_MrOpenSimID))
  1243. {
  1244. remoteClient.SendAvatarProperties(avatarID, "Creator of OpenSimulator shared assets library", Constants.m_MrOpenSimBorn.ToString(),
  1245. Utils.StringToBytes("System agent"), "MrOpenSim has no life", 0x10,
  1246. UUID.Zero, UUID.Zero, "", UUID.Zero);
  1247. remoteClient.SendAvatarInterestsReply(avatarID, 0, "",
  1248. 0, "Getting into trouble", "Droidspeak");
  1249. return;
  1250. }
  1251. ScenePresence p = FindPresence(avatarID);
  1252. if (p is not null && p.IsNPC)
  1253. {
  1254. remoteClient.SendAvatarProperties(avatarID, ((INPC)(p.ControllingClient)).profileAbout, ((INPC)(p.ControllingClient)).Born,
  1255. Utils.StringToBytes("Non Player Character (NPC)"), "NPCs have no life", 0x10,
  1256. UUID.Zero, ((INPC)(p.ControllingClient)).profileImage, "", UUID.Zero);
  1257. remoteClient.SendAvatarInterestsReply(avatarID, 0, "",
  1258. 0, "Getting into trouble", "Droidspeak");
  1259. return;
  1260. }
  1261. UserProfileProperties props;
  1262. lock(m_profilesCache)
  1263. {
  1264. if(m_profilesCache.TryGetValue(avatarID, out UserProfileCacheEntry uce) && uce is not null)
  1265. {
  1266. if(uce.props is not null)
  1267. {
  1268. props = uce.props;
  1269. uint cflags = uce.flags;
  1270. if (IsFriendOnline(remoteClient, avatarID))
  1271. cflags = (uint)ProfileFlags.Online;
  1272. else
  1273. cflags &= (uint)~ProfileFlags.Online;
  1274. remoteClient.SendAvatarProperties(props.UserId, props.AboutText,
  1275. uce.born, uce.membershipType , props.FirstLifeText, cflags,
  1276. props.FirstLifeImageId, props.ImageId, props.WebUrl, props.PartnerId);
  1277. remoteClient.SendAvatarInterestsReply(props.UserId, (uint)props.WantToMask,
  1278. props.WantToText, (uint)props.SkillsMask,
  1279. props.SkillsText, props.Language);
  1280. if(uce.avatarGroups is not null)
  1281. remoteClient.SendAvatarGroupsReply(avatarID, uce.avatarGroups);
  1282. return;
  1283. }
  1284. else
  1285. {
  1286. if(uce.ClientsWaitingProps == null)
  1287. uce.ClientsWaitingProps = new HashSet<IClientAPI>();
  1288. else if(uce.ClientsWaitingProps.Contains(remoteClient))
  1289. return;
  1290. uce.ClientsWaitingProps.Add(remoteClient);
  1291. }
  1292. }
  1293. else
  1294. {
  1295. uce = new UserProfileCacheEntry { ClientsWaitingProps = new HashSet<IClientAPI>() };
  1296. uce.ClientsWaitingProps.Add(remoteClient);
  1297. m_profilesCache.AddOrUpdate(avatarID, uce, PROFILECACHEEXPIRE);
  1298. }
  1299. }
  1300. AsyncPropsRequest req = new()
  1301. {
  1302. client = remoteClient,
  1303. presence = p,
  1304. agent = avatarID,
  1305. reqtype = 0
  1306. };
  1307. m_asyncRequests.Push(req);
  1308. if (Monitor.TryEnter(m_asyncRequestsLock))
  1309. {
  1310. if (!m_asyncRequestsRunning)
  1311. {
  1312. m_asyncRequestsRunning = true;
  1313. Util.FireAndForget(x => ProcessRequests());
  1314. }
  1315. Monitor.Exit(m_asyncRequestsLock);
  1316. }
  1317. /*
  1318. string serverURI = string.Empty;
  1319. bool foreign = GetUserProfileServerURI(avatarID, out serverURI);
  1320. UserAccount account = null;
  1321. Dictionary<string,object> userInfo;
  1322. if (!foreign)
  1323. {
  1324. account = Scene.UserAccountService.GetUserAccount(Scene.RegionInfo.ScopeID, avatarID);
  1325. }
  1326. else
  1327. {
  1328. userInfo = new Dictionary<string, object>();
  1329. }
  1330. Byte[] membershipType = new Byte[1];
  1331. string born = string.Empty;
  1332. uint flags = 0x00;
  1333. if (null != account)
  1334. {
  1335. if (account.UserTitle.Length == 0)
  1336. membershipType[0] = (Byte)((account.UserFlags & 0xf00) >> 8);
  1337. else
  1338. membershipType = Utils.StringToBytes(account.UserTitle);
  1339. born = Util.ToDateTime(account.Created).ToString(
  1340. "M/d/yyyy", CultureInfo.InvariantCulture);
  1341. flags = (uint)(account.UserFlags & 0xff);
  1342. }
  1343. else
  1344. {
  1345. if (GetUserAccountData(avatarID, out userInfo) == true)
  1346. {
  1347. if ((string)userInfo["user_title"].Length == 0)
  1348. membershipType[0] = (Byte)(((Byte)userInfo["user_flags"] & 0xf00) >> 8);
  1349. else
  1350. membershipType = Utils.StringToBytes((string)userInfo["user_title"]);
  1351. int val_born = (int)userInfo["user_created"];
  1352. if(val_born != 0)
  1353. born = Util.ToDateTime(val_born).ToString(
  1354. "M/d/yyyy", CultureInfo.InvariantCulture);
  1355. // picky, picky
  1356. int val_flags = (int)userInfo["user_flags"];
  1357. flags = (uint)(val_flags & 0xff);
  1358. }
  1359. }
  1360. props = new UserProfileProperties();
  1361. props.UserId = avatarID;
  1362. string result = string.Empty;
  1363. if(!GetProfileData(ref props, foreign, serverURI, out result))
  1364. {
  1365. props.AboutText ="Profile not available at this time. User may still be unknown to this grid";
  1366. }
  1367. if(!m_allowUserProfileWebURLs)
  1368. props.WebUrl ="";
  1369. HashSet<IClientAPI> clients;
  1370. lock(m_profilesCache)
  1371. {
  1372. if(!m_profilesCache.TryGetValue(props.UserId, out uce) || uce == null)
  1373. uce = new UserProfileCacheEntry();
  1374. uce.props = props;
  1375. uce.born = born;
  1376. uce.membershipType = membershipType;
  1377. uce.flags = flags;
  1378. clients = uce.ClientsWaitingProps;
  1379. uce.ClientsWaitingProps = null;
  1380. m_profilesCache.AddOrUpdate(props.UserId, uce, PROFILECACHEEXPIRE);
  1381. }
  1382. // if on same region force online
  1383. if(p != null && !p.IsDeleted)
  1384. flags |= 0x10;
  1385. if(clients == null)
  1386. {
  1387. remoteClient.SendAvatarProperties(props.UserId, props.AboutText, born, membershipType , props.FirstLifeText, flags,
  1388. props.FirstLifeImageId, props.ImageId, props.WebUrl, props.PartnerId);
  1389. remoteClient.SendAvatarInterestsReply(props.UserId, (uint)props.WantToMask, props.WantToText,
  1390. (uint)props.SkillsMask, props.SkillsText, props.Language);
  1391. }
  1392. else
  1393. {
  1394. if(!clients.Contains(remoteClient))
  1395. clients.Add(remoteClient);
  1396. foreach(IClientAPI cli in clients)
  1397. {
  1398. if(!cli.IsActive)
  1399. continue;
  1400. cli.SendAvatarProperties(props.UserId, props.AboutText, born, membershipType , props.FirstLifeText, flags,
  1401. props.FirstLifeImageId, props.ImageId, props.WebUrl, props.PartnerId);
  1402. cli.SendAvatarInterestsReply(props.UserId, (uint)props.WantToMask, props.WantToText,
  1403. (uint)props.SkillsMask, props.SkillsText, props.Language);
  1404. }
  1405. }
  1406. */
  1407. }
  1408. /// <summary>
  1409. /// Updates the avatar properties.
  1410. /// </summary>
  1411. /// <param name='remoteClient'>
  1412. /// Remote client.
  1413. /// </param>
  1414. /// <param name='newProfile'>
  1415. /// New profile.
  1416. /// </param>
  1417. public void AvatarPropertiesUpdate(IClientAPI remoteClient, UserProfileProperties newProfile)
  1418. {
  1419. GetUserProfileServerURI(remoteClient.AgentId, out string serverURI);
  1420. if (string.IsNullOrWhiteSpace(serverURI))
  1421. return;
  1422. if (!m_allowUserProfileWebURLs)
  1423. newProfile.WebUrl = string.Empty;
  1424. object Prop = newProfile;
  1425. if(!rpc.JsonRpcRequest(ref Prop, "avatar_properties_update", serverURI, UUID.Random().ToString()))
  1426. {
  1427. remoteClient.SendAgentAlertMessage("Error updating properties", false);
  1428. return;
  1429. }
  1430. // flush cache
  1431. lock(m_profilesCache)
  1432. {
  1433. if(m_profilesCache.TryGetValue(remoteClient.AgentId, out UserProfileCacheEntry uce) && uce is not null)
  1434. {
  1435. uce.props = null;
  1436. uce.ClientsWaitingProps = null;
  1437. }
  1438. }
  1439. RequestAvatarProperties(remoteClient, remoteClient.AgentId);
  1440. }
  1441. /// <summary>
  1442. /// Gets the profile data.
  1443. /// </summary>
  1444. /// <returns>
  1445. /// The profile data.
  1446. /// </returns>
  1447. bool GetProfileData(ref UserProfileProperties properties, bool foreign, string serverURI, out string message)
  1448. {
  1449. if (String.IsNullOrEmpty(serverURI))
  1450. {
  1451. message = "User profile service unknown at this time";
  1452. return false;
  1453. }
  1454. object Prop = properties;
  1455. if (!rpc.JsonRpcRequest(ref Prop, "avatar_properties_request", serverURI, UUID.Random().ToString()))
  1456. {
  1457. // If it's a foreign user then try again using OpenProfile, in case that's what the grid is using
  1458. bool secondChanceSuccess = false;
  1459. if (foreign)
  1460. {
  1461. try
  1462. {
  1463. OpenProfileClient client = new(serverURI);
  1464. if (client.RequestAvatarPropertiesUsingOpenProfile(ref properties))
  1465. secondChanceSuccess = true;
  1466. }
  1467. catch (Exception e)
  1468. {
  1469. m_log.Debug(
  1470. $"[PROFILES]: Request using the OpenProfile API for user {properties.UserId} to {serverURI} failed: {e.Message}");
  1471. // Allow the return 'message' to say "JsonRpcRequest" and not "OpenProfile", because
  1472. // the most likely reason that OpenProfile failed is that the remote server
  1473. // doesn't support OpenProfile, and that's not very interesting.
  1474. }
  1475. }
  1476. if (!secondChanceSuccess)
  1477. {
  1478. message = $"JsonRpcRequest for user {properties.UserId} to {serverURI} failed";
  1479. m_log.Debug($"[PROFILES]: {message}");
  1480. return false;
  1481. }
  1482. }
  1483. properties = (UserProfileProperties)Prop;
  1484. if(foreign)
  1485. {
  1486. cacheForeignImage(properties.UserId, properties.ImageId);
  1487. cacheForeignImage(properties.UserId, properties.FirstLifeImageId);
  1488. }
  1489. message = "Success";
  1490. return true;
  1491. }
  1492. #endregion Avatar Properties
  1493. #region Utils
  1494. /// <summary>
  1495. /// Gets the user account data.
  1496. /// </summary>
  1497. /// <returns>
  1498. /// The user profile data.
  1499. /// </returns>
  1500. /// <param name='userID'>
  1501. /// If set to <c>true</c> user I.
  1502. /// </param>
  1503. /// <param name='userInfo'>
  1504. /// If set to <c>true</c> user info.
  1505. /// </param>
  1506. bool GetUserAccountData(UUID userID, out UserAccount account)
  1507. {
  1508. account = null;
  1509. if (UserManagementModule.IsLocalGridUser(userID))
  1510. {
  1511. // Is local
  1512. IUserAccountService uas = Scene.UserAccountService;
  1513. account = uas.GetUserAccount(Scene.RegionInfo.ScopeID, userID);
  1514. return account is not null;
  1515. }
  1516. else
  1517. {
  1518. // Is Foreign
  1519. string home_url = UserManagementModule.GetUserServerURL(userID, "HomeURI", out bool recentFailedWeb);
  1520. if (recentFailedWeb || String.IsNullOrEmpty(home_url))
  1521. return false;
  1522. UserAgentServiceConnector uConn = new(home_url);
  1523. Dictionary<string, object> info;
  1524. try
  1525. {
  1526. info = uConn.GetUserInfo(userID);
  1527. }
  1528. catch (Exception e)
  1529. {
  1530. m_log.Debug("[PROFILES]: GetUserInfo call failed ", e);
  1531. UserManagementModule.UserWebFailed(userID);
  1532. return false;
  1533. }
  1534. if (info.Count == 0)
  1535. return false;
  1536. account = new UserAccount();
  1537. if (info.ContainsKey("user_flags"))
  1538. account.UserFlags = (int)info["user_flags"];
  1539. if (info.ContainsKey("user_created"))
  1540. account.Created = (int)info["user_created"];
  1541. account.UserTitle = "HG Visitor";
  1542. return true;
  1543. }
  1544. }
  1545. /// <summary>
  1546. /// Gets the user profile server UR.
  1547. /// </summary>
  1548. /// <returns>
  1549. /// The user profile server UR.
  1550. /// </returns>
  1551. /// <param name='userID'>
  1552. /// If set to <c>true</c> user I.
  1553. /// </param>
  1554. /// <param name='serverURI'>
  1555. /// If set to <c>true</c> server UR.
  1556. /// </param>
  1557. bool GetUserProfileServerURI(UUID userID, out string serverURI)
  1558. {
  1559. if (!UserManagementModule.IsLocalGridUser(userID))
  1560. {
  1561. serverURI = UserManagementModule.GetUserServerURL(userID, "ProfileServerURI", out bool failed);
  1562. if(failed)
  1563. serverURI = string.Empty;
  1564. // Is Foreign
  1565. return true;
  1566. }
  1567. else
  1568. {
  1569. serverURI = ProfileServerUri;
  1570. // Is local
  1571. return false;
  1572. }
  1573. }
  1574. void cacheForeignImage(UUID agent, UUID imageID)
  1575. {
  1576. if(imageID.IsZero())
  1577. return;
  1578. string assetServerURI = UserManagementModule.GetUserServerURL(agent, "AssetServerURI");
  1579. if(string.IsNullOrWhiteSpace(assetServerURI))
  1580. return;
  1581. Scene.AssetService.Get(imageID.ToString(), assetServerURI, false);
  1582. }
  1583. /// <summary>
  1584. /// Finds the presence.
  1585. /// </summary>
  1586. /// <returns>
  1587. /// The presence.
  1588. /// </returns>
  1589. /// <param name='clientID'>
  1590. /// Client I.
  1591. /// </param>
  1592. ScenePresence FindPresence(UUID clientID)
  1593. {
  1594. ScenePresence p = Scene.GetScenePresence(clientID);
  1595. if (p is not null && !p.IsChildAgent)
  1596. return p;
  1597. return null;
  1598. }
  1599. public virtual bool IsFriendOnline(IClientAPI client, UUID agent)
  1600. {
  1601. // if on same region force online
  1602. ScenePresence p = Scene.GetScenePresence(agent);
  1603. if (p is not null && !p.IsChildAgent && !p.IsDeleted)
  1604. return true;
  1605. IFriendsModule friendsModule = Scene.RequestModuleInterface<IFriendsModule>();
  1606. if (friendsModule is not null && friendsModule.IsFriendOnline(client.AgentId, agent))
  1607. return true;
  1608. if(client.SceneAgent is ScenePresence sp && sp.IsViewerUIGod)
  1609. {
  1610. Services.Interfaces.PresenceInfo[] pi = Scene.PresenceService?.GetAgents(new string[] { agent.ToString() });
  1611. return pi is not null && pi.Length > 0;
  1612. }
  1613. return false;
  1614. }
  1615. #endregion Util
  1616. #region Web Util
  1617. /// <summary>
  1618. /// Sends json-rpc request with a serializable type.
  1619. /// </summary>
  1620. /// <returns>
  1621. /// OSD Map.
  1622. /// </returns>
  1623. /// <param name='parameters'>
  1624. /// Serializable type .
  1625. /// </param>
  1626. /// <param name='method'>
  1627. /// Json-rpc method to call.
  1628. /// </param>
  1629. /// <param name='uri'>
  1630. /// URI of json-rpc service.
  1631. /// </param>
  1632. /// <param name='jsonId'>
  1633. /// Id for our call.
  1634. /// </param>
  1635. bool JsonRpcRequest(ref object parameters, string method, string uri, string jsonId)
  1636. {
  1637. return rpc?.JsonRpcRequest(ref parameters, method, uri, jsonId) ?? false;
  1638. }
  1639. /// <summary>
  1640. /// Sends json-rpc request with OSD parameter.
  1641. /// </summary>
  1642. /// <returns>
  1643. /// The rpc request.
  1644. /// </returns>
  1645. /// <param name='data'>
  1646. /// data - incoming as parameters, outgong as result/error
  1647. /// </param>
  1648. /// <param name='method'>
  1649. /// Json-rpc method to call.
  1650. /// </param>
  1651. /// <param name='uri'>
  1652. /// URI of json-rpc service.
  1653. /// </param>
  1654. /// <param name='jsonId'>
  1655. /// If set to <c>true</c> json identifier.
  1656. /// </param>
  1657. bool JsonRpcRequest(ref OSD data, string method, string uri, string jsonId)
  1658. {
  1659. return rpc?.JsonRpcRequest(ref data, method, uri, jsonId) ?? false;
  1660. }
  1661. #endregion Web Util
  1662. }
  1663. }