1 // OpenTween - Client of Twitter
2 // Copyright (c) 2016 kim_upsilon (@kim_upsilon) <https://upsilo.net/~upsilon/>
3 // All rights reserved.
5 // This file is part of OpenTween.
7 // This program is free software; you can redistribute it and/or modify it
8 // under the terms of the GNU General Public License as published by the Free
9 // Software Foundation; either version 3 of the License, or (at your option)
12 // This program is distributed in the hope that it will be useful, but
13 // WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
14 // or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
17 // You should have received a copy of the GNU General Public License along
18 // with this program. If not, see <http://www.gnu.org/licenses/>, or write to
19 // the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor,
20 // Boston, MA 02110-1301, USA.
23 using System.Collections.Generic;
27 using System.Net.Cache;
28 using System.Net.Http;
29 using System.Runtime.Serialization;
31 using System.Threading.Tasks;
34 using OpenTween.Api.DataModel;
36 namespace OpenTween.Connection
38 public class TwitterApiConnection : IApiConnection, IDisposable
40 public static Uri RestApiBase { get; set; } = new Uri("https://api.twitter.com/1.1/");
42 public bool IsDisposed { get; private set; } = false;
44 public string AccessToken { get; }
45 public string AccessSecret { get; }
47 internal HttpClient http;
49 public TwitterApiConnection(string accessToken, string accessSecret)
51 this.AccessToken = accessToken;
52 this.AccessSecret = accessSecret;
54 this.http = InitializeHttpClient(accessToken, accessSecret);
55 Networking.WebProxyChanged += this.Networking_WebProxyChanged;
58 public Task<T> GetAsync<T>(Uri uri, IDictionary<string, string> param)
59 => this.GetAsync<T>(uri, param, null);
61 public async Task<T> GetAsync<T>(Uri uri, IDictionary<string, string> param, string endpointName)
63 var requestUri = new Uri(RestApiBase, uri);
66 requestUri = new Uri(requestUri, "?" + MyCommon.BuildQueryString(param));
68 var request = new HttpRequestMessage(HttpMethod.Get, requestUri);
72 using (var response = await this.http.SendAsync(request, HttpCompletionOption.ResponseHeadersRead)
73 .ConfigureAwait(false))
75 await this.CheckStatusCode(response)
76 .ConfigureAwait(false);
78 if (endpointName != null)
79 MyCommon.TwitterApiInfo.UpdateFromHeader(response.Headers, endpointName);
81 using (var content = response.Content)
83 var responseText = await content.ReadAsStringAsync()
84 .ConfigureAwait(false);
88 return MyCommon.CreateDataFromJson<T>(responseText);
90 catch (SerializationException ex)
92 throw TwitterApiException.CreateFromException(ex, responseText);
97 catch (HttpRequestException ex)
99 throw TwitterApiException.CreateFromException(ex);
101 catch (OperationCanceledException ex)
103 throw TwitterApiException.CreateFromException(ex);
107 public async Task<Stream> GetStreamAsync(Uri uri, IDictionary<string, string> param)
109 var requestUri = new Uri(RestApiBase, uri);
112 requestUri = new Uri(requestUri, "?" + MyCommon.BuildQueryString(param));
116 return await this.http.GetStreamAsync(requestUri)
117 .ConfigureAwait(false);
119 catch (HttpRequestException ex)
121 throw TwitterApiException.CreateFromException(ex);
123 catch (OperationCanceledException ex)
125 throw TwitterApiException.CreateFromException(ex);
129 public async Task<LazyJson<T>> PostLazyAsync<T>(Uri uri, IDictionary<string, string> param)
131 var requestUri = new Uri(RestApiBase, uri);
132 var request = new HttpRequestMessage(HttpMethod.Post, requestUri);
134 using (var postContent = new FormUrlEncodedContent(param))
136 request.Content = postContent;
138 HttpResponseMessage response = null;
141 response = await this.http.SendAsync(request, HttpCompletionOption.ResponseHeadersRead)
142 .ConfigureAwait(false);
144 await this.CheckStatusCode(response)
145 .ConfigureAwait(false);
147 var result = new LazyJson<T>(response);
152 catch (HttpRequestException ex)
154 throw TwitterApiException.CreateFromException(ex);
156 catch (OperationCanceledException ex)
158 throw TwitterApiException.CreateFromException(ex);
167 public async Task<LazyJson<T>> PostLazyAsync<T>(Uri uri, IDictionary<string, string> param, IDictionary<string, IMediaItem> media)
169 var requestUri = new Uri(RestApiBase, uri);
170 var request = new HttpRequestMessage(HttpMethod.Post, requestUri);
172 using (var postContent = new MultipartFormDataContent())
174 foreach (var kv in param)
175 postContent.Add(new StringContent(kv.Value), kv.Key);
177 foreach (var kv in media)
178 postContent.Add(new StreamContent(kv.Value.OpenRead()), kv.Key, kv.Value.Name);
180 request.Content = postContent;
182 HttpResponseMessage response = null;
185 response = await this.http.SendAsync(request, HttpCompletionOption.ResponseHeadersRead)
186 .ConfigureAwait(false);
188 await this.CheckStatusCode(response)
189 .ConfigureAwait(false);
191 var result = new LazyJson<T>(response);
196 catch (HttpRequestException ex)
198 throw TwitterApiException.CreateFromException(ex);
200 catch (OperationCanceledException ex)
202 throw TwitterApiException.CreateFromException(ex);
211 protected async Task CheckStatusCode(HttpResponseMessage response)
213 var statusCode = response.StatusCode;
214 if (statusCode == HttpStatusCode.OK)
216 Twitter.AccountState = MyCommon.ACCOUNT_STATE.Valid;
221 using (var content = response.Content)
223 responseText = await content.ReadAsStringAsync()
224 .ConfigureAwait(false);
227 if (string.IsNullOrWhiteSpace(responseText))
229 if (statusCode == HttpStatusCode.Unauthorized)
230 Twitter.AccountState = MyCommon.ACCOUNT_STATE.Invalid;
232 throw new TwitterApiException(statusCode, responseText);
237 var error = TwitterError.ParseJson(responseText);
239 if (error?.Errors == null || error.Errors.Length == 0)
240 throw new TwitterApiException(statusCode, responseText);
242 var errorCodes = error.Errors.Select(x => x.Code);
243 if (errorCodes.Any(x => x == TwitterErrorCode.InternalError || x == TwitterErrorCode.SuspendedAccount))
245 Twitter.AccountState = MyCommon.ACCOUNT_STATE.Invalid;
248 throw new TwitterApiException(error, responseText);
250 catch (SerializationException)
252 throw new TwitterApiException(statusCode, responseText);
256 public OAuthEchoHandler CreateOAuthEchoHandler(Uri authServiceProvider, Uri realm = null)
258 var uri = new Uri(RestApiBase, authServiceProvider);
260 return OAuthEchoHandler.CreateHandler(Networking.CreateHttpClientHandler(), uri,
261 ApplicationSettings.TwitterConsumerKey, ApplicationSettings.TwitterConsumerSecret,
262 this.AccessToken, this.AccessSecret, realm);
265 public void Dispose()
268 GC.SuppressFinalize(this);
271 protected virtual void Dispose(bool disposing)
276 this.IsDisposed = true;
280 Networking.WebProxyChanged -= this.Networking_WebProxyChanged;
285 ~TwitterApiConnection()
290 private void Networking_WebProxyChanged(object sender, EventArgs e)
292 this.http = InitializeHttpClient(this.AccessToken, this.AccessSecret);
295 public static async Task<Tuple<string, string>> GetRequestTokenAsync()
297 var param = new Dictionary<string, string>
299 ["oauth_callback"] = "oob",
301 var response = await GetOAuthTokenAsync(new Uri("https://api.twitter.com/oauth/request_token"), param, oauthToken: null)
302 .ConfigureAwait(false);
304 return Tuple.Create(response["oauth_token"], response["oauth_token_secret"]);
307 public static Uri GetAuthorizeUri(Tuple<string, string> requestToken, string screenName = null)
309 var param = new Dictionary<string, string>
311 ["oauth_token"] = requestToken.Item1,
314 if (screenName != null)
315 param["screen_name"] = screenName;
317 return new Uri("https://api.twitter.com/oauth/authorize?" + MyCommon.BuildQueryString(param));
320 public static async Task<IDictionary<string, string>> GetAccessTokenAsync(Tuple<string, string> requestToken, string verifier)
322 var param = new Dictionary<string, string>
324 ["oauth_verifier"] = verifier,
326 var response = await GetOAuthTokenAsync(new Uri("https://api.twitter.com/oauth/access_token"), param, requestToken)
327 .ConfigureAwait(false);
332 private static async Task<IDictionary<string, string>> GetOAuthTokenAsync(Uri uri, IDictionary<string, string> param,
333 Tuple<string, string> oauthToken)
335 HttpClient authorizeClient;
336 if (oauthToken != null)
337 authorizeClient = InitializeHttpClient(oauthToken.Item1, oauthToken.Item2);
339 authorizeClient = InitializeHttpClient("", "");
341 var requestUri = new Uri(uri, "?" + MyCommon.BuildQueryString(param));
345 using (var request = new HttpRequestMessage(HttpMethod.Post, requestUri))
346 using (var response = await authorizeClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead)
347 .ConfigureAwait(false))
348 using (var content = response.Content)
350 var responseText = await content.ReadAsStringAsync()
351 .ConfigureAwait(false);
353 if (!response.IsSuccessStatusCode)
354 throw new TwitterApiException(response.StatusCode, responseText);
356 var responseParams = HttpUtility.ParseQueryString(responseText);
358 return responseParams.Cast<string>()
359 .ToDictionary(x => x, x => responseParams[x]);
362 catch (HttpRequestException ex)
364 throw TwitterApiException.CreateFromException(ex);
366 catch (OperationCanceledException ex)
368 throw TwitterApiException.CreateFromException(ex);
372 private static HttpClient InitializeHttpClient(string accessToken, string accessSecret)
374 var innerHandler = Networking.CreateHttpClientHandler();
375 innerHandler.CachePolicy = new RequestCachePolicy(RequestCacheLevel.BypassCache);
377 var handler = new OAuthHandler(innerHandler,
378 ApplicationSettings.TwitterConsumerKey, ApplicationSettings.TwitterConsumerSecret,
379 accessToken, accessSecret);
381 return Networking.CreateHttpClient(handler);