OSDN Git Service

Extended Mode (tweet_mode=extended) でのツイートの取得に対応
authorKimura Youichi <kim.upsilon@bucyou.net>
Wed, 21 Sep 2016 02:31:06 +0000 (11:31 +0900)
committerKimura Youichi <kim.upsilon@bucyou.net>
Wed, 21 Sep 2016 02:33:02 +0000 (11:33 +0900)
 * TwitterStatus クラスのメンバーを Extended Mode の JSON に合わせて変更
 * TwitterStatusCompat は UserStreams から流れてくる Compatibility Mode
   の JSON に使用する
 * TwitterStatusCompat から TwitterStatus への変換は Normarize メソッドで行う

OpenTween/Api/DataModel/TwitterStatus.cs
OpenTween/Api/TwitterApi.cs
OpenTween/Resources/ChangeLog.txt
OpenTween/Twitter.cs
OpenTween/UserInfo.cs
OpenTween/UserInfoDialog.cs

index 1019de3..c3cd10c 100644 (file)
@@ -55,6 +55,9 @@ namespace OpenTween.Api.DataModel
         [DataMember(Name = "created_at")]
         public string CreatedAt { get; set; }
 
+        [DataMember(Name = "display_text_range")]
+        public int[] DisplayTextRange { get; set; }
+
         [DataMember(Name = "entities")]
         public TwitterEntities Entities { get; set; }
 
@@ -70,6 +73,9 @@ namespace OpenTween.Api.DataModel
         [DataMember(Name = "filter_level")]
         public string FilterLevel { get; set; }
 
+        [DataMember(Name = "full_text")]
+        public string FullText { get; set; }
+
         [DataMember(Name = "id")]
         public long Id { get; set; }
 
@@ -121,9 +127,6 @@ namespace OpenTween.Api.DataModel
         [DataMember(Name = "source")]
         public string Source { get; set; }
 
-        [DataMember(Name = "text")]
-        public string Text { get; set; }
-
         [DataMember(Name = "truncated")]
         public bool Truncated { get; set; }
 
@@ -173,6 +176,197 @@ namespace OpenTween.Api.DataModel
         }
     }
 
