OSDN Git Service

string.IsNullOrEmpty の nullable annotation あり版のメソッドを追加
[opentween/open-tween.git] / OpenTween / Api / MicrosoftTranslatorApi.cs
index ae62b69..b55835e 100644 (file)
@@ -19,6 +19,8 @@
 // the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor,
 // Boston, MA 02110-1301, USA.
 
+#nullable enable
+
 using System;
 using System.Collections.Generic;
 using System.Linq;
@@ -29,6 +31,7 @@ using System.Text;
 using System.Threading.Tasks;
 using System.Xml;
 using System.Xml.Linq;
+using System.Xml.XPath;
 using OpenTween.Connection;
 
 namespace OpenTween.Api
@@ -36,32 +39,30 @@ namespace OpenTween.Api
     public class MicrosoftTranslatorApi
     {
         public static readonly Uri IssueTokenEndpoint = new Uri("https://api.cognitive.microsoft.com/sts/v1.0/issueToken");
-        public static readonly Uri TranslateEndpoint = new Uri("https://api.microsofttranslator.com/v2/Http.svc/Translate");
+        public static readonly Uri TranslateEndpoint = new Uri("https://api.cognitive.microsofttranslator.com/translate");
 
-        public string AccessToken { get; internal set; }
-        public DateTime RefreshAccessTokenAt { get; internal set; }
+        public string AccessToken { get; internal set; } = "";
+        public DateTimeUtc RefreshAccessTokenAt { get; internal set; } = DateTimeUtc.MinValue;
 
         private HttpClient Http => this.localHttpClient ?? Networking.Http;
-        private readonly HttpClient localHttpClient;
+        private readonly HttpClient? localHttpClient;
 
         public MicrosoftTranslatorApi()
             : this(null)
         {
         }
 
-        public MicrosoftTranslatorApi(HttpClient http)
-        {
-            this.localHttpClient = http;
-        }
+        public MicrosoftTranslatorApi(HttpClient? http)
+            => this.localHttpClient = http;
 
-        public async Task<string> TranslateAsync(string text, string langTo, string langFrom = null)
+        public async Task<string> TranslateAsync(string text, string langTo, string? langFrom = null)
         {
             await this.UpdateAccessTokenIfExpired()
                 .ConfigureAwait(false);
 
             var param = new Dictionary<string, string>
             {
-                ["text"] = text,
+                ["api-version"] = "3.0",
                 ["to"] = langTo,
             };
 
@@ -70,21 +71,33 @@ namespace OpenTween.Api
 
             var requestUri = new Uri(TranslateEndpoint, "?" + MyCommon.BuildQueryString(param));
 
-            using (var request = new HttpRequestMessage(HttpMethod.Get, requestUri))
-            {
-                request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", this.AccessToken);
-
-                using (var response = await this.Http.SendAsync(request).ConfigureAwait(false))
-                {
-                    return await response.Content.ReadAsStringAsync()
-                        .ConfigureAwait(false);
-                }
-            }
+            using var request = new HttpRequestMessage(HttpMethod.Post, requestUri);
+            request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", this.AccessToken);
+
+            var escapedText = JsonUtils.EscapeJsonString(text);
+            var json = $@"[{{""Text"": ""{escapedText}""}}]";
+
+            using var body = new StringContent(json, Encoding.UTF8, "application/json");
+            request.Content = body;
+
+            using var response = await this.Http.SendAsync(request)
+                .ConfigureAwait(false);
+
+            response.EnsureSuccessStatusCode();
+
+            var responseJson = await response.Content.ReadAsByteArrayAsync()
+                .ConfigureAwait(false);
+
+            using var jsonReader = JsonReaderWriterFactory.CreateJsonReader(responseJson, XmlDictionaryReaderQuotas.Max);
+            var xElm = XElement.Load(jsonReader);
+            var transtlationTextElm = xElm.XPathSelectElement("/item/translations/item/text[1]");
+
+            return transtlationTextElm?.Value ?? "";
         }
 
         public async Task UpdateAccessTokenIfExpired()
         {
-            if (this.AccessToken != null && this.RefreshAccessTokenAt > DateTime.Now)
+            if (!MyCommon.IsNullOrEmpty(this.AccessToken) && this.RefreshAccessTokenAt > DateTimeUtc.Now)
                 return;
 
             var (accessToken, expiresIn) = await this.GetAccessTokenAsync()
@@ -93,23 +106,23 @@ namespace OpenTween.Api
             this.AccessToken = accessToken;
 
             // アクセストークンの実際の有効期限より 30 秒早めに失効として扱う
-            this.RefreshAccessTokenAt = DateTime.Now + expiresIn - TimeSpan.FromSeconds(30);
+            this.RefreshAccessTokenAt = DateTimeUtc.Now + expiresIn - TimeSpan.FromSeconds(30);
         }
 
         internal virtual async Task<(string AccessToken, TimeSpan ExpiresIn)> GetAccessTokenAsync()
         {
-            using (var request = new HttpRequestMessage(HttpMethod.Post, IssueTokenEndpoint))
-            {
-                request.Headers.Add("Ocp-Apim-Subscription-Key", ApplicationSettings.TranslatorSubscriptionKey);
+            using var request = new HttpRequestMessage(HttpMethod.Post, IssueTokenEndpoint);
+            request.Headers.Add("Ocp-Apim-Subscription-Key", ApplicationSettings.TranslatorSubscriptionKey);
 
-                using (var response = await this.Http.SendAsync(request).ConfigureAwait(false))
-                {
-                    var accessToken = await response.Content.ReadAsStringAsync()
-                        .ConfigureAwait(false);
+            using var response = await this.Http.SendAsync(request)
+                .ConfigureAwait(false);
+
+            response.EnsureSuccessStatusCode();
+
+            var accessToken = await response.Content.ReadAsStringAsync()
+                .ConfigureAwait(false);
 
-                    return (accessToken, TimeSpan.FromMinutes(10));
-                }
-            }
+            return (accessToken, TimeSpan.FromMinutes(10));
         }
     }
 }