Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions lib/x/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@
require_relative "redirect_handler"
require_relative "request_builder"
require_relative "response_parser"
require_relative "users"

module X
# A client for interacting with the X API
# @api public
class Client
extend Forwardable
include ClientCredentials
include Users

# Default base URL for the X API
DEFAULT_BASE_URL = "https://api.twitter.com/2/".freeze
Expand Down
110 changes: 110 additions & 0 deletions lib/x/users.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
module X
# Methods for the Users Lookup API
# @see https://docs.x.com/x-api/users/lookup/introduction
# @api public
module Users
# Retrieve a single user by ID
#
# @param id [String, Integer] the user ID
# @param user_fields [Array<String>, String, nil] list of user fields to return
# @param expansions [Array<String>, String, nil] list of expansions to include
# @param tweet_fields [Array<String>, String, nil] list of tweet fields to return (requires pinned_tweet_id expansion)
# @return [Hash] the parsed response body
# @see https://docs.x.com/x-api/users/lookup/api-reference/get-users-id
# @example Retrieve a user with default fields
# client.user("123456")
# @example Retrieve a user with additional fields
# client.user("123456", user_fields: ["created_at", "description", "public_metrics"])
def user(id, user_fields: nil, expansions: nil, tweet_fields: nil)
get(build_users_endpoint("users/#{id}", user_fields:, expansions:, tweet_fields:))
end

# Retrieve multiple users by their IDs
#
# @param ids [Array<String>, Array<Integer>] list of user IDs (up to 100)
# @param user_fields [Array<String>, String, nil] list of user fields to return
# @param expansions [Array<String>, String, nil] list of expansions to include
# @param tweet_fields [Array<String>, String, nil] list of tweet fields to return (requires pinned_tweet_id expansion)
# @return [Hash] the parsed response body
# @see https://docs.x.com/x-api/users/lookup/api-reference/get-users
# @example Retrieve multiple users
# client.users(ids: ["123456", "789012"])
# @example Retrieve multiple users with fields
# client.users(ids: ["123456", "789012"], user_fields: ["created_at", "public_metrics"])
def users(ids:, user_fields: nil, expansions: nil, tweet_fields: nil)
params = assemble_users_params(user_fields:, expansions:, tweet_fields:)
params["ids"] = Array(ids).join(",")
get("users?#{URI.encode_www_form(params)}")
end

# Retrieve a single user by username
#
# @param username [String] the username (without @ prefix)
# @param user_fields [Array<String>, String, nil] list of user fields to return
# @param expansions [Array<String>, String, nil] list of expansions to include
# @param tweet_fields [Array<String>, String, nil] list of tweet fields to return (requires pinned_tweet_id expansion)
# @return [Hash] the parsed response body
# @see https://docs.x.com/x-api/users/lookup/api-reference/get-users-by-username-username
# @example Retrieve a user by username
# client.user_by_username("sferik")
# @example Retrieve a user by username with fields
# client.user_by_username("sferik", user_fields: ["created_at", "description"])
def user_by_username(username, user_fields: nil, expansions: nil, tweet_fields: nil)
get(build_users_endpoint("users/by/username/#{username}", user_fields:, expansions:, tweet_fields:))
end

# Retrieve multiple users by their usernames
#
# @param usernames [Array<String>] list of usernames (up to 100, without @ prefix)
# @param user_fields [Array<String>, String, nil] list of user fields to return
# @param expansions [Array<String>, String, nil] list of expansions to include
# @param tweet_fields [Array<String>, String, nil] list of tweet fields to return (requires pinned_tweet_id expansion)
# @return [Hash] the parsed response body
# @see https://docs.x.com/x-api/users/lookup/api-reference/get-users-by
# @example Retrieve users by usernames
# client.users_by_usernames(usernames: ["sferik", "xdevelopers"])
# @example Retrieve users by usernames with fields
# client.users_by_usernames(usernames: ["sferik", "xdevelopers"], user_fields: ["created_at"])
def users_by_usernames(usernames:, user_fields: nil, expansions: nil, tweet_fields: nil)
params = assemble_users_params(user_fields:, expansions:, tweet_fields:)
params["usernames"] = Array(usernames).join(",")
get("users/by?#{URI.encode_www_form(params)}")
end

# Retrieve the authenticated user
#
# @param user_fields [Array<String>, String, nil] list of user fields to return
# @param expansions [Array<String>, String, nil] list of expansions to include
# @param tweet_fields [Array<String>, String, nil] list of tweet fields to return (requires pinned_tweet_id expansion)
# @return [Hash] the parsed response body
# @see https://docs.x.com/x-api/users/lookup/api-reference/get-users-me
# @example Retrieve the authenticated user
# client.me
# @example Retrieve the authenticated user with fields
# client.me(user_fields: ["created_at", "description", "public_metrics"])
def me(user_fields: nil, expansions: nil, tweet_fields: nil)
get(build_users_endpoint("users/me", user_fields:, expansions:, tweet_fields:))
end

private

# Build an endpoint path with optional query parameters
# @api private
def build_users_endpoint(path, user_fields: nil, expansions: nil, tweet_fields: nil)
params = assemble_users_params(user_fields:, expansions:, tweet_fields:)
return path if params.empty?

"#{path}?#{URI.encode_www_form(params)}"
end