+    /// <summary>
+    /// Streaming API または tweet_mode=compat の REST API から返されるツイート (Compatibility mode)
+    /// </summary>
+    [DataContract]
+    public class TwitterStatusCompat
+    {
+        [DataMember(Name = "contributors", IsRequired = false)]
+        public TwitterStatus.Contributor[] Contributors { get; set; } // Nullable
+
+        [DataMember(Name = "coordinates", IsRequired = false)]
+        public GeoJsonPoint Coordinates { get; set; }
+
+        [DataMember(Name = "created_at")]
+        public string CreatedAt { get; set; }
+
+        [DataMember(Name = "entities")]
+        public TwitterEntities Entities { get; set; }
+
+        [DataMember(Name = "extended_entities", IsRequired = false)]
+        public TwitterEntities ExtendedEntities { get; set; }
+
+        [DataMember(Name = "extended_tweet", IsRequired = false)]
+        public TwitterStatusCompat.Extended ExtendedTweet { get; set; }
+
+        [DataContract]
+        public class Extended
+        {
+            [DataMember(Name = "display_text_range")]
+            public int[] DisplayTextRange { get; set; }
+
+            [DataMember(Name = "entities")]
+            public TwitterEntities Entities { get; set; }
+
+            [DataMember(Name = "extended_entities", IsRequired = false)]
+            public TwitterEntities ExtendedEntities { get; set; }
+
+            [DataMember(Name = "full_text")]
+            public string FullText { get; set; }
+        }
+
+        [DataMember(Name = "favorite_count")]
+        public int? FavoriteCount { get; set; }
+
+        [DataMember(Name = "favorited")]
+        public bool? Favorited { get; set; }
+
+        [DataMember(Name = "filter_level")]
+        public string FilterLevel { get; set; }
+
+        [DataMember(Name = "id")]
+        public long Id { get; set; }
+
+        [DataMember(Name = "id_str")]
+        public string IdStr { get; set; }
+
+        [DataMember(Name = "in_reply_to_screen_name")]
+        public string InReplyToScreenName { get; set; } // Nullable
+
+        [DataMember(Name = "in_reply_to_status_id")]
+        public long? InReplyToStatusId { get; set; }
+
+        [DataMember(Name = "in_reply_to_status_id_str")]
+        public string InReplyToStatusIdStr { get; set; } // Nullable
+
+        [DataMember(Name = "in_reply_to_user_id")]
+        public long? InReplyToUserId { get; set; }
+
+        [DataMember(Name = "in_reply_to_user_id_str")]
+        public string InReplyToUserIdStr { get; set; } // Nullable
+
+        [DataMember(Name = "lang")]
+        public string Lang { get; set; } // Nullable
+
+        [DataMember(Name = "place", IsRequired = false)]
+        public TwitterPlace Place { get; set; }
+
+        [DataMember(Name = "possibly_sensitive")]
+        public bool? PossiblySensitive { get; set; }
+
+        [DataMember(Name = "quoted_status_id", IsRequired = false)]
+        public long? QuotedStatusId { get; set; }
+
+        [DataMember(Name = "quoted_status_id_str", IsRequired = false)]
+        public string QuotedStatusIdStr { get; set; }
+
+        [DataMember(Name = "quoted_status", IsRequired = false)]
+        public TwitterStatusCompat QuotedStatus { get; set; }
+
+        [DataMember(Name = "retweet_count")]
+        public int RetweetCount { get; set; }
+
+        [DataMember(Name = "retweeted")]
+        public bool Retweeted { get; set; }
+
+        [DataMember(Name = "retweeted_status", IsRequired = false)]
+        public TwitterStatusCompat RetweetedStatus { get; set; }
+
+        [DataMember(Name = "source")]
+        public string Source { get; set; }
+
+        [DataMember(Name = "text")]
+        public string Text { get; set; }
+
+        [DataMember(Name = "truncated")]
+        public bool Truncated { get; set; }
+
+        [DataMember(Name = "user")]
+        public TwitterUser User { get; set; }
+
+        [DataMember(Name = "withheld_copyright")]
+        public bool WithheldCopyright { get; set; }
+
+        [DataMember(Name = "withheld_in_countries")]
+        public string[] WithheldInCountries { get; set; }
+
+        [DataMember(Name = "withheld_scope")]
+        public string WithheldScope { get; set; }
+
+        /// <summary>Compatibility Modeのツイートを<see cref="TwitterStatus"/>に変換します</summary>
+        public TwitterStatus Normarize()
+        {
+            var normarized = new TwitterStatus
+            {
+                Contributors = this.Contributors,
+                Coordinates = this.Coordinates,
+                CreatedAt = this.CreatedAt,
+                FavoriteCount = this.FavoriteCount,
+                Favorited = this.Favorited,
+                FilterLevel = this.FilterLevel,
+                Id = this.Id,
+                IdStr = this.IdStr,
+                InReplyToScreenName = this.InReplyToScreenName,
+                InReplyToStatusId = this.InReplyToStatusId,
+                InReplyToStatusIdStr = this.InReplyToStatusIdStr,
+                InReplyToUserId = this.InReplyToUserId,
+                InReplyToUserIdStr = this.InReplyToUserIdStr,
+                Lang = this.Lang,
+                Place = this.Place,
+                PossiblySensitive = this.PossiblySensitive,
+                QuotedStatusId = this.QuotedStatusId,
+                QuotedStatusIdStr = this.QuotedStatusIdStr,
+                QuotedStatus = this.QuotedStatus?.Normarize(),
+                RetweetCount = this.RetweetCount,
+                Retweeted = this.Retweeted,
+                RetweetedStatus = this.RetweetedStatus?.Normarize(),
+                Source = this.Source,
+                User = this.User,
+                WithheldCopyright = this.WithheldCopyright,
+                WithheldInCountries = this.WithheldInCountries,
+                WithheldScope = this.WithheldScope,
+            };
+
+            if (this.ExtendedTweet != null)
+            {
+                // Extended Tweet
+                normarized.DisplayTextRange = this.ExtendedTweet.DisplayTextRange;
+                normarized.Entities = this.ExtendedTweet.Entities;
+                normarized.ExtendedEntities = this.ExtendedTweet.ExtendedEntities;
+                normarized.FullText = this.ExtendedTweet.FullText;
+                normarized.Truncated = false;
+            }
+            else
+            {
+                // Classic Tweet
+                normarized.DisplayTextRange = new[] { 0, this.GetCodePointCount(this.Text) };
+                normarized.Entities = this.Entities;
+                normarized.ExtendedEntities = this.ExtendedEntities;
+                normarized.FullText = this.Text;
+                normarized.Truncated = this.Truncated;
+            }
+
+            return normarized;
+        }
+
+        /// <summary>Unicodeコードポイント単位の文字数を返します</summary>
+        private int GetCodePointCount(string text)
+            => text.Length - text.Sum(x => char.IsHighSurrogate(x) ? 1 : 0);
+
+        /// <exception cref="SerializationException"/>
+        public static TwitterStatusCompat ParseJson(string json)
+        {
+            return MyCommon.CreateDataFromJson<TwitterStatusCompat>(json);
+        }
+
+        /// <exception cref="SerializationException"/>
+        public static TwitterStatusCompat[] ParseJsonArray(string json)
+        {
+            return MyCommon.CreateDataFromJson<TwitterStatusCompat[]>(json);
+        }
+    }
+
     [DataContract]
     public class TwitterDirectMessage
     {
index 5f904d5..c708caf 100644 (file)
@@ -57,6 +57,7 @@ namespace OpenTween.Api
             {
                 ["include_entities"] = "true",
                 ["include_ext_alt_text"] = "true",
+                ["tweet_mode"] = "extended",
             };
 
             if (count != null)
@@ -76,6 +77,7 @@ namespace OpenTween.Api
             {
                 ["include_entities"] = "true",
                 ["include_ext_alt_text"] = "true",
+                ["tweet_mode"] = "extended",
             };
 
             if (count != null)
@@ -97,6 +99,7 @@ namespace OpenTween.Api
                 ["include_rts"] = "true",
                 ["include_entities"] = "true",
                 ["include_ext_alt_text"] = "true",
+                ["tweet_mode"] = "extended",
             };
 
             if (count != null)
