var response = await request.Send(mock.Object);
Assert.Single(response.Tweets);
- Assert.Equal("DAABCgABGENe-W5AJxEKAAIWeWboXhcQAAgAAwAAAAEAAA", response.CursorTop);
- Assert.Equal("DAABCgABGENe-W4__5oKAAIWK_5v3BcQAAgAAwAAAAIAAA", response.CursorBottom);
+ Assert.Equal("DAABCgABGENe-W5AJxEKAAIWeWboXhcQAAgAAwAAAAEAAA", response.CursorTop?.Value);
+ Assert.Equal("DAABCgABGENe-W4__5oKAAIWK_5v3BcQAAgAAwAAAAIAAA", response.CursorBottom?.Value);
mock.VerifyAll();
}
var request = new HomeLatestTimelineRequest
{
Count = 20,
- Cursor = "aaa",
+ Cursor = new("aaa"),
};
await request.Send(mock.Object);
var response = await request.Send(mock.Object);
Assert.Single(response.Tweets);
- Assert.Equal("DAAHCgABGEs2Ve9AAAELAAIAAAATMTc4OTA3OTU2MDM5NDcxNTMyMggAAwAAAAEAAA", response.CursorTop);
- Assert.Equal("DAAHCgABGEs2Ve8___8LAAIAAAATMTc4OTA3OTU2MDM5NDcxNTMyMggAAwAAAAIAAA", response.CursorBottom);
+ Assert.Equal("DAAHCgABGEs2Ve9AAAELAAIAAAATMTc4OTA3OTU2MDM5NDcxNTMyMggAAwAAAAEAAA", response.CursorTop?.Value);
+ Assert.Equal("DAAHCgABGEs2Ve8___8LAAIAAAATMTc4OTA3OTU2MDM5NDcxNTMyMggAAwAAAAIAAA", response.CursorBottom?.Value);
mock.VerifyAll();
}
{
UserId = "12345",
Count = 20,
- Cursor = "aaa",
+ Cursor = new("aaa"),
};
await request.Send(mock.Object);
var response = await request.Send(mock.Object);
Assert.Single(response.Tweets);
- Assert.Equal("DAABCgABF0HfRMjAJxEKAAIWes8rE1oQAAgAAwAAAAEAAA", response.CursorTop);
- Assert.Equal("DAABCgABF0HfRMi__7QKAAIVAxUYmFWQAwgAAwAAAAIAAA", response.CursorBottom);
+ Assert.Equal("DAABCgABF0HfRMjAJxEKAAIWes8rE1oQAAgAAwAAAAEAAA", response.CursorTop?.Value);
+ Assert.Equal("DAABCgABF0HfRMi__7QKAAIVAxUYmFWQAwgAAwAAAAIAAA", response.CursorBottom?.Value);
mock.VerifyAll();
}
var request = new ListLatestTweetsTimelineRequest(listId: "1675863884757110790")
{
Count = 20,
- Cursor = "aaa",
+ Cursor = new("aaa"),
};
await request.Send(mock.Object);
var response = await request.Send(mock.Object);
Assert.Single(response.Tweets);
- Assert.Equal("DAADDAABCgABFnlh4hraMAYKAAIOTm0DEhTAAQAIAAIAAAABCAADAAAAAAgABAAAAAAKAAUX8j3ezIAnEAoABhfyPd7Mf9jwAAA", response.CursorTop);
- Assert.Equal("DAADDAABCgABFnlh4hraMAYKAAIOTm0DEhTAAQAIAAIAAAACCAADAAAAAAgABAAAAAAKAAUX8j3ezIAnEAoABhfyPd7Mf9jwAAA", response.CursorBottom);
+ Assert.Equal("DAADDAABCgABFnlh4hraMAYKAAIOTm0DEhTAAQAIAAIAAAABCAADAAAAAAgABAAAAAAKAAUX8j3ezIAnEAoABhfyPd7Mf9jwAAA", response.CursorTop?.Value);
+ Assert.Equal("DAADDAABCgABFnlh4hraMAYKAAIOTm0DEhTAAQAIAAIAAAACCAADAAAAAAgABAAAAAAKAAUX8j3ezIAnEAoABhfyPd7Mf9jwAAA", response.CursorBottom?.Value);
mock.VerifyAll();
}
var response = await request.Send(mock.Object);
Assert.Empty(response.Tweets);
- Assert.Equal("DAADDAABCgABFnlh4hraMAYKAAIOTm0DEhTAAQAIAAIAAAABCAADAAAAAQgABAAAAAAKAAUX8j3ezIBOIAoABhfyPd7Mf9jwAAA", response.CursorTop);
- Assert.Equal("DAADDAABCgABFnlh4hraMAYKAAIOTm0DEhTAAQAIAAIAAAACCAADAAAAAQgABAAAAAAKAAUX8j3ezIBOIAoABhfyPd7Mf9jwAAA", response.CursorBottom);
+ Assert.Equal("DAADDAABCgABFnlh4hraMAYKAAIOTm0DEhTAAQAIAAIAAAABCAADAAAAAQgABAAAAAAKAAUX8j3ezIBOIAoABhfyPd7Mf9jwAAA", response.CursorTop?.Value);
+ Assert.Equal("DAADDAABCgABFnlh4hraMAYKAAIOTm0DEhTAAQAIAAIAAAACCAADAAAAAQgABAAAAAAKAAUX8j3ezIBOIAoABhfyPd7Mf9jwAAA", response.CursorBottom?.Value);
mock.VerifyAll();
}
var request = new SearchTimelineRequest(rawQuery: "#OpenTween")
{
Count = 20,
- Cursor = "aaa",
+ Cursor = new("aaa"),
};
await request.Send(mock.Object);
{
using var apiResponse = await TestUtils.CreateApiResponse("Resources/Responses/SearchTimeline_SimpleTweet.json");
var tweets = TimelineTweet.ExtractTimelineTweets(await apiResponse.ReadAsJsonXml());
- var timelineResponse = new TimelineResponse(tweets, "", "");
+ var timelineResponse = new TimelineResponse(tweets, new(""), new(""));
var statuses = timelineResponse.ToTwitterStatuses();
Assert.Single(statuses);
var response = await request.Send(mock.Object);
Assert.Single(response.Tweets);
- Assert.Equal("DAABCgABF_tTnZvAJxEKAAIWes8rE1oQAAgAAwAAAAEAAA", response.CursorTop);
- Assert.Equal("DAABCgABF_tTnZu__-0KAAIWZa6KTRoAAwgAAwAAAAIAAA", response.CursorBottom);
+ Assert.Equal("DAABCgABF_tTnZvAJxEKAAIWes8rE1oQAAgAAwAAAAEAAA", response.CursorTop?.Value);
+ Assert.Equal("DAABCgABF_tTnZu__-0KAAIWZa6KTRoAAwgAAwAAAAIAAA", response.CursorBottom?.Value);
mock.VerifyAll();
}
var request = new UserTweetsAndRepliesRequest(userId: "40480664")
{
Count = 20,
- Cursor = "aaa",
+ Cursor = new("aaa"),
};
await request.Send(mock.Object);
Assert.Equal("1748671085438988794", status.IdStr);
Assert.Equal("40480664", status.User.IdStr);
- Assert.Equal("DAABDAABCgABAAAAAC4B0ZQIAAIAAAACCAADm5udsQgABCaolIMACwACAAAAC0FZMG1xVjB6VEZjAAA", response.CursorTop);
- Assert.Equal("DAACDAABCgABAAAAAC4B0ZQIAAIAAAACCAADm5udsQgABCaolIMACwACAAAAC0FZMG1xVjB6VEZjAAA", response.CursorBottom);
+ Assert.Equal("DAABDAABCgABAAAAAC4B0ZQIAAIAAAACCAADm5udsQgABCaolIMACwACAAAAC0FZMG1xVjB6VEZjAAA", response.CursorTop?.Value);
+ Assert.Equal("DAACDAABCgABAAAAAC4B0ZQIAAIAAAACCAADm5udsQgABCaolIMACwACAAAAC0FZMG1xVjB6VEZjAAA", response.CursorBottom?.Value);
mock.VerifyAll();
}
var request = new NotificationsMentionsRequest()
{
Count = 20,
- Cursor = "aaa",
+ Cursor = new("aaa"),
};
await request.Send(mock.Object);
using System.Collections.Generic;
using System.Threading.Tasks;
using OpenTween.Connection;
+using OpenTween.Models;
namespace OpenTween.Api.GraphQL
{
public int Count { get; set; } = 20;
- public string? Cursor { get; set; }
+ public TwitterGraphqlCursor? Cursor { get; set; }
public Dictionary<string, string> CreateParameters()
{
+ var cursorStr = this.Cursor?.Value;
+
return new()
{
["variables"] = "{" +
$@"""includePromotedContent"":true,""latestControlAvailable"":true,""requestContext"":""launch""," +
$@"""count"":{this.Count}" +
- (this.Cursor != null ? $@",""cursor"":""{JsonUtils.EscapeJsonString(this.Cursor)}""" : "") +
+ (cursorStr != null ? $@",""cursor"":""{JsonUtils.EscapeJsonString(cursorStr)}""" : "") +
"}",
["features"] = """
{"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,"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":true,"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,"rweb_video_timestamps_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}
public int Count { get; set; } = 20;
- public string? Cursor { get; set; }
+ public TwitterGraphqlCursor? Cursor { get; set; }
public Dictionary<string, string> CreateParameters()
{
+ var cursorStr = this.Cursor?.Value;
+
return new()
{
["variables"] = "{" +
$@"""withBirdwatchNotes"":false," +
$@"""withVoice"":true," +
$@"""withV2Timeline"":true" +
- (this.Cursor != null ? $@",""cursor"":""{JsonUtils.EscapeJsonString(this.Cursor)}""" : "") +
+ (cursorStr != null ? $@",""cursor"":""{JsonUtils.EscapeJsonString(cursorStr)}""" : "") +
"}",
["features"] = """
{"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,"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":true,"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,"rweb_video_timestamps_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}
public int Count { get; set; } = 20;
- public string? Cursor { get; set; }
+ public TwitterGraphqlCursor? Cursor { get; set; }
public ListLatestTweetsTimelineRequest(string listId)
=> this.ListId = listId;
public Dictionary<string, string> CreateParameters()
{
+ var cursorStr = this.Cursor?.Value;
+
return new()
{
["variables"] = "{" +
$@"""listId"":""{JsonUtils.EscapeJsonString(this.ListId)}""," +
$@"""count"":{this.Count}" +
- (this.Cursor != null ? $@",""cursor"":""{JsonUtils.EscapeJsonString(this.Cursor)}""" : "") +
+ (cursorStr != null ? $@",""cursor"":""{JsonUtils.EscapeJsonString(cursorStr)}""" : "") +
"}",
["features"] = "{" +
@"""rweb_lists_timeline_redesign_enabled"":true," +
public int Count { get; set; } = 20;
- public string? Cursor { get; set; }
+ public TwitterGraphqlCursor? Cursor { get; set; }
public SearchTimelineRequest(string rawQuery)
=> this.RawQuery = rawQuery;
public Dictionary<string, string> CreateParameters()
{
+ var cursorStr = this.Cursor?.Value;
+
return new()
{
["variables"] = "{" +
$@"""rawQuery"":""{JsonUtils.EscapeJsonString(this.RawQuery)}""," +
$@"""count"":{this.Count}," +
$@"""product"":""Latest""" +
- (this.Cursor != null ? $@",""cursor"":""{JsonUtils.EscapeJsonString(this.Cursor)}""" : "") +
+ (cursorStr != null ? $@",""cursor"":""{JsonUtils.EscapeJsonString(cursorStr)}""" : "") +
"}",
["features"] = "{" +
@"""responsive_web_graphql_exclude_directive_enabled"":true," +
{
public record TimelineResponse(
TimelineTweet[] Tweets,
- string? CursorTop,
- string? CursorBottom
+ TwitterGraphqlCursor? CursorTop,
+ TwitterGraphqlCursor? CursorBottom
)
{
public TwitterStatus[] ToTwitterStatuses()
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;
+ var cursorTopStr = rootElm.XPathSelectElement("//content[__typename[text()='TimelineTimelineCursor']][cursorType[text()='Top']]/value")?.Value;
+ var cursorTop = cursorTopStr != null
+ ? new TwitterGraphqlCursor(cursorTopStr)
+ : null;
+
+ var cursorBottomStr = rootElm.XPathSelectElement("//content[__typename[text()='TimelineTimelineCursor']][cursorType[text()='Bottom']]/value")?.Value;
+ var cursorBottom = cursorBottomStr != null
+ ? new TwitterGraphqlCursor(cursorBottomStr)
+ : null;
return new(tweets, cursorTop, cursorBottom);
}
--- /dev/null
+// OpenTween - Client of Twitter
+// Copyright (c) 2024 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
+
+namespace OpenTween.Api.GraphQL
+{
+ public record TwitterGraphqlCursor(
+ string Value
+ );
+}
public int Count { get; set; } = 20;
- public string? Cursor { get; set; }
+ public TwitterGraphqlCursor? Cursor { get; set; }
public UserTweetsAndRepliesRequest(string userId)
=> this.UserId = userId;
public Dictionary<string, string> CreateParameters()
{
+ var cursorStr = this.Cursor?.Value;
+
return new()
{
["variables"] = "{" +
$@"""withCommunity"":true," +
$@"""withVoice"":true," +
$@"""withV2Timeline"":true" +
- (this.Cursor != null ? $@",""cursor"":""{JsonUtils.EscapeJsonString(this.Cursor)}""" : "") +
+ (cursorStr != null ? $@",""cursor"":""{JsonUtils.EscapeJsonString(cursorStr)}""" : "") +
"}",
["features"] = """
{"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}
public int Count { get; set; } = 100;
- public string? Cursor { get; set; }
+ public TwitterGraphqlCursor? Cursor { get; set; }
public Dictionary<string, string> CreateParameters()
{
["count"] = this.Count.ToString(CultureInfo.InvariantCulture),
};
- if (!MyCommon.IsNullOrEmpty(this.Cursor))
- param["cursor"] = this.Cursor;
+ if (this.Cursor != null)
+ param["cursor"] = this.Cursor.Value;
return param;
}
statuses.Add(tweet);
}
- var cursorTop = rootElm.XPathSelectElement("//content/operation/cursor[cursorType[text()='Top']]/value")?.Value;
- var cursorBottom = rootElm.XPathSelectElement("//content/operation/cursor[cursorType[text()='Bottom']]/value")?.Value;
+ var cursorTopStr = rootElm.XPathSelectElement("//content/operation/cursor[cursorType[text()='Top']]/value")?.Value;
+ var cursorTop = cursorTopStr != null
+ ? new TwitterGraphqlCursor(cursorTopStr)
+ : null;
+
+ var cursorBottomStr = rootElm.XPathSelectElement("//content/operation/cursor[cursorType[text()='Bottom']]/value")?.Value;
+ var cursorBottom = cursorBottomStr != null
+ ? new TwitterGraphqlCursor(cursorBottomStr)
+ : null;
return new(statuses.ToArray(), cursorTop, cursorBottom);
}
public readonly record struct NotificationsResponse(
TwitterStatus[] Statuses,
- string? CursorTop,
- string? CursorBottom
+ TwitterGraphqlCursor? CursorTop,
+ TwitterGraphqlCursor? CursorBottom
);
}
}
using System.Linq;
using System.Text;
using System.Threading.Tasks;
+using OpenTween.Api.GraphQL;
using OpenTween.Setting;
using OpenTween.SocialProtocol;
using OpenTween.SocialProtocol.Twitter;
public PostId? OldestId { get; set; }
- public string? CursorTop { get; set; }
+ public TwitterGraphqlCursor? CursorTop { get; set; }
- public string? CursorBottom { get; set; }
+ public TwitterGraphqlCursor? CursorBottom { get; set; }
public FavoritesTabModel()
: this(MyCommon.DEFAULTTAB.FAV)
using System.Text;
using System.Threading;
using System.Threading.Tasks;
+using OpenTween.Api.GraphQL;
using OpenTween.Setting;
using OpenTween.SocialProtocol;
using OpenTween.SocialProtocol.Twitter;
public PostId? OldestId { get; set; }
- public string? CursorTop { get; set; }
+ public TwitterGraphqlCursor? CursorTop { get; set; }
- public string? CursorBottom { get; set; }
+ public TwitterGraphqlCursor? CursorBottom { get; set; }
public int TweetsPerHour => this.tweetsPerHour;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
+using OpenTween.Api.GraphQL;
using OpenTween.Setting;
using OpenTween.SocialProtocol;
using OpenTween.SocialProtocol.Twitter;
public PostId? OldestId { get; set; }
- public string? CursorTop { get; set; }
+ public TwitterGraphqlCursor? CursorTop { get; set; }
- public string? CursorBottom { get; set; }
+ public TwitterGraphqlCursor? CursorBottom { get; set; }
public ListTimelineTabModel(string tabName, ListElement list)
: base(tabName)
using System.Linq;
using System.Text;
using System.Threading.Tasks;
+using OpenTween.Api.GraphQL;
using OpenTween.Setting;
using OpenTween.SocialProtocol;
using OpenTween.SocialProtocol.Twitter;
public PostId? OldestId { get; set; }
- public string? CursorTop { get; set; }
+ public TwitterGraphqlCursor? CursorTop { get; set; }
- public string? CursorBottom { get; set; }
+ public TwitterGraphqlCursor? CursorBottom { get; set; }
public MentionsTabModel()
: this(MyCommon.DEFAULTTAB.REPLY)
using System.Linq;
using System.Text;
using System.Threading.Tasks;
+using OpenTween.Api.GraphQL;
using OpenTween.Setting;
using OpenTween.SocialProtocol;
using OpenTween.SocialProtocol.Twitter;
public PostId? SinceId { get; set; }
- public string? CursorTop { get; set; }
+ public TwitterGraphqlCursor? CursorTop { get; set; }
- public string? CursorBottom { get; set; }
+ public TwitterGraphqlCursor? CursorBottom { get; set; }
public string SearchWords
{
using System.Linq;
using System.Text;
using System.Threading.Tasks;
+using OpenTween.Api.GraphQL;
using OpenTween.Setting;
using OpenTween.SocialProtocol;
using OpenTween.SocialProtocol.Twitter;
public PostId? OldestId { get; set; }
- public string? CursorTop { get; set; }
+ public TwitterGraphqlCursor? CursorTop { get; set; }
- public string? CursorBottom { get; set; }
+ public TwitterGraphqlCursor? CursorBottom { get; set; }
public UserTimelineTabModel(string tabName, string screenName)
: base(tabName)