t 0.0.2 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.rspec +1 -0
- data/.travis.yml +1 -1
- data/README.md +24 -14
- data/bin/t +34 -1
- data/lib/t/cli.rb +203 -121
- data/lib/t/core_ext/string.rb +7 -0
- data/lib/t/delete.rb +101 -0
- data/lib/t/set.rb +25 -17
- data/lib/t/version.rb +2 -2
- data/spec/cli_spec.rb +587 -0
- data/spec/delete_spec.rb +251 -0
- data/spec/fixtures/.trc +4 -4
- data/spec/fixtures/access_token +1 -0
- data/spec/fixtures/checkip.html +1 -0
- data/spec/fixtures/direct_message.json +1 -0
- data/spec/fixtures/direct_messages.json +1 -0
- data/spec/fixtures/favorites.json +1 -0
- data/spec/fixtures/recommendations.json +1 -0
- data/spec/fixtures/request_token +1 -0
- data/spec/fixtures/retweet.json +1 -0
- data/spec/fixtures/settings.json +1 -0
- data/spec/fixtures/sferik.json +1 -0
- data/spec/fixtures/status.json +1 -0
- data/spec/fixtures/statuses.json +1 -0
- data/spec/fixtures/xml.gp +17 -0
- data/spec/helper.rb +42 -2
- data/spec/rcfile_spec.rb +17 -16
- data/spec/set_spec.rb +125 -0
- data/t.gemspec +5 -3
- metadata +95 -36
data/.rspec
CHANGED
data/.travis.yml
CHANGED
data/README.md
CHANGED
@@ -19,15 +19,15 @@ John Nunemaker's original code.
|
|
19
19
|
## <a name="installation"></a>Installation
|
20
20
|
gem install t
|
21
21
|
|
22
|
-
## <a name="
|
23
|
-
[![Build Status](https://secure.travis-ci.org/sferik/t.png)][
|
22
|
+
## <a name="build"></a>Build Status
|
23
|
+
[![Build Status](https://secure.travis-ci.org/sferik/t.png?branch=master)][travis]
|
24
24
|
|
25
|
-
[
|
25
|
+
[travis]: http://travis-ci.org/sferik/t
|
26
26
|
|
27
27
|
## <a name="dependencies"></a>Dependency Status
|
28
|
-
[![Dependency Status](https://gemnasium.com/
|
28
|
+
[![Dependency Status](https://gemnasium.com/sferik/t.png?travis)][gemnasium]
|
29
29
|
|
30
|
-
[gemnasium]: https://gemnasium.com/
|
30
|
+
[gemnasium]: https://gemnasium.com/sferik/t
|
31
31
|
|
32
32
|
## <a name="examples"></a>Usage Examples
|
33
33
|
Typing `t help` will give you a list of all the available commands. You can
|
@@ -41,26 +41,36 @@ create your application make sure to set the "Application Type" to "Read, Write
|
|
41
41
|
and Access direct messages", otherwise you won't be able to post status updates
|
42
42
|
or send direct messages via the CLI.
|
43
43
|
|
44
|
-
Once you have registered your application, you'll be
|
45
|
-
|
44
|
+
Once you have registered your application, you'll be given a consumer key and
|
45
|
+
secret, which you can use to authorize your Twitter account.
|
46
46
|
|
47
47
|
t authorize --consumer-key YOUR_CONSUMER_KEY --consumer-secret YOUR_CONSUMER_SECRET
|
48
48
|
|
49
|
-
This will open a new browser window where you can authenticate to Twitter
|
49
|
+
This will open a new browser window where you can authenticate to Twitter and
|
50
|
+
then enter the returned PIN back into the terminal. Assuming all that works
|
51
|
+
well, you will be authorized to make requests with the API.
|
50
52
|
|
51
53
|
You can see a list of all the accounts you've authorized.
|
52
54
|
|
53
55
|
t accounts
|
54
56
|
|
55
|
-
|
57
|
+
sferik
|
58
|
+
UDfNTpOz5ZDG4a6w7dIWj
|
59
|
+
uuP7Xbl2mEfGMiDu1uIyFN
|
60
|
+
gem
|
61
|
+
thG9EfWoADtIr6NjbL9ON (default)
|
56
62
|
|
57
|
-
|
63
|
+
Notice that one account is marked as the default. To change the default use the
|
64
|
+
`set` subcommand, passing either just the username, if it's unambiguous, or the
|
65
|
+
username and consumer key pair:
|
58
66
|
|
59
|
-
|
67
|
+
t set default sferik thG9EfWoADtIr6NjbL9ON
|
68
|
+
|
69
|
+
Account information is stored in the YAML-formatted file `~/.trc`.
|
60
70
|
|
61
71
|
### <a name="update"></a>Update your status
|
62
72
|
|
63
|
-
t update "I'm tweeting from the command line
|
73
|
+
t update "I'm tweeting from the command line. Isn't that special?"
|
64
74
|
|
65
75
|
### <a name="dm"></a>Send a user a private message
|
66
76
|
|
@@ -104,7 +114,7 @@ Incidentally, account information is stored in YAML format in `~/.trc`.
|
|
104
114
|
|
105
115
|
### <a name="reply"></a>Reply to a Tweet
|
106
116
|
|
107
|
-
t reply sferik Thanks
|
117
|
+
t reply sferik "Thanks Erik"
|
108
118
|
|
109
119
|
### <a name="retweet"></a>Send another user's latest Tweet to your followers
|
110
120
|
|
@@ -162,7 +172,7 @@ bug report should include a pull request with failing specs.
|
|
162
172
|
reason, please do so in a separate commit.)
|
163
173
|
|
164
174
|
## <a name="versions"></a>Supported Ruby Versions
|
165
|
-
This library aims to support and is [tested against][
|
175
|
+
This library aims to support and is [tested against][travis] the following Ruby
|
166
176
|
implementations:
|
167
177
|
|
168
178
|
* Ruby 1.8.7
|
data/bin/t
CHANGED
@@ -5,5 +5,38 @@ require 't'
|
|
5
5
|
begin
|
6
6
|
T::CLI.start(ARGV)
|
7
7
|
rescue Interrupt
|
8
|
-
puts "Quitting..."
|
8
|
+
$stderr.puts "Quitting..."
|
9
|
+
exit 1
|
10
|
+
rescue Twitter::Error::BadRequest => error
|
11
|
+
$stderr.puts error.message
|
12
|
+
exit 400
|
13
|
+
rescue OAuth::Unauthorized
|
14
|
+
$stderr.puts "Authorization failed"
|
15
|
+
exit 401
|
16
|
+
rescue Twitter::Error::Unauthorized => error
|
17
|
+
$stderr.puts error.message
|
18
|
+
$stderr.puts
|
19
|
+
$stderr.puts "Run `#{$0} authorize --consumer-key=CONSUMER_KEY --consumer-secret=CONSUMER_SECRET` to authorize."
|
20
|
+
exit 401
|
21
|
+
rescue Twitter::Error::Forbidden => error
|
22
|
+
$stderr.puts error.message
|
23
|
+
exit 403
|
24
|
+
rescue Twitter::Error::NotFound => error
|
25
|
+
$stderr.puts error.message
|
26
|
+
exit 404
|
27
|
+
rescue Twitter::Error::NotAcceptable => error
|
28
|
+
$stderr.puts error.message
|
29
|
+
exit 406
|
30
|
+
rescue Twitter::Error::EnhanceYourCalm => error
|
31
|
+
$stderr.puts error.message
|
32
|
+
exit 420
|
33
|
+
rescue Twitter::Error::InternalServerError => error
|
34
|
+
$stderr.puts error.message
|
35
|
+
exit 500
|
36
|
+
rescue Twitter::Error::BadGateway => error
|
37
|
+
$stderr.puts error.message
|
38
|
+
exit 502
|
39
|
+
rescue Twitter::Error::ServiceUnavailable => error
|
40
|
+
$stderr.puts error.message
|
41
|
+
exit 503
|
9
42
|
end
|
data/lib/t/cli.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
require 'action_view'
|
2
2
|
require 'launchy'
|
3
3
|
require 'oauth'
|
4
|
+
require 't/core_ext/string'
|
5
|
+
require 't/delete'
|
4
6
|
require 't/rcfile'
|
5
7
|
require 't/set'
|
6
8
|
require 'thor'
|
@@ -10,262 +12,305 @@ require 'yaml'
|
|
10
12
|
|
11
13
|
module T
|
12
14
|
class CLI < Thor
|
15
|
+
include ActionView::Helpers::DateHelper
|
16
|
+
include ActionView::Helpers::NumberHelper
|
17
|
+
|
13
18
|
DEFAULT_HOST = 'api.twitter.com'
|
14
19
|
DEFAULT_PROTOCOL = 'https'
|
15
20
|
|
16
|
-
class_option
|
17
|
-
class_option
|
21
|
+
class_option :host, :aliases => "-H", :type => :string, :default => DEFAULT_HOST, :desc => "Twitter API server"
|
22
|
+
class_option :no_ssl, :aliases => "-U", :type => :boolean, :default => false, :desc => "Disable SSL"
|
23
|
+
class_option :profile, :aliases => "-P", :type => :string, :default => File.join(File.expand_path("~"), RCFile::FILE_NAME), :desc => "Path to RC file", :banner => "FILE"
|
18
24
|
|
19
|
-
|
20
|
-
|
25
|
+
check_unknown_options!
|
26
|
+
|
27
|
+
def initialize(*)
|
28
|
+
super
|
29
|
+
@rcfile = RCFile.instance
|
30
|
+
end
|
21
31
|
|
22
32
|
desc "accounts", "List accounts"
|
23
33
|
def accounts
|
24
|
-
rcfile =
|
25
|
-
rcfile.profiles.each do |profile|
|
34
|
+
@rcfile.path = options['profile'] if options['profile']
|
35
|
+
@rcfile.profiles.each do |profile|
|
26
36
|
say profile[0]
|
27
37
|
profile[1].keys.each do |key|
|
28
|
-
say " #{key}#{rcfile.default_profile[0] == profile[0] && rcfile.default_profile[1] == key ? " (default)" : nil}"
|
38
|
+
say " #{key}#{@rcfile.default_profile[0] == profile[0] && @rcfile.default_profile[1] == key ? " (default)" : nil}"
|
29
39
|
end
|
30
40
|
end
|
31
41
|
end
|
32
42
|
map %w(list ls) => :accounts
|
33
43
|
|
34
44
|
desc "authorize", "Allows an application to request user authorization"
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
45
|
+
method_option :consumer_key, :aliases => "-c", :required => true
|
46
|
+
method_option :consumer_secret, :aliases => "-s", :required => true
|
47
|
+
method_option :prompt, :aliases => "-p", :type => :boolean, :default => true
|
48
|
+
method_option :dry_run, :type => :boolean
|
39
49
|
def authorize
|
40
50
|
request_token = consumer.get_request_token
|
41
51
|
url = generate_authorize_url(request_token)
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
52
|
+
if options['prompt']
|
53
|
+
say "In a moment, your web browser will open to the Twitter app authorization page."
|
54
|
+
say "Perform the following steps to complete the authorization process:"
|
55
|
+
say " 1. Sign in to Twitter"
|
56
|
+
say " 2. Press \"Authorize app\""
|
57
|
+
say " 3. Copy or memorize the supplied PIN"
|
58
|
+
say " 4. Return to the terminal to enter the PIN"
|
59
|
+
say
|
60
|
+
ask "Press [Enter] to open the Twitter app authorization page."
|
61
|
+
say
|
51
62
|
end
|
52
|
-
Launchy.open(url)
|
53
|
-
pin = ask "
|
63
|
+
Launchy.open(url, :dry_run => options.fetch('dry_run', false))
|
64
|
+
pin = ask "Paste in the supplied PIN:"
|
54
65
|
access_token = request_token.get_access_token(:oauth_verifier => pin.chomp)
|
55
66
|
oauth_response = access_token.get('/1/account/verify_credentials.json')
|
56
67
|
username = oauth_response.body.match(/"screen_name"\s*:\s*"(.*?)"/).captures.first
|
57
|
-
rcfile =
|
58
|
-
rcfile[username] = {
|
59
|
-
options['
|
68
|
+
@rcfile.path = options['profile'] if options['profile']
|
69
|
+
@rcfile[username] = {
|
70
|
+
options['consumer_key'] => {
|
60
71
|
'username' => username,
|
61
|
-
'consumer_key' => options['
|
62
|
-
'consumer_secret' => options['
|
72
|
+
'consumer_key' => options['consumer_key'],
|
73
|
+
'consumer_secret' => options['consumer_secret'],
|
63
74
|
'token' => access_token.token,
|
64
75
|
'secret' => access_token.secret,
|
65
76
|
}
|
66
77
|
}
|
67
|
-
rcfile.default_profile = {'username' => username, 'consumer_key' => options['
|
78
|
+
@rcfile.default_profile = {'username' => username, 'consumer_key' => options['consumer_key']}
|
68
79
|
say "Authorization successful"
|
69
|
-
rescue OAuth::Unauthorized
|
70
|
-
raise Exception, "Authorization failed. Check that your consumer key and secret are correct, as well as your username and password."
|
71
80
|
end
|
72
81
|
|
73
82
|
desc "block USERNAME", "Block a user."
|
74
83
|
def block(username)
|
75
|
-
|
76
|
-
|
84
|
+
username = username.strip_at
|
85
|
+
user = client.block(username)
|
86
|
+
say "@#{@rcfile.default_profile[0]} blocked @#{user.screen_name}"
|
77
87
|
say
|
78
|
-
say "Run `#{$0}
|
88
|
+
say "Run `#{$0} delete block #{user.screen_name}` to unblock."
|
79
89
|
end
|
80
90
|
|
81
91
|
desc "direct_messages", "Returns the 20 most recent Direct Messages sent to you."
|
82
92
|
def direct_messages
|
83
|
-
|
93
|
+
run_pager
|
94
|
+
client.direct_messages.map do |direct_message|
|
84
95
|
say "#{direct_message.sender.screen_name.rjust(20)}: #{direct_message.text} (#{time_ago_in_words(direct_message.created_at)} ago)"
|
85
96
|
end
|
86
97
|
end
|
98
|
+
map %w(dms) => :direct_messages
|
87
99
|
|
88
100
|
desc "sent_messages", "Returns the 20 most recent Direct Messages sent to you."
|
89
101
|
def sent_messages
|
90
|
-
|
102
|
+
run_pager
|
103
|
+
client.direct_messages_sent.map do |direct_message|
|
91
104
|
say "#{direct_message.recipient.screen_name.rjust(20)}: #{direct_message.text} (#{time_ago_in_words(direct_message.created_at)} ago)"
|
92
105
|
end
|
93
106
|
end
|
107
|
+
map %w(sms) => :sent_messages
|
94
108
|
|
95
109
|
desc "dm USERNAME MESSAGE", "Sends that person a Direct Message."
|
96
110
|
def dm(username, message)
|
111
|
+
username = username.strip_at
|
97
112
|
direct_message = client.direct_message_create(username, message)
|
98
|
-
say "Direct Message sent to @#{
|
113
|
+
say "Direct Message sent from @#{@rcfile.default_profile[0]} to @#{direct_message.recipient.screen_name} (#{time_ago_in_words(direct_message.created_at)} ago)"
|
99
114
|
end
|
100
|
-
map
|
115
|
+
map %w(m) => :dm
|
101
116
|
|
102
117
|
desc "favorite USERNAME", "Marks that user's last Tweet as one of your favorites."
|
103
118
|
def favorite(username)
|
104
|
-
|
105
|
-
client.
|
106
|
-
|
119
|
+
username = username.strip_at
|
120
|
+
user = client.user(username)
|
121
|
+
if user.status
|
122
|
+
client.favorite(user.status.id)
|
123
|
+
say "@#{@rcfile.default_profile[0]} favorited @#{user.screen_name}'s latest status: #{user.status.text}"
|
124
|
+
say
|
125
|
+
say "Run `#{$0} delete favorite` to unfavorite."
|
126
|
+
else
|
127
|
+
raise Thor::Error, "Tweet not found"
|
128
|
+
end
|
107
129
|
rescue Twitter::Error::Forbidden => error
|
108
|
-
|
130
|
+
if error.message =~ /You have already favorited this status\./
|
131
|
+
say "@#{@rcfile.default_profile[0]} favorited @#{user.screen_name}'s latest status: #{user.status.text}"
|
132
|
+
else
|
133
|
+
raise
|
134
|
+
end
|
109
135
|
end
|
110
|
-
map
|
136
|
+
map %w(fave) => :favorite
|
111
137
|
|
112
138
|
desc "follow USERNAME", "Allows you to start following a specific user."
|
113
139
|
def follow(username)
|
140
|
+
username = username.strip_at
|
114
141
|
user = client.follow(username)
|
115
|
-
say "
|
116
|
-
recommendations = client.recommendations(:user_id => user.id, :limit => 2)
|
142
|
+
say "@#{@rcfile.default_profile[0]} is now following @#{user.screen_name}."
|
117
143
|
say
|
118
|
-
say "
|
119
|
-
status = client.user_timeline(username).first
|
120
|
-
say "#{username}: #{status.text} (#{time_ago_in_words(status.created_at)} ago)"
|
144
|
+
say "Run `#{$0} unfollow #{user.screen_name}` to stop."
|
121
145
|
end
|
122
|
-
map
|
146
|
+
map %w(befriend) => :follow
|
123
147
|
|
124
148
|
desc "get USERNAME", "Retrieves the latest update posted by the user."
|
125
149
|
def get(username)
|
126
|
-
|
127
|
-
|
150
|
+
username = username.strip_at
|
151
|
+
user = client.user(username)
|
152
|
+
if user.status
|
153
|
+
say "#{user.status.text} (#{time_ago_in_words(user.status.created_at)} ago)"
|
154
|
+
else
|
155
|
+
raise Thor::Error, "Tweet not found"
|
156
|
+
end
|
128
157
|
end
|
129
158
|
|
130
159
|
desc "mentions", "Returns the 20 most recent Tweets mentioning you."
|
131
|
-
|
160
|
+
method_option :reverse, :aliases => "-r", :type => :boolean, :default => false
|
132
161
|
def mentions
|
133
162
|
timeline = client.mentions
|
134
163
|
timeline.reverse! if options['reverse']
|
135
|
-
|
164
|
+
run_pager
|
165
|
+
timeline.map do |status|
|
136
166
|
say "#{status.user.screen_name.rjust(20)}: #{status.text} (#{time_ago_in_words(status.created_at)} ago)"
|
137
167
|
end
|
138
168
|
end
|
139
|
-
map
|
169
|
+
map %w(replies) => :mentions
|
140
170
|
|
141
171
|
desc "open USERNAME", "Opens that user's profile in a web browser."
|
172
|
+
method_option :dry_run, :type => :boolean
|
142
173
|
def open(username)
|
143
|
-
|
174
|
+
username = username.strip_at
|
175
|
+
Launchy.open("https://twitter.com/#{username}", :dry_run => options.fetch('dry_run', false))
|
144
176
|
end
|
145
177
|
|
146
178
|
desc "reply USERNAME MESSAGE", "Post your Tweet as a reply directed at another person."
|
179
|
+
method_option :location, :aliases => "-l", :type => :boolean, :default => true
|
147
180
|
def reply(username, message)
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
181
|
+
username = username.strip_at
|
182
|
+
hash = {}
|
183
|
+
hash.merge!(:lat => location.lat, :long => location.lng) if options['location']
|
184
|
+
user = client.user(username)
|
185
|
+
hash.merge!(:in_reply_to_status_id => user.status.id) if user.status
|
186
|
+
status = client.update("@#{user.screen_name} #{message}", hash)
|
187
|
+
say "Reply created by @#{@rcfile.default_profile[0]} to @#{user.screen_name} (#{time_ago_in_words(status.created_at)} ago)"
|
188
|
+
say
|
189
|
+
say "Run `#{$0} delete status` to delete."
|
153
190
|
end
|
154
191
|
|
155
192
|
desc "retweet USERNAME", "Sends that user's latest Tweet to your followers."
|
156
193
|
def retweet(username)
|
157
|
-
|
158
|
-
client.
|
159
|
-
|
194
|
+
username = username.strip_at
|
195
|
+
user = client.user(username)
|
196
|
+
if user.status
|
197
|
+
client.retweet(user.status.id)
|
198
|
+
say "@#{@rcfile.default_profile[0]} retweeted @#{user.screen_name}'s latest status: #{user.status.text}"
|
199
|
+
say
|
200
|
+
say "Run `#{$0} delete status` to undo."
|
201
|
+
else
|
202
|
+
raise Thor::Error, "Tweet not found"
|
203
|
+
end
|
204
|
+
rescue Twitter::Error::Forbidden => error
|
205
|
+
if error.message =~ /sharing is not permissable for this status \(Share validations failed\)/
|
206
|
+
say "@#{@rcfile.default_profile[0]} retweeted @#{user.screen_name}'s latest status: #{user.status.text}"
|
207
|
+
else
|
208
|
+
raise
|
209
|
+
end
|
160
210
|
end
|
161
|
-
map
|
211
|
+
map %w(rt) => :retweet
|
162
212
|
|
163
213
|
desc "stats USERNAME", "Retrieves the given user's number of followers and how many people they're following."
|
164
214
|
def stats(username)
|
215
|
+
username = username.strip_at
|
165
216
|
user = client.user(username)
|
166
217
|
say "Followers: #{number_with_delimiter(user.followers_count)}"
|
167
218
|
say "Following: #{number_with_delimiter(user.friends_count)}"
|
168
219
|
say
|
169
|
-
say "Run `#{$0} whois #{
|
220
|
+
say "Run `#{$0} whois #{user.screen_name}` to view profile."
|
170
221
|
end
|
171
222
|
|
223
|
+
desc "status MESSAGE", "Post a Tweet."
|
224
|
+
method_option :location, :aliases => "-l", :type => :boolean, :default => true
|
225
|
+
def status(message)
|
226
|
+
hash = {}
|
227
|
+
hash.merge!(:lat => location.lat, :long => location.lng) if options['location']
|
228
|
+
status = client.update(message, hash)
|
229
|
+
say "Tweet created by @#{@rcfile.default_profile[0]} (#{time_ago_in_words(status.created_at)} ago)"
|
230
|
+
say
|
231
|
+
say "Run `#{$0} delete status` to delete."
|
232
|
+
end
|
233
|
+
map %w(post tweet update) => :status
|
234
|
+
|
172
235
|
desc "suggest", "This command returns a listing of Twitter users' accounts we think you might enjoy following."
|
173
236
|
def suggest
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
237
|
+
recommendation = client.recommendations(:limit => 1).first
|
238
|
+
if recommendation
|
239
|
+
say "Try following @#{recommendation.screen_name}."
|
240
|
+
say
|
241
|
+
say "Run `#{$0} follow #{recommendation.screen_name}` to follow."
|
242
|
+
say "Run `#{$0} whois #{recommendation.screen_name}` for profile."
|
243
|
+
say "Run `#{$0} suggest` for another recommendation."
|
244
|
+
end
|
180
245
|
end
|
181
246
|
|
182
247
|
desc "timeline", "Returns the 20 most recent Tweets posted by you and the users you follow."
|
183
|
-
|
248
|
+
method_option :reverse, :aliases => "-r", :type => :boolean, :default => false
|
184
249
|
def timeline
|
185
250
|
timeline = client.home_timeline
|
186
251
|
timeline.reverse! if options['reverse']
|
187
|
-
|
252
|
+
run_pager
|
253
|
+
timeline.map do |status|
|
188
254
|
say "#{status.user.screen_name.rjust(20)}: #{status.text} (#{time_ago_in_words(status.created_at)} ago)"
|
189
255
|
end
|
190
256
|
end
|
191
|
-
map
|
192
|
-
|
193
|
-
desc "unblock USERNAME", "Unblock a user."
|
194
|
-
def unblock(username)
|
195
|
-
client.unblock(username)
|
196
|
-
say "Unblocked @#{username}"
|
197
|
-
say
|
198
|
-
say "Run `#{$0} block #{username}` to block."
|
199
|
-
end
|
200
|
-
|
201
|
-
desc "unfavorite USERNAME", "Marks that user's last Tweet as one of your favorites."
|
202
|
-
def unfavorite(username)
|
203
|
-
status = client.user_timeline(username).first
|
204
|
-
client.unfavorite(status.id)
|
205
|
-
say "You have unfavorited @#{username}'s latest tweet: #{status.text}"
|
206
|
-
end
|
257
|
+
map %w(tl) => :timeline
|
207
258
|
|
208
259
|
desc "unfollow USERNAME", "Allows you to stop following a specific user."
|
209
260
|
def unfollow(username)
|
261
|
+
username = username.strip_at
|
210
262
|
user = client.unfollow(username)
|
211
|
-
say "
|
263
|
+
say "@#{@rcfile.default_profile[0]} is no longer following @#{user.screen_name}."
|
264
|
+
say
|
265
|
+
say "Run `#{$0} follow #{user.screen_name}` to follow again."
|
212
266
|
end
|
213
|
-
map
|
267
|
+
map %w(defriend) => :unfollow
|
214
268
|
|
215
|
-
desc "
|
216
|
-
def
|
217
|
-
|
218
|
-
say "Tweet created (#{time_ago_in_words(status.created_at)} ago)"
|
219
|
-
rescue Twitter::Error::Forbidden => error
|
220
|
-
say error.message
|
269
|
+
desc "version", "Show version"
|
270
|
+
def version
|
271
|
+
say T::Version
|
221
272
|
end
|
222
|
-
map
|
273
|
+
map %w(-v --version) => :version
|
223
274
|
|
224
275
|
desc "whois USERNAME", "Retrieves profile information for the user."
|
225
276
|
def whois(username)
|
277
|
+
username = username.strip_at
|
226
278
|
user = client.user(username)
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
output << "web: #{user.url}"
|
232
|
-
say output.join("\n")
|
279
|
+
say "#{user.name}, since #{user.created_at.strftime("%b %Y")}."
|
280
|
+
say "bio: #{user.description}"
|
281
|
+
say "location: #{user.location}"
|
282
|
+
say "web: #{user.url}"
|
233
283
|
end
|
234
284
|
|
235
|
-
desc "
|
236
|
-
|
237
|
-
|
238
|
-
end
|
239
|
-
map %w(-v --version) => :version
|
285
|
+
desc "delete SUBCOMMAND ...ARGS", "Delete Tweets, Direct Messages, etc."
|
286
|
+
method_option :force, :aliases => "-f", :type => :boolean
|
287
|
+
subcommand 'delete', Delete
|
240
288
|
|
241
289
|
desc "set SUBCOMMAND ...ARGS", "Change various account settings."
|
242
290
|
subcommand 'set', Set
|
243
291
|
|
244
292
|
no_tasks do
|
245
293
|
|
246
|
-
def access_token
|
247
|
-
OAuth::AccessToken.new(consumer, token, secret)
|
248
|
-
end
|
249
|
-
|
250
294
|
def base_url
|
251
295
|
"#{protocol}://#{host}"
|
252
296
|
end
|
253
297
|
|
254
298
|
def client
|
255
|
-
|
256
|
-
|
299
|
+
return @client if @client
|
300
|
+
@rcfile.path = options['profile'] if options['profile']
|
301
|
+
@client = Twitter::Client.new(
|
257
302
|
:endpoint => base_url,
|
258
|
-
:consumer_key => rcfile.default_consumer_key,
|
259
|
-
:consumer_secret => rcfile.default_consumer_secret,
|
260
|
-
:oauth_token => rcfile.default_token,
|
261
|
-
:oauth_token_secret => rcfile.default_secret
|
303
|
+
:consumer_key => @rcfile.default_consumer_key,
|
304
|
+
:consumer_secret => @rcfile.default_consumer_secret,
|
305
|
+
:oauth_token => @rcfile.default_token,
|
306
|
+
:oauth_token_secret => @rcfile.default_secret
|
262
307
|
)
|
263
308
|
end
|
264
309
|
|
265
310
|
def consumer
|
266
311
|
OAuth::Consumer.new(
|
267
|
-
options['
|
268
|
-
options['
|
312
|
+
options['consumer_key'],
|
313
|
+
options['consumer_secret'],
|
269
314
|
:site => base_url
|
270
315
|
)
|
271
316
|
end
|
@@ -284,12 +329,49 @@ module T
|
|
284
329
|
options['host'] || DEFAULT_HOST
|
285
330
|
end
|
286
331
|
|
332
|
+
def location
|
333
|
+
return @location if @location
|
334
|
+
require 'geokit'
|
335
|
+
require 'open-uri'
|
336
|
+
ip_address = Kernel::open("http://checkip.dyndns.org/") do |body|
|
337
|
+
/(?:\d{1,3}\.){3}\d{1,3}/.match(body.read)[0]
|
338
|
+
end
|
339
|
+
@location = Geokit::Geocoders::MultiGeocoder.geocode(ip_address)
|
340
|
+
end
|
341
|
+
|
287
342
|
def pin_auth_parameters
|
288
343
|
{:oauth_callback => 'oob'}
|
289
344
|
end
|
290
345
|
|
291
346
|
def protocol
|
292
|
-
options['
|
347
|
+
options['no_ssl'] ? 'http' : DEFAULT_PROTOCOL
|
348
|
+
end
|
349
|
+
|
350
|
+
def run_pager
|
351
|
+
return if RUBY_PLATFORM =~ /win32/
|
352
|
+
return if ENV["T_ENV"] == "test"
|
353
|
+
return unless STDOUT.tty?
|
354
|
+
|
355
|
+
read, write = IO.pipe
|
356
|
+
|
357
|
+
unless Kernel.fork # Child process
|
358
|
+
STDOUT.reopen(write)
|
359
|
+
STDERR.reopen(write) if STDERR.tty?
|
360
|
+
read.close
|
361
|
+
write.close
|
362
|
+
return
|
363
|
+
end
|
364
|
+
|
365
|
+
# Parent process, become pager
|
366
|
+
STDIN.reopen(read)
|
367
|
+
read.close
|
368
|
+
write.close
|
369
|
+
|
370
|
+
ENV['LESS'] = 'FSRX' # Don't page if the input is short enough
|
371
|
+
|
372
|
+
Kernel.select [STDIN] # Wait until we have input before we start the pager
|
373
|
+
pager = ENV['PAGER'] || 'less'
|
374
|
+
exec pager rescue exec "/bin/sh", "-c", pager
|
293
375
|
end
|
294
376
|
|
295
377
|
end
|