-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit fd8ea2b
Showing
24 changed files
with
818 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
*.gem | ||
*.swp | ||
.DS_Store | ||
Gemfile.lock | ||
coverage/* |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
AllCops: | ||
NewCops: enable | ||
Style/OpenStructUse: | ||
Enabled: false | ||
Metrics/MethodLength: | ||
Exclude: | ||
- 'test/**/*' | ||
Layout/LineLength: | ||
Exclude: | ||
- 'test/**/*' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
2.7.5 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
# frozen_string_literal: true | ||
|
||
source 'https://rubygems.org' | ||
|
||
gemspec | ||
|
||
gem 'rubocop', '~> 1.23' | ||
|
||
gem 'simplecov', '~> 0.21.2', require: false, group: :test |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
# Twttr | ||
Twitter API v2 Interface | ||
|
||
## Summery | ||
Modular Twitter API interface, initially targeting Twitter API v2 | ||
|
||
## Draft Interface Examples | ||
|
||
### Config | ||
```ruby | ||
# Creating a Client using OAuth 1.0a User context | ||
client = Twttr::Client.new(user_id) do |config| | ||
# App credentials | ||
config.consumer_key = "consumer_key" | ||
config.consumer_secret = "consumer_secret" | ||
|
||
# User credentials | ||
config.access_token = "access_token" | ||
config.access_token_secret = "access_secret" | ||
|
||
# Default user fields | ||
# https://developer.twitter.com/en/docs/twitter-api/data-dictionary/object-model/user | ||
config.user_fields = %w(id, name, username) | ||
end #=> #<Twttr::Client> | ||
``` | ||
|
||
### Client | ||
```ruby | ||
# Twttr::Client#me | ||
client.me #=> #<Twttr::Model::User> | ||
|
||
#Twttr::Client#user(:user_id) | ||
client.user("user_id") #=> #<Twttr::Model::User> | ||
|
||
#Twttr::Client#user_by_username(:username) | ||
client.user_by_username("username") #=> #<Twttr::Model::User> | ||
|
||
#Twttr::Client#users(:user_ids) | ||
client.users(["user_id_1", "user_id_2"]) #=> [#<Twttr::Model::User>] | ||
``` | ||
### User | ||
```ruby | ||
# Twttr::Model::User#following | ||
# | ||
# Yields each page of users | ||
user.following do |users, pagination_token| | ||
users #=> [#<Twttr::Model::User>] | ||
end | ||
|
||
# First page of users | ||
user.following #=> [#<Twttr::Model::User>], pagination_token | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
# frozen_string_literal: true | ||
|
||
require 'rake/testtask' | ||
|
||
Rake::TestTask.new do |t| | ||
t.libs << 'test' | ||
t.test_files = FileList['test/**/test_*.rb'] | ||
t.verbose = true | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
# frozen_string_literal: true | ||
|
||
require 'twttr/client' | ||
require 'twttr/model' | ||
|
||
# Library namespace | ||
module Twttr | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
# frozen_string_literal: true | ||
|
||
require 'forwardable' | ||
require 'json' | ||
require 'net/http' | ||
require 'oauth' | ||
require 'oauth/request_proxy/net_http' | ||
require 'ostruct' | ||
require 'securerandom' | ||
require 'uri/query_params' | ||
|
||
require 'twttr/client/config' | ||
require 'twttr/client/endpoint' | ||
require 'twttr/client/error' | ||
require 'twttr/client/oauth_request' | ||
|
||
require 'uri/generic' | ||
|
||
module Twttr | ||
# Twitter API Client | ||
class Client | ||
include Twttr::Client::Endpoint::V2::Users | ||
include Twttr::Client::Endpoint::V2::Users::Follows | ||
|
||
attr_reader :config | ||
|
||
BASE_URL = 'https://api.twitter.com' | ||
|
||
def initialize | ||
@config = Config.new | ||
yield config if block_given? | ||
end | ||
|
||
def get(path, params: {}, query_params: {}) | ||
uri = uri_for(path, params) | ||
uri.query = URI.encode_www_form(query_params.compact) unless query_params.compact.empty? | ||
|
||
response = OAuthRequest.get(uri, config) | ||
|
||
JSON.parse(response.body) | ||
end | ||
|
||
private | ||
|
||
def uri_for(path, params = {}) | ||
return URI.parse("#{BASE_URL}#{path}") if params.empty? | ||
|
||
URI.parse("#{BASE_URL}#{path}" % params) # rubocop:disable Style/FormatString | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
# frozen_string_literal: true | ||
|
||
module Twttr | ||
class Client | ||
# Client Configuration | ||
class Config | ||
attr_accessor :consumer_key, :consumer_secret, :access_token, :access_token_secret | ||
attr_reader :user_fields | ||
|
||
def user_fields=(fields) | ||
@user_fields = fields.join(',') | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
# frozen_string_literal: true | ||
|
||
require 'twttr/client/endpoint/v2' | ||
|
||
module Twttr | ||
class Client | ||
# Namespace to keep endpoints organized by version and domain. | ||
module Endpoint | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
# frozen_string_literal: true | ||
|
||
module Twttr | ||
class Client | ||
module Endpoint | ||
module V2 | ||
V2_PATH = '/2' | ||
end | ||
end | ||
end | ||
end | ||
|
||
require_relative 'v2/users' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
# frozen_string_literal: true | ||
|
||
module Twttr | ||
class Client | ||
module Endpoint | ||
module V2 | ||
# Twitter API V2 Users related endpoints | ||
# https://developer.twitter.com/en/docs/twitter-api/users/lookup/api-reference | ||
module Users | ||
ME_PATH = "#{V2::V2_PATH}/users/me" | ||
USERS_PATH = "#{V2::V2_PATH}/users" | ||
USER_BY_USERNAME_PATH = "#{V2::V2_PATH}/users/by/username/%<username>s" | ||
USER_PATH = "#{V2::V2_PATH}/users/%<user_id>s" | ||
|
||
def me | ||
response = get(ME_PATH, query_params: { 'user.fields': config.user_fields }) | ||
Model::User.new(response['data'], self) | ||
end | ||
|
||
def user(user_id) | ||
response = get(USER_PATH, params: { user_id: user_id }, | ||
query_params: { 'user.fields': config.user_fields }) | ||
Model::User.new(response['data'], self) | ||
end | ||
|
||
def user_by_username(username) | ||
response = get(USER_BY_USERNAME_PATH, params: { username: username }, | ||
query_params: { 'user.fields': config.user_fields }) | ||
Model::User.new(response['data'], self) | ||
end | ||
|
||
def users(user_ids) | ||
response = get(USERS_PATH, | ||
query_params: { ids: user_ids.join(','), 'user.fields': config.user_fields }) | ||
response['data'].map { |v| Model::User.new(v, self) } | ||
end | ||
end | ||
end | ||
end | ||
end | ||
end | ||
|
||
require_relative 'users/follows' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
# frozen_string_literal: true | ||
|
||
module Twttr | ||
class Client | ||
module Endpoint | ||
module V2 | ||
module Users | ||
# Twitter API V2 User Follow related endpoints | ||
# https://developer.twitter.com/en/docs/twitter-api/users/follows/api-reference | ||
module Follows | ||
FOLLOWING_PATH = "#{Users::USER_PATH}/following" | ||
|
||
# GET /2/users/:id/following | ||
# https://developer.twitter.com/en/docs/twitter-api/users/follows/api-reference/get-users-id-following | ||
# | ||
# @param user_id [String] The user ID whose following 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<Twttr::Model::User>] Users followed by page. | ||
# @return [Array<Twttr::Model::User>] Users followed. | ||
# @return [String,NilClass] Pagination token. | ||
def following(user_id, max_results: nil, pagination_token: nil) # rubocop:disable Metrics/MethodLength | ||
loop do | ||
response = get(FOLLOWING_PATH, params: { user_id: user_id }, | ||
query_params: { | ||
'user.fields': config.user_fields, | ||
max_results: max_results, | ||
pagination_token: pagination_token | ||
}.compact) | ||
|
||
users = response['data'].map { |v| Model::User.new(v, self) } | ||
|
||
pagination_token = response['meta']['pagination_token'] | ||
|
||
return users, pagination_token unless block_given? | ||
|
||
yield users, pagination_token | ||
|
||
break if pagination_token.nil? | ||
end | ||
end | ||
end | ||
end | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
# frozen_string_literal: true | ||
|
||
module Twttr | ||
class Client | ||
# Client rrrors namesace | ||
module Error | ||
# HTTP related errors | ||
class HTTPError < StandardError | ||
def initialize(msg = 'HTTP Error') | ||
super | ||
end | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
# frozen_string_literal: true | ||
|
||
module Twttr | ||
class Client | ||
# OAuth helper methods | ||
class OAuthRequest | ||
attr_reader :response | ||
|
||
def initialize(uri, config) | ||
@uri = uri | ||
@config = config | ||
@request = Net::HTTP::Get.new(uri) | ||
@request['Authorization'] = authorization_header | ||
@response = nil | ||
end | ||
|
||
def self.get(uri, config) | ||
new(uri, config).perform | ||
end | ||
|
||
def perform | ||
Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http| | ||
# :nocov: | ||
self.response = http.request request | ||
# :nocov: | ||
end | ||
|
||
raise Error::HTTPError, response.message unless response.instance_of?(Net::HTTPOK) | ||
|
||
response | ||
end | ||
|
||
private | ||
|
||
attr_reader :config, :request, :uri | ||
attr_writer :response | ||
|
||
def oauth_params | ||
@oauth_params ||= { | ||
'oauth_consumer_key' => CGI.escape(config.consumer_key), | ||
'oauth_nonce' => CGI.escape(SecureRandom.urlsafe_base64(32)), | ||
'oauth_signature_method' => CGI.escape('HMAC-SHA1'), | ||
'oauth_timestamp' => CGI.escape(Time.now.to_i.to_s), | ||
'oauth_token' => CGI.escape(config.access_token), | ||
'oauth_version' => CGI.escape('1.0') | ||
} | ||
end | ||
|
||
def request_params | ||
@request_params if defined?(@request_params) | ||
|
||
query_params = {} | ||
uri.query_params.each_pair do |key, value| | ||
query_params[CGI.escape(key)] = CGI.escape(value) | ||
end | ||
|
||
@request_params = oauth_params.merge(query_params) | ||
end | ||
|
||
def authorization_header | ||
oauth_params['oauth_signature'] = oauth_signature | ||
|
||
serialized_params = oauth_params.keys.sort.map { |k| "#{k}=\"#{oauth_params[k]}\"" }.join(',') | ||
|
||
"OAuth #{serialized_params}" | ||
end | ||
|
||
def oauth_signature | ||
signature_params = request_params.keys.sort.map { |k| "#{k}=#{request_params[k]}" }.join('&') | ||
|
||
base_string = "#{request.method}&#{CGI.escape(request.uri.to_s.sub(/\?.*$/, | ||
''))}&#{CGI.escape(signature_params)}" | ||
|
||
auth_code(base_string) | ||
end | ||
|
||
def auth_code(base_string) | ||
signin_key = "#{CGI.escape(config.consumer_secret)}&#{CGI.escape(config.access_token_secret)}" | ||
|
||
CGI.escape(Base64.encode64(OpenSSL::HMAC.digest('sha1', signin_key, base_string).to_s).chomp) | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
# frozen_string_literal: true | ||
|
||
require 'twttr/model/user' | ||
|
||
module Twttr | ||
# Ruby representations of Twitter entities | ||
module Model | ||
end | ||
end |
Oops, something went wrong.