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 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,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Twttr
4
+ class Client
5
+ module Endpoint
6
+ module V2
7
+ V2_PATH = '/2'
8
+ end
9
+ end
10
+ end
11
+ end
12
+
13
+ require_relative 'v2/users'
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'twttr/client/endpoint/v2'
4
+
5
+ module Twttr
6
+ class Client
7
+ #  Namespace to keep endpoints organized by version and domain.
8
+ module Endpoint
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Twttr
4
+ class Client
5
+ #  Client rrrors namesace
6
+ module Error
7
+ #  HTTP related errors
8
+ class HTTPError < StandardError
9
+ def initialize(msg = 'HTTP Error')
10
+ super
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -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
@@ -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
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'twttr/model/user'
4
+
5
+ module Twttr
6
+ # Ruby representations of Twitter entities
7
+ module Model
8
+ end
9
+ end
data/lib/twttr.rb ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'twttr/client'
4
+ require 'twttr/model'
5
+
6
+ #  Library namespace
7
+ module Twttr
8
+ end
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: []