OSDN Git Service

ユーザータイムラインにリプライが含まれない不具合を修正
authorKimura Youichi <kim.upsilon@bucyou.net>
Tue, 28 Nov 2023 15:38:55 +0000 (00:38 +0900)
committerKimura Youichi <kim.upsilon@bucyou.net>
Tue, 28 Nov 2023 15:38:55 +0000 (00:38 +0900)
Fixes: 3b20d4c8 ("graphqlエンドポイントを使用したユーザータイムラインの取得に対応")

OpenTween.Tests/Api/GraphQL/UserTweetsAndRepliesRequestTest.cs [moved from OpenTween.Tests/Api/GraphQL/UserTweetsRequestTest.cs with 77% similarity]
OpenTween.Tests/OpenTween.Tests.csproj
OpenTween.Tests/Resources/Responses/UserTweetsAndReplies_SimpleTweet.json [moved from OpenTween.Tests/Resources/Responses/UserTweets_SimpleTweet.json with 99% similarity]
OpenTween/Api/GraphQL/UserTweetsAndRepliesRequest.cs [moved from OpenTween/Api/GraphQL/UserTweetsRequest.cs with 69% similarity]
OpenTween/Twitter.cs

@@ -31,12 +31,12 @@ using Xunit;
 
 namespace OpenTween.Api.GraphQL
 {
-    public class UserTweetsRequestTest
+    public class UserTweetsAndRepliesRequestTest
     {
         [Fact]
         public async Task Send_Test()
         {
-            using var responseStream = File.OpenRead("Resources/Responses/UserTweets_SimpleTweet.json");
+            using var responseStream = File.OpenRead("Resources/Responses/UserTweetsAndReplies_SimpleTweet.json");
 
             var mock = new Mock<IApiConnection>();
             mock.Setup(x =>
@@ -44,15 +44,14 @@ namespace OpenTween.Api.GraphQL
                 )
                 .Callback<Uri, IDictionary<string, string>>((url, param) =>
                 {
-                    Assert.Equal(new("https://twitter.com/i/api/graphql/2GIWTr7XwadIixZDtyXd4A/UserTweets"), url);
-                    Assert.Equal(3, param.Count);
-                    Assert.Equal("""{"userId":"40480664","count":20,"includePromotedContent":true,"withQuickPromoteEligibilityTweetFields":true,"withVoice":true,"withV2Timeline":true}""", param["variables"]);
+                    Assert.Equal(new("https://twitter.com/i/api/graphql/YlkSUg0mRBx7-EkxCvc-bw/UserTweetsAndReplies"), url);
+                    Assert.Equal(2, param.Count);
+                    Assert.Equal("""{"userId":"40480664","count":20,"includePromotedContent":true,"withCommunity":true,"withVoice":true,"withV2Timeline":true}""", param["variables"]);
                     Assert.True(param.ContainsKey("features"));
-                    Assert.True(param.ContainsKey("fieldToggles"));
                 })
                 .ReturnsAsync(responseStream);
 
-            var request = new UserTweetsRequest(userId: "40480664")
+            var request = new UserTweetsAndRepliesRequest(userId: "40480664")
             {
                 Count = 20,
             };
@@ -67,7 +66,7 @@ namespace OpenTween.Api.GraphQL
         [Fact]
         public async Task Send_RequestCursor_Test()
         {
-            using var responseStream = File.OpenRead("Resources/Responses/UserTweets_SimpleTweet.json");
+            using var responseStream = File.OpenRead("Resources/Responses/UserTweetsAndReplies_SimpleTweet.json");
 
             var mock = new Mock<IApiConnection>();
             mock.Setup(x =>
@@ -75,15 +74,14 @@ namespace OpenTween.Api.GraphQL
                 )
                 .Callback<Uri, IDictionary<string, string>>((url, param) =>
                 {
-                    Assert.Equal(new("https://twitter.com/i/api/graphql/2GIWTr7XwadIixZDtyXd4A/UserTweets"), url);
-                    Assert.Equal(3, param.Count);
-                    Assert.Equal("""{"userId":"40480664","count":20,"includePromotedContent":true,"withQuickPromoteEligibilityTweetFields":true,"withVoice":true,"withV2Timeline":true,"cursor":"aaa"}""", param["variables"]);
+                    Assert.Equal(new("https://twitter.com/i/api/graphql/YlkSUg0mRBx7-EkxCvc-bw/UserTweetsAndReplies"), url);
+                    Assert.Equal(2, param.Count);
+                    Assert.Equal("""{"userId":"40480664","count":20,"includePromotedContent":true,"withCommunity":true,"withVoice":true,"withV2Timeline":true,"cursor":"aaa"}""", param["variables"]);
                     Assert.True(param.ContainsKey("features"));
-                    Assert.True(param.ContainsKey("fieldToggles"));
                 })
                 .ReturnsAsync(responseStream);
 
-            var request = new UserTweetsRequest(userId: "40480664")
+            var request = new UserTweetsAndRepliesRequest(userId: "40480664")
             {
                 Count = 20,
                 Cursor = "aaa",
index bdf93ee..e4472ea 100644 (file)
     <None Update="Resources\Responses\UserByScreenName.json">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </None>
-    <None Update="Resources\Responses\UserTweets_SimpleTweet.json">
+    <None Update="Resources\Responses\UserTweetsAndReplies_SimpleTweet.json">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </None>
   </ItemGroup>
             ],
             "metadata": {
               "scribeConfig": {
-                "page": "profileBest"
+                "page": "profileAll"
               }
             }
           }
@@ -35,9 +35,9 @@ using OpenTween.Connection;
 
 namespace OpenTween.Api.GraphQL
 {
-    public class UserTweetsRequest
+    public class UserTweetsAndRepliesRequest
     {
-        private static readonly Uri EndpointUri = new("https://twitter.com/i/api/graphql/2GIWTr7XwadIixZDtyXd4A/UserTweets");
+        private static readonly Uri EndpointUri = new("https://twitter.com/i/api/graphql/YlkSUg0mRBx7-EkxCvc-bw/UserTweetsAndReplies");
 
         public string UserId { get; set; }
 
@@ -45,7 +45,7 @@ namespace OpenTween.Api.GraphQL
 
         public string? Cursor { get; set; }
 
-        public UserTweetsRequest(string userId)
+        public UserTweetsAndRepliesRequest(string userId)
             => this.UserId = userId;
 
         public Dictionary<string, string> CreateParameters()
@@ -54,18 +54,15 @@ namespace OpenTween.Api.GraphQL
             {
                 ["variables"] = "{" +
                     $@"""userId"":""{JsonUtils.EscapeJsonString(this.UserId)}""," +
-                    $@"""count"":20," +
+                    $@"""count"":{this.Count}," +
                     $@"""includePromotedContent"":true," +
-                    $@"""withQuickPromoteEligibilityTweetFields"":true," +
+                    $@"""withCommunity"":true," +
                     $@"""withVoice"":true," +
                     $@"""withV2Timeline"":true" +
                     (this.Cursor != null ? $@",""cursor"":""{JsonUtils.EscapeJsonString(this.Cursor)}""" : "") +
                     "}",
                 ["features"] = """
-                    {"rweb_lists_timeline_redesign_enabled":true,"responsive_web_graphql_exclude_directive_enabled":true,"verified_phone_label_enabled":false,"creator_subscriptions_tweet_preview_api_enabled":true,"responsive_web_graphql_timeline_navigation_enabled":true,"responsive_web_graphql_skip_user_profile_image_extensions_enabled":false,"tweetypie_unmention_optimization_enabled":true,"responsive_web_edit_tweet_api_enabled":true,"graphql_is_translatable_rweb_tweet_is_translatable_enabled":true,"view_counts_everywhere_api_enabled":true,"longform_notetweets_consumption_enabled":true,"responsive_web_twitter_article_tweet_consumption_enabled":false,"tweet_awards_web_tipping_enabled":false,"freedom_of_speech_not_reach_fetch_enabled":true,"standardized_nudges_misinfo":true,"tweet_with_visibility_results_prefer_gql_limited_actions_policy_enabled":true,"longform_notetweets_rich_text_read_enabled":true,"longform_notetweets_inline_media_enabled":true,"responsive_web_media_download_video_enabled":false,"responsive_web_enhance_cards_enabled":false}
-                    """,
-                ["fieldToggles"] = """
-                    {"withAuxiliaryUserLabels":false,"withArticleRichContentState":false}
+                    {"responsive_web_graphql_exclude_directive_enabled":true,"verified_phone_label_enabled":false,"responsive_web_home_pinned_timelines_enabled":true,"creator_subscriptions_tweet_preview_api_enabled":true,"responsive_web_graphql_timeline_navigation_enabled":true,"responsive_web_graphql_skip_user_profile_image_extensions_enabled":false,"c9s_tweet_anatomy_moderator_badge_enabled":true,"tweetypie_unmention_optimization_enabled":true,"responsive_web_edit_tweet_api_enabled":true,"graphql_is_translatable_rweb_tweet_is_translatable_enabled":true,"view_counts_everywhere_api_enabled":true,"longform_notetweets_consumption_enabled":true,"responsive_web_twitter_article_tweet_consumption_enabled":false,"tweet_awards_web_tipping_enabled":false,"freedom_of_speech_not_reach_fetch_enabled":true,"standardized_nudges_misinfo":true,"tweet_with_visibility_results_prefer_gql_limited_actions_policy_enabled":true,"longform_notetweets_rich_text_read_enabled":true,"longform_notetweets_inline_media_enabled":true,"responsive_web_media_download_video_enabled":false,"responsive_web_enhance_cards_enabled":false}
                     """,
             };
         }
index 81b03df..b9a7d3a 100644 (file)
@@ -679,7 +679,7 @@ namespace OpenTween
                     tab.UserId = user.IdStr;
                 }
 
-                var request = new UserTweetsRequest(userId)
+                var request = new UserTweetsAndRepliesRequest(userId)
                 {
                     Count = count,
                     Cursor = more ? tab.CursorBottom : null,
@@ -690,6 +690,7 @@ namespace OpenTween
                 statuses = response.Tweets
                     .Where(x => !x.IsTombstone)
                     .Select(x => x.ToTwitterStatus())
+                    .Where(x => x.User.IdStr == userId) // リプライツリーに含まれる他ユーザーのツイートを除外
                     .ToArray();
 
                 tab.CursorBottom = response.CursorBottom;