using Newtonsoft.Json; using Newtonsoft.Json.Serialization; using PocketSharp.Models; using System; using System.Collections.Generic; using System.Net; using System.Net.Http; using System.Net.Http.Headers; using System.Threading; using System.Threading.Tasks; namespace PocketSharp { /// /// PocketClient /// public partial class PocketClient : IPocketClient { /// /// REST client used for the API communication /// protected readonly HttpClient _restClient; /// /// Caches HTTP headers from last response /// public HttpResponseHeaders lastHeaders; /// /// Caches JSON data from last response /// public string lastResponseData; /// /// The base URL for the Pocket API /// protected static Uri baseUri = new Uri("https://getpocket.com/v3/"); /// /// The authentification URL /// protected string authentificationUri = "https://getpocket.com/auth/authorize?request_token={0}&redirect_uri={1}&mobile={2}"; /// /// Indicates, whether this client is used for mobile or desktop /// protected bool isMobileClient = true; /// /// callback URLi for API calls /// /// /// The callback URI. /// public string CallbackUri { get; set; } /// /// Accessor for the Pocket API key /// see: http://getpocket.com/developer /// /// /// The consumer key. /// public string ConsumerKey { get; set; } /// /// Code retrieved on authentification /// /// /// The request code. /// public string RequestCode { get; set; } /// /// Code retrieved on authentification-success /// /// /// The access code. /// public string AccessCode { get; set; } /// /// Action which is executed before every request /// /// /// The pre request callback. /// public Action PreRequest { get; set; } /// /// Initializes a new instance of the class. /// /// The API key /// Provide an access code if the user is already authenticated /// The callback URL is called by Pocket after authentication /// The HttpMessage handler. /// Request timeout (in seconds). /// Indicates, whether this client is used for mobile or desktop public PocketClient( string consumerKey, string accessCode = null, string callbackUri = null, HttpMessageHandler handler = null, int? timeout = null, bool isMobileClient = true) { // assign public properties ConsumerKey = consumerKey; this.isMobileClient = isMobileClient; // assign access code if submitted if (accessCode != null) { AccessCode = accessCode.ToString(); } // assign callback uri if submitted if (callbackUri != null) { CallbackUri = Uri.EscapeUriString(callbackUri.ToString()); } // initialize REST client _restClient = new HttpClient(handler ?? new HttpClientHandler() { AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip }); if (timeout.HasValue) { _restClient.Timeout = TimeSpan.FromSeconds(timeout.Value); } // set base uri _restClient.BaseAddress = baseUri; // Pocket needs this specific Accept header :-S _restClient.DefaultRequestHeaders.Add("Accept", "*/*"); // defines the response format (according to the Pocket docs) _restClient.DefaultRequestHeaders.Add("X-Accept", "application/json"); } /// /// Fetches a typed resource /// /// /// Requested method (path after /v3/) /// The cancellation token. /// Additional POST parameters /// if set to true [require auth]. /// /// No access token available. Use authentification first. protected async Task Request( string method, CancellationToken cancellationToken, Dictionary parameters = null, bool requireAuth = true) where T : class, new() { if (requireAuth && AccessCode == null) { throw new PocketException("SDK error: No access token available. Use authentication first."); } // every single Pocket API endpoint requires HTTP POST data HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, method); HttpResponseMessage response = null; if (parameters == null) { parameters = new Dictionary(); } // add consumer key to each request parameters.Add("consumer_key", ConsumerKey); // add access token (necessary for all requests except authentification) if (AccessCode != null) { parameters.Add("access_token", AccessCode); } // content of the request request.Content = new FormUrlEncodedContent(parameters); // call pre request action if (PreRequest != null) { PreRequest(method); } // make async request try { response = await _restClient.SendAsync(request, cancellationToken); } catch (HttpRequestException exc) { throw new PocketException(exc.Message, exc); } // validate HTTP response ValidateResponse(response); // cache headers lastHeaders = response.Headers; // read response var responseString = await response.Content.ReadAsStringAsync(); // cache response lastResponseData = responseString; responseString = responseString.Replace("[]", "{}"); // deserialize object T parsedResponse = JsonConvert.DeserializeObject( responseString, new JsonSerializerSettings { Error = (object sender, ErrorEventArgs args) => { throw new PocketException(String.Format("Parse error: {0}", args.ErrorContext.Error.Message)); }, Converters = { new PocketItemConverter(), new BoolConverter(), new UnixDateTimeConverter(), new NullableIntConverter(), new UriConverter() } } ); return parsedResponse; } /// /// Sends a list of actions /// /// The action parameters. /// internal async Task Send(List actionParameters, CancellationToken cancellationToken) { List> actionParamList = new List>(); foreach (var action in actionParameters) { actionParamList.Add(action.Convert()); } Dictionary parameters = new Dictionary() {{ "actions", JsonConvert.SerializeObject(actionParamList) }}; Modify response = await Request("send", cancellationToken, parameters); return response.Status; } /// /// Sends an action /// /// The action parameter. /// internal async Task Send(PocketAction actionParameter, CancellationToken cancellationToken) { return await Send(new List() { actionParameter }, cancellationToken); } /// /// Validates the response. /// /// The response. /// /// /// Error retrieving response /// protected void ValidateResponse(HttpResponseMessage response) { // no error found if (response.StatusCode == HttpStatusCode.OK) { return; } string exceptionString = response.ReasonPhrase; bool isPocketError = response.Headers.Contains("X-Error"); // fetch custom pocket headers string error = TryGetHeaderValue(response.Headers, "X-Error"); int errorCode = Convert.ToInt32(TryGetHeaderValue(response.Headers, "X-Error-Code")); // create exception strings if (isPocketError) { exceptionString = String.Format("Pocket error: {0} ({1}) ", error, errorCode); } else { exceptionString = String.Format("Request error: {0} ({1})", response.ReasonPhrase, (int)response.StatusCode); } // create exception PocketException exception = new PocketException(exceptionString); if (isPocketError) { // add custom pocket fields exception.PocketError = error; exception.PocketErrorCode = errorCode; // add to generic exception data exception.Data.Add("X-Error", error); exception.Data.Add("X-Error-Code", errorCode); } throw exception; } /// /// Tries to fetch a header value. /// /// The headers. /// The key. /// protected string TryGetHeaderValue(HttpResponseHeaders headers, string key) { string result = null; if (headers == null || String.IsNullOrEmpty(key)) { return null; } foreach (var header in headers) { if (header.Key == key) { var headerEnumerator = header.Value.GetEnumerator(); headerEnumerator.MoveNext(); result = headerEnumerator.Current; break; } } return result; } } }