From 1d38e239daa14365fb8ed6fe382ba64c69e2dceb Mon Sep 17 00:00:00 2001 From: robertodecurnex Date: Wed, 6 Jul 2022 01:28:46 +0200 Subject: [PATCH] Bumo version v0.0.6 Users#users_by Follow#followers --- README.md | 14 +++++- lib/twttr/client/endpoint/v2/users.rb | 14 ++++++ lib/twttr/client/endpoint/v2/users/follows.rb | 33 +++++++++++++ .../twttr/client/endpoint/v2/test_users.rb | 14 ++++++ .../client/endpoint/v2/users/test_follows.rb | 48 ++++++++++++++++++- twttr.gemspec | 2 +- 6 files changed, 121 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index d52d7ca..b674a05 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,16 @@ client.following("user_id") == client.user("user_id").following ``` ### User ```ruby +# Twttr::Model::User#followers +# +# Yields each page of users +user.followers do |users, pagination_token| + users #=> [#] +end #=> [#] + +# Follower users +user.followers #=> [#] + # Twttr::Model::User#following # # Yields each page of users @@ -77,7 +87,7 @@ user.following #=> [#] |:--- |:---: |:---: | |GET /2/users |v0.0.5 |:white_check_mark: | |GET /2/users/:id |v0.0.5 |:white_check_mark: | -|GET /2/users/by |- |- | +|GET /2/users/by |v0.0.6 |:white_check_mark: | |GET /2/users/by/username/:username |v0.0.5 |:white_check_mark: | |GET /2/users/me |v0.0.5 |:white_check_mark: | @@ -86,6 +96,6 @@ user.following #=> [#] |Endpoint |Initial Release |Implemented? | |:--- |:---: |:---: | |GET /2/users/:id/following |v0.0.5 |:white_check_mark: | -|GET /2/users/:id/followers |- |- | +|GET /2/users/:id/followers |v0.0.6 |:white_check_mark: | |POST /2/users/:id/following |- |- | |DELETE /2/users/:source_user_id/following/:target_user_id |- |- | diff --git a/lib/twttr/client/endpoint/v2/users.rb b/lib/twttr/client/endpoint/v2/users.rb index d3aeed4..5f8748b 100644 --- a/lib/twttr/client/endpoint/v2/users.rb +++ b/lib/twttr/client/endpoint/v2/users.rb @@ -9,6 +9,7 @@ module V2 module Users ME_PATH = "#{V2::V2_PATH}/users/me" USERS_PATH = "#{V2::V2_PATH}/users" + USERS_BY_PATH = "#{V2::V2_PATH}/users/by" USER_BY_USERNAME_PATH = "#{V2::V2_PATH}/users/by/username/%s" USER_PATH = "#{V2::V2_PATH}/users/%s" @@ -73,6 +74,19 @@ def users(user_ids) query_params: { ids: user_ids.join(','), 'user.fields': config.user_fields }) response['data'].map { |v| Model::User.new(v, self) } end + + # GET /2/users/by + # https://developer.twitter.com/en/docs/twitter-api/users/lookup/api-reference/get-users-by + # + # Returns target users by username + # + # @param usernames [Array] Traget usernames. + # @return [Array] Target users. + def users_by(usernames) + response = get(USERS_BY_PATH, + query_params: { usernames: usernames.join(','), 'user.fields': config.user_fields }) + response['data'].map { |v| Model::User.new(v, self) } + end end end end diff --git a/lib/twttr/client/endpoint/v2/users/follows.rb b/lib/twttr/client/endpoint/v2/users/follows.rb index 4a32522..2e63ab1 100644 --- a/lib/twttr/client/endpoint/v2/users/follows.rb +++ b/lib/twttr/client/endpoint/v2/users/follows.rb @@ -8,8 +8,41 @@ module Users # Twitter API V2 User Follow related endpoints # https://developer.twitter.com/en/docs/twitter-api/users/follows/api-reference module Follows + FOLLOWERS_PATH = "#{Users::USER_PATH}/followers" FOLLOWING_PATH = "#{Users::USER_PATH}/following" + # GET /2/users/:id/followers + # https://developer.twitter.com/en/docs/twitter-api/users/follows/api-reference/get-users-id-followers + # + # Returns paginated list of users following user_id. + # + # @param user_id [String] The user ID whose followers you would like to retrieve. + # @param max_results [Integer] Max number of results per peage. + # @param pagination_token [String] Initial page pagination token. + # @yield [Array] User followers page. + # @yield [String,NilClass] Pagination token. + # @return [Array] Follower Users. + def followers(user_id, max_results: nil, pagination_token: nil, &block) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize + response = get(FOLLOWERS_PATH, params: { user_id: user_id }, + query_params: { + 'user.fields': config.user_fields, + max_results: max_results, + pagination_token: pagination_token + }.compact) + + return [] if response['meta']['result_count'].zero? + + users = response['data'].map { |v| Model::User.new(v, self) } + + pagination_token = response['meta']['pagination_token'] + + yield users, pagination_token if block_given? + + return users if pagination_token.nil? + + users + followers(user_id, pagination_token: pagination_token, &block) + end + # GET /2/users/:id/following # https://developer.twitter.com/en/docs/twitter-api/users/follows/api-reference/get-users-id-following # diff --git a/test/lib/twttr/client/endpoint/v2/test_users.rb b/test/lib/twttr/client/endpoint/v2/test_users.rb index 7517678..28ba7ee 100644 --- a/test/lib/twttr/client/endpoint/v2/test_users.rb +++ b/test/lib/twttr/client/endpoint/v2/test_users.rb @@ -59,6 +59,20 @@ def test_user_by_username end end + def test_users_by + mock = lambda do |uri, _config| + assert_equal('https://api.twitter.com/2/users/by?usernames=username_1%2Cusername_2', uri.to_s) + @users_mock_oauth_response + end + Twttr::Client::OAuthRequest.stub :get, mock do + users = @client.users_by(%w[username_1 username_2]) + assert_equal(2, users.length) + users.each { |user| assert_instance_of(Twttr::Model::User, user) } + assert_equal('1234', users.first.id) + assert_equal('@username', users.first.username) + end + end + def test_users mock = lambda do |uri, _config| assert_equal('https://api.twitter.com/2/users?ids=user_id%2Cuser_id_2', uri.to_s) diff --git a/test/lib/twttr/client/endpoint/v2/users/test_follows.rb b/test/lib/twttr/client/endpoint/v2/users/test_follows.rb index 36fffd8..f9eda91 100644 --- a/test/lib/twttr/client/endpoint/v2/users/test_follows.rb +++ b/test/lib/twttr/client/endpoint/v2/users/test_follows.rb @@ -7,7 +7,7 @@ class Client module Endpoint module V2 module Users - class TestFollows < Minitest::Test + class TestFollows < Minitest::Test # rubocop:disable Metrics/ClassLength def setup @client = Twttr::Client.new do |config| config.consumer_key = 'consumer_key' @@ -32,11 +32,57 @@ def setup "meta": {"result_count": 2} }' @mock_oauth_responses = { + 'https://api.twitter.com/2/users/user_id/followers' => OpenStruct.new(body: first_mock_body), + 'https://api.twitter.com/2/users/user_id/followers?pagination_token=next-token' => OpenStruct.new(body: second_mock_body), 'https://api.twitter.com/2/users/user_id/following' => OpenStruct.new(body: first_mock_body), 'https://api.twitter.com/2/users/user_id/following?pagination_token=next-token' => OpenStruct.new(body: second_mock_body) } end + def test_followers + mock = lambda do |uri, _config| + @mock_oauth_responses[uri.to_s] + end + Twttr::Client::OAuthRequest.stub :get, mock do + users = @client.followers('user_id') + assert_equal(4, users.length) + users.each { |user| assert_instance_of(Twttr::Model::User, user) } + end + end + + def test_followers_with_block + mock = lambda do |uri, _config| + @mock_oauth_responses[uri.to_s] + end + Twttr::Client::OAuthRequest.stub :get, mock do + all_users = @client.followers('user_id') do |users, token| + assert_equal(2, users.length) + users.each { |user| assert_instance_of(Twttr::Model::User, user) } + assert_nil(nil, token) + end + assert_equal(4, all_users.length) + all_users.each { |user| assert_instance_of(Twttr::Model::User, user) } + end + end + + def test_followers_without_results + mock_body = '{ + "data": [], + "meta": {"result_count": 0} + }' + mock = lambda do |uri, _config| + assert_equal('https://api.twitter.com/2/users/user_id/followers', uri.to_s) + OpenStruct.new(body: mock_body) + end + Twttr::Client::OAuthRequest.stub :get, mock do + response = @client.followers('user_id') do |_users, _token| + assert(false, 'Block should not be called') + end + + assert_equal([], response) + end + end + def test_following mock = lambda do |uri, _config| @mock_oauth_responses[uri.to_s] diff --git a/twttr.gemspec b/twttr.gemspec index 433b33d..f4b71c8 100644 --- a/twttr.gemspec +++ b/twttr.gemspec @@ -2,7 +2,7 @@ Gem::Specification.new do |s| s.name = 'twttr' - s.version = '0.0.5' + s.version = '0.0.6' s.summary = 'Twitter API v2 Interface' s.description = 'Modular Twitter API interface, initially targeting Twitter API v2' s.authors = ['Roberto Decurnex']