├── .nuget ├── NuGet.exe ├── NuGet.Config └── NuGet.targets ├── Assets ├── pocketsharp.png ├── github-header.png └── authentication-screen.png ├── PocketSharp.Examples └── PocketSharp.UWP │ ├── FodyWeavers.xml │ ├── Assets │ ├── StoreLogo.png │ ├── SplashScreen.scale-200.png │ ├── LockScreenLogo.scale-200.png │ ├── Square44x44Logo.scale-200.png │ ├── Wide310x150Logo.scale-200.png │ ├── Square150x150Logo.scale-200.png │ └── Square44x44Logo.targetsize-24_altform-unplated.png │ ├── App.xaml │ ├── project.json │ ├── Properties │ ├── AssemblyInfo.cs │ └── Default.rd.xml │ ├── MainPage.xaml.cs │ ├── MainPage.xaml │ ├── Package.appxmanifest │ ├── PocketSharp.UWP.nuget.targets │ ├── App.xaml.cs │ ├── PocketSharp.UWP.nuget.props │ └── PocketSharp.UWP.csproj ├── PocketSharp ├── Utilities │ ├── PocketAuthException.cs │ ├── PocketLimitException.cs │ ├── PocketRequestException.cs │ ├── HtmlNodeExtensions.cs │ ├── Utilities.cs │ ├── PocketException.cs │ └── JsonExtensions.cs ├── Models │ ├── PocketBoolean.cs │ ├── PocketVideoType.cs │ ├── Response │ │ ├── Guid.cs │ │ ├── Add.cs │ │ ├── Modify.cs │ │ ├── Topics.cs │ │ ├── ResponseBase.cs │ │ ├── RequestCode.cs │ │ ├── Retrieve.cs │ │ └── GetUser.cs │ ├── Parameters │ │ ├── ModifyParameters.cs │ │ ├── AddParameters.cs │ │ ├── Parameters.cs │ │ └── RetrieveParameters.cs │ ├── PocketTag.cs │ ├── PocketAuthor.cs │ ├── PocketTopic.cs │ ├── PocketStatistics.cs │ ├── PocketImage.cs │ ├── PocketVideo.cs │ ├── PocketUser.cs │ ├── PocketLimits.cs │ ├── PocketAction.cs │ ├── PocketArticle.cs │ ├── PocketExploreItem.cs │ └── PocketItem.cs ├── PocketSharp.nuspec ├── PocketSharp.csproj ├── Components │ ├── Trending.cs │ ├── Add.cs │ ├── Statistics.cs │ ├── Explore.cs │ ├── Account.cs │ ├── Modify.cs │ ├── ModifyTags.cs │ └── Get.cs └── PocketClient.cs ├── .editorconfig ├── PocketSharp.Tests ├── app.config ├── ExploreTests.cs ├── TrendingTests.cs ├── AccountTests.cs ├── TestsBase.cs ├── PocketSharp.Tests.csproj ├── MiscTests.cs ├── ModifyTagsTests.cs ├── AddTests.cs ├── StressTests.cs ├── ModifyTests.cs └── GetTests.cs ├── .gitattributes ├── LICENSE-MIT ├── README.md ├── .gitignore ├── CHANGELOG.md └── PocketSharp.sln /.nuget/NuGet.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ceee/PocketSharp/HEAD/.nuget/NuGet.exe -------------------------------------------------------------------------------- /Assets/pocketsharp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ceee/PocketSharp/HEAD/Assets/pocketsharp.png -------------------------------------------------------------------------------- /Assets/github-header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ceee/PocketSharp/HEAD/Assets/github-header.png -------------------------------------------------------------------------------- /Assets/authentication-screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ceee/PocketSharp/HEAD/Assets/authentication-screen.png -------------------------------------------------------------------------------- /PocketSharp.Examples/PocketSharp.UWP/FodyWeavers.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /PocketSharp/Utilities/PocketAuthException.cs: -------------------------------------------------------------------------------- 1 | 2 | namespace PocketSharp 3 | { 4 | public class PocketAuthException : PocketException 5 | { 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /PocketSharp/Utilities/PocketLimitException.cs: -------------------------------------------------------------------------------- 1 | 2 | namespace PocketSharp 3 | { 4 | public class PocketLimitException : PocketException 5 | { 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /PocketSharp.Examples/PocketSharp.UWP/Assets/StoreLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ceee/PocketSharp/HEAD/PocketSharp.Examples/PocketSharp.UWP/Assets/StoreLogo.png -------------------------------------------------------------------------------- /PocketSharp/Utilities/PocketRequestException.cs: -------------------------------------------------------------------------------- 1 | 2 | namespace PocketSharp 3 | { 4 | public class PocketRequestException : PocketException 5 | { 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /PocketSharp.Examples/PocketSharp.UWP/Assets/SplashScreen.scale-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ceee/PocketSharp/HEAD/PocketSharp.Examples/PocketSharp.UWP/Assets/SplashScreen.scale-200.png -------------------------------------------------------------------------------- /PocketSharp/Models/PocketBoolean.cs: -------------------------------------------------------------------------------- 1 | namespace PocketSharp.Models 2 | { 3 | internal enum PocketBoolean 4 | { 5 | No = 0, 6 | Yes = 1, 7 | IsType = 2 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /PocketSharp.Examples/PocketSharp.UWP/Assets/LockScreenLogo.scale-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ceee/PocketSharp/HEAD/PocketSharp.Examples/PocketSharp.UWP/Assets/LockScreenLogo.scale-200.png -------------------------------------------------------------------------------- /PocketSharp.Examples/PocketSharp.UWP/Assets/Square44x44Logo.scale-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ceee/PocketSharp/HEAD/PocketSharp.Examples/PocketSharp.UWP/Assets/Square44x44Logo.scale-200.png -------------------------------------------------------------------------------- /PocketSharp.Examples/PocketSharp.UWP/Assets/Wide310x150Logo.scale-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ceee/PocketSharp/HEAD/PocketSharp.Examples/PocketSharp.UWP/Assets/Wide310x150Logo.scale-200.png -------------------------------------------------------------------------------- /.nuget/NuGet.Config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /PocketSharp.Examples/PocketSharp.UWP/Assets/Square150x150Logo.scale-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ceee/PocketSharp/HEAD/PocketSharp.Examples/PocketSharp.UWP/Assets/Square150x150Logo.scale-200.png -------------------------------------------------------------------------------- /PocketSharp.Examples/PocketSharp.UWP/Assets/Square44x44Logo.targetsize-24_altform-unplated.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ceee/PocketSharp/HEAD/PocketSharp.Examples/PocketSharp.UWP/Assets/Square44x44Logo.targetsize-24_altform-unplated.png -------------------------------------------------------------------------------- /PocketSharp/Models/PocketVideoType.cs: -------------------------------------------------------------------------------- 1 | namespace PocketSharp.Models 2 | { 3 | public enum PocketVideoType 4 | { 5 | Unknown = 7, 6 | YouTube = 1, 7 | Vimeo = 2, 8 | HTML = 5, 9 | Flash = 6 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /PocketSharp.Examples/PocketSharp.UWP/App.xaml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | ; This file is for unifying the coding style for different editors and IDEs 2 | ; editorconfig.org 3 | 4 | root = true 5 | 6 | [*] 7 | end_of_line = crlf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | indent_style = space 12 | indent_size = 2 13 | 14 | [*.md] 15 | trim_trailing_whitespace = false 16 | -------------------------------------------------------------------------------- /PocketSharp/Models/Response/Guid.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace PocketSharp.Models 4 | { 5 | /// 6 | /// Guid 7 | /// 8 | [JsonObject] 9 | internal class GuidResponse 10 | { 11 | /// 12 | /// Gets or sets the GUID. 13 | /// 14 | /// 15 | /// The GUID. 16 | /// 17 | [JsonProperty] 18 | public string Guid { get; set; } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /PocketSharp/Models/Response/Add.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace PocketSharp.Models 4 | { 5 | /// 6 | /// Add Response 7 | /// 8 | [JsonObject] 9 | internal class Add : ResponseBase 10 | { 11 | /// 12 | /// Gets or sets the item. 13 | /// 14 | /// 15 | /// The item. 16 | /// 17 | [JsonProperty] 18 | public PocketItem Item { get; set; } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /PocketSharp.Examples/PocketSharp.UWP/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "Microsoft.NETCore.UniversalWindowsPlatform": "6.0.8", 4 | "Newtonsoft.Json": "11.0.2", 5 | "PropertyChanged.Fody": "2.4.0" 6 | }, 7 | "frameworks": { 8 | "uap10.0.15063": {} 9 | }, 10 | "runtimes": { 11 | "win10-arm": {}, 12 | "win10-arm-aot": {}, 13 | "win10-x86": {}, 14 | "win10-x86-aot": {}, 15 | "win10-x64": {}, 16 | "win10-x64-aot": {} 17 | } 18 | } -------------------------------------------------------------------------------- /PocketSharp.Tests/app.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /PocketSharp/Models/Response/Modify.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace PocketSharp.Models 4 | { 5 | /// 6 | /// Modify Response 7 | /// 8 | [JsonObject] 9 | internal class Modify : ResponseBase 10 | { 11 | /// 12 | /// Gets or sets the action results. 13 | /// 14 | /// 15 | /// The action results. 16 | /// 17 | //[JsonProperty("action_results")] 18 | //public bool[] ActionResults { get; set; } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /PocketSharp/Models/Response/Topics.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Collections.Generic; 4 | 5 | namespace PocketSharp.Models 6 | { 7 | /// 8 | /// Topics Response 9 | /// 10 | [JsonObject] 11 | internal class TopicsResponse : ResponseBase 12 | { 13 | /// 14 | /// Gets the topics. 15 | /// 16 | /// 17 | /// The topics. 18 | /// 19 | [JsonProperty("topics")] 20 | public IEnumerable Items { get; set; } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /PocketSharp.Tests/ExploreTests.cs: -------------------------------------------------------------------------------- 1 | using PocketSharp.Models; 2 | using System.Collections.Generic; 3 | using System.Threading.Tasks; 4 | using Xunit; 5 | using System.Linq; 6 | 7 | namespace PocketSharp.Tests 8 | { 9 | public class ExploreTests : TestsBase 10 | { 11 | public ExploreTests() : base() 12 | { 13 | 14 | } 15 | 16 | 17 | //[Fact] 18 | //public async Task CheckPreRequestAction() 19 | //{ 20 | // var results = await client.Explore(".net"); 21 | 22 | // Assert.NotEmpty(results); 23 | //} 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /PocketSharp/Models/Response/ResponseBase.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace PocketSharp.Models 4 | { 5 | /// 6 | /// Base for Responses 7 | /// 8 | [JsonObject] 9 | internal class ResponseBase 10 | { 11 | /// 12 | /// Gets or sets a value indicating whether this is status. 13 | /// 14 | /// 15 | /// true if status is OK; otherwise, false. 16 | /// 17 | [JsonProperty("status")] 18 | public bool Status { get; set; } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text 3 | 4 | # Don't check these into the repo as LF to work around TeamCity bug 5 | *.xml -text 6 | *.targets -text 7 | 8 | # Custom for Visual Studio 9 | *.cs diff=csharp 10 | *.sln merge=union 11 | *.csproj merge=union 12 | *.vbproj merge=union 13 | *.fsproj merge=union 14 | *.dbproj merge=union 15 | 16 | # Denote all files that are truly binary and should not be modified. 17 | *.dll binary 18 | *.exe binary 19 | *.png binary 20 | *.ico binary 21 | *.snk binary 22 | *.pdb binary 23 | *.svg binary 24 | -------------------------------------------------------------------------------- /PocketSharp/Models/Parameters/ModifyParameters.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Runtime.Serialization; 3 | 4 | namespace PocketSharp.Models 5 | { 6 | /// 7 | /// All parameters which can be passed to modify an item 8 | /// 9 | [DataContract] 10 | internal class ModifyParameters : Parameters 11 | { 12 | /// 13 | /// Gets or sets the actions. 14 | /// 15 | /// 16 | /// The actions. 17 | /// 18 | [DataMember(Name = "actions")] 19 | public IEnumerable> Actions { get; set; } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /PocketSharp.Tests/TrendingTests.cs: -------------------------------------------------------------------------------- 1 | using PocketSharp.Models; 2 | using System.Collections.Generic; 3 | using System.Threading.Tasks; 4 | using Xunit; 5 | 6 | namespace PocketSharp.Tests 7 | { 8 | public class TrendingTests : TestsBase 9 | { 10 | [Fact] 11 | public async Task AreTrendingArticlesReturned() 12 | { 13 | var articles = await client.GetTrendingArticles(); 14 | Assert.NotEmpty(articles); 15 | } 16 | 17 | 18 | [Fact] 19 | public async Task AreTrendingTopicsReturned() 20 | { 21 | var topics = await client.GetTrendingTopics(); 22 | Assert.NotEmpty(topics); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /PocketSharp/Models/PocketTag.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace PocketSharp.Models 4 | { 5 | /// 6 | /// Tag 7 | /// 8 | [JsonObject] 9 | public class PocketTag 10 | { 11 | /// 12 | /// Gets or sets the name. 13 | /// 14 | /// 15 | /// The name. 16 | /// 17 | [JsonProperty("tag")] 18 | public string Name { get; set; } 19 | 20 | /// 21 | /// Gets or sets the item iD. 22 | /// 23 | /// 24 | /// The name. 25 | /// 26 | [JsonProperty("item_id")] 27 | public string ItemID { get; set; } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /PocketSharp/Models/Response/RequestCode.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace PocketSharp.Models 4 | { 5 | /// 6 | /// Request Code 7 | /// 8 | [JsonObject] 9 | internal class RequestCode 10 | { 11 | /// 12 | /// Gets or sets the code. 13 | /// 14 | /// 15 | /// The code. 16 | /// 17 | [JsonProperty] 18 | public string Code { get; set; } 19 | 20 | /// 21 | /// Gets or sets the state. 22 | /// 23 | /// 24 | /// The state. 25 | /// 26 | [JsonProperty] 27 | public string State { get; set; } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /PocketSharp/Utilities/HtmlNodeExtensions.cs: -------------------------------------------------------------------------------- 1 | //using HtmlAgilityPack; 2 | //using System; 3 | //using System.Collections.Generic; 4 | //using System.Linq; 5 | //using System.Text; 6 | 7 | //namespace PocketSharp 8 | //{ 9 | // internal static class HtmlNodeExtensions 10 | // { 11 | // public static IEnumerable SelectNodesByClass(this HtmlNode node, string className) 12 | // { 13 | // return node.Descendants().Where(x => x.HasClass(className)); 14 | // } 15 | 16 | 17 | // public static HtmlNode SelectNodeByClass(this HtmlNode node, string className) 18 | // { 19 | // return node.Descendants().FirstOrDefault(x => x.HasClass(className)); 20 | // } 21 | // } 22 | //} 23 | -------------------------------------------------------------------------------- /PocketSharp/Utilities/Utilities.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace PocketSharp 5 | { 6 | /// 7 | /// General utilities 8 | /// 9 | internal class Utilities 10 | { 11 | /// 12 | /// converts DateTime to an UNIX timestamp 13 | /// 14 | /// The date. 15 | /// 16 | /// UNIX timestamp 17 | /// 18 | public static int? GetUnixTimestamp(DateTime? dateTime) 19 | { 20 | if (dateTime == null) 21 | { 22 | return null; 23 | } 24 | 25 | return (int)((DateTime)dateTime - new DateTime(1970, 1, 1)).TotalSeconds; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /PocketSharp/PocketSharp.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $id$ 5 | $version$ 6 | $title$ 7 | $author$ 8 | $author$ 9 | MIT 10 | https://github.com/ceee/PocketSharp 11 | https://raw.github.com/ceee/PocketSharp/master/Assets/pocketsharp.png 12 | false 13 | $description$ 14 | en-US 15 | $releaseNotes$ 16 | $copyright$ 17 | $tags$ 18 | 19 | 20 | -------------------------------------------------------------------------------- /PocketSharp.Tests/AccountTests.cs: -------------------------------------------------------------------------------- 1 | using PocketSharp.Models; 2 | using System; 3 | using System.Threading.Tasks; 4 | using Xunit; 5 | 6 | namespace PocketSharp.Tests 7 | { 8 | public class AccountTests : TestsBase 9 | { 10 | public AccountTests() : base() { } 11 | 12 | 13 | [Fact] 14 | public async Task IsAuthenticationSuccessful() 15 | { 16 | string requestCode = await client.GetRequestCode(); 17 | 18 | PocketUser user = await client.GetUser(requestCode); 19 | } 20 | 21 | [Fact] 22 | public async Task IsRegistrationURLSuccessfullyCreated() 23 | { 24 | string requestCode = await client.GetRequestCode(); 25 | 26 | Uri uri = client.GenerateRegistrationUri(requestCode); 27 | 28 | Assert.Contains(requestCode, uri.OriginalString); 29 | Assert.Contains("force=signup", uri.OriginalString); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /PocketSharp/Models/PocketAuthor.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | 4 | namespace PocketSharp.Models 5 | { 6 | /// 7 | /// Author 8 | /// 9 | [JsonObject] 10 | public class PocketAuthor 11 | { 12 | /// 13 | /// Gets or sets the ID. 14 | /// 15 | /// 16 | /// The ID. 17 | /// 18 | [JsonProperty("author_id")] 19 | public string ID { get; set; } 20 | 21 | /// 22 | /// Gets or sets the name. 23 | /// 24 | /// 25 | /// The name. 26 | /// 27 | [JsonProperty] 28 | public string Name { get; set; } 29 | 30 | /// 31 | /// Gets or sets the URI. 32 | /// 33 | /// 34 | /// The URI. 35 | /// 36 | [JsonProperty("url")] 37 | public Uri Uri { get; set; } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /PocketSharp/Models/PocketTopic.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | 4 | namespace PocketSharp.Models 5 | { 6 | /// 7 | /// Topic 8 | /// 9 | [JsonObject] 10 | public class PocketTopic 11 | { 12 | /// 13 | /// Gets or sets the category. 14 | /// 15 | /// 16 | /// The topic category. 17 | /// 18 | [JsonProperty("category")] 19 | public string Category { get; set; } 20 | 21 | /// 22 | /// Gets or sets the name. 23 | /// 24 | /// 25 | /// The name of the topic. 26 | /// 27 | [JsonProperty("name")] 28 | public string Name { get; set; } 29 | 30 | /// 31 | /// Gets or sets the URI. 32 | /// 33 | /// 34 | /// Link to the topic listing on Pocket. 35 | /// 36 | [JsonProperty("url")] 37 | public Uri Uri { get; set; } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /PocketSharp/Models/PocketStatistics.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace PocketSharp.Models 4 | { 5 | /// 6 | /// Statistics 7 | /// 8 | [JsonObject] 9 | public class PocketStatistics 10 | { 11 | /// 12 | /// Gets or sets all items. 13 | /// 14 | /// 15 | /// All items count. 16 | /// 17 | [JsonProperty("count_list")] 18 | public int CountAll { get; set; } 19 | 20 | /// 21 | /// Gets or sets all read items. 22 | /// 23 | /// 24 | /// Read items count. 25 | /// 26 | [JsonProperty("count_read")] 27 | public int CountRead { get; set; } 28 | 29 | /// 30 | /// Gets or sets all unread items. 31 | /// 32 | /// 33 | /// Unread items count. 34 | /// 35 | [JsonProperty("count_unread")] 36 | public int CountUnread { get; set; } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /PocketSharp/PocketSharp.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 5.0.0 6 | Tobias Klika 7 | brothers 8 | https://github.com/ceee/PocketSharp 9 | Copyright by brothers, 2019 10 | PocketAPI Pocket API PocketSharp Tobias Klika cee Scott Lovegrove scottisafool NReadability SgmlReader Reader Article SDK Pockem 11 | PocketSharp is a .NET Standard library that integrates the Pocket API v3. 12 | 15 | true 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /PocketSharp/Models/Response/Retrieve.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Collections.Generic; 4 | 5 | namespace PocketSharp.Models 6 | { 7 | /// 8 | /// Item Response 9 | /// 10 | [JsonObject] 11 | internal class Retrieve : ResponseBase 12 | { 13 | /// 14 | /// Gets or sets the complete. 15 | /// 16 | /// 17 | /// The complete. 18 | /// 19 | [JsonProperty("complete")] 20 | public int Complete { get; set; } 21 | 22 | /// 23 | /// Gets or sets the since. 24 | /// 25 | /// 26 | /// The since. 27 | /// 28 | [JsonProperty("since")] 29 | public DateTime Since { get; set; } 30 | 31 | /// 32 | /// Gets the items. 33 | /// 34 | /// 35 | /// The items. 36 | /// 37 | [JsonProperty("list")] 38 | [JsonConverter(typeof(ObjectToArrayConverter))] 39 | public IEnumerable Items { get; set; } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 cee, Tobias Klika 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /PocketSharp.Examples/PocketSharp.UWP/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("PocketSharp.UWP")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("PocketSharp.UWP")] 13 | [assembly: AssemblyCopyright("Copyright © 2015")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Version information for an assembly consists of the following four values: 18 | // 19 | // Major Version 20 | // Minor Version 21 | // Build Number 22 | // Revision 23 | // 24 | // You can specify all the values or you can default the Build and Revision Numbers 25 | // by using the '*' as shown below: 26 | // [assembly: AssemblyVersion("1.0.*")] 27 | [assembly: AssemblyVersion("1.0.0.0")] 28 | [assembly: AssemblyFileVersion("1.0.0.0")] 29 | [assembly: ComVisible(false)] -------------------------------------------------------------------------------- /PocketSharp/Models/Response/GetUser.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace PocketSharp.Models 4 | { 5 | /// 6 | /// Response from the GetUser() method 7 | /// 8 | [JsonObject] 9 | internal class GetUserResponse 10 | { 11 | public string Access_token { get; set; } 12 | 13 | public string Username { get; set; } 14 | 15 | public GetUserAccountResponse Account { get; set; } 16 | } 17 | 18 | 19 | [JsonObject] 20 | internal class GetUserAccountResponse 21 | { 22 | public string Email { get; set; } 23 | 24 | public string First_name { get; set; } 25 | 26 | public string Last_name { get; set; } 27 | 28 | public string User_id { get; set; } 29 | 30 | //public bool Premium_on_trial { get; set; } 31 | 32 | //public bool Premium_status { get; set; } 33 | 34 | public GetUserAccountProfileResponse Profile { get; set; } 35 | } 36 | 37 | 38 | [JsonObject] 39 | internal class GetUserAccountProfileResponse 40 | { 41 | public string Avatar_url { get; set; } 42 | 43 | public int Follow_count { get; set; } 44 | 45 | public int Follower_count { get; set; } 46 | 47 | public string Description { get; set; } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /PocketSharp/Models/Parameters/AddParameters.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.Serialization; 3 | 4 | namespace PocketSharp.Models 5 | { 6 | /// 7 | /// All parameters which can be passed to add a new item 8 | /// 9 | [DataContract] 10 | internal class AddParameters : Parameters 11 | { 12 | /// 13 | /// Gets or sets the URI. 14 | /// 15 | /// 16 | /// The URI. 17 | /// 18 | [DataMember(Name="url")] 19 | public Uri Uri { get; set; } 20 | 21 | /// 22 | /// Gets or sets the title. 23 | /// 24 | /// 25 | /// The title. 26 | /// 27 | [DataMember(Name="title")] 28 | public string Title { get; set; } 29 | 30 | /// 31 | /// Gets or sets the tags. 32 | /// 33 | /// 34 | /// The tags. 35 | /// 36 | [DataMember(Name="tags")] 37 | public string[] Tags { get; set; } 38 | 39 | /// 40 | /// Gets or sets the tweet ID. 41 | /// 42 | /// 43 | /// The tweet ID. 44 | /// 45 | [DataMember(Name="tweet_id")] 46 | public string TweetID { get; set; } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /PocketSharp.Tests/TestsBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading.Tasks; 4 | using Xunit; 5 | 6 | namespace PocketSharp.Tests 7 | { 8 | public class TestsBase : IDisposable 9 | { 10 | protected PocketClient client; 11 | 12 | protected List itemsToDelete = new List(); 13 | 14 | 15 | // setup 16 | public TestsBase() 17 | { 18 | // !! please don't misuse this account !! 19 | client = new PocketClient( 20 | consumerKey: "15396-f6f92101d72c8e270a6c9bb3", 21 | callbackUri: "http://frontendplay.com", 22 | accessCode: "80acf6c5-c198-03c0-b94c-e74402" 23 | ); 24 | } 25 | 26 | 27 | // teardown 28 | public void Dispose() 29 | { 30 | itemsToDelete.ForEach(async id => 31 | { 32 | await client.Delete(id); 33 | }); 34 | } 35 | 36 | 37 | // async throws 38 | public static async Task ThrowsAsync(Func func) 39 | { 40 | var expected = typeof(TException); 41 | Type actual = null; 42 | try 43 | { 44 | await func(); 45 | } 46 | catch (Exception e) 47 | { 48 | actual = e.GetType(); 49 | } 50 | Assert.Equal(expected, actual); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /PocketSharp.Tests/PocketSharp.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.0 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | PreserveNewest 17 | 18 | 19 | PreserveNewest 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | all 29 | runtime; build; native; contentfiles; analyzers 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /PocketSharp/Models/PocketImage.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | 4 | namespace PocketSharp.Models 5 | { 6 | /// 7 | /// Image 8 | /// 9 | [JsonObject] 10 | public class PocketImage 11 | { 12 | /// 13 | /// Gets or sets the ID. 14 | /// 15 | /// 16 | /// The ID. 17 | /// 18 | [JsonProperty("image_id")] 19 | public string ID { get; set; } 20 | 21 | /// 22 | /// Gets or sets the Item ID. 23 | /// 24 | /// 25 | /// The Item ID. 26 | /// 27 | [JsonProperty("item_id")] 28 | public string ItemID { get; set; } 29 | 30 | /// 31 | /// Gets or sets the caption. 32 | /// 33 | /// 34 | /// The caption. 35 | /// 36 | [JsonProperty] 37 | public string Caption { get; set; } 38 | 39 | /// 40 | /// Gets or sets the credit. 41 | /// 42 | /// 43 | /// The credit. 44 | /// 45 | [JsonProperty] 46 | public string Credit { get; set; } 47 | 48 | /// 49 | /// Gets or sets the URI. 50 | /// 51 | /// 52 | /// The URI. 53 | /// 54 | [JsonProperty("src")] 55 | public Uri Uri { get; set; } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /PocketSharp.Examples/PocketSharp.UWP/Properties/Default.rd.xml: -------------------------------------------------------------------------------- 1 | 17 | 18 | 19 | 20 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /PocketSharp/Models/PocketVideo.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | 4 | namespace PocketSharp.Models 5 | { 6 | /// 7 | /// Video 8 | /// 9 | [JsonObject] 10 | public class PocketVideo 11 | { 12 | /// 13 | /// Gets or sets the ID. 14 | /// 15 | /// 16 | /// The ID. 17 | /// 18 | [JsonProperty("video_id")] 19 | public string ID { get; set; } 20 | 21 | /// 22 | /// Gets or sets the Item ID. 23 | /// 24 | /// 25 | /// The Item ID. 26 | /// 27 | [JsonProperty("item_id")] 28 | public string ItemID { get; set; } 29 | 30 | /// 31 | /// Gets or sets the external ID. 32 | /// 33 | /// 34 | /// The external ID. 35 | /// 36 | [JsonProperty("vid")] 37 | public string ExternalID { get; set; } 38 | 39 | /// 40 | /// Gets or sets the URI. 41 | /// 42 | /// 43 | /// The URI. 44 | /// 45 | [JsonProperty("src")] 46 | public Uri Uri { get; set; } 47 | 48 | /// 49 | /// Gets or sets the URI. 50 | /// 51 | /// 52 | /// The URI. 53 | /// 54 | [JsonProperty("type")] 55 | [JsonConverter(typeof(VideoTypeConverter))] 56 | public PocketVideoType Type { get; set; } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /PocketSharp/Models/PocketUser.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | 4 | namespace PocketSharp.Models 5 | { 6 | /// 7 | /// Access Code 8 | /// 9 | [JsonObject] 10 | public class PocketUser 11 | { 12 | /// 13 | /// Pocket user id. 14 | /// 15 | public string Id { get; set; } 16 | 17 | /// 18 | /// The access code. 19 | /// 20 | public string Code { get; set; } 21 | 22 | /// 23 | /// Pocket username. 24 | /// 25 | public string Username { get; set; } 26 | 27 | /// 28 | /// Email address. 29 | /// 30 | public string Email { get; set; } 31 | 32 | /// 33 | /// First name. 34 | /// 35 | public string FirstName { get; set; } 36 | 37 | /// 38 | /// Last name. 39 | /// 40 | public string LastName { get; set; } 41 | 42 | /// 43 | /// Profile avatar. 44 | /// 45 | public Uri Avatar { get; set; } 46 | 47 | /// 48 | /// Is default avatar. 49 | /// 50 | public bool IsDefaultAvatar { get; set; } = true; 51 | 52 | /// 53 | /// Follower count. 54 | /// 55 | public int Followers { get; set; } 56 | 57 | /// 58 | /// Follow count. 59 | /// 60 | public int Follows { get; set; } 61 | 62 | /// 63 | /// Profile text. 64 | /// 65 | public string Description { get; set; } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /PocketSharp.Tests/MiscTests.cs: -------------------------------------------------------------------------------- 1 | using PocketSharp.Models; 2 | using System.Collections.Generic; 3 | using System.Threading.Tasks; 4 | using Xunit; 5 | using System.Linq; 6 | 7 | namespace PocketSharp.Tests 8 | { 9 | public class MiscTests : TestsBase 10 | { 11 | private int Incrementor = 0; 12 | 13 | public MiscTests() : base() 14 | { 15 | client.PreRequest = method => Incrementor++; 16 | } 17 | 18 | 19 | [Fact] 20 | public async Task CheckPreRequestAction() 21 | { 22 | IEnumerable items = await client.Get(count: 1); 23 | PocketItem item = items.First(); 24 | 25 | await client.Favorite(item); 26 | await client.Unfavorite(item); 27 | 28 | Assert.True(Incrementor >= 3); 29 | } 30 | 31 | [Fact] 32 | public void ItemEqualityChecks() 33 | { 34 | PocketItem item1 = new PocketItem() { ID = "12872" }; 35 | PocketItem item2 = new PocketItem() { ID = "12872" }; 36 | PocketItem item3 = new PocketItem() { ID = "12800" }; 37 | PocketArticle article = new PocketArticle(); 38 | 39 | Assert.True(item1.Equals(item2)); 40 | Assert.False(item1.Equals(item3)); 41 | Assert.False(item1.Equals(null)); 42 | 43 | Assert.True(item1 == item2); 44 | Assert.False(item1 == item3); 45 | Assert.False(item1 == null); 46 | 47 | Assert.False(item1 != item2); 48 | Assert.True(item1 != item3); 49 | Assert.True(item1 != null); 50 | 51 | Assert.False(item1.Equals(article)); 52 | 53 | Assert.True(new List() { item1 }.IndexOf(item2) > -1); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /PocketSharp/Utilities/PocketException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace PocketSharp 4 | { 5 | /// 6 | /// custom Pocket API Exceptions 7 | /// 8 | public class PocketException : Exception 9 | { 10 | /// 11 | /// Gets or sets the pocket error code. 12 | /// 13 | /// 14 | /// The pocket error code. 15 | /// 16 | public int? PocketErrorCode { get; set; } 17 | 18 | /// 19 | /// Gets or sets the pocket error. 20 | /// 21 | /// 22 | /// The pocket error. 23 | /// 24 | public string PocketError { get; set; } 25 | 26 | 27 | /// 28 | /// Initializes a new instance of the class. 29 | /// 30 | public PocketException() 31 | : base() { } 32 | 33 | 34 | /// 35 | /// Initializes a new instance of the class. 36 | /// 37 | /// The message that describes the error. 38 | public PocketException(string message) 39 | : base(message) { } 40 | 41 | 42 | /// 43 | /// Initializes a new instance of the class. 44 | /// 45 | /// The error message that explains the reason for the exception. 46 | /// The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) if no inner exception is specified. 47 | public PocketException(string message, Exception innerException) 48 | : base(message, innerException) { } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /PocketSharp.Examples/PocketSharp.UWP/MainPage.xaml.cs: -------------------------------------------------------------------------------- 1 | using PocketSharp.Models; 2 | using System.Collections.Generic; 3 | using System.Collections.ObjectModel; 4 | using System.Diagnostics; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | using Windows.UI.Xaml.Controls; 8 | 9 | // The Blank Page item template is documented at http://go.microsoft.com/fwlink/?LinkId=402352&clcid=0x409 10 | 11 | namespace PocketSharp.UWP 12 | { 13 | /// 14 | /// An empty page that can be used on its own or navigated to within a Frame. 15 | /// 16 | 17 | public sealed partial class MainPage : Page 18 | { 19 | public ObservableCollection Items { get; set; } 20 | 21 | 22 | public MainPage() 23 | { 24 | InitializeComponent(); 25 | Items = new ObservableCollection(); 26 | } 27 | 28 | public async Task LoadData() 29 | { 30 | // !! please don't misuse this account !! 31 | PocketClient client = new PocketClient( 32 | consumerKey: "15396-f6f92101d72c8e270a6c9bb3", 33 | callbackUri: "http://frontendplay.com", 34 | accessCode: "80acf6c5-c198-03c0-b94c-e74402" 35 | ); 36 | 37 | List items = null; 38 | 39 | try 40 | { 41 | items = (await client.Get()).ToList(); 42 | foreach (var item in items) 43 | { 44 | Items.Add(item); 45 | } 46 | } 47 | catch (PocketException ex) 48 | { 49 | Debug.WriteLine(ex.Message); 50 | } 51 | } 52 | 53 | private async void Button_Click(object sender, Windows.UI.Xaml.RoutedEventArgs e) 54 | { 55 | await LoadData(); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /PocketSharp.Examples/PocketSharp.UWP/MainPage.xaml: -------------------------------------------------------------------------------- 1 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /PocketSharp.Examples/PocketSharp.UWP/Package.appxmanifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 13 | 14 | 15 | 16 | 17 | PocketSharp.UWP 18 | tobi 19 | Assets\StoreLogo.png 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 34 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /PocketSharp/Models/PocketLimits.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace PocketSharp.Models 4 | { 5 | /// 6 | /// API Limitation Statistics 7 | /// 8 | [JsonObject] 9 | public class PocketLimits 10 | { 11 | /// 12 | /// Gets or sets the rate limit. 13 | /// 14 | /// 15 | /// Rate limit for current consumer key. 16 | /// 17 | [JsonProperty("X-Limit-Key-Limit")] 18 | public int RateLimitForConsumerKey { get; set; } 19 | 20 | /// 21 | /// Gets or sets the remaining calls. 22 | /// 23 | /// 24 | /// Remaining calls for current consumer key. 25 | /// 26 | [JsonProperty("X-Limit-Key-Remaining")] 27 | public int RemainingCallsForConsumerKey { get; set; } 28 | 29 | /// 30 | /// Gets or sets the reset seconds. 31 | /// 32 | /// 33 | /// Seconds until limit resets for current consumer key. 34 | /// 35 | [JsonProperty("X-Limit-Key-Reset")] 36 | public int SecondsUntilLimitResetsForConsumerKey { get; set; } 37 | 38 | /// 39 | /// Gets or sets the rate limit. 40 | /// 41 | /// 42 | /// Rate limit for current user. 43 | /// 44 | [JsonProperty("X-Limit-User-Limit")] 45 | public int RateLimitForUser { get; set; } 46 | 47 | /// 48 | /// Gets or sets the remaining calls. 49 | /// 50 | /// 51 | /// Remaining calls for current user. 52 | /// 53 | [JsonProperty("X-Limit-User-Remaining")] 54 | public int RemainingCallsForUser { get; set; } 55 | 56 | /// 57 | /// Gets or sets the reset seconds. 58 | /// 59 | /// 60 | /// Seconds until limit resets for current user. 61 | /// 62 | [JsonProperty("X-Limit-User-Reset")] 63 | public int SecondsUntilLimitResetsForUser { get; set; } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /PocketSharp/Components/Trending.cs: -------------------------------------------------------------------------------- 1 | using PocketSharp.Models; 2 | using System.Collections.Generic; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | 6 | namespace PocketSharp 7 | { 8 | /// 9 | /// PocketClient 10 | /// 11 | public partial class PocketClient 12 | { 13 | /// 14 | /// Get trending articles on Pocket. 15 | /// 16 | /// Article count. 17 | /// Two-letter language code for language-specific results. 18 | /// The cancellation token. 19 | /// 20 | /// 21 | public async Task> GetTrendingArticles(int count = 20, string languageCode = "en", CancellationToken cancellationToken = default(CancellationToken)) 22 | { 23 | return (await Request("getGlobalRecs", cancellationToken, new Dictionary() 24 | { 25 | { "locale_lang", languageCode }, 26 | { "count", count.ToString() }, 27 | { "version", "2" } 28 | }, false)).Items ?? new List(); 29 | } 30 | 31 | 32 | /// 33 | /// Get trending topics on Pocket. 34 | /// 35 | /// Two-letter language code for language-specific results. 36 | /// The cancellation token. 37 | /// 38 | /// 39 | public async Task> GetTrendingTopics(string languageCode = "en", CancellationToken cancellationToken = default(CancellationToken)) 40 | { 41 | return (await Request("getTrendingTopics", cancellationToken, new Dictionary() 42 | { 43 | { "locale_lang", languageCode }, 44 | { "version", "2" } 45 | }, false)).Items ?? new List(); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![PocketSharp](https://raw.github.com/ceee/PocketSharp/master/Assets/github-header.png) 2 | 3 | **PocketSharp** is a .NET Standard library that integrates the [Pocket API v3](http://getpocket.com/developer). 4 | 5 | ## Install PocketSharp using [NuGet](https://www.nuget.org/packages/PocketSharp/) 6 | 7 | ``` 8 | Install-Package PocketSharp 9 | ``` 10 | 11 | 12 | ## Documentation 13 | 14 | See [wiki](https://github.com/ceee/PocketSharp/wiki) 15 | 16 | ## Where's the Article View API? 17 | 18 | You can either use the open source [ReadSharp](https://github.com/ceee/ReadSharp) parser or if you want to use the official API by Pocket, you have to request access to it.
19 | Afterwards you can use the access information to query the endpoint with PocketSharp. Instructions [here](https://github.com/ceee/PocketSharp/wiki/Article-parser). 20 | 21 | 22 | --- 23 | 24 | ## Usage Example: 25 | 26 | A search for items containing `CSS`: 27 | 28 | ```csharp 29 | PocketClient client = new PocketClient("[YOUR_CONSUMER_KEY]", "[YOUR_ACCESS_CODE]"); 30 | 31 | List items = await client.Search("css"); 32 | 33 | items.ForEach( 34 | item => Debug.WriteLine(item.ID + " | " + item.Title) 35 | ); 36 | ``` 37 | 38 | Which will output: 39 | 40 | 330361896 | CSS Front-end Frameworks with comparison : By usabli.ca 41 | 345541438 | Editr - HTML, CSS, JavaScript playground 42 | 251743431 | CSS Architecture 43 | 343693149 | CSS3 Transitions - Thank God We Have A Specification! 44 | ... 45 | 46 | --- 47 | 48 | ## Dependencies 49 | 50 | - [Newtonsoft.Json](https://www.nuget.org/packages/Newtonsoft.Json/) 51 | 52 | ## Contributors 53 | 54 | | [![ceee](http://gravatar.com/avatar/9c61b1f4307425f12f05d3adb930ba66?s=70)](https://github.com/ceee "Tobias Klika") | [![ScottIsAFool](http://gravatar.com/avatar/6df656872a87b09a7470feb4867ed927?s=70)](https://github.com/ScottIsAFool "Scott Lovegrove") | 55 | |---|---| 56 | | [ceee](https://github.com/ceee) | [ScottIsAFool](https://github.com/ScottIsAFool) | 57 | 58 | ## License 59 | 60 | [MIT License](https://github.com/ceee/PocketSharp/blob/master/LICENSE-MIT) 61 | -------------------------------------------------------------------------------- /PocketSharp/Components/Add.cs: -------------------------------------------------------------------------------- 1 | using PocketSharp.Models; 2 | using System; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | 6 | namespace PocketSharp 7 | { 8 | /// 9 | /// PocketClient 10 | /// 11 | public partial class PocketClient 12 | { 13 | /// 14 | /// Adds a new item to pocket 15 | /// 16 | /// The URL of the item you want to save 17 | /// A comma-separated list of tags to apply to the item 18 | /// This can be included for cases where an item does not have a title, which is typical for image or PDF URLs. If Pocket detects a title from the content of the page, this parameter will be ignored. 19 | /// If you are adding Pocket support to a Twitter client, please send along a reference to the tweet status id. This allows Pocket to show the original tweet alongside the article. 20 | /// The cancellation token. 21 | /// 22 | /// A simple representation of the saved item which doesn't contain all data (is only returned by calling the Retrieve method) 23 | /// 24 | /// (1) Uri should be absolute. 25 | /// 26 | public async Task Add( 27 | Uri uri, 28 | string[] tags = null, 29 | string title = null, 30 | string tweetID = null, 31 | CancellationToken cancellationToken = default(CancellationToken) 32 | ) 33 | { 34 | if (!uri.IsAbsoluteUri) 35 | { 36 | throw new FormatException("(1) Uri should be absolute."); 37 | } 38 | 39 | AddParameters parameters = new AddParameters() 40 | { 41 | Uri = uri, 42 | Tags = tags, 43 | Title = title, 44 | TweetID = tweetID 45 | }; 46 | 47 | Add response = await Request("add", cancellationToken, parameters.Convert()); 48 | 49 | return response.Item; 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /PocketSharp/Components/Statistics.cs: -------------------------------------------------------------------------------- 1 | using PocketSharp.Models; 2 | using System; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | 6 | namespace PocketSharp 7 | { 8 | /// 9 | /// PocketClient 10 | /// 11 | public partial class PocketClient 12 | { 13 | /// 14 | /// Statistics from the user account. 15 | /// 16 | /// The cancellation token. 17 | /// 18 | /// 19 | public async Task GetUserStatistics(CancellationToken cancellationToken = default(CancellationToken)) 20 | { 21 | return await Request("stats", cancellationToken); 22 | } 23 | 24 | 25 | /// 26 | /// Returns API usage statistics. 27 | /// If a request was made before, the data is returned synchronously from the cache. 28 | /// Note: This method only works for authenticated users with a given AccessCode. 29 | /// 30 | /// The cancellation token. 31 | /// 32 | /// 33 | public async Task GetUsageLimits(CancellationToken cancellationToken = default(CancellationToken)) 34 | { 35 | string rateLimitForConsumerKey = TryGetHeaderValue(lastHeaders, "X-Limit-Key-Limit"); 36 | 37 | if (rateLimitForConsumerKey == null) 38 | { 39 | // this is the fastest way to do a non-failing request to receive the correct headers 40 | await Get( 41 | cancellationToken: cancellationToken, 42 | count: 1 43 | ); 44 | } 45 | 46 | return new PocketLimits() 47 | { 48 | RateLimitForConsumerKey = Convert.ToInt32(TryGetHeaderValue(lastHeaders, "X-Limit-Key-Limit")), 49 | RemainingCallsForConsumerKey = Convert.ToInt32(TryGetHeaderValue(lastHeaders, "X-Limit-Key-Remaining")), 50 | SecondsUntilLimitResetsForConsumerKey = Convert.ToInt32(TryGetHeaderValue(lastHeaders, "X-Limit-Key-Reset")), 51 | RateLimitForUser = Convert.ToInt32(TryGetHeaderValue(lastHeaders, "X-Limit-User-Limit")), 52 | RemainingCallsForUser = Convert.ToInt32(TryGetHeaderValue(lastHeaders, "X-Limit-User-Remaining")), 53 | SecondsUntilLimitResetsForUser = Convert.ToInt32(TryGetHeaderValue(lastHeaders, "X-Limit-User-Reset")) 54 | }; 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /PocketSharp/Models/Parameters/Parameters.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Reflection; 4 | using System.Runtime.Serialization; 5 | using System.Linq; 6 | using System.Collections; 7 | 8 | namespace PocketSharp.Models 9 | { 10 | /// 11 | /// Parameter 12 | /// 13 | internal class Parameters 14 | { 15 | 16 | /// 17 | /// Converts an object to a list of HTTP Post parameters. 18 | /// 19 | /// 20 | public Dictionary Convert() 21 | { 22 | // store HTTP parameters here 23 | Dictionary parameterDict = new Dictionary(); 24 | 25 | // get object properties 26 | IEnumerable properties = this.GetType() 27 | .GetTypeInfo() 28 | .DeclaredMembers 29 | .Where(p => p.CustomAttributes.FirstOrDefault(a => a.AttributeType == typeof(DataMemberAttribute)) != null); 30 | 31 | // gather attributes of object 32 | foreach (MemberInfo memberInfo in properties) 33 | { 34 | DataMemberAttribute attribute = (DataMemberAttribute)memberInfo.GetCustomAttributes(typeof(DataMemberAttribute‌), false).FirstOrDefault(); 35 | string name = attribute.Name ?? memberInfo.Name.ToLower(); 36 | object value = null; 37 | 38 | if (memberInfo is FieldInfo) 39 | { 40 | value = ((FieldInfo)memberInfo).GetValue(this); 41 | } 42 | else 43 | { 44 | value = ((PropertyInfo)memberInfo).GetValue(this, null); 45 | } 46 | 47 | // invalid parameter 48 | if (value == null) 49 | { 50 | continue; 51 | } 52 | 53 | // convert array to comma-seperated list 54 | if (value is IEnumerable && value.GetType().GetElementType() == typeof(string)) 55 | { 56 | value = string.Join(",", ((IEnumerable)value).Cast().Select(x => x.ToString()).ToArray()); 57 | } 58 | 59 | // convert booleans 60 | if (value is bool) 61 | { 62 | value = System.Convert.ToBoolean(value) ? "1" : "0"; 63 | } 64 | 65 | // convert DateTime to UNIX timestamp 66 | if (value is DateTime) 67 | { 68 | value = (int)((DateTime)value - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalSeconds; 69 | } 70 | 71 | parameterDict.Add(name, value.ToString()); 72 | } 73 | 74 | return parameterDict; 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /PocketSharp.Tests/ModifyTagsTests.cs: -------------------------------------------------------------------------------- 1 | using PocketSharp.Models; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Xunit; 7 | 8 | namespace PocketSharp.Tests 9 | { 10 | public class ModifyTagsTests : TestsBase 11 | { 12 | public ModifyTagsTests() : base() { } 13 | 14 | 15 | [Fact] 16 | public async Task AreTagsAddedAndDeletedToAnItem() 17 | { 18 | PocketItem item = await Setup(); 19 | 20 | Assert.True(await client.AddTags(item, new string[] { "test_tag", "test_tag2" })); 21 | 22 | item = await GetItemById(item.ID); 23 | 24 | Assert.True(item.Tags.Count() >= 2); 25 | 26 | Assert.NotNull(item.Tags.Single(tag => tag.Name == "test_tag")); 27 | Assert.NotNull(item.Tags.Single(tag => tag.Name == "test_tag2")); 28 | 29 | Assert.True(await client.RemoveTags(item, new string[] { "test_tag", "test_tag2" })); 30 | 31 | item = await GetItemById(item.ID); 32 | 33 | Assert.Null(item.Tags.SingleOrDefault(tag => tag.Name == "test_tag")); 34 | Assert.Null(item.Tags.SingleOrDefault(tag => tag.Name == "test_tag2")); 35 | } 36 | 37 | 38 | [Fact] 39 | public async Task AreAllTagsRemovedFromItem() 40 | { 41 | PocketItem item = await Setup(); 42 | 43 | Assert.True(await client.AddTags(item, new string[] { "test_tag", "test_tag2" })); 44 | 45 | item = await GetItemById(item.ID); 46 | 47 | Assert.True(item.Tags.Count() >= 2); 48 | 49 | Assert.True(await client.RemoveTags(item)); 50 | 51 | item = await GetItemById(item.ID); 52 | 53 | Assert.Null(item.Tags); 54 | } 55 | 56 | 57 | [Fact] 58 | public async Task AreTagsReplaced() 59 | { 60 | PocketItem item = await Setup(); 61 | 62 | Assert.True(await client.ReplaceTags(item.ID, new string[] { "test_tag", "test_tag2" })); 63 | 64 | item = await GetItemById(item.ID); 65 | 66 | Assert.Equal(2, item.Tags.Count()); 67 | 68 | Assert.NotNull(item.Tags.SingleOrDefault(tag => tag.Name == "test_tag")); 69 | Assert.NotNull(item.Tags.SingleOrDefault(tag => tag.Name == "test_tag2")); 70 | } 71 | 72 | 73 | private async Task Setup() 74 | { 75 | PocketItem item = await client.Add( 76 | uri: new Uri("https://github.com"), 77 | tags: new string[] { "github", "code", "social" } 78 | ); 79 | 80 | itemsToDelete.Add(item.ID); 81 | 82 | return await GetItemById(item.ID); 83 | } 84 | 85 | 86 | private async Task GetItemById(string id, bool archive = false) 87 | { 88 | List items = (await client.Get(state: archive ? State.archive : State.unread)).ToList(); 89 | PocketItem itemDesired = null; 90 | 91 | items.ForEach(itm => 92 | { 93 | if (itm.ID == id) 94 | { 95 | itemDesired = itm; 96 | } 97 | }); 98 | 99 | return itemDesired; 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /PocketSharp.Examples/PocketSharp.UWP/PocketSharp.UWP.nuget.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $(MSBuildAllProjects);$(MSBuildThisFileFullPath) 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and files generated by popular Visual Studio add-ons. 2 | 3 | # User-specific files 4 | *.suo 5 | *.user 6 | *.sln.docstates 7 | 8 | # Build results 9 | 10 | [Dd]ebug/ 11 | [Rr]elease/ 12 | x64/ 13 | build/ 14 | [Bb]in/ 15 | [Oo]bj/ 16 | 17 | # configuration 18 | Web.Release.config 19 | Web.Debug.config 20 | 21 | # MSTest test Results 22 | [Tt]est[Rr]esult*/ 23 | [Bb]uild[Ll]og.* 24 | 25 | *_i.c 26 | *_p.c 27 | *.ilk 28 | *.meta 29 | *.obj 30 | *.pch 31 | *.pdb 32 | *.pgc 33 | *.pgd 34 | *.rsp 35 | *.sbr 36 | *.tlb 37 | *.tli 38 | *.tlh 39 | *.tmp 40 | *.tmp_proj 41 | *.log 42 | *.vspscc 43 | *.vssscc 44 | .builds 45 | *.pidb 46 | *.log 47 | *.scc 48 | 49 | # Visual C++ cache files 50 | ipch/ 51 | *.aps 52 | *.ncb 53 | *.opensdf 54 | *.sdf 55 | *.cachefile 56 | 57 | # Visual Studio profiler 58 | *.psess 59 | *.vsp 60 | *.vspx 61 | 62 | # Guidance Automation Toolkit 63 | *.gpState 64 | 65 | # ReSharper is a .NET coding add-in 66 | _ReSharper*/ 67 | *.[Rr]e[Ss]harper 68 | 69 | # TeamCity is a build add-in 70 | _TeamCity* 71 | 72 | # DotCover is a Code Coverage Tool 73 | *.dotCover 74 | 75 | # NCrunch 76 | *.ncrunch* 77 | .*crunch*.local.xml 78 | 79 | # Installshield output folder 80 | [Ee]xpress/ 81 | 82 | # DocProject is a documentation generator add-in 83 | DocProject/buildhelp/ 84 | DocProject/Help/*.HxT 85 | DocProject/Help/*.HxC 86 | DocProject/Help/*.hhc 87 | DocProject/Help/*.hhk 88 | DocProject/Help/*.hhp 89 | DocProject/Help/Html2 90 | DocProject/Help/html 91 | 92 | # Click-Once directory 93 | publish/ 94 | 95 | # Publish Web Output 96 | *.Publish.xml 97 | *.pubxml 98 | 99 | # NuGet Packages Directory 100 | ## TODO: If you have NuGet Package Restore enabled, uncomment the next line 101 | packages/ 102 | *.nupkg 103 | 104 | # Windows Azure Build Output 105 | csx 106 | *.build.csdef 107 | 108 | # Windows Store app package directory 109 | AppPackages/ 110 | 111 | # Others 112 | sql/ 113 | *.Cache 114 | ClientBin/ 115 | [Ss]tyle[Cc]op.* 116 | ~$* 117 | *~ 118 | *.dbmdl 119 | *.[Pp]ublish.xml 120 | *.pfx 121 | *.publishsettings 122 | .sass-cache 123 | node_modules 124 | *.[L|l]og 125 | tmp 126 | _old 127 | _tmp 128 | Gemfile.lock 129 | WebWorkbench.mswwsettings 130 | 131 | # RIA/Silverlight projects 132 | Generated_Code/ 133 | 134 | # Backup & report files from converting an old project file to a newer 135 | # Visual Studio version. Backup files are not needed, because we have git ;-) 136 | _UpgradeReport_Files/ 137 | Backup*/ 138 | UpgradeLog*.XML 139 | UpgradeLog*.htm 140 | 141 | # SQL Server files 142 | App_Data/*.mdf 143 | App_Data/*.ldf 144 | 145 | .vs 146 | .nuget 147 | 148 | # ========================= 149 | # Windows detritus 150 | # ========================= 151 | 152 | # Windows image file caches 153 | Thumbs.db 154 | ehthumbs.db 155 | 156 | # Folder config file 157 | Desktop.ini 158 | 159 | # Recycle Bin used on file shares 160 | $RECYCLE.BIN/ 161 | 162 | # Mac crap 163 | .DS_Store 164 | 165 | 166 | # ========================= 167 | # Project 168 | # ========================= 169 | 170 | PocketSharp.Console/ 171 | !PocketSharp.Website/Release/ 172 | -------------------------------------------------------------------------------- /PocketSharp.Examples/PocketSharp.UWP/App.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Windows.ApplicationModel; 3 | using Windows.ApplicationModel.Activation; 4 | using Windows.UI.Xaml; 5 | using Windows.UI.Xaml.Controls; 6 | using Windows.UI.Xaml.Navigation; 7 | 8 | namespace PocketSharp.UWP 9 | { 10 | /// 11 | /// Provides application-specific behavior to supplement the default Application class. 12 | /// 13 | sealed partial class App : Application 14 | { 15 | /// 16 | /// Initializes the singleton application object. This is the first line of authored code 17 | /// executed, and as such is the logical equivalent of main() or WinMain(). 18 | /// 19 | public App() 20 | { 21 | this.InitializeComponent(); 22 | this.Suspending += OnSuspending; 23 | } 24 | 25 | /// 26 | /// Invoked when the application is launched normally by the end user. Other entry points 27 | /// will be used such as when the application is launched to open a specific file. 28 | /// 29 | /// Details about the launch request and process. 30 | protected override void OnLaunched(LaunchActivatedEventArgs e) 31 | { 32 | Frame rootFrame = Window.Current.Content as Frame; 33 | 34 | // Do not repeat app initialization when the Window already has content, 35 | // just ensure that the window is active 36 | if (rootFrame == null) 37 | { 38 | // Create a Frame to act as the navigation context and navigate to the first page 39 | rootFrame = new Frame(); 40 | 41 | rootFrame.NavigationFailed += OnNavigationFailed; 42 | 43 | if (e.PreviousExecutionState == ApplicationExecutionState.Terminated) 44 | { 45 | //TODO: Load state from previously suspended application 46 | } 47 | 48 | // Place the frame in the current Window 49 | Window.Current.Content = rootFrame; 50 | } 51 | 52 | if (rootFrame.Content == null) 53 | { 54 | // When the navigation stack isn't restored navigate to the first page, 55 | // configuring the new page by passing required information as a navigation 56 | // parameter 57 | rootFrame.Navigate(typeof(MainPage), e.Arguments); 58 | } 59 | // Ensure the current window is active 60 | Window.Current.Activate(); 61 | } 62 | 63 | /// 64 | /// Invoked when Navigation to a certain page fails 65 | /// 66 | /// The Frame which failed navigation 67 | /// Details about the navigation failure 68 | void OnNavigationFailed(object sender, NavigationFailedEventArgs e) 69 | { 70 | throw new Exception("Failed to load Page " + e.SourcePageType.FullName); 71 | } 72 | 73 | /// 74 | /// Invoked when application execution is being suspended. Application state is saved 75 | /// without knowing whether the application will be terminated or resumed with the contents 76 | /// of memory still intact. 77 | /// 78 | /// The source of the suspend request. 79 | /// Details about the suspend request. 80 | private void OnSuspending(object sender, SuspendingEventArgs e) 81 | { 82 | var deferral = e.SuspendingOperation.GetDeferral(); 83 | //TODO: Save application state and stop any background activity 84 | deferral.Complete(); 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /PocketSharp.Tests/AddTests.cs: -------------------------------------------------------------------------------- 1 | using PocketSharp.Models; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Threading.Tasks; 5 | using Xunit; 6 | using System.Linq; 7 | 8 | namespace PocketSharp.Tests 9 | { 10 | public class AddTests : TestsBase 11 | { 12 | public AddTests() : base() { } 13 | 14 | 15 | [Fact] 16 | public async Task AddSimpleItemWithUriOnly() 17 | { 18 | var uri = new Uri("http://frontendplay.com"); 19 | 20 | PocketItem item = await client.Add(uri); 21 | 22 | Assert.Equal(uri, item.Uri); 23 | 24 | itemsToDelete.Add(item.ID); 25 | } 26 | 27 | 28 | [Fact] 29 | public async Task ItemWithInstableImagesIsAdded() 30 | { 31 | var uri = new Uri("http://www.valoronline.com.br"); 32 | 33 | PocketItem item = await client.Add(uri); 34 | 35 | Assert.NotNull(item); 36 | 37 | itemsToDelete.Add(item.ID); 38 | } 39 | 40 | 41 | [Fact] 42 | public async Task AddComplexItem() 43 | { 44 | PocketItem item = await client.Add( 45 | uri: new Uri("http://frontendplay.com"), 46 | tags: new string[] { "blog", "frontend", "cee" }, 47 | title: "ignored title", 48 | tweetID: "380051788172632065" 49 | ); 50 | 51 | List items = (await client.Get()).ToList(); 52 | PocketItem itemDesired = null; 53 | 54 | items.ForEach(itm => 55 | { 56 | if (itm.ID == item.ID) 57 | { 58 | itemDesired = itm; 59 | } 60 | }); 61 | 62 | Assert.NotNull(itemDesired); 63 | Assert.Equal(itemDesired.ID, item.ID); 64 | Assert.Equal(3, itemDesired.Tags.Count()); 65 | 66 | itemsToDelete.Add(item.ID); 67 | } 68 | 69 | 70 | [Fact] 71 | public async Task ItemViaActionsIsAdded() 72 | { 73 | List actions = new List(); 74 | 75 | actions.Add(new PocketAction() 76 | { 77 | Uri = new Uri("http://frontendplay.com/story/4015/string-indexer-for-text-resources-in-nancy"), 78 | Action = "add", 79 | Time = DateTime.Now 80 | }); 81 | 82 | bool success = await client.SendActions(actions); 83 | 84 | Assert.True(success); 85 | 86 | IEnumerable items = await client.Search("in nancy"); 87 | 88 | Assert.NotNull(items); 89 | Assert.True(items.Count() > 0); 90 | 91 | itemsToDelete.Add(items.First().ID); 92 | } 93 | 94 | 95 | [Fact] 96 | public async Task ItemsViaActionsIsAdded() 97 | { 98 | List actions = new List(); 99 | 100 | actions.Add(new PocketAction() 101 | { 102 | Uri = new Uri("http://frontendplay.com/story/4015/string-indexer-for-text-resources-in-nancy"), 103 | Action = "add", 104 | Time = DateTime.Now 105 | }); 106 | actions.Add(new PocketAction() 107 | { 108 | Uri = new Uri("http://frontendplay.com/story/4013/build-a-custom-razor-viewbase-in-nancy"), 109 | Action = "add", 110 | Time = DateTime.Now 111 | }); 112 | 113 | bool success = await client.SendActions(actions); 114 | 115 | Assert.True(success); 116 | 117 | IEnumerable items = await client.Search("in nancy"); 118 | 119 | Assert.NotNull(items); 120 | Assert.True(items.Count() == 2); 121 | 122 | itemsToDelete.AddRange(items.Select(item => item.ID)); 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /PocketSharp/Components/Explore.cs: -------------------------------------------------------------------------------- 1 | //using HtmlAgilityPack; 2 | //using PocketSharp.Models; 3 | //using System; 4 | //using System.Collections.Generic; 5 | //using System.Linq; 6 | //using System.Threading; 7 | //using System.Threading.Tasks; 8 | //using System.Web; 9 | 10 | //namespace PocketSharp 11 | //{ 12 | // /// 13 | // /// PocketClient 14 | // /// 15 | // public partial class PocketClient 16 | // { 17 | // /// 18 | // /// Term to use for trending articles in the Explore() method 19 | // /// 20 | // const string EXPLORE_TRENDING = "trending"; 21 | 22 | // /// 23 | // /// Term to use for must-read articles in the Explore() method 24 | // /// 25 | // const string EXPLORE_MUST_READS = "must-reads"; 26 | 27 | 28 | // /// 29 | // /// Explore Pocket and find interesting articles by a certain topic 30 | // /// 31 | // /// Term or topic to get articles for 32 | // /// 33 | // /// 34 | // async Task> Explore(string topic, CancellationToken cancellationToken = default(CancellationToken)) 35 | // { 36 | // List items = new List(); 37 | // string html = await RequestAsString("https://getpocket.com/explore/" + HttpUtility.UrlEncode(topic), cancellationToken); 38 | 39 | // var document = new HtmlDocument(); 40 | // document.LoadHtml(html); 41 | 42 | // IEnumerable nodes = document.DocumentNode.SelectNodesByClass("media_item"); 43 | 44 | // if (nodes == null || !nodes.Any()) 45 | // { 46 | // return items; 47 | // } 48 | 49 | // for (int i = 0; i < nodes.Count(); i++) 50 | // { 51 | // HtmlNode node = nodes.ElementAt(i); 52 | // PocketExploreItem item = new PocketExploreItem(); 53 | // item.ID = node.Id; 54 | 55 | // HtmlNode title = node.SelectNodeByClass("title")?.FirstChild; 56 | 57 | // if (title == null) 58 | // { 59 | // continue; 60 | // } 61 | 62 | // // get uri 63 | // string uri = title.GetAttributeValue("data-saveurl", null); 64 | // item.Uri = new Uri(uri); 65 | 66 | // // get image 67 | // string imageUri = node.SelectNodeByClass("item_image")?.GetAttributeValue("data-thumburl", null); 68 | // if (!String.IsNullOrEmpty(imageUri)) 69 | // { 70 | // PocketImage image = new PocketImage(); 71 | // image.Uri = new Uri(imageUri); 72 | // image.ID = "0"; 73 | // image.ItemID = item.ID; 74 | // item.Images = new List() { image }; 75 | // } 76 | 77 | // // get basic infos 78 | // item.Title = title.InnerText; 79 | // item.Excerpt = node.SelectNodeByClass("excerpt")?.InnerText; 80 | // item.IsTrending = node.SelectNodeByClass("flag-trending") != null; 81 | 82 | // // save count 83 | // string saveCountStr = node.SelectNodeByClass("save_count")?.InnerText?.Split(' ')?.FirstOrDefault(); 84 | // int saveCount = 0; 85 | // Int32.TryParse(saveCountStr, out saveCount); 86 | // item.SaveCount = saveCount; 87 | 88 | // // add published date 89 | // DateTime publishedDate = DateTime.Now; 90 | // if (DateTime.TryParse(node.SelectNodeByClass("read_time")?.InnerText, out publishedDate)) 91 | // { 92 | // item.PublishedTime = publishedDate; 93 | // } 94 | 95 | // items.Add(item); 96 | // } 97 | 98 | // return items; 99 | // } 100 | // } 101 | //} 102 | -------------------------------------------------------------------------------- /PocketSharp.Examples/PocketSharp.UWP/PocketSharp.UWP.nuget.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | True 5 | NuGet 6 | O:\PocketSharp\PocketSharp.Examples\PocketSharp.UWP\project.lock.json 7 | $(UserProfile)\.nuget\packages\ 8 | C:\Users\tobi\.nuget\packages\;C:\Program Files (x86)\Microsoft SDKs\NuGetPackagesFallback\ 9 | ProjectJson 10 | 4.5.0 11 | 12 | 13 | $(MSBuildAllProjects);$(MSBuildThisFileFullPath) 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /PocketSharp.Tests/StressTests.cs: -------------------------------------------------------------------------------- 1 | using PocketSharp.Models; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | using Xunit; 8 | 9 | namespace PocketSharp.Tests 10 | { 11 | public class StressTests : TestsBase 12 | { 13 | private static IEnumerable urls; 14 | private static string[] tags = new string[] { "css", "js", "csharp", "windows", "microsoft" }; 15 | 16 | 17 | public StressTests() 18 | : base() 19 | { 20 | // !! please don't misuse this account !! 21 | client = new PocketClient( 22 | consumerKey: "20000-786d0bc8c39294e9829111d6", 23 | callbackUri: "http://frontendplay.com", 24 | accessCode: "9b8ecb6b-7801-1a5c-7b39-2ba05b" 25 | ); 26 | 27 | urls = File.ReadAllLines("../../url-100000.csv").Select(item => item.Split(',')[1]); 28 | } 29 | 30 | [Fact] 31 | public async Task Are100ItemsRetrievedProperly() 32 | { 33 | IEnumerable items = await client.Get(count: 100, state: State.all); 34 | Assert.True(items.Count() == 100); 35 | } 36 | 37 | [Fact] 38 | public async Task Are1000ItemsRetrievedProperly() 39 | { 40 | IEnumerable items = await client.Get(count: 1000, state: State.all); 41 | Assert.True(items.Count() == 1000); 42 | } 43 | 44 | [Fact] 45 | public async Task Are2500ItemsRetrievedProperly() 46 | { 47 | IEnumerable items = await client.Get(count: 2500, state: State.all); 48 | Assert.True(items.Count() == 2500); 49 | } 50 | 51 | [Fact] 52 | public async Task Are5000ItemsRetrievedProperly() 53 | { 54 | IEnumerable items = await client.Get(count: 5000, state: State.all); 55 | Assert.True(items.Count() == 5000); 56 | } 57 | 58 | [Fact] 59 | public async Task IsSearchSuccessfullyOnBigList() 60 | { 61 | IEnumerable items = await client.Get(search: "google"); 62 | 63 | Assert.True(items.Count() > 0); 64 | Assert.True(items.First().Title.ToLower().Contains("google") || items.First().Uri.ToString().ToLower().Contains("google")); 65 | } 66 | 67 | [Fact] 68 | public async Task IsTagSearchSuccessfullyOnBigList() 69 | { 70 | IEnumerable items = await client.SearchByTag(tags[0]); 71 | 72 | Assert.True(items.Count() > 1); 73 | 74 | bool found = false; 75 | 76 | items.First().Tags.ToList().ForEach(tag => 77 | { 78 | if (tag.Name.Contains(tags[0])) 79 | { 80 | found = true; 81 | } 82 | }); 83 | 84 | Assert.True(found); 85 | } 86 | 87 | [Fact] 88 | public async Task RetrieveTagsReturnsResultOnBigList() 89 | { 90 | List items = (await client.GetTags()).ToList(); 91 | 92 | items.ForEach(tag => 93 | { 94 | Assert.Contains(tag.Name, tags); 95 | }); 96 | 97 | Assert.Equal(items.Count, tags.Length); 98 | } 99 | 100 | 101 | private async Task FillAccount(int offset, int count) 102 | { 103 | int r; 104 | int r2; 105 | string[] tag; 106 | Random rnd = new Random(); 107 | 108 | var items = urls.Skip(offset).Take(count) 109 | .Select((value, idx) => new { Value = value, Index = idx }) 110 | .GroupBy(item => item.Index / 100, item => item.Value) 111 | .Cast>(); 112 | 113 | foreach (IEnumerable urlGroup in items) 114 | { 115 | await Task.WhenAll(urlGroup.Select(url => 116 | { 117 | r = rnd.Next(tags.Length); 118 | r2 = rnd.Next(tags.Length); 119 | tag = new string[] { tags[r], tags[r2] }; 120 | return client.Add(new Uri("http://" + url), tag); 121 | })); 122 | } 123 | } 124 | 125 | //[Fact] 126 | //public async Task Fillll() 127 | //{ 128 | // await FillAccount(11000, 89999); 129 | //} 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /PocketSharp/Models/PocketAction.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Runtime.Serialization; 5 | 6 | namespace PocketSharp.Models 7 | { 8 | /// 9 | /// All parameters which can be passed for a send action 10 | /// 11 | [DataContract] 12 | public class PocketAction 13 | { 14 | /// 15 | /// Gets or sets the action. 16 | /// 17 | /// 18 | /// The action. 19 | /// 20 | [DataMember(Name = "action")] 21 | public string Action { get; set; } 22 | 23 | /// 24 | /// Gets or sets the PocketItem ID. 25 | /// 26 | /// 27 | /// The ID. 28 | /// 29 | [DataMember(Name = "item_id")] 30 | public string ID { get; set; } 31 | 32 | /// 33 | /// Gets or sets the URI (for adding a new item). 34 | /// 35 | /// 36 | /// The URI. 37 | /// 38 | [DataMember(Name = "url")] 39 | public Uri Uri { get; set; } 40 | 41 | /// 42 | /// Gets or sets the Title (for adding a new item). 43 | /// 44 | /// 45 | /// The Title. 46 | /// 47 | [DataMember(Name = "title")] 48 | public string Title { get; set; } 49 | 50 | /// 51 | /// Gets or sets the associated Tweet ID. 52 | /// 53 | /// 54 | /// The Tweet ID. 55 | /// 56 | [DataMember(Name = "ref_id")] 57 | public string TweetID { get; set; } 58 | 59 | /// 60 | /// Gets or sets the time. 61 | /// 62 | /// 63 | /// The time. 64 | /// 65 | [DataMember(Name = "time")] 66 | public DateTime? Time { get; set; } 67 | 68 | // specific params 69 | 70 | /// 71 | /// Gets or sets the tags. 72 | /// 73 | /// 74 | /// The tags. 75 | /// 76 | [DataMember(Name = "tags")] 77 | public string[] Tags { get; set; } 78 | 79 | /// 80 | /// Gets or sets the old tag. 81 | /// 82 | /// 83 | /// The old tag. 84 | /// 85 | [DataMember(Name = "old_tag")] 86 | public string OldTag { get; set; } 87 | 88 | /// 89 | /// Gets or sets the new tag. 90 | /// 91 | /// 92 | /// The new tag. 93 | /// 94 | [DataMember(Name = "new_tag")] 95 | public string NewTag { get; set; } 96 | 97 | /// 98 | /// Gets or sets the tag. 99 | /// 100 | /// 101 | /// The old tag. 102 | /// 103 | [DataMember(Name = "tag")] 104 | public string Tag { get; set; } 105 | 106 | 107 | /// 108 | /// Converts this instance to a parameter list. 109 | /// 110 | /// 111 | internal Dictionary Convert() 112 | { 113 | Dictionary parameters = new Dictionary 114 | { 115 | { "action", Action } 116 | }; 117 | 118 | if (!String.IsNullOrEmpty(ID) && ID != "0") 119 | parameters.Add("item_id", ID.ToString()); 120 | if (Time != null) 121 | parameters.Add("time", Time != null ? Utilities.GetUnixTimestamp(Time).ToString() : null); 122 | if (Tags != null) 123 | parameters.Add("tags", Tags); 124 | if (OldTag != null) 125 | parameters.Add("old_tag", OldTag); 126 | if (NewTag != null) 127 | parameters.Add("new_tag", NewTag); 128 | if (Tag != null) 129 | parameters.Add("tag", Tag); 130 | if (!String.IsNullOrEmpty(Title)) 131 | parameters.Add("title", Title); 132 | if (!String.IsNullOrEmpty(TweetID)) 133 | parameters.Add("ref_id", TweetID); 134 | if (Uri != null) 135 | parameters.Add("url", Uri.OriginalString); 136 | 137 | return parameters; 138 | } 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | <<<<<<< HEAD 2 | ======= 3 | ### 4.2.0 (2015-12-05) 4 | 5 | - Update packages 6 | - Remove decompression from HttpClient so it works with UWP 7 | 8 | ### 4.1.6 (2014-08-28) 9 | 10 | - Fix DateTime conversion issue (from local to UTC) 11 | 12 | >>>>>>> simple 13 | ### 4.1.5 (2014-07-27) 14 | 15 | - Fix StackOverflow bug in PocketItem equality overload (fixes #28) 16 | 17 | ### 4.1.4 (2014-07-18) 18 | 19 | - Implement WebAuthenticationBroker-view toggle 20 | 21 | ### 4.1.3 (2014-06-25) 22 | 23 | - Only dispose response if available 24 | 25 | ### 4.1.2 (2014-06-18) 26 | 27 | - Add force param to login+registration 28 | 29 | ### 4.1.1 (2014-06-17) 30 | 31 | - Fix batch adding bug 32 | 33 | 34 | ### 4.1.0 (2014-06-16) 35 | 36 | - Include access to the Article View API 37 | 38 | ### 4.0.0 (2014-04-03) 39 | 40 | - Support for Universal apps (dropped SL and WP7 support) 41 | 42 | ### 3.2.0 (2014-03-13) 43 | 44 | - add raw JSON to each PocketItem (PR #22) 45 | 46 | ### 3.1.1 (2014-02-23) 47 | 48 | - fix SendActions when sending _add_ requests (fixes #20) 49 | - try to normalize URIs retrieved in video array 50 | 51 | ### 3.1.0 (2014-02-07) 52 | 53 | - Use FullTitle if no other title props are available 54 | - Default mobile authentication screen with toggle for desktop (fixes #17) 55 | - Use new registration endpoint 56 | 57 | ### 3.0.2 (2013-12-27) 58 | 59 | - assign given_url to `Uri` when item couldn't be resolved by Pocket 60 | 61 | ### 3.0.1 (2013-12-15) 62 | 63 | - added `PreRequest` to interface 64 | 65 | ### 3.0.0 (2013-12-15) 66 | 67 | - `cancellationToken` support for all methods 68 | - Toggle inclusion of title in PocketArticle.Content 69 | - Make setters for inline objects in PocketItem (images, videos, ...) 70 | - IPocketClient interface 71 | - PreRequest callback allows injection of `Action` before every request 72 | - `lastHeaders` and `lastRequestData` in PocketClient for better debugging 73 | - Submit multiple actions in one request with `SendActions` 74 | - New `IsVideo`, `IsImage` and `ResolvedId` as new properties in PocketItem 75 | - Split PocketReader into own NuGet package 76 | - A lot of bugfixes 77 | 78 | ### 2.2.2 (2013-11-01) 79 | 80 | - Password validation in RegisterAccount 81 | 82 | ### 2.2.1 (2013-10-27) 83 | 84 | - Fix NReadability parsing issue 85 | 86 | ### 2.2.0 (2013-10-26) 87 | 88 | - Made `GetAccessCode` obsolete 89 | - Return username after authentication with `GetUser` 90 | 91 | ### 2.1.0 (2013-10-25) 92 | 93 | - Rename `Statistics()` to `GetUserStatistics()` 94 | - Made `CallbackUri` public 95 | - Added Fody/PropertyChanged 96 | - Method `GetUsageLimits()` to retrieve API usage limits 97 | - Add PORTABLE constant to SgmlReader 98 | 99 | ### 2.0.1 (2013-10-19) 100 | 101 | - Catch HttpRequestExceptions and provide as InnerException for PocketException 102 | - Read Article from URI (method overload) 103 | 104 | ### 2.0.0 (2013-10-17) 105 | 106 | - Add Reader API _(does not use the official Article View API, which is private. The PocketReader is based on a custom PCL port of NReadability and SgmlReader)_ 107 | 108 | ### 1.5.1 (2013-09-30) 109 | 110 | - `RetrieveFilter.All` didn't work 111 | - improve search speed 112 | 113 | ### 1.5.0 (2013-09-28) 114 | 115 | - add statistics API 116 | - add registration API 117 | 118 | ### 1.4.0 (2013-09-21) 119 | 120 | - rename `Retrieve` to `Get` 121 | - update IntelliSense documentation 122 | - add `GetTags` method 123 | 124 | ### 1.3.0 (2013-09-19) 125 | 126 | - get Item by ID 127 | - tag modification bugfixes 128 | 129 | ### 1.2.1 (2013-09-18) 130 | 131 | - correct parameter conversion for DateTime and Boolean 132 | 133 | ### 1.2.0 (2013-09-17) 134 | 135 | - simplified retrieve methods 136 | 137 | ### 1.1.0 (2013-09-17) 138 | 139 | - fix modification requests 140 | 141 | ### 1.0.0 (2013-09-15) 142 | 143 | - convert to PCL 144 | - implement async 145 | 146 | ### 0.3.2 (2013-08-16) 147 | 148 | - tag modification fixed 149 | - full retrieval of items for Retrieve method 150 | 151 | ### 0.3.1 (2013-07-07) 152 | 153 | - authentication fixes 154 | 155 | ### 0.3.0 (2013-07-02) 156 | 157 | - update authentication process 158 | 159 | ### 0.2.0 (2013-06-27) 160 | 161 | - add, modify item 162 | - modify tags 163 | 164 | ### 0.1.0 (2013-06-26) 165 | 166 | - authentication 167 | - retrieve functionality 168 | -------------------------------------------------------------------------------- /PocketSharp.Tests/ModifyTests.cs: -------------------------------------------------------------------------------- 1 | using PocketSharp.Models; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Xunit; 7 | 8 | namespace PocketSharp.Tests 9 | { 10 | public class ModifyTests : TestsBase 11 | { 12 | public ModifyTests() : base() { } 13 | 14 | 15 | [Fact] 16 | public async Task IsAnItemArchivedAndUnarchived() 17 | { 18 | PocketItem item = await Setup(); 19 | 20 | Assert.True(await client.Archive(item)); 21 | 22 | item = await GetItemById(item.ID, true); 23 | 24 | Assert.True(item.IsArchive); 25 | 26 | Assert.True(await client.Unarchive(item)); 27 | 28 | item = await GetItemById(item.ID); 29 | 30 | Assert.False(item.IsArchive); 31 | } 32 | 33 | 34 | [Fact] 35 | public async Task IsAnItemFavoritedAndUnfavorited() 36 | { 37 | PocketItem item = await Setup(); 38 | 39 | Assert.True(await client.Favorite(item)); 40 | 41 | item = await GetItemById(item.ID); 42 | 43 | Assert.True(item.IsFavorite); 44 | 45 | Assert.True(await client.Unfavorite(item)); 46 | 47 | item = await GetItemById(item.ID); 48 | 49 | Assert.False(item.IsFavorite); 50 | } 51 | 52 | 53 | [Fact] 54 | public async Task IsAnItemDeleted() 55 | { 56 | PocketItem item = await Setup(); 57 | 58 | Assert.True(await client.Delete(item)); 59 | 60 | item = await GetItemById(item.ID); 61 | 62 | Assert.Null(item); 63 | } 64 | 65 | 66 | [Fact] 67 | public async Task AreMultipleActionsSent() 68 | { 69 | PocketItem item = await Setup(); 70 | 71 | bool success = await client.SendActions(new List() 72 | { 73 | new PocketAction() { Action = "favorite", ID = item.ID }, 74 | new PocketAction() { Action = "tags_add", ID = item.ID, Tags = new string[] { "new_tag", "another_tag" } }, 75 | new PocketAction() { Action = "archive", ID = item.ID } 76 | }); 77 | 78 | Assert.True(success); 79 | 80 | item = await client.Get(item.ID); 81 | 82 | Assert.True(item.IsFavorite); 83 | Assert.True(item.IsArchive); 84 | Assert.NotNull(item.Tags.Single(tag => tag.Name == "new_tag")); 85 | Assert.NotNull(item.Tags.Single(tag => tag.Name == "another_tag")); 86 | } 87 | 88 | 89 | [Fact] 90 | public async Task AreMultipleAddActionsSent() 91 | { 92 | PocketItem item = await Setup(); 93 | 94 | bool success = await client.SendActions(new List() 95 | { 96 | new PocketAction() { Action = "add", Uri = new Uri("http://msdn.microsoft.com/en-us/library/windows/apps/jj841126.aspx") }, 97 | new PocketAction() { Action = "add", Uri = new Uri("http://pokiapp.com/changelog") } 98 | }); 99 | 100 | Assert.True(success); 101 | } 102 | 103 | 104 | [Fact] 105 | public async Task AreMultipleActionsOnOneItemPerformed() 106 | { 107 | PocketItem item = await Setup(); 108 | 109 | bool success = await client.SendActions(new List() 110 | { 111 | new PocketAction() { Action = "unarchive", ID = item.ID }, 112 | new PocketAction() { Action = "archive", ID = item.ID }, 113 | new PocketAction() { Action = "unfavorite", ID = item.ID } 114 | }); 115 | 116 | Assert.True(success); 117 | 118 | item = await client.Get(item.ID); 119 | 120 | Assert.False(item.IsFavorite); 121 | Assert.True(item.IsArchive); 122 | } 123 | 124 | 125 | private async Task Setup() 126 | { 127 | PocketItem item = await client.Add( 128 | uri: new Uri("https://github.com"), 129 | tags: new string[] { "github", "code", "social" } 130 | ); 131 | 132 | itemsToDelete.Add(item.ID); 133 | 134 | return await GetItemById(item.ID); 135 | } 136 | 137 | 138 | private async Task GetItemById(string id, bool archive = false) 139 | { 140 | List items = (await client.Get(state: archive ? State.archive : State.unread)).ToList(); 141 | PocketItem itemDesired = null; 142 | 143 | items.ForEach(itm => 144 | { 145 | if (itm.ID == id) 146 | { 147 | itemDesired = itm; 148 | } 149 | }); 150 | 151 | return itemDesired; 152 | } 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /PocketSharp/Models/Parameters/RetrieveParameters.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.Serialization; 3 | 4 | namespace PocketSharp.Models 5 | { 6 | /// 7 | /// All parameters which can be passed for item retrieval 8 | /// 9 | [DataContract] 10 | internal class RetrieveParameters : Parameters 11 | { 12 | /// 13 | /// Gets or sets the state. 14 | /// 15 | /// 16 | /// The state. 17 | /// 18 | [DataMember(Name = "state")] 19 | public State? State { get; set; } 20 | 21 | /// 22 | /// Gets or sets the favorite. 23 | /// 24 | /// 25 | /// The favorite. 26 | /// 27 | [DataMember(Name = "favorite")] 28 | public bool? Favorite { get; set; } 29 | 30 | /// 31 | /// Gets or sets the tag. 32 | /// 33 | /// 34 | /// The tag. 35 | /// 36 | [DataMember(Name = "tag")] 37 | public string Tag { get; set; } 38 | 39 | /// 40 | /// Gets or sets the type of the content. 41 | /// 42 | /// 43 | /// The type of the content. 44 | /// 45 | [DataMember(Name = "contentType")] 46 | public ContentType? ContentType { get; set; } 47 | 48 | /// 49 | /// Gets or sets the sort. 50 | /// 51 | /// 52 | /// The sort. 53 | /// 54 | [DataMember(Name = "sort")] 55 | public Sort? Sort { get; set; } 56 | 57 | /// 58 | /// Gets or sets the type of the detail. 59 | /// 60 | /// 61 | /// The type of the detail. 62 | /// 63 | [DataMember(Name="detailType")] 64 | public DetailType? DetailType { get; set; } 65 | 66 | /// 67 | /// Gets or sets the search. 68 | /// 69 | /// 70 | /// The search. 71 | /// 72 | [DataMember(Name = "search")] 73 | public string Search { get; set; } 74 | 75 | /// 76 | /// Gets or sets the domain. 77 | /// 78 | /// 79 | /// The domain. 80 | /// 81 | [DataMember(Name = "domain")] 82 | public string Domain { get; set; } 83 | 84 | /// 85 | /// Gets or sets the since. 86 | /// 87 | /// 88 | /// The since. 89 | /// 90 | [DataMember(Name = "since")] 91 | public DateTime? Since { get; set; } 92 | 93 | /// 94 | /// Gets or sets the count. 95 | /// 96 | /// 97 | /// The count. 98 | /// 99 | [DataMember(Name = "count")] 100 | public int? Count { get; set; } 101 | 102 | /// 103 | /// Gets or sets the offset. 104 | /// 105 | /// 106 | /// The offset. 107 | /// 108 | [DataMember(Name = "offset")] 109 | public int? Offset { get; set; } 110 | 111 | /// 112 | /// Gets or sets the version. 113 | /// 114 | /// 115 | /// The version. 116 | /// 117 | [DataMember(Name = "version")] 118 | public int? Version { get; set; } 119 | } 120 | 121 | 122 | /// 123 | /// Item states 124 | /// 125 | public enum State 126 | { 127 | /// 128 | /// Only unread items 129 | /// 130 | unread, 131 | /// 132 | /// Only archived items 133 | /// 134 | archive, 135 | /// 136 | /// All items 137 | /// 138 | all 139 | } 140 | 141 | /// 142 | /// Sorting 143 | /// 144 | public enum Sort 145 | { 146 | /// 147 | /// Newest first 148 | /// 149 | newest, 150 | /// 151 | /// Oldest first 152 | /// 153 | oldest, 154 | /// 155 | /// Title A-Z descending 156 | /// 157 | title, 158 | /// 159 | /// URL descending 160 | /// 161 | site 162 | } 163 | 164 | /// 165 | /// Item types 166 | /// 167 | public enum ContentType 168 | { 169 | /// 170 | /// Articles 171 | /// 172 | article, 173 | /// 174 | /// Videos 175 | /// 176 | video, 177 | /// 178 | /// Images 179 | /// 180 | image 181 | } 182 | 183 | /// 184 | /// Item data 185 | /// 186 | public enum DetailType 187 | { 188 | /// 189 | /// Necessary data 190 | /// 191 | simple, 192 | /// 193 | /// Includes all Images/videos/tags/authors 194 | /// 195 | complete 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /PocketSharp/Models/PocketArticle.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Collections.Generic; 4 | 5 | namespace PocketSharp.Models 6 | { 7 | /// 8 | /// Article 9 | /// 10 | [JsonObject] 11 | public class PocketArticle 12 | { 13 | /// 14 | /// Gets or sets the response Code (200 = OK). 15 | /// 16 | /// 17 | /// The response Code. 18 | /// 19 | [JsonProperty("responseCode")] 20 | public string ResponseCode { get; set; } 21 | 22 | /// 23 | /// Gets or sets the ID. 24 | /// 25 | /// 26 | /// The ID. 27 | /// 28 | [JsonProperty("resolved_id")] 29 | public string ID { get; set; } 30 | 31 | /// 32 | /// Gets or sets the URI. 33 | /// 34 | /// 35 | /// The URI. 36 | /// 37 | [JsonProperty("resolvedUrl")] 38 | public Uri Uri { get; set; } 39 | 40 | /// 41 | /// Gets or sets the URI. 42 | /// 43 | /// 44 | /// The URI. 45 | /// 46 | [JsonProperty("timePublished")] 47 | public DateTime? PublishedTime { get; set; } 48 | 49 | /// 50 | /// Gets or sets the word count. 51 | /// 52 | /// 53 | /// The word count. 54 | /// 55 | [JsonProperty("wordCount")] 56 | public int WordCount { get; set; } 57 | 58 | /// 59 | /// Gets or sets a value indicating whether this instance is article. 60 | /// 61 | /// 62 | /// true if this instance is article; otherwise, false. 63 | /// 64 | [JsonProperty("isArticle")] 65 | public bool IsArticle { get; set; } 66 | 67 | /// 68 | /// Gets or sets a value indicating whether this instance is video. 69 | /// 70 | /// 71 | /// true if this instance is video; otherwise, false. 72 | /// 73 | [JsonProperty("isVideo")] 74 | public bool IsVideo { get; set; } 75 | 76 | /// 77 | /// Gets or sets a value indicating whether this instance is index. 78 | /// 79 | /// 80 | /// true if this instance is index; otherwise, false. 81 | /// 82 | [JsonProperty("isIndex")] 83 | public bool IsIndex { get; set; } 84 | 85 | /// 86 | /// Gets or sets a value indicating whether this instance used fallback. 87 | /// 88 | /// 89 | /// true if this instance used fallback; otherwise, false. 90 | /// 91 | [JsonProperty("usedFallback")] 92 | public bool UsedFallback { get; set; } 93 | 94 | /// 95 | /// Gets or sets a value indicating whether this instance requires login. 96 | /// 97 | /// 98 | /// true if this instance requires login; otherwise, false. 99 | /// 100 | [JsonProperty("requiresLogin")] 101 | public bool RequiresLogin { get; set; } 102 | 103 | /// 104 | /// Gets or sets the images. 105 | /// 106 | /// 107 | /// The images. 108 | /// 109 | [JsonProperty("images")] 110 | [JsonConverter(typeof(ObjectToArrayConverter))] 111 | public IEnumerable Images { get; set; } 112 | 113 | /// 114 | /// Gets or sets the videos. 115 | /// 116 | /// 117 | /// The videos. 118 | /// 119 | [JsonProperty("videos")] 120 | [JsonConverter(typeof(ObjectToArrayConverter))] 121 | public IEnumerable Videos { get; set; } 122 | 123 | /// 124 | /// Gets or sets the authors. 125 | /// 126 | /// 127 | /// The authors. 128 | /// 129 | [JsonProperty("authors")] 130 | [JsonConverter(typeof(ObjectToArrayConverter))] 131 | public IEnumerable Authors { get; set; } 132 | 133 | /// 134 | /// Gets or sets the article title. 135 | /// 136 | /// 137 | /// The Title. 138 | /// 139 | [JsonProperty("title")] 140 | public string Title { get; set; } 141 | 142 | /// 143 | /// Gets or sets the Excerpt. 144 | /// 145 | /// 146 | /// The Excerpt. 147 | /// 148 | [JsonProperty("excerpt")] 149 | public string Excerpt { get; set; } 150 | 151 | /// 152 | /// Gets or sets the Content. 153 | /// 154 | /// 155 | /// The Content. 156 | /// 157 | [JsonProperty("article")] 158 | public string Content { get; set; } 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /PocketSharp.sln: -------------------------------------------------------------------------------- 1 | Microsoft Visual Studio Solution File, Format Version 12.00 2 | # Visual Studio 15 3 | VisualStudioVersion = 15.0.27130.2027 4 | MinimumVisualStudioVersion = 10.0.40219.1 5 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "PocketSharp.Examples", "PocketSharp.Examples", "{AFD4E3F1-B23C-4924-BA5B-D917FBADB8FA}" 6 | EndProject 7 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{19DD45B5-B849-49E7-89D6-16C89B9B96C7}" 8 | ProjectSection(SolutionItems) = preProject 9 | .nuget\NuGet.Config = .nuget\NuGet.Config 10 | .nuget\NuGet.exe = .nuget\NuGet.exe 11 | .nuget\NuGet.targets = .nuget\NuGet.targets 12 | EndProjectSection 13 | EndProject 14 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PocketSharp.UWP", "PocketSharp.Examples\PocketSharp.UWP\PocketSharp.UWP.csproj", "{5CBB92EC-C1A2-4108-9A23-3B73DD138953}" 15 | EndProject 16 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PocketSharp", "PocketSharp\PocketSharp.csproj", "{0E22926C-7244-4A17-923C-84B46F5149C0}" 17 | EndProject 18 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PocketSharp.Tests", "PocketSharp.Tests\PocketSharp.Tests.csproj", "{3BAA93AD-3B5C-47C4-816B-F29A1E6D1C2D}" 19 | EndProject 20 | Global 21 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 22 | Debug|Any CPU = Debug|Any CPU 23 | Debug|ARM = Debug|ARM 24 | Debug|x64 = Debug|x64 25 | Debug|x86 = Debug|x86 26 | Release|Any CPU = Release|Any CPU 27 | Release|ARM = Release|ARM 28 | Release|x64 = Release|x64 29 | Release|x86 = Release|x86 30 | EndGlobalSection 31 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 32 | {5CBB92EC-C1A2-4108-9A23-3B73DD138953}.Debug|Any CPU.ActiveCfg = Debug|x86 33 | {5CBB92EC-C1A2-4108-9A23-3B73DD138953}.Debug|Any CPU.Build.0 = Debug|x86 34 | {5CBB92EC-C1A2-4108-9A23-3B73DD138953}.Debug|Any CPU.Deploy.0 = Debug|x86 35 | {5CBB92EC-C1A2-4108-9A23-3B73DD138953}.Debug|ARM.ActiveCfg = Debug|ARM 36 | {5CBB92EC-C1A2-4108-9A23-3B73DD138953}.Debug|ARM.Build.0 = Debug|ARM 37 | {5CBB92EC-C1A2-4108-9A23-3B73DD138953}.Debug|ARM.Deploy.0 = Debug|ARM 38 | {5CBB92EC-C1A2-4108-9A23-3B73DD138953}.Debug|x64.ActiveCfg = Debug|x64 39 | {5CBB92EC-C1A2-4108-9A23-3B73DD138953}.Debug|x64.Build.0 = Debug|x64 40 | {5CBB92EC-C1A2-4108-9A23-3B73DD138953}.Debug|x64.Deploy.0 = Debug|x64 41 | {5CBB92EC-C1A2-4108-9A23-3B73DD138953}.Debug|x86.ActiveCfg = Debug|x86 42 | {5CBB92EC-C1A2-4108-9A23-3B73DD138953}.Debug|x86.Build.0 = Debug|x86 43 | {5CBB92EC-C1A2-4108-9A23-3B73DD138953}.Debug|x86.Deploy.0 = Debug|x86 44 | {5CBB92EC-C1A2-4108-9A23-3B73DD138953}.Release|Any CPU.ActiveCfg = Release|x86 45 | {5CBB92EC-C1A2-4108-9A23-3B73DD138953}.Release|ARM.ActiveCfg = Release|ARM 46 | {5CBB92EC-C1A2-4108-9A23-3B73DD138953}.Release|ARM.Build.0 = Release|ARM 47 | {5CBB92EC-C1A2-4108-9A23-3B73DD138953}.Release|ARM.Deploy.0 = Release|ARM 48 | {5CBB92EC-C1A2-4108-9A23-3B73DD138953}.Release|x64.ActiveCfg = Release|x64 49 | {5CBB92EC-C1A2-4108-9A23-3B73DD138953}.Release|x64.Build.0 = Release|x64 50 | {5CBB92EC-C1A2-4108-9A23-3B73DD138953}.Release|x64.Deploy.0 = Release|x64 51 | {5CBB92EC-C1A2-4108-9A23-3B73DD138953}.Release|x86.ActiveCfg = Release|x86 52 | {5CBB92EC-C1A2-4108-9A23-3B73DD138953}.Release|x86.Build.0 = Release|x86 53 | {5CBB92EC-C1A2-4108-9A23-3B73DD138953}.Release|x86.Deploy.0 = Release|x86 54 | {0E22926C-7244-4A17-923C-84B46F5149C0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 55 | {0E22926C-7244-4A17-923C-84B46F5149C0}.Debug|Any CPU.Build.0 = Debug|Any CPU 56 | {0E22926C-7244-4A17-923C-84B46F5149C0}.Debug|ARM.ActiveCfg = Debug|Any CPU 57 | {0E22926C-7244-4A17-923C-84B46F5149C0}.Debug|ARM.Build.0 = Debug|Any CPU 58 | {0E22926C-7244-4A17-923C-84B46F5149C0}.Debug|x64.ActiveCfg = Debug|Any CPU 59 | {0E22926C-7244-4A17-923C-84B46F5149C0}.Debug|x64.Build.0 = Debug|Any CPU 60 | {0E22926C-7244-4A17-923C-84B46F5149C0}.Debug|x86.ActiveCfg = Debug|Any CPU 61 | {0E22926C-7244-4A17-923C-84B46F5149C0}.Debug|x86.Build.0 = Debug|Any CPU 62 | {0E22926C-7244-4A17-923C-84B46F5149C0}.Release|Any CPU.ActiveCfg = Release|Any CPU 63 | {0E22926C-7244-4A17-923C-84B46F5149C0}.Release|Any CPU.Build.0 = Release|Any CPU 64 | {0E22926C-7244-4A17-923C-84B46F5149C0}.Release|ARM.ActiveCfg = Release|Any CPU 65 | {0E22926C-7244-4A17-923C-84B46F5149C0}.Release|ARM.Build.0 = Release|Any CPU 66 | {0E22926C-7244-4A17-923C-84B46F5149C0}.Release|x64.ActiveCfg = Release|Any CPU 67 | {0E22926C-7244-4A17-923C-84B46F5149C0}.Release|x64.Build.0 = Release|Any CPU 68 | {0E22926C-7244-4A17-923C-84B46F5149C0}.Release|x86.ActiveCfg = Release|Any CPU 69 | {0E22926C-7244-4A17-923C-84B46F5149C0}.Release|x86.Build.0 = Release|Any CPU 70 | {3BAA93AD-3B5C-47C4-816B-F29A1E6D1C2D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 71 | {3BAA93AD-3B5C-47C4-816B-F29A1E6D1C2D}.Debug|Any CPU.Build.0 = Debug|Any CPU 72 | {3BAA93AD-3B5C-47C4-816B-F29A1E6D1C2D}.Debug|ARM.ActiveCfg = Debug|Any CPU 73 | {3BAA93AD-3B5C-47C4-816B-F29A1E6D1C2D}.Debug|ARM.Build.0 = Debug|Any CPU 74 | {3BAA93AD-3B5C-47C4-816B-F29A1E6D1C2D}.Debug|x64.ActiveCfg = Debug|Any CPU 75 | {3BAA93AD-3B5C-47C4-816B-F29A1E6D1C2D}.Debug|x64.Build.0 = Debug|Any CPU 76 | {3BAA93AD-3B5C-47C4-816B-F29A1E6D1C2D}.Debug|x86.ActiveCfg = Debug|Any CPU 77 | {3BAA93AD-3B5C-47C4-816B-F29A1E6D1C2D}.Debug|x86.Build.0 = Debug|Any CPU 78 | {3BAA93AD-3B5C-47C4-816B-F29A1E6D1C2D}.Release|Any CPU.ActiveCfg = Release|Any CPU 79 | {3BAA93AD-3B5C-47C4-816B-F29A1E6D1C2D}.Release|Any CPU.Build.0 = Release|Any CPU 80 | {3BAA93AD-3B5C-47C4-816B-F29A1E6D1C2D}.Release|ARM.ActiveCfg = Release|Any CPU 81 | {3BAA93AD-3B5C-47C4-816B-F29A1E6D1C2D}.Release|ARM.Build.0 = Release|Any CPU 82 | {3BAA93AD-3B5C-47C4-816B-F29A1E6D1C2D}.Release|x64.ActiveCfg = Release|Any CPU 83 | {3BAA93AD-3B5C-47C4-816B-F29A1E6D1C2D}.Release|x64.Build.0 = Release|Any CPU 84 | {3BAA93AD-3B5C-47C4-816B-F29A1E6D1C2D}.Release|x86.ActiveCfg = Release|Any CPU 85 | {3BAA93AD-3B5C-47C4-816B-F29A1E6D1C2D}.Release|x86.Build.0 = Release|Any CPU 86 | EndGlobalSection 87 | GlobalSection(SolutionProperties) = preSolution 88 | HideSolutionNode = FALSE 89 | EndGlobalSection 90 | GlobalSection(NestedProjects) = preSolution 91 | {5CBB92EC-C1A2-4108-9A23-3B73DD138953} = {AFD4E3F1-B23C-4924-BA5B-D917FBADB8FA} 92 | EndGlobalSection 93 | GlobalSection(ExtensibilityGlobals) = postSolution 94 | SolutionGuid = {6B433CE4-D4DE-4360-80A1-DE2DB143DF0D} 95 | EndGlobalSection 96 | EndGlobal 97 | -------------------------------------------------------------------------------- /PocketSharp/Models/PocketExploreItem.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Diagnostics; 5 | using System.Linq; 6 | 7 | namespace PocketSharp.Models 8 | { 9 | /// 10 | /// Explore item containing all available data 11 | /// 12 | [JsonObject] 13 | [DebuggerDisplay("Uri = {Uri}, Title = {Title}")] 14 | public class PocketExploreItem : IComparable 15 | { 16 | /// 17 | /// Gets or sets the ID. 18 | /// 19 | /// 20 | /// The ID. 21 | /// 22 | public string ID { get; set; } 23 | 24 | /// 25 | /// Gets or sets the title. 26 | /// 27 | /// 28 | /// The title. 29 | /// 30 | public string Title { get; set; } 31 | 32 | /// 33 | /// Gets or sets the excerpt. 34 | /// 35 | /// 36 | /// The excerpt. 37 | /// 38 | public string Excerpt { get; set; } 39 | 40 | /// 41 | /// Gets or sets the published time. 42 | /// 43 | /// 44 | /// The time when the article was published. 45 | /// 46 | public DateTime? PublishedTime { get; set; } 47 | 48 | /// 49 | /// Gets or sets the URI. 50 | /// 51 | /// 52 | /// The URI. 53 | /// 54 | [JsonIgnore] 55 | public Uri Uri { get; set; } 56 | 57 | /// 58 | /// Gets or sets the save count. 59 | /// 60 | /// 61 | /// The save count. 62 | /// 63 | public int SaveCount { get; set; } 64 | 65 | /// 66 | /// Gets or sets the images. 67 | /// 68 | /// 69 | /// The images. 70 | /// 71 | [JsonConverter(typeof(ObjectToArrayConverter))] 72 | public IEnumerable Images { get; set; } 73 | 74 | /// 75 | /// Gets or sets a value indicating whether this instance is trending. 76 | /// 77 | /// 78 | /// true if this instance is trending; otherwise, false. 79 | /// 80 | public bool IsTrending { get; set; } 81 | 82 | /// 83 | /// Gets the lead image. 84 | /// 85 | /// 86 | /// The lead image. 87 | /// 88 | [JsonIgnore] 89 | public PocketImage LeadImage 90 | { 91 | get { return Images != null && Images.Count() > 0 ? Images.First() : null; } 92 | } 93 | 94 | /// 95 | /// Compares the current instance with another object of the same type and returns an integer that indicates whether the current instance precedes, follows, or occurs in the same position in the sort order as the other object. 96 | /// 97 | /// An object to compare with this instance. 98 | /// 99 | /// A value that indicates the relative order of the objects being compared. The return value has these meanings: Value Meaning Less than zero This instance precedes in the sort order. Zero This instance occurs in the same position in the sort order as . Greater than zero This instance follows in the sort order. 100 | /// 101 | int IComparable.CompareTo(object obj) 102 | { 103 | PocketExploreItem item = (PocketExploreItem)obj; 104 | 105 | if (!PublishedTime.HasValue) 106 | { 107 | return 1; 108 | } 109 | if (!item.PublishedTime.HasValue) 110 | { 111 | return -1; 112 | } 113 | 114 | return DateTime.Compare(PublishedTime.Value, item.PublishedTime.Value); 115 | } 116 | 117 | /// 118 | /// Determines whether the specified , is equal to this instance. 119 | /// 120 | /// The to compare with this instance. 121 | /// 122 | /// true if the specified is equal to this instance; otherwise, false. 123 | /// 124 | public override bool Equals(object obj) 125 | { 126 | if (obj == null) 127 | { 128 | return false; 129 | } 130 | 131 | PocketExploreItem item = obj as PocketExploreItem; 132 | 133 | if (item == null) 134 | { 135 | return false; 136 | } 137 | 138 | return ID == item.ID; 139 | } 140 | 141 | 142 | /// 143 | /// Implements the operator ==. 144 | /// 145 | /// A. 146 | /// The b. 147 | /// 148 | /// The result of the operator. 149 | /// 150 | public static bool operator ==(PocketExploreItem a, PocketExploreItem b) 151 | { 152 | if (Object.ReferenceEquals(a, b)) 153 | { 154 | return true; 155 | } 156 | 157 | PocketExploreItem itemA = (PocketExploreItem)a; 158 | PocketExploreItem itemB = (PocketExploreItem)b; 159 | 160 | if ((Object)itemA == null || (Object)itemB == null) 161 | { 162 | return false; 163 | } 164 | 165 | return itemA.ID == itemB.ID; 166 | } 167 | 168 | /// 169 | /// Implements the operator !=. 170 | /// 171 | /// A. 172 | /// The b. 173 | /// 174 | /// The result of the operator. 175 | /// 176 | public static bool operator !=(PocketExploreItem a, PocketExploreItem b) 177 | { 178 | return !(a == b); 179 | } 180 | 181 | /// 182 | /// Returns a hash code for this instance. 183 | /// 184 | /// 185 | /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. 186 | /// 187 | public override int GetHashCode() 188 | { 189 | return ID.GetHashCode(); 190 | } 191 | 192 | /// 193 | /// Returns a that represents this instance. 194 | /// 195 | /// 196 | /// A that represents this instance. 197 | /// 198 | public override string ToString() 199 | { 200 | return ID; 201 | } 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /PocketSharp/Components/Account.cs: -------------------------------------------------------------------------------- 1 | using PocketSharp.Models; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | 7 | namespace PocketSharp 8 | { 9 | /// 10 | /// PocketClient 11 | /// 12 | public partial class PocketClient 13 | { 14 | /// 15 | /// Retrieves the requestCode from Pocket, which is used to generate the Authentication URI to authenticate the user 16 | /// 17 | /// The cancellation token. 18 | /// 19 | /// Authentication methods need a callbackUri on initialization of the PocketClient class 20 | /// 21 | public async Task GetRequestCode(CancellationToken cancellationToken = default(CancellationToken)) 22 | { 23 | // check if request code is available 24 | if (CallbackUri == null) 25 | { 26 | throw new NullReferenceException("Authentication methods need a callbackUri on initialization of the PocketClient class"); 27 | } 28 | 29 | // do request 30 | RequestCode response = await Request("oauth/request", cancellationToken, new Dictionary() 31 | { 32 | { "redirect_uri", CallbackUri } 33 | }, false); 34 | 35 | // save code to client 36 | RequestCode = response.Code; 37 | 38 | // generate redirection URI and return 39 | return RequestCode; 40 | } 41 | 42 | 43 | /// 44 | /// Generate Authentication URI from requestCode 45 | /// 46 | /// The requestCode. If no requestCode is supplied, the property from the PocketClient intialization is used. 47 | /// A valid URI to redirect the user to. 48 | /// Call GetRequestCode() first to receive a request_code 49 | public Uri GenerateAuthenticationUri(string requestCode = null) 50 | { 51 | // check if request code is available 52 | if (RequestCode == null && requestCode == null) 53 | { 54 | throw new NullReferenceException("Call GetRequestCode() first to receive a request_code"); 55 | } 56 | 57 | // override property with given param if available 58 | if (requestCode != null) 59 | { 60 | RequestCode = requestCode; 61 | } 62 | 63 | return new Uri(String.Format(authentificationUri, RequestCode, CallbackUri, isMobileClient ? "1" : "0", "login", useInsideWebAuthenticationBroker ? "1" : "0")); 64 | } 65 | 66 | 67 | /// 68 | /// Generate registration URI from requestCode 69 | /// Follow the steps as with GenerateAuthenticationUri, but for unregistered users 70 | /// 71 | /// The requestCode. If no requestCode is supplied, the property from the PocketClient intialization is used. 72 | /// A valid URI to redirect the user to. 73 | /// Call GetRequestCode() first to receive a request_code 74 | public Uri GenerateRegistrationUri(string requestCode = null) 75 | { 76 | // check if request code is available 77 | if (RequestCode == null && requestCode == null) 78 | { 79 | throw new NullReferenceException("Call GetRequestCode() first to receive a request_code"); 80 | } 81 | 82 | // override property with given param if available 83 | if (requestCode != null) 84 | { 85 | RequestCode = requestCode; 86 | } 87 | 88 | return new Uri(String.Format(authentificationUri, RequestCode, CallbackUri, isMobileClient ? "1" : "0", "signup", useInsideWebAuthenticationBroker ? "1" : "0")); 89 | } 90 | 91 | 92 | /// 93 | /// Requests the access code and username after authentication 94 | /// The access code has to permanently be stored within the users session, and should be passed in the constructor for all future PocketClient initializations. 95 | /// 96 | /// The cancellation token. 97 | /// The request code. 98 | /// 99 | /// The authenticated user 100 | /// 101 | /// Call GetRequestCode() first to receive a request_code 102 | public async Task GetUser(string requestCode = null, CancellationToken cancellationToken = default(CancellationToken)) 103 | { 104 | // check if request code is available 105 | if (RequestCode == null && requestCode == null) 106 | { 107 | throw new NullReferenceException("Call GetRequestCode() first to receive a request_code"); 108 | } 109 | 110 | // override property with given param if available 111 | if (requestCode != null) 112 | { 113 | RequestCode = requestCode; 114 | } 115 | 116 | // do request 117 | GetUserResponse response = await Request("oauth/authorize", cancellationToken, new Dictionary() 118 | { 119 | { "code", RequestCode }, 120 | { "account", "1" } 121 | }, false); 122 | 123 | string avatar = response.Account?.Profile?.Avatar_url; 124 | 125 | PocketUser user = new PocketUser() 126 | { 127 | Username = response.Username, 128 | Code = response.Access_token, 129 | Id = response.Account?.User_id, 130 | Email = response.Account?.Email, 131 | FirstName = response.Account?.First_name, 132 | LastName = response.Account?.Last_name, 133 | Followers = response.Account?.Profile?.Follower_count ?? 0, 134 | Follows = response.Account?.Profile?.Follow_count ?? 0, 135 | Avatar = avatar != null ? new Uri(avatar, UriKind.Absolute) : null, 136 | IsDefaultAvatar = avatar == null || avatar.Contains("pocket-profile-images."), 137 | Description = response.Account?.Profile?.Description 138 | }; 139 | 140 | // save code to client 141 | AccessCode = user.Code; 142 | 143 | return user; 144 | } 145 | 146 | 147 | /// 148 | /// Get a new GUID from the Pocket API. 149 | /// 150 | /// The cancellation token. 151 | /// 152 | /// The GUID 153 | /// 154 | public async Task GetGuid(CancellationToken cancellationToken = default(CancellationToken)) 155 | { 156 | GuidResponse response = await Request("guid", cancellationToken, requireAuth: false); 157 | return response.Guid; 158 | } 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /PocketSharp.Examples/PocketSharp.UWP/PocketSharp.UWP.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | x86 7 | {5CBB92EC-C1A2-4108-9A23-3B73DD138953} 8 | AppContainerExe 9 | Properties 10 | PocketSharp.UWP 11 | PocketSharp.UWP 12 | en-US 13 | UAP 14 | 10.0.16299.0 15 | 10.0.16299.0 16 | 14 17 | 512 18 | {A5A43C5B-DE2A-4C0C-9213-0A381AF9435A};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 19 | PocketSharp.UWP_TemporaryKey.pfx 20 | 21 | 22 | true 23 | bin\x86\Debug\ 24 | DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP 25 | ;2008 26 | full 27 | x86 28 | false 29 | prompt 30 | true 31 | 32 | 33 | bin\x86\Release\ 34 | TRACE;NETFX_CORE;WINDOWS_UWP 35 | true 36 | ;2008 37 | pdbonly 38 | x86 39 | false 40 | prompt 41 | true 42 | true 43 | 44 | 45 | true 46 | bin\ARM\Debug\ 47 | DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP 48 | ;2008 49 | full 50 | ARM 51 | false 52 | prompt 53 | true 54 | 55 | 56 | bin\ARM\Release\ 57 | TRACE;NETFX_CORE;WINDOWS_UWP 58 | true 59 | ;2008 60 | pdbonly 61 | ARM 62 | false 63 | prompt 64 | true 65 | true 66 | 67 | 68 | true 69 | bin\x64\Debug\ 70 | DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP 71 | ;2008 72 | full 73 | x64 74 | false 75 | prompt 76 | true 77 | 78 | 79 | bin\x64\Release\ 80 | TRACE;NETFX_CORE;WINDOWS_UWP 81 | true 82 | ;2008 83 | pdbonly 84 | x64 85 | false 86 | prompt 87 | true 88 | true 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | App.xaml 97 | 98 | 99 | MainPage.xaml 100 | 101 | 102 | 103 | 104 | 105 | Designer 106 | 107 | 108 | 109 | 110 | 111 | Designer 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | MSBuild:Compile 125 | Designer 126 | 127 | 128 | MSBuild:Compile 129 | Designer 130 | 131 | 132 | 133 | 134 | {0e22926c-7244-4a17-923c-84b46f5149c0} 135 | PocketSharp 136 | 137 | 138 | 139 | 14.0 140 | 141 | 142 | 149 | -------------------------------------------------------------------------------- /PocketSharp/Components/Modify.cs: -------------------------------------------------------------------------------- 1 | using PocketSharp.Models; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | 7 | namespace PocketSharp 8 | { 9 | /// 10 | /// PocketClient 11 | /// 12 | public partial class PocketClient 13 | { 14 | /// 15 | /// Sends multiple actions in one request. 16 | /// See: http://getpocket.com/developer/docs/v3/modify 17 | /// 18 | /// The actions. 19 | /// The cancellation token. 20 | /// 21 | /// 22 | public async Task SendActions(IEnumerable actions, CancellationToken cancellationToken = default(CancellationToken)) 23 | { 24 | return await Send(actions, cancellationToken); 25 | } 26 | 27 | 28 | /// 29 | /// Sends an action. 30 | /// See: http://getpocket.com/developer/docs/v3/modify 31 | /// 32 | /// The action. 33 | /// The cancellation token. 34 | /// 35 | /// 36 | public async Task SendAction(PocketAction action, CancellationToken cancellationToken = default(CancellationToken)) 37 | { 38 | return await Send(new List() { action }, cancellationToken); 39 | } 40 | 41 | 42 | /// 43 | /// Archives the specified item. 44 | /// 45 | /// The item ID. 46 | /// The cancellation token. 47 | /// 48 | /// 49 | public async Task Archive(string itemID, CancellationToken cancellationToken = default(CancellationToken)) 50 | { 51 | return await SendDefault(cancellationToken, itemID, "archive"); 52 | } 53 | 54 | 55 | /// 56 | /// Archives the specified item. 57 | /// 58 | /// The item. 59 | /// The cancellation token. 60 | /// 61 | /// 62 | public async Task Archive(PocketItem item, CancellationToken cancellationToken = default(CancellationToken)) 63 | { 64 | return await Archive(item.ID, cancellationToken); 65 | } 66 | 67 | 68 | /// 69 | /// Un-archives the specified item (alias for Readd). 70 | /// 71 | /// The item ID. 72 | /// The cancellation token. 73 | /// 74 | /// 75 | public async Task Unarchive(string itemID, CancellationToken cancellationToken = default(CancellationToken)) 76 | { 77 | return await SendDefault(cancellationToken, itemID, "readd"); 78 | } 79 | 80 | 81 | /// 82 | /// Unarchives the specified item. 83 | /// 84 | /// The item. 85 | /// The cancellation token. 86 | /// 87 | /// 88 | public async Task Unarchive(PocketItem item, CancellationToken cancellationToken = default(CancellationToken)) 89 | { 90 | return await Unarchive(item.ID, cancellationToken); 91 | } 92 | 93 | 94 | /// 95 | /// Favorites the specified item. 96 | /// 97 | /// The item ID. 98 | /// The cancellation token. 99 | /// 100 | /// 101 | public async Task Favorite(string itemID, CancellationToken cancellationToken = default(CancellationToken)) 102 | { 103 | return await SendDefault(cancellationToken, itemID, "favorite"); 104 | } 105 | 106 | 107 | /// 108 | /// Favorites the specified item. 109 | /// 110 | /// The item. 111 | /// The cancellation token. 112 | /// 113 | /// 114 | public async Task Favorite(PocketItem item, CancellationToken cancellationToken = default(CancellationToken)) 115 | { 116 | return await Favorite(item.ID, cancellationToken); 117 | } 118 | 119 | 120 | /// 121 | /// Un-favorites the specified item. 122 | /// 123 | /// The item ID. 124 | /// The cancellation token. 125 | /// 126 | /// 127 | public async Task Unfavorite(string itemID, CancellationToken cancellationToken = default(CancellationToken)) 128 | { 129 | return await SendDefault(cancellationToken, itemID, "unfavorite"); 130 | } 131 | 132 | 133 | /// 134 | /// Un-favorites the specified item. 135 | /// 136 | /// The item. 137 | /// The cancellation token. 138 | /// 139 | /// 140 | public async Task Unfavorite(PocketItem item, CancellationToken cancellationToken = default(CancellationToken)) 141 | { 142 | return await Unfavorite(item.ID, cancellationToken); 143 | } 144 | 145 | 146 | /// 147 | /// Deletes the specified item. 148 | /// 149 | /// The cancellation token. 150 | /// The item ID. 151 | /// 152 | /// 153 | public async Task Delete(string itemID, CancellationToken cancellationToken = default(CancellationToken)) 154 | { 155 | return await SendDefault(cancellationToken, itemID, "delete"); 156 | } 157 | 158 | 159 | /// 160 | /// Deletes the specified item. 161 | /// 162 | /// The cancellation token. 163 | /// The item. 164 | /// 165 | public async Task Delete(PocketItem item, CancellationToken cancellationToken = default(CancellationToken)) 166 | { 167 | return await Delete(item.ID, cancellationToken); 168 | } 169 | 170 | 171 | /// 172 | /// Puts an action 173 | /// 174 | /// The cancellation token. 175 | /// The item ID. 176 | /// The action. 177 | /// 178 | protected async Task SendDefault(CancellationToken cancellationToken, string itemID, string action) 179 | { 180 | return await Send(new PocketAction() 181 | { 182 | Action = action, 183 | ID = itemID 184 | }, cancellationToken); 185 | } 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /.nuget/NuGet.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $(MSBuildProjectDirectory)\..\ 5 | 6 | 7 | false 8 | 9 | 10 | false 11 | 12 | 13 | true 14 | 15 | 16 | false 17 | 18 | 19 | 20 | 21 | 22 | 26 | 27 | 28 | 29 | 30 | $([System.IO.Path]::Combine($(SolutionDir), ".nuget")) 31 | $([System.IO.Path]::Combine($(ProjectDir), "packages.config")) 32 | 33 | 34 | 35 | 36 | $(SolutionDir).nuget 37 | packages.config 38 | 39 | 40 | 41 | 42 | $(NuGetToolsPath)\NuGet.exe 43 | @(PackageSource) 44 | 45 | "$(NuGetExePath)" 46 | mono --runtime=v4.0.30319 $(NuGetExePath) 47 | 48 | $(TargetDir.Trim('\\')) 49 | 50 | -RequireConsent 51 | -NonInteractive 52 | 53 | "$(SolutionDir) " 54 | "$(SolutionDir)" 55 | 56 | 57 | $(NuGetCommand) install "$(PackagesConfig)" -source "$(PackageSources)" $(NonInteractiveSwitch) $(RequireConsentSwitch) -solutionDir $(PaddedSolutionDir) 58 | $(NuGetCommand) pack "$(ProjectPath)" -Properties "Configuration=$(Configuration);Platform=$(Platform)" $(NonInteractiveSwitch) -OutputDirectory "$(PackageOutputDir)" -symbols 59 | 60 | 61 | 62 | RestorePackages; 63 | $(BuildDependsOn); 64 | 65 | 66 | 67 | 68 | $(BuildDependsOn); 69 | BuildPackage; 70 | 71 | 72 | 73 | 74 | 75 | 76 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 91 | 92 | 95 | 96 | 97 | 98 | 100 | 101 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 133 | 134 | 135 | 136 | -------------------------------------------------------------------------------- /PocketSharp/Utilities/JsonExtensions.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using Newtonsoft.Json.Converters; 3 | using Newtonsoft.Json.Linq; 4 | using System; 5 | using System.Collections.Generic; 6 | using PocketSharp.Models; 7 | 8 | namespace PocketSharp 9 | { 10 | 11 | public class BoolConverter : JsonConverter 12 | { 13 | public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) 14 | { 15 | writer.WriteValue(((bool)value) ? 1 : 0); 16 | } 17 | 18 | public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) 19 | { 20 | return reader.Value == null ? false : (reader.Value.ToString() == "1"); 21 | } 22 | 23 | public override bool CanConvert(Type objectType) 24 | { 25 | return objectType == typeof(bool); 26 | } 27 | } 28 | 29 | 30 | 31 | public class UnixDateTimeConverter : DateTimeConverterBase 32 | { 33 | public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) 34 | { 35 | DateTime date = ((DateTime)value).ToUniversalTime(); 36 | DateTime epoc = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); 37 | var delta = date.Subtract(epoc); 38 | 39 | writer.WriteValue((int)Math.Truncate(delta.TotalSeconds)); 40 | } 41 | 42 | public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) 43 | { 44 | if (reader.Value.ToString() == "0") 45 | { 46 | return null; 47 | } 48 | 49 | if (reader.Value.ToString().StartsWith("-")) 50 | { 51 | return null; 52 | } 53 | 54 | double value; 55 | if (!Double.TryParse(reader.Value.ToString(), out value)) 56 | { 57 | DateTime date; 58 | if (DateTime.TryParse(reader.Value.ToString(), out date)) 59 | { 60 | return date; 61 | } 62 | 63 | return null; 64 | } 65 | 66 | return new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc).AddSeconds(value); 67 | } 68 | } 69 | 70 | 71 | 72 | public class NullableIntConverter : JsonConverter 73 | { 74 | public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) 75 | { 76 | writer.WriteValue(value); 77 | } 78 | 79 | public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) 80 | { 81 | int result = 0; 82 | if (reader.Value != null) 83 | { 84 | result = Convert.ToInt32(reader.Value); 85 | } 86 | return result; 87 | } 88 | 89 | public override bool CanConvert(Type objectType) 90 | { 91 | return objectType == typeof(int); 92 | } 93 | } 94 | 95 | 96 | 97 | public class UriConverter : JsonConverter 98 | { 99 | public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) 100 | { 101 | if (reader.TokenType != JsonToken.String) 102 | { 103 | return null; 104 | } 105 | 106 | string value = reader.Value.ToString(); 107 | 108 | if (Uri.IsWellFormedUriString(value, UriKind.Absolute)) 109 | { 110 | return new Uri(value); 111 | } 112 | else if (value.StartsWith("//") && Uri.IsWellFormedUriString("http:" + value, UriKind.Absolute)) 113 | { 114 | return new Uri("http:" + value); 115 | } 116 | else if (value.StartsWith("www.") && Uri.IsWellFormedUriString("http://" + value, UriKind.Absolute)) 117 | { 118 | return new Uri("http://" + value); 119 | } 120 | 121 | return null; 122 | } 123 | 124 | public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) 125 | { 126 | if (value == null) 127 | { 128 | writer.WriteNull(); 129 | } 130 | else if (value is Uri) 131 | { 132 | writer.WriteValue(((Uri)value).OriginalString); 133 | } 134 | } 135 | 136 | public override bool CanConvert(Type objectType) 137 | { 138 | return objectType.Equals(typeof(Uri)); 139 | } 140 | } 141 | 142 | 143 | 144 | public class ObjectToArrayConverter : CustomCreationConverter> where T : new() 145 | { 146 | public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) 147 | { 148 | JObject jObject; 149 | List results = new List(); 150 | 151 | // object is an array 152 | if (reader.TokenType == JsonToken.StartArray) 153 | { 154 | return serializer.Deserialize>(reader); 155 | } 156 | else if (reader.TokenType == JsonToken.Null) 157 | { 158 | return null; 159 | } 160 | else if (reader.TokenType == JsonToken.String) 161 | { 162 | return null; 163 | } 164 | 165 | try 166 | { 167 | jObject = JObject.Load(reader); 168 | } 169 | catch 170 | { 171 | return null; 172 | } 173 | 174 | // Populate the object properties 175 | foreach (KeyValuePair item in jObject) 176 | { 177 | results.Add( 178 | serializer.Deserialize(item.Value.CreateReader()) 179 | ); 180 | } 181 | 182 | return results; 183 | } 184 | 185 | public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) 186 | { 187 | throw new NotImplementedException(); 188 | } 189 | 190 | public override IEnumerable Create(Type objectType) 191 | { 192 | return new List(); 193 | } 194 | } 195 | 196 | 197 | 198 | public class PocketItemConverter : CustomCreationConverter 199 | { 200 | 201 | public override object ReadJson(Newtonsoft.Json.JsonReader reader, Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer) 202 | { 203 | var jObject = JObject.ReadFrom(reader); 204 | var pocketItem = new PocketItem(); 205 | serializer.Populate(jObject.CreateReader(), pocketItem); 206 | //pocketItem.Json = jObject.ToString(); 207 | 208 | return pocketItem; 209 | } 210 | 211 | public override PocketItem Create(Type objectType) 212 | { 213 | return new PocketItem(); 214 | } 215 | } 216 | 217 | 218 | 219 | public class VideoTypeConverter : JsonConverter 220 | { 221 | public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) 222 | { 223 | writer.WriteValue(((PocketVideoType)value).ToString()); 224 | } 225 | 226 | public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) 227 | { 228 | if (reader.Value == null) 229 | { 230 | return PocketVideoType.Unknown; 231 | } 232 | 233 | string nr = reader.Value.ToString(); 234 | 235 | if (nr == "1") 236 | { 237 | return PocketVideoType.YouTube; 238 | } 239 | if (nr == "2" || nr == "3" || nr == "4") 240 | { 241 | return PocketVideoType.Vimeo; 242 | } 243 | if (nr == "5") 244 | { 245 | return PocketVideoType.HTML; 246 | } 247 | if (nr == "6") 248 | { 249 | return PocketVideoType.Flash; 250 | } 251 | 252 | return PocketVideoType.Unknown; 253 | } 254 | 255 | public override bool CanConvert(Type objectType) 256 | { 257 | return objectType == typeof(int) || objectType == typeof(string); 258 | } 259 | } 260 | } 261 | -------------------------------------------------------------------------------- /PocketSharp/Components/ModifyTags.cs: -------------------------------------------------------------------------------- 1 | using PocketSharp.Models; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | 5 | namespace PocketSharp 6 | { 7 | /// 8 | /// PocketClient 9 | /// 10 | public partial class PocketClient 11 | { 12 | /// 13 | /// Adds the specified tags to an item. 14 | /// 15 | /// The item ID. 16 | /// The tags. 17 | /// The cancellation token. 18 | /// 19 | /// 20 | public async Task AddTags(string itemID, string[] tags, CancellationToken cancellationToken = default(CancellationToken)) 21 | { 22 | return await SendTags(cancellationToken, itemID, "tags_add", tags); 23 | } 24 | 25 | 26 | /// 27 | /// Adds the specified tags to an item. 28 | /// 29 | /// The item. 30 | /// The tags. 31 | /// The cancellation token. 32 | /// 33 | /// 34 | public async Task AddTags(PocketItem item, string[] tags, CancellationToken cancellationToken = default(CancellationToken)) 35 | { 36 | return await AddTags(item.ID, tags, cancellationToken); 37 | } 38 | 39 | 40 | /// 41 | /// Removes the specified tags from an item. 42 | /// 43 | /// The item ID. 44 | /// The tags. 45 | /// The cancellation token. 46 | /// 47 | /// 48 | public async Task RemoveTags(string itemID, string[] tags, CancellationToken cancellationToken = default(CancellationToken)) 49 | { 50 | return await SendTags(cancellationToken, itemID, "tags_remove", tags); 51 | } 52 | 53 | 54 | /// 55 | /// Removes the specified tags from an item. 56 | /// 57 | /// The item. 58 | /// The tag. 59 | /// The cancellation token. 60 | /// 61 | /// 62 | public async Task RemoveTags(PocketItem item, string[] tags, CancellationToken cancellationToken = default(CancellationToken)) 63 | { 64 | return await RemoveTags(item.ID, tags, cancellationToken); 65 | } 66 | 67 | 68 | /// 69 | /// Removes a tag from an item. 70 | /// 71 | /// The item ID. 72 | /// The tag. 73 | /// The cancellation token. 74 | /// 75 | /// 76 | public async Task RemoveTag(string itemID, string tag, CancellationToken cancellationToken = default(CancellationToken)) 77 | { 78 | return await SendTags(cancellationToken, itemID, "tags_remove", new string[] { tag }); 79 | } 80 | 81 | 82 | /// 83 | /// Removes a tag from an item. 84 | /// 85 | /// The item. 86 | /// The tag. 87 | /// The cancellation token. 88 | /// 89 | /// 90 | public async Task RemoveTag(PocketItem item, string tag, CancellationToken cancellationToken = default(CancellationToken)) 91 | { 92 | return await RemoveTag(item.ID, tag, cancellationToken); 93 | } 94 | 95 | 96 | /// 97 | /// Clears all tags from an item. 98 | /// 99 | /// The item ID. 100 | /// The cancellation token. 101 | /// 102 | /// 103 | public async Task RemoveTags(string itemID, CancellationToken cancellationToken = default(CancellationToken)) 104 | { 105 | return await SendDefault(cancellationToken, itemID, "tags_clear"); 106 | } 107 | 108 | 109 | /// 110 | /// Clears all tags from an item. 111 | /// 112 | /// The item. 113 | /// The cancellation token. 114 | /// 115 | /// 116 | public async Task RemoveTags(PocketItem item, CancellationToken cancellationToken = default(CancellationToken)) 117 | { 118 | return await RemoveTags(item.ID, cancellationToken); 119 | } 120 | 121 | 122 | /// 123 | /// Deletes a tag. This will remove it from all affected items too. 124 | /// 125 | /// The tag. 126 | /// The cancellation token. 127 | /// 128 | /// 129 | public async Task DeleteTag(string tag, CancellationToken cancellationToken = default(CancellationToken)) 130 | { 131 | return await Send(new PocketAction() 132 | { 133 | Action = "tag_delete", 134 | Tag = tag 135 | }, cancellationToken); 136 | } 137 | 138 | 139 | /// 140 | /// Replaces all existing tags with the given tags in an item. 141 | /// 142 | /// The item ID. 143 | /// The tags. 144 | /// The cancellation token. 145 | /// 146 | /// 147 | public async Task ReplaceTags(string itemID, string[] tags, CancellationToken cancellationToken = default(CancellationToken)) 148 | { 149 | return await SendTags(cancellationToken, itemID, "tags_replace", tags); 150 | } 151 | 152 | 153 | /// 154 | /// Replaces all existing tags with the given new ones in an item. 155 | /// 156 | /// The item. 157 | /// The tags. 158 | /// The cancellation token. 159 | /// 160 | /// 161 | public async Task ReplaceTags(PocketItem item, string[] tags, CancellationToken cancellationToken = default(CancellationToken)) 162 | { 163 | return await ReplaceTags(item.ID, tags, cancellationToken); 164 | } 165 | 166 | 167 | /// 168 | /// Renames a tag. This affects all items with this tag. 169 | /// 170 | /// The old tag. 171 | /// The new tag name. 172 | /// The cancellation token. 173 | /// 174 | /// 175 | public async Task RenameTag(string oldTag, string newTag, CancellationToken cancellationToken = default(CancellationToken)) 176 | { 177 | return await Send(new PocketAction() 178 | { 179 | Action = "tag_rename", 180 | OldTag = oldTag, 181 | NewTag = newTag 182 | }, cancellationToken); 183 | } 184 | 185 | 186 | /// 187 | /// Puts the send action for tags. 188 | /// 189 | /// The cancellation token. 190 | /// The item ID. 191 | /// The action. 192 | /// The tags. 193 | /// 194 | protected async Task SendTags(CancellationToken cancellationToken, string itemID, string action, string[] tags) 195 | { 196 | return await Send(new PocketAction() 197 | { 198 | Action = action, 199 | ID = itemID, 200 | Tags = tags 201 | }, cancellationToken); 202 | } 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /PocketSharp.Tests/GetTests.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json.Linq; 2 | using Newtonsoft.Json.Schema; 3 | using PocketSharp.Models; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Threading.Tasks; 7 | using Xunit; 8 | using System.Linq; 9 | 10 | namespace PocketSharp.Tests 11 | { 12 | public class GetTests : TestsBase 13 | { 14 | public GetTests() : base() { } 15 | 16 | 17 | [Fact] 18 | public async Task AreItemsRetrieved() 19 | { 20 | List items = (await client.Get()).ToList(); 21 | Assert.True(items.Count > 0); 22 | } 23 | 24 | 25 | [Fact] 26 | public async Task IsItemRetrievedById() 27 | { 28 | List items = (await client.Get()).ToList(); 29 | PocketItem item = items[0]; 30 | PocketItem itemDuplicate = await client.Get(item.ID); 31 | 32 | Assert.True(item.ID == itemDuplicate.ID); 33 | Assert.True(item.Uri == itemDuplicate.Uri); 34 | } 35 | 36 | 37 | [Fact] 38 | public async Task AreSuggestionsRetrieved() 39 | { 40 | var articles = await client.GetTrendingArticles(); 41 | string itemId = articles.First().ID; 42 | 43 | List suggestions = (await client.GetSuggestions(itemId)).ToList(); 44 | Assert.True(suggestions.Count > 0); 45 | } 46 | 47 | // [Fact] 48 | // public async Task IsItemJsonPopulated() 49 | // { 50 | // List items = (await client.Get()).ToList(); 51 | // string schemaJson = @"{ 52 | // 'description': 'PocketItem', 53 | // 'type': 'object' 54 | // }"; 55 | 56 | // JsonSchema schema = JsonSchema.Parse(schemaJson); 57 | // foreach (var pocketItem in items) 58 | // { 59 | // Assert.True(!string.IsNullOrWhiteSpace(pocketItem.Json)); 60 | // var jObject = JObject.Parse(pocketItem.Json); 61 | // Assert.True(jObject.IsValid(schema)); 62 | // } 63 | // } 64 | 65 | [Fact] 66 | public async Task AreFilteredItemsRetrieved() 67 | { 68 | IEnumerable items = await client.Get(RetrieveFilter.Favorite); 69 | 70 | Assert.True(items.Count() > 0); 71 | } 72 | 73 | 74 | [Fact] 75 | public async Task RetrieveWithMultipleFilters() 76 | { 77 | IEnumerable items = await client.Get( 78 | state: State.unread, 79 | tag: "pocket", 80 | sort: Sort.title, 81 | since: new DateTime(2010, 12, 10), 82 | count: 2 83 | ); 84 | 85 | Assert.InRange(items.Count(), 0, 2); 86 | } 87 | 88 | 89 | [Fact] 90 | public async Task ItemContainsUri() 91 | { 92 | IEnumerable items = await client.Get(count: 1); 93 | 94 | Assert.True(1 == items.Count()); 95 | Assert.StartsWith("http", items.First().Uri.OriginalString); 96 | } 97 | 98 | 99 | [Fact] 100 | public async Task InvalidRetrievalReturnsNoResults() 101 | { 102 | IEnumerable items = await client.Get( 103 | favorite: true, 104 | search: "xoiu987a#;" 105 | ); 106 | 107 | Assert.False(items.Count() > 0); 108 | 109 | PocketItem item = await client.Get("99999999"); 110 | 111 | Assert.Null(item); 112 | 113 | items = await client.Get(RetrieveFilter.Video); 114 | 115 | Assert.False(items.Count() > 0); 116 | } 117 | 118 | 119 | [Fact] 120 | public async Task SearchReturnsResult() 121 | { 122 | IEnumerable items = await client.Search("pocket"); 123 | 124 | Assert.True(items.Count() > 0); 125 | Assert.Contains("pocket", items.First().FullTitle.ToLower()); 126 | } 127 | 128 | 129 | [Fact] 130 | public async Task InvalidSearchReturnsNoResult() 131 | { 132 | IEnumerable items = await client.Search("adsüasd-opiu2;.398dfyx"); 133 | 134 | Assert.False(items.Count() > 0); 135 | } 136 | 137 | 138 | [Fact] 139 | public async Task RetrieveTagsReturnsResult() 140 | { 141 | IEnumerable items = await client.GetTags(); 142 | 143 | Assert.True(items.Count() > 0); 144 | } 145 | 146 | 147 | [Fact] 148 | public async Task SearchByTagsReturnsResult() 149 | { 150 | IEnumerable items = await client.SearchByTag("pocket"); 151 | 152 | Assert.True(items.Count() == 1); 153 | 154 | bool found = false; 155 | 156 | items.First().Tags.ToList().ForEach(tag => 157 | { 158 | if (tag.Name.Contains("pocket")) 159 | { 160 | found = true; 161 | } 162 | }); 163 | 164 | Assert.True(found); 165 | } 166 | 167 | 168 | [Fact] 169 | public async Task InvalidSearchByTagsReturnsNoResult() 170 | { 171 | IEnumerable items = await client.SearchByTag("adsüasd-opiu2;.398dfyx"); 172 | 173 | Assert.False(items.Count() > 0); 174 | } 175 | 176 | 177 | [Fact] 178 | public async Task AreStatisticsRetrieved() 179 | { 180 | PocketStatistics statistics = await client.GetUserStatistics(); 181 | 182 | Assert.True(statistics.CountAll > 0); 183 | } 184 | 185 | 186 | [Fact] 187 | public async Task AreLimitsRetrieved() 188 | { 189 | PocketLimits limits = await client.GetUsageLimits(); 190 | 191 | Assert.True(limits.RateLimitForConsumerKey > 9999); 192 | } 193 | 194 | 195 | [Fact] 196 | public async Task IsNewPocketItemListGeneratorWorking() 197 | { 198 | IEnumerable items = await client.Get(count: 1, tag: "pocket"); 199 | PocketItem item = items.First(); 200 | 201 | Assert.True(item.Tags.ToList().Find(i => i.Name == "pocket") != null); 202 | } 203 | 204 | 205 | [Fact] 206 | public async Task IsSinceParameterWorkingOnAddTags() 207 | { 208 | DateTime since = DateTime.Now; 209 | 210 | IEnumerable items = await client.Get(state: State.all); 211 | PocketItem itemToModify = items.First(); 212 | 213 | Assert.True(items.Count() >= 3); 214 | 215 | items = await client.Get(state: State.all, since: since); 216 | 217 | Assert.True(items == null || items.Count() == 0); 218 | 219 | await client.AddTags(itemToModify, new string[] { "pocketsharp" }); 220 | 221 | items = await client.Get(state: State.all, since: since); 222 | 223 | Assert.True(items.Count() > 0); 224 | 225 | await client.RemoveTags(itemToModify, new string[] { "pocketsharp" }); 226 | } 227 | 228 | 229 | [Fact] 230 | public async Task IsSinceParameterWorkingOnFavoriteAndArchiveModification() 231 | { 232 | DateTime since = DateTime.Now;//1409221736 233 | 234 | IEnumerable items = await client.Get(state: State.all); 235 | PocketItem itemToModify = items.First(); 236 | 237 | Assert.True(items.Count() >= 3); 238 | 239 | items = await client.Get(state: State.all, since: since); 240 | 241 | Assert.True(items == null || items.Count() == 0); 242 | 243 | await client.Favorite(itemToModify); 244 | 245 | items = await client.Get(state: State.all, since: since); 246 | 247 | Assert.True(items.Count() > 0); 248 | 249 | await client.Unfavorite(itemToModify); 250 | 251 | since = DateTime.Now; 252 | 253 | await client.Archive(itemToModify); 254 | 255 | items = await client.Get(state: State.all, since: since); 256 | 257 | Assert.True(items.Count() > 0); 258 | 259 | await client.Unarchive(itemToModify); 260 | } 261 | 262 | 263 | [Fact] 264 | public async Task IsUTCSinceParameterWorking() 265 | { 266 | DateTime since = DateTime.UtcNow; 267 | 268 | IEnumerable items = await client.Get(state: State.all); 269 | PocketItem itemToModify = items.First(); 270 | 271 | items = await client.Get(state: State.all, since: since); 272 | 273 | Assert.True(items == null || items.Count() == 0); 274 | 275 | await client.Favorite(itemToModify); 276 | 277 | items = await client.Get(state: State.all, since: since); 278 | 279 | Assert.True(items.Count() > 0); 280 | 281 | await client.Unfavorite(itemToModify); 282 | } 283 | 284 | 285 | [Fact] 286 | public async Task IsSinceParameterWorkingOnAddAndDelete() 287 | { 288 | DateTime since = DateTime.UtcNow; 289 | 290 | PocketItem item = await client.Add(new Uri("http://frontendplay.com")); 291 | 292 | IEnumerable items = await client.Get(state: State.all, since: since); 293 | 294 | since = DateTime.UtcNow; 295 | 296 | await client.Delete(item); 297 | 298 | items = await client.Get(state: State.all, since: since); 299 | 300 | Assert.True(items.Count() == 1 && items.First().IsDeleted); 301 | } 302 | 303 | [Fact] 304 | public async Task AreUncachedItemsProperlyResolved() 305 | { 306 | PocketItem item = await client.Add(new Uri("http://de.ign.com/m/feature/21608/die-20-besten-kurzfilme-des-jahres-2013?bust=1")); 307 | 308 | IEnumerable items = await client.Get(state: State.all); 309 | 310 | Assert.NotNull(item.Uri); 311 | Assert.NotNull(items.First().Uri); 312 | Assert.Equal(item.Uri, items.First().Uri); 313 | 314 | itemsToDelete.Add(item.ID); 315 | } 316 | } 317 | } 318 | -------------------------------------------------------------------------------- /PocketSharp/Components/Get.cs: -------------------------------------------------------------------------------- 1 | using PocketSharp.Models; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | 8 | namespace PocketSharp 9 | { 10 | /// 11 | /// PocketClient 12 | /// 13 | public partial class PocketClient 14 | { 15 | /// 16 | /// Retrieves items from pocket 17 | /// with the given filters 18 | /// 19 | /// The state. 20 | /// The favorite. 21 | /// The tag. 22 | /// Type of the content. 23 | /// The sort. 24 | /// The search. 25 | /// The domain. 26 | /// The since. 27 | /// The count. 28 | /// The offset. 29 | /// The cancellation token. 30 | /// 31 | /// 32 | public async Task> Get( 33 | State? state = null, 34 | bool? favorite = null, 35 | string tag = null, 36 | ContentType? contentType = null, 37 | Sort? sort = null, 38 | string search = null, 39 | string domain = null, 40 | DateTime? since = null, 41 | int? count = null, 42 | int? offset = null, 43 | CancellationToken cancellationToken = default(CancellationToken) 44 | ) 45 | { 46 | RetrieveParameters parameters = new RetrieveParameters() 47 | { 48 | State = state, 49 | Favorite = favorite, 50 | Tag = tag, 51 | ContentType = contentType, 52 | Sort = sort, 53 | DetailType = DetailType.complete, 54 | Search = search, 55 | Domain = domain, 56 | Since = since.HasValue ? ((DateTime)since).ToUniversalTime() : since, 57 | Count = count, 58 | Offset = offset, 59 | Version = 2 60 | }; 61 | 62 | return (await Request("get", cancellationToken, parameters.Convert())).Items ?? new List(); 63 | } 64 | 65 | 66 | /// 67 | /// Retrieves an item by a given ID 68 | /// Note: The Pocket API contains no method, which allows to retrieve a single item, so all items are retrieved and filtered locally by the ID. 69 | /// 70 | /// The item ID. 71 | /// The cancellation token. 72 | /// 73 | /// 74 | public async Task Get(string itemID, CancellationToken cancellationToken = default(CancellationToken)) 75 | { 76 | return (await Get( 77 | cancellationToken: cancellationToken, 78 | state: State.all 79 | )).SingleOrDefault(item => item.ID == itemID); 80 | } 81 | 82 | 83 | /// 84 | /// Retrieves all items by a given filter 85 | /// 86 | /// The filter. 87 | /// The cancellation token. 88 | /// 89 | /// 90 | public async Task> Get(RetrieveFilter filter, CancellationToken cancellationToken = default(CancellationToken)) 91 | { 92 | RetrieveParameters parameters = new RetrieveParameters(); 93 | 94 | switch (filter) 95 | { 96 | case RetrieveFilter.Article: 97 | parameters.ContentType = ContentType.article; 98 | break; 99 | case RetrieveFilter.Image: 100 | parameters.ContentType = ContentType.image; 101 | break; 102 | case RetrieveFilter.Video: 103 | parameters.ContentType = ContentType.video; 104 | break; 105 | case RetrieveFilter.Favorite: 106 | parameters.Favorite = true; 107 | break; 108 | case RetrieveFilter.Unread: 109 | parameters.State = State.unread; 110 | break; 111 | case RetrieveFilter.Archive: 112 | parameters.State = State.archive; 113 | break; 114 | case RetrieveFilter.All: 115 | parameters.State = State.all; 116 | break; 117 | } 118 | 119 | parameters.DetailType = DetailType.complete; 120 | 121 | return (await Request("get", cancellationToken, parameters.Convert())).Items; 122 | } 123 | 124 | 125 | /// 126 | /// Converts a raw JSON response to a PocketItem list 127 | /// 128 | /// The raw JSON response. 129 | /// 130 | /// 131 | public IEnumerable ConvertJsonToList(string itemsJSON) 132 | { 133 | return DeserializeJson(itemsJSON).Items; 134 | } 135 | 136 | 137 | /// 138 | /// Retrieves all available tags. 139 | /// Note: The Pocket API contains no method, which allows to retrieve all tags, so all items are retrieved and the associated tags extracted. 140 | /// 141 | /// The cancellation token. 142 | /// 143 | /// 144 | public async Task> GetTags(CancellationToken cancellationToken = default(CancellationToken)) 145 | { 146 | IEnumerable items = await Get( 147 | cancellationToken: cancellationToken, 148 | state: State.all 149 | ); 150 | 151 | return items 152 | .Where(item => item.Tags != null) 153 | .SelectMany(item => item.Tags) 154 | .GroupBy(item => item.Name) 155 | .Select(item => item.First()) 156 | .ToList(); 157 | } 158 | 159 | 160 | /// 161 | /// Retrieves items by tag 162 | /// 163 | /// The tag. 164 | /// The cancellation token. 165 | /// 166 | /// 167 | public async Task> SearchByTag(string tag, CancellationToken cancellationToken = default(CancellationToken)) 168 | { 169 | return await Get( 170 | state: State.all, 171 | cancellationToken: cancellationToken, 172 | tag: tag 173 | ); 174 | } 175 | 176 | 177 | /// 178 | /// Retrieves items which match the specified search string in title and URI 179 | /// 180 | /// The search string. 181 | /// Filter by tag. 182 | /// The cancellation token. 183 | /// 184 | /// Search string length has to be a minimum of 2 chars 185 | /// 186 | public async Task> Search(string searchString, string tag = null, CancellationToken cancellationToken = default(CancellationToken)) 187 | { 188 | if (String.IsNullOrEmpty(searchString) || searchString.Length < 2) 189 | { 190 | throw new ArgumentOutOfRangeException("Search string length has to be a minimum of 2 chars"); 191 | } 192 | 193 | return await Get( 194 | state: State.all, 195 | search: searchString, 196 | tag: tag, 197 | cancellationToken: cancellationToken 198 | ); 199 | } 200 | 201 | 202 | /// 203 | /// Retrieves the article content from an URI 204 | /// WARNING: 205 | /// You have to pass the parseUri in the PocketClient ctor for this method to work. 206 | /// This is a private API and can only be used by authenticated users. 207 | /// 208 | /// The article URI. 209 | /// Include images into content or use placeholder. 210 | /// Include videos into content or use placeholder. 211 | /// Force refresh of the content (don't use cache). 212 | /// The cancellation token. 213 | /// 214 | /// 215 | public async Task GetArticle(Uri uri, bool includeImages = true, bool includeVideos = true, bool forceRefresh = false, CancellationToken cancellationToken = default(CancellationToken)) 216 | { 217 | Dictionary parameters = new Dictionary() 218 | { 219 | { "url", uri.OriginalString }, 220 | { "images", includeImages ? "1" : "0" }, 221 | { "videos", includeVideos ? "1" : "0" }, 222 | { "refresh", forceRefresh ? "1" : "0" }, 223 | { "output", "json" } 224 | }; 225 | 226 | return await Request("", cancellationToken, parameters, false, true); 227 | } 228 | 229 | 230 | /// 231 | /// Get article suggestions for an existing Pocket item. 232 | /// 233 | /// Get suggestions based on this item. 234 | /// Requested item count. 235 | /// Two-letter language code for language-specific results. 236 | /// The cancellation token. 237 | /// 238 | /// 239 | public async Task> GetSuggestions(string itemId, int count = 3, string languageCode = "en", CancellationToken cancellationToken = default(CancellationToken)) 240 | { 241 | Dictionary parameters = new Dictionary() 242 | { 243 | { "resolved_id", itemId }, 244 | { "version", "1" }, 245 | { "locale_lang", languageCode }, 246 | { "count", count.ToString() } 247 | }; 248 | 249 | return (await Request("getSuggestedItems", cancellationToken, parameters)).Items ?? new List(); 250 | } 251 | } 252 | 253 | 254 | /// 255 | /// Filter for simple retrieve requests 256 | /// 257 | public enum RetrieveFilter 258 | { 259 | /// 260 | /// All types 261 | /// 262 | All, 263 | /// 264 | /// Only unread items 265 | /// 266 | Unread, 267 | /// 268 | /// Archived items 269 | /// 270 | Archive, 271 | /// 272 | /// Favorited items 273 | /// 274 | Favorite, 275 | /// 276 | /// Only articles 277 | /// 278 | Article, 279 | /// 280 | /// Only videos 281 | /// 282 | Video, 283 | /// 284 | /// Only images 285 | /// 286 | Image 287 | } 288 | } 289 | -------------------------------------------------------------------------------- /PocketSharp/Models/PocketItem.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Diagnostics; 5 | using System.Linq; 6 | 7 | namespace PocketSharp.Models 8 | { 9 | /// 10 | /// Item containing all available data 11 | /// see: http://getpocket.com/developer/docs/v3/retrieve 12 | /// 13 | [JsonObject] 14 | [DebuggerDisplay("Uri = {Uri}, Title = {Title}")] 15 | public class PocketItem : IComparable 16 | { 17 | /// 18 | /// Gets or sets the ID. 19 | /// 20 | /// 21 | /// The ID. 22 | /// 23 | [JsonProperty("item_id")] 24 | public string ID { get; set; } 25 | 26 | /// 27 | /// Gets or sets the resolved identifier. 28 | /// 29 | /// 30 | /// The resolved identifier. 31 | /// 32 | [JsonProperty("resolved_id")] 33 | public string ResolvedId { get; set; } 34 | 35 | /// 36 | /// Gets or sets the normal URI. 37 | /// 38 | /// 39 | /// The normal URI. 40 | /// 41 | [JsonProperty("normal_url")] 42 | private Uri _NormalUri { get; set; } 43 | 44 | /// 45 | /// Gets or sets the given URI. 46 | /// 47 | /// 48 | /// The given URI. 49 | /// 50 | [JsonProperty("given_url")] 51 | private Uri _GivenUri { get; set; } 52 | 53 | /// 54 | /// Gets or sets the resolved URI. 55 | /// 56 | /// 57 | /// The resolved URI. 58 | /// 59 | [JsonProperty("resolved_url")] 60 | private Uri _ResolvedUri { get; set; } 61 | 62 | /// 63 | /// Gets or sets the URI. 64 | /// 65 | /// 66 | /// The URI. 67 | /// 68 | [JsonIgnore] 69 | public Uri Uri 70 | { 71 | get { return _ResolvedUri ?? _GivenUri ?? _NormalUri; } 72 | set { _NormalUri = value; _ResolvedUri = value; _GivenUri = value; } 73 | } 74 | 75 | /// 76 | /// Gets or sets the title. 77 | /// 78 | /// 79 | /// The title. 80 | /// 81 | [JsonProperty("resolved_title")] 82 | private string _ResolvedTitle { get; set; } 83 | 84 | /// 85 | /// Gets or sets the title. 86 | /// 87 | /// 88 | /// The title. 89 | /// 90 | [JsonProperty("title")] 91 | private string _InternalTitle { get; set; } 92 | 93 | /// 94 | /// Gets or sets the title. 95 | /// 96 | /// 97 | /// The title. 98 | /// 99 | [JsonIgnore] 100 | public string Title 101 | { 102 | get { return _InternalTitle ?? _ResolvedTitle ?? FullTitle; } 103 | set { _InternalTitle = value; _ResolvedTitle = value; } 104 | } 105 | 106 | /// 107 | /// Gets or sets the full title. 108 | /// 109 | /// 110 | /// The full title. 111 | /// 112 | [JsonProperty("given_title")] 113 | public string FullTitle { get; set; } 114 | 115 | /// 116 | /// Gets or sets the excerpt. 117 | /// 118 | /// 119 | /// The excerpt. 120 | /// 121 | [JsonProperty] 122 | public string Excerpt { get; set; } 123 | 124 | /// 125 | /// Gets or sets the status. 126 | /// 127 | /// 128 | /// The status. 129 | /// 130 | [JsonProperty] 131 | public int Status { get; set; } 132 | 133 | /// 134 | /// Gets or sets a value indicating whether this instance is favorite. 135 | /// 136 | /// 137 | /// true if this instance is favorite; otherwise, false. 138 | /// 139 | [JsonProperty("favorite")] 140 | public bool IsFavorite { get; set; } 141 | 142 | /// 143 | /// Gets a value indicating whether this instance is archive. 144 | /// 145 | /// 146 | /// true if this instance is archive; otherwise, false. 147 | /// 148 | [JsonIgnore] 149 | public bool IsArchive { get { return Status == 1; } } 150 | 151 | /// 152 | /// Gets a value indicating whether this instance is deleted. 153 | /// 154 | /// 155 | /// true if this instance is deleted; otherwise, false. 156 | /// 157 | [JsonIgnore] 158 | public bool IsDeleted { get { return Status == 2; } } 159 | 160 | /// 161 | /// Gets or sets a value indicating whether this instance is article. 162 | /// 163 | /// 164 | /// true if this instance is article; otherwise, false. 165 | /// 166 | [JsonProperty("is_article")] 167 | public bool IsArticle { get; set; } 168 | 169 | /// 170 | /// Gets or sets a value indicating whether this instance has image. 171 | /// 172 | /// 173 | /// true if this instance has image; otherwise, false. 174 | /// 175 | [JsonProperty("has_image")] 176 | private PocketBoolean? _HasImage { get; set; } 177 | 178 | /// 179 | /// Gets or sets a value indicating whether this instance has video. 180 | /// 181 | /// 182 | /// true if this instance has video; otherwise, false. 183 | /// 184 | [JsonProperty("has_video")] 185 | private PocketBoolean? _HasVideo { get; set; } 186 | 187 | /// 188 | /// Gets a value indicating whether [has image]. 189 | /// 190 | /// 191 | /// true if [has image]; otherwise, false. 192 | /// 193 | [JsonIgnore] 194 | public bool HasImage 195 | { 196 | get 197 | { 198 | return _HasImage == PocketBoolean.Yes || _HasImage == PocketBoolean.IsType; 199 | } 200 | } 201 | 202 | /// 203 | /// Gets a value indicating whether [has video]. 204 | /// 205 | /// 206 | /// true if [has video]; otherwise, false. 207 | /// 208 | [JsonIgnore] 209 | public bool HasVideo 210 | { 211 | get 212 | { 213 | return _HasVideo == PocketBoolean.Yes || _HasVideo == PocketBoolean.IsType; 214 | } 215 | } 216 | 217 | /// 218 | /// Gets a value indicating whether [is video]. 219 | /// 220 | /// 221 | /// true if [is video]; otherwise, false. 222 | /// 223 | [JsonIgnore] 224 | public bool IsVideo 225 | { 226 | get 227 | { 228 | return _HasVideo == PocketBoolean.IsType; 229 | } 230 | } 231 | 232 | /// 233 | /// Gets a value indicating whether [is image]. 234 | /// 235 | /// 236 | /// true if [is image]; otherwise, false. 237 | /// 238 | [JsonIgnore] 239 | public bool IsImage 240 | { 241 | get 242 | { 243 | return _HasImage == PocketBoolean.IsType; 244 | } 245 | } 246 | 247 | /// 248 | /// Gets or sets the word count. 249 | /// 250 | /// 251 | /// The word count. 252 | /// 253 | [JsonProperty("word_count")] 254 | public int WordCount { get; set; } 255 | 256 | /// 257 | /// Gets or sets the sort. 258 | /// 259 | /// 260 | /// The sort. 261 | /// 262 | [JsonProperty("sort_id")] 263 | public int Sort { get; set; } 264 | 265 | 266 | /// 267 | /// Gets or sets the add time. 268 | /// 269 | /// 270 | /// The add time. 271 | /// 272 | [JsonProperty("time_added")] 273 | public DateTime? AddTime { get; set; } 274 | 275 | /// 276 | /// Gets or sets the update time. 277 | /// 278 | /// 279 | /// The update time. 280 | /// 281 | [JsonProperty("time_updated")] 282 | public DateTime? UpdateTime { get; set; } 283 | 284 | /// 285 | /// Gets or sets the read time. 286 | /// 287 | /// 288 | /// The read time. 289 | /// 290 | [JsonProperty("time_read")] 291 | public DateTime? ReadTime { get; set; } 292 | 293 | /// 294 | /// Gets or sets the favorite time. 295 | /// 296 | /// 297 | /// The favorite time. 298 | /// 299 | [JsonProperty("time_favorited")] 300 | public DateTime? FavoriteTime { get; set; } 301 | 302 | /// 303 | /// Gets or sets the published time. 304 | /// 305 | /// 306 | /// The time when the article was published. 307 | /// 308 | [JsonProperty("date_published")] 309 | public DateTime? PublishedTime { get; set; } 310 | 311 | /// 312 | /// Gets or sets the tags as comma-separated strings. 313 | /// 314 | /// 315 | /// The tags. 316 | /// 317 | [JsonIgnore] 318 | public string TagsString 319 | { 320 | get { return Tags != null ? String.Join(",", Tags.Select(tag => tag.Name)) : ""; } 321 | } 322 | 323 | /// 324 | /// Gets or sets the tags. 325 | /// 326 | /// 327 | /// The tags. 328 | /// 329 | [JsonProperty("tags")] 330 | [JsonConverter(typeof(ObjectToArrayConverter))] 331 | public IEnumerable Tags { get; set; } 332 | 333 | /// 334 | /// Gets or sets the images. 335 | /// 336 | /// 337 | /// The images. 338 | /// 339 | [JsonProperty("images")] 340 | [JsonConverter(typeof(ObjectToArrayConverter))] 341 | public IEnumerable Images { get; set; } 342 | 343 | /// 344 | /// Gets or sets the videos. 345 | /// 346 | /// 347 | /// The videos. 348 | /// 349 | [JsonProperty("videos")] 350 | [JsonConverter(typeof(ObjectToArrayConverter))] 351 | public IEnumerable Videos { get; set; } 352 | 353 | /// 354 | /// Gets or sets the authors. 355 | /// 356 | /// 357 | /// The authors. 358 | /// 359 | [JsonProperty("authors")] 360 | [JsonConverter(typeof(ObjectToArrayConverter))] 361 | public IEnumerable Authors { get; set; } 362 | 363 | /// 364 | /// Gets or sets a value indicating whether this instance is trending. 365 | /// 366 | /// 367 | /// true if this instance is trending; otherwise, false. 368 | /// 369 | [JsonProperty("trending")] 370 | public bool IsTrending { get; set; } 371 | 372 | /// 373 | /// Gets the lead image. 374 | /// 375 | /// 376 | /// The lead image. 377 | /// 378 | [JsonIgnore] 379 | public PocketImage LeadImage 380 | { 381 | get { return Images != null && Images.Count() > 0 ? Images.First() : null; } 382 | } 383 | 384 | /// 385 | /// Compares the current instance with another object of the same type and returns an integer that indicates whether the current instance precedes, follows, or occurs in the same position in the sort order as the other object. 386 | /// 387 | /// An object to compare with this instance. 388 | /// 389 | /// A value that indicates the relative order of the objects being compared. The return value has these meanings: Value Meaning Less than zero This instance precedes in the sort order. Zero This instance occurs in the same position in the sort order as . Greater than zero This instance follows in the sort order. 390 | /// 391 | int IComparable.CompareTo(object obj) 392 | { 393 | PocketItem item = (PocketItem)obj; 394 | 395 | if (!AddTime.HasValue) 396 | { 397 | return 1; 398 | } 399 | if (!item.AddTime.HasValue) 400 | { 401 | return -1; 402 | } 403 | 404 | return DateTime.Compare(AddTime.Value, item.AddTime.Value); 405 | } 406 | 407 | /// 408 | /// Determines whether the specified , is equal to this instance. 409 | /// 410 | /// The to compare with this instance. 411 | /// 412 | /// true if the specified is equal to this instance; otherwise, false. 413 | /// 414 | public override bool Equals(object obj) 415 | { 416 | if (obj == null) 417 | { 418 | return false; 419 | } 420 | 421 | PocketItem item = obj as PocketItem; 422 | 423 | if (item == null) 424 | { 425 | return false; 426 | } 427 | 428 | return ID == item.ID; 429 | } 430 | 431 | 432 | /// 433 | /// Implements the operator ==. 434 | /// 435 | /// A. 436 | /// The b. 437 | /// 438 | /// The result of the operator. 439 | /// 440 | public static bool operator ==(PocketItem a, PocketItem b) 441 | { 442 | if (Object.ReferenceEquals(a, b)) 443 | { 444 | return true; 445 | } 446 | 447 | PocketItem itemA = (PocketItem)a; 448 | PocketItem itemB = (PocketItem)b; 449 | 450 | if ((Object)itemA == null || (Object)itemB == null) 451 | { 452 | return false; 453 | } 454 | 455 | return itemA.ID == itemB.ID; 456 | } 457 | 458 | /// 459 | /// Implements the operator !=. 460 | /// 461 | /// A. 462 | /// The b. 463 | /// 464 | /// The result of the operator. 465 | /// 466 | public static bool operator !=(PocketItem a, PocketItem b) 467 | { 468 | return !(a == b); 469 | } 470 | 471 | /// 472 | /// Returns a hash code for this instance. 473 | /// 474 | /// 475 | /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. 476 | /// 477 | public override int GetHashCode() 478 | { 479 | return ID.GetHashCode(); 480 | } 481 | 482 | /// 483 | /// Returns a that represents this instance. 484 | /// 485 | /// 486 | /// A that represents this instance. 487 | /// 488 | public override string ToString() 489 | { 490 | return ID; 491 | } 492 | } 493 | } 494 | -------------------------------------------------------------------------------- /PocketSharp/PocketClient.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using Newtonsoft.Json.Serialization; 3 | using PocketSharp.Models; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Net; 7 | using System.Net.Http; 8 | using System.Net.Http.Headers; 9 | using System.Threading; 10 | using System.Threading.Tasks; 11 | using System.Linq; 12 | 13 | namespace PocketSharp 14 | { 15 | /// 16 | /// PocketClient 17 | /// 18 | public partial class PocketClient : IPocketClient, IDisposable 19 | { 20 | /// 21 | /// REST client used for the API communication 22 | /// 23 | protected readonly HttpClient httpClient; 24 | 25 | /// 26 | /// Caches HTTP headers from last response 27 | /// 28 | public HttpResponseHeaders lastHeaders; 29 | 30 | /// 31 | /// Caches JSON data from last response 32 | /// 33 | public string lastResponseData; 34 | 35 | /// 36 | /// The base URL for the Pocket API 37 | /// 38 | protected static string baseUri = "https://getpocket.com/v3/"; 39 | 40 | /// 41 | /// The base URL for Pocket reader requests 42 | /// 43 | protected static string parserUri = null; 44 | 45 | /// 46 | /// The authentification URL 47 | /// 48 | protected string authentificationUri = "https://getpocket.com/auth/authorize?request_token={0}&redirect_uri={1}&mobile={2}&force={3}&webauthenticationbroker={4}"; 49 | 50 | /// 51 | /// Indicates, whether this client is used for mobile or desktop 52 | /// 53 | protected bool isMobileClient = true; 54 | 55 | /// 56 | /// Indicates, whether this client is used inside a broker (on Windows 8) 57 | /// 58 | protected bool useInsideWebAuthenticationBroker = true; 59 | 60 | /// 61 | /// Indicates, whether the last HTTP response is cached 62 | /// 63 | protected bool cacheHTTPResponseData; 64 | 65 | /// 66 | /// callback URLi for API calls 67 | /// 68 | /// 69 | /// The callback URI. 70 | /// 71 | public string CallbackUri { get; set; } 72 | 73 | /// 74 | /// Accessor for the Pocket API key 75 | /// see: http://getpocket.com/developer 76 | /// 77 | /// 78 | /// The consumer key. 79 | /// 80 | public string ConsumerKey { get; set; } 81 | 82 | /// 83 | /// Code retrieved on authentification 84 | /// 85 | /// 86 | /// The request code. 87 | /// 88 | public string RequestCode { get; set; } 89 | 90 | /// 91 | /// Code retrieved on authentification-success 92 | /// 93 | /// 94 | /// The access code. 95 | /// 96 | public string AccessCode { get; set; } 97 | 98 | /// 99 | /// Action which is executed before every request 100 | /// 101 | /// 102 | /// The pre request callback. 103 | /// 104 | public Action PreRequest { get; set; } 105 | 106 | /// 107 | /// Action which is executed after every request 108 | /// 109 | /// 110 | /// The after request callback. 111 | /// 112 | public Action AfterRequest { get; set; } 113 | 114 | 115 | /// 116 | /// Initializes a new instance of the class. 117 | /// 118 | /// The API key 119 | /// Provide an access code if the user is already authenticated 120 | /// The callback URL is called by Pocket after authentication 121 | /// The HttpMessage handler. 122 | /// Request timeout (in seconds). 123 | /// Indicates, whether this client is used for mobile or desktop 124 | /// Indicates, whether this client is used inside a broker (on Windows 8), see: http://getpocket.com/developer/docs/getstarted/windows8 125 | /// Enables the wrapper for the private Text Parser API 126 | /// Caches the last HTTP response in public properties 127 | public PocketClient( 128 | string consumerKey, 129 | string accessCode = null, 130 | string callbackUri = null, 131 | HttpMessageHandler handler = null, 132 | int? timeout = null, 133 | bool isMobileClient = true, 134 | bool useInsideWebAuthenticationBroker = false, 135 | Uri parserUri = null, 136 | bool cacheHTTPResponseData = true) 137 | { 138 | // assign public properties 139 | ConsumerKey = consumerKey; 140 | 141 | this.isMobileClient = isMobileClient; 142 | this.useInsideWebAuthenticationBroker = useInsideWebAuthenticationBroker; 143 | this.cacheHTTPResponseData = cacheHTTPResponseData; 144 | PocketClient.parserUri = parserUri?.OriginalString; 145 | 146 | // assign access code if submitted 147 | if (accessCode != null) 148 | { 149 | AccessCode = accessCode.ToString(); 150 | } 151 | 152 | // assign callback uri if submitted 153 | if (callbackUri != null) 154 | { 155 | CallbackUri = Uri.EscapeUriString(callbackUri.ToString()); 156 | } 157 | 158 | // initialize REST client 159 | httpClient = new HttpClient(handler ?? new HttpClientHandler()); 160 | 161 | if (timeout.HasValue) 162 | { 163 | httpClient.Timeout = TimeSpan.FromSeconds(timeout.Value); 164 | } 165 | 166 | // Pocket needs this specific Accept header :-S 167 | httpClient.DefaultRequestHeaders.Add("Accept", "*/*"); 168 | 169 | // defines the response format (according to the Pocket docs) 170 | httpClient.DefaultRequestHeaders.Add("X-Accept", "application/json"); 171 | } 172 | 173 | 174 | /// 175 | /// Fetches a typed resource 176 | /// 177 | /// 178 | /// Requested method (path after /v3/) 179 | /// The cancellation token. 180 | /// Additional POST parameters 181 | /// if set to true [require auth]. 182 | /// 183 | /// No access token available. Use authentification first. 184 | protected async Task Request( 185 | string method, 186 | CancellationToken cancellationToken, 187 | Dictionary parameters = null, 188 | bool requireAuth = true, 189 | bool isReaderRequest = false) where T : class, new() 190 | { 191 | if (requireAuth && AccessCode == null) 192 | { 193 | throw new PocketException("SDK error: No access token available. Use authentication first."); 194 | } 195 | 196 | string httpBase = isReaderRequest ? parserUri : baseUri; 197 | 198 | if (isReaderRequest && String.IsNullOrEmpty(parserUri)) 199 | { 200 | throw new PocketException("Please pass a valid parserUri in the PocketClient ctor."); 201 | } 202 | 203 | // every single Pocket API endpoint requires HTTP POST data 204 | HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, httpBase + method); 205 | HttpResponseMessage response = null; 206 | string responseString = null; 207 | 208 | if (parameters == null) 209 | { 210 | parameters = new Dictionary(); 211 | } 212 | 213 | // add consumer key to each request 214 | parameters.Add("consumer_key", ConsumerKey); 215 | 216 | // add access token (necessary for all requests except authentification) 217 | if (AccessCode != null && requireAuth) 218 | { 219 | parameters.Add("access_token", AccessCode); 220 | } 221 | 222 | // content of the request 223 | request.Content = new FormUrlEncodedContent(parameters); 224 | 225 | // call pre request action 226 | PreRequest?.Invoke(method); 227 | 228 | // make async request 229 | try 230 | { 231 | response = await httpClient.SendAsync(request, cancellationToken); 232 | 233 | // validate HTTP response 234 | ValidateResponse(response); 235 | 236 | // cache headers 237 | if (cacheHTTPResponseData) 238 | { 239 | lastHeaders = response.Headers; 240 | } 241 | 242 | // read response 243 | responseString = await response.Content.ReadAsStringAsync(); 244 | } 245 | catch (HttpRequestException exc) 246 | { 247 | throw new PocketException(exc.Message, exc); 248 | } 249 | catch (PocketException exc) 250 | { 251 | throw exc; 252 | } 253 | finally 254 | { 255 | request.Dispose(); 256 | 257 | if (response != null) 258 | { 259 | response.Dispose(); 260 | } 261 | } 262 | 263 | // call after request action 264 | AfterRequest?.Invoke(responseString); 265 | 266 | // cache response 267 | if (cacheHTTPResponseData) 268 | { 269 | lastResponseData = responseString; 270 | } 271 | 272 | return DeserializeJson(responseString); 273 | } 274 | 275 | 276 | /// 277 | /// Fetches a string from a resource 278 | /// 279 | /// URI 280 | /// The cancellation token. 281 | /// 282 | /// 283 | protected async Task RequestAsString(string uri, CancellationToken cancellationToken) 284 | { 285 | // every single Pocket API endpoint requires HTTP POST data 286 | HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, uri); 287 | HttpResponseMessage response = null; 288 | string responseString = null; 289 | 290 | // call pre request action 291 | PreRequest?.Invoke(uri); 292 | 293 | // make async request 294 | try 295 | { 296 | response = await httpClient.GetAsync(uri, cancellationToken); 297 | 298 | // validate HTTP response 299 | ValidateResponse(response); 300 | 301 | // cache headers 302 | if (cacheHTTPResponseData) 303 | { 304 | lastHeaders = response.Headers; 305 | } 306 | 307 | // read response 308 | responseString = await response.Content.ReadAsStringAsync(); 309 | } 310 | catch (HttpRequestException exc) 311 | { 312 | throw new PocketException(exc.Message, exc); 313 | } 314 | catch (PocketException exc) 315 | { 316 | throw exc; 317 | } 318 | finally 319 | { 320 | request.Dispose(); 321 | 322 | if (response != null) 323 | { 324 | response.Dispose(); 325 | } 326 | } 327 | 328 | // call after request action 329 | AfterRequest?.Invoke(responseString); 330 | 331 | // cache response 332 | if (cacheHTTPResponseData) 333 | { 334 | lastResponseData = responseString; 335 | } 336 | 337 | return responseString; 338 | } 339 | 340 | 341 | /// 342 | /// Converts JSON to Pocket objects 343 | /// 344 | /// 345 | /// Raw JSON response 346 | /// 347 | /// Parse error. 348 | protected T DeserializeJson(string json) where T : class, new() 349 | { 350 | json = json.Replace("[]", "{}"); 351 | 352 | // deserialize object 353 | T parsedResponse = JsonConvert.DeserializeObject( 354 | json, 355 | new JsonSerializerSettings 356 | { 357 | Error = (object sender, ErrorEventArgs args) => 358 | { 359 | throw new PocketException(String.Format("Parse error: {0}", args.ErrorContext.Error.Message)); 360 | }, 361 | Converters = 362 | { 363 | new PocketItemConverter(), 364 | new BoolConverter(), 365 | new UnixDateTimeConverter(), 366 | new NullableIntConverter(), 367 | new UriConverter() 368 | } 369 | } 370 | ); 371 | 372 | return parsedResponse; 373 | } 374 | 375 | 376 | /// 377 | /// Sends a list of actions 378 | /// 379 | /// The action parameters. 380 | /// 381 | internal async Task Send(IEnumerable actionParameters, CancellationToken cancellationToken) 382 | { 383 | foreach (PocketAction action in actionParameters) 384 | { 385 | action.Time = action.Time.HasValue ? ((DateTime)action.Time).ToUniversalTime() : action.Time; 386 | } 387 | 388 | Dictionary parameters = new Dictionary() {{ 389 | "actions", JsonConvert.SerializeObject(actionParameters.Select(action => action.Convert())) 390 | }}; 391 | 392 | return (await Request("send", cancellationToken, parameters)).Status; 393 | } 394 | 395 | 396 | /// 397 | /// Sends an action 398 | /// 399 | /// The action parameter. 400 | /// 401 | internal async Task Send(PocketAction actionParameter, CancellationToken cancellationToken) 402 | { 403 | return await Send(new List() { actionParameter }, cancellationToken); 404 | } 405 | 406 | 407 | /// 408 | /// Validates the response. 409 | /// 410 | /// The response. 411 | /// 412 | /// 413 | /// Error retrieving response 414 | /// 415 | protected void ValidateResponse(HttpResponseMessage response) 416 | { 417 | // no error found 418 | if (response.StatusCode == HttpStatusCode.OK) 419 | { 420 | return; 421 | } 422 | 423 | string exceptionString = response.ReasonPhrase; 424 | bool isPocketError = response.Headers.Contains("X-Error"); 425 | 426 | // fetch custom pocket headers 427 | string error = TryGetHeaderValue(response.Headers, "X-Error"); 428 | int errorCode = Convert.ToInt32(TryGetHeaderValue(response.Headers, "X-Error-Code")); 429 | 430 | // create exception strings 431 | if (isPocketError) 432 | { 433 | exceptionString = String.Format("Pocket error: {0} ({1}) ", error, errorCode); 434 | } 435 | else 436 | { 437 | exceptionString = String.Format("Request error: {0} ({1})", response.ReasonPhrase, (int)response.StatusCode); 438 | } 439 | 440 | // create exception 441 | PocketException exception = new PocketException(exceptionString); 442 | 443 | if (isPocketError) 444 | { 445 | // add custom pocket fields 446 | exception.PocketError = error; 447 | exception.PocketErrorCode = errorCode; 448 | 449 | // add to generic exception data 450 | exception.Data.Add("X-Error", error); 451 | exception.Data.Add("X-Error-Code", errorCode); 452 | } 453 | 454 | throw exception; 455 | } 456 | 457 | 458 | /// 459 | /// Tries to fetch a header value. 460 | /// 461 | /// The headers. 462 | /// The key. 463 | /// 464 | protected string TryGetHeaderValue(HttpResponseHeaders headers, string key) 465 | { 466 | string result = null; 467 | 468 | if (headers == null || String.IsNullOrEmpty(key)) 469 | { 470 | return null; 471 | } 472 | 473 | foreach (var header in headers) 474 | { 475 | if (header.Key == key) 476 | { 477 | var headerEnumerator = header.Value.GetEnumerator(); 478 | headerEnumerator.MoveNext(); 479 | 480 | result = headerEnumerator.Current; 481 | break; 482 | } 483 | } 484 | 485 | return result; 486 | } 487 | 488 | 489 | /// 490 | /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. 491 | /// 492 | public void Dispose() 493 | { 494 | httpClient.Dispose(); 495 | lastHeaders = null; 496 | } 497 | } 498 | } 499 | --------------------------------------------------------------------------------