using System; using System.Linq; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Threading.Tasks; using Newtonsoft.Json; using LeanCloud.Realtime.Protocol; using LeanCloud.Storage.Internal; using LeanCloud.Storage.Internal.Codec; using LeanCloud.Common; namespace LeanCloud.Realtime.Internal.Controller { internal class LCIMConversationController : LCIMController { internal LCIMConversationController(LCIMClient client) : base(client) { } #region 内部接口 /// /// 创建对话 /// /// /// /// /// /// /// /// /// internal async Task CreateConv( IEnumerable members = null, string name = null, bool transient = false, bool unique = true, bool temporary = false, int temporaryTtl = 86400, Dictionary properties = null) { GenericCommand request = Client.NewCommand(CommandType.Conv, OpType.Start); ConvCommand conv = new ConvCommand { Transient = transient, Unique = unique, }; if (members != null) { conv.M.AddRange(members); } if (!string.IsNullOrEmpty(name)) { conv.N = name; } if (temporary) { conv.TempConv = temporary; conv.TempConvTTL = temporaryTtl; } if (properties != null) { conv.Attr = new JsonObjectMessage { Data = JsonConvert.SerializeObject(LCEncoder.Encode(properties)) }; } if (Client.SignatureFactory != null) { LCIMSignature signature = Client.SignatureFactory.CreateStartConversationSignature(Client.Id, members); conv.S = signature.Signature; conv.T = signature.Timestamp; conv.N = signature.Nonce; } request.ConvMessage = conv; GenericCommand response = await Connection.SendRequest(request); string convId = response.ConvMessage.Cid; if (!Client.ConversationDict.TryGetValue(convId, out LCIMConversation conversation)) { if (transient) { conversation = new LCIMChatRoom(Client); } else if (temporary) { conversation = new LCIMTemporaryConversation(Client); } else if (properties != null && properties.ContainsKey("system")) { conversation = new LCIMServiceConversation(Client); } else { conversation = new LCIMConversation(Client); } Client.ConversationDict[convId] = conversation; } // 合并请求数据 conversation.Id = convId; conversation.Unique = unique; conversation.UniqueId = response.ConvMessage.UniqueId; conversation.Name = name; conversation.CreatorId = Client.Id; conversation.ids = members != null ? new HashSet(members) : new HashSet(); conversation.CreatedAt = DateTime.Parse(response.ConvMessage.Cdate); conversation.UpdatedAt = conversation.CreatedAt; return conversation; } /// /// 查询成员数量 /// /// /// internal async Task GetMembersCount(string convId) { ConvCommand conv = new ConvCommand { Cid = convId, }; GenericCommand command = Client.NewCommand(CommandType.Conv, OpType.Count); command.ConvMessage = conv; GenericCommand response = await Connection.SendRequest(command); return response.ConvMessage.Count; } /// /// 标记对话的消息已读 /// /// /// /// internal async Task Read(string convId, LCIMMessage message) { ReadCommand read = new ReadCommand(); ReadTuple tuple = new ReadTuple { Cid = convId, Mid = message.Id, Timestamp = message.SentTimestamp }; read.Convs.Add(tuple); GenericCommand request = Client.NewCommand(CommandType.Read, OpType.Open); request.ReadMessage = read; await Client.Connection.SendRequest(request); } /// /// 更新对话属性 /// /// /// /// internal async Task> UpdateInfo(string convId, Dictionary attributes) { ConvCommand conv = new ConvCommand { Cid = convId, }; conv.Attr = new JsonObjectMessage { Data = JsonConvert.SerializeObject(attributes) }; GenericCommand request = Client.NewCommand(CommandType.Conv, OpType.Update); request.ConvMessage = conv; GenericCommand response = await Client.Connection.SendRequest(request); JsonObjectMessage attr = response.ConvMessage.AttrModified; // 更新自定义属性 if (attr != null) { Dictionary updatedAttr = JsonConvert.DeserializeObject>(attr.Data); return updatedAttr; } return null; } /// /// 增加成员 /// /// /// /// internal async Task AddMembers(string convId, IEnumerable clientIds) { ConvCommand conv = new ConvCommand { Cid = convId, }; conv.M.AddRange(clientIds); // 签名参数 if (Client.SignatureFactory != null) { LCIMSignature signature = Client.SignatureFactory.CreateConversationSignature(convId, Client.Id, clientIds, LCIMSignatureAction.Invite); conv.S = signature.Signature; conv.T = signature.Timestamp; conv.N = signature.Nonce; } GenericCommand request = Client.NewCommand(CommandType.Conv, OpType.Add); request.ConvMessage = conv; GenericCommand response = await Client.Connection.SendRequest(request); List allowedIds = response.ConvMessage.AllowedPids.ToList(); List errors = response.ConvMessage.FailedPids.ToList(); return NewPartiallySuccessResult(allowedIds, errors); } /// /// 移除成员 /// /// /// /// internal async Task RemoveMembers(string convId, IEnumerable removeIds) { ConvCommand conv = new ConvCommand { Cid = convId, }; conv.M.AddRange(removeIds); // 签名参数 if (Client.SignatureFactory != null) { LCIMSignature signature = Client.SignatureFactory.CreateConversationSignature(convId, Client.Id, removeIds, LCIMSignatureAction.Kick); conv.S = signature.Signature; conv.T = signature.Timestamp; conv.N = signature.Nonce; } GenericCommand request = Client.NewCommand(CommandType.Conv, OpType.Remove); request.ConvMessage = conv; GenericCommand response = await Client.Connection.SendRequest(request); List allowedIds = response.ConvMessage.AllowedPids.ToList(); List errors = response.ConvMessage.FailedPids.ToList(); return NewPartiallySuccessResult(allowedIds, errors); } /// /// 静音 /// /// /// internal async Task Mute(string convId) { ConvCommand conv = new ConvCommand { Cid = convId }; GenericCommand request = Client.NewCommand(CommandType.Conv, OpType.Mute); request.ConvMessage = conv; await Client.Connection.SendRequest(request); } /// /// 解除静音 /// /// /// internal async Task Unmute(string convId) { ConvCommand conv = new ConvCommand { Cid = convId }; GenericCommand request = Client.NewCommand(CommandType.Conv, OpType.Unmute); request.ConvMessage = conv; await Client.Connection.SendRequest(request); } /// /// 禁言用户 /// /// /// /// internal async Task MuteMembers(string convId, IEnumerable clientIds) { if (clientIds == null || clientIds.Count() == 0) { throw new ArgumentNullException(nameof(clientIds)); } ConvCommand conv = new ConvCommand { Cid = convId }; conv.M.AddRange(clientIds); GenericCommand request = Client.NewCommand(CommandType.Conv, OpType.AddShutup); request.ConvMessage = conv; GenericCommand response = await Client.Connection.SendRequest(request); return NewPartiallySuccessResult(response.ConvMessage.AllowedPids, response.ConvMessage.FailedPids); } /// /// 解除用户禁言 /// /// /// /// internal async Task UnmuteMembers(string convId, IEnumerable clientIds) { ConvCommand conv = new ConvCommand { Cid = convId }; conv.M.AddRange(clientIds); GenericCommand request = Client.NewCommand(CommandType.Conv, OpType.Remove); request.ConvMessage = conv; GenericCommand response = await Client.Connection.SendRequest(request); return NewPartiallySuccessResult(response.ConvMessage.AllowedPids, response.ConvMessage.FailedPids); } /// /// 拉黑成员 /// /// /// /// internal async Task BlockMembers(string convId, IEnumerable clientIds) { BlacklistCommand blacklist = new BlacklistCommand { SrcCid = convId, }; blacklist.ToPids.AddRange(clientIds); if (Client.SignatureFactory != null) { LCIMSignature signature = Client.SignatureFactory.CreateBlacklistSignature(convId, Client.Id, clientIds, LCIMSignatureAction.ConversationBlockClients); blacklist.S = signature.Signature; blacklist.T = signature.Timestamp; blacklist.N = signature.Nonce; } GenericCommand request = Client.NewCommand(CommandType.Blacklist, OpType.Block); request.BlacklistMessage = blacklist; GenericCommand response = await Client.Connection.SendRequest(request); return NewPartiallySuccessResult(response.BlacklistMessage.AllowedPids, response.BlacklistMessage.FailedPids); } /// /// 移除成员黑名单 /// /// /// /// internal async Task UnblockMembers(string convId, IEnumerable clientIds) { BlacklistCommand blacklist = new BlacklistCommand { SrcCid = convId, }; blacklist.ToPids.AddRange(clientIds); if (Client.SignatureFactory != null) { LCIMSignature signature = Client.SignatureFactory.CreateBlacklistSignature(convId, Client.Id, clientIds, LCIMSignatureAction.ConversationUnblockClients); blacklist.S = signature.Signature; blacklist.T = signature.Timestamp; blacklist.N = signature.Nonce; } GenericCommand request = Client.NewCommand(CommandType.Blacklist, OpType.Unblock); request.BlacklistMessage = blacklist; GenericCommand response = await Client.Connection.SendRequest(request); return NewPartiallySuccessResult(response.BlacklistMessage.AllowedPids, response.BlacklistMessage.FailedPids); } /// /// 修改成员角色 /// /// /// /// /// internal async Task UpdateMemberRole(string convId, string memberId, string role) { ConvCommand conv = new ConvCommand { Cid = convId, TargetClientId = memberId, Info = new ConvMemberInfo { Pid = memberId, Role = role } }; GenericCommand request = Client.NewCommand(CommandType.Conv, OpType.MemberInfoUpdate); request.ConvMessage = conv; GenericCommand response = await Client.Connection.SendRequest(request); } /// /// 获取所有成员角色 /// /// /// internal async Task> GetAllMemberInfo(string convId) { string path = "classes/_ConversationMemberInfo"; string token = await Client.SessionController.GetToken(); Dictionary headers = new Dictionary { { "X-LC-IM-Session-Token", token } }; Dictionary queryParams = new Dictionary { { "client_id", Client.Id }, { "cid", convId } }; Dictionary response = await LCApplication.HttpClient.Get>(path, headers: headers, queryParams: queryParams); List results = response["results"] as List; return results.Select(item => { Dictionary memberInfo = item as Dictionary; return new LCIMConversationMemberInfo { ConversationId = memberInfo["cid"] as string, MemberId = memberInfo["clientId"] as string, Role = memberInfo["role"] as string }; }).ToList().AsReadOnly(); } /// /// 查询禁言成员 /// /// /// /// /// internal async Task QueryMutedMembers(string convId, int limit = 10, string next = null) { ConvCommand conv = new ConvCommand { Cid = convId, Limit = limit, Next = next }; GenericCommand request = Client.NewCommand(CommandType.Conv, OpType.QueryShutup); request.ConvMessage = conv; GenericCommand response = await Client.Connection.SendRequest(request); return new LCIMPageResult { Results = new ReadOnlyCollection(response.ConvMessage.M), Next = response.ConvMessage.Next }; } /// /// 查询黑名单用户 /// /// /// /// /// internal async Task QueryBlockedMembers(string convId, int limit = 10, string next = null) { BlacklistCommand black = new BlacklistCommand { SrcCid = convId, Limit = limit, Next = next }; GenericCommand request = Client.NewCommand(CommandType.Blacklist, OpType.Query); request.BlacklistMessage = black; GenericCommand response = await Client.Connection.SendRequest(request); return new LCIMPageResult { Results = new ReadOnlyCollection(response.BlacklistMessage.BlockedPids), Next = response.BlacklistMessage.Next }; } /// /// 查找 /// /// /// internal async Task> Find(LCIMConversationQuery query) { GenericCommand command = new GenericCommand { Cmd = CommandType.Conv, Op = OpType.Query, AppId = LCApplication.AppId, PeerId = Client.Id, }; ConvCommand convMessage = new ConvCommand(); string where = query.Condition.BuildWhere(); if (!string.IsNullOrEmpty(where)) { try { convMessage.Where = new JsonObjectMessage { Data = where }; } catch (Exception e) { LCLogger.Error(e); } } command.ConvMessage = convMessage; GenericCommand response = await Connection.SendRequest(command); JsonObjectMessage results = response.ConvMessage.Results; List convs = JsonConvert.DeserializeObject>(results.Data, new LCJsonConverter()); return convs.Select(item => { Dictionary conv = item as Dictionary; string convId = conv["objectId"] as string; if (!Client.ConversationDict.TryGetValue(convId, out LCIMConversation conversation)) { // 解析是哪种类型的对话 if (conv.TryGetValue("tr", out object transient) && (bool)transient == true) { conversation = new LCIMChatRoom(Client); } else if (conv.ContainsKey("tempConv") && conv.ContainsKey("tempConvTTL")) { conversation = new LCIMTemporaryConversation(Client); } else if (conv.TryGetValue("sys", out object sys) && (bool)sys == true) { conversation = new LCIMServiceConversation(Client); } else { conversation = new LCIMConversation(Client); } Client.ConversationDict[convId] = conversation; } conversation.MergeFrom(conv); return conversation; }).ToList().AsReadOnly(); } internal async Task> GetTemporaryConversations(IEnumerable convIds) { if (convIds == null || convIds.Count() == 0) { return null; } ConvCommand convMessage = new ConvCommand(); convMessage.TempConvIds.AddRange(convIds); GenericCommand request = Client.NewCommand(CommandType.Conv, OpType.Query); request.ConvMessage = convMessage; GenericCommand response = await Connection.SendRequest(request); JsonObjectMessage results = response.ConvMessage.Results; List convs = JsonConvert.DeserializeObject>(results.Data, new LCJsonConverter()); List convList = convs.Select(item => { LCIMTemporaryConversation temporaryConversation = new LCIMTemporaryConversation(Client); temporaryConversation.MergeFrom(item as Dictionary); return temporaryConversation; }).ToList(); return convList; } internal async Task FetchReciptTimestamp(string convId) { ConvCommand convCommand = new ConvCommand { Cid = convId }; GenericCommand request = Client.NewCommand(CommandType.Conv, OpType.MaxRead); request.ConvMessage = convCommand; GenericCommand response = await Connection.SendRequest(request); convCommand = response.ConvMessage; LCIMConversation conversation = await Client.GetOrQueryConversation(convCommand.Cid); conversation.LastDeliveredTimestamp = convCommand.MaxAckTimestamp; conversation.LastReadTimestamp = convCommand.MaxReadTimestamp; } private LCIMPartiallySuccessResult NewPartiallySuccessResult(IEnumerable succesfulIds, IEnumerable errors) { LCIMPartiallySuccessResult result = new LCIMPartiallySuccessResult { SuccessfulClientIdList = succesfulIds.ToList() }; if (errors != null) { result.FailureList = new List(); foreach (ErrorCommand error in errors) { result.FailureList.Add(new LCIMOperationFailure(error)); } } return result; } #endregion #region 消息处理 internal override async Task OnNotification(GenericCommand notification) { ConvCommand convMessage = notification.ConvMessage; switch (notification.Op) { case OpType.Joined: await OnJoined(convMessage); break; case OpType.MembersJoined: await OnMembersJoined(convMessage); break; case OpType.Left: await OnLeft(convMessage); break; case OpType.MembersLeft: await OnMemberLeft(convMessage); break; case OpType.Blocked: await OnBlocked(convMessage); break; case OpType.MembersBlocked: await OnMembersBlocked(convMessage); break; case OpType.MembersUnblocked: await OnMembersUnblocked(convMessage); break; case OpType.Shutuped: await OnMuted(convMessage); break; case OpType.MembersShutuped: await OnMembersMuted(convMessage); break; case OpType.MembersUnshutuped: await OnMembersUnmuted(convMessage); break; case OpType.Updated: await OnPropertiesUpdated(convMessage); break; case OpType.MemberInfoChanged: await OnMemberInfoChanged(convMessage); break; default: break; } } /// /// 当前用户加入会话 /// /// /// private async Task OnJoined(ConvCommand convMessage) { LCIMConversation conversation = await Client.GetOrQueryConversation(convMessage.Cid); Client.OnInvited?.Invoke(conversation, convMessage.InitBy); } /// /// 有用户加入会话 /// /// /// private async Task OnMembersJoined(ConvCommand convMessage) { LCIMConversation conversation = await Client.GetOrQueryConversation(convMessage.Cid); ReadOnlyCollection joinedIds = new ReadOnlyCollection(convMessage.M); conversation.ids.Union(joinedIds); Client.OnMembersJoined?.Invoke(conversation, joinedIds, convMessage.InitBy); } /// /// 当前用户离开会话 /// /// /// private async Task OnLeft(ConvCommand convMessage) { LCIMConversation conversation = await Client.GetOrQueryConversation(convMessage.Cid); Client.OnKicked?.Invoke(conversation, convMessage.InitBy); // TODO 从内存中清除对话 } /// /// 有成员离开会话 /// /// /// private async Task OnMemberLeft(ConvCommand convMessage) { LCIMConversation conversation = await Client.GetOrQueryConversation(convMessage.Cid); ReadOnlyCollection leftIdList = new ReadOnlyCollection(convMessage.M); conversation.ids.RemoveWhere(item => leftIdList.Contains(item)); Client.OnMembersLeft?.Invoke(conversation, leftIdList, convMessage.InitBy); } /// /// 当前用户被禁言 /// /// /// private async Task OnMuted(ConvCommand convMessage) { LCIMConversation conversation = await Client.GetOrQueryConversation(convMessage.Cid); Client.OnMuted?.Invoke(conversation, convMessage.InitBy); } /// /// 有成员被禁言 /// /// /// private async Task OnMembersMuted(ConvCommand convMessage) { LCIMConversation conversation = await Client.GetOrQueryConversation(convMessage.Cid); ReadOnlyCollection mutedMemberIds = new ReadOnlyCollection(convMessage.M); conversation.mutedIds.Union(mutedMemberIds); Client.OnMembersMuted?.Invoke(conversation, mutedMemberIds, convMessage.InitBy); } /// /// 有成员被解除禁言 /// /// /// private async Task OnMembersUnmuted(ConvCommand convMessage) { LCIMConversation conversation = await Client.GetOrQueryConversation(convMessage.Cid); ReadOnlyCollection unmutedMemberIds = new ReadOnlyCollection(convMessage.M); conversation.mutedIds.RemoveWhere(id => unmutedMemberIds.Contains(id)); Client.OnMembersUnmuted?.Invoke(conversation, unmutedMemberIds, convMessage.InitBy); } /// /// 当前用户被拉黑 /// /// /// private async Task OnBlocked(ConvCommand convMessage) { LCIMConversation conversation = await Client.GetOrQueryConversation(convMessage.Cid); Client.OnBlocked?.Invoke(conversation, convMessage.InitBy); } /// /// 有用户被拉黑 /// /// /// private async Task OnMembersBlocked(ConvCommand convMessage) { LCIMConversation conversation = await Client.GetOrQueryConversation(convMessage.Cid); ReadOnlyCollection blockedMemberIds = convMessage.M.ToList().AsReadOnly(); Client.OnMembersBlocked?.Invoke(conversation, blockedMemberIds, convMessage.InitBy); } /// /// 有用户被移除黑名单 /// /// /// private async Task OnMembersUnblocked(ConvCommand convMessage) { LCIMConversation conversation = await Client.GetOrQueryConversation(convMessage.Cid); ReadOnlyCollection unblockedMemberIds = convMessage.M.ToList().AsReadOnly(); Client.OnMembersUnblocked?.Invoke(conversation, unblockedMemberIds, convMessage.InitBy); } /// /// 对话属性被修改 /// /// /// private async Task OnPropertiesUpdated(ConvCommand conv) { LCIMConversation conversation = await Client.GetOrQueryConversation(conv.Cid); Dictionary updatedAttr = JsonConvert.DeserializeObject>(conv.AttrModified.Data, new LCJsonConverter()); // 更新内存数据 conversation.MergeInfo(updatedAttr); Client.OnConversationInfoUpdated?.Invoke(conversation, new ReadOnlyDictionary(updatedAttr), conv.InitBy); } /// /// 用户角色被修改 /// /// /// private async Task OnMemberInfoChanged(ConvCommand conv) { LCIMConversation conversation = await Client.GetOrQueryConversation(conv.Cid); ConvMemberInfo memberInfo = conv.Info; Client.OnMemberInfoUpdated?.Invoke(conversation, memberInfo.Pid, memberInfo.Role, conv.InitBy); } #endregion } }