OSDN Git Service

Foursquareの新しい形式のチェックインURLのサムネイル表示に対応
authorKimura Youichi <kim.upsilon@bucyou.net>
Sat, 5 Dec 2015 08:48:52 +0000 (17:48 +0900)
committerKimura Youichi <kim.upsilon@bucyou.net>
Thu, 10 Dec 2015 16:44:11 +0000 (01:44 +0900)
9c7bfc9c500252cab34bc1d54fa0c264aea0dfcf の修正では単なる URL の変更と扱っていたが、
実際には URL に直接チェックイン ID が含まれなくなり呼び出す API エンドポイントも変化していたため
改めて新しい形式の チェックイン URL から正しく位置情報を取得できるように修正した。

OpenTween.Tests/Thumbnail/Services/FoursquareCheckinTest.cs
OpenTween/Thumbnail/Services/FoursquareCheckin.cs

index ec8756f..37384d8 100644 (file)
@@ -45,29 +45,64 @@ namespace OpenTween.Thumbnail.Services
             var match = FoursquareCheckin.UrlPatternRegex.Match("https://www.swarmapp.com/c/xxxxxxxx");
             Assert.True(match.Success);
             Assert.Equal("xxxxxxxx", match.Groups["checkin_id"].Value);
-            Assert.Equal("", match.Groups["signature"].Value);
-
-            // signature 付きのチェックイン URL (www.swarmapp.com/c/***?s=***)
-            match = FoursquareCheckin.UrlPatternRegex.Match("https://www.swarmapp.com/c/xxxxxxxx?s=aaaaaaa");
-            Assert.True(match.Success);
-            Assert.Equal("xxxxxxxx", match.Groups["checkin_id"].Value);
-            Assert.Equal("aaaaaaa", match.Groups["signature"].Value);
+        }
 
+        [Fact]
+        public void LegacyUrlPattern_Test()
+        {
             // 古い形式の URL (foursquare.com/***/checkin/***?s=***)
-            match = FoursquareCheckin.UrlPatternRegex.Match("https://foursquare.com/hogehoge/checkin/xxxxxxxx?s=aaaaaaa");
+            var match = FoursquareCheckin.LegacyUrlPatternRegex.Match("https://foursquare.com/hogehoge/checkin/xxxxxxxx?s=aaaaaaa");
             Assert.True(match.Success);
             Assert.Equal("xxxxxxxx", match.Groups["checkin_id"].Value);
             Assert.Equal("aaaaaaa", match.Groups["signature"].Value);
 
             // 古い形式の URL (www.swarmapp.com/***/checkin/***?s=***)
-            match = FoursquareCheckin.UrlPatternRegex.Match("https://www.swarmapp.com/hogehoge/checkin/xxxxxxxx?s=aaaaaaa");
+            match = FoursquareCheckin.LegacyUrlPatternRegex.Match("https://www.swarmapp.com/hogehoge/checkin/xxxxxxxx?s=aaaaaaa");
             Assert.True(match.Success);
             Assert.Equal("xxxxxxxx", match.Groups["checkin_id"].Value);
             Assert.Equal("aaaaaaa", match.Groups["signature"].Value);
         }
 
         [Fact]
-        public async Task GetThumbnailInfoAsync_RequestTest()
+        public async Task GetThumbnailInfoAsync_NewUrlTest()
+        {
+            var handler = new HttpMessageHandlerMock();
+            using (var http = new HttpClient(handler))
+            {
+                var service = new FoursquareCheckin(http);
+
+                handler.Enqueue(x =>
+                {
+                    Assert.Equal(HttpMethod.Get, x.Method);
+                    Assert.Equal("https://api.foursquare.com/v2/checkins/resolve",
+                        x.RequestUri.GetLeftPart(UriPartial.Path));
+
+                    var query = HttpUtility.ParseQueryString(x.RequestUri.Query);
+
+                    Assert.Equal(ApplicationSettings.FoursquareClientId, query["client_id"]);
+                    Assert.Equal(ApplicationSettings.FoursquareClientSecret, query["client_secret"]);
+                    Assert.NotNull(query["v"]);
+                    Assert.Equal("xxxxxxxx", query["shortId"]);
+
+                    // リクエストに対するテストなのでレスポンスは適当に返す
+                    return new HttpResponseMessage(HttpStatusCode.NotFound);
+                });
+
+                var post = new PostClass
+                {
+                    PostGeo = null,
+                };
+
+                var thumb = await service.GetThumbnailInfoAsync(
+                    "https://www.swarmapp.com/c/xxxxxxxx",
+                    post, CancellationToken.None);
+
+                Assert.Equal(0, handler.QueueCount);
+            }
+        }
+
+        [Fact]
+        public async Task GetThumbnailInfoAsync_LegacyUrlTest()
         {
             var handler = new HttpMessageHandlerMock();
             using (var http = new HttpClient(handler))
@@ -105,7 +140,7 @@ namespace OpenTween.Thumbnail.Services
         }
 
         [Fact]
