GroupsMessagingModule.cs 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543
  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.CoreModules.Framework.EventQueue;
  37. using OpenSim.Region.Framework.Interfaces;
  38. using OpenSim.Region.Framework.Scenes;
  39. using Caps = OpenSim.Framework.Capabilities.Caps;
  40. namespace OpenSim.Region.OptionalModules.Avatar.XmlRpcGroups
  41. {
  42. [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule")]
  43. public class GroupsMessagingModule : ISharedRegionModule, IGroupsMessagingModule
  44. {
  45. private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
  46. private List<Scene> m_sceneList = new List<Scene>();
  47. private IMessageTransferModule m_msgTransferModule = null;
  48. private IGroupsServicesConnector m_groupData = null;
  49. // Config Options
  50. private bool m_groupMessagingEnabled = false;
  51. private bool m_debugEnabled = true;
  52. #region IRegionModuleBase Members
  53. public void Initialise(IConfigSource config)
  54. {
  55. IConfig groupsConfig = config.Configs["Groups"];
  56. if (groupsConfig == null)
  57. {
  58. // Do not run this module by default.
  59. return;
  60. }
  61. else
  62. {
  63. // if groups aren't enabled, we're not needed.
  64. // if we're not specified as the connector to use, then we're not wanted
  65. if ((groupsConfig.GetBoolean("Enabled", false) == false)
  66. || (groupsConfig.GetString("MessagingModule", "Default") != Name))
  67. {
  68. m_groupMessagingEnabled = false;
  69. return;
  70. }
  71. m_groupMessagingEnabled = groupsConfig.GetBoolean("MessagingEnabled", true);
  72. if (!m_groupMessagingEnabled)
  73. {
  74. return;
  75. }
  76. m_log.Info("[GROUPS-MESSAGING]: Initializing GroupsMessagingModule");
  77. m_debugEnabled = groupsConfig.GetBoolean("DebugEnabled", true);
  78. }
  79. m_log.Info("[GROUPS-MESSAGING]: GroupsMessagingModule starting up");
  80. }
  81. public void AddRegion(Scene scene)
  82. {
  83. if (!m_groupMessagingEnabled)
  84. return;
  85. scene.RegisterModuleInterface<IGroupsMessagingModule>(this);
  86. }
  87. public void RegionLoaded(Scene scene)
  88. {
  89. if (!m_groupMessagingEnabled)
  90. return;
  91. if (m_debugEnabled) m_log.DebugFormat("[GROUPS-MESSAGING]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name);
  92. m_groupData = scene.RequestModuleInterface<IGroupsServicesConnector>();
  93. // No groups module, no groups messaging
  94. if (m_groupData == null)
  95. {
  96. m_log.Error("[GROUPS-MESSAGING]: Could not get IGroupsServicesConnector, GroupsMessagingModule is now disabled.");
  97. Close();
  98. m_groupMessagingEnabled = false;
  99. return;
  100. }
  101. m_msgTransferModule = scene.RequestModuleInterface<IMessageTransferModule>();
  102. // No message transfer module, no groups messaging
  103. if (m_msgTransferModule == null)
  104. {
  105. m_log.Error("[GROUPS-MESSAGING]: Could not get MessageTransferModule");
  106. Close();
  107. m_groupMessagingEnabled = false;
  108. return;
  109. }
  110. m_sceneList.Add(scene);
  111. scene.EventManager.OnNewClient += OnNewClient;
  112. scene.EventManager.OnIncomingInstantMessage += OnGridInstantMessage;
  113. scene.EventManager.OnClientLogin += OnClientLogin;
  114. }
  115. public void RemoveRegion(Scene scene)
  116. {
  117. if (!m_groupMessagingEnabled)
  118. return;
  119. if (m_debugEnabled) m_log.DebugFormat("[GROUPS-MESSAGING]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name);
  120. m_sceneList.Remove(scene);
  121. }
  122. public void Close()
  123. {
  124. if (!m_groupMessagingEnabled)
  125. return;
  126. if (m_debugEnabled) m_log.Debug("[GROUPS-MESSAGING]: Shutting down GroupsMessagingModule module.");
  127. foreach (Scene scene in m_sceneList)
  128. {
  129. scene.EventManager.OnNewClient -= OnNewClient;
  130. scene.EventManager.OnIncomingInstantMessage -= OnGridInstantMessage;
  131. }
  132. m_sceneList.Clear();
  133. m_groupData = null;
  134. m_msgTransferModule = null;
  135. }
  136. public Type ReplaceableInterface
  137. {
  138. get { return null; }
  139. }
  140. public string Name
  141. {
  142. get { return "GroupsMessagingModule"; }
  143. }
  144. #endregion
  145. #region ISharedRegionModule Members
  146. public void PostInitialise()
  147. {
  148. // NoOp
  149. }
  150. #endregion
  151. /// <summary>
  152. /// Not really needed, but does confirm that the group exists.
  153. /// </summary>
  154. public bool StartGroupChatSession(UUID agentID, UUID groupID)
  155. {
  156. if (m_debugEnabled)
  157. m_log.DebugFormat("[GROUPS-MESSAGING]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name);
  158. GroupRecord groupInfo = m_groupData.GetGroupRecord(agentID, groupID, null);
  159. if (groupInfo != null)
  160. {
  161. return true;
  162. }
  163. else
  164. {
  165. return false;
  166. }
  167. }
  168. public void SendMessageToGroup(GridInstantMessage im, UUID groupID)
  169. {
  170. if (m_debugEnabled)
  171. m_log.DebugFormat("[GROUPS-MESSAGING]: {0} called", System.Reflection.MethodBase.GetCurrentMethod().Name);
  172. foreach (GroupMembersData member in m_groupData.GetGroupMembers(UUID.Zero, groupID))
  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(EventQueueHelper.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. }