twurl 0.9.1 → 0.9.6

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.
@@ -9,15 +9,27 @@ module Twurl
9
9
  end
10
10
 
11
11
  def load_from_options(options)
12
- if rcfile.has_oauth_profile_for_username_with_consumer_key?(options.username, options.consumer_key)
12
+ if options.command == 'request' && has_oauth_options?(options)
13
+ load_new_client_from_oauth_options(options)
14
+ elsif options.command == 'request' && options.app_only && options.consumer_key
15
+ load_client_for_non_profile_app_only_auth(options)
16
+ elsif rcfile.has_oauth_profile_for_username_with_consumer_key?(options.username, options.consumer_key)
13
17
  load_client_for_username_and_consumer_key(options.username, options.consumer_key)
14
- elsif options.username || (options.command == 'authorize')
18
+ elsif options.username
19
+ load_client_for_username(options.username)
20
+ elsif options.command == 'authorize' && options.app_only
21
+ load_client_for_app_only_auth(options, options.consumer_key)
22
+ elsif options.command == 'authorize'
15
23
  load_new_client_from_options(options)
16
24
  else
17
- load_default_client
25
+ load_default_client(options)
18
26
  end
19
27
  end
20
28
 
29
+ def has_oauth_options?(options)
30
+ (options.consumer_key && options.consumer_secret && options.access_token && options.token_secret) ? true : false
31
+ end
32
+
21
33
  def load_client_for_username_and_consumer_key(username, consumer_key)
22
34
  user_profiles = rcfile[username]
23
35
  if user_profiles && attributes = user_profiles[consumer_key]
@@ -40,21 +52,63 @@ module Twurl
40
52
  end
41
53
 
42
54
  def load_new_client_from_options(options)
43
- new(options.oauth_client_options.merge('password' => options.password))
55
+ new(options.oauth_client_options)
56
+ end
57
+
58
+ def load_new_client_from_oauth_options(options)
59
+ new(options.oauth_client_options.merge(
60
+ 'token' => options.access_token,
61
+ 'secret' => options.token_secret
62
+ )
63
+ )
64
+ end
65
+
66
+ def load_client_for_app_only_auth(options, consumer_key)
67
+ if options.command == 'authorize'
68
+ AppOnlyOAuthClient.new(options)
69
+ else
70
+ AppOnlyOAuthClient.new(
71
+ options.oauth_client_options.merge(
72
+ 'bearer_token' => rcfile.bearer_tokens.to_hash[consumer_key]
73
+ )
74
+ )
75
+ end
44
76
  end
45
77
 
46
- def load_default_client
47
- raise Exception, "You must authorize first" unless rcfile.default_profile
48
- load_client_for_username_and_consumer_key(*rcfile.default_profile)
78
+ def load_client_for_non_profile_app_only_auth(options)
79
+ AppOnlyOAuthClient.new(
80
+ options.oauth_client_options.merge(
81
+ 'bearer_token' => rcfile.bearer_tokens.to_hash[options.consumer_key]
82
+ )
83
+ )
84
+ end
85
+
86
+ def load_default_client(options)
87
+ return if options.command == 'bearer_tokens'
88
+
89
+ exception_message = "You must authorize first."
90
+ app_only_exception_message = "To use --bearer option, you need to authorize (OAuth1.0a) and create at least one user profile (~/.twurlrc):\n\n" \
91
+ "twurl authorize -c key -s secret\n" \
92
+ "\nor, you can specify issued token's consumer_key directly:\n" \
93
+ "(to see your issued tokens: 'twurl bearer_tokens')\n\n" \
94
+ "twurl --bearer -c key '/path/to/api'"
95
+
96
+ raise Exception, "#{options.app_only ? app_only_exception_message : exception_message}" unless rcfile.default_profile
97
+ if options.app_only
98
+ raise Exception, "No available bearer token found for consumer_key:#{rcfile.default_profile_consumer_key}" \
99
+ unless rcfile.has_bearer_token_for_consumer_key?(rcfile.default_profile_consumer_key)
100
+ load_client_for_app_only_auth(options, rcfile.default_profile_consumer_key)
101
+ else
102
+ load_client_for_username_and_consumer_key(*rcfile.default_profile)
103
+ end
49
104
  end
50
105
  end
51
106
 
52
107
  OAUTH_CLIENT_OPTIONS = %w[username consumer_key consumer_secret token secret]
