diff --git a/FeedlySharp/Endpoints/Markers.cs b/FeedlySharp/Endpoints/Markers.cs new file mode 100644 index 0000000..797ed4d --- /dev/null +++ b/FeedlySharp/Endpoints/Markers.cs @@ -0,0 +1,118 @@ +using FeedlySharp.Models; +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using System.Linq; +using System.Dynamic; + +namespace FeedlySharp +{ + public partial class FeedlyClient + { + public async Task> GetMarkersUnreadCount(bool isAutoRefresh = false, DateTime? newerThan = null, string categoryId = null, CancellationToken cancellationToken = default(CancellationToken)) + { + Dictionary parameters = new Dictionary(); + if (isAutoRefresh) + { + parameters["autorefresh"] = "true"; + } + if (newerThan.HasValue) + { + DateTime date = ((DateTime)newerThan.Value).ToUniversalTime(); + DateTime epoc = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + parameters["newerThan"] = Math.Truncate(date.Subtract(epoc).TotalMilliseconds).ToString(); + } + if (!String.IsNullOrEmpty(categoryId)) + { + parameters["streamId"] = ValueToResource("category", categoryId, false); + } + return (await Client.Request(HttpMethod.Get, "v3/markers/counts", parameters, false, true, cancellationToken)).List; + } + + + public async Task MarkEntriesAsRead(string[] entryIds, CancellationToken cancellationToken = default(CancellationToken)) + { + await Client.Request(HttpMethod.Post, "v3/markers", new { action = "markAsRead", entryIds = entryIds, type = "entries" }, true, true, cancellationToken); + return true; + } + + public async Task MarkEntryAsRead(string entryId, CancellationToken cancellationToken = default(CancellationToken)) + { + return await MarkEntriesAsRead(new string[] { entryId }, cancellationToken); + } + + + public async Task KeepEntriesAsUnread(string[] entryIds, CancellationToken cancellationToken = default(CancellationToken)) + { + await Client.Request(HttpMethod.Post, "v3/markers", new { action = "keepUnread", entryIds = entryIds, type = "entries" }, true, true, cancellationToken); + return true; + } + + public async Task KeepEntryAsUnread(string entryId, CancellationToken cancellationToken = default(CancellationToken)) + { + return await KeepEntriesAsUnread(new string[] { entryId }, cancellationToken); + } + + + public async Task MarkFeedsAsRead(string[] feedIds, CancellationToken cancellationToken = default(CancellationToken)) + { + await Client.Request(HttpMethod.Post, "v3/markers", new { action = "markAsRead", feedIds = feedIds, type = "feeds" }, true, true, cancellationToken); + return true; + } + + public async Task MarkFeedAsRead(string feedId, CancellationToken cancellationToken = default(CancellationToken)) + { + return await MarkFeedsAsRead(new string[] { feedId }, cancellationToken); + } + + + public async Task MarkCategoriesAsRead(string[] categoryIds, CancellationToken cancellationToken = default(CancellationToken)) + { + categoryIds = categoryIds.Select(x => ValueToResource("category", x, false)).ToArray(); + await Client.Request(HttpMethod.Post, "v3/markers", new { action = "markAsRead", categoryIds = categoryIds, type = "categories" }, true, true, cancellationToken); + return true; + } + + public async Task MarkCategoryAsRead(string categoryId, CancellationToken cancellationToken = default(CancellationToken)) + { + return await MarkCategoriesAsRead(new string[] { categoryId }, cancellationToken); + } + + + public async Task MarkEntriesAsSaved(string[] entryIds, CancellationToken cancellationToken = default(CancellationToken)) + { + await Client.Request(HttpMethod.Post, "v3/markers", new { action = "markAsSaved", categoryIds = entryIds, type = "entries" }, true, true, cancellationToken); + return true; + } + + public async Task MarkEntryAsSaved(string entryId, CancellationToken cancellationToken = default(CancellationToken)) + { + return await MarkEntriesAsSaved(new string[] { entryId }, cancellationToken); + } + + + public async Task MarkEntriesAsUnsaved(string[] entryIds, CancellationToken cancellationToken = default(CancellationToken)) + { + await Client.Request(HttpMethod.Post, "v3/markers", new { action = "markAsUnsaved", categoryIds = entryIds, type = "entries" }, true, true, cancellationToken); + return true; + } + + public async Task MarkEntryAsUnsaved(string entryId, CancellationToken cancellationToken = default(CancellationToken)) + { + return await MarkEntriesAsUnsaved(new string[] { entryId }, cancellationToken); + } + + + public async Task GetMarkersReadOperations(DateTime newerThan, CancellationToken cancellationToken = default(CancellationToken)) + { + return await Client.Request(HttpMethod.Get, "v3/markers/reads", new Dictionary() + { + { "newerThan", Math.Truncate(newerThan.ToUniversalTime().Subtract(new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalMilliseconds).ToString() } + }, false, true, cancellationToken); + } + + } +} diff --git a/FeedlySharp/Extensions/UriExtensions.cs b/FeedlySharp/Extensions/UriExtensions.cs index 8694697..d690504 100644 --- a/FeedlySharp/Extensions/UriExtensions.cs +++ b/FeedlySharp/Extensions/UriExtensions.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Net; +using System.Text; namespace FeedlySharp.Extensions { @@ -23,5 +24,29 @@ namespace FeedlySharp.Extensions return output; } + + + internal static string ToQueryString(this IDictionary dict) + { + if (dict.Count == 0) return string.Empty; + + var buffer = new StringBuilder(); + int count = 0; + bool end = false; + + foreach (var key in dict.Keys) + { + string value = WebUtility.UrlEncode(dict[key]); + + if (count == dict.Count - 1) end = true; + + if (end) buffer.AppendFormat("{0}={1}", key, value); + else buffer.AppendFormat("{0}={1}&", key, value); + + count++; + } + + return buffer.ToString(); + } } } diff --git a/FeedlySharp/FeedlyClient.cs b/FeedlySharp/FeedlyClient.cs index 41f0a4f..9dfe6ef 100644 --- a/FeedlySharp/FeedlyClient.cs +++ b/FeedlySharp/FeedlyClient.cs @@ -7,7 +7,7 @@ using System.Threading.Tasks; namespace FeedlySharp { - public partial class FeedlyClient : IDisposable, FeedlySharp.IFeedlyClient + public partial class FeedlyClient : IDisposable { public readonly CloudEnvironment Environment; @@ -56,7 +56,7 @@ namespace FeedlySharp private string ValueToResource(string key, string value, bool encode = true) { - string text = String.Format("user/{0}/{1}/{2}", UserId, key, value); + string text = value.StartsWith("user/") ? value : String.Format("user/{0}/{1}/{2}", UserId, key, value); return encode ? WebUtility.UrlEncode(text) : text; } diff --git a/FeedlySharp/FeedlyHttpClient.cs b/FeedlySharp/FeedlyHttpClient.cs index d91478d..99e8ff4 100644 --- a/FeedlySharp/FeedlyHttpClient.cs +++ b/FeedlySharp/FeedlyHttpClient.cs @@ -58,14 +58,15 @@ namespace FeedlySharp CancellationToken cancellationToken = default(CancellationToken) ) { - HttpRequestMessage request = new HttpRequestMessage(method, requestUri); + string append = body != null && !bodyAsJson && method == HttpMethod.Get ? ((requestUri.Contains("?") ? "&" : "?") + (body as Dictionary).ToQueryString()) : ""; + HttpRequestMessage request = new HttpRequestMessage(method, requestUri + append); // content of the request - if (body != null && !bodyAsJson) + if (body != null && !bodyAsJson && method != HttpMethod.Get) { request.Content = new FormUrlEncodedContent(body as Dictionary); } - else if (body != null) + else if (body != null && method != HttpMethod.Get) { request.Content = new StringContent(JsonConvert.SerializeObject(body)); } diff --git a/FeedlySharp/FeedlySharp.csproj b/FeedlySharp/FeedlySharp.csproj index 2303744..30374c4 100644 --- a/FeedlySharp/FeedlySharp.csproj +++ b/FeedlySharp/FeedlySharp.csproj @@ -36,13 +36,16 @@ + + + diff --git a/FeedlySharp/IFeedlyClient.cs b/FeedlySharp/IFeedlyClient.cs deleted file mode 100644 index f12d3e5..0000000 --- a/FeedlySharp/IFeedlyClient.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System; -using System.Threading; - -namespace FeedlySharp -{ - public interface IFeedlyClient - { - void Activate(string accessToken, string userId); - System.Threading.Tasks.Task AddOrUpdateSubscription(string id, System.Collections.Generic.List categories = null, string optionalTitle = null, System.Threading.CancellationToken cancellationToken = default(CancellationToken)); - System.Threading.Tasks.Task AddOrUpdateTopic(string topic, FeedlySharp.Models.Interest interest = Interest.Low, System.Threading.CancellationToken cancellationToken = default(CancellationToken)); - System.Threading.Tasks.Task DeleteCategory(string id, System.Threading.CancellationToken cancellationToken = default(CancellationToken)); - void Dispose(); - string GetAuthenticationUri(string scope = "subscriptions", string state = default(String)); - System.Threading.Tasks.Task> GetCategories(System.Threading.CancellationToken cancellationToken = default(CancellationToken)); - System.Threading.Tasks.Task> GetEntries(string[] ids, System.Threading.CancellationToken cancellationToken = default(CancellationToken)); - System.Threading.Tasks.Task GetEntry(string id, System.Threading.CancellationToken cancellationToken = default(CancellationToken)); - System.Threading.Tasks.Task GetFeed(string id, System.Threading.CancellationToken cancellationToken = default(CancellationToken)); - System.Threading.Tasks.Task> GetFeeds(string[] ids, System.Threading.CancellationToken cancellationToken = default(CancellationToken)); - System.Threading.Tasks.Task GetOPML(System.Threading.CancellationToken cancellationToken = default(CancellationToken)); - System.Threading.Tasks.Task> GetPreferences(System.Threading.CancellationToken cancellationToken = default(CancellationToken)); - System.Threading.Tasks.Task GetProfile(System.Threading.CancellationToken cancellationToken = default(CancellationToken)); - System.Threading.Tasks.Task> GetSubscriptions(System.Threading.CancellationToken cancellationToken = default(CancellationToken)); - System.Threading.Tasks.Task> GetTags(System.Threading.CancellationToken cancellationToken = default(CancellationToken)); - System.Threading.Tasks.Task> GetTopics(System.Threading.CancellationToken cancellationToken = default(CancellationToken)); - System.Threading.Tasks.Task ImportOPML(string opml, System.Threading.CancellationToken cancellationToken = default(CancellationToken)); - FeedlySharp.Models.AuthenticationResponse ParseAuthenticationResponseUri(string uri); - FeedlySharp.Models.AuthenticationResponse ParseAuthenticationResponseUri(Uri uri); - System.Threading.Tasks.Task RemoveSubscription(string id, System.Threading.CancellationToken cancellationToken = default(CancellationToken)); - System.Threading.Tasks.Task RemoveTags(string[] entryIds, string[] tags, System.Threading.CancellationToken cancellationToken = default(CancellationToken)); - System.Threading.Tasks.Task RemoveTags(string[] tags, System.Threading.CancellationToken cancellationToken = default(CancellationToken)); - System.Threading.Tasks.Task RemoveTopic(string topic, FeedlySharp.Models.Interest interest = Interest.Low, System.Threading.CancellationToken cancellationToken = default(CancellationToken)); - System.Threading.Tasks.Task RenameCategory(string id, string label, System.Threading.CancellationToken cancellationToken = default(CancellationToken)); - System.Threading.Tasks.Task RenameTag(string oldTag, string newTag, System.Threading.CancellationToken cancellationToken = default(CancellationToken)); - System.Threading.Tasks.Task RequestAccessToken(string authenticationCode, System.Threading.CancellationToken cancellationToken = default(CancellationToken)); - System.Threading.Tasks.Task RequestRefreshToken(string refreshToken, System.Threading.CancellationToken cancellationToken = default(CancellationToken)); - System.Threading.Tasks.Task RevokeRefreshToken(string refreshToken, System.Threading.CancellationToken cancellationToken = default(CancellationToken)); - System.Threading.Tasks.Task> UpdatePreferences(System.Collections.Generic.Dictionary preferences, System.Threading.CancellationToken cancellationToken = default(CancellationToken)); - System.Threading.Tasks.Task UpdateProfile(System.Collections.Generic.Dictionary parameters, System.Threading.CancellationToken cancellationToken = default(CancellationToken)); - System.Threading.Tasks.Task UpdateTags(string entryId, string[] tags, System.Threading.CancellationToken cancellationToken = default(CancellationToken)); - System.Threading.Tasks.Task UpdateTags(string[] entryIds, string[] tags, System.Threading.CancellationToken cancellationToken = default(CancellationToken)); - } -} diff --git a/FeedlySharp/Models/FeedlyCategoryUnreadCount.cs b/FeedlySharp/Models/FeedlyCategoryUnreadCount.cs new file mode 100644 index 0000000..54f2526 --- /dev/null +++ b/FeedlySharp/Models/FeedlyCategoryUnreadCount.cs @@ -0,0 +1,21 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace FeedlySharp.Models +{ + public class FeedlyCategoryUnreadCount + { + [JsonProperty("updated")] + public DateTime? UpdatedAt { get; set; } + + public int Count { get; set; } + + public string Id { get; set; } + + public string Name { get { return Id == null ? String.Empty : Id.Split('/').Last(); } } + } +} diff --git a/FeedlySharp/Models/FeedlyReadOperations.cs b/FeedlySharp/Models/FeedlyReadOperations.cs new file mode 100644 index 0000000..f2f1dbf --- /dev/null +++ b/FeedlySharp/Models/FeedlyReadOperations.cs @@ -0,0 +1,29 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace FeedlySharp.Models +{ + public class FeedlyReadOperations + { + [JsonProperty("feeds")] + public List ReadFeeds { get; set; } + + [JsonProperty("entries")] + public List ReadEntries { get; set; } + + [JsonProperty("unread")] + public List UnreadEntries { get; set; } + } + + public class FeedlyReadOperationsFeed + { + [JsonProperty("asOf")] + public DateTime? ReadAt { get; set; } + + public string Id { get; set; } + } +} diff --git a/FeedlySharp/Models/Responses.cs b/FeedlySharp/Models/Responses.cs index df5e234..47ab75a 100644 --- a/FeedlySharp/Models/Responses.cs +++ b/FeedlySharp/Models/Responses.cs @@ -1,6 +1,7 @@ using FeedlySharp.Extensions; using Newtonsoft.Json; using System; +using System.Collections.Generic; namespace FeedlySharp.Models { @@ -35,6 +36,13 @@ namespace FeedlySharp.Models } + internal class CategoryUnreadCountResponse + { + [JsonProperty("unreadcounts")] + public List List { get; set; } + } + + public enum FeedlyUserAccountPlan { Standard,