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;
25 using System.Net.Http;
26 using System.Net.Http.Headers;
27 using System.Runtime.Serialization.Json;
29 using System.Threading.Tasks;
31 using System.Xml.Linq;
32 using OpenTween.Connection;
34 namespace OpenTween.Api
36 public class MicrosoftTranslatorApi
38 public static readonly Uri OAuthEndpoint = new Uri("https://datamarket.accesscontrol.windows.net/v2/OAuth2-13");
39 public static readonly Uri TranslateEndpoint = new Uri("https://api.microsofttranslator.com/v2/Http.svc/Translate");
41 public string AccessToken { get; internal set; }
42 public DateTime RefreshAccessTokenAt { get; internal set; }
44 private HttpClient Http => this.localHttpClient ?? Networking.Http;
45 private readonly HttpClient localHttpClient;
47 public MicrosoftTranslatorApi()
52 public MicrosoftTranslatorApi(HttpClient http)
54 this.localHttpClient = http;
57 public async Task<string> TranslateAsync(string text, string langTo, string langFrom = null)
59 await this.UpdateAccessTokenIfExpired()
60 .ConfigureAwait(false);
62 var param = new Dictionary<string, string>
69 param["from"] = langFrom;
71 var requestUri = new Uri(TranslateEndpoint, "?" + MyCommon.BuildQueryString(param));
73 using (var request = new HttpRequestMessage(HttpMethod.Get, requestUri))
75 request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", this.AccessToken);
77 using (var response = await this.Http.SendAsync(request).ConfigureAwait(false))
79 return await response.Content.ReadAsStringAsync()
80 .ConfigureAwait(false);
85 public async Task UpdateAccessTokenIfExpired()
87 if (this.AccessToken != null && this.RefreshAccessTokenAt > DateTime.Now)
90 var (accessToken, expiresIn) = await this.GetAccessTokenAsync()
91 .ConfigureAwait(false);
93 this.AccessToken = accessToken;
95 // expires_in の示す時刻より 30 秒早めに再発行する
96 this.RefreshAccessTokenAt = DateTime.Now + expiresIn - TimeSpan.FromSeconds(30);
99 internal virtual async Task<(string AccessToken, TimeSpan ExpiresIn)> GetAccessTokenAsync()
101 var param = new Dictionary<string, string>
103 ["grant_type"] = "client_credentials",
104 ["client_id"] = ApplicationSettings.AzureClientId,
105 ["client_secret"] = ApplicationSettings.AzureClientSecret,
106 ["scope"] = "http://api.microsofttranslator.com",
109 using (var request = new HttpRequestMessage(HttpMethod.Post, OAuthEndpoint))
110 using (var postContent = new FormUrlEncodedContent(param))
112 request.Content = postContent;
114 using (var response = await this.Http.SendAsync(request).ConfigureAwait(false))
116 var responseBytes = await response.Content.ReadAsByteArrayAsync()
117 .ConfigureAwait(false);
119 return ParseOAuthCredential(responseBytes);
124 internal static (string AccessToken, TimeSpan ExpiresIn) ParseOAuthCredential(byte[] responseBytes)
126 using (var jsonReader = JsonReaderWriterFactory.CreateJsonReader(responseBytes, XmlDictionaryReaderQuotas.Max))
128 var xElm = XElement.Load(jsonReader);
130 var tokenTypeElm = xElm.Element("token_type");
131 if (tokenTypeElm == null)
132 throw new WebApiException("Property `token_type` required");
134 var accessTokenElm = xElm.Element("access_token");
135 if (accessTokenElm == null)
136 throw new WebApiException("Property `access_token` required");
138 var expiresInElm = xElm.Element("expires_in");
140 int expiresInSeconds;
141 if (expiresInElm != null)
143 if (!int.TryParse(expiresInElm.Value, out expiresInSeconds))
144 throw new WebApiException("Invalid number: expires_in = " + expiresInElm.Value);
148 // expires_in が省略された場合は有効期間が不明なので、
149 // 次回のリクエスト時は経過時間に関わらずアクセストークンの再発行を行う
150 expiresInSeconds = 0;
153 return (accessTokenElm.Value, TimeSpan.FromSeconds(expiresInSeconds));