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 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(this.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 (OperationCanceledException ex)
99 throw TwitterApiException.CreateFromException(ex);
103 public async Task<Stream> GetStreamAsync(Uri uri, IDictionary<string, string> param)
105 var requestUri = new Uri(this.RestApiBase, uri);
108 requestUri = new Uri(requestUri, "?" + MyCommon.BuildQueryString(param));
112 return await this.http.GetStreamAsync(requestUri)
113 .ConfigureAwait(false);
115 catch (OperationCanceledException ex)
117 throw TwitterApiException.CreateFromException(ex);
121 public async Task<LazyJson<T>> PostLazyAsync<T>(Uri uri, IDictionary<string, string> param)
123 var requestUri = new Uri(this.RestApiBase, uri);
124 var request = new HttpRequestMessage(HttpMethod.Post, requestUri);
126 using (var postContent = new FormUrlEncodedContent(param))
128 request.Content = postContent;
130 HttpResponseMessage response = null;
133 response = await this.http.SendAsync(request, HttpCompletionOption.ResponseHeadersRead)
134 .ConfigureAwait(false);
136 await this.CheckStatusCode(response)
137 .ConfigureAwait(false);
139 var result = new LazyJson<T>(response);
144 catch (OperationCanceledException ex)
146 throw TwitterApiException.CreateFromException(ex);
155 public async Task<LazyJson<T>> PostLazyAsync<T>(Uri uri, IDictionary<string, string> param, IDictionary<string, IMediaItem> media)
157 var requestUri = new Uri(this.RestApiBase, uri);
158 var request = new HttpRequestMessage(HttpMethod.Post, requestUri);
160 using (var postContent = new MultipartFormDataContent())
162 foreach (var kv in param)
163 postContent.Add(new StringContent(kv.Value), kv.Key);
165 foreach (var kv in media)
166 postContent.Add(new StreamContent(kv.Value.OpenRead()), kv.Key, kv.Value.Name);
168 request.Content = postContent;
170 HttpResponseMessage response = null;
173 response = await this.http.SendAsync(request, HttpCompletionOption.ResponseHeadersRead)
174 .ConfigureAwait(false);
176 await this.CheckStatusCode(response)
177 .ConfigureAwait(false);
179 var result = new LazyJson<T>(response);
184 catch (OperationCanceledException ex)
186 throw TwitterApiException.CreateFromException(ex);
195 protected async Task CheckStatusCode(HttpResponseMessage response)
197 var statusCode = response.StatusCode;
198 if (statusCode == HttpStatusCode.OK)
200 Twitter.AccountState = MyCommon.ACCOUNT_STATE.Valid;
205 using (var content = response.Content)
207 responseText = await content.ReadAsStringAsync()
208 .ConfigureAwait(false);
211 if (string.IsNullOrWhiteSpace(responseText))
213 if (statusCode == HttpStatusCode.Unauthorized)
214 Twitter.AccountState = MyCommon.ACCOUNT_STATE.Invalid;
216 throw new TwitterApiException(statusCode, responseText);
221 var error = TwitterError.ParseJson(responseText);
223 if (error?.Errors == null || error.Errors.Length == 0)
224 throw new TwitterApiException(statusCode, responseText);
226 var errorCodes = error.Errors.Select(x => x.Code);
227 if (errorCodes.Any(x => x == TwitterErrorCode.InternalError || x == TwitterErrorCode.SuspendedAccount))
229 Twitter.AccountState = MyCommon.ACCOUNT_STATE.Invalid;
232 throw new TwitterApiException(error, responseText);
234 catch (SerializationException)
236 throw new TwitterApiException(statusCode, responseText);
240 public OAuthEchoHandler CreateOAuthEchoHandler(Uri authServiceProvider, Uri realm = null)
242 var uri = new Uri(this.RestApiBase, authServiceProvider);
244 return OAuthEchoHandler.CreateHandler(Networking.CreateHttpClientHandler(), uri,
245 ApplicationSettings.TwitterConsumerKey, ApplicationSettings.TwitterConsumerSecret,
246 this.AccessToken, this.AccessSecret, realm);
249 public void Dispose()
252 GC.SuppressFinalize(this);
255 protected virtual void Dispose(bool disposing)
260 this.IsDisposed = true;
264 Networking.WebProxyChanged -= this.Networking_WebProxyChanged;
269 ~TwitterApiConnection()
274 private void Networking_WebProxyChanged(object sender, EventArgs e)
276 this.http = InitializeHttpClient(this.AccessToken, this.AccessSecret);
279 public static async Task<Tuple<string, string>> GetRequestTokenAsync()
281 var param = new Dictionary<string, string>
283 ["oauth_callback"] = "oob",
285 var response = await GetOAuthTokenAsync(new Uri("https://api.twitter.com/oauth/request_token"), param, oauthToken: null)
286 .ConfigureAwait(false);
288 return Tuple.Create(response["oauth_token"], response["oauth_token_secret"]);
291 public static Uri GetAuthorizeUri(Tuple<string, string> requestToken, string screenName = null)
293 var param = new Dictionary<string, string>
295 ["oauth_token"] = requestToken.Item1,
298 if (screenName != null)
299 param["screen_name"] = screenName;
301 return new Uri("https://api.twitter.com/oauth/authorize?" + MyCommon.BuildQueryString(param));
304 public static async Task<IDictionary<string, string>> GetAccessTokenAsync(Tuple<string, string> requestToken, string verifier)
306 var param = new Dictionary<string, string>
308 ["oauth_verifier"] = verifier,
310 var response = await GetOAuthTokenAsync(new Uri("https://api.twitter.com/oauth/access_token"), param, requestToken)
311 .ConfigureAwait(false);
316 private static async Task<IDictionary<string, string>> GetOAuthTokenAsync(Uri uri, IDictionary<string, string> param,
317 Tuple<string, string> oauthToken)
319 HttpClient authorizeClient;
320 if (oauthToken != null)
321 authorizeClient = InitializeHttpClient(oauthToken.Item1, oauthToken.Item2);
323 authorizeClient = InitializeHttpClient("", "");
325 var requestUri = new Uri(uri, "?" + MyCommon.BuildQueryString(param));
329 using (var request = new HttpRequestMessage(HttpMethod.Post, requestUri))
330 using (var response = await authorizeClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead)
331 .ConfigureAwait(false))
332 using (var content = response.Content)
334 var responseText = await content.ReadAsStringAsync()
335 .ConfigureAwait(false);
337 if (!response.IsSuccessStatusCode)
338 throw new TwitterApiException(response.StatusCode, responseText);
340 var responseParams = HttpUtility.ParseQueryString(responseText);
342 return responseParams.Cast<string>()
343 .ToDictionary(x => x, x => responseParams[x]);
346 catch (OperationCanceledException ex)
348 throw TwitterApiException.CreateFromException(ex);
352 private static HttpClient InitializeHttpClient(string accessToken, string accessSecret)
354 var innerHandler = Networking.CreateHttpClientHandler();
355 innerHandler.CachePolicy = new RequestCachePolicy(RequestCacheLevel.BypassCache);
357 var handler = new OAuthHandler(innerHandler,
358 ApplicationSettings.TwitterConsumerKey, ApplicationSettings.TwitterConsumerSecret,
359 accessToken, accessSecret);
361 return Networking.CreateHttpClient(handler);