OSDN Git Service

凍結されたユーザーのツイートに対するエラー表示に対応
authorKimura Youichi <kim.upsilon@bucyou.net>
Sat, 25 Nov 2023 01:24:53 +0000 (10:24 +0900)
committerKimura Youichi <kim.upsilon@bucyou.net>
Sat, 25 Nov 2023 01:46:04 +0000 (10:46 +0900)
OpenTween.Tests/Api/GraphQL/TimelineTweetTest.cs
OpenTween.Tests/OpenTween.Tests.csproj
OpenTween.Tests/Resources/Responses/TimelineTweet_TweetTombstone.json [new file with mode: 0644]
OpenTween/Api/GraphQL/TimelineTweet.cs
OpenTween/Twitter.cs

index 2613b97..6747ad1 100644 (file)
@@ -153,5 +153,18 @@ namespace OpenTween.Api.GraphQL
             Assert.True(post.IsPromoted);
             Assert.Matches(new Regex(@"^\[Promoted\]\n"), post.TextFromApi);
         }
+
+        [Fact]
+        public void ToStatus_TweetTombstone_Test()
+        {
+            var rootElm = this.LoadResponseDocument("TimelineTweet_TweetTombstone.json");
+            var timelineTweet = new TimelineTweet(rootElm);
+
+            Assert.True(timelineTweet.IsTombstone);
+            var ex = Assert.Throws<WebApiException>(
+                () => timelineTweet.ToTwitterStatus()
+            );
+            Assert.Equal("This Post is from a suspended account. Learn more", ex.Message);
+        }
     }
 }
index ebdd16c..3541dc9 100644 (file)
@@ -73,6 +73,9 @@
     <None Update="Resources\Responses\TimelineTweet_RetweetedTweet.json">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </None>
+    <None Update="Resources\Responses\TimelineTweet_TweetTombstone.json">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </None>
     <None Update="Resources\Responses\TimelineTweet_TweetWithVisibility.json">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </None>
diff --git a/OpenTween.Tests/Resources/Responses/TimelineTweet_TweetTombstone.json b/OpenTween.Tests/Resources/Responses/TimelineTweet_TweetTombstone.json
new file mode 100644 (file)
index 0000000..d47d614
--- /dev/null
@@ -0,0 +1,29 @@
+{
+  "itemType": "TimelineTweet",
+  "__typename": "TimelineTweet",
+  "tweet_results": {
+    "result": {
+      "__typename": "TweetTombstone",
+      "tombstone": {
+        "__typename": "TextTombstone",
+        "text": {
+          "rtl": false,
+          "text": "This Post is from a suspended account. Learn more",
+          "entities": [
+            {
+              "fromIndex": 39,
+              "toIndex": 49,
+              "ref": {
+                "type": "TimelineUrl",
+                "url": "https://help.twitter.com/rules-and-policies/notices-on-twitter",
+                "urlType": "ExternalUrl"
+              }
+            }
+          ]
+        }
+      }
+    }
+  },
+  "tweetDisplayType": "Tweet",
+  "hasModeratedReplies": false
+}
index 04392fa..375a87d 100644 (file)
@@ -38,6 +38,11 @@ namespace OpenTween.Api.GraphQL
 
         public XElement Element { get; }
 
+        public bool IsTombstone
+            => this.tombstoneElm != null;
+
+        private readonly XElement? tombstoneElm;
+
         public TimelineTweet(XElement element)
         {
             var typeName = element.Element("itemType")?.Value;
@@ -45,10 +50,16 @@ namespace OpenTween.Api.GraphQL
                 throw new ArgumentException($"Invalid itemType: {typeName}", nameof(element));
 
             this.Element = element;
+            this.tombstoneElm = this.TryGetTombstoneElm();
         }
 
+        private XElement? TryGetTombstoneElm()
+            => this.Element.XPathSelectElement("tweet_results/result[__typename[text()='TweetTombstone']]");
+
         public TwitterStatus ToTwitterStatus()
         {
+            this.ThrowIfTweetIsTombstone();
+
             try
             {
                 var resultElm = this.Element.Element("tweet_results")?.Element("result") ?? throw CreateParseError();
@@ -67,6 +78,18 @@ namespace OpenTween.Api.GraphQL
             }
         }
 
+        public void ThrowIfTweetIsTombstone()
+        {
+            if (this.tombstoneElm == null)
+                return;
+
+            var tombstoneText = this.tombstoneElm.XPathSelectElement("tombstone/text/text")?.Value;
+            var message = tombstoneText ?? "Tweet is not available";
+            var json = JsonUtils.JsonXmlToString(this.Element);
+
+            throw new WebApiException(message, json);
+        }
+
         public static TwitterStatus ParseTweetUnion(XElement tweetUnionElm)
         {
             var tweetElm = tweetUnionElm.Element("__typename")?.Value switch
index 9074fb7..81b03df 100644 (file)
@@ -687,7 +687,11 @@ namespace OpenTween
                 var response = await request.Send(this.Api.Connection)
                     .ConfigureAwait(false);
 
-                statuses = response.Tweets.Select(x => x.ToTwitterStatus()).ToArray();
+                statuses = response.Tweets
+                    .Where(x => !x.IsTombstone)
+                    .Select(x => x.ToTwitterStatus())
+                    .ToArray();
+
                 tab.CursorBottom = response.CursorBottom;
             }
             else
@@ -881,7 +885,10 @@ namespace OpenTween
                 var response = await request.Send(this.Api.Connection)
                     .ConfigureAwait(false);
 
-                var convertedStatuses = response.Tweets.Select(x => x.ToTwitterStatus());
+                var convertedStatuses = response.Tweets
+                    .Where(x => !x.IsTombstone)
+                    .Select(x => x.ToTwitterStatus());
+
                 if (!SettingManager.Instance.Common.IsListsIncludeRts)
                     convertedStatuses = convertedStatuses.Where(x => x.RetweetedStatus == null);
 
@@ -1085,7 +1092,11 @@ namespace OpenTween
                 var response = await request.Send(this.Api.Connection)
                     .ConfigureAwait(false);
 
-                statuses = response.Tweets.Select(x => x.ToTwitterStatus()).ToArray();
+                statuses = response.Tweets
+                    .Where(x => !x.IsTombstone)
+                    .Select(x => x.ToTwitterStatus())
+                    .ToArray();
+
                 tab.CursorBottom = response.CursorBottom;
             }
             else