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 httpStreaming;
58 public TwitterApiConnection(string accessToken, string accessSecret)
60 this.AccessToken = accessToken;
61 this.AccessSecret = accessSecret;
63 this.InitializeHttpClients();
64 Networking.WebProxyChanged += this.Networking_WebProxyChanged;
67 private void InitializeHttpClients()
69 this.http = InitializeHttpClient(this.AccessToken, this.AccessSecret);
71 this.httpStreaming = InitializeHttpClient(this.AccessToken, this.AccessSecret, disableGzip: true);
72 this.httpStreaming.Timeout = Timeout.InfiniteTimeSpan;
75 public async Task<T> GetAsync<T>(Uri uri, IDictionary<string, string> param, string endpointName)
77 var requestUri = new Uri(RestApiBase, uri);
80 requestUri = new Uri(requestUri, "?" + MyCommon.BuildQueryString(param));
82 var request = new HttpRequestMessage(HttpMethod.Get, requestUri);
86 using (var response = await this.http.SendAsync(request, HttpCompletionOption.ResponseHeadersRead)
87 .ConfigureAwait(false))
89 await this.CheckStatusCode(response)
90 .ConfigureAwait(false);
92 if (endpointName != null)
93 MyCommon.TwitterApiInfo.UpdateFromHeader(response.Headers, endpointName);
95 using (var content = response.Content)
97 var responseText = await content.ReadAsStringAsync()
98 .ConfigureAwait(false);
102 return MyCommon.CreateDataFromJson<T>(responseText);
104 catch (SerializationException ex)
106 throw TwitterApiException.CreateFromException(ex, responseText);
111 catch (HttpRequestException ex)
113 throw TwitterApiException.CreateFromException(ex);
115 catch (OperationCanceledException ex)
117 throw TwitterApiException.CreateFromException(ex);
121 public async Task<Stream> GetStreamAsync(Uri uri, IDictionary<string, string> param)
123 var requestUri = new Uri(RestApiBase, uri);
126 requestUri = new Uri(requestUri, "?" + MyCommon.BuildQueryString(param));
130 return await this.http.GetStreamAsync(requestUri)
131 .ConfigureAwait(false);
133 catch (HttpRequestException ex)
135 throw TwitterApiException.CreateFromException(ex);
137 catch (OperationCanceledException ex)
139 throw TwitterApiException.CreateFromException(ex);
143 public async Task<Stream> GetStreamingStreamAsync(Uri uri, IDictionary<string, string> param)
145 var requestUri = new Uri(RestApiBase, uri);
148 requestUri = new Uri(requestUri, "?" + MyCommon.BuildQueryString(param));
152 return await this.httpStreaming.GetStreamAsync(requestUri)
153 .ConfigureAwait(false);
155 catch (HttpRequestException ex)
157 throw TwitterApiException.CreateFromException(ex);
159 catch (OperationCanceledException ex)
161 throw TwitterApiException.CreateFromException(ex);
165 public async Task<LazyJson<T>> PostLazyAsync<T>(Uri uri, IDictionary<string, string> param)
167 var requestUri = new Uri(RestApiBase, uri);
168 var request = new HttpRequestMessage(HttpMethod.Post, requestUri);
170 using (var postContent = new FormUrlEncodedContent(param))
172 request.Content = postContent;
174 HttpResponseMessage response = null;
177 response = await this.http.SendAsync(request, HttpCompletionOption.ResponseHeadersRead)
178 .ConfigureAwait(false);
180 await this.CheckStatusCode(response)
181 .ConfigureAwait(false);
183 var result = new LazyJson<T>(response);
188 catch (HttpRequestException ex)
190 throw TwitterApiException.CreateFromException(ex);
192 catch (OperationCanceledException ex)
194 throw TwitterApiException.CreateFromException(ex);
203 public async Task<LazyJson<T>> PostLazyAsync<T>(Uri uri, IDictionary<string, string> param, IDictionary<string, IMediaItem> media)
205 var requestUri = new Uri(RestApiBase, uri);
206 var request = new HttpRequestMessage(HttpMethod.Post, requestUri);
208 using (var postContent = new MultipartFormDataContent())
212 foreach (var kv in param)
213 postContent.Add(new StringContent(kv.Value), kv.Key);
217 foreach (var kv in media)
218 postContent.Add(new StreamContent(kv.Value.OpenRead()), kv.Key, kv.Value.Name);
221 request.Content = postContent;
223 HttpResponseMessage response = null;
226 response = await this.http.SendAsync(request, HttpCompletionOption.ResponseHeadersRead)
227 .ConfigureAwait(false);
229 await this.CheckStatusCode(response)
230 .ConfigureAwait(false);
232 var result = new LazyJson<T>(response);
237 catch (HttpRequestException ex)
239 throw TwitterApiException.CreateFromException(ex);
241 catch (OperationCanceledException ex)
243 throw TwitterApiException.CreateFromException(ex);
252 public async Task PostJsonAsync(Uri uri, string json)
254 var requestUri = new Uri(RestApiBase, uri);
255 var request = new HttpRequestMessage(HttpMethod.Post, requestUri);
257 using (var postContent = new StringContent(json, Encoding.UTF8, "application/json"))
259 request.Content = postContent;
263 using (var response = await this.http.SendAsync(request, HttpCompletionOption.ResponseHeadersRead)
264 .ConfigureAwait(false))
266 await this.CheckStatusCode(response)
267 .ConfigureAwait(false);
270 catch (HttpRequestException ex)
272 throw TwitterApiException.CreateFromException(ex);
274 catch (OperationCanceledException ex)
276 throw TwitterApiException.CreateFromException(ex);
281 protected async Task CheckStatusCode(HttpResponseMessage response)
283 var statusCode = response.StatusCode;
284 if (statusCode == HttpStatusCode.OK)
286 Twitter.AccountState = MyCommon.ACCOUNT_STATE.Valid;
291 using (var content = response.Content)
293 responseText = await content.ReadAsStringAsync()
294 .ConfigureAwait(false);
297 if (string.IsNullOrWhiteSpace(responseText))
299 if (statusCode == HttpStatusCode.Unauthorized)
300 Twitter.AccountState = MyCommon.ACCOUNT_STATE.Invalid;
302 throw new TwitterApiException(statusCode, responseText);
307 var error = TwitterError.ParseJson(responseText);
309 if (error?.Errors == null || error.Errors.Length == 0)
310 throw new TwitterApiException(statusCode, responseText);
312 var errorCodes = error.Errors.Select(x => x.Code);
313 if (errorCodes.Any(x => x == TwitterErrorCode.InternalError || x == TwitterErrorCode.SuspendedAccount))
315 Twitter.AccountState = MyCommon.ACCOUNT_STATE.Invalid;
318 throw new TwitterApiException(error, responseText);
320 catch (SerializationException)
322 throw new TwitterApiException(statusCode, responseText);
326 public OAuthEchoHandler CreateOAuthEchoHandler(Uri authServiceProvider, Uri realm = null)
328 var uri = new Uri(RestApiBase, authServiceProvider);
330 return OAuthEchoHandler.CreateHandler(Networking.CreateHttpClientHandler(), uri,
331 ApplicationSettings.TwitterConsumerKey, ApplicationSettings.TwitterConsumerSecret,
332 this.AccessToken, this.AccessSecret, realm);
335 public void Dispose()
338 GC.SuppressFinalize(this);
341 protected virtual void Dispose(bool disposing)
346 this.IsDisposed = true;
350 Networking.WebProxyChanged -= this.Networking_WebProxyChanged;
352 this.httpStreaming.Dispose();
356 ~TwitterApiConnection()
361 private void Networking_WebProxyChanged(object sender, EventArgs e)
363 this.InitializeHttpClients();
366 public static async Task<Tuple<string, string>> GetRequestTokenAsync()
368 var param = new Dictionary<string, string>
370 ["oauth_callback"] = "oob",
372 var response = await GetOAuthTokenAsync(new Uri("https://api.twitter.com/oauth/request_token"), param, oauthToken: null)
373 .ConfigureAwait(false);
375 return Tuple.Create(response["oauth_token"], response["oauth_token_secret"]);
378 public static Uri GetAuthorizeUri(Tuple<string, string> requestToken, string screenName = null)
380 var param = new Dictionary<string, string>
382 ["oauth_token"] = requestToken.Item1,
385 if (screenName != null)
386 param["screen_name"] = screenName;
388 return new Uri("https://api.twitter.com/oauth/authorize?" + MyCommon.BuildQueryString(param));
391 public static async Task<IDictionary<string, string>> GetAccessTokenAsync(Tuple<string, string> requestToken, string verifier)
393 var param = new Dictionary<string, string>
395 ["oauth_verifier"] = verifier,
397 var response = await GetOAuthTokenAsync(new Uri("https://api.twitter.com/oauth/access_token"), param, requestToken)
398 .ConfigureAwait(false);
403 private static async Task<IDictionary<string, string>> GetOAuthTokenAsync(Uri uri, IDictionary<string, string> param,
404 Tuple<string, string> oauthToken)
406 HttpClient authorizeClient;
407 if (oauthToken != null)
408 authorizeClient = InitializeHttpClient(oauthToken.Item1, oauthToken.Item2);
410 authorizeClient = InitializeHttpClient("", "");
412 var requestUri = new Uri(uri, "?" + MyCommon.BuildQueryString(param));
416 using (var request = new HttpRequestMessage(HttpMethod.Post, requestUri))
417 using (var response = await authorizeClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead)
418 .ConfigureAwait(false))
419 using (var content = response.Content)
421 var responseText = await content.ReadAsStringAsync()
422 .ConfigureAwait(false);
424 if (!response.IsSuccessStatusCode)
425 throw new TwitterApiException(response.StatusCode, responseText);
427 var responseParams = HttpUtility.ParseQueryString(responseText);
429 return responseParams.Cast<string>()
430 .ToDictionary(x => x, x => responseParams[x]);
433 catch (HttpRequestException ex)
435 throw TwitterApiException.CreateFromException(ex);
437 catch (OperationCanceledException ex)
439 throw TwitterApiException.CreateFromException(ex);
443 private static HttpClient InitializeHttpClient(string accessToken, string accessSecret, bool disableGzip = false)
445 var innerHandler = Networking.CreateHttpClientHandler();
446 innerHandler.CachePolicy = new RequestCachePolicy(RequestCacheLevel.BypassCache);
449 innerHandler.AutomaticDecompression = DecompressionMethods.None;
451 var handler = new OAuthHandler(innerHandler,
452 ApplicationSettings.TwitterConsumerKey, ApplicationSettings.TwitterConsumerSecret,
453 accessToken, accessSecret);
455 return Networking.CreateHttpClient(handler);