From cd7075384afb965c9c397066737dc61e2d4d1829 Mon Sep 17 00:00:00 2001 From: bmclane Date: Wed, 14 Dec 2022 13:47:50 -0500 Subject: [PATCH 1/9] implemented NextTweetsAsync for GetTweetsFromUserId --- src/Client/TwitterClient.cs | 31 +++++++++++++++++++++++++++---- src/Response/RTweet/RTweets.cs | 12 ++++++++++++ test/TestTweet.cs | 31 ++++++++++++++++++++++--------- 3 files changed, 61 insertions(+), 13 deletions(-) create mode 100644 src/Response/RTweet/RTweets.cs diff --git a/src/Client/TwitterClient.cs b/src/Client/TwitterClient.cs index abc6d46..c1bedaf 100644 --- a/src/Client/TwitterClient.cs +++ b/src/Client/TwitterClient.cs @@ -214,12 +214,35 @@ public async Task GetTweetsAsync(string[] ids, TweetSearchOptions optio /// Get the latest tweets of an user /// /// Username of the user you want the tweets of - public async Task GetTweetsFromUserIdAsync(string userId, TweetSearchOptions options = null) + public async Task GetTweetsFromUserIdAsync(string userId, TweetSearchOptions options = null) { - options ??= new(); - var res = await _httpClient.GetAsync(_baseUrl + "users/" + HttpUtility.HtmlEncode(userId) + "/tweets?" + options.Build(true)); + options ??= new(); + var query = _baseUrl + "users/" + HttpUtility.HtmlEncode(userId) + "/tweets?" + options.Build(true); + + var res = await _httpClient.GetAsync(query); BuildRateLimit(res.Headers, Endpoint.UserTweetTimeline); - return ParseArrayData(await res.Content.ReadAsStringAsync()); + var data = ParseData(await res.Content.ReadAsStringAsync()); + return new() + { + Tweets = data.Data, + NextAsync = data.Meta.NextToken == null ? null : async () => await NextTweetsAsync(query, data.Meta.NextToken, Endpoint.UserTweetTimeline) + }; + } + + /// + /// General method for getting the next page of tweets + /// + /// + private async Task NextTweetsAsync(string baseQuery, string token, Endpoint endpoint) + { + var res = await _httpClient.GetAsync(baseQuery + (!baseQuery.EndsWith("?") ? "&" : "") + "pagination_token=" + token); + var data = ParseData(await res.Content.ReadAsStringAsync()); + BuildRateLimit(res.Headers, endpoint); + return new() + { + Tweets = data.Data, + NextAsync = data.Meta.NextToken == null ? null : async () => await NextTweetsAsync(baseQuery, data.Meta.NextToken, endpoint) + }; } /// diff --git a/src/Response/RTweet/RTweets.cs b/src/Response/RTweet/RTweets.cs new file mode 100644 index 0000000..37fa1a4 --- /dev/null +++ b/src/Response/RTweet/RTweets.cs @@ -0,0 +1,12 @@ +using System; +using System.Threading.Tasks; + +namespace TwitterSharp.Response.RTweet +{ + public class RTweets + { + public Tweet[] Tweets { init; get; } + + public Func> NextAsync { init; get; } + } +} diff --git a/test/TestTweet.cs b/test/TestTweet.cs index 07a0fe6..f802946 100644 --- a/test/TestTweet.cs +++ b/test/TestTweet.cs @@ -93,8 +93,8 @@ public async Task GetTweetsFromUserIdAsync() { var client = new TwitterClient(Environment.GetEnvironmentVariable("TWITTER_TOKEN")); var answer = await client.GetTweetsFromUserIdAsync("1109748792721432577"); - Assert.AreEqual(10, answer.Length); - Assert.IsNull(answer[0].Author); + Assert.AreEqual(10, answer.Tweets.Length); + Assert.IsNull(answer.Tweets[0].Author); } [TestMethod] @@ -105,7 +105,7 @@ public async Task GetTweetsFromUserIdWithSinceIdAsync() { SinceId = "1410551383795781634" }); - Assert.AreEqual(2, answer.Length); + Assert.AreEqual(2, answer.Tweets.Length); } [TestMethod] @@ -116,7 +116,7 @@ public async Task GetTweetsFromUserIdWithStartTimeAsync() { StartTime = new DateTime(2021, 7, 1, 12, 50, 0) }); - Assert.AreEqual(1, answer.Length); + Assert.AreEqual(1, answer.Tweets.Length); } [TestMethod] @@ -128,8 +128,8 @@ public async Task GetTweetsFromUserIdWithArgumentsAsync() // Issue #2 TweetOptions = new[] { TweetOption.Attachments }, UserOptions = Array.Empty() }); - Assert.AreEqual(10, answer.Length); - Assert.IsNotNull(answer[0].Author); + Assert.AreEqual(10, answer.Tweets.Length); + Assert.IsNotNull(answer.Tweets[0].Author); } [TestMethod] @@ -140,8 +140,8 @@ public async Task GetTweetsFromUserIdWithAuthorAsync() { UserOptions = Array.Empty() }); - Assert.IsTrue(answer.Length == 10); - foreach (var t in answer) + Assert.IsTrue(answer.Tweets.Length == 10); + foreach (var t in answer.Tweets) { Assert.IsNotNull(t.Author); Assert.AreEqual("inugamikorone", t.Author.Username); @@ -156,7 +156,20 @@ public async Task GetTweetsFromUserIdWithModifiedLimitAsync() { Limit = 100 }); - Assert.IsTrue(answer.Length == 100); + Assert.IsTrue(answer.Tweets.Length == 100); + } + + [TestMethod] + public async Task GetTweetsFromUserIdWithNextToken() + { + var client = new TwitterClient(Environment.GetEnvironmentVariable("TWITTER_TOKEN")); + var answer = await client.GetTweetsFromUserIdAsync("1109748792721432577"); + + Assert.IsTrue(answer.Tweets.Length == 10); + + var nextAnswer = await answer.NextAsync(); + + Assert.IsTrue(nextAnswer.Tweets.Length == 10); } [TestMethod] From 60489ca4f7f50d71b54f502f2343841df8017b49 Mon Sep 17 00:00:00 2001 From: bmclane Date: Wed, 14 Dec 2022 15:52:31 -0500 Subject: [PATCH 2/9] refactored ParseArrayData to fix internal author parsing on tweets --- src/Client/TwitterClient.cs | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/Client/TwitterClient.cs b/src/Client/TwitterClient.cs index c1bedaf..74026f8 100644 --- a/src/Client/TwitterClient.cs +++ b/src/Client/TwitterClient.cs @@ -129,7 +129,7 @@ private static void InternalIncludesParse(Answer answer) } } - private T[] ParseArrayData(string json) + private Answer ParseArrayData(string json) { var answer = JsonSerializer.Deserialize>(json, _jsonOptions); if (answer.Detail != null || answer.Errors != null) @@ -138,10 +138,11 @@ private T[] ParseArrayData(string json) } if (answer.Data == null) { - return Array.Empty(); + answer.Data = Array.Empty(); + return answer; } InternalIncludesParse(answer); - return answer.Data; + return answer; } private Answer ParseData(string json) @@ -207,7 +208,7 @@ public async Task GetTweetsAsync(string[] ids, TweetSearchOptions optio options ??= new(); var res = await _httpClient.GetAsync(_baseUrl + "tweets?ids=" + string.Join(",", ids.Select(x => HttpUtility.UrlEncode(x))) + "&" + options.Build(true)); BuildRateLimit(res.Headers, Endpoint.GetTweetsByIds); - return ParseArrayData(await res.Content.ReadAsStringAsync()); + return ParseArrayData(await res.Content.ReadAsStringAsync()).Data; } /// @@ -221,7 +222,7 @@ public async Task GetTweetsFromUserIdAsync(string userId, TweetSearchOp var res = await _httpClient.GetAsync(query); BuildRateLimit(res.Headers, Endpoint.UserTweetTimeline); - var data = ParseData(await res.Content.ReadAsStringAsync()); + var data = ParseArrayData(await res.Content.ReadAsStringAsync()); return new() { Tweets = data.Data, @@ -255,7 +256,7 @@ public async Task GetRecentTweets(Expression expression, TweetSearchOpt options ??= new(); var res = await _httpClient.GetAsync(_baseUrl + "tweets/search/recent?query=" + HttpUtility.UrlEncode(expression.ToString()) + "&" + options.Build(true)); BuildRateLimit(res.Headers, Endpoint.RecentSearch); - return ParseArrayData(await res.Content.ReadAsStringAsync()); + return ParseArrayData(await res.Content.ReadAsStringAsync()).Data; } /// @@ -269,7 +270,7 @@ public async Task GetAllTweets(Expression expression, TweetSearchOption options ??= new(); var res = await _httpClient.GetAsync(_baseUrl + "tweets/search/all?query=" + HttpUtility.UrlEncode(expression.ToString()) + "&" + options.Build(true)); BuildRateLimit(res.Headers, Endpoint.FullArchiveSearch); - return ParseArrayData(await res.Content.ReadAsStringAsync()); + return ParseArrayData(await res.Content.ReadAsStringAsync()).Data; } #endregion TweetSearch @@ -280,7 +281,7 @@ public async Task GetInfoTweetStreamAsync() { var res = await _httpClient.GetAsync(_baseUrl + "tweets/search/stream/rules"); BuildRateLimit(res.Headers, Endpoint.ListingFilters); - return ParseArrayData(await res.Content.ReadAsStringAsync()); + return ParseArrayData(await res.Content.ReadAsStringAsync()).Data; } private StreamReader _reader; @@ -369,7 +370,7 @@ public async Task AddTweetStreamAsync(params StreamRequest[] reque var content = new StringContent(JsonSerializer.Serialize(new StreamRequestAdd { Add = request }, _jsonOptions), Encoding.UTF8, "application/json"); var res = await _httpClient.PostAsync(_baseUrl + "tweets/search/stream/rules", content); BuildRateLimit(res.Headers, Endpoint.AddingDeletingFilters); - return ParseArrayData(await res.Content.ReadAsStringAsync()); + return ParseArrayData(await res.Content.ReadAsStringAsync()).Data; } /// @@ -409,7 +410,7 @@ public async Task GetUsersAsync(string[] usernames, UserSearchOptions op options ??= new(); var res = await _httpClient.GetAsync(_baseUrl + $"users/by?usernames={string.Join(",", usernames.Select(x => HttpUtility.UrlEncode(x)))}&{options.Build(false)}"); BuildRateLimit(res.Headers, Endpoint.GetUsersByNames); - return ParseArrayData(await res.Content.ReadAsStringAsync()); + return ParseArrayData(await res.Content.ReadAsStringAsync()).Data; } /// @@ -433,7 +434,7 @@ public async Task GetUsersByIdsAsync(string[] ids, UserSearchOptions opt options ??= new(); var res = await _httpClient.GetAsync(_baseUrl + $"users?ids={string.Join(",", ids.Select(x => HttpUtility.UrlEncode(x)))}&{options.Build(false)}"); BuildRateLimit(res.Headers, Endpoint.GetUsersByIds); - return ParseArrayData(await res.Content.ReadAsStringAsync()); + return ParseArrayData(await res.Content.ReadAsStringAsync()).Data; } #endregion UserSearch From 3935c7543cf4876a0d554e6f0fbcc467565de383 Mon Sep 17 00:00:00 2001 From: bmclane Date: Wed, 14 Dec 2022 17:05:17 -0500 Subject: [PATCH 3/9] updated NextTweetsAsync --- src/Client/TwitterClient.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Client/TwitterClient.cs b/src/Client/TwitterClient.cs index 74026f8..ff0324b 100644 --- a/src/Client/TwitterClient.cs +++ b/src/Client/TwitterClient.cs @@ -237,7 +237,7 @@ public async Task GetTweetsFromUserIdAsync(string userId, TweetSearchOp private async Task NextTweetsAsync(string baseQuery, string token, Endpoint endpoint) { var res = await _httpClient.GetAsync(baseQuery + (!baseQuery.EndsWith("?") ? "&" : "") + "pagination_token=" + token); - var data = ParseData(await res.Content.ReadAsStringAsync()); + var data = ParseArrayData(await res.Content.ReadAsStringAsync()); BuildRateLimit(res.Headers, endpoint); return new() { From 5a402e104676ba1d8173c78a914efe78cc6861d6 Mon Sep 17 00:00:00 2001 From: bmclane Date: Thu, 15 Dec 2022 13:45:01 -0500 Subject: [PATCH 4/9] refactored to use response list for user and tweets --- src/Client/TwitterClient.cs | 123 ++++++++++++++++++--------------- src/Response/RList.cs | 16 +++++ src/Response/RTweet/RTweets.cs | 12 ---- src/Response/RUser/RUsers.cs | 11 --- test/TestFollow.cs | 5 +- test/TestLike.cs | 5 +- test/TestMedia.cs | 16 ++--- test/TestRetweet.cs | 5 +- test/TestTweet.cs | 81 +++++++++++----------- 9 files changed, 143 insertions(+), 131 deletions(-) create mode 100644 src/Response/RList.cs delete mode 100644 src/Response/RTweet/RTweets.cs delete mode 100644 src/Response/RUser/RUsers.cs diff --git a/src/Client/TwitterClient.cs b/src/Client/TwitterClient.cs index ff0324b..d431515 100644 --- a/src/Client/TwitterClient.cs +++ b/src/Client/TwitterClient.cs @@ -195,6 +195,8 @@ public async Task GetTweetAsync(string id, TweetSearchOptions options = n { options ??= new(); var res = await _httpClient.GetAsync(_baseUrl + "tweets/" + HttpUtility.UrlEncode(id) + "?" + options.Build(true)); + + var res = await _httpClient.GetAsync(query); BuildRateLimit(res.Headers, Endpoint.GetTweetById); return ParseData(await res.Content.ReadAsStringAsync()).Data; } @@ -203,19 +205,27 @@ public async Task GetTweetAsync(string id, TweetSearchOptions options = n /// Get a list of tweet given their IDs /// /// All the IDs you want the tweets of - public async Task GetTweetsAsync(string[] ids, TweetSearchOptions options = null) + public async Task> GetTweetsAsync(string[] ids, TweetSearchOptions options = null) { options ??= new(); - var res = await _httpClient.GetAsync(_baseUrl + "tweets?ids=" + string.Join(",", ids.Select(x => HttpUtility.UrlEncode(x))) + "&" + options.Build(true)); + var query = _baseUrl + "tweets?ids=" + string.Join(",", ids.Select(x => HttpUtility.UrlEncode(x))) + "&" + options.Build(true); + + var res = await _httpClient.GetAsync(query); BuildRateLimit(res.Headers, Endpoint.GetTweetsByIds); - return ParseArrayData(await res.Content.ReadAsStringAsync()).Data; + var data = ParseArrayData(await res.Content.ReadAsStringAsync()); + var meta = data.Meta; + return new() + { + Data = data.Data, + NextAsync = data.Meta == null ? null : (data.Meta.NextToken == null ? null : async () => await NextAsync(query, data.Meta.NextToken, Endpoint.UserTweetTimeline)) + }; } /// /// Get the latest tweets of an user /// /// Username of the user you want the tweets of - public async Task GetTweetsFromUserIdAsync(string userId, TweetSearchOptions options = null) + public async Task> GetTweetsFromUserIdAsync(string userId, TweetSearchOptions options = null) { options ??= new(); var query = _baseUrl + "users/" + HttpUtility.HtmlEncode(userId) + "/tweets?" + options.Build(true); @@ -225,38 +235,30 @@ public async Task GetTweetsFromUserIdAsync(string userId, TweetSearchOp var data = ParseArrayData(await res.Content.ReadAsStringAsync()); return new() { - Tweets = data.Data, - NextAsync = data.Meta.NextToken == null ? null : async () => await NextTweetsAsync(query, data.Meta.NextToken, Endpoint.UserTweetTimeline) + Data = data.Data, + NextAsync = data.Meta == null ? null : (data.Meta.NextToken == null ? null : async () => await NextAsync(query, data.Meta.NextToken, Endpoint.UserTweetTimeline)) }; } - /// - /// General method for getting the next page of tweets - /// - /// - private async Task NextTweetsAsync(string baseQuery, string token, Endpoint endpoint) - { - var res = await _httpClient.GetAsync(baseQuery + (!baseQuery.EndsWith("?") ? "&" : "") + "pagination_token=" + token); - var data = ParseArrayData(await res.Content.ReadAsStringAsync()); - BuildRateLimit(res.Headers, endpoint); - return new() - { - Tweets = data.Data, - NextAsync = data.Meta.NextToken == null ? null : async () => await NextTweetsAsync(baseQuery, data.Meta.NextToken, endpoint) - }; - } /// /// Get the latest tweets for an expression /// /// An expression to build the query /// properties send with the tweet - public async Task GetRecentTweets(Expression expression, TweetSearchOptions options = null) + public async Task> GetRecentTweets(Expression expression, TweetSearchOptions options = null) { options ??= new(); - var res = await _httpClient.GetAsync(_baseUrl + "tweets/search/recent?query=" + HttpUtility.UrlEncode(expression.ToString()) + "&" + options.Build(true)); + var query = _baseUrl + "tweets/search/recent?query=" + HttpUtility.UrlEncode(expression.ToString()) + "&" + options.Build(true); + + var res = await _httpClient.GetAsync(query); BuildRateLimit(res.Headers, Endpoint.RecentSearch); - return ParseArrayData(await res.Content.ReadAsStringAsync()).Data; + var data = ParseArrayData(await res.Content.ReadAsStringAsync()); + return new() + { + Data = data.Data, + NextAsync = data.Meta == null ? null : (data.Meta.NextToken == null ? null : async () => await NextAsync(query, data.Meta.NextToken, Endpoint.UserTweetTimeline)) + }; } /// @@ -265,12 +267,19 @@ public async Task GetRecentTweets(Expression expression, TweetSearchOpt /// /// An expression to build the query /// properties send with the tweet - public async Task GetAllTweets(Expression expression, TweetSearchOptions options = null) + public async Task> GetAllTweets(Expression expression, TweetSearchOptions options = null) { options ??= new(); - var res = await _httpClient.GetAsync(_baseUrl + "tweets/search/all?query=" + HttpUtility.UrlEncode(expression.ToString()) + "&" + options.Build(true)); + var query = _baseUrl + "tweets/search/all?query=" + HttpUtility.UrlEncode(expression.ToString()) + "&" + options.Build(true); + + var res = await _httpClient.GetAsync(query); BuildRateLimit(res.Headers, Endpoint.FullArchiveSearch); - return ParseArrayData(await res.Content.ReadAsStringAsync()).Data; + var data = ParseArrayData(await res.Content.ReadAsStringAsync()); + return new() + { + Data = data.Data, + NextAsync = data.Meta.NextToken == null ? null : async () => await NextAsync(query, data.Meta.NextToken, Endpoint.UserTweetTimeline) + }; } #endregion TweetSearch @@ -441,28 +450,13 @@ public async Task GetUsersByIdsAsync(string[] ids, UserSearchOptions opt #region GetUsers - /// - /// General method for getting the next page of users - /// - /// - private async Task NextUsersAsync(string baseQuery, string token, Endpoint endpoint) - { - var res = await _httpClient.GetAsync(baseQuery + (!baseQuery.EndsWith("?") ? "&" : "") + "pagination_token=" + token); - var data = ParseData(await res.Content.ReadAsStringAsync()); - BuildRateLimit(res.Headers, endpoint); - return new() - { - Users = data.Data, - NextAsync = data.Meta.NextToken == null ? null : async () => await NextUsersAsync(baseQuery, data.Meta.NextToken, endpoint) - }; - } /// /// Get the follower of an user /// /// ID of the user /// Max number of result, max is 1000 - public async Task GetFollowersAsync(string id, UserSearchOptions options = null) + public async Task> GetFollowersAsync(string id, UserSearchOptions options = null) { options ??= new(); var query = _baseUrl + $"users/{HttpUtility.UrlEncode(id)}/followers?{options.Build(false)}"; @@ -471,8 +465,8 @@ public async Task GetFollowersAsync(string id, UserSearchOptions options BuildRateLimit(res.Headers, Endpoint.GetFollowersById); return new() { - Users = data.Data, - NextAsync = data.Meta.NextToken == null ? null : async () => await NextUsersAsync(query, data.Meta.NextToken, Endpoint.GetFollowersById) + Data = data.Data, + NextAsync = data.Meta.NextToken == null ? null : async () => await NextAsync(query, data.Meta.NextToken, Endpoint.GetFollowersById) }; } @@ -481,7 +475,7 @@ public async Task GetFollowersAsync(string id, UserSearchOptions options /// /// ID of the user /// Max number of result, max is 1000 - public async Task GetFollowingAsync(string id, UserSearchOptions options = null) + public async Task> GetFollowingAsync(string id, UserSearchOptions options = null) { options ??= new(); var query = _baseUrl + $"users/{HttpUtility.UrlEncode(id)}/following?{options.Build(false)}"; @@ -490,8 +484,8 @@ public async Task GetFollowingAsync(string id, UserSearchOptions options BuildRateLimit(res.Headers, Endpoint.GetFollowingsById); return new() { - Users = data.Data, - NextAsync = data.Meta.NextToken == null ? null : async () => await NextUsersAsync(query, data.Meta.NextToken, Endpoint.GetFollowingsById) + Data = data.Data, + NextAsync = data.Meta.NextToken == null ? null : async () => await NextAsync(query, data.Meta.NextToken, Endpoint.GetFollowingsById) }; } @@ -500,7 +494,7 @@ public async Task GetFollowingAsync(string id, UserSearchOptions options /// /// ID of the tweet /// This parameter enables you to select which specific user fields will deliver with each returned users objects. You can also set a Limit per page. Max is 100 - public async Task GetLikesAsync(string id, UserSearchOptions options = null) + public async Task> GetLikesAsync(string id, UserSearchOptions options = null) { options ??= new(); var query = _baseUrl + $"tweets/{HttpUtility.UrlEncode(id)}/liking_users?{options.Build(false)}"; @@ -509,8 +503,8 @@ public async Task GetLikesAsync(string id, UserSearchOptions options = n BuildRateLimit(res.Headers, Endpoint.UsersLiked); return new() { - Users = data.Data, - NextAsync = data.Meta.NextToken == null ? null : async () => await NextUsersAsync(query, data.Meta.NextToken, Endpoint.UsersLiked) + Data = data.Data, + NextAsync = data.Meta.NextToken == null ? null : async () => await NextAsync(query, data.Meta.NextToken, Endpoint.UsersLiked) }; } @@ -519,7 +513,7 @@ public async Task GetLikesAsync(string id, UserSearchOptions options = n /// /// ID of the tweet /// This parameter enables you to select which specific user fields will deliver with each returned users objects. You can also set a Limit per page. Max is 100 - public async Task GetRetweetsAsync(string id, UserSearchOptions options = null) + public async Task> GetRetweetsAsync(string id, UserSearchOptions options = null) { options ??= new(); var query = _baseUrl + $"tweets/{HttpUtility.UrlEncode(id)}/retweeted_by?{options.Build(false)}"; @@ -528,13 +522,34 @@ public async Task GetRetweetsAsync(string id, UserSearchOptions options BuildRateLimit(res.Headers, Endpoint.RetweetsLookup); return new() { - Users = data.Data, - NextAsync = data.Meta.NextToken == null ? null : async () => await NextUsersAsync(query, data.Meta.NextToken, Endpoint.RetweetsLookup) + Data = data.Data, + NextAsync = data.Meta.NextToken == null ? null : async () => await NextAsync(query, data.Meta.NextToken, Endpoint.RetweetsLookup) }; } #endregion Users + + #region General + + + /// + /// General method for getting the next page with meta token + /// + /// + private async Task> NextAsync(string baseQuery, string token, Endpoint endpoint) + { + var res = await _httpClient.GetAsync(baseQuery + (!baseQuery.EndsWith("?") ? "&" : "") + "pagination_token=" + token); + var data = ParseArrayData(await res.Content.ReadAsStringAsync()); + BuildRateLimit(res.Headers, endpoint); + return new() + { + Data = data.Data, + NextAsync = data.Meta.NextToken == null ? null : async () => await NextAsync(baseQuery, data.Meta.NextToken, endpoint) + }; + } + + #endregion private const string _baseUrl = "https://api.twitter.com/2/"; private readonly HttpClient _httpClient; diff --git a/src/Response/RList.cs b/src/Response/RList.cs new file mode 100644 index 0000000..9414375 --- /dev/null +++ b/src/Response/RList.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using TwitterSharp.Response.RUser; + +namespace TwitterSharp.Response +{ + public class RList + { + public T[] Data { get; set; } + public Func>> NextAsync { init; get; } + + } +} diff --git a/src/Response/RTweet/RTweets.cs b/src/Response/RTweet/RTweets.cs deleted file mode 100644 index 37fa1a4..0000000 --- a/src/Response/RTweet/RTweets.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; -using System.Threading.Tasks; - -namespace TwitterSharp.Response.RTweet -{ - public class RTweets - { - public Tweet[] Tweets { init; get; } - - public Func> NextAsync { init; get; } - } -} diff --git a/src/Response/RUser/RUsers.cs b/src/Response/RUser/RUsers.cs deleted file mode 100644 index 1d5b575..0000000 --- a/src/Response/RUser/RUsers.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System; -using System.Threading.Tasks; - -namespace TwitterSharp.Response.RUser -{ - public class RUsers - { - public User[] Users { init; get; } - public Func> NextAsync { init; get; } - } -} diff --git a/test/TestFollow.cs b/test/TestFollow.cs index bbf60e1..b907343 100644 --- a/test/TestFollow.cs +++ b/test/TestFollow.cs @@ -4,6 +4,7 @@ using System.Threading.Tasks; using TwitterSharp.Client; using TwitterSharp.Request.Option; +using TwitterSharp.Response; using TwitterSharp.Response.RUser; namespace TwitterSharp.UnitTests @@ -11,9 +12,9 @@ namespace TwitterSharp.UnitTests [TestClass] public class TestFollow { - private async Task ContainsFollowAsync(string username, RUsers rUsers) + private async Task ContainsFollowAsync(string username, RList rUsers) { - if (rUsers.Users.Any(x => x.Username == username)) + if (rUsers.Data.Any(x => x.Username == username)) { return true; } diff --git a/test/TestLike.cs b/test/TestLike.cs index 6151ae6..99c8a5c 100644 --- a/test/TestLike.cs +++ b/test/TestLike.cs @@ -4,6 +4,7 @@ using System.Threading.Tasks; using TwitterSharp.Client; using TwitterSharp.Request.Option; +using TwitterSharp.Response; using TwitterSharp.Response.RUser; namespace TwitterSharp.UnitTests @@ -11,9 +12,9 @@ namespace TwitterSharp.UnitTests [TestClass] public class TestLike { - private async Task ContainsLikeAsync(string username, RUsers rUsers) + private async Task ContainsLikeAsync(string username, RList rUsers) { - if (rUsers.Users.Any(x => x.Username == username)) + if (rUsers.Data.Any(x => x.Username == username)) { return true; } diff --git a/test/TestMedia.cs b/test/TestMedia.cs index 2825189..a4413d5 100644 --- a/test/TestMedia.cs +++ b/test/TestMedia.cs @@ -16,8 +16,8 @@ public async Task GetTweetWithoutMedia() { var client = new TwitterClient(Environment.GetEnvironmentVariable("TWITTER_TOKEN")); var answer = await client.GetTweetsAsync(new[] { "1237543996861251586" }); - Assert.IsTrue(answer.Length == 1); - var a = answer[0]; + Assert.IsTrue(answer.Data.Length == 1); + var a = answer.Data[0]; Assert.IsNull(a.Attachments); } @@ -29,8 +29,8 @@ public async Task GetTweetWithMediaId() { TweetOptions = new[] { TweetOption.Attachments_Ids } }); - Assert.IsTrue(answer.Length == 1); - var a = answer[0]; + Assert.IsTrue(answer.Data.Length == 1); + var a = answer.Data[0]; Assert.IsNotNull(a.Attachments); Assert.IsNotNull(a.Attachments.Media); Assert.AreEqual(1, a.Attachments.Media.Length); @@ -47,8 +47,8 @@ public async Task GetTweetWithMedia() { TweetOptions = new[] { TweetOption.Attachments } }); - Assert.IsTrue(answer.Length == 1); - var a = answer[0]; + Assert.IsTrue(answer.Data.Length == 1); + var a = answer.Data[0]; Assert.IsNotNull(a.Attachments); Assert.IsNotNull(a.Attachments.Media); Assert.AreEqual(1, a.Attachments.Media.Length); @@ -67,8 +67,8 @@ public async Task GetTweetWithMediaPreview() TweetOptions = new[] { TweetOption.Attachments }, MediaOptions = new[] { MediaOption.Preview_Image_Url } }); - Assert.IsTrue(answer.Length == 1); - var a = answer[0]; + Assert.IsTrue(answer.Data.Length == 1); + var a = answer.Data[0]; Assert.IsNotNull(a.Attachments); Assert.IsNotNull(a.Attachments.Media); Assert.AreEqual(1, a.Attachments.Media.Length); diff --git a/test/TestRetweet.cs b/test/TestRetweet.cs index e9a116d..0db1409 100644 --- a/test/TestRetweet.cs +++ b/test/TestRetweet.cs @@ -4,6 +4,7 @@ using System.Threading.Tasks; using TwitterSharp.Client; using TwitterSharp.Request.Option; +using TwitterSharp.Response; using TwitterSharp.Response.RUser; namespace TwitterSharp.UnitTests @@ -11,9 +12,9 @@ namespace TwitterSharp.UnitTests [TestClass] public class TestRetweet { - private async Task ContainsUserAsync(string username, RUsers rUsers) + private async Task ContainsUserAsync(string username, RList rUsers) { - if (rUsers.Users.Any(x => x.Username == username)) + if (rUsers.Data.Any(x => x.Username == username)) { return true; } diff --git a/test/TestTweet.cs b/test/TestTweet.cs index f802946..32abec4 100644 --- a/test/TestTweet.cs +++ b/test/TestTweet.cs @@ -19,11 +19,11 @@ public async Task GetTweetByIdsAsync() { var client = new TwitterClient(Environment.GetEnvironmentVariable("TWITTER_TOKEN")); var answer = await client.GetTweetsAsync(new[] { "1389189291582967809" }); - Assert.IsTrue(answer.Length == 1); - Assert.AreEqual("1389189291582967809", answer[0].Id); - Assert.AreEqual("たのしみ!!\uD83D\uDC93 https://t.co/DgBYVYr9lN", answer[0].Text); - Assert.IsNull(answer[0].Author); - Assert.IsNull(answer[0].PossiblySensitive); + Assert.IsTrue(answer.Data.Length == 1); + Assert.AreEqual("1389189291582967809", answer.Data[0].Id); + Assert.AreEqual("たのしみ!!\uD83D\uDC93 https://t.co/DgBYVYr9lN", answer.Data[0].Text); + Assert.IsNull(answer.Data[0].Author); + Assert.IsNull(answer.Data[0].PossiblySensitive); } [TestMethod] @@ -46,13 +46,13 @@ public async Task GetTweetByIdsWithAuthorAndSensitivityAsync() TweetOptions = new[] { TweetOption.Possibly_Sensitive }, UserOptions = Array.Empty() }); - Assert.IsTrue(answer.Length == 1); - Assert.AreEqual("1389189291582967809", answer[0].Id); - Assert.AreEqual("たのしみ!!\uD83D\uDC93 https://t.co/DgBYVYr9lN", answer[0].Text); - Assert.IsNotNull(answer[0].Author); - Assert.IsNotNull(answer[0].PossiblySensitive); - Assert.AreEqual("kiryucoco", answer[0].Author.Username); - Assert.IsFalse(answer[0].PossiblySensitive.Value); + Assert.IsTrue(answer.Data.Length == 1); + Assert.AreEqual("1389189291582967809", answer.Data[0].Id); + Assert.AreEqual("たのしみ!!\uD83D\uDC93 https://t.co/DgBYVYr9lN", answer.Data[0].Text); + Assert.IsNotNull(answer.Data[0].Author); + Assert.IsNotNull(answer.Data[0].PossiblySensitive); + Assert.AreEqual("kiryucoco", answer.Data[0].Author.Username); + Assert.IsFalse(answer.Data[0].PossiblySensitive.Value); } [TestMethod] @@ -60,13 +60,13 @@ public async Task GetTweetsAsync() { var client = new TwitterClient(Environment.GetEnvironmentVariable("TWITTER_TOKEN")); var answer = await client.GetTweetsAsync(new[] { "1389330151779930113", "1389331863102128130" }); - Assert.IsTrue(answer.Length == 2); - Assert.AreEqual("1389330151779930113", answer[0].Id); - Assert.AreEqual("ねむくなーい!ねむくないねむくない!ドタドタドタドタ", answer[0].Text); - Assert.IsNull(answer[0].Author); - Assert.AreEqual("1389331863102128130", answer[1].Id); - Assert.AreEqual("( - ω・ )", answer[1].Text); - Assert.IsNull(answer[1].Author); + Assert.IsTrue(answer.Data.Length == 2); + Assert.AreEqual("1389330151779930113", answer.Data[0].Id); + Assert.AreEqual("ねむくなーい!ねむくないねむくない!ドタドタドタドタ", answer.Data[0].Text); + Assert.IsNull(answer.Data[0].Author); + Assert.AreEqual("1389331863102128130", answer.Data[1].Id); + Assert.AreEqual("( - ω・ )", answer.Data[1].Text); + Assert.IsNull(answer.Data[1].Author); } [TestMethod] @@ -77,15 +77,15 @@ public async Task GetTweetsByIdsWithAuthorAsync() { UserOptions = Array.Empty() }); - Assert.IsTrue(answer.Length == 2); - Assert.AreEqual("1389330151779930113", answer[0].Id); - Assert.AreEqual("ねむくなーい!ねむくないねむくない!ドタドタドタドタ", answer[0].Text); - Assert.IsNotNull(answer[0].Author); - Assert.AreEqual("tsunomakiwatame", answer[0].Author.Username); - Assert.AreEqual("1389331863102128130", answer[1].Id); - Assert.AreEqual("( - ω・ )", answer[1].Text); - Assert.IsNotNull(answer[1].Author); - Assert.AreEqual("tsunomakiwatame", answer[1].Author.Username); + Assert.IsTrue(answer.Data.Length == 2); + Assert.AreEqual("1389330151779930113", answer.Data[0].Id); + Assert.AreEqual("ねむくなーい!ねむくないねむくない!ドタドタドタドタ", answer.Data[0].Text); + Assert.IsNotNull(answer.Data[0].Author); + Assert.AreEqual("tsunomakiwatame", answer.Data[0].Author.Username); + Assert.AreEqual("1389331863102128130", answer.Data[1].Id); + Assert.AreEqual("( - ω・ )", answer.Data[1].Text); + Assert.IsNotNull(answer.Data[1].Author); + Assert.AreEqual("tsunomakiwatame", answer.Data[1].Author.Username); } [TestMethod] @@ -93,8 +93,8 @@ public async Task GetTweetsFromUserIdAsync() { var client = new TwitterClient(Environment.GetEnvironmentVariable("TWITTER_TOKEN")); var answer = await client.GetTweetsFromUserIdAsync("1109748792721432577"); - Assert.AreEqual(10, answer.Tweets.Length); - Assert.IsNull(answer.Tweets[0].Author); + Assert.AreEqual(10, answer.Data.Length); + Assert.IsNull(answer.Data[0].Author); } [TestMethod] @@ -105,7 +105,7 @@ public async Task GetTweetsFromUserIdWithSinceIdAsync() { SinceId = "1410551383795781634" }); - Assert.AreEqual(2, answer.Tweets.Length); + Assert.AreEqual(2, answer.Data.Length); } [TestMethod] @@ -116,7 +116,7 @@ public async Task GetTweetsFromUserIdWithStartTimeAsync() { StartTime = new DateTime(2021, 7, 1, 12, 50, 0) }); - Assert.AreEqual(1, answer.Tweets.Length); + Assert.AreEqual(1, answer.Data.Length); } [TestMethod] @@ -128,8 +128,8 @@ public async Task GetTweetsFromUserIdWithArgumentsAsync() // Issue #2 TweetOptions = new[] { TweetOption.Attachments }, UserOptions = Array.Empty() }); - Assert.AreEqual(10, answer.Tweets.Length); - Assert.IsNotNull(answer.Tweets[0].Author); + Assert.AreEqual(10, answer.Data.Length); + Assert.IsNotNull(answer.Data[0].Author); } [TestMethod] @@ -140,8 +140,8 @@ public async Task GetTweetsFromUserIdWithAuthorAsync() { UserOptions = Array.Empty() }); - Assert.IsTrue(answer.Tweets.Length == 10); - foreach (var t in answer.Tweets) + Assert.IsTrue(answer.Data.Length == 10); + foreach (var t in answer.Data) { Assert.IsNotNull(t.Author); Assert.AreEqual("inugamikorone", t.Author.Username); @@ -156,7 +156,7 @@ public async Task GetTweetsFromUserIdWithModifiedLimitAsync() { Limit = 100 }); - Assert.IsTrue(answer.Tweets.Length == 100); + Assert.IsTrue(answer.Data.Length == 100); } [TestMethod] @@ -165,11 +165,12 @@ public async Task GetTweetsFromUserIdWithNextToken() var client = new TwitterClient(Environment.GetEnvironmentVariable("TWITTER_TOKEN")); var answer = await client.GetTweetsFromUserIdAsync("1109748792721432577"); - Assert.IsTrue(answer.Tweets.Length == 10); + Assert.IsTrue(answer.Data.Length == 10); var nextAnswer = await answer.NextAsync(); - Assert.IsTrue(nextAnswer.Tweets.Length == 10); + Assert.IsTrue(nextAnswer.Data.Length == 10); + } [TestMethod] @@ -462,7 +463,7 @@ public async Task GetRecentTweets() // note: retweets are truncated at 140 characters, so i had to exclude them for making the check reliable var a = await client.GetRecentTweets(Expression.Hashtag(hashtag).And(Expression.IsRetweet().Negate())); - Assert.IsTrue(a.All(x => x.Text.Contains("#"+hashtag, StringComparison.InvariantCultureIgnoreCase))); + Assert.IsTrue(a.Data.All(x => x.Text.Contains("#"+hashtag, StringComparison.InvariantCultureIgnoreCase))); } [TestMethod] From 03b536fc2293f22b66f05dfd668d4d9b397d612b Mon Sep 17 00:00:00 2001 From: bmclane Date: Thu, 15 Dec 2022 13:45:59 -0500 Subject: [PATCH 5/9] fix commit --- src/Client/TwitterClient.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Client/TwitterClient.cs b/src/Client/TwitterClient.cs index d431515..d7c013a 100644 --- a/src/Client/TwitterClient.cs +++ b/src/Client/TwitterClient.cs @@ -194,7 +194,7 @@ private void BuildRateLimit(HttpResponseHeaders headers, Endpoint endpoint) public async Task GetTweetAsync(string id, TweetSearchOptions options = null) { options ??= new(); - var res = await _httpClient.GetAsync(_baseUrl + "tweets/" + HttpUtility.UrlEncode(id) + "?" + options.Build(true)); + var query = _baseUrl + "tweets/" + HttpUtility.UrlEncode(id) + "?" + options.Build(true); var res = await _httpClient.GetAsync(query); BuildRateLimit(res.Headers, Endpoint.GetTweetById); From 95a8dca7727a4da7ece3cb77eccf58818b442ab4 Mon Sep 17 00:00:00 2001 From: bmclane Date: Thu, 15 Dec 2022 15:50:24 -0500 Subject: [PATCH 6/9] remove next token from GetTweets --- src/Client/TwitterClient.cs | 34 ++++++++++++++-------- test/TestMedia.cs | 16 +++++------ test/TestTweet.cs | 56 ++++++++++++++++++------------------- 3 files changed, 58 insertions(+), 48 deletions(-) diff --git a/src/Client/TwitterClient.cs b/src/Client/TwitterClient.cs index d7c013a..23e4611 100644 --- a/src/Client/TwitterClient.cs +++ b/src/Client/TwitterClient.cs @@ -205,20 +205,14 @@ public async Task GetTweetAsync(string id, TweetSearchOptions options = n /// Get a list of tweet given their IDs /// /// All the IDs you want the tweets of - public async Task> GetTweetsAsync(string[] ids, TweetSearchOptions options = null) + public async Task GetTweetsAsync(string[] ids, TweetSearchOptions options = null) { options ??= new(); - var query = _baseUrl + "tweets?ids=" + string.Join(",", ids.Select(x => HttpUtility.UrlEncode(x))) + "&" + options.Build(true); - var res = await _httpClient.GetAsync(query); + var res = await _httpClient.GetAsync(_baseUrl + "tweets?ids=" + string.Join(",", ids.Select(x => HttpUtility.UrlEncode(x))) + "&" + options.Build(true)); BuildRateLimit(res.Headers, Endpoint.GetTweetsByIds); - var data = ParseArrayData(await res.Content.ReadAsStringAsync()); - var meta = data.Meta; - return new() - { - Data = data.Data, - NextAsync = data.Meta == null ? null : (data.Meta.NextToken == null ? null : async () => await NextAsync(query, data.Meta.NextToken, Endpoint.UserTweetTimeline)) - }; + return ParseArrayData(await res.Content.ReadAsStringAsync()).Data; + } /// @@ -257,7 +251,7 @@ public async Task> GetRecentTweets(Expression expression, TweetSear return new() { Data = data.Data, - NextAsync = data.Meta == null ? null : (data.Meta.NextToken == null ? null : async () => await NextAsync(query, data.Meta.NextToken, Endpoint.UserTweetTimeline)) + NextAsync = data.Meta == null ? null : (data.Meta.NextToken == null ? null : async () => await NextAsync(query, data.Meta.NextToken, Endpoint.RecentSearch)) }; } @@ -278,7 +272,7 @@ public async Task> GetAllTweets(Expression expression, TweetSearchO return new() { Data = data.Data, - NextAsync = data.Meta.NextToken == null ? null : async () => await NextAsync(query, data.Meta.NextToken, Endpoint.UserTweetTimeline) + NextAsync = data.Meta.NextToken == null ? null : async () => await NextAsync(query, data.Meta.NextToken, Endpoint.FullArchiveSearch) }; } @@ -533,6 +527,22 @@ public async Task> GetRetweetsAsync(string id, UserSearchOptions opt #region General + /// + /// General method for getting the next page with meta token + /// + /// + private async Task> RequestList(string baseQuery, string token, Endpoint endpoint) + { + var res = await _httpClient.GetAsync(baseQuery + (!baseQuery.EndsWith("?") ? "&" : "") + "pagination_token=" + token); + var data = ParseArrayData(await res.Content.ReadAsStringAsync()); + BuildRateLimit(res.Headers, endpoint); + return new() + { + Data = data.Data, + NextAsync = data.Meta.NextToken == null ? null : async () => await NextAsync(baseQuery, data.Meta.NextToken, endpoint) + }; + } + /// /// General method for getting the next page with meta token /// diff --git a/test/TestMedia.cs b/test/TestMedia.cs index a4413d5..2825189 100644 --- a/test/TestMedia.cs +++ b/test/TestMedia.cs @@ -16,8 +16,8 @@ public async Task GetTweetWithoutMedia() { var client = new TwitterClient(Environment.GetEnvironmentVariable("TWITTER_TOKEN")); var answer = await client.GetTweetsAsync(new[] { "1237543996861251586" }); - Assert.IsTrue(answer.Data.Length == 1); - var a = answer.Data[0]; + Assert.IsTrue(answer.Length == 1); + var a = answer[0]; Assert.IsNull(a.Attachments); } @@ -29,8 +29,8 @@ public async Task GetTweetWithMediaId() { TweetOptions = new[] { TweetOption.Attachments_Ids } }); - Assert.IsTrue(answer.Data.Length == 1); - var a = answer.Data[0]; + Assert.IsTrue(answer.Length == 1); + var a = answer[0]; Assert.IsNotNull(a.Attachments); Assert.IsNotNull(a.Attachments.Media); Assert.AreEqual(1, a.Attachments.Media.Length); @@ -47,8 +47,8 @@ public async Task GetTweetWithMedia() { TweetOptions = new[] { TweetOption.Attachments } }); - Assert.IsTrue(answer.Data.Length == 1); - var a = answer.Data[0]; + Assert.IsTrue(answer.Length == 1); + var a = answer[0]; Assert.IsNotNull(a.Attachments); Assert.IsNotNull(a.Attachments.Media); Assert.AreEqual(1, a.Attachments.Media.Length); @@ -67,8 +67,8 @@ public async Task GetTweetWithMediaPreview() TweetOptions = new[] { TweetOption.Attachments }, MediaOptions = new[] { MediaOption.Preview_Image_Url } }); - Assert.IsTrue(answer.Data.Length == 1); - var a = answer.Data[0]; + Assert.IsTrue(answer.Length == 1); + var a = answer[0]; Assert.IsNotNull(a.Attachments); Assert.IsNotNull(a.Attachments.Media); Assert.AreEqual(1, a.Attachments.Media.Length); diff --git a/test/TestTweet.cs b/test/TestTweet.cs index 32abec4..a739ea7 100644 --- a/test/TestTweet.cs +++ b/test/TestTweet.cs @@ -19,11 +19,11 @@ public async Task GetTweetByIdsAsync() { var client = new TwitterClient(Environment.GetEnvironmentVariable("TWITTER_TOKEN")); var answer = await client.GetTweetsAsync(new[] { "1389189291582967809" }); - Assert.IsTrue(answer.Data.Length == 1); - Assert.AreEqual("1389189291582967809", answer.Data[0].Id); - Assert.AreEqual("たのしみ!!\uD83D\uDC93 https://t.co/DgBYVYr9lN", answer.Data[0].Text); - Assert.IsNull(answer.Data[0].Author); - Assert.IsNull(answer.Data[0].PossiblySensitive); + Assert.IsTrue(answer.Length == 1); + Assert.AreEqual("1389189291582967809", answer[0].Id); + Assert.AreEqual("たのしみ!!\uD83D\uDC93 https://t.co/DgBYVYr9lN", answer[0].Text); + Assert.IsNull(answer[0].Author); + Assert.IsNull(answer[0].PossiblySensitive); } [TestMethod] @@ -46,13 +46,13 @@ public async Task GetTweetByIdsWithAuthorAndSensitivityAsync() TweetOptions = new[] { TweetOption.Possibly_Sensitive }, UserOptions = Array.Empty() }); - Assert.IsTrue(answer.Data.Length == 1); - Assert.AreEqual("1389189291582967809", answer.Data[0].Id); - Assert.AreEqual("たのしみ!!\uD83D\uDC93 https://t.co/DgBYVYr9lN", answer.Data[0].Text); - Assert.IsNotNull(answer.Data[0].Author); - Assert.IsNotNull(answer.Data[0].PossiblySensitive); - Assert.AreEqual("kiryucoco", answer.Data[0].Author.Username); - Assert.IsFalse(answer.Data[0].PossiblySensitive.Value); + Assert.IsTrue(answer.Length == 1); + Assert.AreEqual("1389189291582967809", answer[0].Id); + Assert.AreEqual("たのしみ!!\uD83D\uDC93 https://t.co/DgBYVYr9lN", answer[0].Text); + Assert.IsNotNull(answer[0].Author); + Assert.IsNotNull(answer[0].PossiblySensitive); + Assert.AreEqual("kiryucoco", answer[0].Author.Username); + Assert.IsFalse(answer[0].PossiblySensitive.Value); } [TestMethod] @@ -60,13 +60,13 @@ public async Task GetTweetsAsync() { var client = new TwitterClient(Environment.GetEnvironmentVariable("TWITTER_TOKEN")); var answer = await client.GetTweetsAsync(new[] { "1389330151779930113", "1389331863102128130" }); - Assert.IsTrue(answer.Data.Length == 2); - Assert.AreEqual("1389330151779930113", answer.Data[0].Id); - Assert.AreEqual("ねむくなーい!ねむくないねむくない!ドタドタドタドタ", answer.Data[0].Text); - Assert.IsNull(answer.Data[0].Author); - Assert.AreEqual("1389331863102128130", answer.Data[1].Id); - Assert.AreEqual("( - ω・ )", answer.Data[1].Text); - Assert.IsNull(answer.Data[1].Author); + Assert.IsTrue(answer.Length == 2); + Assert.AreEqual("1389330151779930113", answer[0].Id); + Assert.AreEqual("ねむくなーい!ねむくないねむくない!ドタドタドタドタ", answer[0].Text); + Assert.IsNull(answer[0].Author); + Assert.AreEqual("1389331863102128130", answer[1].Id); + Assert.AreEqual("( - ω・ )", answer[1].Text); + Assert.IsNull(answer[1].Author); } [TestMethod] @@ -77,15 +77,15 @@ public async Task GetTweetsByIdsWithAuthorAsync() { UserOptions = Array.Empty() }); - Assert.IsTrue(answer.Data.Length == 2); - Assert.AreEqual("1389330151779930113", answer.Data[0].Id); - Assert.AreEqual("ねむくなーい!ねむくないねむくない!ドタドタドタドタ", answer.Data[0].Text); - Assert.IsNotNull(answer.Data[0].Author); - Assert.AreEqual("tsunomakiwatame", answer.Data[0].Author.Username); - Assert.AreEqual("1389331863102128130", answer.Data[1].Id); - Assert.AreEqual("( - ω・ )", answer.Data[1].Text); - Assert.IsNotNull(answer.Data[1].Author); - Assert.AreEqual("tsunomakiwatame", answer.Data[1].Author.Username); + Assert.IsTrue(answer.Length == 2); + Assert.AreEqual("1389330151779930113", answer[0].Id); + Assert.AreEqual("ねむくなーい!ねむくないねむくない!ドタドタドタドタ", answer[0].Text); + Assert.IsNotNull(answer[0].Author); + Assert.AreEqual("tsunomakiwatame", answer[0].Author.Username); + Assert.AreEqual("1389331863102128130", answer[1].Id); + Assert.AreEqual("( - ω・ )", answer[1].Text); + Assert.IsNotNull(answer[1].Author); + Assert.AreEqual("tsunomakiwatame", answer[1].Author.Username); } [TestMethod] From 4bf7aaf46a660f750f55bbd1516841a933ce8673 Mon Sep 17 00:00:00 2001 From: bmclane Date: Thu, 15 Dec 2022 19:13:37 -0500 Subject: [PATCH 7/9] removed duplicate code inside IList requests --- src/Client/TwitterClient.cs | 92 ++++++------------------------------- src/Response/RList.cs | 4 -- 2 files changed, 13 insertions(+), 83 deletions(-) diff --git a/src/Client/TwitterClient.cs b/src/Client/TwitterClient.cs index 23e4611..0706352 100644 --- a/src/Client/TwitterClient.cs +++ b/src/Client/TwitterClient.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Net.Http; @@ -223,15 +224,7 @@ public async Task> GetTweetsFromUserIdAsync(string userId, TweetSea { options ??= new(); var query = _baseUrl + "users/" + HttpUtility.HtmlEncode(userId) + "/tweets?" + options.Build(true); - - var res = await _httpClient.GetAsync(query); - BuildRateLimit(res.Headers, Endpoint.UserTweetTimeline); - var data = ParseArrayData(await res.Content.ReadAsStringAsync()); - return new() - { - Data = data.Data, - NextAsync = data.Meta == null ? null : (data.Meta.NextToken == null ? null : async () => await NextAsync(query, data.Meta.NextToken, Endpoint.UserTweetTimeline)) - }; + return await RequestList(query, Endpoint.UserTweetTimeline); } @@ -244,15 +237,7 @@ public async Task> GetRecentTweets(Expression expression, TweetSear { options ??= new(); var query = _baseUrl + "tweets/search/recent?query=" + HttpUtility.UrlEncode(expression.ToString()) + "&" + options.Build(true); - - var res = await _httpClient.GetAsync(query); - BuildRateLimit(res.Headers, Endpoint.RecentSearch); - var data = ParseArrayData(await res.Content.ReadAsStringAsync()); - return new() - { - Data = data.Data, - NextAsync = data.Meta == null ? null : (data.Meta.NextToken == null ? null : async () => await NextAsync(query, data.Meta.NextToken, Endpoint.RecentSearch)) - }; + return await RequestList(query, Endpoint.RecentSearch); } /// @@ -265,15 +250,7 @@ public async Task> GetAllTweets(Expression expression, TweetSearchO { options ??= new(); var query = _baseUrl + "tweets/search/all?query=" + HttpUtility.UrlEncode(expression.ToString()) + "&" + options.Build(true); - - var res = await _httpClient.GetAsync(query); - BuildRateLimit(res.Headers, Endpoint.FullArchiveSearch); - var data = ParseArrayData(await res.Content.ReadAsStringAsync()); - return new() - { - Data = data.Data, - NextAsync = data.Meta.NextToken == null ? null : async () => await NextAsync(query, data.Meta.NextToken, Endpoint.FullArchiveSearch) - }; + return await RequestList(query, Endpoint.FullArchiveSearch); } #endregion TweetSearch @@ -454,14 +431,7 @@ public async Task> GetFollowersAsync(string id, UserSearchOptions op { options ??= new(); var query = _baseUrl + $"users/{HttpUtility.UrlEncode(id)}/followers?{options.Build(false)}"; - var res = await _httpClient.GetAsync(query); - var data = ParseData(await res.Content.ReadAsStringAsync()); - BuildRateLimit(res.Headers, Endpoint.GetFollowersById); - return new() - { - Data = data.Data, - NextAsync = data.Meta.NextToken == null ? null : async () => await NextAsync(query, data.Meta.NextToken, Endpoint.GetFollowersById) - }; + return await RequestList(query, Endpoint.GetFollowersById); } /// @@ -473,16 +443,9 @@ public async Task> GetFollowingAsync(string id, UserSearchOptions op { options ??= new(); var query = _baseUrl + $"users/{HttpUtility.UrlEncode(id)}/following?{options.Build(false)}"; - var res = await _httpClient.GetAsync(query); - var data = ParseData(await res.Content.ReadAsStringAsync()); - BuildRateLimit(res.Headers, Endpoint.GetFollowingsById); - return new() - { - Data = data.Data, - NextAsync = data.Meta.NextToken == null ? null : async () => await NextAsync(query, data.Meta.NextToken, Endpoint.GetFollowingsById) - }; + return await RequestList(query, Endpoint.GetFollowersById); } - + /// /// Get the likes of a tweet /// @@ -492,14 +455,7 @@ public async Task> GetLikesAsync(string id, UserSearchOptions option { options ??= new(); var query = _baseUrl + $"tweets/{HttpUtility.UrlEncode(id)}/liking_users?{options.Build(false)}"; - var res = await _httpClient.GetAsync(query); - var data = ParseData(await res.Content.ReadAsStringAsync()); - BuildRateLimit(res.Headers, Endpoint.UsersLiked); - return new() - { - Data = data.Data, - NextAsync = data.Meta.NextToken == null ? null : async () => await NextAsync(query, data.Meta.NextToken, Endpoint.UsersLiked) - }; + return await RequestList(query, Endpoint.UsersLiked); } /// @@ -511,14 +467,7 @@ public async Task> GetRetweetsAsync(string id, UserSearchOptions opt { options ??= new(); var query = _baseUrl + $"tweets/{HttpUtility.UrlEncode(id)}/retweeted_by?{options.Build(false)}"; - var res = await _httpClient.GetAsync(query); - var data = ParseData(await res.Content.ReadAsStringAsync()); - BuildRateLimit(res.Headers, Endpoint.RetweetsLookup); - return new() - { - Data = data.Data, - NextAsync = data.Meta.NextToken == null ? null : async () => await NextAsync(query, data.Meta.NextToken, Endpoint.RetweetsLookup) - }; + return await RequestList(query, Endpoint.RetweetsLookup); } #endregion Users @@ -531,35 +480,20 @@ public async Task> GetRetweetsAsync(string id, UserSearchOptions opt /// General method for getting the next page with meta token /// /// - private async Task> RequestList(string baseQuery, string token, Endpoint endpoint) + private async Task> RequestList(string baseQuery, Endpoint endpoint, string token = null) { - var res = await _httpClient.GetAsync(baseQuery + (!baseQuery.EndsWith("?") ? "&" : "") + "pagination_token=" + token); + var res = await _httpClient.GetAsync(baseQuery + (string.IsNullOrEmpty(token) ? "" : (!baseQuery.EndsWith("?") ? "&" : "") + "pagination_token=" + token)); var data = ParseArrayData(await res.Content.ReadAsStringAsync()); BuildRateLimit(res.Headers, endpoint); return new() { Data = data.Data, - NextAsync = data.Meta.NextToken == null ? null : async () => await NextAsync(baseQuery, data.Meta.NextToken, endpoint) - }; - } - - /// - /// General method for getting the next page with meta token - /// - /// - private async Task> NextAsync(string baseQuery, string token, Endpoint endpoint) - { - var res = await _httpClient.GetAsync(baseQuery + (!baseQuery.EndsWith("?") ? "&" : "") + "pagination_token=" + token); - var data = ParseArrayData(await res.Content.ReadAsStringAsync()); - BuildRateLimit(res.Headers, endpoint); - return new() - { - Data = data.Data, - NextAsync = data.Meta.NextToken == null ? null : async () => await NextAsync(baseQuery, data.Meta.NextToken, endpoint) + NextAsync = data.Meta.NextToken == null ? null : async () => await RequestList(baseQuery, endpoint, data.Meta.NextToken) }; } #endregion + private const string _baseUrl = "https://api.twitter.com/2/"; private readonly HttpClient _httpClient; diff --git a/src/Response/RList.cs b/src/Response/RList.cs index 9414375..475ac43 100644 --- a/src/Response/RList.cs +++ b/src/Response/RList.cs @@ -1,9 +1,5 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; using System.Threading.Tasks; -using TwitterSharp.Response.RUser; namespace TwitterSharp.Response { From db80b1cd058f0dcc3bb7e0923a8b513a3144c7ca Mon Sep 17 00:00:00 2001 From: bmclane Date: Thu, 15 Dec 2022 20:26:39 -0500 Subject: [PATCH 8/9] renamed RList to RArray, RList should be used for Twitters Lists API --- src/Client/TwitterClient.cs | 16 ++++++++-------- src/Response/{RList.cs => RArray.cs} | 4 ++-- test/TestFollow.cs | 2 +- test/TestLike.cs | 2 +- test/TestRetweet.cs | 2 +- 5 files changed, 13 insertions(+), 13 deletions(-) rename src/Response/{RList.cs => RArray.cs} (60%) diff --git a/src/Client/TwitterClient.cs b/src/Client/TwitterClient.cs index 0706352..8b1ca43 100644 --- a/src/Client/TwitterClient.cs +++ b/src/Client/TwitterClient.cs @@ -220,7 +220,7 @@ public async Task GetTweetsAsync(string[] ids, TweetSearchOptions optio /// Get the latest tweets of an user /// /// Username of the user you want the tweets of - public async Task> GetTweetsFromUserIdAsync(string userId, TweetSearchOptions options = null) + public async Task> GetTweetsFromUserIdAsync(string userId, TweetSearchOptions options = null) { options ??= new(); var query = _baseUrl + "users/" + HttpUtility.HtmlEncode(userId) + "/tweets?" + options.Build(true); @@ -233,7 +233,7 @@ public async Task> GetTweetsFromUserIdAsync(string userId, TweetSea /// /// An expression to build the query /// properties send with the tweet - public async Task> GetRecentTweets(Expression expression, TweetSearchOptions options = null) + public async Task> GetRecentTweets(Expression expression, TweetSearchOptions options = null) { options ??= new(); var query = _baseUrl + "tweets/search/recent?query=" + HttpUtility.UrlEncode(expression.ToString()) + "&" + options.Build(true); @@ -246,7 +246,7 @@ public async Task> GetRecentTweets(Expression expression, TweetSear /// /// An expression to build the query /// properties send with the tweet - public async Task> GetAllTweets(Expression expression, TweetSearchOptions options = null) + public async Task> GetAllTweets(Expression expression, TweetSearchOptions options = null) { options ??= new(); var query = _baseUrl + "tweets/search/all?query=" + HttpUtility.UrlEncode(expression.ToString()) + "&" + options.Build(true); @@ -427,7 +427,7 @@ public async Task GetUsersByIdsAsync(string[] ids, UserSearchOptions opt /// /// ID of the user /// Max number of result, max is 1000 - public async Task> GetFollowersAsync(string id, UserSearchOptions options = null) + public async Task> GetFollowersAsync(string id, UserSearchOptions options = null) { options ??= new(); var query = _baseUrl + $"users/{HttpUtility.UrlEncode(id)}/followers?{options.Build(false)}"; @@ -439,7 +439,7 @@ public async Task> GetFollowersAsync(string id, UserSearchOptions op /// /// ID of the user /// Max number of result, max is 1000 - public async Task> GetFollowingAsync(string id, UserSearchOptions options = null) + public async Task> GetFollowingAsync(string id, UserSearchOptions options = null) { options ??= new(); var query = _baseUrl + $"users/{HttpUtility.UrlEncode(id)}/following?{options.Build(false)}"; @@ -451,7 +451,7 @@ public async Task> GetFollowingAsync(string id, UserSearchOptions op /// /// ID of the tweet /// This parameter enables you to select which specific user fields will deliver with each returned users objects. You can also set a Limit per page. Max is 100 - public async Task> GetLikesAsync(string id, UserSearchOptions options = null) + public async Task> GetLikesAsync(string id, UserSearchOptions options = null) { options ??= new(); var query = _baseUrl + $"tweets/{HttpUtility.UrlEncode(id)}/liking_users?{options.Build(false)}"; @@ -463,7 +463,7 @@ public async Task> GetLikesAsync(string id, UserSearchOptions option /// /// ID of the tweet /// This parameter enables you to select which specific user fields will deliver with each returned users objects. You can also set a Limit per page. Max is 100 - public async Task> GetRetweetsAsync(string id, UserSearchOptions options = null) + public async Task> GetRetweetsAsync(string id, UserSearchOptions options = null) { options ??= new(); var query = _baseUrl + $"tweets/{HttpUtility.UrlEncode(id)}/retweeted_by?{options.Build(false)}"; @@ -480,7 +480,7 @@ public async Task> GetRetweetsAsync(string id, UserSearchOptions opt /// General method for getting the next page with meta token /// /// - private async Task> RequestList(string baseQuery, Endpoint endpoint, string token = null) + private async Task> RequestList(string baseQuery, Endpoint endpoint, string token = null) { var res = await _httpClient.GetAsync(baseQuery + (string.IsNullOrEmpty(token) ? "" : (!baseQuery.EndsWith("?") ? "&" : "") + "pagination_token=" + token)); var data = ParseArrayData(await res.Content.ReadAsStringAsync()); diff --git a/src/Response/RList.cs b/src/Response/RArray.cs similarity index 60% rename from src/Response/RList.cs rename to src/Response/RArray.cs index 475ac43..adecfba 100644 --- a/src/Response/RList.cs +++ b/src/Response/RArray.cs @@ -3,10 +3,10 @@ namespace TwitterSharp.Response { - public class RList + public class RArray { public T[] Data { get; set; } - public Func>> NextAsync { init; get; } + public Func>> NextAsync { init; get; } } } diff --git a/test/TestFollow.cs b/test/TestFollow.cs index b907343..60fe1e7 100644 --- a/test/TestFollow.cs +++ b/test/TestFollow.cs @@ -12,7 +12,7 @@ namespace TwitterSharp.UnitTests [TestClass] public class TestFollow { - private async Task ContainsFollowAsync(string username, RList rUsers) + private async Task ContainsFollowAsync(string username, RArray rUsers) { if (rUsers.Data.Any(x => x.Username == username)) { diff --git a/test/TestLike.cs b/test/TestLike.cs index 99c8a5c..e33d81f 100644 --- a/test/TestLike.cs +++ b/test/TestLike.cs @@ -12,7 +12,7 @@ namespace TwitterSharp.UnitTests [TestClass] public class TestLike { - private async Task ContainsLikeAsync(string username, RList rUsers) + private async Task ContainsLikeAsync(string username, RArray rUsers) { if (rUsers.Data.Any(x => x.Username == username)) { diff --git a/test/TestRetweet.cs b/test/TestRetweet.cs index 0db1409..ced3003 100644 --- a/test/TestRetweet.cs +++ b/test/TestRetweet.cs @@ -12,7 +12,7 @@ namespace TwitterSharp.UnitTests [TestClass] public class TestRetweet { - private async Task ContainsUserAsync(string username, RList rUsers) + private async Task ContainsUserAsync(string username, RArray rUsers) { if (rUsers.Data.Any(x => x.Username == username)) { From ce3515b0ba016f61004732a1121ade7e9428435a Mon Sep 17 00:00:00 2001 From: bmclane Date: Fri, 16 Dec 2022 12:23:09 -0500 Subject: [PATCH 9/9] implemented previous token --- src/Client/TwitterClient.cs | 3 ++- src/Response/Answer.cs | 1 + src/Response/RArray.cs | 1 + test/TestTweet.cs | 6 +++++- 4 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/Client/TwitterClient.cs b/src/Client/TwitterClient.cs index 8b1ca43..8c92802 100644 --- a/src/Client/TwitterClient.cs +++ b/src/Client/TwitterClient.cs @@ -488,7 +488,8 @@ private async Task> RequestList(string baseQuery, Endpoint endpoint return new() { Data = data.Data, - NextAsync = data.Meta.NextToken == null ? null : async () => await RequestList(baseQuery, endpoint, data.Meta.NextToken) + NextAsync = data.Meta.NextToken == null ? null : async () => await RequestList(baseQuery, endpoint, data.Meta.NextToken), + PreviousAsync = data.Meta.PreviousToken == null ? null : async () => await RequestList(baseQuery, endpoint, data.Meta.PreviousToken) }; } diff --git a/src/Response/Answer.cs b/src/Response/Answer.cs index 99e4bb2..6016bfd 100644 --- a/src/Response/Answer.cs +++ b/src/Response/Answer.cs @@ -32,6 +32,7 @@ internal class Meta { public Summary Summary { init; get; } public string NextToken { init; get; } + public string PreviousToken { init; get; } } internal class Summary diff --git a/src/Response/RArray.cs b/src/Response/RArray.cs index adecfba..a7cee14 100644 --- a/src/Response/RArray.cs +++ b/src/Response/RArray.cs @@ -7,6 +7,7 @@ public class RArray { public T[] Data { get; set; } public Func>> NextAsync { init; get; } + public Func>> PreviousAsync { init; get; } } } diff --git a/test/TestTweet.cs b/test/TestTweet.cs index a739ea7..26a764e 100644 --- a/test/TestTweet.cs +++ b/test/TestTweet.cs @@ -160,7 +160,7 @@ public async Task GetTweetsFromUserIdWithModifiedLimitAsync() } [TestMethod] - public async Task GetTweetsFromUserIdWithNextToken() + public async Task GetTweetsFromUserIdWithNextTokenAndPreviousToken() { var client = new TwitterClient(Environment.GetEnvironmentVariable("TWITTER_TOKEN")); var answer = await client.GetTweetsFromUserIdAsync("1109748792721432577"); @@ -171,6 +171,10 @@ public async Task GetTweetsFromUserIdWithNextToken() Assert.IsTrue(nextAnswer.Data.Length == 10); + var previousAnswer = await nextAnswer.PreviousAsync(); + + Assert.IsTrue(previousAnswer.Data.Length == 10); + Assert.IsTrue(previousAnswer.Data[0].Id.Equals(answer.Data[0].Id)); } [TestMethod]