using LeanCloud; using LeanCloud.Storage.Internal; using LeanCloud.Realtime.Internal; using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Text; using System.Threading; using System.Threading.Tasks; namespace LeanCloud.Realtime { /// /// 代表一个实时通信的终端用户 /// public class AVIMClient { private readonly string clientId; private readonly AVRealtime _realtime; internal readonly object mutex = new object(); internal readonly object patchMutex = new object(); /// /// 一些可变的配置选项,便于应对各种需求场景 /// public struct Configuration { /// /// Gets or sets a value indicating whether this /// auto read. /// /// true if auto read; otherwise, false. public bool AutoRead { get; set; } } /// /// Gets or sets the current configuration. /// /// The current configuration. public Configuration CurrentConfiguration { get; set; } internal AVRealtime LinkedRealtime { get { return _realtime; } } /// /// 单点登录所使用的 Tag /// public string Tag { get; private set; } /// /// 客户端的标识,在一个 Application 内唯一。 /// public string ClientId { get { return clientId; } } //private EventHandler m_OnNoticeReceived; ///// ///// 接收到服务器的命令时触发的事件 ///// //public event EventHandler OnNoticeReceived //{ // add // { // m_OnNoticeReceived += value; // } // remove // { // m_OnNoticeReceived -= value; // } //} private int onMessageReceivedCount = 0; private EventHandler m_OnMessageReceived; /// /// 接收到聊天消息的事件通知 /// public event EventHandler OnMessageReceived { add { onMessageReceivedCount++; AVRealtime.PrintLog("AVIMClient.OnMessageReceived event add with " + onMessageReceivedCount + " times"); m_OnMessageReceived += value; } remove { onMessageReceivedCount--; AVRealtime.PrintLog("AVIMClient.OnMessageReceived event remove with" + onMessageReceivedCount + " times"); m_OnMessageReceived -= value; } } /// /// Occurs when on members joined. /// public event EventHandler OnMembersJoined; /// /// Occurs when on members left. /// public event EventHandler OnMembersLeft; /// /// Occurs when on kicked. /// public event EventHandler OnKicked; /// /// Occurs when on invited. /// public event EventHandler OnInvited; internal event EventHandler OnOfflineMessageReceived; private EventHandler m_OnSessionClosed; /// /// 当前打开的链接被迫关闭时触发的事件回调 /// 可能的原因有单点登录冲突,或者被 REST API 强制踢下线 /// public event EventHandler OnSessionClosed { add { m_OnSessionClosed += value; } remove { m_OnSessionClosed -= value; } } /// /// 创建 AVIMClient 对象 /// /// /// internal AVIMClient(string clientId, AVRealtime realtime) : this(clientId, null, realtime) { } /// /// /// /// /// /// internal AVIMClient(string clientId, string tag, AVRealtime realtime) { this.clientId = clientId; Tag = tag ?? tag; _realtime = realtime; #region sdk 强制在接收到消息之后一定要向服务端回发 ack var ackListener = new AVIMMessageListener(); ackListener.OnMessageReceived += AckListener_OnMessageReceieved; //this.RegisterListener(ackListener); #endregion #region 默认要为当前 client 绑定一个消息的监听器,用作消息的事件通知 var messageListener = new AVIMMessageListener(); messageListener.OnMessageReceived += MessageListener_OnMessageReceived; this.RegisterListener(messageListener); #endregion #region 默认要为当前 client 绑定一个 session close 的监听器,用来监测单点登录冲突的事件通知 var sessionListener = new SessionListener(); sessionListener.OnSessionClosed += SessionListener_OnSessionClosed; this.RegisterListener(sessionListener); #endregion #region 默认要为当前 client 监听 Ta 所出的对话中的人员变动的被动消息通知 var membersJoinedListener = new AVIMMembersJoinListener(); membersJoinedListener.OnMembersJoined += MembersJoinedListener_OnMembersJoined; this.RegisterListener(membersJoinedListener); var membersLeftListener = new AVIMMembersLeftListener(); membersLeftListener.OnMembersLeft += MembersLeftListener_OnMembersLeft; this.RegisterListener(membersLeftListener); var invitedListener = new AVIMInvitedListener(); invitedListener.OnInvited += InvitedListener_OnInvited; this.RegisterListener(invitedListener); var kickedListener = new AVIMKickedListener(); kickedListener.OnKicked += KickedListener_OnKicked; this.RegisterListener(kickedListener); #endregion #region 当前 client id 离线的时间内,TA 所在的对话产生的普通消息会以离线消息的方式送达到 TA 下一次登录的客户端 var offlineMessageListener = new OfflineMessageListener(); offlineMessageListener.OnOfflineMessageReceived += OfflineMessageListener_OnOfflineMessageReceived; this.RegisterListener(offlineMessageListener); #endregion #region 当前 client 离线期间内产生的未读消息可以通过之后调用 Conversation.SyncStateAsync 获取一下离线期间内的未读状态 var unreadListener = new ConversationUnreadListener(); this.RegisterListener(unreadListener); #endregion #region 消息补丁(修改或者撤回) var messagePatchListener = new MessagePatchListener(); messagePatchListener.OnReceived = (messages) => { foreach (var message in messages) { if (message is AVIMRecalledMessage) { m_OnMessageRecalled?.Invoke(this, new AVIMMessagePatchEventArgs(message)); } else { m_OnMessageUpdated?.Invoke(this, new AVIMMessagePatchEventArgs(message)); } } }; this.RegisterListener(messagePatchListener); #endregion #region configuration CurrentConfiguration = new Configuration() { AutoRead = true, }; #endregion } private void OfflineMessageListener_OnOfflineMessageReceived(object sender, AVIMMessageEventArgs e) { if (OnOfflineMessageReceived != null) { OnOfflineMessageReceived(this, e); } this.AckListener_OnMessageReceieved(sender, e); } private void KickedListener_OnKicked(object sender, AVIMOnKickedEventArgs e) { if (OnKicked != null) OnKicked(this, e); } private void InvitedListener_OnInvited(object sender, AVIMOnInvitedEventArgs e) { if (OnInvited != null) OnInvited(this, e); } private void MembersLeftListener_OnMembersLeft(object sender, AVIMOnMembersLeftEventArgs e) { if (OnMembersLeft != null) OnMembersLeft(this, e); } private void MembersJoinedListener_OnMembersJoined(object sender, AVIMOnMembersJoinedEventArgs e) { if (OnMembersJoined != null) OnMembersJoined(this, e); } private void SessionListener_OnSessionClosed(int arg1, string arg2, string arg3) { if (m_OnSessionClosed != null) { var args = new AVIMSessionClosedEventArgs() { Code = arg1, Reason = arg2, Detail = arg3 }; if (args.Code == 4115 || args.Code == 4111) { this._realtime.sessionConflict = true; } m_OnSessionClosed(this, args); } AVRealtime.PrintLog("SessionListener_OnSessionClosed invoked."); //this.LinkedRealtime.LogOut(); } private void MessageListener_OnMessageReceived(object sender, AVIMMessageEventArgs e) { if (this.m_OnMessageReceived != null) { this.m_OnMessageReceived.Invoke(this, e); } this.AckListener_OnMessageReceieved(sender, e); } private void AckListener_OnMessageReceieved(object sender, AVIMMessageEventArgs e) { lock (mutex) { var ackCommand = new AckCommand().MessageId(e.Message.Id) .ConversationId(e.Message.ConversationId); // 在 v.2 协议下,只要在线收到消息,就默认是已读的,下次上线不会再把当前消息当做未读消息 if (this.LinkedRealtime.CurrentConfiguration.OfflineMessageStrategy == AVRealtime.OfflineMessageStrategy.UnreadNotice) { ackCommand = ackCommand.ReadAck(); } this.RunCommandAsync(ackCommand); } } private void UpdateUnreadNotice(object sender, AVIMMessageEventArgs e) { ConversationUnreadListener.UpdateNotice(e.Message); } #region listener /// /// 注册 IAVIMListener /// /// /// public void RegisterListener(IAVIMListener listener, Func runtimeHook = null) { _realtime.SubscribeNoticeReceived(listener, runtimeHook); } #region get client instance /// /// Get the specified clientId. /// /// The get. /// Client identifier. public static AVIMClient Get(string clientId) { if (AVRealtime.clients == null || !AVRealtime.clients.ContainsKey(clientId)) throw new Exception(string.Format("no client found with a id in {0}", clientId)); return AVRealtime.clients[clientId]; } #endregion #endregion /// /// 创建对话 /// /// 对话 /// 是否创建唯一对话,当 isUnique 为 true 时,如果当前已经有相同成员的对话存在则返回该对话,否则会创建新的对话。该值默认为 false。 /// internal Task CreateConversationAsync(AVIMConversation conversation, bool isUnique = true) { var cmd = new ConversationCommand() .Generate(conversation) .Unique(isUnique); var convCmd = cmd.Option("start") .PeerId(clientId); return LinkedRealtime.AttachSignature(convCmd, LinkedRealtime.SignatureFactory.CreateStartConversationSignature(this.clientId, conversation.MemberIds)).OnSuccess(_ => { return this.RunCommandAsync(convCmd).OnSuccess(t => { var result = t.Result; if (result.Item1 < 1) { var members = conversation.MemberIds.ToList(); members.Add(ClientId); conversation.MemberIds = members; conversation.MergeFromPushServer(result.Item2); } return conversation; }); }).Unwrap(); } /// /// 创建与目标成员的对话. /// /// 返回对话实例. /// 目标成员. /// 目标成员列表. /// 对话名称. /// 是否是系统对话. /// 是否为暂态对话(聊天室). /// 是否是唯一对话. /// 自定义属性. public Task CreateConversationAsync(string member = null, IEnumerable members = null, string name = "", bool isSystem = false, bool isTransient = false, bool isUnique = true, bool isTemporary = false, int ttl = 86400, IDictionary options = null) { if (member == null) member = ClientId; var membersAsList = Concat(member, members, "创建对话时被操作的 member(s) 不可以为空。"); var conversation = new AVIMConversation(members: membersAsList, name: name, isUnique: isUnique, isSystem: isSystem, isTransient: isTransient, isTemporary: isTemporary, ttl: ttl, client: this); if (options != null) { foreach (var key in options.Keys) { conversation[key] = options[key]; } } return CreateConversationAsync(conversation, isUnique); } /// /// Creates the conversation async. /// /// The conversation async. /// Builder. public Task CreateConversationAsync(IAVIMConversatioBuilder builder) { var conversation = builder.Build(); return CreateConversationAsync(conversation, conversation.IsUnique); } /// /// Gets the conversatio builder. /// /// The conversatio builder. public AVIMConversationBuilder GetConversationBuilder() { var builder = AVIMConversationBuilder.CreateDefaultBuilder(); builder.Client = this; return builder; } /// /// 创建虚拟对话,对话 id 是由本地直接生成,云端根据规则消息发送给指定的 client id(s) /// /// /// /// 过期时间,默认是一天(86400 秒),单位是秒 /// public Task CreateTemporaryConversationAsync(string member = null, IEnumerable members = null, int ttl = 86400) { if (member == null) member = ClientId; var membersAsList = Concat(member, members, "创建对话时被操作的 member(s) 不可以为空。"); return CreateConversationAsync(member, membersAsList, isTemporary: true, ttl: ttl); } /// /// 创建聊天室(即:暂态对话) /// /// 聊天室名称 /// public Task CreateChatRoomAsync(string chatroomName) { return CreateConversationAsync(name: chatroomName, isTransient: true); } /// /// 获取一个对话 /// /// 对话的 ID /// 从服务器获取 /// public Task GetConversationAsync(string id, bool noCache = true) { if (!noCache) return Task.FromResult(new AVIMConversation(this) { ConversationId = id }); else { return this.GetQuery().WhereEqualTo("objectId", id).FirstAsync(); } } #region send message /// /// 向目标对话发送消息 /// /// 目标对话 /// 消息体 /// public Task SendMessageAsync( AVIMConversation conversation, IAVIMMessage message) { return this.SendMessageAsync(conversation, message, new AVIMSendOptions() { Receipt = true, Transient = false, Priority = 1, Will = false, PushData = null, }); } /// /// 向目标对话发送消息 /// /// 目标对话 /// 消息体 /// 消息的发送选项,包含了一些特殊的标记 /// public Task SendMessageAsync( AVIMConversation conversation, IAVIMMessage message, AVIMSendOptions options) { if (this.LinkedRealtime.State != AVRealtime.Status.Online) throw new Exception("未能连接到服务器,无法发送消息。"); var messageBody = message.Serialize(); message.ConversationId = conversation.ConversationId; message.FromClientId = this.ClientId; var cmd = new MessageCommand() .Message(messageBody) .ConvId(conversation.ConversationId) .Receipt(options.Receipt) .Transient(options.Transient) .Priority(options.Priority) .Will(options.Will) .MentionAll(message.MentionAll); if (message is AVIMMessage) { cmd = ((AVIMMessage)message).BeforeSend(cmd); } if (options.PushData != null) { cmd = cmd.PushData(options.PushData); } if (message.MentionList != null) { cmd = cmd.Mention(message.MentionList); } var directCmd = cmd.PeerId(this.ClientId); return this.RunCommandAsync(directCmd).OnSuccess(t => { var response = t.Result.Item2; message.Id = response["uid"].ToString(); message.ServerTimestamp = long.Parse(response["t"].ToString()); return message; }); } #endregion #region mute & unmute /// /// 当前用户对目标对话进行静音操作 /// /// /// public Task MuteConversationAsync(AVIMConversation conversation) { var convCmd = new ConversationCommand() .ConversationId(conversation.ConversationId) .Option("mute") .PeerId(this.ClientId); return this.RunCommandAsync(convCmd); } /// /// 当前用户对目标对话取消静音,恢复该对话的离线消息推送 /// /// /// public Task UnmuteConversationAsync(AVIMConversation conversation) { var convCmd = new ConversationCommand() .ConversationId(conversation.ConversationId) .Option("unmute") .PeerId(this.ClientId); return this.RunCommandAsync(convCmd); } #endregion #region Conversation members operations internal Task OperateMembersAsync(AVIMConversation conversation, string action, string member = null, IEnumerable members = null) { if (string.IsNullOrEmpty(conversation.ConversationId)) { throw new Exception("conversation id 不可以为空。"); } var membersAsList = Concat(member, members, "加人或者踢人的时候,被操作的 member(s) 不可以为空。"); var cmd = new ConversationCommand().ConversationId(conversation.ConversationId) .Members(membersAsList) .Option(action) .PeerId(clientId); return this.LinkedRealtime.AttachSignature(cmd, LinkedRealtime.SignatureFactory.CreateConversationSignature(conversation.ConversationId, ClientId, membersAsList, ConversationSignatureAction.Add)).OnSuccess(_ => { return this.RunCommandAsync(cmd).OnSuccess(t => { var result = t.Result; if (!conversation.IsTransient) { if (conversation.MemberIds == null) conversation.MemberIds = new List(); conversation.MemberIds = conversation.MemberIds.Concat(membersAsList); } return result; }); }).Unwrap(); } internal IEnumerable Concat(T single, IEnumerable collection, string exString = null) { List asList = null; if (collection == null) { collection = new List(); } asList = collection.ToList(); if (asList.Count == 0 && single == null) { exString = exString ?? "can not cancat a collection with a null value."; throw new ArgumentNullException(exString); } asList.Add(single); return asList; } #region Join /// /// 当前用户加入到目标的对话中 /// /// 目标对话 /// public Task JoinAsync(AVIMConversation conversation) { return this.OperateMembersAsync(conversation, "add", this.ClientId); } #endregion #region Invite /// /// 直接将其他人加入到目标对话 /// 被操作的人会在客户端会触发 OnInvited 事件,而已经存在于对话的用户会触发 OnMembersJoined 事件 /// /// 目标对话 /// 单个的 Client Id /// Client Id 集合 /// public Task InviteAsync(AVIMConversation conversation, string member = null, IEnumerable members = null) { return this.OperateMembersAsync(conversation, "add", member, members); } #endregion #region Left /// /// 当前 Client 离开目标对话 /// 可以理解为是 QQ 群的退群操作 /// /// /// 目标对话 /// [Obsolete("use LeaveAsync instead.")] public Task LeftAsync(AVIMConversation conversation) { return this.OperateMembersAsync(conversation, "remove", this.ClientId); } /// /// Leaves the conversation async. /// /// The async. /// Conversation. public Task LeaveAsync(AVIMConversation conversation) { return this.OperateMembersAsync(conversation, "remove", this.ClientId); } #endregion #region Kick /// /// 从目标对话中剔除成员 /// /// 目标对话 /// 被剔除的单个成员 /// 被剔除的成员列表 /// public Task KickAsync(AVIMConversation conversation, string member = null, IEnumerable members = null) { return this.OperateMembersAsync(conversation, "remove", member, members); } #endregion #endregion #region Query && Message history && ack /// /// Get conversation query. /// /// public AVIMConversationQuery GetQuery() { return GetConversationQuery(); } /// /// Get conversation query. /// /// The conversation query. public AVIMConversationQuery GetConversationQuery() { return new AVIMConversationQuery(this); } #region load message history /// /// 查询目标对话的历史消息 /// 不支持聊天室(暂态对话) /// /// 目标对话 /// 从 beforeMessageId 开始向前查询(和 beforeTimeStampPoint 共同使用,为防止某毫秒时刻有重复消息) /// 截止到某个 afterMessageId (不包含) /// 从 beforeTimeStampPoint 开始向前查询 /// 拉取截止到 afterTimeStampPoint 时间戳(不包含) /// 查询方向,默认是 1,如果是 1 表示从新消息往旧消息方向, 0 则相反,其他值无效 /// 拉取消息条数,默认值 20 条,可设置为 1 - 1000 之间的任意整数 /// public Task> QueryMessageAsync(AVIMConversation conversation, string beforeMessageId = null, string afterMessageId = null, DateTime? beforeTimeStampPoint = null, DateTime? afterTimeStampPoint = null, int direction = 1, int limit = 20) where T : IAVIMMessage { var maxLimit = 1000; var actualLimit = limit > maxLimit ? maxLimit : limit; var logsCmd = new AVIMCommand() .Command("logs") .Argument("cid", conversation.ConversationId) .Argument("l", actualLimit); if (beforeMessageId != null) { logsCmd = logsCmd.Argument("mid", beforeMessageId); } if (afterMessageId != null) { logsCmd = logsCmd.Argument("tmid", afterMessageId); } if (beforeTimeStampPoint != null && beforeTimeStampPoint.Value != DateTime.MinValue) { logsCmd = logsCmd.Argument("t", beforeTimeStampPoint.Value.ToUnixTimeStamp()); } if (afterTimeStampPoint != null && afterTimeStampPoint.Value != DateTime.MinValue) { logsCmd = logsCmd.Argument("tt", afterTimeStampPoint.Value.ToUnixTimeStamp()); } if (direction == 0) { logsCmd = logsCmd.Argument("direction", "NEW"); } var subMessageType = typeof(T); var subTypeInteger = subMessageType == typeof(AVIMTypedMessage) ? 0 : FreeStyleMessageClassInfo.GetTypedInteger(subMessageType.GetTypeInfo()); if (subTypeInteger != 0) { logsCmd = logsCmd.Argument("lctype", subTypeInteger); } return this.RunCommandAsync(logsCmd).OnSuccess(t => { var rtn = new List(); var result = t.Result.Item2; var logs = result["logs"] as List; if (logs != null) { foreach (var log in logs) { var logMap = log as IDictionary; if (logMap != null) { var msgStr = logMap["data"].ToString(); var messageObj = AVRealtime.FreeStyleMessageClassingController.Instantiate(msgStr, logMap); messageObj.ConversationId = conversation.ConversationId; rtn.Add(messageObj); } } } conversation.OnMessageLoad(rtn); return rtn.AsEnumerable().OfType(); }); } #endregion //public Task MarkAsReadAsync(string conversationId = null, string messageId = null, AVIMConversation conversation = null, AVIMMessage message = null) //{ // var msgId = messageId != null ? messageId : message.Id; // var convId = conversationId != null ? conversationId : conversation.ConversationId; // if (convId == null && msgId == null) throw new ArgumentNullException("发送已读回执的时候,必须指定 conversation id 或者 message id"); // lock (mutex) // { // var ackCommand = new AckCommand() // .ReadAck().MessageId(msgId) // .ConversationId(convId) // .PeerId(this.ClientId); // return this.RunCommandAsync(ackCommand); // } //} #region 查询对话中对方的接收状态,也就是已读回执 private Task> FetchAllReceiptTimestampsAsync(string targetClientId = null, string conversationId = null, AVIMConversation conversation = null, bool queryAllMembers = false) { var convId = conversationId != null ? conversationId : conversation.ConversationId; if (convId == null) throw new ArgumentNullException("conversationId 和 conversation 不可以同时为 null"); var cmd = new ConversationCommand().ConversationId(convId) .TargetClientId(targetClientId) .QueryAllMembers(queryAllMembers) .Option("max-read") .PeerId(clientId); return this.RunCommandAsync(cmd).OnSuccess(t => { var result = t.Result; long maxReadTimestamp = -1; long maxAckTimestamp = -1; if (result.Item2.ContainsKey("maxReadTimestamp")) { long.TryParse(result.Item2["maxReadTimestamp"].ToString(), out maxReadTimestamp); } if (result.Item2.ContainsKey("maxAckTimestamp")) { long.TryParse(result.Item2["maxAckTimestamp"].ToString(), out maxAckTimestamp); } return new Tuple(maxAckTimestamp, maxReadTimestamp); }); } #endregion #region 查询对方是否在线 /// /// 查询对方 client Id 是否在线 /// /// 单个 client Id /// 多个 client Id 集合 /// public Task>> PingAsync(string targetClientId = null, IEnumerable targetClientIds = null) { List queryIds = null; if (targetClientIds != null) queryIds = targetClientIds.ToList(); if (queryIds == null && string.IsNullOrEmpty(targetClientId)) throw new ArgumentNullException("必须查询至少一个 client id 的状态,targetClientId 和 targetClientIds 不可以同时为空"); queryIds.Add(targetClientId); var cmd = new SessionCommand() .SessionPeerIds(queryIds) .Option("query"); return this.RunCommandAsync(cmd).OnSuccess(t => { var result = t.Result; List> rtn = new List>(); var onlineSessionPeerIds = AVDecoder.Instance.DecodeList(result.Item2["onlineSessionPeerIds"]); foreach (var peerId in targetClientIds) { rtn.Add(new Tuple(peerId, onlineSessionPeerIds.Contains(peerId))); } return rtn.AsEnumerable(); }); } #endregion #region 获取暂态对话在线人数 /// /// 获取暂态对话(聊天室)在线人数,依赖缓存,并不一定 100% 与真实数据一致。 /// /// /// public Task CountOnlineClientsAsync(string chatroomId) { var command = new AVCommand(relativeUri: "rtm/transient_group/onlines?gid=" + chatroomId, method: "GET", sessionToken: null, headers: null, data: null); return AVPlugins.Instance.CommandRunner.RunCommandAsync(command).OnSuccess(t => { var result = t.Result.Item2; if (result.ContainsKey("result")) { return int.Parse(result["result"].ToString()); } return -1; }); } #endregion #endregion #region mark as read /// /// /// /// /// /// /// public Task ReadAsync(AVIMConversation conversation, IAVIMMessage message = null, DateTime? readAt = null) { var convRead = new ReadCommand.ConvRead() { ConvId = conversation.ConversationId, }; if (message != null) { convRead.MessageId = message.Id; convRead.Timestamp = message.ServerTimestamp; } if (readAt != null && readAt.Value != DateTime.MinValue) { convRead.Timestamp = readAt.Value.ToUnixTimeStamp(); } var readCmd = new ReadCommand().Conv(convRead).PeerId(this.ClientId); this.RunCommandAsync(readCmd); return Task.FromResult(true); } /// /// mark the conversation as read with conversation id. /// /// conversation id /// public Task ReadAsync(string conversationId) { var conv = AVIMConversation.CreateWithoutData(conversationId, this); return this.ReadAsync(conv); } /// /// mark all conversations as read. /// /// public Task ReadAllAsync() { var cids = ConversationUnreadListener.FindAllConvIds(); var readCmd = new ReadCommand().ConvIds(cids).PeerId(this.ClientId); return this.RunCommandAsync(readCmd); } #endregion #region recall & modify /// /// Recalls the async. /// /// The async. /// Message. public Task RecallAsync(IAVIMMessage message) { var tcs = new TaskCompletionSource(); var patchCmd = new PatchCommand().Recall(message); RunCommandAsync(patchCmd) .OnSuccess(t => { var recalledMsg = new AVIMRecalledMessage(); AVIMMessage.CopyMetaData(message, recalledMsg); tcs.SetResult(recalledMsg); }); return tcs.Task; } /// /// Modifies the aysnc. /// /// The aysnc. /// 要修改的消息对象 /// 新的消息对象 public Task UpdateAsync(IAVIMMessage oldMessage, IAVIMMessage newMessage) { var tcs = new TaskCompletionSource(); var patchCmd = new PatchCommand().Modify(oldMessage, newMessage); this.RunCommandAsync(patchCmd) .OnSuccess(t => { // 从旧消息对象中拷贝数据 AVIMMessage.CopyMetaData(oldMessage, newMessage); // 获取更新时间戳 var response = t.Result.Item2; if (response.TryGetValue("lastPatchTime", out object updatedAtObj) && long.TryParse(updatedAtObj.ToString(), out long updatedAt)) { newMessage.UpdatedAt = updatedAt; } tcs.SetResult(newMessage); }); return tcs.Task; } internal EventHandler m_OnMessageRecalled; /// /// Occurs when on message recalled. /// public event EventHandler OnMessageRecalled { add { this.m_OnMessageRecalled += value; } remove { this.m_OnMessageRecalled -= value; } } internal EventHandler m_OnMessageUpdated; /// /// Occurs when on message modified. /// public event EventHandler OnMessageUpdated { add { this.m_OnMessageUpdated += value; } remove { this.m_OnMessageUpdated -= value; } } #endregion #region log out /// /// 退出登录或者切换账号 /// /// public Task CloseAsync() { var cmd = new SessionCommand().Option("close"); return this.RunCommandAsync(cmd).ContinueWith(t => { m_OnSessionClosed(this, null); }); } #endregion /// /// Run command async. /// /// The command async. /// Command. public Task>> RunCommandAsync(AVIMCommand command) { command.PeerId(this.ClientId); return this.LinkedRealtime.RunCommandAsync(command); } /// /// Run command. /// /// Command. public void RunCommand(AVIMCommand command) { command.PeerId(this.ClientId); this.LinkedRealtime.RunCommand(command); } } /// /// AVIMClient extensions. /// public static class AVIMClientExtensions { /// /// Create conversation async. /// /// The conversation async. /// Client. /// Members. public static Task CreateConversationAsync(this AVIMClient client, IEnumerable members) { return client.CreateConversationAsync(members: members); } public static Task CreateConversationAsync(this AVIMClient client, IEnumerable members, string conversationName) { return client.CreateConversationAsync(members: members, name: conversationName); } /// /// Get conversation. /// /// The conversation. /// Client. /// Conversation identifier. public static AVIMConversation GetConversation(this AVIMClient client, string conversationId) { return AVIMConversation.CreateWithoutData(conversationId, client); } /// /// Join conversation async. /// /// The async. /// Client. /// Conversation identifier. public static Task JoinAsync(this AVIMClient client, string conversationId) { var conversation = client.GetConversation(conversationId); return client.JoinAsync(conversation); } /// /// Leave conversation async. /// /// The async. /// Client. /// Conversation identifier. public static Task LeaveAsync(this AVIMClient client, string conversationId) { var conversation = client.GetConversation(conversationId); return client.LeaveAsync(conversation); } /// /// Query messages. /// /// The message async. /// Client. /// Conversation. /// Before message identifier. /// After message identifier. /// Before time stamp point. /// After time stamp point. /// Direction. /// Limit. public static Task> QueryMessageAsync(this AVIMClient client, AVIMConversation conversation, string beforeMessageId = null, string afterMessageId = null, DateTime? beforeTimeStampPoint = null, DateTime? afterTimeStampPoint = null, int direction = 1, int limit = 20) { return client.QueryMessageAsync(conversation, beforeMessageId, afterMessageId, beforeTimeStampPoint, afterTimeStampPoint, direction, limit); } /// /// Get the chat room query. /// /// The chat room query. /// Client. public static AVIMConversationQuery GetChatRoomQuery(this AVIMClient client) { return client.GetQuery().WhereEqualTo("tr", true); } } }