twttr 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/lib/twttr/client/config.rb +15 -0
- data/lib/twttr/client/endpoint/v2/users/follows.rb +47 -0
- data/lib/twttr/client/endpoint/v2/users.rb +43 -0
- data/lib/twttr/client/endpoint/v2.rb +13 -0
- data/lib/twttr/client/endpoint.rb +11 -0
- data/lib/twttr/client/error.rb +15 -0
- data/lib/twttr/client/oauth_request.rb +84 -0
- data/lib/twttr/client.rb +51 -0
- data/lib/twttr/model/user.rb +27 -0
- data/lib/twttr/model.rb +9 -0
- data/lib/twttr.rb +8 -0
- metadata +82 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 0e5bcf2caf8b35685648f7ea2f2e3ded03f29a2d645c3fefd0dd201c40a29063
|
4
|
+
data.tar.gz: e4abe1c561235477fa18e53a99712f6c81dff499a084597f05c076a7db47bdc1
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ba330361302ab0f8cb28ab17235baae529674fd02b766a6e55c5e48b10fd960d682b4efcb05d594216cf40288606fb87f77b50fbe8f2ef66cfde75d2a075165a
|
7
|
+
data.tar.gz: '04890ed05e20b2e710d3a3f7697b21823542da9e8e88e35e9361a73251d89b1a18a12a0dddd3dbf031cf390a3d344894b775cb80daf1b37978993c8e2d3ad6f5'
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Twttr
|
4
|
+
class Client
|
5
|
+
# Client Configuration
|
6
|
+
class Config
|
7
|
+
attr_accessor :consumer_key, :consumer_secret, :access_token, :access_token_secret
|
8
|
+
attr_reader :user_fields
|
9
|
+
|
10
|
+
def user_fields=(fields)
|
11
|
+
@user_fields = fields.join(',')
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Twttr
|
4
|
+
class Client
|
5
|
+
module Endpoint
|
6
|
+
module V2
|
7
|
+
module Users
|
8
|
+
# Twitter API V2 User Follow related endpoints
|
9
|
+
# https://developer.twitter.com/en/docs/twitter-api/users/follows/api-reference
|
10
|
+
module Follows
|
11
|
+
FOLLOWING_PATH = "#{Users::USER_PATH}/following"
|
12
|
+
|
13
|
+
# GET /2/users/:id/following
|
14
|
+
# https://developer.twitter.com/en/docs/twitter-api/users/follows/api-reference/get-users-id-following
|
15
|
+
#
|
16
|
+
# @param user_id [String] The user ID whose following you would like to retrieve.
|
17
|
+
# @param max_results [Integer] Max number of results per peage.
|
18
|
+
# @param pagination_token [String] Initial page pagination token.
|
19
|
+
# @yield [Array<Twttr::Model::User>] Users followed by page.
|
20
|
+
# @return [Array<Twttr::Model::User>] Users followed.
|
21
|
+
# @return [String,NilClass] Pagination token.
|
22
|
+
def following(user_id, max_results: nil, pagination_token: nil) # rubocop:disable Metrics/MethodLength
|
23
|
+
loop do
|
24
|
+
response = get(FOLLOWING_PATH, params: { user_id: user_id },
|
25
|
+
query_params: {
|
26
|
+
'user.fields': config.user_fields,
|
27
|
+
max_results: max_results,
|
28
|
+
pagination_token: pagination_token
|
29
|
+
}.compact)
|
30
|
+
|
31
|
+
users = response['data'].map { |v| Model::User.new(v, self) }
|
32
|
+
|
33
|
+
pagination_token = response['meta']['pagination_token']
|
34
|
+
|
35
|
+
return users, pagination_token unless block_given?
|
36
|
+
|
37
|
+
yield users, pagination_token
|
38
|
+
|
39
|
+
break if pagination_token.nil?
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Twttr
|
4
|
+
class Client
|
5
|
+
module Endpoint
|
6
|
+
module V2
|
7
|
+
# Twitter API V2 Users related endpoints
|
8
|
+
# https://developer.twitter.com/en/docs/twitter-api/users/lookup/api-reference
|
9
|
+
module Users
|
10
|
+
ME_PATH = "#{V2::V2_PATH}/users/me"
|
11
|
+
USERS_PATH = "#{V2::V2_PATH}/users"
|
12
|
+
USER_BY_USERNAME_PATH = "#{V2::V2_PATH}/users/by/username/%<username>s"
|
13
|
+
USER_PATH = "#{V2::V2_PATH}/users/%<user_id>s"
|
14
|
+
|
15
|
+
def me
|
16
|
+
response = get(ME_PATH, query_params: { 'user.fields': config.user_fields })
|
17
|
+
Model::User.new(response['data'], self)
|
18
|
+
end
|
19
|
+
|
20
|
+
def user(user_id)
|
21
|
+
response = get(USER_PATH, params: { user_id: user_id },
|
22
|
+
query_params: { 'user.fields': config.user_fields })
|
23
|
+
Model::User.new(response['data'], self)
|
24
|
+
end
|
25
|
+
|
26
|
+
def user_by_username(username)
|
27
|
+
response = get(USER_BY_USERNAME_PATH, params: { username: username },
|
28
|
+
query_params: { 'user.fields': config.user_fields })
|
29
|
+
Model::User.new(response['data'], self)
|
30
|
+
end
|
31
|
+
|
32
|
+
def users(user_ids)
|
33
|
+
response = get(USERS_PATH,
|
34
|
+
query_params: { ids: user_ids.join(','), 'user.fields': config.user_fields })
|
35
|
+
response['data'].map { |v| Model::User.new(v, self) }
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
require_relative 'users/follows'
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Twttr
|
4
|
+
class Client
|
5
|
+
# OAuth helper methods
|
6
|
+
class OAuthRequest
|
7
|
+
attr_reader :response
|
8
|
+
|
9
|
+
def initialize(uri, config)
|
10
|
+
@uri = uri
|
11
|
+
@config = config
|
12
|
+
@request = Net::HTTP::Get.new(uri)
|
13
|
+
@request['Authorization'] = authorization_header
|
14
|
+
@response = nil
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.get(uri, config)
|
18
|
+
new(uri, config).perform
|
19
|
+
end
|
20
|
+
|
21
|
+
def perform
|
22
|
+
Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
|
23
|
+
# :nocov:
|
24
|
+
self.response = http.request request
|
25
|
+
# :nocov:
|
26
|
+
end
|
27
|
+
|
28
|
+
raise Error::HTTPError, response.message unless response.instance_of?(Net::HTTPOK)
|
29
|
+
|
30
|
+
response
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
attr_reader :config, :request, :uri
|
36
|
+
attr_writer :response
|
37
|
+
|
38
|
+
def oauth_params
|
39
|
+
@oauth_params ||= {
|
40
|
+
'oauth_consumer_key' => CGI.escape(config.consumer_key),
|
41
|
+
'oauth_nonce' => CGI.escape(SecureRandom.urlsafe_base64(32)),
|
42
|
+
'oauth_signature_method' => CGI.escape('HMAC-SHA1'),
|
43
|
+
'oauth_timestamp' => CGI.escape(Time.now.to_i.to_s),
|
44
|
+
'oauth_token' => CGI.escape(config.access_token),
|
45
|
+
'oauth_version' => CGI.escape('1.0')
|
46
|
+
}
|
47
|
+
end
|
48
|
+
|
49
|
+
def request_params
|
50
|
+
@request_params if defined?(@request_params)
|
51
|
+
|
52
|
+
query_params = {}
|
53
|
+
uri.query_params.each_pair do |key, value|
|
54
|
+
query_params[CGI.escape(key)] = CGI.escape(value)
|
55
|
+
end
|
56
|
+
|
57
|
+
@request_params = oauth_params.merge(query_params)
|
58
|
+
end
|
59
|
+
|
60
|
+
def authorization_header
|
61
|
+
oauth_params['oauth_signature'] = oauth_signature
|
62
|
+
|
63
|
+
serialized_params = oauth_params.keys.sort.map { |k| "#{k}=\"#{oauth_params[k]}\"" }.join(',')
|
64
|
+
|
65
|
+
"OAuth #{serialized_params}"
|
66
|
+
end
|
67
|
+
|
68
|
+
def oauth_signature
|
69
|
+
signature_params = request_params.keys.sort.map { |k| "#{k}=#{request_params[k]}" }.join('&')
|
70
|
+
|
71
|
+
base_string = "#{request.method}&#{CGI.escape(request.uri.to_s.sub(/\?.*$/,
|
72
|
+
''))}&#{CGI.escape(signature_params)}"
|
73
|
+
|
74
|
+
auth_code(base_string)
|
75
|
+
end
|
76
|
+
|
77
|
+
def auth_code(base_string)
|
78
|
+
signin_key = "#{CGI.escape(config.consumer_secret)}&#{CGI.escape(config.access_token_secret)}"
|
79
|
+
|
80
|
+
CGI.escape(Base64.encode64(OpenSSL::HMAC.digest('sha1', signin_key, base_string).to_s).chomp)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
data/lib/twttr/client.rb
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'forwardable'
|
4
|
+
require 'json'
|
5
|
+
require 'net/http'
|
6
|
+
require 'oauth'
|
7
|
+
require 'oauth/request_proxy/net_http'
|
8
|
+
require 'ostruct'
|
9
|
+
require 'securerandom'
|
10
|
+
require 'uri/query_params'
|
11
|
+
|
12
|
+
require 'twttr/client/config'
|
13
|
+
require 'twttr/client/endpoint'
|
14
|
+
require 'twttr/client/error'
|
15
|
+
require 'twttr/client/oauth_request'
|
16
|
+
|
17
|
+
require 'uri/generic'
|
18
|
+
|
19
|
+
module Twttr
|
20
|
+
# Twitter API Client
|
21
|
+
class Client
|
22
|
+
include Twttr::Client::Endpoint::V2::Users
|
23
|
+
include Twttr::Client::Endpoint::V2::Users::Follows
|
24
|
+
|
25
|
+
attr_reader :config
|
26
|
+
|
27
|
+
BASE_URL = 'https://api.twitter.com'
|
28
|
+
|
29
|
+
def initialize
|
30
|
+
@config = Config.new
|
31
|
+
yield config if block_given?
|
32
|
+
end
|
33
|
+
|
34
|
+
def get(path, params: {}, query_params: {})
|
35
|
+
uri = uri_for(path, params)
|
36
|
+
uri.query = URI.encode_www_form(query_params.compact) unless query_params.compact.empty?
|
37
|
+
|
38
|
+
response = OAuthRequest.get(uri, config)
|
39
|
+
|
40
|
+
JSON.parse(response.body)
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def uri_for(path, params = {})
|
46
|
+
return URI.parse("#{BASE_URL}#{path}") if params.empty?
|
47
|
+
|
48
|
+
URI.parse("#{BASE_URL}#{path}" % params) # rubocop:disable Style/FormatString
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Twttr
|
4
|
+
module Model
|
5
|
+
# Twitter User representation
|
6
|
+
# https://developer.twitter.com/en/docs/twitter-api/data-dictionary/object-model/user
|
7
|
+
class User
|
8
|
+
extend Forwardable
|
9
|
+
|
10
|
+
def_delegators :@data, :created_at, :description, :entities, :id, :location, :name, :pinned_tweet_id,
|
11
|
+
:profile_image_url, :protected, :public_metrics, :url, :username, :verified, :withheld
|
12
|
+
|
13
|
+
def initialize(data, client = nil)
|
14
|
+
@data = JSON.parse(data.to_json, object_class: OpenStruct)
|
15
|
+
@client = client
|
16
|
+
end
|
17
|
+
|
18
|
+
def following(pagination_token: nil, &block)
|
19
|
+
client.following(id, pagination_token: pagination_token, &block)
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
attr_reader :client
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
data/lib/twttr/model.rb
ADDED
data/lib/twttr.rb
ADDED
metadata
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: twttr
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Roberto Decurnex
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2021-12-29 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: oauth
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.5.8
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.5.8
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: uri-query_params
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 0.7.2
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 0.7.2
|
41
|
+
description: Modular Twitter API interface, initially targeting Twitter API v2
|
42
|
+
email: roberto@decurnex.io
|
43
|
+
executables: []
|
44
|
+
extensions: []
|
45
|
+
extra_rdoc_files: []
|
46
|
+
files:
|
47
|
+
- lib/twttr.rb
|
48
|
+
- lib/twttr/client.rb
|
49
|
+
- lib/twttr/client/config.rb
|
50
|
+
- lib/twttr/client/endpoint.rb
|
51
|
+
- lib/twttr/client/endpoint/v2.rb
|
52
|
+
- lib/twttr/client/endpoint/v2/users.rb
|
53
|
+
- lib/twttr/client/endpoint/v2/users/follows.rb
|
54
|
+
- lib/twttr/client/error.rb
|
55
|
+
- lib/twttr/client/oauth_request.rb
|
56
|
+
- lib/twttr/model.rb
|
57
|
+
- lib/twttr/model/user.rb
|
58
|
+
homepage: https://rubygems.org/gems/twttr
|
59
|
+
licenses:
|
60
|
+
- MIT
|
61
|
+
metadata:
|
62
|
+
rubygems_mfa_required: 'true'
|
63
|
+
post_install_message:
|
64
|
+
rdoc_options: []
|
65
|
+
require_paths:
|
66
|
+
- lib
|
67
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
68
|
+
requirements:
|
69
|
+
- - ">="
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
version: '2.7'
|
72
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - ">="
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '0'
|
77
|
+
requirements: []
|
78
|
+
rubygems_version: 3.2.33
|
79
|
+
signing_key:
|
80
|
+
specification_version: 4
|
81
|
+
summary: Twitter API v2 Interface
|
82
|
+
test_files: []
|