@@ -117,6 +120,7 @@ namespace OpenTween.Api
                 ["id"] = statusId.ToString(),
                 ["include_entities"] = "true",
                 ["include_ext_alt_text"] = "true",
+                ["tweet_mode"] = "extended",
             };
 
             return this.apiConnection.GetAsync<TwitterStatus>(endpoint, param, "/statuses/show/:id");
@@ -130,6 +134,7 @@ namespace OpenTween.Api
                 ["status"] = status,
                 ["include_entities"] = "true",
                 ["include_ext_alt_text"] = "true",
+                ["tweet_mode"] = "extended",
             };
 
             if (replyToId != null)
@@ -159,6 +164,7 @@ namespace OpenTween.Api
                 ["id"] = statusId.ToString(),
                 ["include_entities"] = "true",
                 ["include_ext_alt_text"] = "true",
+                ["tweet_mode"] = "extended",
             };
 
             return this.apiConnection.PostLazyAsync<TwitterStatus>(endpoint, param);
@@ -173,6 +179,7 @@ namespace OpenTween.Api
                 ["result_type"] = "recent",
                 ["include_entities"] = "true",
                 ["include_ext_alt_text"] = "true",
+                ["tweet_mode"] = "extended",
             };
 
             if (lang != null)
@@ -290,6 +297,7 @@ namespace OpenTween.Api
                 ["list_id"] = listId.ToString(),
                 ["include_entities"] = "true",
                 ["include_ext_alt_text"] = "true",
+                ["tweet_mode"] = "extended",
             };
 
             if (count != null)
@@ -312,6 +320,7 @@ namespace OpenTween.Api
                 ["list_id"] = listId.ToString(),
                 ["include_entities"] = "true",
                 ["include_ext_alt_text"] = "true",
+                ["tweet_mode"] = "extended",
             };
 
             if (cursor != null)
@@ -329,6 +338,7 @@ namespace OpenTween.Api
                 ["screen_name"] = screenName,
                 ["include_entities"] = "true",
                 ["include_ext_alt_text"] = "true",
+                ["tweet_mode"] = "extended",
             };
 
             return this.apiConnection.GetAsync<TwitterUser>(endpoint, param, "/lists/members/show");
@@ -343,6 +353,7 @@ namespace OpenTween.Api
                 ["screen_name"] = screenName,
                 ["include_entities"] = "true",
                 ["include_ext_alt_text"] = "true",
+                ["tweet_mode"] = "extended",
             };
 
             return this.apiConnection.PostLazyAsync<TwitterUser>(endpoint, param);
