twurl 0.9.4 → 0.9.5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dee149b8c4ba5db925b3aa0a52182185fa432e316f72165f66b7dd088a3772a9
4
- data.tar.gz: 612f7f0a9bbda2235f25247cb24861d851b318197355de88de0d34eff882a850
3
+ metadata.gz: 833ceb46ed18d449d282d4ef9ae820c31887ffe617a9d717f0319ba72071dc47
4
+ data.tar.gz: 59e6d7960c134eb4815108da8da7f7118c14e61cef3e6b322bb78ba2fff2b313
5
5
  SHA512:
6
- metadata.gz: b6f1457730a541173b6a0537cf32501436e06fa3b0672cd0e81660eebaa05df7d1f4f46213ed7fd7d23896a7ffeeee0c9442bc3defdf9ae92c72b1a81232eea1
7
- data.tar.gz: 9a06241334f788f2226b3ef1adc886cf0776e58939f8951cb27020a5dae629f75a8f296b274c5dcd61b0e6bf6bb0d1a30ca1accbe1d264060aa3f7758d890613
6
+ metadata.gz: d8bf943ed2cc90fb627edf90d4c0c991df8076e0c055c7b1be2ae8fd7b36f7f04f0ad2a273fb8d09489ae7beb08a60995119646ea366641b321e2e16dfb8f6e5
7
+ data.tar.gz: 6f22c2a87d1a6c0b1db8b696d0a3a79984b532497de618a2d17dc6f1677d91242818a9933e5e6255adb37cf19f049c8e4e72758b59011920b10ff6ab6941a3b1
data/.gitignore CHANGED
@@ -36,3 +36,5 @@ test/version_tmp
36
36
  tmp
37
37
  tmtags
38
38
  tramp
