OSDN Git Service

TimelineTimelineCursorを使用した過去ツイートの取得に対応
authorKimura Youichi <kim.upsilon@bucyou.net>
Fri, 7 Jul 2023 18:14:53 +0000 (03:14 +0900)
committerKimura Youichi <kim.upsilon@bucyou.net>
Fri, 7 Jul 2023 18:23:16 +0000 (03:23 +0900)
OpenTween.Tests/Api/GraphQL/ListLatestTweetsTimelineRequestTest.cs
OpenTween/Api/GraphQL/ListLatestTweetsTimelineRequest.cs
OpenTween/Api/GraphQL/TimelineResponse.cs [new file with mode: 0644]
OpenTween/Models/ListTimelineTabModel.cs
OpenTween/Resources/ChangeLog.txt
OpenTween/Twitter.cs

index c53b27b..0516c84 100644 (file)
@@ -57,10 +57,39 @@ namespace OpenTween.Api.GraphQL
                 Count = 20,
             };
 
-            var tweets = await request.Send(mock.Object).ConfigureAwait(false);
-            Assert.Single(tweets);
+            var response = await request.Send(mock.Object).ConfigureAwait(false);
+            Assert.Single(response.Tweets);
+            Assert.Equal("DAABCgABF0HfRMi__7QKAAIVAxUYmFWQAwgAAwAAAAIAAA", response.CursorBottom);
 
             mock.VerifyAll();
         }
+
+        [Fact]
+        public async Task Send_RequestCursor_Test()
+        {
+            using var responseStream = File.OpenRead("Resources/Responses/ListLatestTweetsTimeline_SimpleTweet.json");
+
+            var mock = new Mock<IApiConnection>();
+            mock.Setup(x =>
+                    x.GetStreamAsync(It.IsAny<Uri>(), It.IsAny<IDictionary<string, string>>())
+                )
+                .Callback<Uri, IDictionary<string, string>>((url, param) =>
+                {
+                    Assert.Equal(new("https://twitter.com/i/api/graphql/6ClPnsuzQJ1p7-g32GQw9Q/ListLatestTweetsTimeline"), url);
+                    Assert.Equal(2, param.Count);
+                    Assert.Equal("""{"listId":"1675863884757110790","count":20,"cursor":"aaa"}""", param["variables"]);
+                    Assert.True(param.ContainsKey("features"));
+                })
+                .ReturnsAsync(responseStream);
+
+            var request = new ListLatestTweetsTimelineRequest(listId: "1675863884757110790")
+            {
+                Count = 20,
+                Cursor = "aaa",
+            };
+
+            await request.Send(mock.Object).ConfigureAwait(false);
+            mock.VerifyAll();
+        }
     }
 }
index a44b123..676d4e6 100644 (file)
@@ -30,6 +30,7 @@ using System.Text;
 using System.Threading.Tasks;
 using System.Xml;
 using System.Xml.Linq;
+using System.Xml.XPath;
 using OpenTween.Connection;
 
 namespace OpenTween.Api.GraphQL
@@ -42,6 +43,8 @@ namespace OpenTween.Api.GraphQL
 
         public int Count { get; set; } = 20;
 
+        public string? Cursor { get; set; }
+
         public ListLatestTweetsTimelineRequest(string listId)
             => this.ListId = listId;
 
@@ -52,6 +55,7 @@ namespace OpenTween.Api.GraphQL
                 ["variables"] = "{" +
                     $@"""listId"":""{JsonUtils.EscapeJsonString(this.ListId)}""," +
                     $@"""count"":{this.Count}" +
+                    (this.Cursor != null ? $@",""cursor"":""{JsonUtils.EscapeJsonString(this.Cursor)}""" : "") +
                     "}",
                 ["features"] = "{" +
                     @"""rweb_lists_timeline_redesign_enabled"":true," +
@@ -78,7 +82,7 @@ namespace OpenTween.Api.GraphQL
             };
         }
 
-        public async Task<TimelineTweet[]> Send(IApiConnection apiConnection)
+        public async Task<TimelineResponse> Send(IApiConnection apiConnection)
         {
             var param = this.CreateParameters();
             using var stream = await apiConnection.GetStreamAsync(EndpointUri, param);
@@ -94,7 +98,10 @@ namespace OpenTween.Api.GraphQL
                 throw new WebApiException("IO Error", ex);
             }
 
-            return TimelineTweet.ExtractTimelineTweets(rootElm);
+            var tweets = TimelineTweet.ExtractTimelineTweets(rootElm);
+            var cursorBottom = rootElm.XPathSelectElement("//content[__typename[text()='TimelineTimelineCursor']][cursorType[text()='Bottom']]/value")?.Value;
+
+            return new(tweets, cursorBottom);
         }
     }
 }
diff --git a/OpenTween/Api/GraphQL/TimelineResponse.cs b/OpenTween/Api/GraphQL/TimelineResponse.cs
new file mode 100644 (file)
index 0000000..f1942c1
--- /dev/null
@@ -0,0 +1,36 @@
+// OpenTween - Client of Twitter
+// Copyright (c) 2023 kim_upsilon (@kim_upsilon) <https://upsilo.net/~upsilon/>
+// All rights reserved.
+//
+// This file is part of OpenTween.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License as published by the Free
+// Software Foundation; either version 3 of the License, or (at your option)
+// any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+// for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program. If not, see <http://www.gnu.org/licenses/>, or write to
+// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor,
+// Boston, MA 02110-1301, USA.
+
+#nullable enable
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace OpenTween.Api.GraphQL
+{
+    public record TimelineResponse(
+        TimelineTweet[] Tweets,
+        string? CursorBottom
+    );
+}
index 0b99e42..d1658a6 100644 (file)
@@ -45,6 +45,8 @@ namespace OpenTween.Models
 
         public long OldestId { get; set; } = long.MaxValue;
 
+        public string? CursorBottom { get; set; }
+
         public ListTimelineTabModel(string tabName, ListElement list)
             : base(tabName)
         {
index b4e1fef..87a01f3 100644 (file)
@@ -1,6 +1,7 @@
 更新履歴
 
 ==== Unreleased
+ * NEW: graphqlエンドポイントを使用しているタブでの「前データを取得」に対応
 
 ==== Ver 3.6.2(2023/07/07)
  * FIX: リプライ制限されたツイートのRTがリストのタイムラインに含まれているとエラーになる不具合を修正
index 8e14b30..ea9cf42 100644 (file)
@@ -750,10 +750,13 @@ namespace OpenTween
                 var request = new ListLatestTweetsTimelineRequest(tab.ListInfo.Id.ToString())
                 {
                     Count = count,
+                    Cursor = more ? tab.CursorBottom : null,
                 };
-                var timelineTweets = await request.Send(this.Api.Connection)
+                var response = await request.Send(this.Api.Connection)
                     .ConfigureAwait(false);
-                statuses = timelineTweets.Select(x => x.ToTwitterStatus()).ToArray();
+
+                statuses = response.Tweets.Select(x => x.ToTwitterStatus()).ToArray();
+                tab.CursorBottom = response.CursorBottom;
             }
             else if (more)
             {