-        public async Task GetThumbnailInfoAsync_RequestWithSignatureTest()
+        public async Task GetThumbnailInfoAsync_LegacyUrlWithSignatureTest()
         {
             var handler = new HttpMessageHandlerMock();
             using (var http = new HttpClient(handler))
@@ -164,7 +199,7 @@ namespace OpenTween.Thumbnail.Services
                 };
 
                 var thumb = await service.GetThumbnailInfoAsync(
-                    "https://foursquare.com/hogehoge/checkin/xxxxxxxx?s=aaaaaaa",
+                    "https://www.swarmapp.com/c/xxxxxxxx",
                     post, CancellationToken.None);
 
                 Assert.Equal(1, handler.QueueCount);
index 52965d5..d201e50 100644 (file)
@@ -37,7 +37,10 @@ namespace OpenTween.Thumbnail.Services
     class FoursquareCheckin : IThumbnailService
     {
         public static readonly Regex UrlPatternRegex =
-            new Regex(@"^https?://(?:foursquare\.com|www\.swarmapp\.com)/(?:c/|.+?/checkin/)(?<checkin_id>[0-9a-z]+)(?:\?s=(?<signature>[^&]+))?");
+            new Regex(@"^https?://www\.swarmapp\.com/c/(?<checkin_id>[0-9a-zA-Z]+)");
+
+        public static readonly Regex LegacyUrlPatternRegex =
+            new Regex(@"^https?://(?:foursquare\.com|www\.swarmapp\.com)/.+?/checkin/(?<checkin_id>[0-9a-z]+)(?:\?s=(?<signature>[^&]+))?");
 
         public static readonly string ApiBase = "https://api.foursquare.com/v2";
 
@@ -63,7 +66,75 @@ namespace OpenTween.Thumbnail.Services
             if (post.PostGeo != null)
                 return null;
 
+            var location = await this.FetchCheckinLocation(url, token)
+                .ConfigureAwait(false);
+
+            if (location == null)
+            {
+                location = await this.FetchCheckinLocationLegacy(url, token)
+                    .ConfigureAwait(false);
+            }
+
+            if (location != null)
+            {
+                var map = MapThumb.GetDefaultInstance();
+
+                return await map.GetThumbnailInfoAsync(new PostClass.StatusGeo(location.Longitude, location.Latitude))
+                    .ConfigureAwait(false);
+            }
+
+            return null;
+        }
+
+        /// <summary>
+        /// Foursquare のチェックイン URL から位置情報を取得します
+        /// </summary>
+        public async Task<GlobalLocation> FetchCheckinLocation(string url, CancellationToken token)
+        {
             var match = UrlPatternRegex.Match(url);
+            if (!match.Success)
+                return null;
+
+            var checkinIdGroup = match.Groups["checkin_id"];
+
+            try
+            {
+                // Foursquare のチェックイン情報を取得
+                // 参照: https://developer.foursquare.com/docs/checkins/resolve
+
+                var query = new Dictionary<string, string>
+                {
+                    ["client_id"] = ApplicationSettings.FoursquareClientId,
+                    ["client_secret"] = ApplicationSettings.FoursquareClientSecret,
+                    ["v"] = "20140419", // https://developer.foursquare.com/overview/versioning
+
+                    ["shortId"] = checkinIdGroup.Value,
+                };
+
+                var apiUrl = new Uri(ApiBase + "/checkins/resolve?" + MyCommon.BuildQueryString(query));
+
+                using (var response = await this.http.GetAsync(apiUrl, token).ConfigureAwait(false))
+                {
+                    response.EnsureSuccessStatusCode();
+
+                    var jsonBytes = await response.Content.ReadAsByteArrayAsync()
+                        .ConfigureAwait(false);
+
+                    return ParseIntoLocation(jsonBytes);
+                }
+            }
+            catch (HttpRequestException)
+            {
+                return null;
+            }
+        }
+
+        /// <summary>
+        /// Foursquare のチェックイン URL から位置情報を取得します (古い形式の URL)
+        /// </summary>
+        public async Task<GlobalLocation> FetchCheckinLocationLegacy(string url, CancellationToken token)
+        {
+            var match = LegacyUrlPatternRegex.Match(url);
 
             if (!match.Success)
                 return null;
@@ -95,19 +166,13 @@ namespace OpenTween.Thumbnail.Services
                     var jsonBytes = await response.Content.ReadAsByteArrayAsync()
                         .ConfigureAwait(false);
 
-                    var location = ParseIntoLocation(jsonBytes);
-                    if (location == null)
-                        return null;
-
-                    var map = MapThumb.GetDefaultInstance();
-
-                    return await map.GetThumbnailInfoAsync(new PostClass.StatusGeo(location.Longitude, location.Latitude))
-                        .ConfigureAwait(false);
+                    return ParseIntoLocation(jsonBytes);
                 }
             }
-            catch (HttpRequestException) { }
-
-            return null;
+            catch (HttpRequestException)
+            {
+                return null;
+            }
         }
 
         internal static GlobalLocation ParseIntoLocation(byte[] jsonBytes)