53
108
  attr_reader *OAUTH_CLIENT_OPTIONS
54
- attr_reader :username, :password
109
+ attr_reader :username
55
110
  def initialize(options = {})
56
111
  @username = options['username']
57
- @password = options['password']
58
112
  @consumer_key = options['consumer_key']
59
113
  @consumer_secret = options['consumer_secret']
60
114
  @token = options['token']
@@ -72,13 +126,14 @@ module Twurl
72
126
  :copy => Net::HTTP::Copy
73
127
  }
74
128
 
75
- def perform_request_from_options(options, &block)
129
+ def build_request_from_options(options, &block)
76
130
  request_class = METHODS.fetch(options.request_method.to_sym)
77
131
  request = request_class.new(options.path, options.headers)
78
132
 
79
133
  if options.upload && options.upload['file'].count > 0
80
134
  boundary = "00Twurl" + rand(1000000000000000000).to_s + "lruwT99"
81
135
  multipart_body = []
136
+ file_field = options.upload['filefield'] ? options.upload['filefield'] : 'media[]'
82
137
 
83
138
  options.data.each {|key, value|
84
139
  multipart_body << "--#{boundary}\r\n"
@@ -90,34 +145,53 @@ module Twurl
90
145
 
91
146
  options.upload['file'].each {|filename|
92
147
  multipart_body << "--#{boundary}\r\n"
93
- multipart_body << "Content-Disposition: form-data; name=\"#{options.upload['filefield']}\"; filename=\"#{File.basename(filename)}\"\r\n"
148
+ multipart_body << "Content-Disposition: form-data; name=\"#{file_field}\"; filename=\"#{File.basename(filename)}\"\r\n"
94
149
  multipart_body << "Content-Type: application/octet-stream\r\n"
95
150
  multipart_body << "Content-Transfer-Encoding: base64\r\n" if options.upload['base64']
96
151
  multipart_body << "\r\n"
97
152
 
98
153
  if options.upload['base64']
99
- enc = Base64.encode64(File.read(filename))
154
+ enc = Base64.encode64(File.binread(filename))
100
155
  multipart_body << enc
101
- else
102
- multipart_body << File.read(filename)
156
+ else
157
+ multipart_body << File.binread(filename)
103
158
  end
104
159
  }
105
160
 
106
161
  multipart_body << "\r\n--#{boundary}--\r\n"
107
-
162
+
108
163
  request.body = multipart_body.join
109
- request['Content-Type'] = "multipart/form-data, boundary=\"#{boundary}\""
164
+ request.content_type = "multipart/form-data, boundary=\"#{boundary}\""
165
+ elsif request.content_type && options.data
166
+ request.body = options.data.keys.first
110
167
  elsif options.data
111
- request.set_form_data(options.data)
168
+ request.content_type = "application/x-www-form-urlencoded"
169
+ if options.data.length == 1 && options.data.values.first == nil
170
+ request.body = options.data.keys.first
171
+ else
172
+ request.body = options.data.map do |key, value|
173
+ "#{key}=#{CGI.escape value}"
174
+ end.join("&")
175
+ end
112
176
  end
177
+ request
178
+ end
113
179
 
180
+ def perform_request_from_options(options, &block)
181
+ request = build_request_from_options(options)
114
182
  request.oauth!(consumer.http, consumer, access_token)
183
+ request['user-agent'] = user_agent
115
184
  consumer.http.request(request, &block)
116
185
  end
117
186
 
187
+ def user_agent
188
+ "twurl version: #{Version} " \
189
+ "platform: #{RUBY_ENGINE} #{RUBY_VERSION} (#{RUBY_PLATFORM})"
190
+ end
191
+
118
192
  def exchange_credentials_for_access_token
119
193
  response = begin
120
- consumer.token_request(:post, consumer.access_token_path, nil, {}, client_auth_parameters)
194
+ consumer.token_request(:post, consumer.access_token_path, nil, {})
121
195
  rescue OAuth::Unauthorized
122
196
  perform_pin_authorize_workflow
123
197
  end
@@ -125,14 +199,10 @@ module Twurl
125
199
  @secret = response[:oauth_token_secret]
126
200
  end
127
201
 
128
- def client_auth_parameters
129
- {'x_auth_username' => username, 'x_auth_password' => password, 'x_auth_mode' => 'client_auth'}
130
- end
131
-
132
202
  def perform_pin_authorize_workflow
133
203
  @request_token = consumer.get_request_token
134
204
  CLI.puts("Go to #{generate_authorize_url} and paste in the supplied PIN")
135
- pin = gets
205
+ pin = STDIN.gets
136
206
  access_token = @request_token.get_access_token(:oauth_verifier => pin.chomp)
137
207
  {:oauth_token => access_token.token, :oauth_token_secret => access_token.secret}
138
208
  end
@@ -188,6 +258,10 @@ module Twurl
188
258
 
189
259
  def configure_http!
190
260
  consumer.http.set_debug_output(Twurl.options.debug_output_io) if Twurl.options.trace
261
+ consumer.http.read_timeout = consumer.http.open_timeout = Twurl.options.timeout || 60
262
+ consumer.http.open_timeout = Twurl.options.connection_timeout if Twurl.options.connection_timeout
263
+ # Only override if Net::HTTP support max_retries (since Ruby >= 2.5)
264
+ consumer.http.max_retries = 0 if consumer.http.respond_to?(:max_retries=)
191
265
  if Twurl.options.ssl?
192
266
  consumer.http.use_ssl = true
193
267
  consumer.http.verify_mode = OpenSSL::SSL::VERIFY_NONE
@@ -52,6 +52,11 @@ module Twurl
52
52
  configuration['default_profile']
53
53
  end
54
54
 
55
+ def default_profile_consumer_key
56
+ username, consumer_key = configuration['default_profile']
57
+ consumer_key
58
+ end
59
+
55
60
  def default_profile=(profile)
56
61
  configuration['default_profile'] = [profile.username, profile.consumer_key]
57
62
  end
@@ -67,7 +72,17 @@ module Twurl
67
72
  end
68
73
 
69
74
  def aliases
70
- data['aliases']
75
+ data['aliases'] ||= {}
76
+ end
77
+
78
+ def bearer_token(consumer_key, bearer_token)
79
+ data['bearer_tokens'] ||= {}
80
+ data['bearer_tokens'][consumer_key] = bearer_token
81
+ save
82
+ end
83
+
84
+ def bearer_tokens
85
+ data['bearer_tokens']
71
86
  end
72
87
 
73
88
  def alias_from_options(options)
@@ -87,6 +102,10 @@ module Twurl
87
102
  !user_profiles.nil? && !user_profiles[consumer_key].nil?
88
103
  end
89
104
 
105
+ def has_bearer_token_for_consumer_key?(consumer_key)
106
+ bearer_tokens.nil? ? false : bearer_tokens.to_hash.has_key?(consumer_key)
107
+ end
108
+
90
109
  def <<(oauth_client)
91
110
  client_from_file = self[oauth_client.username] || {}
92
111
  client_from_file[oauth_client.consumer_key] = oauth_client.to_hash
@@ -1,20 +1,41 @@
1
1
  module Twurl
2
2
  class RequestController < AbstractCommandController
3
- NO_URI_MESSAGE = "No URI specified"
3
+ NO_URI_MESSAGE = 'No URI specified'
4
+ INVALID_URI_MESSAGE = 'Invalid URI detected'
5
+ READ_TIMEOUT_MESSAGE = 'A timeout occurred (Net::ReadTimeout). ' \
6
+ 'Please try again or increase the value using --timeout option.'
7
+ OPEN_TIMEOUT_MESSAGE = 'A timeout occurred (Net::OpenTimeout). ' \
8
+ 'Please try again or increase the value using --connection-timeout option.'
4
9
  def dispatch
5
10
  if client.needs_to_authorize?
6
11
  raise Exception, "You need to authorize first."
7
12
  end
8
13
  options.path ||= OAuthClient.rcfile.alias_from_options(options)
14
+ raise Exception, NO_URI_MESSAGE if options.path.empty?
9
15
  perform_request
10
16
  end
11
17
 
12
18
  def perform_request
13
19
  client.perform_request_from_options(options) { |response|
14
- response.read_body { |chunk| CLI.print chunk }
20
+ response.read_body { |body|
21
+ CLI.print options.json_format ? JsonFormatter.format(body) : body
22
+ }
15
23
  }
16
24
  rescue URI::InvalidURIError
17
- CLI.puts NO_URI_MESSAGE
25
+ CLI.puts INVALID_URI_MESSAGE
26
+ rescue Net::ReadTimeout
27
+ CLI.puts READ_TIMEOUT_MESSAGE
28
+ rescue Net::OpenTimeout
29
+ CLI.puts OPEN_TIMEOUT_MESSAGE
30
+ end
31
+ end
32
+
33
+ class JsonFormatter
34
+ def self.format(string)
35
+ json = JSON.parse(string)
36
+ (json.is_a?(Array) || json.is_a?(Hash)) ? JSON.pretty_generate(json) : string
37
+ rescue JSON::ParserError, TypeError
38
+ string
18
39
  end
19
40
  end
20
41
  end
@@ -2,13 +2,13 @@ module Twurl
2
2
  class Version
3
3
  MAJOR = 0 unless defined? Twurl::Version::MAJOR
4
4
  MINOR = 9 unless defined? Twurl::Version::MINOR
5
- PATCH = 1 unless defined? Twurl::Version::PATCH
6
- BETA = nil unless defined? Twurl::Version::BETA # Time.now.to_i.to_s
5
+ PATCH = 6 unless defined? Twurl::Version::PATCH
6
+ PRE = nil unless defined? Twurl::Version::PRE # Time.now.to_i.to_s
7
7
 
8
8
  class << self
9
9
  # @return [String]
10
10
  def to_s
11
- [MAJOR, MINOR, PATCH, BETA].compact.join('.')
11
+ [MAJOR, MINOR, PATCH, PRE].compact.join('.')
12
12
  end
13
13
  end
14
14
  end
@@ -5,21 +5,18 @@ require 'twurl/version'
5
5
 
6
6
  Gem::Specification.new do |spec|
7
7
  spec.add_dependency 'oauth', '~> 0.4'
8
- spec.add_development_dependency 'bundler', '~> 1.0'
9
- spec.authors = ["Marcel Molina", "Erik Michaels-Ober"]
8
+ spec.authors = ["Marcel Molina", "Erik Michaels-Ober", "@TwitterDev team"]
10
9
  spec.description = %q{Curl for the Twitter API}
11
- spec.email = ['marcel@twitter.com']
12
- spec.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
13
- spec.extra_rdoc_files = %w(COPYING INSTALL README)
14
- spec.files = `git ls-files`.split("\n")
15
- spec.homepage = 'http://github.com/marcel/twurl'
10
+ spec.bindir = 'bin'
11
+ spec.executables << 'twurl'
12
+ spec.extra_rdoc_files = Dir["*.md", "LICENSE"]
13
+ spec.files = Dir["*.md", "LICENSE", "twurl.gemspec", "bin/*", "lib/**/*"]
14
+ spec.homepage = 'http://github.com/twitter/twurl'
16
15
  spec.licenses = ['MIT']
17
16
  spec.name = 'twurl'
18
- spec.rdoc_options = ['--title', 'twurl -- OAuth-enabled curl for the Twitter API', '--main', 'README', '--line-numbers', '--inline-source']
17
+ spec.rdoc_options = ['--title', 'twurl -- OAuth-enabled curl for the Twitter API', '--main', 'README.md', '--line-numbers', '--inline-source']
19
18
  spec.require_paths = ['lib']
20
- spec.required_rubygems_version = '>= 1.3.5'
21
- spec.rubyforge_project = 'twurl'
19
+ spec.required_ruby_version = '>= 2.4.0'
22
20
  spec.summary = spec.description
23
- spec.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
24
21
  spec.version = Twurl::Version
25
22
  end
metadata CHANGED
@@ -1,73 +1,55 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: twurl
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.1
5
- prerelease:
4
+ version: 0.9.6
6
5
  platform: ruby
7
6
  authors:
8
7
  - Marcel Molina
9
8
  - Erik Michaels-Ober
10
- autorequire:
9
+ - "@TwitterDev team"
10
+ autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2013-12-02 00:00:00.000000000 Z
13
+ date: 2020-08-27 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: oauth
17
17
  requirement: !ruby/object:Gem::Requirement
18
- none: false
19
18
  requirements:
20
- - - ~>
19
+ - - "~>"
21
20
  - !ruby/object:Gem::Version
22
21
  version: '0.4'
23
22
  type: :runtime
24
23
  prerelease: false
25
24
  version_requirements: !ruby/object:Gem::Requirement
26
- none: false
27
25
  requirements:
28
- - - ~>
26
+ - - "~>"
29
27
  - !ruby/object:Gem::Version
30
28
  version: '0.4'
31
- - !ruby/object:Gem::Dependency
32
- name: bundler
33
- requirement: !ruby/object:Gem::Requirement
34
- none: false
35
- requirements:
36
- - - ~>
37
- - !ruby/object:Gem::Version
38
- version: '1.0'
39
- type: :development
40
- prerelease: false
41
- version_requirements: !ruby/object:Gem::Requirement
42
- none: false
43
- requirements:
44
- - - ~>
45
- - !ruby/object:Gem::Version
46
- version: '1.0'
47
29
  description: Curl for the Twitter API
48
30
  email:
49
- - marcel@twitter.com
50
31
  executables:
51
32
  - twurl
52
33
  extensions: []
53
34
  extra_rdoc_files:
54
- - COPYING
55
- - INSTALL
56
- - README
35
+ - CODE_OF_CONDUCT.md
36
+ - INSTALL.md
37
+ - README.md
38
+ - CONTRIBUTING.md
39
+ - LICENSE
57
40
  files:
58
- - .gemtest
59
- - .gitignore
60
- - .travis.yml
61
- - COPYING
62
- - Gemfile
63
- - INSTALL
64
- - README
65
- - Rakefile
41
+ - CODE_OF_CONDUCT.md
42
+ - CONTRIBUTING.md
43
+ - INSTALL.md
44
+ - LICENSE
45
+ - README.md
66
46
  - bin/twurl
67
47
  - lib/twurl.rb
68
48
  - lib/twurl/abstract_command_controller.rb
69
49
  - lib/twurl/account_information_controller.rb
70
50
  - lib/twurl/aliases_controller.rb
51
+ - lib/twurl/app_only_oauth_client.rb
52
+ - lib/twurl/app_only_token_information_controller.rb
71
53
  - lib/twurl/authorization_controller.rb
72
54
  - lib/twurl/cli.rb
73
55
  - lib/twurl/configuration_controller.rb
@@ -75,57 +57,34 @@ files:
75
57
  - lib/twurl/rcfile.rb
76
58
  - lib/twurl/request_controller.rb
77
59
  - lib/twurl/version.rb
78
- - test/account_information_controller_test.rb
79
- - test/alias_controller_test.rb
80
- - test/authorization_controller_test.rb
81
- - test/cli_options_test.rb
82
- - test/cli_test.rb
83
- - test/configuration_controller_test.rb
84
- - test/oauth_client_test.rb
85
- - test/rcfile_test.rb
86
- - test/request_controller_test.rb
87
- - test/test_helper.rb
88
60
  - twurl.gemspec
89
- homepage: http://github.com/marcel/twurl
61
+ homepage: http://github.com/twitter/twurl
90
62
  licenses:
91
63
  - MIT
92
- post_install_message:
64
+ metadata: {}
65
+ post_install_message:
93
66
  rdoc_options:
94
- - --title
67
+ - "--title"
95
68
  - twurl -- OAuth-enabled curl for the Twitter API
96
- - --main
97
- - README
98
- - --line-numbers
99
- - --inline-source
69
+ - "--main"
70
+ - README.md
71
+ - "--line-numbers"
72
+ - "--inline-source"
100
73
  require_paths:
101
74
  - lib
102
75
  required_ruby_version: !ruby/object:Gem::Requirement
103
- none: false
104
76
  requirements:
105
- - - ! '>='
77
+ - - ">="
106
78
  - !ruby/object:Gem::Version
107
- version: '0'
79
+ version: 2.4.0
108
80
  required_rubygems_version: !ruby/object:Gem::Requirement
109
- none: false
110
81
  requirements:
111
- - - ! '>='
82
+ - - ">="
112
83
  - !ruby/object:Gem::Version
113
- version: 1.3.5
84
+ version: '0'
114
85
  requirements: []
115
- rubyforge_project: twurl
116
- rubygems_version: 1.8.23
117
- signing_key:
118
- specification_version: 3
86
+ rubygems_version: 3.0.6
87
+ signing_key:
88
+ specification_version: 4
119
89
  summary: Curl for the Twitter API
120
- test_files:
121
- - test/account_information_controller_test.rb
122
- - test/alias_controller_test.rb
123
- - test/authorization_controller_test.rb
124
- - test/cli_options_test.rb
125
- - test/cli_test.rb
126
- - test/configuration_controller_test.rb
127
- - test/oauth_client_test.rb
128
- - test/rcfile_test.rb
129
- - test/request_controller_test.rb
130
- - test/test_helper.rb
131
- has_rdoc:
90
+ test_files: []