@@ -357,6 +368,7 @@ namespace OpenTween.Api
                 ["screen_name"] = screenName,
                 ["include_entities"] = "true",
                 ["include_ext_alt_text"] = "true",
+                ["tweet_mode"] = "extended",
             };
 
             return this.apiConnection.PostLazyAsync<TwitterUser>(endpoint, param);
@@ -433,6 +445,7 @@ namespace OpenTween.Api
                 ["screen_name"] = screenName,
                 ["include_entities"] = "true",
                 ["include_ext_alt_text"] = "true",
+                ["tweet_mode"] = "extended",
             };
 
             return this.apiConnection.GetAsync<TwitterUser>(endpoint, param, "/users/show/:id");
@@ -444,6 +457,7 @@ namespace OpenTween.Api
             var param = new Dictionary<string, string>
             {
                 ["screen_name"] = screenName,
+                ["tweet_mode"] = "extended",
             };
 
             return this.apiConnection.PostLazyAsync<TwitterUser>(endpoint, param);
@@ -456,6 +470,7 @@ namespace OpenTween.Api
             {
                 ["include_entities"] = "true",
                 ["include_ext_alt_text"] = "true",
+                ["tweet_mode"] = "extended",
             };
 
             if (count != null)
@@ -474,6 +489,7 @@ namespace OpenTween.Api
             var param = new Dictionary<string, string>
             {
                 ["id"] = statusId.ToString(),
+                ["tweet_mode"] = "extended",
             };
 
             return this.apiConnection.PostLazyAsync<TwitterStatus>(endpoint, param);
@@ -485,6 +501,7 @@ namespace OpenTween.Api
             var param = new Dictionary<string, string>
             {
                 ["id"] = statusId.ToString(),
+                ["tweet_mode"] = "extended",
             };
 
             return this.apiConnection.PostLazyAsync<TwitterStatus>(endpoint, param);
@@ -570,6 +587,7 @@ namespace OpenTween.Api
             var param = new Dictionary<string, string>
             {
                 ["screen_name"] = screenName,
+                ["tweet_mode"] = "extended",
             };
 
             return this.apiConnection.PostLazyAsync<TwitterUser>(endpoint, param);
@@ -581,6 +599,7 @@ namespace OpenTween.Api
             var param = new Dictionary<string, string>
             {
                 ["screen_name"] = screenName,
+                ["tweet_mode"] = "extended",
             };
 
             return this.apiConnection.PostLazyAsync<TwitterUser>(endpoint, param);
@@ -593,6 +612,7 @@ namespace OpenTween.Api
             {
                 ["include_entities"] = "true",
                 ["include_ext_alt_text"] = "true",
+                ["tweet_mode"] = "extended",
             };
 
             var user = await this.apiConnection.GetAsync<TwitterUser>(endpoint, param, "/account/verify_credentials")
@@ -611,6 +631,7 @@ namespace OpenTween.Api
             {
                 ["include_entities"] = "true",
                 ["include_ext_alt_text"] = "true",
+                ["tweet_mode"] = "extended",
             };
 
             if (name != null)
