1 // OpenTween - Client of Twitter
2 // Copyright (c) 2012 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.
25 using System.Collections.Generic;
26 using System.Globalization;
27 using System.Net.Http;
28 using System.Runtime.Serialization.Json;
29 using System.Text.RegularExpressions;
30 using System.Threading;
31 using System.Threading.Tasks;
34 using System.Xml.Linq;
35 using System.Xml.XPath;
36 using OpenTween.Connection;
37 using OpenTween.Models;
39 namespace OpenTween.Thumbnail.Services
41 class FoursquareCheckin : IThumbnailService
43 public static readonly Regex UrlPatternRegex =
44 new Regex(@"^https?://www\.swarmapp\.com/c/(?<checkin_id>[0-9a-zA-Z]+)");
46 public static readonly Regex LegacyUrlPatternRegex =
47 new Regex(@"^https?://(?:foursquare\.com|www\.swarmapp\.com)/.+?/checkin/(?<checkin_id>[0-9a-z]+)(?:\?s=(?<signature>[^&]+))?");
49 public static readonly string ApiBase = "https://api.foursquare.com/v2";
51 protected HttpClient http
52 => this.localHttpClient ?? Networking.Http;
54 private readonly HttpClient? localHttpClient;
56 public FoursquareCheckin()
61 public FoursquareCheckin(HttpClient? http)
62 => this.localHttpClient = http;
64 public override async Task<ThumbnailInfo?> GetThumbnailInfoAsync(string url, PostClass post, CancellationToken token)
66 // ツイートに位置情報が付与されている場合は何もしない
67 if (post.PostGeo != null)
70 var location = await this.FetchCheckinLocation(url, token)
71 .ConfigureAwait(false);
75 location = await this.FetchCheckinLocationLegacy(url, token)
76 .ConfigureAwait(false);
81 var map = MapThumb.GetDefaultInstance();
83 return await map.GetThumbnailInfoAsync(new PostClass.StatusGeo(location.Longitude, location.Latitude))
84 .ConfigureAwait(false);
91 /// Foursquare のチェックイン URL から位置情報を取得します
93 public async Task<GlobalLocation?> FetchCheckinLocation(string url, CancellationToken token)
95 var match = UrlPatternRegex.Match(url);
99 var checkinIdGroup = match.Groups["checkin_id"];
103 // Foursquare のチェックイン情報を取得
104 // 参照: https://developer.foursquare.com/docs/checkins/resolve
106 var query = new Dictionary<string, string>
108 ["client_id"] = ApplicationSettings.FoursquareClientId,
109 ["client_secret"] = ApplicationSettings.FoursquareClientSecret,
110 ["v"] = "20140419", // https://developer.foursquare.com/overview/versioning
112 ["shortId"] = checkinIdGroup.Value,
115 var apiUrl = new Uri(ApiBase + "/checkins/resolve?" + MyCommon.BuildQueryString(query));
117 using var response = await this.http.GetAsync(apiUrl, token)
118 .ConfigureAwait(false);
120 response.EnsureSuccessStatusCode();
122 var jsonBytes = await response.Content.ReadAsByteArrayAsync()
123 .ConfigureAwait(false);
125 return ParseIntoLocation(jsonBytes);
127 catch (HttpRequestException)
134 /// Foursquare のチェックイン URL から位置情報を取得します (古い形式の URL)
136 public async Task<GlobalLocation?> FetchCheckinLocationLegacy(string url, CancellationToken token)
138 var match = LegacyUrlPatternRegex.Match(url);
143 var checkinIdGroup = match.Groups["checkin_id"];
144 var signatureGroup = match.Groups["signature"];
148 // Foursquare のベニュー情報を取得
149 // 参照: https://developer.foursquare.com/docs/venues/venues
151 var query = new Dictionary<string, string>
153 ["client_id"] = ApplicationSettings.FoursquareClientId,
154 ["client_secret"] = ApplicationSettings.FoursquareClientSecret,
155 ["v"] = "20140419", // https://developer.foursquare.com/overview/versioning
158 if (signatureGroup.Success)
159 query["signature"] = signatureGroup.Value;
161 var apiUrl = new Uri(ApiBase + "/checkins/" + checkinIdGroup.Value + "?" + MyCommon.BuildQueryString(query));
163 using var response = await this.http.GetAsync(apiUrl, token)
164 .ConfigureAwait(false);
166 response.EnsureSuccessStatusCode();
168 var jsonBytes = await response.Content.ReadAsByteArrayAsync()
169 .ConfigureAwait(false);
171 return ParseIntoLocation(jsonBytes);
173 catch (HttpRequestException)
179 internal static GlobalLocation? ParseIntoLocation(byte[] jsonBytes)
181 using var jsonReader = JsonReaderWriterFactory.CreateJsonReader(jsonBytes, XmlDictionaryReaderQuotas.Max);
182 var xElm = XElement.Load(jsonReader);
184 var locationElm = xElm.XPathSelectElement("/response/checkin/venue/location");
187 if (locationElm == null)
190 // 月など、地球以外の星の座標である場合
191 var planetElm = locationElm.Element("planet");
192 if (planetElm != null && planetElm.Value != "earth")
195 return new GlobalLocation
197 Latitude = double.Parse(locationElm.Element("lat").Value, CultureInfo.InvariantCulture),
198 Longitude = double.Parse(locationElm.Element("lng").Value, CultureInfo.InvariantCulture),