# Assemble query parameters hash from field options
# @api private
def assemble_users_params(user_fields: nil, expansions: nil, tweet_fields: nil)
params = {}
params["user.fields"] = Array(user_fields).join(",") if user_fields
params["expansions"] = Array(expansions).join(",") if expansions
params["tweet.fields"] = Array(tweet_fields).join(",") if tweet_fields
params
end
end
end
171 changes: 171 additions & 0 deletions test/x/users_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
require_relative "../test_helper"

module X
class UsersTest < Minitest::Test
cover Users

BASE_URL = "https://api.twitter.com/2/"

def setup
@client = Client.new(bearer_token: TEST_BEARER_TOKEN)
@user_data = {"data" => {"id" => "123", "name" => "Test", "username" => "test"}}.freeze
@users_data = {"data" => [{"id" => "123", "name" => "Test", "username" => "test"}]}.freeze
@headers = {"Content-Type" => "application/json"}
end

def test_user
stub_request(:get, "#{BASE_URL}users/123")
.to_return(body: @user_data.to_json, headers: @headers)

result = @client.user("123")

assert_equal @user_data, result
end

def test_user_with_user_fields
stub_request(:get, "#{BASE_URL}users/123?user.fields=created_at,description")
.to_return(body: @user_data.to_json, headers: @headers)

result = @client.user("123", user_fields: %w[created_at description])

assert_equal @user_data, result
end

def test_user_with_expansions
stub_request(:get, "#{BASE_URL}users/123?expansions=pinned_tweet_id")
.to_return(body: @user_data.to_json, headers: @headers)

result = @client.user("123", expansions: ["pinned_tweet_id"])

assert_equal @user_data, result
end

def test_user_with_tweet_fields
stub_request(:get, "#{BASE_URL}users/123?tweet.fields=created_at,text")
.to_return(body: @user_data.to_json, headers: @headers)

result = @client.user("123", tweet_fields: %w[created_at text])

assert_equal @user_data, result
end

def test_user_with_all_fields
stub_request(:get, "#{BASE_URL}users/123?user.fields=created_at&expansions=pinned_tweet_id&tweet.fields=text")
.to_return(body: @user_data.to_json, headers: @headers)

result = @client.user("123", user_fields: ["created_at"], expansions: ["pinned_tweet_id"],
tweet_fields: ["text"])

assert_equal @user_data, result
end

def test_user_with_string_fields
stub_request(:get, "#{BASE_URL}users/123?user.fields=created_at")
.to_return(body: @user_data.to_json, headers: @headers)

result = @client.user("123", user_fields: "created_at")

assert_equal @user_data, result
end

def test_users
stub_request(:get, "#{BASE_URL}users?ids=123,456")
.to_return(body: @users_data.to_json, headers: @headers)

result = @client.users(ids: %w[123 456])

assert_equal @users_data, result
end

def test_users_with_user_fields
stub_request(:get, "#{BASE_URL}users?user.fields=created_at,description&ids=123,456")
.to_return(body: @users_data.to_json, headers: @headers)

result = @client.users(ids: %w[123 456], user_fields: %w[created_at description])

assert_equal @users_data, result
end

def test_users_with_single_id
stub_request(:get, "#{BASE_URL}users?ids=123")
.to_return(body: @users_data.to_json, headers: @headers)

result = @client.users(ids: "123")

assert_equal @users_data, result
end

def test_user_by_username
stub_request(:get, "#{BASE_URL}users/by/username/sferik")
.to_return(body: @user_data.to_json, headers: @headers)

result = @client.user_by_username("sferik")

assert_equal @user_data, result
end

def test_user_by_username_with_user_fields
stub_request(:get, "#{BASE_URL}users/by/username/sferik?user.fields=created_at,public_metrics")
.to_return(body: @user_data.to_json, headers: @headers)

result = @client.user_by_username("sferik", user_fields: %w[created_at public_metrics])

assert_equal @user_data, result
end

def test_users_by_usernames
stub_request(:get, "#{BASE_URL}users/by?usernames=sferik,xdevelopers")
.to_return(body: @users_data.to_json, headers: @headers)

result = @client.users_by_usernames(usernames: %w[sferik xdevelopers])

assert_equal @users_data, result
end

def test_users_by_usernames_with_user_fields
stub_request(:get, "#{BASE_URL}users/by?user.fields=created_at&usernames=sferik,xdevelopers")
.to_return(body: @users_data.to_json, headers: @headers)

result = @client.users_by_usernames(usernames: %w[sferik xdevelopers], user_fields: ["created_at"])

assert_equal @users_data, result
end

def test_users_by_usernames_with_single_username
stub_request(:get, "#{BASE_URL}users/by?usernames=sferik")
.to_return(body: @users_data.to_json, headers: @headers)

result = @client.users_by_usernames(usernames: "sferik")

assert_equal @users_data, result
end

def test_me
stub_request(:get, "#{BASE_URL}users/me")
.to_return(body: @user_data.to_json, headers: @headers)

result = @client.me

assert_equal @user_data, result
end

def test_me_with_user_fields
stub_request(:get, "#{BASE_URL}users/me?user.fields=created_at,description,public_metrics")
.to_return(body: @user_data.to_json, headers: @headers)

result = @client.me(user_fields: %w[created_at description public_metrics])

assert_equal @user_data, result
end

def test_me_with_all_fields
stub_request(:get, "#{BASE_URL}users/me?user.fields=created_at&expansions=pinned_tweet_id&tweet.fields=text")
.to_return(body: @user_data.to_json, headers: @headers)

result = @client.me(user_fields: ["created_at"], expansions: ["pinned_tweet_id"],
tweet_fields: ["text"])

assert_equal @user_data, result
end
end
end
Loading