@@ -638,6 +659,7 @@ namespace OpenTween.Api
             {
                 ["include_entities"] = "true",
                 ["include_ext_alt_text"] = "true",
+                ["tweet_mode"] = "extended",
             };
             var paramMedia = new Dictionary<string, IMediaItem>
             {
index aa47f94..f2c0035 100644 (file)
@@ -1,6 +1,7 @@
 更新履歴
 
 ==== Ver 1.3.5-dev(2016/xx/xx)
+ * NEW: 140文字を越えるツイートの表示に対応しました
  * NEW: リスト管理で未取得(グレー表示)の状態が無くなり、常に追加・未追加の状態が表示されるようになりました
  * CHG: リストの一覧を表示する際に20件を越える場合のAPIリクエスト回数が少なくなりました
  * FIX: UserStreamsが正常に動作しないことがある不具合を修正
index 28aa505..5a9bed1 100644 (file)
@@ -657,7 +657,7 @@ namespace OpenTween
                 //Id
                 post.RetweetedId = retweeted.Id;
                 //本文
-                post.TextFromApi = retweeted.Text;
+                post.TextFromApi = retweeted.FullText;
                 entities = retweeted.MergedEntities;
                 sourceHtml = retweeted.Source;
                 //Reply先
@@ -713,7 +713,7 @@ namespace OpenTween
             {
                 post.CreatedAt = MyCommon.DateTimeParse(status.CreatedAt);
                 //本文
-                post.TextFromApi = status.Text;
+                post.TextFromApi = status.FullText;
                 entities = status.MergedEntities;
                 sourceHtml = status.Source;
                 post.InReplyToStatusId = status.InReplyToStatusId;
@@ -1297,7 +1297,7 @@ namespace OpenTween
                         var matchStatusUrl = Twitter.StatusUrlRegex.Match(entity.ExpandedUrl);
                         if (matchStatusUrl.Success && matchStatusUrl.Groups["StatusId"].Value == quoteStatus.IdStr)
                         {
-                            var quoteText = this.CreateAccessibleText(quoteStatus.Text, quoteStatus.MergedEntities, quoteStatus: null);
+                            var quoteText = this.CreateAccessibleText(quoteStatus.FullText, quoteStatus.MergedEntities, quoteStatus: null);
                             text = text.Replace(entity.Url, string.Format(Properties.Resources.QuoteStatus_AccessibleText, quoteStatus.User.ScreenName, quoteText));
                         }
                     }
@@ -1905,8 +1905,8 @@ namespace OpenTween
                 {
                     try
                     {
-                        var status = TwitterStatus.ParseJson(line);
-                        this.CreatePostsFromJson(new[] { status }, MyCommon.WORKERTYPE.UserStream, null, false);
+                        var status = TwitterStatusCompat.ParseJson(line);
+                        this.CreatePostsFromJson(new[] { status.Normarize() }, MyCommon.WORKERTYPE.UserStream, null, false);
                     }
                     catch (SerializationException ex)
                     {
@@ -1974,7 +1974,7 @@ namespace OpenTween
             eventTable.TryGetValue(eventData.Event, out eventType);
             evt.Eventtype = eventType;
 
-            TwitterStreamEvent<TwitterStatus> tweetEvent;
+            TwitterStreamEvent<TwitterStatusCompat> tweetEvent;
 
             switch (eventData.Event)
             {
@@ -2002,7 +2002,7 @@ namespace OpenTween
                     return;
                 case "favorite":
                 case "unfavorite":
-                    tweetEvent = TwitterStreamEvent<TwitterStatus>.ParseJson(content);
+                    tweetEvent = TwitterStreamEvent<TwitterStatusCompat>.ParseJson(content);
                     evt.Target = "@" + tweetEvent.TargetObject.User.ScreenName + ":" + WebUtility.HtmlDecode(tweetEvent.TargetObject.Text);
                     evt.Id = tweetEvent.TargetObject.Id;
 
@@ -2052,7 +2052,7 @@ namespace OpenTween
                 case "quoted_tweet":
                     if (evt.IsMe) return;
 
-                    tweetEvent = TwitterStreamEvent<TwitterStatus>.ParseJson(content);
+                    tweetEvent = TwitterStreamEvent<TwitterStatusCompat>.ParseJson(content);
                     evt.Target = "@" + tweetEvent.TargetObject.User.ScreenName + ":" + WebUtility.HtmlDecode(tweetEvent.TargetObject.Text);
                     evt.Id = tweetEvent.TargetObject.Id;
 
index 343df32..71f3734 100644 (file)
@@ -60,7 +60,7 @@ namespace OpenTween
             this.Verified = user.Verified;
             if (user.Status != null)
             {
-                this.RecentPost = user.Status.Text;
+                this.RecentPost = user.Status.FullText;
                 this.PostCreatedAt = MyCommon.DateTimeParse(user.Status.CreatedAt);
                 this.PostSource = user.Status.Source;
             }
index bfade68..5ca0342 100644 (file)
@@ -247,7 +247,7 @@ namespace OpenTween
                 foreach (var entity in entities.Urls)
                     entity.ExpandedUrl = await ShortUrl.Instance.ExpandUrlAsync(entity.ExpandedUrl);
 
-                var html = TweetFormatter.AutoLinkHtml(status.Text, entities);
+                var html = TweetFormatter.AutoLinkHtml(status.FullText, entities);
                 html = this.mainForm.createDetailHtml(html +
                     " Posted at " + MyCommon.DateTimeParse(status.CreatedAt) +
                     " via " + status.Source);