39
+ .vscode
40
+ vendor
@@ -0,0 +1,25 @@
1
+ # How to Contribute
2
+
3
+ We'd love to get patches from you!
4
+
5
+ ## Getting Started
6
+
7
+ We follow the [GitHub Flow Workflow](https://guides.github.com/introduction/flow/)
8
+
9
+ 1. Fork the project
10
+ 2. Check out the `master` branch
11
+ 3. Create a feature branch
12
+ 4. Write code and tests for your change
13
+ 5. From your branch, make a pull request against `twitter/twurl/master`
14
+ 6. Work with repo maintainers to get your change reviewed
15
+ 7. Wait for your change to be pulled into `twitter/twurl/master`
16
+ 8. Delete your feature branch
17
+
18
+ ## License
19
+
20
+ By contributing your code, you agree to license your contribution under the
21
+ terms of the MIT License: https://github.com/twitter/twurl/blob/master/LICENSE
22
+
23
+ ## Code of Conduct
24
+
25
+ Read our [Code of Conduct](CODE_OF_CONDUCT.md) for the project.
@@ -0,0 +1,36 @@
1
+ # Install
2
+
3
+ ## Install with RubyGems (recommended)
4
+
5
+ ```sh
6
+ # installing the latest release
7
+ $ gem install twurl
8
+ ```
9
+
10
+ ```sh
11
+ # verify installation
12
+ $ twurl -v
13
+ 0.9.4
14
+ ```
15
+
16
+ ## Install from source
17
+
18
+ In case if you haven't installed `bundler` you need to install it first:
19
+
20
+ ```sh
21
+ $ gem install bundler
22
+ ```
23
+
24
+ ```sh
25
+ $ git clone https://github.com/twitter/twurl
26
+ $ cd twurl
27
+ $ bundle install
28
+ ```
29
+
30
+ If you don't want to install Twurl globally on your system, use `--path` [option](https://bundler.io/v2.0/bundle_install.html):
31
+
32
+ ```
33
+ $ bundle install --path path/to/directory
34
+ $ bundle exec twurl -v
35
+ 0.9.4
36
+ ```
data/README.md CHANGED
@@ -1,5 +1,7 @@
1
- Twurl
2
- =====
1
+ # Twurl
2
+
3
+ [![MIT License](https://img.shields.io/apm/l/atomic-design-ui.svg?)](https://github.com/twitter/twurl/blob/master/LICENSE)
4
+ [![Gem Version](https://badge.fury.io/rb/twurl.svg)](https://badge.fury.io/rb/twurl)
3
5
 
4
6
  Twurl is like curl, but tailored specifically for the Twitter API.
5
7
  It knows how to grant an access token to a client application for
@@ -10,34 +12,29 @@ as defining aliases for common requests, as well as support for
10
12
  multiple access tokens to easily switch between different client
11
13
  applications and Twitter accounts.
12
14
 
15
+ ## Installing Twurl
13
16
 
14
- Installing Twurl
15
- ----------------
16
-
17
- Twurl can be installed using ruby gems:
17
+ Twurl can be installed using RubyGems:
18
18
 
19
- ```
19
+ ```sh
20
20
  gem install twurl
21
21
  ```
22
22
 
23
+ ## Getting Started
23
24
 
24
- Getting Started
25
- ---------------
25
+ If you haven't already, the first thing to do is apply for a developer account to access Twitter APIs:
26
26
 
27
- If you haven't already, the first thing to do is apply for a developer account to access Twitter APIs:
28
-
29
- ```
27
+ ```text
30
28
  https://developer.twitter.com/en/apply-for-access
31
29
  ```
32
-
33
- After you have that access you can create a Twitter app and generate a consumer key and secret.
34
-
30
+
31
+ After you have that access you can create a Twitter app and generate a consumer key and secret.
35
32
 
36
33
  When you have your consumer key and its secret you authorize
37
- your Twitter account to make API requests with your consumer key
34
+ your Twitter account to make API requests with that consumer key
38
35
  and secret.
39
36
 
40
- ```
37
+ ```sh
41
38
  twurl authorize --consumer-key key \
42
39
  --consumer-secret secret
43
40
  ```
@@ -47,14 +44,12 @@ Authenticate to Twitter, and then enter the returned PIN back into
47
44
  the terminal. Assuming all that works well, you will be authorized
48
45
  to make requests with the API. Twurl will tell you as much.
49
46
 
50
-
51
- Making Requests
52
- ---------------
47
+ ## Making Requests
53
48
 
54
49
  The simplest request just requires that you specify the path you
55
50
  want to request.
56
51
 
57
- ```
52
+ ```sh
58
53
  twurl /1.1/statuses/home_timeline.json
59
54
  ```
60
55
 
@@ -63,47 +58,80 @@ Similar to curl, a GET request is performed by default.
63
58
  You can implicitly perform a POST request by passing the -d option,
64
59
  which specifies POST parameters.
65
60
 
66
- ```
61
+ ```sh
67
62
  twurl -d 'status=Testing twurl' /1.1/statuses/update.json
68
63
  ```
69
64
 
70
65
  You can explicitly specify what request method to perform with
71
66
  the -X (or --request-method) option.
72
67
 
73
- ```
68
+ ```sh
74
69
  twurl -X POST /1.1/statuses/destroy/1234567890.json
75
70
  ```
76
71
 
72
+ ## Using Bearer Tokens (Application-only authentication)
77
73
 
78
- Creating aliases
79
- ----------------
74
+ You can generate a bearer token using `--bearer` option in combination with the `authorize` subcommand.
80
75
 
76
+ ```sh
77
+ twurl authorize --bearer --consumer-key key \
78
+ --consumer-secret secret
81
79
  ```
80
+
81
+ And then, you can make a request using a generated bearer token using `--bearer` request option.
82
+
83
+ ```sh
84
+ twurl --bearer '/1.1/search/tweets.json?q=hello'
85
+ ```
86
+
87
+ To list your generated bearer tokens, you can use the `bearer_tokens` subcommand.
88
+
89
+ ```sh
90
+ twurl bearer_tokens
91
+ ```
92
+
93
+ This will print a pair of consumer_key and its associated bearer token. Note, tokens are omitted from this output.
94
+
95
+ ## Accessing Different Hosts
96
+
97
+ You can access different hosts for other Twitter APIs using the -H flag.
98
+
99
+ ```sh
100
+ twurl -H "ads-api.twitter.com" "/5/accounts"
101
+ ```
102
+
103
+ ## Uploading Media
104
+
105
+ To upload binary files, you can format the call as a form post. Below, the binary is "/path/to/media.jpg" and the form field is "media":
106
+
107
+ ```sh
108
+ twurl -H "upload.twitter.com" -X POST "/1.1/media/upload.json" --file "/path/to/media.jpg" --file-field "media"
109
+ ```
110
+
111
+ ## Creating aliases
112
+
113
+ ```sh
82
114
  twurl alias h /1.1/statuses/home_timeline.json
83
115
  ```
84
116
 
85
117
  You can then use "h" in place of the full path.
86
118
 
87
- ```
119
+ ```sh
88
120
  twurl h
89
121
  ```
90
122
 
91
- Paths that require additional options such as request parameters for example can
92
- be used with aliases the same as with full explicit paths, just as you might
93
- expect.
123
+ Paths that require additional options (such as request parameters, for example) can be used with aliases the same as with full explicit paths, just as you might expect.
94
124
 
95
- ```
125
+ ```sh
96
126
  twurl alias tweet /1.1/statuses/update.json
97
127
  twurl tweet -d "status=Aliases in twurl are convenient"
98
128
  ```
99
129
 
130
+ ## Changing your default profile
100
131
 
101
- Changing your default profile
102
- -----------------------------
103
-
104
- The first time you authorize a client application to make requests on behalf of your account, twurl stores your access token information in its .twurlrc file. Subsequent requests will use this profile as the default profile. You can use the 'accounts' subcommand to see what client applications have been authorized for what user names:
132
+ The first time you authorize a client application to make requests on behalf of your account, twurl stores your access token information in its `~/.twurlrc` file. Subsequent requests will use this profile as the default profile. You can use the `accounts` subcommand to see what client applications have been authorized for what user names:
105
133
 
106
- ```
134
+ ```sh
107
135
  twurl accounts
108
136
  noradio
109
137
  HQsAGcBm5MQT4n6j7qVJw
@@ -112,9 +140,9 @@ twurl accounts
112
140
  guT9RsJbNQgVe6AwoY9BA
113
141
  ```
114
142
 
115
- Notice that one of those consumer keys is marked as the default. To change the default use the 'set' subcommand, passing then either just the username, if it's unambiguous, or the username and consumer key pair if it isn't unambiguous:
143
+ Notice that one of those consumer keys is marked as the default. To change the default use the `set` subcommand, passing then either just the username, if it's unambiguous, or the username and consumer key pair if it isn't unambiguous:
116
144
 
117
- ```
145
+ ```sh
118
146
  twurl set default testiverse
119
147
  twurl accounts
120
148
  noradio
@@ -124,7 +152,7 @@ twurl accounts
124
152
  guT9RsJbNQgVe6AwoY9BA (default)
125
153
  ```
126
154
 
127
- ```
155
+ ```sh
128
156
  twurl set default noradio HQsAGcBm5MQT4n6j7qVJw
129
157
  twurl accounts
130
158
  noradio
@@ -134,10 +162,14 @@ twurl accounts
134
162
  guT9RsJbNQgVe6AwoY9BA
135
163
  ```
136
164
 
137
- Contributors
138
- ------------
165
+ ### Profiles and Bearer Tokens
166
+
167
+ While changing the default profile allows you to select which access token (OAuth1.0a) to use, bearer tokens don't link to any user profiles as the Application-only authentication doesn't require user context. That is, you can make an application-only request regardless of your default profile if you specify the `-c` (--consumer-key) option once you generate a bearer token with this consumer key. By default, twurl reads the current profile's consumer key and its associated bearer token from `~/.twurlrc` file.
168
+
169
+ ## Contributors
170
+
171
+ Marcel Molina / @noradio
139
172
 
140
- Marcel Molina <marcel@twitter.com> / @noradio
141
173
  Erik Michaels-Ober / @sferik
142
174
 
143
175
  and there are many [more](https://github.com/twitter/twurl/graphs/contributors)!
@@ -4,6 +4,7 @@ require 'optparse'
4
4
  require 'ostruct'
5
5
  require 'stringio'
6
6
  require 'yaml'
7
+ require 'json'
7
8
 
8
9
  library_files = Dir[File.join(File.dirname(__FILE__), "/twurl/**/*.rb")].sort
9
10
  library_files.each do |file|
@@ -0,0 +1,78 @@
1
+ require 'base64'
2
+ require_relative 'oauth_client'
3
+
4
+ module Twurl
5
+ class AppOnlyOAuthClient < Twurl::OAuthClient
6
+
7
+ AUTHORIZATION_FAILED_MESSAGE = "Authorization failed. Check that your consumer key and secret are correct."
8
+
9
+ attr_reader :consumer_key, :consumer_secret, :bearer_token
10
+
11
+ def initialize(options = {})
12
+ @consumer_key = options['consumer_key']
13
+ @consumer_secret = options['consumer_secret']
14
+ @bearer_token = options['bearer_token']
15
+ end
16
+
17
+ def save
18
+ self.class.rcfile.bearer_token(consumer_key, bearer_token)
19
+ end
20
+
21
+ def exchange_credentials_for_access_token
22
+ response = fetch_oauth2_token
23
+ if response.nil? || response[:access_token].nil?
24
+ raise Exception, AUTHORIZATION_FAILED_MESSAGE
25
+ end
26
+ @bearer_token = response[:access_token]
27
+ end
28
+
29
+ def perform_request_from_options(options, &block)
30
+ request = build_request_from_options(options)
31
+ request['user-agent'] = user_agent
32
+ request['authorization'] = "Bearer #{bearer_token}"
33
+
34
+ http_client.request(request, &block)
35
+ end
36
+
37
+ def needs_to_authorize?
38
+ bearer_token.nil?
39
+ end
40
+
41
+ def request_data
42
+ {'grant_type' => 'client_credentials'}
43
+ end
44
+
45
+ def http_client
46
+ uri = URI.parse(Twurl.options.base_url)
47
+ http = if Twurl.options.proxy
48
+ proxy_uri = URI.parse(Twurl.options.proxy)
49
+ Net::HTTP.new(uri.host, uri.port, proxy_uri.host, proxy_uri.port)
50
+ else
51
+ Net::HTTP.new(uri.host, uri.port)
52
+ end
53
+ set_http_client_options(http)
54
+ end
55
+
56
+ def set_http_client_options(http)
57
+ http.set_debug_output(Twurl.options.debug_output_io) if Twurl.options.trace
58
+ http.read_timeout = http.open_timeout = Twurl.options.timeout || 60
59
+ http.open_timeout = Twurl.options.connection_timeout if Twurl.options.connection_timeout
60
+ # Only override if Net::HTTP support max_retries (since Ruby >= 2.5)
61
+ http.max_retries = 0 if http.respond_to?(:max_retries=)
62
+ if Twurl.options.ssl?
63
+ http.use_ssl = true
64
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
65
+ end
66
+ http
67
+ end
68
+
69
+ def fetch_oauth2_token
70
+ request = Net::HTTP::Post.new('/oauth2/token')
71
+ request.body = URI.encode_www_form(request_data)
72
+ request['user-agent'] = user_agent
73
+ request['authorization'] = "Basic #{Base64.strict_encode64("#{consumer_key}:#{consumer_secret}")}"
74
+ response = http_client.request(request).body
75
+ JSON.parse(response,:symbolize_names => true)
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,18 @@
1
+ module Twurl
2
+ class AppOnlyTokenInformationController < AbstractCommandController
3
+ NO_ISSUED_TOKENS_MESSAGE = "No issued application-only (Bearer) tokens"
4
+
5
+ def dispatch
6
+ rcfile = OAuthClient.rcfile
7
+ if rcfile.empty? || rcfile.bearer_tokens.nil?
8
+ CLI.puts NO_ISSUED_TOKENS_MESSAGE
9
+ else
10
+ tokens = rcfile.bearer_tokens
11
+ CLI.puts "[consumer_key: bearer_token]"
12
+ tokens.each_key do |consumer_key|
13
+ CLI.puts "#{consumer_key}: (omitted)"
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -1,6 +1,6 @@
1
1
  module Twurl
2
2
  class CLI
3
- SUPPORTED_COMMANDS = %w(authorize accounts alias set)
3
+ SUPPORTED_COMMANDS = %w(authorize accounts bearer_tokens alias set)
4
4
  DEFAULT_COMMAND = 'request'
5
5
  PATH_PATTERN = /^\/\w+/
6
6
  PROTOCOL_PATTERN = /^\w+:\/\//
@@ -28,6 +28,8 @@ module Twurl
28
28
  AuthorizationController
29
29
  when 'accounts'
30
30
  AccountInformationController
31
+ when 'bearer_tokens'
32
+ AppOnlyTokenInformationController
31
33
  when 'alias'
32
34
  AliasesController
33
35
  when 'set'
@@ -87,8 +89,10 @@ Supported Commands: #{SUPPORTED_COMMANDS.sort.join(', ')}
87
89
  file
88
90
  filefield
89
91
  base64
92
+ json_format
90
93
  timeout
91
94
  connection_timeout
95
+ app_only
92
96
  end
93
97
  end
94
98
 
@@ -327,6 +331,12 @@ Supported Commands: #{SUPPORTED_COMMANDS.sort.join(', ')}
327
331
  end
328
332
  end
329
333
 
334
+ def json_format
335
+ on('-j', '--json-pretty', 'Format response body to JSON pretty style') do |json_format|
336
+ options.json_format = true
337
+ end
338
+ end
339
+
330
340
  def timeout
331
341
  on('--timeout [sec]', Integer, 'Number of seconds to wait for the request to be read (default: 60)') do |timeout|
332
342
  options.timeout = timeout
@@ -338,6 +348,12 @@ Supported Commands: #{SUPPORTED_COMMANDS.sort.join(', ')}
338
348
  options.connection_timeout = connection_timeout
339
349
  end
340
350
  end
351
+
352
+ def app_only
353
+ on('--bearer', "Use application-only authentication (Bearer Token)") do |app_only|
354
+ options.app_only = true
355
+ end
356
+ end
341
357
  end
342
358
  end
343
359
 
@@ -13,13 +13,23 @@ module Twurl
13
13
  load_client_for_username_and_consumer_key(options.username, options.consumer_key)
14
14
  elsif options.username
15
15
  load_client_for_username(options.username)
16
+ elsif options.command == 'authorize' && options.app_only
17
+ load_client_for_app_only_auth(options, options.consumer_key)
16
18
  elsif options.command == 'authorize'
17
19
  load_new_client_from_options(options)
20
+ elsif options.command == 'request' && has_oauth_options?(options)
21
+ load_new_client_from_oauth_options(options)
22
+ elsif options.command == 'request' && options.app_only && options.consumer_key
23
+ load_client_for_non_profile_app_only_auth(options)
18
24
  else
19
- load_default_client
25
+ load_default_client(options)
20
26
  end
21
27
  end
22
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
+
23
33
  def load_client_for_username_and_consumer_key(username, consumer_key)
24
34
  user_profiles = rcfile[username]
25
35
  if user_profiles && attributes = user_profiles[consumer_key]
@@ -45,9 +55,52 @@ module Twurl
45
55
  new(options.oauth_client_options.merge('password' => options.password))
46
56
  end
47
57
 
48
- def load_default_client
49
- raise Exception, "You must authorize first" unless rcfile.default_profile
50
- load_client_for_username_and_consumer_key(*rcfile.default_profile)
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
76
+ end
77
+
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
51
104
  end
52
105
  end
53
106
 
@@ -74,7 +127,7 @@ module Twurl
74
127
  :copy => Net::HTTP::Copy
75
128
  }
76
129
 
77
- def perform_request_from_options(options, &block)
130
+ def build_request_from_options(options, &block)
78
131
  request_class = METHODS.fetch(options.request_method.to_sym)
79
132
  request = request_class.new(options.path, options.headers)
80
133
 
@@ -122,7 +175,11 @@ module Twurl
122
175
  end.join("&")
123
176
  end
124
177
  end
178
+ request
179
+ end
125
180
 
181
+ def perform_request_from_options(options, &block)
182
+ request = build_request_from_options(options)
126
183
  request.oauth!(consumer.http, consumer, access_token)
127
184
  request['user-agent'] = user_agent
128
185
  consumer.http.request(request, &block)
@@ -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
@@ -70,6 +75,16 @@ module Twurl
70
75
  data['aliases']
71
76
  end
72
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']
86
+ end
87
+
73
88
  def alias_from_options(options)
74
89
  options.subcommands.each do |potential_alias|
75
90
  if path = alias_from_name(potential_alias)
@@ -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,6 +1,7 @@
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'
4
5
  READ_TIMEOUT_MESSAGE = 'A timeout occurred (Net::ReadTimeout). ' \
5
6
  'Please try again or increase the value using --timeout option.'
6
7
  OPEN_TIMEOUT_MESSAGE = 'A timeout occurred (Net::OpenTimeout). ' \
@@ -10,19 +11,31 @@ module Twurl
10
11
  raise Exception, "You need to authorize first."
11
12
  end
12
13
  options.path ||= OAuthClient.rcfile.alias_from_options(options)
14
+ raise Exception, NO_URI_MESSAGE if options.path.empty?
13
15
  perform_request
14
16
  end
15
17
 
16
18
  def perform_request
17
19
  client.perform_request_from_options(options) { |response|
18
- response.read_body { |chunk| CLI.print chunk }
20
+ response.read_body { |body|
21
+ CLI.print options.json_format ? JsonFormatter.format(body) : body
22
+ }
19
23
  }
20
24
  rescue URI::InvalidURIError
21
- CLI.puts NO_URI_MESSAGE
25
+ CLI.puts INVALID_URI_MESSAGE
22
26
  rescue Net::ReadTimeout
23
27
  CLI.puts READ_TIMEOUT_MESSAGE
24
28
  rescue Net::OpenTimeout
25
29
  CLI.puts OPEN_TIMEOUT_MESSAGE
26
30
  end
27
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
39
+ end
40
+ end
28
41
  end
@@ -2,7 +2,7 @@ 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 = 4 unless defined? Twurl::Version::PATCH
5
+ PATCH = 5 unless defined? Twurl::Version::PATCH
6
6
  PRE = nil unless defined? Twurl::Version::PRE # Time.now.to_i.to_s
7
7
 
8
8
  class << self
@@ -5,11 +5,10 @@ require 'twurl/version'
5
5
 
6
6
  Gem::Specification.new do |spec|
7
7
  spec.add_dependency 'oauth', '~> 0.4'
8
- spec.authors = ["Marcel Molina", "Erik Michaels-Ober"]
8
+ spec.authors = ["Marcel Molina", "Erik Michaels-Ober", "@TwitterDev team"]
9
9
  spec.description = %q{Curl for the Twitter API}
10
- spec.email = ['marcel@twitter.com']
11
10
  spec.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
12
- spec.extra_rdoc_files = %w(CODE_OF_CONDUCT.md INSTALL LICENSE README.md)
11
+ spec.extra_rdoc_files = %w(CODE_OF_CONDUCT.md CONTRIBUTING.md INSTALL.md LICENSE README.md)
13
12
  spec.files = `git ls-files -z`.split("\x0").reject { |f| f.start_with?('test/') }
14
13
  spec.homepage = 'http://github.com/twitter/twurl'
15
14
  spec.licenses = ['MIT']
metadata CHANGED
@@ -1,15 +1,16 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: twurl
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.4
4
+ version: 0.9.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Marcel Molina
8
8
  - Erik Michaels-Ober
9
+ - "@TwitterDev team"
9
10
  autorequire:
10
11
  bindir: bin
11
12
  cert_chain: []
12
- date: 2019-12-10 00:00:00.000000000 Z
13
+ date: 2019-12-26 00:00:00.000000000 Z
13
14
  dependencies:
14
15
  - !ruby/object:Gem::Dependency
15
16
  name: oauth
@@ -26,14 +27,14 @@ dependencies:
26
27
  - !ruby/object:Gem::Version
27
28
  version: '0.4'
28
29
  description: Curl for the Twitter API
29
- email:
30
- - marcel@twitter.com
30
+ email:
31
31
  executables:
32
32
  - twurl
33
33
  extensions: []
34
34
  extra_rdoc_files:
35
35
  - CODE_OF_CONDUCT.md
36
- - INSTALL
36
+ - CONTRIBUTING.md
37
+ - INSTALL.md
37
38
  - LICENSE
38
39
  - README.md
39
40
  files:
@@ -42,8 +43,9 @@ files:
42
43
  - ".gitignore"
43
44
  - ".travis.yml"
44
45
  - CODE_OF_CONDUCT.md
46
+ - CONTRIBUTING.md
45
47
  - Gemfile
46
- - INSTALL
48
+ - INSTALL.md
47
49
  - LICENSE
48
50
  - README.md
49
51
  - Rakefile
@@ -52,6 +54,8 @@ files:
52
54
  - lib/twurl/abstract_command_controller.rb
53
55
  - lib/twurl/account_information_controller.rb
54
56
  - lib/twurl/aliases_controller.rb
57
+ - lib/twurl/app_only_oauth_client.rb
58
+ - lib/twurl/app_only_token_information_controller.rb
55
59
  - lib/twurl/authorization_controller.rb
56
60
  - lib/twurl/cli.rb
57
61
  - lib/twurl/configuration_controller.rb
@@ -85,7 +89,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
85
89
  - !ruby/object:Gem::Version
86
90
  version: '0'
87
91
  requirements: []
88
- rubygems_version: 3.0.6
92
+ rubygems_version: 3.0.3
89
93
  signing_key:
90
94
  specification_version: 4
91
95
  summary: Curl for the Twitter API
data/INSTALL DELETED
@@ -1,23 +0,0 @@
1
- +-----------------------+
2
- | Install with RubyGems |
3
- +-----------------------+
4
-
5
- sudo gem i twurl --source http://rubygems.org
6
-
7
- +---------------------+
8
- | Build from source |
9
- +---------------------+
10
-
11
- rake build
12
-
13
- +---------------------+
14
- | Install from source |
15
- +---------------------+
16
-
17
- rake install
18
-
19
- +--------------+
20
- | Dependencies |
21
- +--------------+
22
-
23
- sudo gem i oauth