OSDN Git Service

翻訳機能に使用するAPIをMicrosoft Cognitive Servicesに移行
authorKimura Youichi <kim.upsilon@bucyou.net>
Wed, 17 May 2017 02:04:51 +0000 (11:04 +0900)
committerKimura Youichi <kim.upsilon@bucyou.net>
Wed, 17 May 2017 13:28:22 +0000 (22:28 +0900)
Closes #49

OpenTween.Tests/Api/MicrosoftTranslatorApiTest.cs
OpenTween/Api/MicrosoftTranslatorApi.cs
OpenTween/ApplicationSettings.cs
OpenTween/Resources/ChangeLog.txt

index ba27a6f..9097b8b 100644 (file)
@@ -137,66 +137,28 @@ namespace OpenTween.Api
             {
                 var translateApi = new MicrosoftTranslatorApi(http);
 
-                mockHandler.Enqueue(async x =>
+                mockHandler.Enqueue(x =>
                 {
                     Assert.Equal(HttpMethod.Post, x.Method);
-                    Assert.Equal(MicrosoftTranslatorApi.OAuthEndpoint, x.RequestUri);
-
-                    var body = await x.Content.ReadAsStringAsync()
-                        .ConfigureAwait(false);
-                    var query = HttpUtility.ParseQueryString(body);
+                    Assert.Equal(MicrosoftTranslatorApi.IssueTokenEndpoint, x.RequestUri);
 
-                    Assert.Equal("client_credentials", query["grant_type"]);
-                    Assert.Equal(ApplicationSettings.AzureClientId, query["client_id"]);
-                    Assert.Equal(ApplicationSettings.AzureClientSecret, query["client_secret"]);
-                    Assert.Equal("http://api.microsofttranslator.com", query["scope"]);
+                    var keyHeader = x.Headers.First(y => y.Key == "Ocp-Apim-Subscription-Key");
+                    Assert.Equal(ApplicationSettings.TranslatorSubscriptionKey, keyHeader.Value.Single());
 
                     return new HttpResponseMessage(HttpStatusCode.OK)
                     {
-                        Content = new StringContent(@"
-{
-  ""access_token"": ""12345acbde"",
-  ""token_type"": ""bearer"",
-  ""expires_in"": 3600
-}"),
+                        Content = new StringContent(@"ACCESS_TOKEN"),
                     };
                 });
 
                 var result = await translateApi.GetAccessTokenAsync()
                     .ConfigureAwait(false);
 
-                var expectedToken = (@"12345acbde", TimeSpan.FromSeconds(3600));
+                var expectedToken = (@"ACCESS_TOKEN", TimeSpan.FromMinutes(10));
                 Assert.Equal(expectedToken, result);
 
                 Assert.Equal(0, mockHandler.QueueCount);
             }
         }
-
-        [Fact]
-        public void ParseOAuthCredential_ValidResponseTest()
-        {
-            var jsonBytes = Encoding.UTF8.GetBytes(@"
-{
-  ""access_token"": ""12345acbde"",
-  ""token_type"": ""bearer"",
-  ""expires_in"": 3600
-}
-");
-            var expected = (@"12345acbde", TimeSpan.FromSeconds(3600));
-            Assert.Equal(expected, MicrosoftTranslatorApi.ParseOAuthCredential(jsonBytes));
-        }
-
-        [Fact]
-        public void ParseOAuthCredential_OmittedExpiresInTest()
-        {
-            var jsonBytes = Encoding.UTF8.GetBytes(@"
-{
-  ""access_token"": ""12345acbde"",
-  ""token_type"": ""bearer""
-}
-");
-            var expected = (@"12345acbde", TimeSpan.Zero);
-            Assert.Equal(expected, MicrosoftTranslatorApi.ParseOAuthCredential(jsonBytes));
-        }
     }
 }
index 9eca7d9..ae62b69 100644 (file)
@@ -35,7 +35,7 @@ namespace OpenTween.Api
 {
     public class MicrosoftTranslatorApi
     {
-        public static readonly Uri OAuthEndpoint = new Uri("https://datamarket.accesscontrol.windows.net/v2/OAuth2-13");
+        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 string AccessToken { get; internal set; }
@@ -92,65 +92,23 @@ namespace OpenTween.Api
 
             this.AccessToken = accessToken;
 
-            // expires_in の示す時刻より 30 秒早めに再発行する
+            // アクセストークンの実際の有効期限より 30 秒早めに失効として扱う
             this.RefreshAccessTokenAt = DateTime.Now + expiresIn - TimeSpan.FromSeconds(30);
         }
 
         internal virtual async Task<(string AccessToken, TimeSpan ExpiresIn)> GetAccessTokenAsync()
         {
-            var param = new Dictionary<string, string>
+            using (var request = new HttpRequestMessage(HttpMethod.Post, IssueTokenEndpoint))
             {
-                ["grant_type"] = "client_credentials",
-                ["client_id"] = ApplicationSettings.AzureClientId,
-                ["client_secret"] = ApplicationSettings.AzureClientSecret,
-                ["scope"] = "http://api.microsofttranslator.com",
-            };
-
-            using (var request = new HttpRequestMessage(HttpMethod.Post, OAuthEndpoint))
-            using (var postContent = new FormUrlEncodedContent(param))
-            {
-                request.Content = postContent;
+                request.Headers.Add("Ocp-Apim-Subscription-Key", ApplicationSettings.TranslatorSubscriptionKey);
 
                 using (var response = await this.Http.SendAsync(request).ConfigureAwait(false))
                 {
-                    var responseBytes = await response.Content.ReadAsByteArrayAsync()
+                    var accessToken = await response.Content.ReadAsStringAsync()
                         .ConfigureAwait(false);
 
-                    return ParseOAuthCredential(responseBytes);
-                }
-            }
-        }
-
-        internal static (string AccessToken, TimeSpan ExpiresIn) ParseOAuthCredential(byte[] responseBytes)
-        {
-            using (var jsonReader = JsonReaderWriterFactory.CreateJsonReader(responseBytes, XmlDictionaryReaderQuotas.Max))
-            {
-                var xElm = XElement.Load(jsonReader);
-
-                var tokenTypeElm = xElm.Element("token_type");
-                if (tokenTypeElm == null)
-                    throw new WebApiException("Property `token_type` required");
-
-                var accessTokenElm = xElm.Element("access_token");
-                if (accessTokenElm == null)
-                    throw new WebApiException("Property `access_token` required");
-
-                var expiresInElm = xElm.Element("expires_in");
-
-                int expiresInSeconds;
-                if (expiresInElm != null)
-                {
-                    if (!int.TryParse(expiresInElm.Value, out expiresInSeconds))
-                        throw new WebApiException("Invalid number: expires_in = " + expiresInElm.Value);
-                }
-                else
-                {
-                    // expires_in が省略された場合は有効期間が不明なので、
-                    // 次回のリクエスト時は経過時間に関わらずアクセストークンの再発行を行う
-                    expiresInSeconds = 0;
+                    return (accessToken, TimeSpan.FromMinutes(10));
                 }
-
-                return (accessTokenElm.Value, TimeSpan.FromSeconds(expiresInSeconds));
             }
         }
     }
index 414a4e7..aa757e4 100644 (file)
@@ -141,18 +141,13 @@ namespace OpenTween
         public const string TINAMIApiKey = "4f48bb4858d36";
 
         //=====================================================================
-        // Windows Azure Marketplace
-        // https://datamarket.azure.com/developer/applications から取得できます。
+        // Microsoft Translator API (Cognitive Service)
+        // https://www.microsoft.com/ja-jp/translator/getstarted.aspx から取得できます。
 
         /// <summary>
-        /// Windows Azure Marketplace Client Id
+        /// Translator Text API Subscription Key
         /// </summary>
-        public readonly static string AzureClientId = "OpenTween";
-
-        /// <summary>
-        /// Windows Azure Marketplace Client Secret
-        /// </summary>
-        public readonly static string AzureClientSecret = "UiTaBcOkGxCpjloU/2W3P0fTiHNz+FUeGuzgUz2rwZU=";
+        public readonly static string TranslatorSubscriptionKey = "6c47d2ea341148bf856bdbfafd429db7";
 
         //=====================================================================
         // Imgur
index f6321d2..ac84410 100644 (file)
@@ -3,6 +3,7 @@
 ==== Ver 1.3.8-dev(2017/xx/xx)
  * NEW: bit.ly の認証方式が変更されました
   - 短縮URLに bit.ly を使用する場合は、設定画面の「短縮URL」から bit.ly の「認可」ボタンを押して認証情報を入力して下さい
+ * FIX: Microsoft DataMarket廃止により翻訳機能が使用できなくなった不具合を修正
 
 ==== Ver 1.3.7(2017/03/20)
  * NEW: PNG画像のアップロード時にJPEGへの変換による劣化を回避する機能を追加しました (pic.twitter.com のみ)