twurl 0.9.1 → 0.9.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CODE_OF_CONDUCT.md +48 -0
- data/CONTRIBUTING.md +39 -0
- data/INSTALL.md +36 -0
- data/LICENSE +21 -0
- data/README.md +176 -0
- data/lib/twurl.rb +1 -0
- data/lib/twurl/app_only_oauth_client.rb +78 -0
- data/lib/twurl/app_only_token_information_controller.rb +18 -0
- data/lib/twurl/cli.rb +71 -32
- data/lib/twurl/oauth_client.rb +97 -23
- data/lib/twurl/rcfile.rb +20 -1
- data/lib/twurl/request_controller.rb +24 -3
- data/lib/twurl/version.rb +3 -3
- data/twurl.gemspec +8 -11
- metadata +34 -75
- data/.gemtest +0 -0
- data/.gitignore +0 -38
- data/.travis.yml +0 -16
- data/COPYING +0 -18
- data/Gemfile +0 -20
- data/INSTALL +0 -18
- data/README +0 -119
- data/Rakefile +0 -12
- data/test/account_information_controller_test.rb +0 -61
- data/test/alias_controller_test.rb +0 -53
- data/test/authorization_controller_test.rb +0 -30
- data/test/cli_options_test.rb +0 -23
- data/test/cli_test.rb +0 -192
- data/test/configuration_controller_test.rb +0 -44
- data/test/oauth_client_test.rb +0 -165
- data/test/rcfile_test.rb +0 -147
- data/test/request_controller_test.rb +0 -58
- data/test/test_helper.rb +0 -44
checksums.yaml
ADDED
@@ -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
|
data/CODE_OF_CONDUCT.md
ADDED
@@ -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.
|
data/CONTRIBUTING.md
ADDED
@@ -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.
|
data/INSTALL.md
ADDED
@@ -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.
|
data/README.md
ADDED
@@ -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)!
|
data/lib/twurl.rb
CHANGED
@@ -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
|
data/lib/twurl/cli.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
-
|
162
|
-
key
|
163
|
-
|
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
|
-
|
233
|
-
|
234
|
-
|
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
|
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'
|