GroupsMessagingModule.cs 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538
  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 Mono.Addins;
  32. using Nini.Config;
  33. using OpenMetaverse;
  34. using OpenMetaverse.StructuredData;
  35. using OpenSim.Framework;
  36. using OpenSim.Region.Framework.Interfaces;
  37. using OpenSim.Region.Framework.Scenes;
  38. namespace OpenSim.Region.OptionalModules.Avatar.XmlRpcGroups
  39. {
  40. [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule")]
  41. public class GroupsMessagingModule : ISharedRegionModule, IGroupsMessagingModule
  42. {
  43. private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
  44. private List<Scene> m_sceneList = new List<Scene>();
  45. private IMessageTransferModule m_msgTransferModule = null;
  46. private IGroupsServicesConnector m_groupData = null;
  47. // Config Options
  48. private bool m_groupMessagingEnabled = false;
  49. private bool m_debugEnabled = true;
  50. #region IRegionModuleBase Members
  51. public void Initialise(IConfigSource config)
  52. {
  53. IConfig groupsConfig = config.Configs["Groups"];
  54. if (groupsConfig == null)
  55. {
  56. // Do not run this module by default.
  57. return;
  58. }
  59. else
  60. {
  61. // if groups aren't enabled, we're not needed.
  62. // if we're not specified as the connector to use, then we're not wanted
  63. if ((groupsConfig.GetBoolean("Enabled", false) == false)
  64. || (groupsConfig.GetString("MessagingModule", "GroupsMessagingModule") != Name))
  65. {
  66. m_groupMessagingEnabled = false;
  67. return;
  68. }
  69. m_groupMessagingEnabled = groupsConfig.GetBoolean("MessagingEnabled", true);
  70. if (!m_groupMessagingEnabled)
  71. {
  72. return;
  73. }
  74. m_debugEnabled = groupsConfig.GetBoolean("DebugEnabled", true);
  75. }
  76. m_log.Info("[GROUPS-MESSAGING]: GroupsMessagingModule starting up");
  77. }
  78. public void AddRegion(Scene scene)
  79. {
  80. if (!m_groupMessagingEnabled)
  81. return;
  82. scene.RegisterModuleInterface<IGroupsMessagingModule>(this);
  83. }
  84. public void RegionLoaded(Scene scene)
  85. {
  86. if (!m_groupMessagingEnabled)
  87. return;
  88. if (m_debugEnabled) m_log.DebugFormat("[GROUPS-MESSAGING]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name);
  89. m_groupData = scene.RequestModuleInterface<IGroupsServicesConnector>();
  90. // No groups module, no groups messaging
  91. if (m_groupData == null)
  92. {
  93. m_log.Error("[GROUPS-MESSAGING]: Could not get IGroupsServicesConnector, GroupsMessagingModule is now disabled.");
  94. Close();
  95. m_groupMessagingEnabled = false;
  96. return;
  97. }
  98. m_msgTransferModule = scene.RequestModuleInterface<IMessageTransferModule>();
  99. // No message transfer module, no groups messaging
  100. if (m_msgTransferModule == null)
  101. {
  102. m_log.Error("[GROUPS-MESSAGING]: Could not get MessageTransferModule");
  103. Close();
  104. m_groupMessagingEnabled = false;
  105. return;
  106. }
  107. m_sceneList.Add(scene);
  108. scene.EventManager.OnNewClient += OnNewClient;
  109. scene.EventManager.OnIncomingInstantMessage += OnGridInstantMessage;
  110. scene.EventManager.OnClientLogin += OnClientLogin;
  111. }
  112. public void RemoveRegion(Scene scene)
  113. {
  114. if (!m_groupMessagingEnabled)
  115. return;
  116. if (m_debugEnabled) m_log.DebugFormat("[GROUPS-MESSAGING]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name);
  117. m_sceneList.Remove(scene);
  118. }
  119. public void Close()
  120. {
  121. if (!m_groupMessagingEnabled)
  122. return;
  123. if (m_debugEnabled) m_log.Debug("[GROUPS-MESSAGING]: Shutting down GroupsMessagingModule module.");
  124. foreach (Scene scene in m_sceneList)
  125. {
  126. scene.EventManager.OnNewClient -= OnNewClient;
  127. scene.EventManager.OnIncomingInstantMessage -= OnGridInstantMessage;
  128. }
  129. m_sceneList.Clear();
  130. m_groupData = null;
  131. m_msgTransferModule = null;
  132. }
  133. public Type ReplaceableInterface
  134. {
  135. get { return null; }
  136. }
  137. public string Name
  138. {
  139. get { return "GroupsMessagingModule"; }
  140. }
  141. #endregion
  142. #region ISharedRegionModule Members
  143. public void PostInitialise()
  144. {
  145. // NoOp
  146. }
  147. #endregion
  148. /// <summary>
  149. /// Not really needed, but does confirm that the group exists.
  150. /// </summary>
  151. public bool StartGroupChatSession(UUID agentID, UUID groupID)
  152. {
  153. if (m_debugEnabled)
  154. m_log.DebugFormat("[GROUPS-MESSAGING]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name);
  155. GroupRecord groupInfo = m_groupData.GetGroupRecord(agentID, groupID, null);
  156. if (groupInfo != null)
  157. {
  158. return true;
  159. }
  160. else
  161. {
  162. return false;
  163. }
  164. }
  165. public void SendMessageToGroup(GridInstantMessage im, UUID groupID)
  166. {
  167. List<GroupMembersData> groupMembers = m_groupData.GetGroupMembers(new UUID(im.fromAgentID), groupID);
  168. if (m_debugEnabled)
  169. m_log.DebugFormat(
  170. "[GROUPS-MESSAGING]: SendMessageToGroup called for group {0} with {1} visible members",
  171. groupID, groupMembers.Count);
  172. foreach (GroupMembersData member in groupMembers)
  173. {
  174. if (m_groupData.hasAgentDroppedGroupChatSession(member.AgentID, groupID))
  175. {
  176. // Don't deliver messages to people who have dropped this session
  177. if (m_debugEnabled) m_log.DebugFormat("[GROUPS-MESSAGING]: {0} has dropped session, not delivering to them", member.AgentID);
  178. continue;
  179. }
  180. // Copy Message
  181. GridInstantMessage msg = new GridInstantMessage();
  182. msg.imSessionID = groupID.Guid;
  183. msg.fromAgentName = im.fromAgentName;
  184. msg.message = im.message;
  185. msg.dialog = im.dialog;
  186. msg.offline = im.offline;
  187. msg.ParentEstateID = im.ParentEstateID;
  188. msg.Position = im.Position;
  189. msg.RegionID = im.RegionID;
  190. msg.binaryBucket = im.binaryBucket;
  191. msg.timestamp = (uint)Util.UnixTimeSinceEpoch();
  192. msg.fromAgentID = im.fromAgentID;
  193. msg.fromGroup = true;
  194. msg.toAgentID = member.AgentID.Guid;
  195. IClientAPI client = GetActiveClient(member.AgentID);
  196. if (client == null)
  197. {
  198. // If they're not local, forward across the grid
  199. if (m_debugEnabled) m_log.DebugFormat("[GROUPS-MESSAGING]: Delivering to {0} via Grid", member.AgentID);
  200. m_msgTransferModule.SendInstantMessage(msg, delegate(bool success) { });
  201. }
  202. else
  203. {
  204. // Deliver locally, directly
  205. if (m_debugEnabled) m_log.DebugFormat("[GROUPS-MESSAGING]: Passing to ProcessMessageFromGroupSession to deliver to {0} locally", client.Name);
  206. ProcessMessageFromGroupSession(msg);
  207. }
  208. }
  209. }
  210. #region SimGridEventHandlers
  211. void OnClientLogin(IClientAPI client)
  212. {
  213. if (m_debugEnabled) m_log.DebugFormat("[GROUPS-MESSAGING]: OnInstantMessage registered for {0}", client.Name);
  214. }
  215. private void OnNewClient(IClientAPI client)
  216. {
  217. if (m_debugEnabled) m_log.DebugFormat("[GROUPS-MESSAGING]: OnInstantMessage registered for {0}", client.Name);
  218. client.OnInstantMessage += OnInstantMessage;
  219. }
  220. private void OnGridInstantMessage(GridInstantMessage msg)
  221. {
  222. // The instant message module will only deliver messages of dialog types:
  223. // MessageFromAgent, StartTyping, StopTyping, MessageFromObject
  224. //
  225. // Any other message type will not be delivered to a client by the
  226. // Instant Message Module
  227. if (m_debugEnabled)
  228. {
  229. m_log.DebugFormat("[GROUPS-MESSAGING]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name);
  230. DebugGridInstantMessage(msg);
  231. }
  232. // Incoming message from a group
  233. if ((msg.fromGroup == true) &&
  234. ((msg.dialog == (byte)InstantMessageDialog.SessionSend)
  235. || (msg.dialog == (byte)InstantMessageDialog.SessionAdd)
  236. || (msg.dialog == (byte)InstantMessageDialog.SessionDrop)))
  237. {
  238. ProcessMessageFromGroupSession(msg);
  239. }
  240. }
  241. private void ProcessMessageFromGroupSession(GridInstantMessage msg)
  242. {
  243. if (m_debugEnabled) m_log.DebugFormat("[GROUPS-MESSAGING]: Session message from {0} going to agent {1}", msg.fromAgentName, msg.toAgentID);
  244. UUID AgentID = new UUID(msg.fromAgentID);
  245. UUID GroupID = new UUID(msg.imSessionID);
  246. switch (msg.dialog)
  247. {
  248. case (byte)InstantMessageDialog.SessionAdd:
  249. m_groupData.AgentInvitedToGroupChatSession(AgentID, GroupID);
  250. break;
  251. case (byte)InstantMessageDialog.SessionDrop:
  252. m_groupData.AgentDroppedFromGroupChatSession(AgentID, GroupID);
  253. break;
  254. case (byte)InstantMessageDialog.SessionSend:
  255. if (!m_groupData.hasAgentDroppedGroupChatSession(AgentID, GroupID)
  256. && !m_groupData.hasAgentBeenInvitedToGroupChatSession(AgentID, GroupID)
  257. )
  258. {
  259. // Agent not in session and hasn't dropped from session
  260. // Add them to the session for now, and Invite them
  261. m_groupData.AgentInvitedToGroupChatSession(AgentID, GroupID);
  262. UUID toAgentID = new UUID(msg.toAgentID);
  263. IClientAPI activeClient = GetActiveClient(toAgentID);
  264. if (activeClient != null)
  265. {
  266. GroupRecord groupInfo = m_groupData.GetGroupRecord(UUID.Zero, GroupID, null);
  267. if (groupInfo != null)
  268. {
  269. if (m_debugEnabled) m_log.DebugFormat("[GROUPS-MESSAGING]: Sending chatterbox invite instant message");
  270. // Force? open the group session dialog???
  271. // and simultanously deliver the message, so we don't need to do a seperate client.SendInstantMessage(msg);
  272. IEventQueue eq = activeClient.Scene.RequestModuleInterface<IEventQueue>();
  273. eq.ChatterboxInvitation(
  274. GroupID
  275. , groupInfo.GroupName
  276. , new UUID(msg.fromAgentID)
  277. , msg.message
  278. , new UUID(msg.toAgentID)
  279. , msg.fromAgentName
  280. , msg.dialog
  281. , msg.timestamp
  282. , msg.offline == 1
  283. , (int)msg.ParentEstateID
  284. , msg.Position
  285. , 1
  286. , new UUID(msg.imSessionID)
  287. , msg.fromGroup
  288. , Utils.StringToBytes(groupInfo.GroupName)
  289. );
  290. eq.ChatterBoxSessionAgentListUpdates(
  291. new UUID(GroupID)
  292. , new UUID(msg.fromAgentID)
  293. , new UUID(msg.toAgentID)
  294. , false //canVoiceChat
  295. , false //isModerator
  296. , false //text mute
  297. );
  298. }
  299. }
  300. }
  301. else if (!m_groupData.hasAgentDroppedGroupChatSession(AgentID, GroupID))
  302. {
  303. // User hasn't dropped, so they're in the session,
  304. // maybe we should deliver it.
  305. IClientAPI client = GetActiveClient(new UUID(msg.toAgentID));
  306. if (client != null)
  307. {
  308. // Deliver locally, directly
  309. if (m_debugEnabled) m_log.DebugFormat("[GROUPS-MESSAGING]: Delivering to {0} locally", client.Name);
  310. client.SendInstantMessage(msg);
  311. }
  312. else
  313. {
  314. m_log.WarnFormat("[GROUPS-MESSAGING]: Received a message over the grid for a client that isn't here: {0}", msg.toAgentID);
  315. }
  316. }
  317. break;
  318. default:
  319. m_log.WarnFormat("[GROUPS-MESSAGING]: I don't know how to proccess a {0} message.", ((InstantMessageDialog)msg.dialog).ToString());
  320. break;
  321. }
  322. }
  323. #endregion
  324. #region ClientEvents
  325. private void OnInstantMessage(IClientAPI remoteClient, GridInstantMessage im)
  326. {
  327. if (m_debugEnabled)
  328. {
  329. m_log.DebugFormat("[GROUPS-MESSAGING]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name);
  330. DebugGridInstantMessage(im);
  331. }
  332. // Start group IM session
  333. if ((im.dialog == (byte)InstantMessageDialog.SessionGroupStart))
  334. {
  335. if (m_debugEnabled) m_log.InfoFormat("[GROUPS-MESSAGING]: imSessionID({0}) toAgentID({1})", im.imSessionID, im.toAgentID);
  336. UUID GroupID = new UUID(im.imSessionID);
  337. UUID AgentID = new UUID(im.fromAgentID);
  338. GroupRecord groupInfo = m_groupData.GetGroupRecord(UUID.Zero, GroupID, null);
  339. if (groupInfo != null)
  340. {
  341. m_groupData.AgentInvitedToGroupChatSession(AgentID, GroupID);
  342. ChatterBoxSessionStartReplyViaCaps(remoteClient, groupInfo.GroupName, GroupID);
  343. IEventQueue queue = remoteClient.Scene.RequestModuleInterface<IEventQueue>();
  344. queue.ChatterBoxSessionAgentListUpdates(
  345. GroupID
  346. , AgentID
  347. , new UUID(im.toAgentID)
  348. , false //canVoiceChat
  349. , false //isModerator
  350. , false //text mute
  351. );
  352. }
  353. }
  354. // Send a message from locally connected client to a group
  355. if ((im.dialog == (byte)InstantMessageDialog.SessionSend))
  356. {
  357. UUID GroupID = new UUID(im.imSessionID);
  358. UUID AgentID = new UUID(im.fromAgentID);
  359. if (m_debugEnabled)
  360. m_log.DebugFormat("[GROUPS-MESSAGING]: Send message to session for group {0} with session ID {1}", GroupID, im.imSessionID.ToString());
  361. //If this agent is sending a message, then they want to be in the session
  362. m_groupData.AgentInvitedToGroupChatSession(AgentID, GroupID);
  363. SendMessageToGroup(im, GroupID);
  364. }
  365. }
  366. #endregion
  367. void ChatterBoxSessionStartReplyViaCaps(IClientAPI remoteClient, string groupName, UUID groupID)
  368. {
  369. if (m_debugEnabled) m_log.DebugFormat("[GROUPS-MESSAGING]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name);
  370. OSDMap moderatedMap = new OSDMap(4);
  371. moderatedMap.Add("voice", OSD.FromBoolean(false));
  372. OSDMap sessionMap = new OSDMap(4);
  373. sessionMap.Add("moderated_mode", moderatedMap);
  374. sessionMap.Add("session_name", OSD.FromString(groupName));
  375. sessionMap.Add("type", OSD.FromInteger(0));
  376. sessionMap.Add("voice_enabled", OSD.FromBoolean(false));
  377. OSDMap bodyMap = new OSDMap(4);
  378. bodyMap.Add("session_id", OSD.FromUUID(groupID));
  379. bodyMap.Add("temp_session_id", OSD.FromUUID(groupID));
  380. bodyMap.Add("success", OSD.FromBoolean(true));
  381. bodyMap.Add("session_info", sessionMap);
  382. IEventQueue queue = remoteClient.Scene.RequestModuleInterface<IEventQueue>();
  383. if (queue != null)
  384. {
  385. queue.Enqueue(queue.BuildEvent("ChatterBoxSessionStartReply", bodyMap), remoteClient.AgentId);
  386. }
  387. }
  388. private void DebugGridInstantMessage(GridInstantMessage im)
  389. {
  390. // Don't log any normal IMs (privacy!)
  391. if (m_debugEnabled && im.dialog != (byte)InstantMessageDialog.MessageFromAgent)
  392. {
  393. m_log.WarnFormat("[GROUPS-MESSAGING]: IM: fromGroup({0})", im.fromGroup ? "True" : "False");
  394. m_log.WarnFormat("[GROUPS-MESSAGING]: IM: Dialog({0})", ((InstantMessageDialog)im.dialog).ToString());
  395. m_log.WarnFormat("[GROUPS-MESSAGING]: IM: fromAgentID({0})", im.fromAgentID.ToString());
  396. m_log.WarnFormat("[GROUPS-MESSAGING]: IM: fromAgentName({0})", im.fromAgentName.ToString());
  397. m_log.WarnFormat("[GROUPS-MESSAGING]: IM: imSessionID({0})", im.imSessionID.ToString());
  398. m_log.WarnFormat("[GROUPS-MESSAGING]: IM: message({0})", im.message.ToString());
  399. m_log.WarnFormat("[GROUPS-MESSAGING]: IM: offline({0})", im.offline.ToString());
  400. m_log.WarnFormat("[GROUPS-MESSAGING]: IM: toAgentID({0})", im.toAgentID.ToString());
  401. m_log.WarnFormat("[GROUPS-MESSAGING]: IM: binaryBucket({0})", OpenMetaverse.Utils.BytesToHexString(im.binaryBucket, "BinaryBucket"));
  402. }
  403. }
  404. #region Client Tools
  405. /// <summary>
  406. /// Try to find an active IClientAPI reference for agentID giving preference to root connections
  407. /// </summary>
  408. private IClientAPI GetActiveClient(UUID agentID)
  409. {
  410. if (m_debugEnabled) m_log.WarnFormat("[GROUPS-MESSAGING]: Looking for local client {0}", agentID);
  411. IClientAPI child = null;
  412. // Try root avatar first
  413. foreach (Scene scene in m_sceneList)
  414. {
  415. if (scene.Entities.ContainsKey(agentID) &&
  416. scene.Entities[agentID] is ScenePresence)
  417. {
  418. ScenePresence user = (ScenePresence)scene.Entities[agentID];
  419. if (!user.IsChildAgent)
  420. {
  421. if (m_debugEnabled) m_log.WarnFormat("[GROUPS-MESSAGING]: Found root agent for client : {0}", user.ControllingClient.Name);
  422. return user.ControllingClient;
  423. }
  424. else
  425. {
  426. if (m_debugEnabled) m_log.WarnFormat("[GROUPS-MESSAGING]: Found child agent for client : {0}", user.ControllingClient.Name);
  427. child = user.ControllingClient;
  428. }
  429. }
  430. }
  431. // If we didn't find a root, then just return whichever child we found, or null if none
  432. if (child == null)
  433. {
  434. if (m_debugEnabled) m_log.WarnFormat("[GROUPS-MESSAGING]: Could not find local client for agent : {0}", agentID);
  435. }
  436. else
  437. {
  438. if (m_debugEnabled) m_log.WarnFormat("[GROUPS-MESSAGING]: Returning child agent for client : {0}", child.Name);
  439. }
  440. return child;
  441. }
  442. #endregion
  443. }
  444. }