* FIX: ツイート検索のキーワードを後から変更すると検索結果が表示されない不具合を修正
* FIX: Cookie使用時にステータスバーにRecentタブのレートリミットが表示されない不具合を修正
* FIX: 取得したツイートの中身が空だった場合のエラー処理を改善
+ * FIX: タイムラインの取得結果にレートリミットに関するメッセージが含まれていた場合はエラーとして扱う
==== Ver 3.12.0(2024/01/20)
* NEW: graphqlエンドポイントを使用したホームタイムラインの取得に対応
using System.Threading.Tasks;
using Moq;
+using OpenTween.Api.DataModel;
using OpenTween.Connection;
using Xunit;
await request.Send(mock.Object);
mock.VerifyAll();
}
+
+ [Fact]
+ public async Task Send_RateLimitTest()
+ {
+ using var apiResponse = await TestUtils.CreateApiResponse("Resources/Responses/HomeLatestTimeline_RateLimit.json");
+
+ var mock = new Mock<IApiConnection>();
+ mock.Setup(x =>
+ x.SendAsync(It.IsAny<IHttpRequest>())
+ )
+ .Callback<IHttpRequest>(x =>
+ {
+ var request = Assert.IsType<GetRequest>(x);
+ Assert.Equal(new("https://twitter.com/i/api/graphql/lAKISuk_McyDUlhS2Zmv4A/HomeLatestTimeline"), request.RequestUri);
+ })
+ .ReturnsAsync(apiResponse);
+
+ var request = new HomeLatestTimelineRequest();
+
+ var ex = await Assert.ThrowsAsync<TwitterApiException>(
+ () => request.Send(mock.Object)
+ );
+ var errorItem = Assert.Single(ex.Errors);
+ Assert.Equal(TwitterErrorCode.RateLimit, errorItem.Code);
+
+ mock.VerifyAll();
+ }
}
}
--- /dev/null
+{
+ "data": {
+ "home": {
+ "home_timeline_urt": {
+ "instructions": [
+ {
+ "type": "TimelineAddEntries",
+ "entries": [
+ {
+ "entryId": "messageprompt-1682783911",
+ "sortIndex": "1749988213990096896",
+ "content": {
+ "entryType": "TimelineTimelineItem",
+ "__typename": "TimelineTimelineItem",
+ "itemContent": {
+ "itemType": "TimelineMessagePrompt",
+ "__typename": "TimelineMessagePrompt",
+ "content": {
+ "contentType": "TimelineInlinePrompt",
+ "headerText": "Unlock more posts by subscribing",
+ "bodyText": "You have reached the limit for seeing posts today. Subscribe to see more posts every day.",
+ "primaryButtonAction": {
+ "text": "Subscribe",
+ "action": {
+ "url": "https://twitter.com/i/twitter_blue_sign_up",
+ "dismissOnClick": false
+ }
+ }
+ }
+ },
+ "clientEventInfo": {
+ "component": "verified_prompt",
+ "element": "message"
+ }
+ }
+ },
+ {
+ "entryId": "cursor-top-1749988213990096897",
+ "sortIndex": "1749988213990096897",
+ "content": {
+ "entryType": "TimelineTimelineCursor",
+ "__typename": "TimelineTimelineCursor",
+ "value": "DAABCgABGEk1AkAAJxEKAAIYSJVb2ltBzQgAAwAAAAEAAA",
+ "cursorType": "Top"
+ }
+ },
+ {
+ "entryId": "cursor-bottom-1749988213990096895",
+ "sortIndex": "1749988213990096895",
+ "content": {
+ "entryType": "TimelineTimelineCursor",
+ "__typename": "TimelineTimelineCursor",
+ "value": "DAABCgABGEk1Aj____0KAAIYSJVb2ltBzQgAAwAAAAIAAA",
+ "cursorType": "Bottom"
+ }
+ }
+ ]
+ }
+ ],
+ "metadata": {
+ "scribeConfig": {
+ "page": "following"
+ }
+ }
+ }
+ }
+ }
+}
using System.Xml;
using System.Xml.Linq;
using System.Xml.XPath;
+using OpenTween.Api.DataModel;
namespace OpenTween.Api.GraphQL
{
throw new WebApiException(messageText, responseJson);
}
+
+ public static void ThrowIfContainsRateLimitMessage(XElement rootElm)
+ {
+ var messageElm = rootElm.XPathSelectElement("//itemContent[itemType[text()='TimelineMessagePrompt']]");
+ if (messageElm == null)
+ return;
+
+ var bodyText = messageElm.XPathSelectElement("content/bodyText")?.Value ?? "";
+ if (bodyText.StartsWith("You have reached the limit"))
+ {
+ var error = new TwitterError
+ {
+ Errors = new[]
+ {
+ new TwitterErrorItem { Code = TwitterErrorCode.RateLimit, Message = "" },
+ },
+ };
+ throw new TwitterApiException(0, error, "");
+ }
+ }
}
}
ErrorResponse.ThrowIfError(rootElm);
var tweets = TimelineTweet.ExtractTimelineTweets(rootElm);
+ if (tweets.Length == 0)
+ ErrorResponse.ThrowIfContainsRateLimitMessage(rootElm);
+
var cursorTop = rootElm.XPathSelectElement("//content[__typename[text()='TimelineTimelineCursor']][cursorType[text()='Top']]/value")?.Value;
var cursorBottom = rootElm.XPathSelectElement("//content[__typename[text()='TimelineTimelineCursor']][cursorType[text()='Bottom']]/value")?.Value;