twurl 0.9.1 → 0.9.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 7e12cf717906fbf24bcd34598212105569647dbed1f6b5ee37a195ec0db8ca15
4
+ data.tar.gz: c5d7a22975a06fccc328a99323ead35584104e77fcf3827611d9cf279ccb22bf
5
+ SHA512:
6
+ metadata.gz: daf4dae500fb05b24eaed8413538420bf66d5f113982663e7e3bd416df6d4a98eef31b982eac9174eaa251421aef512db60bd3675c4ae55529ef009603a070de
7
+ data.tar.gz: 7ef9671671c1d946f0671147b49c8485b21da16837a1c890b2e67b28f5b3f4ed2065e9b896ae9a3533d92bb384908c79f0fd72543adb5a06626aec0390d98f2c
@@ -0,0 +1,48 @@
1
+ # Code of Conduct v2.0
2
+
3
+ This code of conduct outlines our expectations for participants within the [@TwitterOSS](https://twitter.com/twitteross) community, as well as steps to reporting unacceptable behavior. We are committed to providing a welcoming and inspiring community for all and expect our code of conduct to be honored. Anyone who violates this code of conduct may be banned from the community.
4
+
5
+ Our open source community strives to:
6
+
7
+ * **Be friendly and patient.**
8
+ * **Be welcoming**: We strive to be a community that welcomes and supports people of all backgrounds and identities. This includes, but is not limited to members of any race, ethnicity, culture, national origin, colour, immigration status, social and economic class, educational level, sex, sexual orientation, gender identity and expression, age, size, family status, political belief, religion, and mental and physical ability.
9
+ * **Be considerate**: Your work will be used by other people, and you in turn will depend on the work of others. Any decision you take will affect users and colleagues, and you should take those consequences into account when making decisions. Remember that we're a world-wide community, so you might not be communicating in someone else's primary language.
10
+ * **Be respectful**: Not all of us will agree all the time, but disagreement is no excuse for poor behavior and poor manners. We might all experience some frustration now and then, but we cannot allow that frustration to turn into a personal attack. It’s important to remember that a community where people feel uncomfortable or threatened is not a productive one.
11
+ * **Be careful in the words that you choose**: we are a community of professionals, and we conduct ourselves professionally. Be kind to others. Do not insult or put down other participants. Harassment and other exclusionary behavior aren't acceptable. This includes, but is not limited to:
12
+ * Violent threats or language directed against another person.
13
+ * Discriminatory jokes and language.
14
+ * Posting sexually explicit or violent material.
15
+ * Posting (or threatening to post) other people's personally identifying information ("doxing").
16
+ * Personal insults, especially those using racist or sexist terms.
17
+ * Unwelcome sexual attention.
18
+ * Advocating for, or encouraging, any of the above behavior.
19
+ * Repeated harassment of others. In general, if someone asks you to stop, then stop.
20
+ * **When we disagree, try to understand why**: Disagreements, both social and technical, happen all the time. It is important that we resolve disagreements and differing views constructively. Remember that we’re different. The strength of our community comes from its diversity, people from a wide range of backgrounds. Different people have different perspectives on issues. Being unable to understand why someone holds a viewpoint doesn’t mean that they’re wrong. Don’t forget that it is human to err and blaming each other doesn’t get us anywhere. Instead, focus on helping to resolve issues and learning from mistakes.
21
+
22
+ This code is not exhaustive or complete. It serves to distill our common understanding of a collaborative, shared environment, and goals. We expect it to be followed in spirit as much as in the letter.
23
+
24
+ ### Diversity Statement
25
+
26
+ We encourage everyone to participate and are committed to building a community for all. Although we may not be able to satisfy everyone, we all agree that everyone is equal. Whenever a participant has made a mistake, we expect them to take responsibility for it. If someone has been harmed or offended, it is our responsibility to listen carefully and respectfully, and do our best to right the wrong.
27
+
28
+ Although this list cannot be exhaustive, we explicitly honor diversity in age, gender, gender identity or expression, culture, ethnicity, language, national origin, political beliefs, profession, race, religion, sexual orientation, socioeconomic status, and technical ability. We will not tolerate discrimination based on any of the protected
29
+ characteristics above, including participants with disabilities.
30
+
31
+ ### Reporting Issues
32
+
33
+ If you experience or witness unacceptable behavior—or have any other concerns—please report it by contacting us via [opensource+codeofconduct@twitter.com](mailto:opensource+codeofconduct@twitter.com). All reports will be handled with discretion. In your report please include:
34
+
35
+ - Your contact information.
36
+ - Names (real, nicknames, or pseudonyms) of any individuals involved. If there are additional witnesses, please
37
+ include them as well. Your account of what occurred, and if you believe the incident is ongoing. If there is a publicly available record (e.g. a mailing list archive or a public IRC logger), please include a link.
38
+ - Any additional information that may be helpful.
39
+
40
+ After filing a report, a representative will contact you personally. If the person who is harassing you is part of the response team, they will recuse themselves from handling your incident. A representative will then review the incident, follow up with any additional questions, and make a decision as to how to respond. We will respect confidentiality requests for the purpose of protecting victims of abuse.
41
+
42
+ Anyone asked to stop unacceptable behavior is expected to comply immediately. If an individual engages in unacceptable behavior, the representative may take any action they deem appropriate, up to and including a permanent ban from our community without warning.
43
+
44
+ ## Thanks
45
+
46
+ This code of conduct is based on the [Open Code of Conduct](https://github.com/todogroup/opencodeofconduct) from the [TODOGroup](http://todogroup.org).
47
+
48
+ We are thankful for their work and all the communities who have paved the way with code of conducts.
@@ -0,0 +1,39 @@
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
+
14
+ ```sh
15
+ $ cd ./twurl
16
+
17
+ # install twurl from source
18
+ $ bundle install
19
+
20
+ # run twurl from source
21
+ $ bundle exec twurl -v
22
+
23
+ # run tests
24
+ $ bundle exec rake
25
+ ```
26
+
27
+ 5. From your branch, make a pull request against `twitter/twurl/master`
28
+ 6. Work with repo maintainers to get your change reviewed
29
+ 7. Wait for your change to be pulled into `twitter/twurl/master`
30
+ 8. Delete your feature branch
31
+
32
+ ## License
33
+
34
+ By contributing your code, you agree to license your contribution under the
35
+ terms of the MIT License: https://github.com/twitter/twurl/blob/master/LICENSE
36
+
37
+ ## Code of Conduct
38
+
39
+ 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.6
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.6
36
+ ```
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2009 Marcel Molina <marcel@twitter.com>, 2019 Twitter, Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,176 @@
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)
5
+ [![Build Status](https://travis-ci.com/twitter/twurl.svg?branch=master)](https://travis-ci.com/twitter/twurl)
6
+
7
+ Twurl is like curl, but tailored specifically for the Twitter API.
8
+ It knows how to grant an access token to a client application for
9
+ a specified user and then sign all requests with that access token.
10
+
11
+ It also provides other development and debugging conveniences such
12
+ as defining aliases for common requests, as well as support for
13
+ multiple access tokens to easily switch between different client
14
+ applications and Twitter accounts.
15
+
16
+ ## Installing Twurl
17
+
18
+ Twurl can be installed using RubyGems:
19
+
20
+ ```sh
21
+ gem install twurl
22
+ ```
23
+
24
+ ## Getting Started
25
+
26
+ If you haven't already, the first thing to do is apply for a developer account to access Twitter APIs:
27
+
28
+ ```text
29
+ https://developer.twitter.com/en/apply-for-access
30
+ ```
31
+
32
+ After you have that access you can create a Twitter app and generate a consumer key and secret.
33
+
34
+ When you have your consumer key and its secret you authorize
35
+ your Twitter account to make API requests with that consumer key
36
+ and secret.
37
+
38
+ ```sh
39
+ twurl authorize --consumer-key key \
40
+ --consumer-secret secret
41
+ ```
42
+
43
+ This will return an URL that you should open up in your browser.
44
+ Authenticate to Twitter, and then enter the returned PIN back into
45
+ the terminal. Assuming all that works well, you will be authorized
46
+ to make requests with the API. Twurl will tell you as much.
47
+
48
+ ## Making Requests
49
+
50
+ The simplest request just requires that you specify the path you
51
+ want to request.
52
+
53
+ ```sh
54
+ twurl /1.1/statuses/home_timeline.json
55
+ ```
56
+
57
+ Similar to curl, a GET request is performed by default.
58
+
59
+ You can implicitly perform a POST request by passing the -d option,
60
+ which specifies POST parameters.
61
+
62
+ ```sh
63
+ twurl -d 'status=Testing twurl' /1.1/statuses/update.json
64
+ ```
65
+
66
+ You can explicitly specify what request method to perform with
67
+ the -X (or --request-method) option.
68
+
69
+ ```sh
70
+ twurl -X POST /1.1/statuses/destroy/1234567890.json
71
+ ```
72
+
73
+ ## Using Bearer Tokens (Application-only authentication)
74
+
75
+ You can generate a bearer token using `--bearer` option in combination with the `authorize` subcommand.
76
+
77
+ ```sh
78
+ twurl authorize --bearer --consumer-key key \
79
+ --consumer-secret secret
80
+ ```
81
+
82
+ And then, you can make a request using a generated bearer token using `--bearer` request option.
83
+
84
+ ```sh
85
+ twurl --bearer '/1.1/search/tweets.json?q=hello'
86
+ ```
87
+
88
+ To list your generated bearer tokens, you can use the `bearer_tokens` subcommand.
89
+
90
+ ```sh
91
+ twurl bearer_tokens
92
+ ```
93
+
94
+ This will print a pair of consumer_key and its associated bearer token. Note, tokens are omitted from this output.
95
+
96
+ ## Accessing Different Hosts
97
+
98
+ You can access different hosts for other Twitter APIs using the -H flag.
99
+
100
+ ```sh
101
+ twurl -H "ads-api.twitter.com" "/7/accounts"
102
+ ```
103
+
104
+ ## Uploading Media
105
+
106
+ 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":
107
+
108
+ ```sh
109
+ twurl -H "upload.twitter.com" -X POST "/1.1/media/upload.json" --file "/path/to/media.jpg" --file-field "media"
110
+ ```
111
+
112
+ ## Creating aliases
113
+
114
+ ```sh
115
+ twurl alias h /1.1/statuses/home_timeline.json
116
+ ```
117
+
118
+ You can then use "h" in place of the full path.
119
+
120
+ ```sh
121
+ twurl h
122
+ ```
123
+
124
+ 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.
125
+
126
+ ```sh
127
+ twurl alias tweet /1.1/statuses/update.json
128
+ twurl tweet -d "status=Aliases in twurl are convenient"
129
+ ```
130
+
131
+ ## Changing your default profile
132
+
133
+ 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:
134
+
135
+ ```sh
136
+ twurl accounts
137
+ noradio
138
+ HQsAGcBm5MQT4n6j7qVJw
139
+ hhC7Koy2zRsTZvQh1hVlSA (default)
140
+ testiverse
141
+ guT9RsJbNQgVe6AwoY9BA
142
+ ```
143
+
144
+ 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:
145
+
146
+ ```sh
147
+ twurl set default testiverse
148
+ twurl accounts
149
+ noradio
150
+ HQsAGcBm5MQT4n6j7qVJw
151
+ hhC7Koy2zRsTZvQh1hVlSA
152
+ testiverse
153
+ guT9RsJbNQgVe6AwoY9BA (default)
154
+ ```
155
+
156
+ ```sh
157
+ twurl set default noradio HQsAGcBm5MQT4n6j7qVJw
158
+ twurl accounts
159
+ noradio
160
+ HQsAGcBm5MQT4n6j7qVJw (default)
161
+ hhC7Koy2zRsTZvQh1hVlSA
162
+ testiverse
163
+ guT9RsJbNQgVe6AwoY9BA
164
+ ```
165
+
166
+ ### Profiles and Bearer Tokens
167
+
168
+ 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.
169
+
170
+ ## Contributors
171
+
172
+ Marcel Molina / @noradio
173
+
174
+ Erik Michaels-Ober / @sferik
175
+
176
+ 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,10 +1,10 @@
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+:\/\//
7
- README = File.dirname(__FILE__) + '/../../README'
7
+ README = File.dirname(__FILE__) + '/../../README.md'
8
8
  @output ||= STDOUT
9
9
  class NoPathFound < Exception
10
10
  end
@@ -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'
@@ -41,9 +43,8 @@ module Twurl
41
43
  end
42
44
 
43
45
  def parse_options(args)
44
- arguments = args.dup
45
-
46
46
  Twurl.options = Options.new
47
+ Twurl.options.args = args.dup
47
48
  Twurl.options.trace = false
48
49
  Twurl.options.data = {}
49
50
  Twurl.options.headers = {}
@@ -66,7 +67,6 @@ Supported Commands: #{SUPPORTED_COMMANDS.sort.join(', ')}
66
67
 
67
68
  o.section "Authorization options:" do
68
69
  username
69
- password
70
70
  consumer_key
71
71
  consumer_secret
72
72
  access_token
@@ -76,6 +76,7 @@ Supported Commands: #{SUPPORTED_COMMANDS.sort.join(', ')}
76
76
  o.section "Common options:" do
77
77
  trace
78
78
  data
79
+ raw_data
79
80
  headers
80
81
  host
81
82
  quiet
@@ -87,19 +88,31 @@ Supported Commands: #{SUPPORTED_COMMANDS.sort.join(', ')}
87
88
  file
88
89
  filefield
89
90
  base64
91
+ json_format
92
+ timeout
93
+ connection_timeout
94
+ app_only
90
95
  end
91
96
  end
92
97
 
93
- arguments = option_parser.parse!(args)
98
+ begin
99
+ arguments = option_parser.parse!(args)
100
+ rescue OptionParser::InvalidOption
101
+ CLI.puts "ERROR: undefined option"
102
+ exit
103
+ rescue
104
+ CLI.puts "ERROR: invalid argument"
105
+ exit
106
+ end
94
107
  Twurl.options.command = extract_command!(arguments)
95
108
  Twurl.options.path = extract_path!(arguments)
96
-
97
- if Twurl.options.command == DEFAULT_COMMAND and Twurl.options.path.nil?
109
+ Twurl.options.subcommands = arguments
110
+
111
+ if Twurl.options.command == DEFAULT_COMMAND and Twurl.options.path.nil? and Twurl.options.args.empty?
98
112
  CLI.puts option_parser
99
113
  raise NoPathFound, "No path found"
100
114
  end
101
115
 
102
- Twurl.options.subcommands = arguments
103
116
  Twurl.options
104
117
  end
105
118
 
@@ -158,11 +171,9 @@ Supported Commands: #{SUPPORTED_COMMANDS.sort.join(', ')}
158
171
  end
159
172
 
160
173
  def escape_params(params)
161
- split_params = params.split("&").map do |param|
162
- key, value = param.split('=', 2)
163
- CGI::escape(key) + '=' + CGI::escape(value)
164
- end
165
- split_params.join("&")
174
+ CGI::parse(params).map do |key, value|
175
+ "#{CGI.escape key}=#{CGI.escape value.first}"
176
+ end.join("&")
166
177
  end
167
178
  end
168
179
 
@@ -204,7 +215,7 @@ Supported Commands: #{SUPPORTED_COMMANDS.sort.join(', ')}
204
215
  end
205
216
 
206
217
  def token_secret
207
- on('-S', '--token-secret', "Your token secret") do |secret|
218
+ on('-S', '--token-secret [secret]', "Your token secret") do |secret|
208
219
  options.token_secret = secret
209
220
  end
210
221
  end
@@ -215,12 +226,6 @@ Supported Commands: #{SUPPORTED_COMMANDS.sort.join(', ')}
215
226
  end
216
227
  end
217
228
 
218
- def password
219
- on('-p', '--password [password]', 'Password of account to authorize (required)') do |password|
220
- options.password = password ? password : CLI.prompt_for('Password')
221
- end
222
- end
223
-
224
229
  def trace
225
230
  on('-t', '--[no-]trace', 'Trace request/response traffic (default: --no-trace)') do |trace|
226
231
  options.trace = trace
@@ -229,9 +234,21 @@ Supported Commands: #{SUPPORTED_COMMANDS.sort.join(', ')}
229
234
 
230
235
  def data
231
236
  on('-d', '--data [data]', 'Sends the specified data in a POST request to the HTTP server.') do |data|
232
- data.split('&').each do |pair|
233
- key, value = pair.split('=', 2)
234
- options.data[key] = value
237
+ if options.args.count { |item| /content-type: (.*)/i.match(item) } > 0
238
+ options.data[data] = nil
239
+ else
240
+ data.split('&').each do |pair|
241
+ key, value = pair.split('=', 2)
242
+ options.data[key] = value
243
+ end
244
+ end
245
+ end
246
+ end
247
+
248
+ def raw_data
249
+ on('-r', '--raw-data [data]', 'Sends the specified data as it is in a POST request to the HTTP server.') do |data|
250
+ CGI::parse(data).each_pair do |key, value|
251
+ options.data[key] = value.first
235
252
  end
236
253
  end
237
254
  end
@@ -296,29 +313,51 @@ Supported Commands: #{SUPPORTED_COMMANDS.sort.join(', ')}
296
313
  on('-f', '--file [path_to_file]', 'Specify the path to the file to upload') do |file|
297
314
  if File.file?(file)
298
315
  options.upload['file'] << file
299
- else
316
+ else
300
317
  CLI.puts "ERROR: File not found"
301
318
  exit
302
319
  end
303
320
  end
304
321
  end
305
-
306
- def filefield
307
- on('-F', '--file-field [field_name]', 'Specify the file field for the upload to be included within (default: media)') do |filefield|
322
+
323
+ def filefield
324
+ on('-F', '--file-field [field_name]', 'Specify the POST parameter name for the file upload data (default: media)') do |filefield|
308
325
  options.upload['filefield'] = filefield
309
- options.upload['filefield'] = 'media[]' if options.upload['filefield'].empty?
310
326
  end
311
327
  end
312
-
313
- def base64
328
+
329
+ def base64
314
330
  on('-b', '--base64', 'Encode the uploaded file as base64 (default: false)') do |base64|
315
331
  options.upload['base64'] = base64
316
332
  end
317
333
  end
334
+
335
+ def json_format
336
+ on('-j', '--json-pretty', 'Format response body to JSON pretty style') do |json_format|
337
+ options.json_format = true
338
+ end
339
+ end
340
+
341
+ def timeout
342
+ on('--timeout [sec]', Integer, 'Number of seconds to wait for the request to be read (default: 60)') do |timeout|
343
+ options.timeout = timeout
344
+ end
345
+ end
346
+
347
+ def connection_timeout
348
+ on('--connection-timeout [sec]', Integer, 'Number of seconds to wait for the connection to open (default: 60)') do |connection_timeout|
349
+ options.connection_timeout = connection_timeout
350
+ end
351
+ end
352
+
353
+ def app_only
354
+ on('--bearer', "Use application-only authentication (Bearer Token)") do |app_only|
355
+ options.app_only = true
356
+ end
357
+ end
318
358
  end
319
359
  end
320
360
 
321
-
322
361
  class Options < OpenStruct
323
362
  DEFAULT_REQUEST_METHOD = 'get'
324
363
  DEFAULT_HOST = 'api.twitter.com'