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 CHANGED
@@ -1 +1,2 @@
1
+ --format=documentation
1
2
  --color
data/.travis.yml CHANGED
@@ -2,6 +2,6 @@ rvm:
2
2
  - 1.8.7
3
3
  - 1.9.2
4
4
  - 1.9.3
5
+ - jruby
5
6
  - rbx
6
7
  - ree
7
- - jruby
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="ci"></a>Continuous Integration
23
- [![Build Status](https://secure.travis-ci.org/sferik/t.png)][ci]
22
+ ## <a name="build"></a>Build Status
23
+ [![Build Status](https://secure.travis-ci.org/sferik/t.png?branch=master)][travis]
24
24
 
25
- [ci]: http://travis-ci.org/sferik/t
25
+ [travis]: http://travis-ci.org/sferik/t
26
26
 
27
27
  ## <a name="dependencies"></a>Dependency Status
28
- [![Dependency Status](https://gemnasium.com/codeforamerica/adopt-a-hydrant.png)][gemnasium]
28
+ [![Dependency Status](https://gemnasium.com/sferik/t.png?travis)][gemnasium]
29
29
 
30
- [gemnasium]: https://gemnasium.com/codeforamerica/adopt-a-hydrant
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 assigned a consumer key
45
- and secret, which you can use to authorize your Twitter account.
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
- You can easily switch between accounts.
57
+ sferik
58
+ UDfNTpOz5ZDG4a6w7dIWj
59
+ uuP7Xbl2mEfGMiDu1uIyFN
60
+ gem
61
+ thG9EfWoADtIr6NjbL9ON (default)
56
62
 
57
- t set default sferik
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
- Incidentally, account information is stored in YAML format in `~/.trc`.
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, powered by @gem."
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][ci] the following Ruby
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 "host", :aliases => "-H", :default => DEFAULT_HOST
17
- class_option "no-ssl", :aliases => "-U", :type => :boolean, :default => false
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
- include ActionView::Helpers::DateHelper
20
- include ActionView::Helpers::NumberHelper
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 = RCFile.instance
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
- option "consumer-key", :aliases => "-c", :required => true
36
- option "consumer-secret", :aliases => "-s", :required => true
37
- option "access-token", :aliases => "-a"
38
- option "token-secret", :aliases => "-S"
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
- say "Authorize this app and copy the supplied PIN to complete the authorization process."
43
- print "Your default web browser will open in "
44
- 9.downto(1) do |i|
45
- sleep 0.2
46
- print i
47
- 4.times do
48
- sleep 0.2
49
- print '.'
50
- end
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 "\nPaste in the supplied PIN:"
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 = RCFile.instance
58
- rcfile[username] = {
59
- options['consumer-key'] => {
68
+ @rcfile.path = options['profile'] if options['profile']
69
+ @rcfile[username] = {
70
+ options['consumer_key'] => {
60
71
  'username' => username,
61
- 'consumer_key' => options['consumer-key'],
62
- 'consumer_secret' => options['consumer-secret'],
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['consumer-key']}
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
- client.block(username)
76
- say "Blocked @#{username}"
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} unblock #{username}` to unblock."
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
- client.direct_messages.each do |direct_message|
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
- client.direct_messages_sent.each do |direct_message|
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 @#{username} (#{time_ago_in_words(status.created_at)} ago)"
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 :m => :dm
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
- status = client.user_timeline(username).first
105
- client.favorite(status.id)
106
- say "You have favorited @#{username}'s latest tweet: #{status.text}"
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
- say error.message
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 :fave => :favorite
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 "You're now following @#{username}. Run `#{$0} unfollow #{username}` to stop."
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 "Try following @#{recommendations[0].screen_name} or @#{recommendations[1].screen_name}."
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 :befriend => :follow
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
- status = client.user_timeline(username).first
127
- say "#{status.text} (#{time_ago_in_words(status.created_at)} ago)"
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
- option "reverse", :aliases => "-r", :type => :boolean, :default => false
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
- timeline.each do |status|
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 :replies => :mentions
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
- Launchy.open("https://twitter.com/#{username}")
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
- in_reply_to_status = client.user_timeline(username).first
149
- status = client.update("@#{username} #{message}", :in_reply_to_status_id => in_reply_to_status.id)
150
- say "Reply created (#{time_ago_in_words(status.created_at)} ago)"
151
- rescue Twitter::Error::Forbidden => error
152
- say error.message
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
- status = client.user_timeline(username).first
158
- client.retweet(status.id)
159
- say "You have retweeted @#{username}'s latest tweet: #{status.text}"
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 :rt => :retweet
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 #{username}` to view profile."
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
- recommendations = client.recommendations(:limit => 2)
175
- say "Try following @#{recommendations[0].screen_name} or @#{recommendations[1].screen_name}."
176
- say
177
- say "Run `#{$0} follow USERNAME` to follow."
178
- say "Run `#{$0} whois USERNAME` for profile."
179
- say "Run `#{$0} suggest` for more."
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
- option "reverse", :aliases => "-r", :type => :boolean, :default => false
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
- timeline.each do |status|
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 :tl => :timeline
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 "You are no longer following @#{username}. Run `#{$0} follow #{username}` to follow again."
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 :defriend => :unfollow
267
+ map %w(defriend) => :unfollow
214
268
 
215
- desc "update MESSAGE", "Post a Tweet."
216
- def update(message)
217
- status = client.update(message)
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 :post => :update
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
- output = []
228
- output << "#{user.name}, since #{user.created_at.strftime("%b %Y")}."
229
- output << "bio: #{user.description}"
230
- output << "location: #{user.location}"
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 "version", "Show version"
236
- def version
237
- say T::Version
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
- rcfile = RCFile.instance
256
- Twitter::Client.new(
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['consumer-key'],
268
- options['consumer-secret'],
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['no-ssl'] ? 'http' : DEFAULT_PROTOCOL
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