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 // SettingCommon.xml の TwitterUrl との互換性のために用意
43 public static string RestApiHost
45 get { return RestApiBase.Host; }
46 set { RestApiBase = new Uri($"https://{value}/1.1/"); }
49 public bool IsDisposed { get; private set; } = false;
51 public string AccessToken { get; }
52 public string AccessSecret { get; }
54 internal HttpClient http;
55 internal HttpClient httpStreaming;
57 public TwitterApiConnection(string accessToken, string accessSecret)
59 this.AccessToken = accessToken;
60 this.AccessSecret = accessSecret;
62 this.InitializeHttpClients();
63 Networking.WebProxyChanged += this.Networking_WebProxyChanged;
66 private void InitializeHttpClients()
68 this.http = InitializeHttpClient(this.AccessToken, this.AccessSecret, streaming: false);
69 this.httpStreaming = InitializeHttpClient(this.AccessToken, this.AccessSecret, streaming: true);
72 public async Task<T> GetAsync<T>(Uri uri, IDictionary<string, string> param, string endpointName)
74 var requestUri = new Uri(RestApiBase, uri);
77 requestUri = new Uri(requestUri, "?" + MyCommon.BuildQueryString(param));
79 var request = new HttpRequestMessage(HttpMethod.Get, requestUri);
83 using (var response = await this.http.SendAsync(request, HttpCompletionOption.ResponseHeadersRead)
84 .ConfigureAwait(false))
86 await this.CheckStatusCode(response)
87 .ConfigureAwait(false);
89 if (endpointName != null)
90 MyCommon.TwitterApiInfo.UpdateFromHeader(response.Headers, endpointName);
92 using (var content = response.Content)
94 var responseText = await content.ReadAsStringAsync()
95 .ConfigureAwait(false);
99 return MyCommon.CreateDataFromJson<T>(responseText);
101 catch (SerializationException ex)
103 throw TwitterApiException.CreateFromException(ex, responseText);
108 catch (HttpRequestException ex)
110 throw TwitterApiException.CreateFromException(ex);
112 catch (OperationCanceledException ex)
114 throw TwitterApiException.CreateFromException(ex);
118 public async Task<Stream> GetStreamAsync(Uri uri, IDictionary<string, string> param)
120 var requestUri = new Uri(RestApiBase, uri);
123 requestUri = new Uri(requestUri, "?" + MyCommon.BuildQueryString(param));
127 return await this.http.GetStreamAsync(requestUri)
128 .ConfigureAwait(false);
130 catch (HttpRequestException ex)
132 throw TwitterApiException.CreateFromException(ex);
134 catch (OperationCanceledException ex)
136 throw TwitterApiException.CreateFromException(ex);
140 public async Task<LazyJson<T>> PostLazyAsync<T>(Uri uri, IDictionary<string, string> param)
142 var requestUri = new Uri(RestApiBase, uri);
143 var request = new HttpRequestMessage(HttpMethod.Post, requestUri);
145 using (var postContent = new FormUrlEncodedContent(param))
147 request.Content = postContent;
149 HttpResponseMessage response = null;
152 response = await this.http.SendAsync(request, HttpCompletionOption.ResponseHeadersRead)
153 .ConfigureAwait(false);
155 await this.CheckStatusCode(response)
156 .ConfigureAwait(false);
158 var result = new LazyJson<T>(response);
163 catch (HttpRequestException ex)
165 throw TwitterApiException.CreateFromException(ex);
167 catch (OperationCanceledException ex)
169 throw TwitterApiException.CreateFromException(ex);
178 public async Task<LazyJson<T>> PostLazyAsync<T>(Uri uri, IDictionary<string, string> param, IDictionary<string, IMediaItem> media)
180 var requestUri = new Uri(RestApiBase, uri);
181 var request = new HttpRequestMessage(HttpMethod.Post, requestUri);
183 using (var postContent = new MultipartFormDataContent())
187 foreach (var kv in param)
188 postContent.Add(new StringContent(kv.Value), kv.Key);
192 foreach (var kv in media)
193 postContent.Add(new StreamContent(kv.Value.OpenRead()), kv.Key, kv.Value.Name);
196 request.Content = postContent;
198 HttpResponseMessage response = null;
201 response = await this.http.SendAsync(request, HttpCompletionOption.ResponseHeadersRead)
202 .ConfigureAwait(false);
204 await this.CheckStatusCode(response)
205 .ConfigureAwait(false);
207 var result = new LazyJson<T>(response);
212 catch (HttpRequestException ex)
214 throw TwitterApiException.CreateFromException(ex);
216 catch (OperationCanceledException ex)
218 throw TwitterApiException.CreateFromException(ex);
227 public async Task PostJsonAsync(Uri uri, string json)
229 var requestUri = new Uri(RestApiBase, uri);
230 var request = new HttpRequestMessage(HttpMethod.Post, requestUri);
232 using (var postContent = new StringContent(json, Encoding.UTF8, "application/json"))
234 request.Content = postContent;
238 using (var response = await this.http.SendAsync(request, HttpCompletionOption.ResponseHeadersRead)
239 .ConfigureAwait(false))
241 await this.CheckStatusCode(response)
242 .ConfigureAwait(false);
245 catch (HttpRequestException ex)
247 throw TwitterApiException.CreateFromException(ex);
249 catch (OperationCanceledException ex)
251 throw TwitterApiException.CreateFromException(ex);
256 protected async Task CheckStatusCode(HttpResponseMessage response)
258 var statusCode = response.StatusCode;
259 if (statusCode == HttpStatusCode.OK)
261 Twitter.AccountState = MyCommon.ACCOUNT_STATE.Valid;
266 using (var content = response.Content)
268 responseText = await content.ReadAsStringAsync()
269 .ConfigureAwait(false);
272 if (string.IsNullOrWhiteSpace(responseText))
274 if (statusCode == HttpStatusCode.Unauthorized)
275 Twitter.AccountState = MyCommon.ACCOUNT_STATE.Invalid;
277 throw new TwitterApiException(statusCode, responseText);
282 var error = TwitterError.ParseJson(responseText);
284 if (error?.Errors == null || error.Errors.Length == 0)
285 throw new TwitterApiException(statusCode, responseText);
287 var errorCodes = error.Errors.Select(x => x.Code);
288 if (errorCodes.Any(x => x == TwitterErrorCode.InternalError || x == TwitterErrorCode.SuspendedAccount))
290 Twitter.AccountState = MyCommon.ACCOUNT_STATE.Invalid;
293 throw new TwitterApiException(error, responseText);
295 catch (SerializationException)
297 throw new TwitterApiException(statusCode, responseText);
301 public OAuthEchoHandler CreateOAuthEchoHandler(Uri authServiceProvider, Uri realm = null)
303 var uri = new Uri(RestApiBase, authServiceProvider);
305 return OAuthEchoHandler.CreateHandler(Networking.CreateHttpClientHandler(), uri,
306 ApplicationSettings.TwitterConsumerKey, ApplicationSettings.TwitterConsumerSecret,
307 this.AccessToken, this.AccessSecret, realm);
310 public void Dispose()
313 GC.SuppressFinalize(this);
316 protected virtual void Dispose(bool disposing)
321 this.IsDisposed = true;
325 Networking.WebProxyChanged -= this.Networking_WebProxyChanged;
327 this.httpStreaming.Dispose();
331 ~TwitterApiConnection()
336 private void Networking_WebProxyChanged(object sender, EventArgs e)
338 this.InitializeHttpClients();
341 public static async Task<Tuple<string, string>> GetRequestTokenAsync()
343 var param = new Dictionary<string, string>
345 ["oauth_callback"] = "oob",
347 var response = await GetOAuthTokenAsync(new Uri("https://api.twitter.com/oauth/request_token"), param, oauthToken: null)
348 .ConfigureAwait(false);
350 return Tuple.Create(response["oauth_token"], response["oauth_token_secret"]);
353 public static Uri GetAuthorizeUri(Tuple<string, string> requestToken, string screenName = null)
355 var param = new Dictionary<string, string>
357 ["oauth_token"] = requestToken.Item1,
360 if (screenName != null)
361 param["screen_name"] = screenName;
363 return new Uri("https://api.twitter.com/oauth/authorize?" + MyCommon.BuildQueryString(param));
366 public static async Task<IDictionary<string, string>> GetAccessTokenAsync(Tuple<string, string> requestToken, string verifier)
368 var param = new Dictionary<string, string>
370 ["oauth_verifier"] = verifier,
372 var response = await GetOAuthTokenAsync(new Uri("https://api.twitter.com/oauth/access_token"), param, requestToken)
373 .ConfigureAwait(false);
378 private static async Task<IDictionary<string, string>> GetOAuthTokenAsync(Uri uri, IDictionary<string, string> param,
379 Tuple<string, string> oauthToken)
381 HttpClient authorizeClient;
382 if (oauthToken != null)
383 authorizeClient = InitializeHttpClient(oauthToken.Item1, oauthToken.Item2);
385 authorizeClient = InitializeHttpClient("", "");
387 var requestUri = new Uri(uri, "?" + MyCommon.BuildQueryString(param));
391 using (var request = new HttpRequestMessage(HttpMethod.Post, requestUri))
392 using (var response = await authorizeClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead)
393 .ConfigureAwait(false))
394 using (var content = response.Content)
396 var responseText = await content.ReadAsStringAsync()
397 .ConfigureAwait(false);
399 if (!response.IsSuccessStatusCode)
400 throw new TwitterApiException(response.StatusCode, responseText);
402 var responseParams = HttpUtility.ParseQueryString(responseText);
404 return responseParams.Cast<string>()
405 .ToDictionary(x => x, x => responseParams[x]);
408 catch (HttpRequestException ex)
410 throw TwitterApiException.CreateFromException(ex);
412 catch (OperationCanceledException ex)
414 throw TwitterApiException.CreateFromException(ex);
418 private static HttpClient InitializeHttpClient(string accessToken, string accessSecret, bool streaming = false)
420 var innerHandler = Networking.CreateHttpClientHandler(streaming);
421 innerHandler.CachePolicy = new RequestCachePolicy(RequestCacheLevel.BypassCache);
423 var handler = new OAuthHandler(innerHandler,
424 ApplicationSettings.TwitterConsumerKey, ApplicationSettings.TwitterConsumerSecret,
425 accessToken, accessSecret);
427 return Networking.CreateHttpClient(handler, streaming);