csharp-sdk-upm/RTM/Source/Public/AVIMClient.cs

1196 lines
45 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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
{
/// <summary>
/// 代表一个实时通信的终端用户
/// </summary>
public class AVIMClient
{
private readonly string clientId;
private readonly AVRealtime _realtime;
internal readonly object mutex = new object();
internal readonly object patchMutex = new object();
/// <summary>
/// 一些可变的配置选项,便于应对各种需求场景
/// </summary>
public struct Configuration
{
/// <summary>
/// Gets or sets a value indicating whether this <see cref="T:LeanCloud.Realtime.AVIMClient.Configuration"/>
/// auto read.
/// </summary>
/// <value><c>true</c> if auto read; otherwise, <c>false</c>.</value>
public bool AutoRead { get; set; }
}
/// <summary>
/// Gets or sets the current configuration.
/// </summary>
/// <value>The current configuration.</value>
public Configuration CurrentConfiguration
{
get; set;
}
internal AVRealtime LinkedRealtime
{
get { return _realtime; }
}
/// <summary>
/// 单点登录所使用的 Tag
/// </summary>
public string Tag
{
get;
private set;
}
/// <summary>
/// 客户端的标识,在一个 Application 内唯一。
/// </summary>
public string ClientId
{
get { return clientId; }
}
//private EventHandler<AVIMNotice> m_OnNoticeReceived;
///// <summary>
///// 接收到服务器的命令时触发的事件
///// </summary>
//public event EventHandler<AVIMNotice> OnNoticeReceived
//{
// add
// {
// m_OnNoticeReceived += value;
// }
// remove
// {
// m_OnNoticeReceived -= value;
// }
//}
private int onMessageReceivedCount = 0;
private EventHandler<AVIMMessageEventArgs> m_OnMessageReceived;
/// <summary>
/// 接收到聊天消息的事件通知
/// </summary>
public event EventHandler<AVIMMessageEventArgs> 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;
}
}
/// <summary>
/// Occurs when on members joined.
/// </summary>
public event EventHandler<AVIMOnMembersJoinedEventArgs> OnMembersJoined;
/// <summary>
/// Occurs when on members left.
/// </summary>
public event EventHandler<AVIMOnMembersLeftEventArgs> OnMembersLeft;
/// <summary>
/// Occurs when on kicked.
/// </summary>
public event EventHandler<AVIMOnKickedEventArgs> OnKicked;
/// <summary>
/// Occurs when on invited.
/// </summary>
public event EventHandler<AVIMOnInvitedEventArgs> OnInvited;
internal event EventHandler<AVIMMessageEventArgs> OnOfflineMessageReceived;
private EventHandler<AVIMSessionClosedEventArgs> m_OnSessionClosed;
/// <summary>
/// 当前打开的链接被迫关闭时触发的事件回调
/// <remarks>可能的原因有单点登录冲突,或者被 REST API 强制踢下线</remarks>
/// </summary>
public event EventHandler<AVIMSessionClosedEventArgs> OnSessionClosed
{
add
{
m_OnSessionClosed += value;
}
remove
{
m_OnSessionClosed -= value;
}
}
/// <summary>
/// 创建 AVIMClient 对象
/// </summary>
/// <param name="clientId"></param>
/// <param name="realtime"></param>
internal AVIMClient(string clientId, AVRealtime realtime)
: this(clientId, null, realtime)
{
}
/// <summary>
///
/// </summary>
/// <param name="clientId"></param>
/// <param name="tag"></param>
/// <param name="realtime"></param>
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
/// <summary>
/// 注册 IAVIMListener
/// </summary>
/// <param name="listener"></param>
/// <param name="runtimeHook"></param>
public void RegisterListener(IAVIMListener listener, Func<AVIMNotice, bool> runtimeHook = null)
{
_realtime.SubscribeNoticeReceived(listener, runtimeHook);
}
#region get client instance
/// <summary>
/// Get the specified clientId.
/// </summary>
/// <returns>The get.</returns>
/// <param name="clientId">Client identifier.</param>
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
/// <summary>
/// 创建对话
/// </summary>
/// <param name="conversation">对话</param>
/// <param name="isUnique">是否创建唯一对话,当 isUnique 为 true 时,如果当前已经有相同成员的对话存在则返回该对话,否则会创建新的对话。该值默认为 false。</param>
/// <returns></returns>
internal Task<AVIMConversation> 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();
}
/// <summary>
/// 创建与目标成员的对话.
/// </summary>
/// <returns>返回对话实例.</returns>
/// <param name="member">目标成员.</param>
/// <param name="members">目标成员列表.</param>
/// <param name="name">对话名称.</param>
/// <param name="isSystem">是否是系统对话.</param>
/// <param name="isTransient">是否为暂态对话(聊天室).</param>
/// <param name="isUnique">是否是唯一对话.</param>
/// <param name="options">自定义属性.</param>
public Task<AVIMConversation> CreateConversationAsync(string member = null,
IEnumerable<string> members = null,
string name = "",
bool isSystem = false,
bool isTransient = false,
bool isUnique = true,
bool isTemporary = false,
int ttl = 86400,
IDictionary<string, object> options = null)
{
if (member == null) member = ClientId;
var membersAsList = Concat<string>(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);
}
/// <summary>
/// Creates the conversation async.
/// </summary>
/// <returns>The conversation async.</returns>
/// <param name="builder">Builder.</param>
public Task<AVIMConversation> CreateConversationAsync(IAVIMConversatioBuilder builder)
{
var conversation = builder.Build();
return CreateConversationAsync(conversation, conversation.IsUnique);
}
/// <summary>
/// Gets the conversatio builder.
/// </summary>
/// <returns>The conversatio builder.</returns>
public AVIMConversationBuilder GetConversationBuilder()
{
var builder = AVIMConversationBuilder.CreateDefaultBuilder();
builder.Client = this;
return builder;
}
/// <summary>
/// 创建虚拟对话,对话 id 是由本地直接生成,云端根据规则消息发送给指定的 client id(s)
/// </summary>
/// <param name="member"></param>
/// <param name="members"></param>
/// <param name="ttl">过期时间,默认是一天(86400 秒),单位是秒</param>
/// <returns></returns>
public Task<AVIMConversation> CreateTemporaryConversationAsync(string member = null,
IEnumerable<string> members = null, int ttl = 86400)
{
if (member == null) member = ClientId;
var membersAsList = Concat<string>(member, members, "创建对话时被操作的 member(s) 不可以为空。");
return CreateConversationAsync(member, membersAsList, isTemporary: true, ttl: ttl);
}
/// <summary>
/// 创建聊天室(即:暂态对话)
/// </summary>
/// <param name="chatroomName">聊天室名称</param>
/// <returns></returns>
public Task<AVIMConversation> CreateChatRoomAsync(string chatroomName)
{
return CreateConversationAsync(name: chatroomName, isTransient: true);
}
/// <summary>
/// 获取一个对话
/// </summary>
/// <param name="id">对话的 ID</param>
/// <param name="noCache">从服务器获取</param>
/// <returns></returns>
public Task<AVIMConversation> 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
/// <summary>
/// 向目标对话发送消息
/// </summary>
/// <param name="conversation">目标对话</param>
/// <param name="message">消息体</param>
/// <returns></returns>
public Task<IAVIMMessage> SendMessageAsync(
AVIMConversation conversation,
IAVIMMessage message)
{
return this.SendMessageAsync(conversation, message, new AVIMSendOptions()
{
Receipt = true,
Transient = false,
Priority = 1,
Will = false,
PushData = null,
});
}
/// <summary>
/// 向目标对话发送消息
/// </summary>
/// <param name="conversation">目标对话</param>
/// <param name="message">消息体</param>
/// <param name="options">消息的发送选项,包含了一些特殊的标记<see cref="AVIMSendOptions"/></param>
/// <returns></returns>
public Task<IAVIMMessage> 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
/// <summary>
/// 当前用户对目标对话进行静音操作
/// </summary>
/// <param name="conversation"></param>
/// <returns></returns>
public Task MuteConversationAsync(AVIMConversation conversation)
{
var convCmd = new ConversationCommand()
.ConversationId(conversation.ConversationId)
.Option("mute")
.PeerId(this.ClientId);
return this.RunCommandAsync(convCmd);
}
/// <summary>
/// 当前用户对目标对话取消静音,恢复该对话的离线消息推送
/// </summary>
/// <param name="conversation"></param>
/// <returns></returns>
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<string> members = null)
{
if (string.IsNullOrEmpty(conversation.ConversationId))
{
throw new Exception("conversation id 不可以为空。");
}
var membersAsList = Concat<string>(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<string>();
conversation.MemberIds = conversation.MemberIds.Concat(membersAsList);
}
return result;
});
}).Unwrap();
}
internal IEnumerable<T> Concat<T>(T single, IEnumerable<T> collection, string exString = null)
{
List<T> asList = null;
if (collection == null)
{
collection = new List<T>();
}
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
/// <summary>
/// 当前用户加入到目标的对话中
/// </summary>
/// <param name="conversation">目标对话</param>
/// <returns></returns>
public Task JoinAsync(AVIMConversation conversation)
{
return this.OperateMembersAsync(conversation, "add", this.ClientId);
}
#endregion
#region Invite
/// <summary>
/// 直接将其他人加入到目标对话
/// <remarks>被操作的人会在客户端会触发 OnInvited 事件,而已经存在于对话的用户会触发 OnMembersJoined 事件</remarks>
/// </summary>
/// <param name="conversation">目标对话</param>
/// <param name="member">单个的 Client Id</param>
/// <param name="members">Client Id 集合</param>
/// <returns></returns>
public Task InviteAsync(AVIMConversation conversation, string member = null, IEnumerable<string> members = null)
{
return this.OperateMembersAsync(conversation, "add", member, members);
}
#endregion
#region Left
/// <summary>
/// 当前 Client 离开目标对话
/// <remarks>可以理解为是 QQ 群的退群操作</remarks>
/// <remarks></remarks>
/// </summary>
/// <param name="conversation">目标对话</param>
/// <returns></returns>
[Obsolete("use LeaveAsync instead.")]
public Task LeftAsync(AVIMConversation conversation)
{
return this.OperateMembersAsync(conversation, "remove", this.ClientId);
}
/// <summary>
/// Leaves the conversation async.
/// </summary>
/// <returns>The async.</returns>
/// <param name="conversation">Conversation.</param>
public Task LeaveAsync(AVIMConversation conversation)
{
return this.OperateMembersAsync(conversation, "remove", this.ClientId);
}
#endregion
#region Kick
/// <summary>
/// 从目标对话中剔除成员
/// </summary>
/// <param name="conversation">目标对话</param>
/// <param name="member">被剔除的单个成员</param>
/// <param name="members">被剔除的成员列表</param>
/// <returns></returns>
public Task KickAsync(AVIMConversation conversation, string member = null, IEnumerable<string> members = null)
{
return this.OperateMembersAsync(conversation, "remove", member, members);
}
#endregion
#endregion
#region Query && Message history && ack
/// <summary>
/// Get conversation query.
/// </summary>
/// <returns></returns>
public AVIMConversationQuery GetQuery()
{
return GetConversationQuery();
}
/// <summary>
/// Get conversation query.
/// </summary>
/// <returns>The conversation query.</returns>
public AVIMConversationQuery GetConversationQuery()
{
return new AVIMConversationQuery(this);
}
#region load message history
/// <summary>
/// 查询目标对话的历史消息
/// <remarks>不支持聊天室(暂态对话)</remarks>
/// </summary>
/// <param name="conversation">目标对话</param>
/// <param name="beforeMessageId">从 beforeMessageId 开始向前查询(和 beforeTimeStampPoint 共同使用,为防止某毫秒时刻有重复消息)</param>
/// <param name="afterMessageId"> 截止到某个 afterMessageId (不包含)</param>
/// <param name="beforeTimeStampPoint">从 beforeTimeStampPoint 开始向前查询</param>
/// <param name="afterTimeStampPoint">拉取截止到 afterTimeStampPoint 时间戳(不包含)</param>
/// <param name="direction">查询方向,默认是 1如果是 1 表示从新消息往旧消息方向, 0 则相反,其他值无效</param>
/// <param name="limit">拉取消息条数,默认值 20 条,可设置为 1 - 1000 之间的任意整数</param>
/// <returns></returns>
public Task<IEnumerable<T>> QueryMessageAsync<T>(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<IAVIMMessage>();
var result = t.Result.Item2;
var logs = result["logs"] as List<object>;
if (logs != null)
{
foreach (var log in logs)
{
var logMap = log as IDictionary<string, object>;
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<T>();
});
}
#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<Tuple<long, long>> 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<long, long>(maxAckTimestamp, maxReadTimestamp);
});
}
#endregion
#region 查询对方是否在线
/// <summary>
/// 查询对方 client Id 是否在线
/// </summary>
/// <param name="targetClientId">单个 client Id</param>
/// <param name="targetClientIds">多个 client Id 集合</param>
/// <returns></returns>
public Task<IEnumerable<Tuple<string, bool>>> PingAsync(string targetClientId = null, IEnumerable<string> targetClientIds = null)
{
List<string> 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<Tuple<string, bool>> rtn = new List<Tuple<string, bool>>();
var onlineSessionPeerIds = AVDecoder.Instance.DecodeList<string>(result.Item2["onlineSessionPeerIds"]);
foreach (var peerId in targetClientIds)
{
rtn.Add(new Tuple<string, bool>(peerId, onlineSessionPeerIds.Contains(peerId)));
}
return rtn.AsEnumerable();
});
}
#endregion
#region 获取暂态对话在线人数
/// <summary>
/// 获取暂态对话(聊天室)在线人数,依赖缓存,并不一定 100% 与真实数据一致。
/// </summary>
/// <param name="chatroomId"></param>
/// <returns></returns>
public Task<int> 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
/// <summary>
///
/// </summary>
/// <param name="conversation"></param>
/// <param name="message"></param>
/// <param name="readAt"></param>
/// <returns></returns>
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);
}
/// <summary>
/// mark the conversation as read with conversation id.
/// </summary>
/// <param name="conversationId">conversation id</param>
/// <returns></returns>
public Task ReadAsync(string conversationId)
{
var conv = AVIMConversation.CreateWithoutData(conversationId, this);
return this.ReadAsync(conv);
}
/// <summary>
/// mark all conversations as read.
/// </summary>
/// <returns></returns>
public Task ReadAllAsync()
{
var cids = ConversationUnreadListener.FindAllConvIds();
var readCmd = new ReadCommand().ConvIds(cids).PeerId(this.ClientId);
return this.RunCommandAsync(readCmd);
}
#endregion
#region recall & modify
/// <summary>
/// Recalls the async.
/// </summary>
/// <returns>The async.</returns>
/// <param name="message">Message.</param>
public Task<AVIMRecalledMessage> RecallAsync(IAVIMMessage message)
{
var tcs = new TaskCompletionSource<AVIMRecalledMessage>();
var patchCmd = new PatchCommand().Recall(message);
RunCommandAsync(patchCmd)
.OnSuccess(t => {
var recalledMsg = new AVIMRecalledMessage();
AVIMMessage.CopyMetaData(message, recalledMsg);
tcs.SetResult(recalledMsg);
});
return tcs.Task;
}
/// <summary>
/// Modifies the aysnc.
/// </summary>
/// <returns>The aysnc.</returns>
/// <param name="oldMessage">要修改的消息对象</param>
/// <param name="newMessage">新的消息对象</param>
public Task<IAVIMMessage> UpdateAsync(IAVIMMessage oldMessage, IAVIMMessage newMessage)
{
var tcs = new TaskCompletionSource<IAVIMMessage>();
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<AVIMMessagePatchEventArgs> m_OnMessageRecalled;
/// <summary>
/// Occurs when on message recalled.
/// </summary>
public event EventHandler<AVIMMessagePatchEventArgs> OnMessageRecalled
{
add
{
this.m_OnMessageRecalled += value;
}
remove
{
this.m_OnMessageRecalled -= value;
}
}
internal EventHandler<AVIMMessagePatchEventArgs> m_OnMessageUpdated;
/// <summary>
/// Occurs when on message modified.
/// </summary>
public event EventHandler<AVIMMessagePatchEventArgs> OnMessageUpdated
{
add
{
this.m_OnMessageUpdated += value;
}
remove
{
this.m_OnMessageUpdated -= value;
}
}
#endregion
#region log out
/// <summary>
/// 退出登录或者切换账号
/// </summary>
/// <returns></returns>
public Task CloseAsync()
{
var cmd = new SessionCommand().Option("close");
return this.RunCommandAsync(cmd).ContinueWith(t =>
{
m_OnSessionClosed(this, null);
});
}
#endregion
/// <summary>
/// Run command async.
/// </summary>
/// <returns>The command async.</returns>
/// <param name="command">Command.</param>
public Task<Tuple<int, IDictionary<string, object>>> RunCommandAsync(AVIMCommand command)
{
command.PeerId(this.ClientId);
return this.LinkedRealtime.RunCommandAsync(command);
}
/// <summary>
/// Run command.
/// </summary>
/// <param name="command">Command.</param>
public void RunCommand(AVIMCommand command)
{
command.PeerId(this.ClientId);
this.LinkedRealtime.RunCommand(command);
}
}
/// <summary>
/// AVIMClient extensions.
/// </summary>
public static class AVIMClientExtensions
{
/// <summary>
/// Create conversation async.
/// </summary>
/// <returns>The conversation async.</returns>
/// <param name="client">Client.</param>
/// <param name="members">Members.</param>
public static Task<AVIMConversation> CreateConversationAsync(this AVIMClient client, IEnumerable<string> members)
{
return client.CreateConversationAsync(members: members);
}
public static Task<AVIMConversation> CreateConversationAsync(this AVIMClient client, IEnumerable<string> members, string conversationName)
{
return client.CreateConversationAsync(members: members, name: conversationName);
}
/// <summary>
/// Get conversation.
/// </summary>
/// <returns>The conversation.</returns>
/// <param name="client">Client.</param>
/// <param name="conversationId">Conversation identifier.</param>
public static AVIMConversation GetConversation(this AVIMClient client, string conversationId)
{
return AVIMConversation.CreateWithoutData(conversationId, client);
}
/// <summary>
/// Join conversation async.
/// </summary>
/// <returns>The async.</returns>
/// <param name="client">Client.</param>
/// <param name="conversationId">Conversation identifier.</param>
public static Task JoinAsync(this AVIMClient client, string conversationId)
{
var conversation = client.GetConversation(conversationId);
return client.JoinAsync(conversation);
}
/// <summary>
/// Leave conversation async.
/// </summary>
/// <returns>The async.</returns>
/// <param name="client">Client.</param>
/// <param name="conversationId">Conversation identifier.</param>
public static Task LeaveAsync(this AVIMClient client, string conversationId)
{
var conversation = client.GetConversation(conversationId);
return client.LeaveAsync(conversation);
}
/// <summary>
/// Query messages.
/// </summary>
/// <returns>The message async.</returns>
/// <param name="client">Client.</param>
/// <param name="conversation">Conversation.</param>
/// <param name="beforeMessageId">Before message identifier.</param>
/// <param name="afterMessageId">After message identifier.</param>
/// <param name="beforeTimeStampPoint">Before time stamp point.</param>
/// <param name="afterTimeStampPoint">After time stamp point.</param>
/// <param name="direction">Direction.</param>
/// <param name="limit">Limit.</param>
public static Task<IEnumerable<IAVIMMessage>> 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<IAVIMMessage>(conversation,
beforeMessageId,
afterMessageId,
beforeTimeStampPoint,
afterTimeStampPoint,
direction,
limit);
}
/// <summary>
/// Get the chat room query.
/// </summary>
/// <returns>The chat room query.</returns>
/// <param name="client">Client.</param>
public static AVIMConversationQuery GetChatRoomQuery(this AVIMClient client)
{
return client.GetQuery().WhereEqualTo("tr", true);
}
}
}