t 0.0.2 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
-
[][
|
22
|
+
## <a name="build"></a>Build Status
|
23
|
+
[][travis]
|
24
24
|
|
25
|
-
[
|
25
|
+
[travis]: http://travis-ci.org/sferik/t
|
26
26
|
|
27
27
|
## <a name="dependencies"></a>Dependency Status
|
28
|
-
[][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
|