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;
32 using System.Threading.Tasks;
35 using OpenTween.Api.DataModel;
37 namespace OpenTween.Connection
39 public class TwitterApiConnection : IApiConnection, IDisposable
41 public static Uri RestApiBase { get; set; } = new Uri("https://api.twitter.com/1.1/");
43 // SettingCommon.xml の TwitterUrl との互換性のために用意
44 public static string RestApiHost
46 get { return RestApiBase.Host; }
47 set { RestApiBase = new Uri($"https://{value}/1.1/"); }
50 public bool IsDisposed { get; private set; } = false;
52 public string AccessToken { get; }
53 public string AccessSecret { get; }
55 internal HttpClient http;
56 internal HttpClient httpUpload;
57 internal HttpClient httpStreaming;
59 public TwitterApiConnection(string accessToken, string accessSecret)
61 this.AccessToken = accessToken;
62 this.AccessSecret = accessSecret;
64 this.InitializeHttpClients();
65 Networking.WebProxyChanged += this.Networking_WebProxyChanged;
68 private void InitializeHttpClients()
70 this.http = InitializeHttpClient(this.AccessToken, this.AccessSecret);
72 this.httpUpload = InitializeHttpClient(this.AccessToken, this.AccessSecret);
73 this.httpUpload.Timeout = Networking.UploadImageTimeout;
75 this.httpStreaming = InitializeHttpClient(this.AccessToken, this.AccessSecret, disableGzip: true);
76 this.httpStreaming.Timeout = Timeout.InfiniteTimeSpan;
79 public async Task<T> GetAsync<T>(Uri uri, IDictionary<string, string> param, string endpointName)
81 var requestUri = new Uri(RestApiBase, uri);
84 requestUri = new Uri(requestUri, "?" + MyCommon.BuildQueryString(param));
86 var request = new HttpRequestMessage(HttpMethod.Get, requestUri);
90 using (var response = await this.http.SendAsync(request, HttpCompletionOption.ResponseHeadersRead)
91 .ConfigureAwait(false))
93 await this.CheckStatusCode(response)
94 .ConfigureAwait(false);
96 if (endpointName != null)
97 MyCommon.TwitterApiInfo.UpdateFromHeader(response.Headers, endpointName);
99 using (var content = response.Content)
101 var responseText = await content.ReadAsStringAsync()
102 .ConfigureAwait(false);
106 return MyCommon.CreateDataFromJson<T>(responseText);
108 catch (SerializationException ex)
110 throw TwitterApiException.CreateFromException(ex, responseText);
115 catch (HttpRequestException ex)
117 throw TwitterApiException.CreateFromException(ex);
119 catch (OperationCanceledException ex)
121 throw TwitterApiException.CreateFromException(ex);
125 public async Task<Stream> GetStreamAsync(Uri uri, IDictionary<string, string> param)
127 var requestUri = new Uri(RestApiBase, uri);
130 requestUri = new Uri(requestUri, "?" + MyCommon.BuildQueryString(param));
134 return await this.http.GetStreamAsync(requestUri)
135 .ConfigureAwait(false);
137 catch (HttpRequestException ex)
139 throw TwitterApiException.CreateFromException(ex);
141 catch (OperationCanceledException ex)
143 throw TwitterApiException.CreateFromException(ex);
147 public async Task<Stream> GetStreamingStreamAsync(Uri uri, IDictionary<string, string> param)
149 var requestUri = new Uri(RestApiBase, uri);
152 requestUri = new Uri(requestUri, "?" + MyCommon.BuildQueryString(param));
156 return await this.httpStreaming.GetStreamAsync(requestUri)
157 .ConfigureAwait(false);
159 catch (HttpRequestException ex)
161 throw TwitterApiException.CreateFromException(ex);
163 catch (OperationCanceledException ex)
165 throw TwitterApiException.CreateFromException(ex);
169 public async Task<LazyJson<T>> PostLazyAsync<T>(Uri uri, IDictionary<string, string> param)
171 var requestUri = new Uri(RestApiBase, uri);
172 var request = new HttpRequestMessage(HttpMethod.Post, requestUri);
174 using (var postContent = new FormUrlEncodedContent(param))
176 request.Content = postContent;
178 HttpResponseMessage response = null;
181 response = await this.http.SendAsync(request, HttpCompletionOption.ResponseHeadersRead)
182 .ConfigureAwait(false);
184 await this.CheckStatusCode(response)
185 .ConfigureAwait(false);
187 var result = new LazyJson<T>(response);
192 catch (HttpRequestException ex)
194 throw TwitterApiException.CreateFromException(ex);
196 catch (OperationCanceledException ex)
198 throw TwitterApiException.CreateFromException(ex);
207 public async Task<LazyJson<T>> PostLazyAsync<T>(Uri uri, IDictionary<string, string> param, IDictionary<string, IMediaItem> media)
209 var requestUri = new Uri(RestApiBase, uri);
210 var request = new HttpRequestMessage(HttpMethod.Post, requestUri);
212 using (var postContent = new MultipartFormDataContent())
216 foreach (var kv in param)
217 postContent.Add(new StringContent(kv.Value), kv.Key);
221 foreach (var kv in media)
222 postContent.Add(new StreamContent(kv.Value.OpenRead()), kv.Key, kv.Value.Name);
225 request.Content = postContent;
227 HttpResponseMessage response = null;
230 response = await this.httpUpload.SendAsync(request, HttpCompletionOption.ResponseHeadersRead)
231 .ConfigureAwait(false);
233 await this.CheckStatusCode(response)
234 .ConfigureAwait(false);
236 var result = new LazyJson<T>(response);
241 catch (HttpRequestException ex)
243 throw TwitterApiException.CreateFromException(ex);
245 catch (OperationCanceledException ex)
247 throw TwitterApiException.CreateFromException(ex);
256 public async Task PostJsonAsync(Uri uri, string json)
258 var requestUri = new Uri(RestApiBase, uri);
259 var request = new HttpRequestMessage(HttpMethod.Post, requestUri);
261 using (var postContent = new StringContent(json, Encoding.UTF8, "application/json"))
263 request.Content = postContent;
267 using (var response = await this.http.SendAsync(request, HttpCompletionOption.ResponseHeadersRead)
268 .ConfigureAwait(false))
270 await this.CheckStatusCode(response)
271 .ConfigureAwait(false);
274 catch (HttpRequestException ex)
276 throw TwitterApiException.CreateFromException(ex);
278 catch (OperationCanceledException ex)
280 throw TwitterApiException.CreateFromException(ex);
285 protected async Task CheckStatusCode(HttpResponseMessage response)
287 var statusCode = response.StatusCode;
288 if (statusCode == HttpStatusCode.OK)
290 Twitter.AccountState = MyCommon.ACCOUNT_STATE.Valid;
295 using (var content = response.Content)
297 responseText = await content.ReadAsStringAsync()
298 .ConfigureAwait(false);
301 if (string.IsNullOrWhiteSpace(responseText))
303 if (statusCode == HttpStatusCode.Unauthorized)
304 Twitter.AccountState = MyCommon.ACCOUNT_STATE.Invalid;
306 throw new TwitterApiException(statusCode, responseText);
311 var error = TwitterError.ParseJson(responseText);
313 if (error?.Errors == null || error.Errors.Length == 0)
314 throw new TwitterApiException(statusCode, responseText);
316 var errorCodes = error.Errors.Select(x => x.Code);
317 if (errorCodes.Any(x => x == TwitterErrorCode.InternalError || x == TwitterErrorCode.SuspendedAccount))
319 Twitter.AccountState = MyCommon.ACCOUNT_STATE.Invalid;
322 throw new TwitterApiException(error, responseText);
324 catch (SerializationException)
326 throw new TwitterApiException(statusCode, responseText);
330 public OAuthEchoHandler CreateOAuthEchoHandler(Uri authServiceProvider, Uri realm = null)
332 var uri = new Uri(RestApiBase, authServiceProvider);
334 return OAuthEchoHandler.CreateHandler(Networking.CreateHttpClientHandler(), uri,
335 ApplicationSettings.TwitterConsumerKey, ApplicationSettings.TwitterConsumerSecret,
336 this.AccessToken, this.AccessSecret, realm);
339 public void Dispose()
342 GC.SuppressFinalize(this);
345 protected virtual void Dispose(bool disposing)
350 this.IsDisposed = true;
354 Networking.WebProxyChanged -= this.Networking_WebProxyChanged;
356 this.httpStreaming.Dispose();
360 ~TwitterApiConnection()
365 private void Networking_WebProxyChanged(object sender, EventArgs e)
367 this.InitializeHttpClients();
370 public static async Task<Tuple<string, string>> GetRequestTokenAsync()
372 var param = new Dictionary<string, string>
374 ["oauth_callback"] = "oob",
376 var response = await GetOAuthTokenAsync(new Uri("https://api.twitter.com/oauth/request_token"), param, oauthToken: null)
377 .ConfigureAwait(false);
379 return Tuple.Create(response["oauth_token"], response["oauth_token_secret"]);
382 public static Uri GetAuthorizeUri(Tuple<string, string> requestToken, string screenName = null)
384 var param = new Dictionary<string, string>
386 ["oauth_token"] = requestToken.Item1,
389 if (screenName != null)
390 param["screen_name"] = screenName;
392 return new Uri("https://api.twitter.com/oauth/authorize?" + MyCommon.BuildQueryString(param));
395 public static async Task<IDictionary<string, string>> GetAccessTokenAsync(Tuple<string, string> requestToken, string verifier)
397 var param = new Dictionary<string, string>
399 ["oauth_verifier"] = verifier,
401 var response = await GetOAuthTokenAsync(new Uri("https://api.twitter.com/oauth/access_token"), param, requestToken)
402 .ConfigureAwait(false);
407 private static async Task<IDictionary<string, string>> GetOAuthTokenAsync(Uri uri, IDictionary<string, string> param,
408 Tuple<string, string> oauthToken)
410 HttpClient authorizeClient;
411 if (oauthToken != null)
412 authorizeClient = InitializeHttpClient(oauthToken.Item1, oauthToken.Item2);
414 authorizeClient = InitializeHttpClient("", "");
416 var requestUri = new Uri(uri, "?" + MyCommon.BuildQueryString(param));
420 using (var request = new HttpRequestMessage(HttpMethod.Post, requestUri))
421 using (var response = await authorizeClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead)
422 .ConfigureAwait(false))
423 using (var content = response.Content)
425 var responseText = await content.ReadAsStringAsync()
426 .ConfigureAwait(false);
428 if (!response.IsSuccessStatusCode)
429 throw new TwitterApiException(response.StatusCode, responseText);
431 var responseParams = HttpUtility.ParseQueryString(responseText);
433 return responseParams.Cast<string>()
434 .ToDictionary(x => x, x => responseParams[x]);
437 catch (HttpRequestException ex)
439 throw TwitterApiException.CreateFromException(ex);
441 catch (OperationCanceledException ex)
443 throw TwitterApiException.CreateFromException(ex);
447 private static HttpClient InitializeHttpClient(string accessToken, string accessSecret, bool disableGzip = false)
449 var innerHandler = Networking.CreateHttpClientHandler();
450 innerHandler.CachePolicy = new RequestCachePolicy(RequestCacheLevel.BypassCache);
453 innerHandler.AutomaticDecompression = DecompressionMethods.None;
455 var handler = new OAuthHandler(innerHandler,
456 ApplicationSettings.TwitterConsumerKey, ApplicationSettings.TwitterConsumerSecret,
457 accessToken, accessSecret);
459 return Networking.CreateHttpClient(handler);