using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using LeanCloud.Realtime.Internal; using LeanCloud; using LeanCloud.Storage.Internal; using System.Collections; using System.IO; namespace LeanCloud.Realtime { /// /// 对话 /// public class AVIMConversation : IEnumerable>, IAVObject { private DateTime? updatedAt; private DateTime? createdAt; private DateTime? lastMessageAt; internal DateTime? expiredAt; private string name; private AVObject convState; internal readonly Object mutex = new Object(); //private readonly IDictionary estimatedData = new Dictionary(); internal AVIMClient _currentClient; IEnumerator> IEnumerable>.GetEnumerator() { lock (mutex) { return ((IEnumerable>)convState).GetEnumerator(); } } System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { lock (mutex) { return ((IEnumerable>)convState).GetEnumerator(); } } virtual public object this[string key] { get { return convState[key]; } set { convState[key] = value; } } public ICollection Keys { get { lock (mutex) { return convState.Keys; } } } public T Get(string key) { return this.convState.Get(key); } public bool ContainsKey(string key) { return this.convState.ContainsKey(key); } internal IDictionary EncodeAttributes() { var currentOperations = convState.StartSave(); var jsonToSave = AVObject.ToJSONObjectForSaving(currentOperations); return jsonToSave; } internal void MergeFromPushServer(IDictionary json) { if (json.Keys.Contains("cdate")) { createdAt = DateTime.Parse(json["cdate"].ToString()); updatedAt = DateTime.Parse(json["cdate"].ToString()); json.Remove("cdate"); } if (json.Keys.Contains("lm")) { var ts = long.Parse(json["lm"].ToString()); updatedAt = ts.ToDateTime(); lastMessageAt = ts.ToDateTime(); json.Remove("lm"); } if (json.Keys.Contains("c")) { Creator = json["c"].ToString(); json.Remove("c"); } if (json.Keys.Contains("m")) { MemberIds = json["m"] as IList; json.Remove("m"); } if (json.Keys.Contains("mu")) { MuteMemberIds = json["mu"] as IList; json.Remove("mu"); } if (json.Keys.Contains("tr")) { IsTransient = bool.Parse(json["tr"].ToString()); json.Remove("tr"); } if (json.Keys.Contains("sys")) { IsSystem = bool.Parse(json["sys"].ToString()); json.Remove("sys"); } if (json.Keys.Contains("cid")) { ConversationId = json["cid"].ToString(); json.Remove("cid"); } if (json.Keys.Contains("name")) { Name = json["name"].ToString(); json.Remove("name"); } } /// /// 当前的AVIMClient,一个对话理论上只存在一个AVIMClient。 /// public AVIMClient CurrentClient { get { if (_currentClient == null) throw new NullReferenceException("当前对话没有关联有效的 AVIMClient。"); return _currentClient; } //set //{ // _currentClient = value; //} } /// /// 对话的唯一ID /// public string ConversationId { get; internal set; } /// /// 对话在全局的唯一的名字 /// public string Name { get { if (convState.ContainsKey("name")) { name = this.convState.Get("name"); } return name; } set { if (value == null) this["name"] = ""; else { this["name"] = value; } } } /// /// 对话中存在的 Client 的 ClientId 列表 /// public IEnumerable MemberIds { get; internal set; } /// /// 对该对话静音的成员列表 /// /// 对该对话设置了静音的成员,将不会收到离线消息的推送。 /// /// public IEnumerable MuteMemberIds { get; internal set; } /// /// 对话的创建者 /// public string Creator { get; private set; } /// /// 是否为聊天室 /// public bool IsTransient { get; internal set; } /// /// 是否系统对话 /// public bool IsSystem { get; internal set; } /// /// 是否是唯一对话 /// public bool IsUnique { get; internal set; } /// /// 对话是否为虚拟对话 /// public bool IsTemporary { get; internal set; } /// /// 对话创建的时间 /// public DateTime? CreatedAt { get { DateTime? nullable; lock (this.mutex) { nullable = this.createdAt; } return nullable; } private set { lock (this.mutex) { this.createdAt = value; } } } /// /// 对话更新的时间 /// public DateTime? UpdatedAt { get { DateTime? nullable; lock (this.mutex) { nullable = this.updatedAt; } return nullable; } private set { lock (this.mutex) { this.updatedAt = value; } } } /// /// 对话中最后一条消息的时间,可以用此判断对话的最后活跃时间 /// public DateTime? LastMessageAt { get { DateTime? nullable; lock (this.mutex) { nullable = this.lastMessageAt; } return nullable; } private set { lock (this.mutex) { this.lastMessageAt = value; } } } /// /// 已知 id,在本地构建一个 AVIMConversation 对象 /// public AVIMConversation(string id) : this(id, null) { } /// /// 已知 id 在本地构建一个对话 /// /// 对话 id /// AVIMClient 实例,必须是登陆成功的 public AVIMConversation(string id, AVIMClient client) : this(client) { this.ConversationId = id; } internal AVIMConversation(AVIMClient client) { this._currentClient = client; this.CurrentClient.OnMessageReceived += CurrentClient_OnMessageReceived; } /// /// AVIMConversation Build 驱动器 /// /// /// /// /// /// /// /// /// /// /// /// internal AVIMConversation(AVIMConversation source = null, string name = null, string creator = null, IEnumerable members = null, IEnumerable muteMembers = null, bool isTransient = false, bool isSystem = false, IEnumerable> attributes = null, AVObject state = null, bool isUnique = true, bool isTemporary = false, int ttl = 86400, AVIMClient client = null) : this(client) { convState = source != null ? source.convState : new AVObject("_Conversation"); this.Name = source?.Name; this.MemberIds = source?.MemberIds; this.Creator = source?.Creator; this.MuteMemberIds = source?.MuteMemberIds; if (!string.IsNullOrEmpty(name)) { this.Name = name; } if (!string.IsNullOrEmpty(creator)) { this.Creator = creator; } if (members != null) { this.MemberIds = members.ToList(); } if (muteMembers != null) { this.MuteMemberIds = muteMembers.ToList(); } this.IsTransient = isTransient; this.IsSystem = isSystem; this.IsUnique = isUnique; this.IsTemporary = isTemporary; this.expiredAt = DateTime.Now + new TimeSpan(0, 0, ttl); if (state != null) { convState = state; this.ConversationId = state.ObjectId; this.CreatedAt = state.CreatedAt; this.UpdatedAt = state.UpdatedAt; this.MergeMagicFields(state.ToDictionary(x => x.Key, x => x.Value)); } if (attributes != null) { this.MergeMagicFields(attributes.ToDictionary(x => x.Key, x => x.Value)); } } /// /// 从本地构建一个对话 /// /// 对话的 objectId /// /// public static AVIMConversation CreateWithoutData(string convId, AVIMClient client) { return new AVIMConversation(client) { ConversationId = convId, }; } /// /// /// /// /// /// public static AVIMConversation CreateWithData(IEnumerable> magicFields, AVIMClient client) { if (magicFields is AVObject) { return new AVIMConversation(state: (AVObject)magicFields, client: client); } return new AVIMConversation(attributes: magicFields, client: client); } #region save to cloud /// /// 将修改保存到云端 /// /// public Task SaveAsync() { var cmd = new ConversationCommand() .Generate(this); var convCmd = cmd.Option("update") .PeerId(this.CurrentClient.ClientId); return this.CurrentClient.RunCommandAsync(convCmd); } #endregion #region send message /// /// 向该对话发送消息。 /// /// 消息体 /// 是否需要送达回执 /// 是否是暂态消息,暂态消息不返回送达回执(ack),不保留离线消息,不触发离线推送 /// 消息等级,默认是1,可选值还有 2 ,3 /// 标记该消息是否为下线通知消息 /// 如果消息的接收者已经下线了,这个字段的内容就会被离线推送到接收者 /// 例如,一张图片消息的离线消息内容可以类似于:[您收到一条图片消息,点击查看] 这样的推送内容,参照微信的做法 /// /// public Task SendMessageAsync(IAVIMMessage avMessage, bool receipt = true, bool transient = false, int priority = 1, bool will = false, IDictionary pushData = null) { return this.SendMessageAsync(avMessage, new AVIMSendOptions() { Receipt = receipt, Transient = transient, Priority = priority, Will = will, PushData = pushData }); } /// /// 发送消息 /// /// 消息体 /// 消息的发送选项,包含了一些特殊的标记 /// public Task SendMessageAsync(IAVIMMessage avMessage, AVIMSendOptions options) { if (this.CurrentClient == null) throw new Exception("当前对话未指定有效 AVIMClient,无法发送消息。"); return this.CurrentClient.SendMessageAsync(this, avMessage, options); } #endregion #region recall message #endregion #region message listener and event notify /// /// Registers the listener. /// /// Listener. public void RegisterListener(IAVIMListener listener) { this.CurrentClient.RegisterListener(listener, this.ConversationIdHook); } internal bool ConversationIdHook(AVIMNotice notice) { if (!notice.RawData.ContainsKey("cid")) return false; return notice.RawData["cid"].ToString() == this.ConversationId; } #endregion #region mute && unmute /// /// 当前用户针对对话做静音操作 /// /// public Task MuteAsync() { return this.CurrentClient.MuteConversationAsync(this); } /// /// 当前用户取消对话的静音,恢复该对话的离线消息推送 /// /// public Task UnmuteAsync() { return this.CurrentClient.UnmuteConversationAsync(this); } #endregion #region 成员操作相关接口 /// /// Joins the async. /// /// The async. public Task JoinAsync() { return AddMembersAsync(CurrentClient.ClientId); } /// /// Adds the members async. /// /// The members async. /// Client identifier. /// Client identifiers. public Task AddMembersAsync(string clientId = null, IEnumerable clientIds = null) { return this.CurrentClient.InviteAsync(this, clientId, clientIds); } /// /// Removes the members async. /// /// The members async. /// Client identifier. /// Client identifiers. public Task RemoveMembersAsync(string clientId = null, IEnumerable clientIds = null) { return this.CurrentClient.KickAsync(this, clientId, clientIds); } /// /// Quits the async. /// /// The async. public Task QuitAsync() { return RemoveMembersAsync(CurrentClient.ClientId); } #endregion #region load message history /// /// 获取当前对话的历史消息 /// 不支持聊天室(暂态对话) /// /// 从 beforeMessageId 开始向前查询(和 beforeTimeStampPoint 共同使用,为防止某毫秒时刻有重复消息) /// 截止到某个 afterMessageId (不包含) /// 从 beforeTimeStampPoint 开始向前查询 /// 拉取截止到 afterTimeStampPoint 时间戳(不包含) /// 查询方向,默认是 1,如果是 1 表示从新消息往旧消息方向, 0 则相反,其他值无效 /// 获取的消息数量 /// public Task> QueryMessageAsync( string beforeMessageId = null, string afterMessageId = null, DateTime? beforeTimeStampPoint = null, DateTime? afterTimeStampPoint = null, int direction = 1, int limit = 20) where T : IAVIMMessage { return this.CurrentClient.QueryMessageAsync(this, beforeMessageId, afterMessageId, beforeTimeStampPoint, afterTimeStampPoint, direction, limit) .OnSuccess(t => { //OnMessageLoad(t.Result); return t.Result; }); } /// /// Gets the message query. /// /// The message query. public AVIMMessageQuery GetMessageQuery() { return new AVIMMessageQuery(this); } /// /// Gets the message pager. /// /// The message pager. public AVIMMessagePager GetMessagePager() { return new AVIMMessagePager(this); } #endregion #region 字典与对象之间的转换 internal virtual void MergeMagicFields(IDictionary data) { lock (this.mutex) { if (data.ContainsKey("objectId")) { this.ConversationId = (data["objectId"] as String); data.Remove("objectId"); } if (data.ContainsKey("createdAt")) { this.CreatedAt = AVDecoder.ParseDate(data["createdAt"] as string); data.Remove("createdAt"); } if (data.ContainsKey("updatedAt")) { this.updatedAt = AVDecoder.ParseDate(data["updatedAt"] as string); data.Remove("updatedAt"); } if (data.ContainsKey("name")) { this.Name = (data["name"] as String); data.Remove("name"); } if (data.ContainsKey("lm")) { this.LastMessageAt = AVDecoder.Instance.Decode(data["lm"]) as DateTime?; data.Remove("lm"); } if (data.ContainsKey("m")) { this.MemberIds = AVDecoder.Instance.DecodeList(data["m"]); data.Remove("m"); } if (data.ContainsKey("mu")) { this.MuteMemberIds = AVDecoder.Instance.DecodeList(data["mu"]); data.Remove("mu"); } if (data.ContainsKey("c")) { this.Creator = data["c"].ToString(); data.Remove("c"); } if (data.ContainsKey("unique")) { if (data["unique"] is bool) { this.IsUnique = (bool)data["unique"]; } data.Remove("unique"); } foreach (var kv in data) { this[kv.Key] = kv.Value; } } } #endregion #region SyncStateAsync & unread & mark as read /// /// sync state from server.suhc unread state .etc; /// /// public Task SyncStateAsync() { lock (mutex) { var rtn = new AggregatedState(); rtn.Unread = GetUnreadStateFromLocal(); return Task.FromResult(rtn); } } private UnreadState _unread; private UnreadState _lastUnreadWhenOpenSession; public UnreadState Unread { get { _lastUnreadWhenOpenSession = GetUnreadStateFromLocal(); // v.2 协议,只给出上次离线之后的未读消息,本次在线的收到的消息均视为已读 if (this.CurrentClient.LinkedRealtime.CurrentConfiguration.OfflineMessageStrategy == AVRealtime.OfflineMessageStrategy.UnreadNotice) { _unread = _lastUnreadWhenOpenSession; } else if (this.CurrentClient.LinkedRealtime.CurrentConfiguration.OfflineMessageStrategy == AVRealtime.OfflineMessageStrategy.UnreadAck) { if (_lastUnreadWhenOpenSession == null) _unread = new UnreadState().MergeReceived(this.Received); else _unread = _lastUnreadWhenOpenSession.MergeReceived(this.Received); } return _unread; } internal set { _unread = value; } } private readonly object receivedMutex = new object(); public ReceivedState Received { get; set; } public ReadState Read { get; set; } UnreadState GetUnreadStateFromLocal() { lock (mutex) { var notice = ConversationUnreadListener.Get(this.ConversationId); if (notice != null) { var unreadState = new UnreadState() { LastMessage = notice.LastUnreadMessage, SyncdAt = ConversationUnreadListener.NotifTime, Count = notice.UnreadCount }; return unreadState; } return null; } } internal void OnMessageLoad(IEnumerable messages) { var lastestInCollection = messages.OrderByDescending(m => m.ServerTimestamp).FirstOrDefault(); if (lastestInCollection != null) { if (CurrentClient.CurrentConfiguration.AutoRead) { this.ReadAsync(lastestInCollection); } } } /// /// mark this conversation as read /// /// public Task ReadAsync(IAVIMMessage message = null, DateTime? readAt = null) { // 标记已读必须至少是从上一次离线产生的最后一条消息开始,否则无法计算 Count if (_lastUnreadWhenOpenSession != null) { if (_lastUnreadWhenOpenSession.LastMessage != null) { message = _lastUnreadWhenOpenSession.LastMessage; } } return this.CurrentClient.ReadAsync(this, message, readAt).OnSuccess(t => { Received = null; _lastUnreadWhenOpenSession = null; Read = new ReadState() { ReadAt = readAt != null ? readAt.Value.ToUnixTimeStamp() : 0, LastMessage = message, SyncdAt = DateTime.Now.ToUnixTimeStamp() }; }); } /// /// aggregated state for the conversation /// public class AggregatedState { /// /// Unread state /// public UnreadState Unread { get; internal set; } } /// /// UnreadState recoder for the conversation /// public class UnreadState { /// /// unread count /// public int Count { get; internal set; } /// /// last unread message /// public IAVIMMessage LastMessage { get; internal set; } /// /// last sync timestamp /// public long SyncdAt { get; internal set; } internal UnreadState MergeReceived(ReceivedState receivedState) { if (receivedState == null) return this; var count = Count + receivedState.Count; var lastMessage = this.LastMessage; if (receivedState.LastMessage != null) { lastMessage = receivedState.LastMessage; } var syncdAt = this.SyncdAt > receivedState.SyncdAt ? this.SyncdAt : receivedState.SyncdAt; return new UnreadState() { Count = count, LastMessage = lastMessage, SyncdAt = syncdAt }; } } public class ReceivedState { public int Count { get; internal set; } /// /// last received message /// public IAVIMMessage LastMessage { get; internal set; } /// /// last sync timestamp /// public long SyncdAt { get; internal set; } } public class ReadState { public long ReadAt { get; set; } public IAVIMMessage LastMessage { get; internal set; } public long SyncdAt { get; internal set; } } #endregion #region on client message received to update unread private void CurrentClient_OnMessageReceived(object sender, AVIMMessageEventArgs e) { if (this.CurrentClient.CurrentConfiguration.AutoRead) { this.ReadAsync(e.Message); return; } lock (receivedMutex) { if (this.Received == null) this.Received = new ReceivedState(); this.Received.Count++; this.Received.LastMessage = e.Message; this.Received.SyncdAt = DateTime.Now.ToUnixTimeStamp(); } } #endregion } /// /// AVIMConversation extensions. /// public static class AVIMConversationExtensions { /// /// Send message async. /// /// The async. /// Conversation. /// Message. /// Options. public static Task SendAsync(this AVIMConversation conversation, IAVIMMessage message, AVIMSendOptions options) { return conversation.SendMessageAsync(message, options); } /// /// Send message async. /// /// The async. /// Conversation. /// Message. /// Options. /// The 1st type parameter. public static Task SendAsync(this AVIMConversation conversation, T message, AVIMSendOptions options) where T : IAVIMMessage { return conversation.SendMessageAsync(message, options).OnSuccess(t => { return (T)t.Result; }); } /// /// Send message async. /// /// The async. /// Conversation. /// Message. /// The 1st type parameter. public static Task SendAsync(this AVIMConversation conversation, T message) where T : IAVIMMessage { return conversation.SendMessageAsync(message).OnSuccess(t => { return (T)t.Result; }); } /// /// Send text message. /// /// The text async. /// Conversation. /// Text. public static Task SendTextAsync(this AVIMConversation conversation, string text) { return conversation.SendAsync(new AVIMTextMessage(text)); } #region Image messages /// /// Send image message async. /// /// The image async. /// Conversation. /// URL. /// Name. /// Text title. /// Custom attributes. public static Task SendImageAsync(this AVIMConversation conversation, string url, string name = null, string textTitle = null, IDictionary customAttributes = null) { return conversation.SendFileMessageAsync(url, name, textTitle, customAttributes); } /// /// Send image message async. /// /// The image async. /// Conversation. /// File name. /// Data. /// MIME type. /// Text title. /// Meta data. /// Custom attributes. public static Task SendImageAsync(this AVIMConversation conversation, string fileName, Stream data, string mimeType = null, string textTitle = null, IDictionary metaData = null, IDictionary customAttributes = null) { return conversation.SendFileMessageAsync(fileName, data, mimeType, textTitle, metaData, customAttributes); } /// /// Send image message async. /// /// The image async. /// Conversation. /// File name. /// Data. /// MIME type. /// Text title. /// Meta data. /// Custom attributes. public static Task SendImageAsync(this AVIMConversation conversation, string fileName, byte[] data, string mimeType = null, string textTitle = null, IDictionary metaData = null, IDictionary customAttributes = null) { return conversation.SendFileMessageAsync(fileName, data, mimeType, textTitle, metaData, customAttributes); } #endregion #region audio message /// /// Send audio message async. /// /// The audio async. /// Conversation. /// URL. /// Name. /// Text title. /// Custom attributes. public static Task SendAudioAsync(this AVIMConversation conversation, string url, string name = null, string textTitle = null, IDictionary customAttributes = null) { return conversation.SendFileMessageAsync(name, url, textTitle, customAttributes); } /// /// Send audio message async. /// /// The audio async. /// Conversation. /// File name. /// Data. /// MIME type. /// Text title. /// Meta data. /// Custom attributes. public static Task SendAudioAsync(this AVIMConversation conversation, string fileName, Stream data, string mimeType = null, string textTitle = null, IDictionary metaData = null, IDictionary customAttributes = null) { return conversation.SendFileMessageAsync(fileName, data, mimeType, textTitle, metaData, customAttributes); } /// /// Send audio message async. /// /// The audio async. /// Conversation. /// File name. /// Data. /// MIME type. /// Text title. /// Meta data. /// Custom attributes. public static Task SendAudioAsync(this AVIMConversation conversation, string fileName, byte[] data, string mimeType = null, string textTitle = null, IDictionary metaData = null, IDictionary customAttributes = null) { return conversation.SendFileMessageAsync(fileName, data, mimeType, textTitle, metaData, customAttributes); } #endregion #region video message /// /// Send video message async. /// /// The video async. /// Conversation. /// URL. /// Name. /// Text title. /// Custom attributes. public static Task SendVideoAsync(this AVIMConversation conversation, string url, string name = null, string textTitle = null, IDictionary customAttributes = null) { return conversation.SendFileMessageAsync(name, url, textTitle, customAttributes); } /// /// Send video message async. /// /// The video async. /// Conversation. /// File name. /// Data. /// MIME type. /// Text title. /// Meta data. /// Custom attributes. public static Task SendVideoAsync(this AVIMConversation conversation, string fileName, Stream data, string mimeType = null, string textTitle = null, IDictionary metaData = null, IDictionary customAttributes = null) { return conversation.SendFileMessageAsync(fileName, data, mimeType, textTitle, metaData, customAttributes); } /// /// Send video message async. /// /// The video async. /// Conversation. /// File name. /// Data. /// MIME type. /// Text title. /// Meta data. /// Custom attributes. public static Task SendVideoAsync(this AVIMConversation conversation, string fileName, byte[] data, string mimeType = null, string textTitle = null, IDictionary metaData = null, IDictionary customAttributes = null) { return conversation.SendFileMessageAsync(fileName, data, mimeType, textTitle, metaData, customAttributes); } #endregion /// /// Send file message async. /// /// The file message async. /// Conversation. /// URL. /// Name. /// Text title. /// Custom attributes. /// The 1st type parameter. public static Task SendFileMessageAsync(this AVIMConversation conversation, string url, string name = null, string textTitle = null, IDictionary customAttributes = null) where T : AVIMFileMessage, new() { var fileMessage = AVIMFileMessage.FromUrl(name, url, textTitle, customAttributes); return conversation.SendAsync(fileMessage); } /// /// Send file message async. /// /// The file message async. /// Conversation. /// File name. /// Data. /// MIME type. /// Text title. /// Meta data. /// Custom attributes. /// The 1st type parameter. public static Task SendFileMessageAsync(this AVIMConversation conversation, string fileName, Stream data, string mimeType = null, string textTitle = null, IDictionary metaData = null, IDictionary customAttributes = null) where T : AVIMFileMessage, new() { var fileMessage = AVIMFileMessage.FromStream(fileName, data, mimeType, textTitle, metaData, customAttributes); return fileMessage.File.SaveAsync().OnSuccess(fileUploaded => { return conversation.SendAsync(fileMessage); }).Unwrap(); } /// /// Send file message async. /// /// The file message async. /// Conversation. /// File name. /// Data. /// MIME type. /// Text title. /// Meta data. /// Custom attributes. /// The 1st type parameter. public static Task SendFileMessageAsync(this AVIMConversation conversation, string fileName, byte[] data, string mimeType = null, string textTitle = null, IDictionary metaData = null, IDictionary customAttributes = null) where T : AVIMFileMessage, new() { var fileMessage = AVIMFileMessage.FromStream(fileName, new MemoryStream(data), mimeType, textTitle, metaData, customAttributes); return conversation.SendFileMessageAsync(fileName, new MemoryStream(data), mimeType, textTitle, metaData, customAttributes); } /// /// Send location async. /// /// The location async. /// Conversation. /// Point. public static Task SendLocationAsync(this AVIMConversation conversation, AVGeoPoint point) { var locationMessage = new AVIMLocationMessage(point); return conversation.SendAsync(locationMessage); } /// /// Query the message async. /// /// The message async. /// Conversation. /// Before message identifier. /// After message identifier. /// Before time stamp. /// After time stamp. /// Direction. /// Limit. public static Task> QueryMessageAsync( this AVIMConversation conversation, string beforeMessageId = null, string afterMessageId = null, long? beforeTimeStamp = null, long? afterTimeStamp = null, int direction = 1, int limit = 20) { return conversation.QueryMessageAsync(beforeMessageId, afterMessageId, beforeTimeStamp, afterTimeStamp, direction, limit); } /// /// Query message with speciafic subclassing type. /// /// The message async. /// Conversation. /// Before message identifier. /// After message identifier. /// Before time stamp. /// After time stamp. /// Direction. /// Limit. /// The 1st type parameter. public static Task> QueryMessageAsync( this AVIMConversation conversation, string beforeMessageId = null, string afterMessageId = null, long? beforeTimeStamp = null, long? afterTimeStamp = null, int direction = 1, int limit = 20) where T : IAVIMMessage { return conversation.QueryMessageAsync(beforeMessageId, afterMessageId, beforeTimeStampPoint: beforeTimeStamp.HasValue ? beforeTimeStamp.Value.ToDateTime() : DateTime.MinValue, afterTimeStampPoint: afterTimeStamp.HasValue ? afterTimeStamp.Value.ToDateTime() : DateTime.MinValue, direction: direction, limit: limit); } /// /// Query message record before the given message async. /// /// The message before async. /// Conversation. /// Message. /// Limit. public static Task> QueryMessageBeforeAsync( this AVIMConversation conversation, IAVIMMessage message, int limit = 20) { return conversation.QueryMessageAsync(beforeMessageId: message.Id, beforeTimeStamp: message.ServerTimestamp, limit: limit); } /// /// Query message record after the given message async. /// /// The message after async. /// Conversation. /// Message. /// Limit. public static Task> QueryMessageAfterAsync( this AVIMConversation conversation, IAVIMMessage message, int limit = 20) { return conversation.QueryMessageAsync(afterMessageId: message.Id, afterTimeStamp: message.ServerTimestamp, limit: limit); } /// /// Query messages after conversation created. /// /// The message after async. /// Conversation. /// Limit. public static Task> QueryMessageFromOldToNewAsync( this AVIMConversation conversation, int limit = 20) { return conversation.QueryMessageAsync(afterTimeStamp: conversation.CreatedAt.Value.ToUnixTimeStamp(), limit: limit); } /// /// Query messages in interval async. /// /// The message interval async. /// Conversation. /// Start. /// End. /// Limit. public static Task> QueryMessageInIntervalAsync( this AVIMConversation conversation, IAVIMMessage start, IAVIMMessage end, int limit = 20) { return conversation.QueryMessageAsync( beforeMessageId: end.Id, beforeTimeStamp: end.ServerTimestamp, afterMessageId: start.Id, afterTimeStamp: start.ServerTimestamp, limit: limit); } /// /// Recall message async. /// /// The async. /// Conversation. /// Message. public static Task RecallAsync(this AVIMConversation conversation, IAVIMMessage message) { return conversation.CurrentClient.RecallAsync(message); } /// /// Modifiy message async. /// /// The async. /// Conversation. /// 要修改的消息对象 /// 新的消息对象 public static Task UpdateAsync(this AVIMConversation conversation, IAVIMMessage oldMessage, IAVIMMessage newMessage) { return conversation.CurrentClient.UpdateAsync(oldMessage, newMessage); } } /// /// AVIMConversatio builder. /// public interface IAVIMConversatioBuilder { /// /// Build this instance. /// /// The build. AVIMConversation Build(); } /// /// AVIMConversation builder. /// public class AVIMConversationBuilder : IAVIMConversatioBuilder { /// /// Gets or sets the client. /// /// The client. public AVIMClient Client { get; internal set; } private bool isUnique; private Dictionary properties; private string name; private bool isTransient; private bool isSystem; private List members; internal static AVIMConversationBuilder CreateDefaultBuilder() { return new AVIMConversationBuilder(); } /// /// Build this instance. /// /// The build. public AVIMConversation Build() { var result = new AVIMConversation( members: members, name: name, isUnique: isUnique, isSystem: isSystem, isTransient: isTransient, client: Client); if (properties != null) { foreach (var key in properties.Keys) { result[key] = properties[key]; } } return result; } /// /// Sets the unique. /// /// The unique. /// If set to true toggle. public AVIMConversationBuilder SetUnique(bool toggle = true) { this.isUnique = toggle; return this; } /// /// Sets the system. /// /// The system. /// If set to true toggle. public AVIMConversationBuilder SetSystem(bool toggle = true) { this.isSystem = toggle; return this; } /// /// Sets the transient. /// /// The transient. /// If set to true toggle. public AVIMConversationBuilder SetTransient(bool toggle = true) { this.isTransient = toggle; return this; } /// /// Sets the name. /// /// The name. /// Name. public AVIMConversationBuilder SetName(string name) { this.name = name; return this; } /// /// Sets the members. /// /// The members. /// Member client identifiers. public AVIMConversationBuilder SetMembers(IEnumerable memberClientIds) { this.members = memberClientIds.ToList(); return this; } /// /// Adds the member. /// /// The member. /// Member client identifier. public AVIMConversationBuilder AddMember(string memberClientId) { return AddMembers(new string[] { memberClientId }); } /// /// Adds the members. /// /// The members. /// Member client identifiers. public AVIMConversationBuilder AddMembers(IEnumerable memberClientIds) { if (this.members == null) this.members = new List(); this.members.AddRange(memberClientIds); return this; } /// /// Sets the property. /// /// The property. /// Key. /// Value. public AVIMConversationBuilder SetProperty(string key, object value) { if (this.properties == null) this.properties = new Dictionary(); this.properties[key] = value; return this; } } /// /// AVIMM essage emitter builder. /// public class AVIMMessageEmitterBuilder { private AVIMConversation _conversation; /// /// Sets the conversation. /// /// The conversation. /// Conversation. public AVIMMessageEmitterBuilder SetConversation(AVIMConversation conversation) { _conversation = conversation; return this; } /// /// Gets the conversation. /// /// The conversation. public AVIMConversation Conversation { get { return _conversation; } } private AVIMSendOptions _sendOptions; /// /// Gets the send options. /// /// The send options. public AVIMSendOptions SendOptions { get { return _sendOptions; } } /// /// Sets the send options. /// /// The send options. /// Send options. public AVIMMessageEmitterBuilder SetSendOptions(AVIMSendOptions sendOptions) { _sendOptions = sendOptions; return this; } private IAVIMMessage _message; /// /// Gets the message. /// /// The message. public IAVIMMessage Message { get { return _message; } } /// /// Sets the message. /// /// The message. /// Message. public AVIMMessageEmitterBuilder SetMessage(IAVIMMessage message) { _message = message; return this; } /// /// Send async. /// /// The async. public Task SendAsync() { return this.Conversation.SendAsync(this.Message, this.SendOptions); } } }