From 8ded92262edd9a9702bc1c8a694e11b2d2d7ffa2 Mon Sep 17 00:00:00 2001 From: Jim Radford Date: Sat, 5 Jan 2008 23:06:19 +0000 Subject: [PATCH] * Implements Group Chat * Adds Example TestClient command imgroup * Adds convenience methods to InternalDictionary git-svn-id: http://libopenmetaverse.googlecode.com/svn/trunk@1597 52acb1d6-8a22-11de-b505-999d5b087335 --- libsecondlife/AgentManager.cs | 222 +++++++++++++++++- libsecondlife/InternalDictionary.cs | 32 +++ .../Commands/Communication/IMGroupCommand.cs | 61 +++++ .../examples/TestClient/TestClient.cs | 6 +- .../examples/TestClient/TestClient.csproj | 1 + 5 files changed, 313 insertions(+), 9 deletions(-) create mode 100644 libsecondlife/examples/TestClient/Commands/Communication/IMGroupCommand.cs diff --git a/libsecondlife/AgentManager.cs b/libsecondlife/AgentManager.cs index cfa055d8..36c62520 100644 --- a/libsecondlife/AgentManager.cs +++ b/libsecondlife/AgentManager.cs @@ -681,6 +681,15 @@ namespace libsecondlife /// Simulator agent is now currently occupying public delegate void RegionCrossedCallback(Simulator oldSim, Simulator newSim); + /// + /// Fired when group chat session confirmed joined + /// LLUUID of Session (groups UUID) + public delegate void GroupChatJoined(LLUUID groupChatSessionID); + + /// Fired when agent group chat session terminated + /// LLUUID of Session (groups UUID) + public delegate void GroupChatLeft(LLUUID groupchatSessionID); + /// Callback for incoming chat packets public event ChatCallback OnChat; /// Callback for pop-up dialogs from scripts @@ -705,6 +714,10 @@ namespace libsecondlife public event MeanCollisionCallback OnMeanCollision; /// Callback for the agent moving in to a neighboring sim public event RegionCrossedCallback OnRegionCrossed; + /// Callback for when agent is confirmed joined group chat session. + public event GroupChatJoined OnGroupChatJoin; + /// Callback for when agent is confirmed to have left group chat session. + public event GroupChatLeft OnGroupChatLeft; #endregion @@ -885,7 +898,7 @@ namespace libsecondlife private float health; private int balance; private LLUUID activeGroup; - + private InternalDictionary> GroupChatSessions = new InternalDictionary>(); #endregion Private Members /// @@ -933,7 +946,12 @@ namespace libsecondlife Client.Network.RegisterCallback(PacketType.CrossedRegion, new NetworkManager.PacketCallback(CrossedRegionHandler)); // CAPS callbacks Client.Network.RegisterEventCallback("EstablishAgentCommunication", new Caps.EventQueueCallback(EstablishAgentCommunicationEventHandler)); - + // Incoming Group Chat + Client.Network.RegisterEventCallback("ChatterBoxInvitation", new Caps.EventQueueCallback(ChatterBoxInvitationHandler)); + // Outgoing Group Chat Reply + Client.Network.RegisterEventCallback("ChatterBoxSessionEventReply", new Caps.EventQueueCallback(ChatterBoxSessionEventHandler)); + Client.Network.RegisterEventCallback("ChatterBoxSessionStartReply", new Caps.EventQueueCallback(ChatterBoxSessionStartReplyHandler)); + Client.Network.RegisterEventCallback("ChatterBoxSessionAgentListUpdates", new Caps.EventQueueCallback(ChatterBoxSessionAgentListReplyHandler)); // Login Client.Network.RegisterLoginResponseCallback(new NetworkManager.LoginResponseCallback(Network_OnLoginResponse)); } @@ -1091,15 +1109,72 @@ namespace libsecondlife /// Text message being sent /// This does not appear to function with groups the agent is not in public void InstantMessageGroup(string fromName, LLUUID groupUUID, string message) + { + lock (GroupChatSessions.Dictionary) + if (GroupChatSessions.ContainsKey(groupUUID)) + { + ImprovedInstantMessagePacket im = new ImprovedInstantMessagePacket(); + + im.AgentData.AgentID = Client.Self.AgentID; + im.AgentData.SessionID = Client.Self.SessionID; + im.MessageBlock.Dialog = (byte)InstantMessageDialog.SessionSend; + im.MessageBlock.FromAgentName = Helpers.StringToField(fromName); + im.MessageBlock.FromGroup = false; + im.MessageBlock.Message = Helpers.StringToField(message); + im.MessageBlock.Offline = 0; + im.MessageBlock.ID = groupUUID; + im.MessageBlock.ToAgentID = groupUUID; + im.MessageBlock.Position = LLVector3.Zero; + im.MessageBlock.RegionID = LLUUID.Zero; + im.MessageBlock.BinaryBucket = Helpers.StringToField("\0"); + + Client.Network.SendPacket(im); + } + else + { + Client.Log("No Active group chat session appears to exist, use RequestJoinGroupChat() to join one", + Helpers.LogLevel.Error); + } + } + + /// + /// Send a request to join a group chat session + /// + /// UUID of Group + public void RequestJoinGroupChat(LLUUID groupUUID) { ImprovedInstantMessagePacket im = new ImprovedInstantMessagePacket(); im.AgentData.AgentID = Client.Self.AgentID; im.AgentData.SessionID = Client.Self.SessionID; - im.MessageBlock.Dialog = (byte)InstantMessageDialog.SessionSend; - im.MessageBlock.FromAgentName = Helpers.StringToField(fromName); + im.MessageBlock.Dialog = (byte)InstantMessageDialog.SessionGroupStart; + im.MessageBlock.FromAgentName = Helpers.StringToField(Client.Self.Name); im.MessageBlock.FromGroup = false; - im.MessageBlock.Message = Helpers.StringToField(message); + im.MessageBlock.Message = new byte[0]; + im.MessageBlock.Offline = 0; + im.MessageBlock.ID = groupUUID; + im.MessageBlock.ToAgentID = groupUUID; + im.MessageBlock.BinaryBucket = new byte[0]; + im.MessageBlock.Position = LLVector3.Zero; + im.MessageBlock.RegionID = LLUUID.Zero; + + Client.Network.SendPacket(im); + } + /// + /// Request self terminates group chat. This will stop Group IM's from showing up + /// until session is rejoined or expires. + /// + /// UUID of Group + public void RequestLeaveGroupChat(LLUUID groupUUID) + { + ImprovedInstantMessagePacket im = new ImprovedInstantMessagePacket(); + + im.AgentData.AgentID = Client.Self.AgentID; + im.AgentData.SessionID = Client.Self.SessionID; + im.MessageBlock.Dialog = (byte)InstantMessageDialog.SessionDrop; + im.MessageBlock.FromAgentName = Helpers.StringToField(Client.Self.Name); + im.MessageBlock.FromGroup = false; + im.MessageBlock.Message = new byte[0]; im.MessageBlock.Offline = 0; im.MessageBlock.ID = groupUUID; im.MessageBlock.ToAgentID = groupUUID; @@ -1107,7 +1182,6 @@ namespace libsecondlife im.MessageBlock.Position = LLVector3.Zero; im.MessageBlock.RegionID = LLUUID.Zero; - // Send the message Client.Network.SendPacket(im); } @@ -2197,6 +2271,7 @@ namespace libsecondlife { StructuredData.LLSDMap body = (StructuredData.LLSDMap)llsd; + if (Client.Settings.MULTIPLE_SIMS && body.ContainsKey("sim-ip-and-port")) { string ipAndPort = body["sim-ip-and-port"].AsString(); @@ -2465,6 +2540,141 @@ namespace libsecondlife } } + /// + /// Group Chat event handler + /// + /// + /// + /// + private void ChatterBoxSessionEventHandler(string capsKey, LLSD llsd, Simulator simulator) + { + // TODO: this appears to occur when you try and initiate group chat with an unopened session + // + // Key=ChatterBoxSessionEventReply + // llsd={ + // ("error": "generic") + // ("event": "message") + // ("session_id": "3dafea18-cda1-9813-d5f1-fd3de6b13f8c") // group uuid + // ("success": "0")} + //LLSDMap map = (LLSDMap)llsd; + //LLUUID groupUUID = map["session_id"].AsUUID(); + //Console.WriteLine("SessionEvent: Key={0} llsd={1}", capsKey, llsd.ToString()); + } + + /// + /// Response from request to join a group chat + /// + /// + /// + /// + private void ChatterBoxSessionStartReplyHandler(string capsKey, LLSD llsd, Simulator simulator) + { + LLSDMap map = (LLSDMap)llsd; + LLUUID sessionID = map["session_id"].AsUUID(); + + if (map["success"].AsBoolean()) + { + LLSDArray agentlist = (LLSDArray)map["agents"]; + List agents = new List(); + foreach (LLSD id in agentlist) + agents.Add(id.AsUUID()); + + lock (GroupChatSessions.Dictionary) + { + if (GroupChatSessions.ContainsKey(sessionID)) + GroupChatSessions.Dictionary[sessionID] = agents; + else + GroupChatSessions.Add(sessionID, agents); + } + } + + if (OnGroupChatJoin != null) + { + try { OnGroupChatJoin(sessionID); } + catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); } + } + } + + /// + /// Someone joined or left group chat + /// + /// + /// + /// + private void ChatterBoxSessionAgentListReplyHandler(string capsKey, LLSD llsd, Simulator simulator) + { + LLSDMap map = (LLSDMap)llsd; + LLUUID sessionID = map["session_id"].AsUUID(); + LLSDMap update = (LLSDMap)map["updates"]; + string errormsg = map["error"].AsString(); + + //if (errormsg.Equals("already in session")) + // return; + + foreach (KeyValuePair kvp in update) + { + if (kvp.Value.Equals("ENTER")) + { + lock (GroupChatSessions.Dictionary) + { + if (!GroupChatSessions.Dictionary[sessionID].Contains((LLUUID)kvp.Key)) + GroupChatSessions.Dictionary[sessionID].Add((LLUUID)kvp.Key); + } + } + else if (kvp.Value.Equals("LEAVE")) + { + lock (GroupChatSessions.Dictionary) + { + if (GroupChatSessions.Dictionary[sessionID].Contains((LLUUID)kvp.Key)) + GroupChatSessions.Dictionary[sessionID].Remove((LLUUID)kvp.Key); + + // we left session, remove from dictionary + if (kvp.Key.Equals(Client.Self.id) && OnGroupChatLeft != null) + { + GroupChatSessions.Dictionary.Remove(sessionID); + OnGroupChatLeft(sessionID); + } + } + } + } + } + + /// + /// Group Chat Request + /// + /// + /// + /// + private void ChatterBoxInvitationHandler(string capsKey, LLSD llsd, Simulator simulator) + { + if (OnInstantMessage != null) + { + LLSDMap map = (LLSDMap)llsd; + LLSDMap im = (LLSDMap)map["instantmessage"]; + LLSDMap agent = (LLSDMap)im["agent_params"]; + LLSDMap msg = (LLSDMap)im["message_params"]; + LLSDMap msgdata = (LLSDMap)msg["data"]; + + InstantMessage message = new InstantMessage(); + + message.FromAgentID = map["from_id"].AsUUID(); + message.FromAgentName = map["from_name"].AsString(); + message.ToAgentID = msg["to_id"].AsString(); + message.ParentEstateID = (uint)msg["parent_estate_id"].AsInteger(); + message.RegionID = msg["region_id"].AsUUID(); + message.Position.FromLLSD(msg["position"]); + message.Dialog = (InstantMessageDialog)msgdata["type"].AsInteger(); + message.GroupIM = true; + message.IMSessionID = map["session_id"].AsUUID(); + message.Timestamp = new DateTime(msgdata["timestamp"].AsInteger()); + message.Message = msg["message"].AsString(); + message.Offline = (InstantMessageOnline)msg["offline"].AsInteger(); + message.BinaryBucket = msg["binary_bucket"].AsBinary(); + + try { OnInstantMessage(message, simulator); } + catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); } + } + } #endregion Packet Handlers } } diff --git a/libsecondlife/InternalDictionary.cs b/libsecondlife/InternalDictionary.cs index f61df9a1..6b6dab7a 100644 --- a/libsecondlife/InternalDictionary.cs +++ b/libsecondlife/InternalDictionary.cs @@ -92,5 +92,37 @@ namespace libsecondlife } } } + + public bool ContainsKey(TKey key) + { + return Dictionary.ContainsKey(key); + } + + public bool ContainsValue(TValue value) + { + return Dictionary.ContainsValue(value); + } + + internal void Add(TKey key, TValue value) + { + Dictionary.Add(key, value); + } + + internal bool Remove(TKey key) + { + return Dictionary.Remove(key); + } + + internal void SafeAdd(TKey key, TValue value) + { + lock (Dictionary) + Dictionary.Add(key, value); + } + + internal bool SafeRemove(TKey key) + { + lock (Dictionary) + return Dictionary.Remove(key); + } } } diff --git a/libsecondlife/examples/TestClient/Commands/Communication/IMGroupCommand.cs b/libsecondlife/examples/TestClient/Commands/Communication/IMGroupCommand.cs new file mode 100644 index 00000000..a2265c09 --- /dev/null +++ b/libsecondlife/examples/TestClient/Commands/Communication/IMGroupCommand.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using libsecondlife; +using libsecondlife.Packets; + +namespace libsecondlife.TestClient +{ + public class ImGroupCommand : Command + { + LLUUID ToGroupID = LLUUID.Zero; + ManualResetEvent WaitForSessionStart = new ManualResetEvent(false); + public ImGroupCommand(TestClient testClient) + { + + Name = "imgroup"; + Description = "Send an instant message to a group. Usage: imgroup [group_uuid] [message]"; + } + + public override string Execute(string[] args, LLUUID fromAgentID) + { + if (args.Length < 2) + return "Usage: imgroup [group_uuid] [message]"; + + + + if (LLUUID.TryParse(args[0], out ToGroupID)) + { + string message = String.Empty; + for (int ct = 1; ct < args.Length; ct++) + message += args[ct] + " "; + message = message.TrimEnd(); + if (message.Length > 1023) message = message.Remove(1023); + + Client.Self.OnGroupChatJoin += new AgentManager.GroupChatJoined(Self_OnGroupChatJoin); + Client.Self.RequestJoinGroupChat(ToGroupID); + WaitForSessionStart.Reset(); + if (WaitForSessionStart.WaitOne(10000, false)) + { + Client.Self.InstantMessageGroup(ToGroupID, message); + } + else + { + return "Timeout waiting for group session start"; + } + Client.Self.OnGroupChatJoin -= new AgentManager.GroupChatJoined(Self_OnGroupChatJoin); + Client.Self.RequestLeaveGroupChat(ToGroupID); + return "Instant Messaged group " + ToGroupID.ToString() + " with message: " + message; + } + else + { + return "failed to instant message group"; + } + } + + void Self_OnGroupChatJoin(LLUUID groupChatSessionID) + { + WaitForSessionStart.Set(); + } + } +} diff --git a/libsecondlife/examples/TestClient/TestClient.cs b/libsecondlife/examples/TestClient/TestClient.cs index d700dd02..747f6c8b 100644 --- a/libsecondlife/examples/TestClient/TestClient.cs +++ b/libsecondlife/examples/TestClient/TestClient.cs @@ -196,7 +196,7 @@ namespace libsecondlife.TestClient if (im.FromAgentID != MasterKey) { // Received an IM from someone that is not the bot's master, ignore - Console.WriteLine(" {1} (not master): {2} (@{3}:{4})", im.Dialog, im.FromAgentName, im.Message, + Console.WriteLine("<{0} ({1})> {2} (not master): {3} (@{4}:{5})", im.GroupIM ? "GroupIM" : "IM", im.Dialog, im.FromAgentName, im.Message, im.RegionID, im.Position); return; } @@ -204,13 +204,13 @@ namespace libsecondlife.TestClient else if (GroupMembers != null && !GroupMembers.ContainsKey(im.FromAgentID)) { // Received an IM from someone outside the bot's group, ignore - Console.WriteLine(" {1} (not in group): {2} (@{3}:{4})", im.Dialog, im.FromAgentName, + Console.WriteLine("<{0} ({1})> {2} (not in group): {3} (@{4}:{5})", im.GroupIM ? "GroupIM" : "IM", im.Dialog, im.FromAgentName, im.Message, im.RegionID, im.Position); return; } // Received an IM from someone that is authenticated - Console.WriteLine(" {1}: {2} (@{3}:{4})", im.Dialog, im.FromAgentName, im.Message, im.RegionID, im.Position); + Console.WriteLine("<{0} ({1})> {2}: {3} (@{4}:{5})", im.GroupIM ? "GroupIM" : "IM", im.Dialog, im.FromAgentName, im.Message, im.RegionID, im.Position); if (im.Dialog == InstantMessageDialog.RequestTeleport) { diff --git a/libsecondlife/examples/TestClient/TestClient.csproj b/libsecondlife/examples/TestClient/TestClient.csproj index 7b6bdfa3..ea036b6a 100644 --- a/libsecondlife/examples/TestClient/TestClient.csproj +++ b/libsecondlife/examples/TestClient/TestClient.csproj @@ -43,6 +43,7 @@ +