diff --git a/Storage/Storage.Test/StatusTest.cs b/Storage/Storage.Test/StatusTest.cs index 002b653..930fb0f 100644 --- a/Storage/Storage.Test/StatusTest.cs +++ b/Storage/Storage.Test/StatusTest.cs @@ -85,6 +85,50 @@ namespace Storage.Test { Assert.AreEqual(followersAndFollowees.FolloweesCount, 1); } + [Test] + [Order(3)] + public async Task Send() { + await LCUser.BecomeWithSessionToken(user1.SessionToken); + + // 给粉丝发送状态 + LCStatus status = new LCStatus { + Data = new Dictionary { + { "image", "xxx.jpg" }, + { "content", "hello, world" } + } + }; + await LCStatus.SendToFollowers(status); + + // 给某个用户发送私信 + LCStatus privateStatus = new LCStatus { + Data = new Dictionary { + { "image", "xxx.jpg" }, + { "content", "hello, game" } + } + }; + await LCStatus.SendPrivately(status, user2.ObjectId); + } + + [Test] + [Order(4)] + public async Task Query() { + await LCUser.BecomeWithSessionToken(user2.SessionToken); + + LCStatusCount statusCount = await LCStatus.GetCount(LCStatus.InboxTypeDefault); + Assert.Greater(statusCount.Total, 0); + LCStatusCount privateCount = await LCStatus.GetCount(LCStatus.InboxTypePrivate); + Assert.Greater(privateCount.Total, 0); + + LCStatusQuery query = new LCStatusQuery(LCStatus.InboxTypeDefault); + ReadOnlyCollection statuses = await query.Find(); + foreach (LCStatus status in statuses) { + Assert.AreEqual((status["source"] as LCObject).ObjectId, user1.ObjectId); + await status.Delete(); + } + + await LCStatus.ResetUnreadCount(LCStatus.InboxTypePrivate); + } + [Test] [Order(5)] public async Task Unfollow() { diff --git a/Storage/Storage/Internal/Http/LCHttpClient.cs b/Storage/Storage/Internal/Http/LCHttpClient.cs index 85b4d1f..b5c1086 100644 --- a/Storage/Storage/Internal/Http/LCHttpClient.cs +++ b/Storage/Storage/Internal/Http/LCHttpClient.cs @@ -136,15 +136,25 @@ namespace LeanCloud.Storage.Internal.Http { throw HandleErrorResponse(response.StatusCode, resultString); } - public async Task Delete(string path) { - string url = await BuildUrl(path); + public async Task Delete(string path, + Dictionary headers = null, + object data = null, + Dictionary queryParams = null) { + string url = await BuildUrl(path, queryParams); HttpRequestMessage request = new HttpRequestMessage { RequestUri = new Uri(url), Method = HttpMethod.Delete }; - await FillHeaders(request.Headers); + await FillHeaders(request.Headers, headers); - LCHttpUtils.PrintRequest(client, request); + string content = null; + if (data != null) { + content = JsonConvert.SerializeObject(data); + StringContent requestContent = new StringContent(content); + requestContent.Headers.ContentType = new MediaTypeHeaderValue("application/json"); + request.Content = requestContent; + } + LCHttpUtils.PrintRequest(client, request, content); HttpResponseMessage response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead); request.Dispose(); @@ -153,7 +163,7 @@ namespace LeanCloud.Storage.Internal.Http { LCHttpUtils.PrintResponse(response, resultString); if (response.IsSuccessStatusCode) { - Dictionary ret = JsonConvert.DeserializeObject>(resultString, + _ = JsonConvert.DeserializeObject>(resultString, LCJsonConverter.Default); return; } diff --git a/Storage/Storage/LCApplication.cs b/Storage/Storage/LCApplication.cs index 6256fde..d659ed7 100644 --- a/Storage/Storage/LCApplication.cs +++ b/Storage/Storage/LCApplication.cs @@ -61,6 +61,7 @@ namespace LeanCloud { LCObject.RegisterSubclass(LCUser.CLASS_NAME, () => new LCUser()); LCObject.RegisterSubclass(LCRole.CLASS_NAME, () => new LCRole()); LCObject.RegisterSubclass(LCFile.CLASS_NAME, () => new LCFile()); + LCObject.RegisterSubclass(LCStatus.CLASS_NAME, () => new LCStatus()); LCObject.RegisterSubclass(LCFriendshipRequest.CLASS_NAME, () => new LCFriendshipRequest()); AppRouter = new LCAppRouter(appId, server); diff --git a/Storage/Storage/LCStatus.cs b/Storage/Storage/LCStatus.cs new file mode 100644 index 0000000..97030dc --- /dev/null +++ b/Storage/Storage/LCStatus.cs @@ -0,0 +1,176 @@ +using System; +using System.Threading.Tasks; +using System.Collections.Generic; +using LeanCloud.Storage.Internal.Codec; +using LeanCloud.Storage.Internal.Object; +using Newtonsoft.Json; + +namespace LeanCloud.Storage { + public class LCStatus : LCObject { + public const string CLASS_NAME = "_Status"; + + /// Public, shown on followees' timeline. + public const string InboxTypeDefault = "default"; + + /// Private. + public const string InboxTypePrivate = "private"; + + /// Keys + public const string SourceKey = "source"; + public const string InboxTypeKey = "inboxType"; + public const string OwnerKey = "owner"; + public const string MessageIdKey = "messageId"; + + public int MessageId { + get; internal set; + } + + public string InboxType { + get; internal set; + } + + private LCQuery query; + + public Dictionary Data { + get; set; + } + + public LCStatus() : base(CLASS_NAME) { + InboxType = InboxTypeDefault; + Data = new Dictionary(); + } + + public static async Task SendToFollowers(LCStatus status) { + if (status == null) { + throw new ArgumentNullException(nameof(status)); + } + LCUser user = await LCUser.GetCurrent(); + if (user == null) { + throw new ArgumentNullException("current user"); + } + + status.Data[SourceKey] = user; + + LCQuery query = new LCQuery("_Follower") + .WhereEqualTo("user", user) + .Select("follower"); + status.query = query; + + status.InboxType = InboxTypeDefault; + + return await status.Send(); + } + + public static async Task SendPrivately(LCStatus status, string targetId) { + if (status == null) { + throw new ArgumentNullException(nameof(status)); + } + if (string.IsNullOrEmpty(targetId)) { + throw new ArgumentNullException(nameof(targetId)); + } + LCUser user = await LCUser.GetCurrent(); + if (user == null) { + throw new ArgumentNullException("current user"); + } + + status.Data[SourceKey] = user; + LCQuery query = new LCQuery("_User") + .WhereEqualTo("objectId", targetId); + status.query = query; + + status.InboxType = InboxTypePrivate; + + return await status.Send(); + } + + public async Task Send() { + LCUser user = await LCUser.GetCurrent(); + if (user == null) { + throw new ArgumentNullException("current user"); + } + + Dictionary formData = new Dictionary { + { InboxTypeKey, InboxType } + }; + if (Data != null) { + formData["data"] = LCEncoder.Encode(Data); + } + if (query != null) { + Dictionary queryData = new Dictionary { + { "className", query.ClassName } + }; + Dictionary ps = query.BuildParams(); + if (ps.TryGetValue("where", out object whereObj) && + whereObj is string where) { + queryData["where"] = JsonConvert.DeserializeObject(where); + } + if (ps.TryGetValue("keys", out object keys)) { + queryData["keys"] = keys; + } + formData["query"] = queryData; + } + Dictionary response = await LCApplication.HttpClient.Post>("statuses", + data: formData); + LCObjectData objectData = LCObjectData.Decode(response); + Merge(objectData); + + return this; + } + + public new async Task Delete() { + LCUser user = await LCUser.GetCurrent(); + if (user == null) { + throw new ArgumentNullException("current user"); + } + + LCUser source = (Data[SourceKey] ?? this[SourceKey]) as LCUser; + if (source != null && source.ObjectId == user.ObjectId) { + await LCApplication.HttpClient.Delete($"statuses/{ObjectId}"); + } else { + Dictionary data = new Dictionary { + { OwnerKey, JsonConvert.SerializeObject(LCEncoder.Encode(user)) }, + { InboxTypeKey, InboxType }, + { MessageIdKey, MessageId } + }; + await LCApplication.HttpClient.Delete("subscribe/statuses/inbox", queryParams: data); + } + } + + public static async Task GetCount(string inboxType) { + LCUser user = await LCUser.GetCurrent(); + if (user == null) { + throw new ArgumentNullException("current user"); + } + + Dictionary queryParams = new Dictionary { + { OwnerKey, JsonConvert.SerializeObject(LCEncoder.Encode(user)) } + }; + if (!string.IsNullOrEmpty(inboxType)) { + queryParams[InboxTypeKey] = inboxType; + } + Dictionary response = await LCApplication.HttpClient.Get>("subscribe/statuses/count", + queryParams: queryParams); + LCStatusCount statusCount = new LCStatusCount { + Total = (int)response["total"], + Unread = (int)response["unread"] + }; + return statusCount; + } + + public static async Task ResetUnreadCount(string inboxType = null) { + LCUser user = await LCUser.GetCurrent(); + if (user == null) { + throw new ArgumentNullException("current user"); + } + + Dictionary queryParams = new Dictionary { + { OwnerKey, JsonConvert.SerializeObject(LCEncoder.Encode(user)) } + }; + if (!string.IsNullOrEmpty(inboxType)) { + queryParams[InboxTypeKey] = inboxType; + } + await LCApplication.HttpClient.Post>("subscribe/statuses/resetUnreadCount", + queryParams:queryParams); + } + } +} diff --git a/Storage/Storage/LCStatusCount.cs b/Storage/Storage/LCStatusCount.cs new file mode 100644 index 0000000..56ce6b4 --- /dev/null +++ b/Storage/Storage/LCStatusCount.cs @@ -0,0 +1,11 @@ +namespace LeanCloud.Storage { + public class LCStatusCount { + public int Total { + get; set; + } + + public int Unread { + get; set; + } + } +} diff --git a/Storage/Storage/LCStatusQuery.cs b/Storage/Storage/LCStatusQuery.cs new file mode 100644 index 0000000..473cd6a --- /dev/null +++ b/Storage/Storage/LCStatusQuery.cs @@ -0,0 +1,61 @@ +using System; +using System.Threading.Tasks; +using System.Collections; +using System.Collections.ObjectModel; +using System.Collections.Generic; +using Newtonsoft.Json; +using LeanCloud.Storage.Internal.Codec; +using LeanCloud.Storage.Internal.Object; + +namespace LeanCloud.Storage { + public class LCStatusQuery : LCQuery { + public string InboxType { + get; set; + } + + public int SinceId { + get; set; + } + + public int MaxId { + get; set; + } + + public LCStatusQuery(string inboxType = LCStatus.InboxTypeDefault) : base("_Status") { + InboxType = inboxType; + SinceId = 0; + MaxId = 0; + } + + public async Task> Find() { + LCUser user = await LCUser.GetCurrent(); + if (user == null) { + throw new ArgumentNullException("current user"); + } + + Dictionary queryParams = new Dictionary { + { LCStatus.OwnerKey, JsonConvert.SerializeObject(LCEncoder.Encode(user)) }, + { LCStatus.InboxTypeKey, InboxType }, + { "where", BuildWhere() }, + { "sinceId", SinceId }, + { "maxId", MaxId }, + { "limit", Condition.Limit } + }; + Dictionary response = await LCApplication.HttpClient.Get>("subscribe/statuses", + queryParams: queryParams); + List results = response["results"] as List; + List statuses = new List(); + foreach (object item in results) { + LCObjectData objectData = LCObjectData.Decode(item as IDictionary); + LCStatus status = new LCStatus(); + status.Merge(objectData); + status.MessageId = (int)objectData.CustomPropertyDict[LCStatus.MessageIdKey]; + status.Data = objectData.CustomPropertyDict; + status.InboxType = objectData.CustomPropertyDict[LCStatus.InboxTypeKey] as string; + statuses.Add(status); + } + + return statuses.AsReadOnly(); + } + } +}