t 0.9.9 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -2,7 +2,10 @@
2
2
 
3
3
  [icon]: https://github.com/sferik/t/raw/master/icon/t.png
4
4
 
5
- # Twitter CLI [![Build Status](https://secure.travis-ci.org/sferik/t.png?branch=master)][travis] [![Dependency Status](https://gemnasium.com/sferik/t.png?travis)][gemnasium] [![Click here to make a donation to T](http://www.pledgie.com/campaigns/17330.png)][pledgie]
5
+ # Twitter CLI
6
+ [![Build Status](https://secure.travis-ci.org/sferik/t.png?branch=master)][travis]
7
+ [![Dependency Status](https://gemnasium.com/sferik/t.png?travis)][gemnasium]
8
+ [![Click here to make a donation to T](http://www.pledgie.com/campaigns/17330.png)][pledgie]
6
9
 
7
10
  ### A command-line power tool for Twitter.
8
11
 
@@ -15,28 +18,53 @@ offers vastly more commands and capabilities than are available via SMS.
15
18
  [sms]: https://support.twitter.com/articles/14020-twitter-sms-command
16
19
 
17
20
  ## Installation
18
- gem install t # Requires Ruby :)
21
+
22
+ First, make sure you have Ruby installed.
23
+
24
+ **On a Mac**, open `/Applications/Utilities/Terminal.app` and type:
25
+
26
+ ruby -v
27
+
28
+ If the output looks something like this, you're in good shape:
29
+
30
+ ruby 1.9.3p194 (2012-04-20 revision 35410) [x86_64-darwin12.0.0]
31
+
32
+ If the output looks more like this, you need to [install Ruby][ruby]:
33
+
34
+ ruby: command not found
35
+
36
+ **On Windows**, you can install Ruby with [RubyInstaller][].
37
+
38
+ Once you've verified that Ruby is installed:
39
+
40
+ gem install t
41
+
42
+ [ruby]: http://www.ruby-lang.org/en/downloads/
43
+ [rubyinstaller]: http://rubyinstaller.org/
19
44
 
20
45
  ## Configuration
21
46
 
22
- Twitter requires OAuth for most of its functionality, so you'll need to
23
- register a new application at <http://dev.twitter.com/apps/new>. Once you
24
- create your application, make sure to set your application's Access Level to
25
- "Read, Write and Access direct messages", otherwise you may receive an error
26
- that looks something like this:
47
+ Twitter requires OAuth for most of its functionality, so you'll need a
48
+ registered Twitter application. If you've never registered a Twitter
49
+ application before, it's easy! Just sign-in using your Twitter account and the
50
+ fill out the short form at <http://dev.twitter.com/apps/new>. If you've
51
+ previously registered a Twitter application, it should be listed at
52
+ <http://dev.twitter.com/apps>. Once you've registered an application, make sure
53
+ to set your application's Access Level to "Read, Write and Access direct
54
+ messages", otherwise you'll receive an error that looks like this:
27
55
 
28
56
  Read-only application cannot POST
29
57
 
30
- Once you've successfully registered your application, you'll be given a
31
- consumer key and secret, which you can use to authorize your Twitter account.
58
+ Now, you're ready to authorize a Twitter account with your application. To
59
+ proceed, type the following command at the prompt and follow the instructions:
32
60
 
33
- t authorize -c YOUR_CONSUMER_KEY -s YOUR_CONSUMER_SECRET
61
+ t authorize
34
62
 
35
- This command directs you to a URL where you can sign-in to Twitter and then
36
- enter the returned PIN back into the terminal. If you type the PIN correctly,
37
- you should now be authorized to use `t` as that user. To authorize multiple
38
- accounts, simply repeat the last step, signing into Twitter as a different
39
- user.
63
+ This command will direct you to a URL where you can sign-in to Twitter,
64
+ authorize the application, and then enter the returned PIN back into the
65
+ terminal. If you type the PIN correctly, you should now be authorized to use
66
+ `t` as that user. To authorize multiple accounts, simply repeat the last step,
67
+ signing into Twitter as a different user.
40
68
 
41
69
  You can see a list of all the accounts you've authorized by typing the command:
42
70
 
@@ -108,14 +136,17 @@ example, send a user a direct message only if he already follows you:
108
136
  t lists -l
109
137
 
110
138
  ### List all your friends, in long format, ordered by number of followers
111
- t friends -lf
139
+ t friends -l --sort=followers
112
140
 
113
141
  ### List all your leaders (people you follow who don't follow you back)
114
- t leaders -lf
142
+ t leaders -l --sort=followers
115
143
 
116
144
  ### Unfollow everyone you follow who doesn't follow you back
117
145
  t leaders | xargs t unfollow
118
146
 
147
+ ### Unfollow 10 people who haven't tweeted in the longest time
148
+ t followings -l --sort=tweeted | head -10 | awk '{print $1}' | xargs t unfollow
149
+
119
150
  ### Twitter roulette: randomly follow someone who follows you (who you don't already follow)
120
151
  t groupies | shuf | head -1 | xargs t follow
121
152
 
@@ -154,12 +185,12 @@ example, send a user a direct message only if he already follows you:
154
185
 
155
186
  ## Features
156
187
  * Deep search: Instead of using the Twitter Search API, [which only only goes
157
- back 6-9 days][index], `t search` fetches up to 3,200 tweets via the REST API
188
+ back 6-9 days][search], `t search` fetches up to 3,200 tweets via the REST API
158
189
  and then checks each one against a regular expression.
159
- * Multithreaded: Whenever possible, Twitter API requests are made in parallel,
190
+ * Multi-threaded: Whenever possible, Twitter API requests are made in parallel,
160
191
  resulting in faster performance for bulk operations.
161
192
  * Designed for Unix: Output is designed to be piped to other Unix utilities,
162
- like grep, cut, awk, bc, wc, and xargs for advanced text processing.
193
+ like grep, comm, cut, awk, bc, wc, and xargs for advanced text processing.
163
194
  * Generate spreadsheets: Convert the output of any command to CSV format simply
164
195
  by adding the `--csv` flag.
165
196
  * 95% C0 Code Coverage: Well tested, with a 2.5:1 test-to-code ratio.
data/bin/t CHANGED
@@ -18,7 +18,8 @@ rescue OAuth::Unauthorized
18
18
  pute "Authorization failed"
19
19
  exit 1
20
20
  rescue Twitter::Error::Unauthorized => error
21
- pute "#{error.message} Run `#{$0} authorize --consumer-key=CONSUMER_KEY --consumer-secret=CONSUMER_SECRET` to authorize."
21
+ pute error.message
22
+ pute "Run `#{$0} authorize` to authorize."
22
23
  exit 1
23
24
  rescue Twitter::Error => error
24
25
  pute error.message
data/lib/t.rb CHANGED
@@ -1,16 +1,9 @@
1
- require 'active_support/string_inquirer'
2
1
  require 't/cli'
3
2
  require 'time'
4
3
 
5
4
  module T
6
5
  class << self
7
6
 
8
- attr_reader :env
9
-
10
- def env=(environment)
11
- @env = ActiveSupport::StringInquirer.new(environment)
12
- end
13
-
14
7
  # Convert time to local time by applying the `utc_offset` setting.
15
8
  def local_time(time)
16
9
  utc_offset ? (time.utc + utc_offset) : time.localtime
data/lib/t/cli.rb CHANGED
@@ -1,29 +1,28 @@
1
+ # encoding: utf-8
2
+ require 'oauth'
1
3
  require 'thor'
2
4
  require 'twitter'
5
+ require 't/collectable'
6
+ require 't/delete'
7
+ require 't/list'
8
+ require 't/printable'
9
+ require 't/rcfile'
10
+ require 't/requestable'
11
+ require 't/search'
12
+ require 't/set'
13
+ require 't/stream'
14
+ require 't/utils'
3
15
 
4
16
  module T
5
- autoload :Authorizable, 't/authorizable'
6
- autoload :Collectable, 't/collectable'
7
- autoload :Delete, 't/delete'
8
- autoload :FormatHelpers, 't/format_helpers'
9
- autoload :List, 't/list'
10
- autoload :Printable, 't/printable'
11
- autoload :RCFile, 't/rcfile'
12
- autoload :Requestable, 't/requestable'
13
- autoload :Search, 't/search'
14
- autoload :Set, 't/set'
15
- autoload :Stream, 't/stream'
16
- autoload :Version, 't/version'
17
17
  class CLI < Thor
18
- include T::Authorizable
19
18
  include T::Collectable
20
19
  include T::Printable
21
20
  include T::Requestable
22
- include T::FormatHelpers
21
+ include T::Utils
23
22
 
23
+ DEFAULT_HOST = 'api.twitter.com'
24
+ DEFAULT_PROTOCOL = 'https'
24
25
  DEFAULT_NUM_RESULTS = 20
25
- MAX_SCREEN_NAME_SIZE = 20
26
- MAX_USERS_PER_REQUEST = 100
27
26
  DIRECT_MESSAGE_HEADINGS = ["ID", "Posted at", "Screen name", "Text"]
28
27
  TREND_HEADINGS = ["WOEID", "Parent ID", "Type", "Name", "Country"]
29
28
 
@@ -32,11 +31,11 @@ module T
32
31
  option "host", :aliases => "-H", :type => :string, :default => DEFAULT_HOST, :desc => "Twitter API server"
33
32
  option "no-color", :aliases => "-N", :type => :boolean, :desc => "Disable colorization in output"
34
33
  option "no-ssl", :aliases => "-U", :type => :boolean, :default => false, :desc => "Disable SSL"
35
- option "profile", :aliases => "-P", :type => :string, :default => File.join(File.expand_path("~"), RCFile::FILE_NAME), :desc => "Path to RC file", :banner => "FILE"
34
+ option "profile", :aliases => "-P", :type => :string, :default => File.join(File.expand_path("~"), T::RCFile::FILE_NAME), :desc => "Path to RC file", :banner => "FILE"
36
35
 
37
36
  def initialize(*)
37
+ @rcfile = T::RCFile.instance
38
38
  super
39
- @rcfile = RCFile.instance
40
39
  end
41
40
 
42
41
  desc "accounts", "List accounts"
@@ -51,63 +50,75 @@ module T
51
50
  end
52
51
 
53
52
  desc "authorize", "Allows an application to request user authorization"
54
- method_option "consumer-key", :aliases => "-c", :required => true, :desc => "This can be found at https://dev.twitter.com/apps", :banner => "KEY"
55
- method_option "consumer-secret", :aliases => "-s", :required => true, :desc => "This can be found at https://dev.twitter.com/apps", :banner => "SECRET"
56
53
  method_option "display-url", :aliases => "-d", :type => :boolean, :default => false, :desc => "Display the authorization URL instead of attempting to open it."
57
- method_option "prompt", :aliases => "-p", :type => :boolean, :default => true
58
54
  def authorize
59
- request_token = consumer.get_request_token
60
- url = generate_authorize_url(request_token)
61
- if options['prompt']
62
- say "In a moment, you will be directed to the Twitter app authorization page."
63
- say "Perform the following steps to complete the authorization process:"
64
- say " 1. Sign in to Twitter"
65
- say " 2. Press \"Authorize app\""
66
- say " 3. Copy or memorize the supplied PIN"
67
- say " 4. Return to the terminal to enter the PIN"
55
+ @rcfile.path = options['profile'] if options['profile']
56
+ if @rcfile.empty?
57
+ say "Welcome! Before you can use t, you'll first need to register an"
58
+ say "application with Twitter. Just follow the steps below:"
59
+ say " 1. Sign in to the Twitter Developer site and click"
60
+ say " \"Create a new application\"."
61
+ say " 2. Complete the required fields and submit the form."
62
+ say " Note: Your application must have a unique name."
63
+ say " We recommend: \"<your handle>/t\"."
64
+ say " 3. Go to the Settings tab of your application, and change the"
65
+ say " Access setting to \"Read, Write and Access direct messages\"."
66
+ say " 4. Go to the Details tab to view the consumer key and secret,"
67
+ say " which you'll need to copy and paste below when prompted."
68
68
  say
69
- ask "Press [Enter] to open the Twitter app authorization page."
69
+ ask "Press [Enter] to open the Twitter Developer site."
70
+ say
71
+ else
72
+ say "It looks like you've already registered an application with Twitter."
73
+ say "To authorize a new account, just follow the steps below:"
74
+ say " 1. Sign in to the Twitter Developer site."
75
+ say " 2. Select the application for which you'd like to authorize an account."
76
+ say " 3. Copy and paste the consumer key and secret below when prompted."
77
+ say
78
+ ask "Press [Enter] to open the Twitter Developer site."
70
79
  say
71
80
  end
72
81
  require 'launchy'
82
+ Launchy.open("https://dev.twitter.com/apps", :dry_run => options['display-url'])
83
+ key = ask "Enter your consumer key:"
84
+ secret = ask "Enter your consumer secret:"
85
+ consumer = OAuth::Consumer.new(key, secret, :site => base_url)
86
+ request_token = consumer.get_request_token
87
+ url = generate_authorize_url(consumer, request_token)
88
+ say
89
+ say "In a moment, you will be directed to the Twitter app authorization page."
90
+ say "Perform the following steps to complete the authorization process:"
91
+ say " 1. Sign in to Twitter."
92
+ say " 2. Press \"Authorize app\"."
93
+ say " 3. Copy and paste the supplied PIN below when prompted."
94
+ say
95
+ ask "Press [Enter] to open the Twitter app authorization page."
96
+ say
73
97
  Launchy.open(url, :dry_run => options['display-url'])
74
- pin = ask "Paste in the supplied PIN:"
98
+ pin = ask "Enter the supplied PIN:"
75
99
  access_token = request_token.get_access_token(:oauth_verifier => pin.chomp)
76
100
  oauth_response = access_token.get('/1/account/verify_credentials.json')
77
101
  screen_name = oauth_response.body.match(/"screen_name"\s*:\s*"(.*?)"/).captures.first
78
- @rcfile.path = options['profile'] if options['profile']
79
102
  @rcfile[screen_name] = {
80
- options['consumer-key'] => {
103
+ key => {
81
104
  'username' => screen_name,
82
- 'consumer_key' => options['consumer-key'],
83
- 'consumer_secret' => options['consumer-secret'],
105
+ 'consumer_key' => key,
106
+ 'consumer_secret' => secret,
84
107
  'token' => access_token.token,
85
108
  'secret' => access_token.secret,
86
109
  }
87
110
  }
88
- @rcfile.active_profile = {'username' => screen_name, 'consumer_key' => options['consumer-key']}
111
+ @rcfile.active_profile = {'username' => screen_name, 'consumer_key' => key}
89
112
  say "Authorization successful."
90
113
  end
91
114
 
92
115
  desc "block USER [USER...]", "Block users."
93
116
  method_option "id", :aliases => "-i", :type => "boolean", :default => false, :desc => "Specify input as Twitter user IDs instead of screen names."
94
117
  def block(user, *users)
95
- users.unshift(user)
96
- require 't/core_ext/string'
97
- if options['id']
98
- users.map!(&:to_i)
99
- else
100
- users.map!(&:strip_ats)
101
- end
102
- require 't/core_ext/enumerable'
103
- require 'retryable'
104
- users = users.threaded_map do |user|
105
- retryable(:tries => 3, :on => Twitter::Error::ServerError, :sleep => 0) do
106
- client.block(user)
107
- end
118
+ users, number = fetch_users(users.unshift(user), options) do |users|
119
+ client.block(users)
108
120
  end
109
- number = users.length
110
- say "@#{@rcfile.active_profile[0]} blocked #{number} #{number == 1 ? 'user' : 'users'}."
121
+ say "@#{@rcfile.active_profile[0]} blocked #{pluralize(number, 'user')}."
111
122
  say
112
123
  say "Run `#{File.basename($0)} delete block #{users.map{|user| "@#{user.screen_name}"}.join(' ')}` to unblock."
113
124
  end
@@ -139,13 +150,13 @@ module T
139
150
  print_table_with_headings(array, DIRECT_MESSAGE_HEADINGS, format)
140
151
  else
141
152
  direct_messages.each do |direct_message|
142
- say "#{direct_message.sender.screen_name.rjust(MAX_SCREEN_NAME_SIZE)}: #{direct_message.text.gsub(/\n+/, ' ')} (#{time_ago_in_words(direct_message.created_at)} ago)"
153
+ print_message(direct_message.sender.screen_name, direct_message.text)
143
154
  end
144
155
  end
145
156
  end
146
157
  map %w(directmessages dms) => :direct_messages
147
158
 
148
- desc "direct_messages_sent", "Returns the #{DEFAULT_NUM_RESULTS} most recent Direct Messages sent to you."
159
+ desc "direct_messages_sent", "Returns the #{DEFAULT_NUM_RESULTS} most recent Direct Messages you've sent."
149
160
  method_option "csv", :aliases => "-c", :type => :boolean, :default => false, :desc => "Output in CSV format."
150
161
  method_option "long", :aliases => "-l", :type => :boolean, :default => false, :desc => "Output in long format."
151
162
  method_option "number", :aliases => "-n", :type => :numeric, :default => DEFAULT_NUM_RESULTS, :desc => "Limit the number of results."
@@ -172,7 +183,7 @@ module T
172
183
  print_table_with_headings(array, DIRECT_MESSAGE_HEADINGS, format)
173
184
  else
174
185
  direct_messages.each do |direct_message|
175
- say "#{direct_message.recipient.screen_name.rjust(MAX_SCREEN_NAME_SIZE)}: #{direct_message.text.gsub(/\n+/, ' ')} (#{time_ago_in_words(direct_message.created_at)} ago)"
186
+ print_message(direct_message.recipient.screen_name, direct_message.text)
176
187
  end
177
188
  end
178
189
  end
@@ -180,15 +191,10 @@ module T
180
191
 
181
192
  desc "groupies [USER]", "Returns the list of people who follow you but you don't follow back."
182
193
  method_option "csv", :aliases => "-c", :type => :boolean, :default => false, :desc => "Output in CSV format."
183
- method_option "favorites", :aliases => "-v", :type => :boolean, :default => false, :desc => "Sort by number of favorites."
184
- method_option "followers", :aliases => "-f", :type => :boolean, :default => false, :desc => "Sort by number of followers."
185
- method_option "friends", :aliases => "-e", :type => :boolean, :default => false, :desc => "Sort by number of friends."
186
194
  method_option "id", :aliases => "-i", :type => "boolean", :default => false, :desc => "Specify user via ID instead of screen name."
187
- method_option "listed", :aliases => "-d", :type => :boolean, :default => false, :desc => "Sort by number of list memberships."
188
195
  method_option "long", :aliases => "-l", :type => :boolean, :default => false, :desc => "Output in long format."
189
- method_option "posted", :aliases => "-p", :type => :boolean, :default => false, :desc => "Sort by the time when Twitter account was posted."
190
196
  method_option "reverse", :aliases => "-r", :type => :boolean, :default => false, :desc => "Reverse the order of the sort."
191
- method_option "tweets", :aliases => "-t", :type => :boolean, :default => false, :desc => "Sort by number of Tweets."
197
+ method_option "sort", :aliases => "-s", :type => :string, :enum => %w(favorites followers friends listed screen_name since tweets tweeted), :default => "screen_name", :desc => "Specify the order of the results.", :banner => "ORDER"
192
198
  method_option "unsorted", :aliases => "-u", :type => :boolean, :default => false, :desc => "Output is not sorted."
193
199
  def groupies(user=nil)
194
200
  if user
@@ -199,21 +205,21 @@ module T
199
205
  user.strip_ats
200
206
  end
201
207
  end
202
- follower_ids = collect_with_cursor do |cursor|
203
- client.follower_ids(user, :cursor => cursor)
208
+ follower_ids = Thread.new do
209
+ collect_with_cursor do |cursor|
210
+ client.follower_ids(user, :cursor => cursor)
211
+ end
204
212
  end
205
- following_ids = collect_with_cursor do |cursor|
206
- client.friend_ids(user, :cursor => cursor)
213
+ following_ids = Thread.new do
214
+ collect_with_cursor do |cursor|
215
+ client.friend_ids(user, :cursor => cursor)
216
+ end
207
217
  end
208
- disciple_ids = (follower_ids - following_ids)
209
- require 'active_support/core_ext/array/grouping'
210
- require 't/core_ext/enumerable'
218
+ disciple_ids = (follower_ids.value - following_ids.value)
211
219
  require 'retryable'
212
- users = disciple_ids.in_groups_of(MAX_USERS_PER_REQUEST, false).threaded_map do |disciple_id_group|
213
- retryable(:tries => 3, :on => Twitter::Error::ServerError, :sleep => 0) do
214
- client.users(disciple_id_group)
215
- end
216
- end.flatten
220
+ users = retryable(:tries => 3, :on => Twitter::Error::ServerError, :sleep => 0) do
221
+ client.users(disciple_ids)
222
+ end
217
223
  print_users(users)
218
224
  end
219
225
  map %w(disciples) => :groupies
@@ -228,25 +234,14 @@ module T
228
234
  user.strip_ats
229
235
  end
230
236
  direct_message = client.direct_message_create(user, message)
231
- say "Direct Message sent from @#{@rcfile.active_profile[0]} to @#{direct_message.recipient.screen_name} (#{time_ago_in_words(direct_message.created_at)} ago)."
237
+ say "Direct Message sent from @#{@rcfile.active_profile[0]} to @#{direct_message.recipient.screen_name}."
232
238
  end
233
239
  map %w(d m) => :dm
234
240
 
235
241
  desc "does_contain [USER/]LIST USER", "Find out whether a list contains a user."
236
242
  method_option "id", :aliases => "-i", :type => "boolean", :default => false, :desc => "Specify user via ID instead of screen name."
237
243
  def does_contain(list, user=nil)
238
- owner, list = list.split('/')
239
- if list.nil?
240
- list = owner
241
- owner = @rcfile.active_profile[0]
242
- else
243
- require 't/core_ext/string'
244
- owner = if options['id']
245
- client.user(owner.to_i).screen_name
246
- else
247
- owner.strip_ats
248
- end
249
- end
244
+ owner, list = extract_owner(list, options)
250
245
  if user.nil?
251
246
  user = @rcfile.active_profile[0]
252
247
  else
@@ -258,9 +253,9 @@ module T
258
253
  end
259
254
  end
260
255
  if client.list_member?(owner, list, user)
261
- say "Yes, @#{owner}/#{list} contains @#{user}."
256
+ say "Yes, #{list} contains @#{user}."
262
257
  else
263
- say "No, @#{owner}/#{list} does not contain @#{user}."
258
+ say "No, #{list} does not contain @#{user}."
264
259
  exit 1
265
260
  end
266
261
  end
@@ -297,15 +292,12 @@ module T
297
292
  def favorite(status_id, *status_ids)
298
293
  status_ids.unshift(status_id)
299
294
  status_ids.map!(&:to_i)
300
- require 't/core_ext/enumerable'
301
295
  require 'retryable'
302
- favorites = status_ids.threaded_map do |status_id|
303
- retryable(:tries => 3, :on => Twitter::Error::ServerError, :sleep => 0) do
304
- client.favorite(status_id)
305
- end
296
+ favorites = retryable(:tries => 3, :on => Twitter::Error::ServerError, :sleep => 0) do
297
+ client.favorite(status_ids)
306
298
  end
307
299
  number = favorites.length
308
- say "@#{@rcfile.active_profile[0]} favorited #{number} #{number == 1 ? 'tweet' : 'tweets'}."
300
+ say "@#{@rcfile.active_profile[0]} favorited #{pluralize(number, 'tweet')}."
309
301
  say
310
302
  say "Run `#{File.basename($0)} delete favorite #{status_ids.join(' ')}` to unfavorite."
311
303
  end
@@ -337,37 +329,20 @@ module T
337
329
  desc "follow USER [USER...]", "Allows you to start following users."
338
330
  method_option "id", :aliases => "-i", :type => "boolean", :default => false, :desc => "Specify input as Twitter user IDs instead of screen names."
339
331
  def follow(user, *users)
340
- users.unshift(user)
341
- require 't/core_ext/string'
342
- if options['id']
343
- users.map!(&:to_i)
344
- else
345
- users.map!(&:strip_ats)
346
- end
347
- require 't/core_ext/enumerable'
348
- require 'retryable'
349
- users = users.threaded_map do |user|
350
- retryable(:tries => 3, :on => Twitter::Error::ServerError, :sleep => 0) do
351
- client.follow(user)
352
- end
332
+ users, number = fetch_users(users.unshift(user), options) do |users|
333
+ client.follow(users)
353
334
  end
354
- number = users.length
355
- say "@#{@rcfile.active_profile[0]} is now following #{number} more #{number == 1 ? 'user' : 'users'}."
335
+ say "@#{@rcfile.active_profile[0]} is now following #{pluralize(number, 'more user')}."
356
336
  say
357
337
  say "Run `#{File.basename($0)} unfollow #{users.map{|user| "@#{user.screen_name}"}.join(' ')}` to stop."
358
338
  end
359
339
 
360
340
  desc "followings [USER]", "Returns a list of the people you follow on Twitter."
361
341
  method_option "csv", :aliases => "-c", :type => :boolean, :default => false, :desc => "Output in CSV format."
362
- method_option "favorites", :aliases => "-v", :type => :boolean, :default => false, :desc => "Sort by number of favorites."
363
- method_option "followers", :aliases => "-f", :type => :boolean, :default => false, :desc => "Sort by number of followers."
364
- method_option "friends", :aliases => "-e", :type => :boolean, :default => false, :desc => "Sort by number of friends."
365
342
  method_option "id", :aliases => "-i", :type => "boolean", :default => false, :desc => "Specify user via ID instead of screen name."
366
- method_option "listed", :aliases => "-d", :type => :boolean, :default => false, :desc => "Sort by number of list memberships."
367
343
  method_option "long", :aliases => "-l", :type => :boolean, :default => false, :desc => "Output in long format."
368
- method_option "posted", :aliases => "-p", :type => :boolean, :default => false, :desc => "Sort by the time when Twitter account was posted."
369
344
  method_option "reverse", :aliases => "-r", :type => :boolean, :default => false, :desc => "Reverse the order of the sort."
370
- method_option "tweets", :aliases => "-t", :type => :boolean, :default => false, :desc => "Sort by number of Tweets."
345
+ method_option "sort", :aliases => "-s", :type => :string, :enum => %w(favorites followers friends listed screen_name since tweets tweeted), :default => "screen_name", :desc => "Specify the order of the results.", :banner => "ORDER"
371
346
  method_option "unsorted", :aliases => "-u", :type => :boolean, :default => false, :desc => "Output is not sorted."
372
347
  def followings(user=nil)
373
348
  if user
@@ -381,28 +356,19 @@ module T
381
356
  following_ids = collect_with_cursor do |cursor|
382
357
  client.friend_ids(user, :cursor => cursor)
383
358
  end
384
- require 'active_support/core_ext/array/grouping'
385
- require 't/core_ext/enumerable'
386
359
  require 'retryable'
387
- users = following_ids.in_groups_of(MAX_USERS_PER_REQUEST, false).threaded_map do |following_id_group|
388
- retryable(:tries => 3, :on => Twitter::Error::ServerError, :sleep => 0) do
389
- client.users(following_id_group)
390
- end
391
- end.flatten
360
+ users = retryable(:tries => 3, :on => Twitter::Error::ServerError, :sleep => 0) do
361
+ client.users(following_ids)
362
+ end
392
363
  print_users(users)
393
364
  end
394
365
 
395
366
  desc "followers [USER]", "Returns a list of the people who follow you on Twitter."
396
367
  method_option "csv", :aliases => "-c", :type => :boolean, :default => false, :desc => "Output in CSV format."
397
- method_option "favorites", :aliases => "-v", :type => :boolean, :default => false, :desc => "Sort by number of favorites."
398
- method_option "followers", :aliases => "-f", :type => :boolean, :default => false, :desc => "Sort by number of followers."
399
- method_option "friends", :aliases => "-e", :type => :boolean, :default => false, :desc => "Sort by number of friends."
400
368
  method_option "id", :aliases => "-i", :type => "boolean", :default => false, :desc => "Specify user via ID instead of screen name."
401
- method_option "listed", :aliases => "-d", :type => :boolean, :default => false, :desc => "Sort by number of list memberships."
402
369
  method_option "long", :aliases => "-l", :type => :boolean, :default => false, :desc => "Output in long format."
403
- method_option "posted", :aliases => "-p", :type => :boolean, :default => false, :desc => "Sort by the time when Twitter account was posted."
404
370
  method_option "reverse", :aliases => "-r", :type => :boolean, :default => false, :desc => "Reverse the order of the sort."
405
- method_option "tweets", :aliases => "-t", :type => :boolean, :default => false, :desc => "Sort by number of Tweets."
371
+ method_option "sort", :aliases => "-s", :type => :string, :enum => %w(favorites followers friends listed screen_name since tweets tweeted), :default => "screen_name", :desc => "Specify the order of the results.", :banner => "ORDER"
406
372
  method_option "unsorted", :aliases => "-u", :type => :boolean, :default => false, :desc => "Output is not sorted."
407
373
  def followers(user=nil)
408
374
  if user
@@ -416,28 +382,19 @@ module T
416
382
  follower_ids = collect_with_cursor do |cursor|
417
383
  client.follower_ids(user, :cursor => cursor)
418
384
  end
419
- require 'active_support/core_ext/array/grouping'
420
- require 't/core_ext/enumerable'
421
385
  require 'retryable'
422
- users = follower_ids.in_groups_of(MAX_USERS_PER_REQUEST, false).threaded_map do |follower_id_group|
423
- retryable(:tries => 3, :on => Twitter::Error::ServerError, :sleep => 0) do
424
- client.users(follower_id_group)
425
- end
426
- end.flatten
386
+ users = retryable(:tries => 3, :on => Twitter::Error::ServerError, :sleep => 0) do
387
+ client.users(follower_ids)
388
+ end
427
389
  print_users(users)
428
390
  end
429
391
 
430
392
  desc "friends [USER]", "Returns the list of people who you follow and follow you back."
431
393
  method_option "csv", :aliases => "-c", :type => :boolean, :default => false, :desc => "Output in CSV format."
432
- method_option "favorites", :aliases => "-v", :type => :boolean, :default => false, :desc => "Sort by number of favorites."
433
- method_option "followers", :aliases => "-f", :type => :boolean, :default => false, :desc => "Sort by number of followers."
434
- method_option "friends", :aliases => "-e", :type => :boolean, :default => false, :desc => "Sort by number of friends."
435
394
  method_option "id", :aliases => "-i", :type => "boolean", :default => false, :desc => "Specify user via ID instead of screen name."
436
- method_option "listed", :aliases => "-d", :type => :boolean, :default => false, :desc => "Sort by number of list memberships."
437
395
  method_option "long", :aliases => "-l", :type => :boolean, :default => false, :desc => "Output in long format."
438
- method_option "posted", :aliases => "-p", :type => :boolean, :default => false, :desc => "Sort by the time when Twitter account was posted."
439
396
  method_option "reverse", :aliases => "-r", :type => :boolean, :default => false, :desc => "Reverse the order of the sort."
440
- method_option "tweets", :aliases => "-t", :type => :boolean, :default => false, :desc => "Sort by number of Tweets."
397
+ method_option "sort", :aliases => "-s", :type => :string, :enum => %w(favorites followers friends listed screen_name since tweets tweeted), :default => "screen_name", :desc => "Specify the order of the results.", :banner => "ORDER"
441
398
  method_option "unsorted", :aliases => "-u", :type => :boolean, :default => false, :desc => "Output is not sorted."
442
399
  def friends(user=nil)
443
400
  if user
@@ -448,35 +405,30 @@ module T
448
405
  user.strip_ats
449
406
  end
450
407
  end
451
- following_ids = collect_with_cursor do |cursor|
452
- client.friend_ids(user, :cursor => cursor)
408
+ following_ids = Thread.new do
409
+ collect_with_cursor do |cursor|
410
+ client.friend_ids(user, :cursor => cursor)
411
+ end
453
412
  end
454
- follower_ids = collect_with_cursor do |cursor|
455
- client.follower_ids(user, :cursor => cursor)
413
+ follower_ids = Thread.new do
414
+ collect_with_cursor do |cursor|
415
+ client.follower_ids(user, :cursor => cursor)
416
+ end
456
417
  end
457
- friend_ids = (following_ids & follower_ids)
458
- require 'active_support/core_ext/array/grouping'
459
- require 't/core_ext/enumerable'
418
+ friend_ids = (following_ids.value & follower_ids.value)
460
419
  require 'retryable'
461
- users = friend_ids.in_groups_of(MAX_USERS_PER_REQUEST, false).threaded_map do |friend_id_group|
462
- retryable(:tries => 3, :on => Twitter::Error::ServerError, :sleep => 0) do
463
- client.users(friend_id_group)
464
- end
465
- end.flatten
420
+ users = retryable(:tries => 3, :on => Twitter::Error::ServerError, :sleep => 0) do
421
+ client.users(friend_ids)
422
+ end
466
423
  print_users(users)
467
424
  end
468
425
 
469
426
  desc "leaders [USER]", "Returns the list of people who you follow but don't follow you back."
470
427
  method_option "csv", :aliases => "-c", :type => :boolean, :default => false, :desc => "Output in CSV format."
471
- method_option "favorites", :aliases => "-v", :type => :boolean, :default => false, :desc => "Sort by number of favorites."
472
- method_option "followers", :aliases => "-f", :type => :boolean, :default => false, :desc => "Sort by number of followers."
473
- method_option "friends", :aliases => "-e", :type => :boolean, :default => false, :desc => "Sort by number of friends."
474
428
  method_option "id", :aliases => "-i", :type => "boolean", :default => false, :desc => "Specify user via ID instead of screen name."
475
- method_option "listed", :aliases => "-d", :type => :boolean, :default => false, :desc => "Sort by number of list memberships."
476
429
  method_option "long", :aliases => "-l", :type => :boolean, :default => false, :desc => "Output in long format."
477
- method_option "posted", :aliases => "-p", :type => :boolean, :default => false, :desc => "Sort by the time when Twitter account was posted."
478
430
  method_option "reverse", :aliases => "-r", :type => :boolean, :default => false, :desc => "Reverse the order of the sort."
479
- method_option "tweets", :aliases => "-t", :type => :boolean, :default => false, :desc => "Sort by number of Tweets."
431
+ method_option "sort", :aliases => "-s", :type => :string, :enum => %w(favorites followers friends listed screen_name since tweets tweeted), :default => "screen_name", :desc => "Specify the order of the results.", :banner => "ORDER"
480
432
  method_option "unsorted", :aliases => "-u", :type => :boolean, :default => false, :desc => "Output is not sorted."
481
433
  def leaders(user=nil)
482
434
  if user
@@ -487,21 +439,21 @@ module T
487
439
  user.strip_ats
488
440
  end
489
441
  end
490
- following_ids = collect_with_cursor do |cursor|
491
- client.friend_ids(user, :cursor => cursor)
442
+ following_ids = Thread.new do
443
+ collect_with_cursor do |cursor|
444
+ client.friend_ids(user, :cursor => cursor)
445
+ end
492
446
  end
493
- follower_ids = collect_with_cursor do |cursor|
494
- client.follower_ids(user, :cursor => cursor)
447
+ follower_ids = Thread.new do
448
+ collect_with_cursor do |cursor|
449
+ client.follower_ids(user, :cursor => cursor)
450
+ end
495
451
  end
496
- leader_ids = (following_ids - follower_ids)
497
- require 'active_support/core_ext/array/grouping'
498
- require 't/core_ext/enumerable'
452
+ leader_ids = (following_ids.value - follower_ids.value)
499
453
  require 'retryable'
500
- users = leader_ids.in_groups_of(MAX_USERS_PER_REQUEST, false).threaded_map do |leader_id_group|
501
- retryable(:tries => 3, :on => Twitter::Error::ServerError, :sleep => 0) do
502
- client.users(leader_id_group)
503
- end
504
- end.flatten
454
+ users = retryable(:tries => 3, :on => Twitter::Error::ServerError, :sleep => 0) do
455
+ client.users(leader_ids)
456
+ end
505
457
  print_users(users)
506
458
  end
507
459
 
@@ -509,11 +461,8 @@ module T
509
461
  method_option "csv", :aliases => "-c", :type => :boolean, :default => false, :desc => "Output in CSV format."
510
462
  method_option "id", :aliases => "-i", :type => "boolean", :default => false, :desc => "Specify user via ID instead of screen name."
511
463
  method_option "long", :aliases => "-l", :type => :boolean, :default => false, :desc => "Output in long format."
512
- method_option "members", :aliases => "-m", :type => :boolean, :default => false, :desc => "Sort by number of members."
513
- method_option "mode", :aliases => "-o", :type => :boolean, :default => false, :desc => "Sort by mode."
514
- method_option "posted", :aliases => "-p", :type => :boolean, :default => false, :desc => "Sort by the time when Twitter list was posted."
515
464
  method_option "reverse", :aliases => "-r", :type => :boolean, :default => false, :desc => "Reverse the order of the sort."
516
- method_option "subscribers", :aliases => "-s", :type => :boolean, :default => false, :desc => "Sort by number of subscribers."
465
+ method_option "sort", :aliases => "-s", :type => :string, :enum => %w(members mode posted slug subscribers), :default => "slug", :desc => "Specify the order of the results.", :banner => "ORDER"
517
466
  method_option "unsorted", :aliases => "-u", :type => :boolean, :default => false, :desc => "Output is not sorted."
518
467
  def lists(user=nil)
519
468
  if user
@@ -593,11 +542,7 @@ module T
593
542
  status = client.status(status_id.to_i, :include_my_retweet => false)
594
543
  users = Array(status.from_user)
595
544
  if options['all']
596
- # twitter-text requires $KCODE to be set to UTF8 on Ruby versions < 1.8
597
- major, minor, patch = RUBY_VERSION.split('.')
598
- $KCODE='u' if major.to_i == 1 && minor.to_i < 9
599
- require 'twitter-text'
600
- users += Twitter::Extractor.extract_mentioned_screen_names(status.full_text)
545
+ users += extract_mentioned_screen_names(status.full_text)
601
546
  users.uniq!
602
547
  end
603
548
  require 't/core_ext/string'
@@ -605,7 +550,7 @@ module T
605
550
  opts = {:in_reply_to_status_id => status.id, :trim_user => true}
606
551
  opts.merge!(:lat => location.lat, :long => location.lng) if options['location']
607
552
  reply = client.update("#{users.join(' ')} #{message}", opts)
608
- say "Reply created by @#{@rcfile.active_profile[0]} to #{users.join(' ')} (#{time_ago_in_words(reply.created_at)} ago)."
553
+ say "Reply posted by @#{@rcfile.active_profile[0]} to #{users.join(' ')}."
609
554
  say
610
555
  say "Run `#{File.basename($0)} delete status #{reply.id}` to delete."
611
556
  end
@@ -613,22 +558,10 @@ module T
613
558
  desc "report_spam USER [USER...]", "Report users for spam."
614
559
  method_option "id", :aliases => "-i", :type => "boolean", :default => false, :desc => "Specify input as Twitter user IDs instead of screen names."
615
560
  def report_spam(user, *users)
616
- users.unshift(user)
617
- require 't/core_ext/string'
618
- if options['id']
619
- users.map!(&:to_i)
620
- else
621
- users.map!(&:strip_ats)
622
- end
623
- require 't/core_ext/enumerable'
624
- require 'retryable'
625
- users = users.threaded_map do |user|
626
- retryable(:tries => 3, :on => Twitter::Error::ServerError, :sleep => 0) do
627
- client.report_spam(user)
628
- end
561
+ users, number = fetch_users(users.unshift(user), options) do |users|
562
+ client.report_spam(users)
629
563
  end
630
- number = users.length
631
- say "@#{@rcfile.active_profile[0]} reported #{number} #{number == 1 ? 'user' : 'users'}."
564
+ say "@#{@rcfile.active_profile[0]} reported #{pluralize(number, 'user')}."
632
565
  end
633
566
  map %w(report reportspam spam) => :report_spam
634
567
 
@@ -636,15 +569,12 @@ module T
636
569
  def retweet(status_id, *status_ids)
637
570
  status_ids.unshift(status_id)
638
571
  status_ids.map!(&:to_i)
639
- require 't/core_ext/enumerable'
640
572
  require 'retryable'
641
- retweets = status_ids.threaded_map do |status_id|
642
- retryable(:tries => 3, :on => Twitter::Error::ServerError, :sleep => 0) do
643
- client.retweet(status_id, :trim_user => true)
644
- end
573
+ retweets = retryable(:tries => 3, :on => Twitter::Error::ServerError, :sleep => 0) do
574
+ client.retweet(status_ids, :trim_user => true)
645
575
  end
646
576
  number = retweets.length
647
- say "@#{@rcfile.active_profile[0]} retweeted #{number} #{number == 1 ? 'tweet' : 'tweets'}."
577
+ say "@#{@rcfile.active_profile[0]} retweeted #{pluralize(number, 'tweet')}."
648
578
  say
649
579
  say "Run `#{File.basename($0)} delete status #{retweets.map(&:id).join(' ')}` to undo."
650
580
  end
@@ -657,17 +587,21 @@ module T
657
587
  method_option "number", :aliases => "-n", :type => :numeric, :default => DEFAULT_NUM_RESULTS, :desc => "Limit the number of results."
658
588
  method_option "reverse", :aliases => "-r", :type => :boolean, :default => false, :desc => "Reverse the order of the sort."
659
589
  def retweets(user=nil)
660
- if user
590
+ count = options['number'] || DEFAULT_NUM_RESULTS
591
+ statuses = if user
661
592
  require 't/core_ext/string'
662
593
  user = if options['id']
663
594
  user.to_i
664
595
  else
665
596
  user.strip_ats
666
597
  end
667
- end
668
- count = options['number'] || DEFAULT_NUM_RESULTS
669
- statuses = collect_with_count(count) do |opts|
670
- client.retweeted_by(user, opts)
598
+ collect_with_count(count) do |opts|
599
+ client.retweeted_by_user(user, opts)
600
+ end
601
+ else
602
+ collect_with_count(count) do |opts|
603
+ client.retweeted_by_me(opts)
604
+ end
671
605
  end
672
606
  print_statuses(statuses)
673
607
  end
@@ -683,12 +617,12 @@ module T
683
617
  def status(status_id)
684
618
  status = client.status(status_id.to_i, :include_my_retweet => false)
685
619
  location = if status.place
686
- if status.place.name && status.place.attributes && status.place.attributes['street_address'] && status.place.attributes['locality'] && status.place.attributes['region'] && status.place.country
687
- [status.place.name, status.place.attributes['street_address'], status.place.attributes['locality'], status.place.attributes['region'], status.place.country].join(", ")
688
- elsif status.place.name && status.place.attributes && status.place.attributes['locality'] && status.place.attributes['region'] && status.place.country
689
- [status.place.name, status.place.attributes['locality'], status.place.attributes['region'], status.place.country].join(", ")
690
- elsif status.place.full_name && status.place.attributes && status.place.attributes['region'] && status.place.country
691
- [status.place.full_name, status.place.attributes['region'], status.place.country].join(", ")
620
+ if status.place.name && status.place.attributes && status.place.attributes[:street_address] && status.place.attributes[:locality] && status.place.attributes[:region] && status.place.country
621
+ [status.place.name, status.place.attributes[:street_address], status.place.attributes[:locality], status.place.attributes[:region], status.place.country].join(", ")
622
+ elsif status.place.name && status.place.attributes && status.place.attributes[:locality] && status.place.attributes[:region] && status.place.country
623
+ [status.place.name, status.place.attributes[:locality], status.place.attributes[:region], status.place.country].join(", ")
624
+ elsif status.place.full_name && status.place.attributes && status.place.attributes[:region] && status.place.country
625
+ [status.place.full_name, status.place.attributes[:region], status.place.country].join(", ")
692
626
  elsif status.place.full_name && status.place.country
693
627
  [status.place.full_name, status.place.country].join(", ")
694
628
  elsif status.place.full_name
@@ -721,16 +655,11 @@ module T
721
655
 
722
656
  desc "suggest [USER]", "Returns a listing of Twitter users' accounts you might enjoy following."
723
657
  method_option "csv", :aliases => "-c", :type => :boolean, :default => false, :desc => "Output in CSV format."
724
- method_option "favorites", :aliases => "-v", :type => :boolean, :default => false, :desc => "Sort by number of favorites."
725
- method_option "followers", :aliases => "-f", :type => :boolean, :default => false, :desc => "Sort by number of followers."
726
- method_option "friends", :aliases => "-e", :type => :boolean, :default => false, :desc => "Sort by number of friends."
727
658
  method_option "id", :aliases => "-i", :type => "boolean", :default => false, :desc => "Specify user via ID instead of screen name."
728
- method_option "listed", :aliases => "-d", :type => :boolean, :default => false, :desc => "Sort by number of list memberships."
729
659
  method_option "long", :aliases => "-l", :type => :boolean, :default => false, :desc => "Output in long format."
730
660
  method_option "number", :aliases => "-n", :type => :numeric, :default => DEFAULT_NUM_RESULTS, :desc => "Limit the number of results."
731
- method_option "posted", :aliases => "-p", :type => :boolean, :default => false, :desc => "Sort by the time when Twitter account was posted."
732
661
  method_option "reverse", :aliases => "-r", :type => :boolean, :default => false, :desc => "Reverse the order of the sort."
733
- method_option "tweets", :aliases => "-t", :type => :boolean, :default => false, :desc => "Sort by number of Tweets."
662
+ method_option "sort", :aliases => "-s", :type => :string, :enum => %w(favorites followers friends listed screen_name since tweets tweeted), :default => "screen_name", :desc => "Specify the order of the results.", :banner => "ORDER"
734
663
  method_option "unsorted", :aliases => "-u", :type => :boolean, :default => false, :desc => "Output is not sorted."
735
664
  def suggest(user=nil)
736
665
  if user
@@ -813,22 +742,10 @@ module T
813
742
  desc "unfollow USER [USER...]", "Allows you to stop following users."
814
743
  method_option "id", :aliases => "-i", :type => "boolean", :default => false, :desc => "Specify input as Twitter user IDs instead of screen names."
815
744
  def unfollow(user, *users)
816
- users.unshift(user)
817
- require 't/core_ext/string'
818
- if options['id']
819
- users.map!(&:to_i)
820
- else
821
- users.map!(&:strip_ats)
745
+ users, number = fetch_users(users.unshift(user), options) do |users|
746
+ client.unfollow(users)
822
747
  end
823
- require 't/core_ext/enumerable'
824
- require 'retryable'
825
- users = users.threaded_map do |user|
826
- retryable(:tries => 3, :on => Twitter::Error::ServerError, :sleep => 0) do
827
- client.unfollow(user)
828
- end
829
- end
830
- number = users.length
831
- say "@#{@rcfile.active_profile[0]} is no longer following #{number} #{number == 1 ? 'user' : 'users'}."
748
+ say "@#{@rcfile.active_profile[0]} is no longer following #{pluralize(number, 'user')}."
832
749
  say
833
750
  say "Run `#{File.basename($0)} follow #{users.map{|user| "@#{user.screen_name}"}.join(' ')}` to follow again."
834
751
  end
@@ -839,7 +756,7 @@ module T
839
756
  opts = {:trim_user => true}
840
757
  opts.merge!(:lat => location.lat, :long => location.lng) if options['location']
841
758
  status = client.update(message, opts)
842
- say "Tweet created by @#{@rcfile.active_profile[0]} (#{time_ago_in_words(status.created_at)} ago)."
759
+ say "Tweet posted by @#{@rcfile.active_profile[0]}."
843
760
  say
844
761
  say "Run `#{File.basename($0)} delete status #{status.id}` to delete."
845
762
  end
@@ -847,15 +764,10 @@ module T
847
764
 
848
765
  desc "users USER [USER...]", "Returns a list of users you specify."
849
766
  method_option "csv", :aliases => "-c", :type => :boolean, :default => false, :desc => "Output in CSV format."
850
- method_option "favorites", :aliases => "-v", :type => :boolean, :default => false, :desc => "Sort by number of favorites."
851
- method_option "followers", :aliases => "-f", :type => :boolean, :default => false, :desc => "Sort by number of followers."
852
- method_option "friends", :aliases => "-e", :type => :boolean, :default => false, :desc => "Sort by number of friends."
853
767
  method_option "id", :aliases => "-i", :type => "boolean", :default => false, :desc => "Specify input as Twitter user IDs instead of screen names."
854
- method_option "listed", :aliases => "-d", :type => :boolean, :default => false, :desc => "Sort by number of list memberships."
855
768
  method_option "long", :aliases => "-l", :type => :boolean, :default => false, :desc => "Output in long format."
856
- method_option "posted", :aliases => "-p", :type => :boolean, :default => false, :desc => "Sort by the time when Twitter account was posted."
857
769
  method_option "reverse", :aliases => "-r", :type => :boolean, :default => false, :desc => "Reverse the order of the sort."
858
- method_option "tweets", :aliases => "-t", :type => :boolean, :default => false, :desc => "Sort by number of Tweets."
770
+ method_option "sort", :aliases => "-s", :type => :string, :enum => %w(favorites followers friends listed screen_name since tweets tweeted), :default => "screen_name", :desc => "Specify the order of the results.", :banner => "ORDER"
859
771
  method_option "unsorted", :aliases => "-u", :type => :boolean, :default => false, :desc => "Output is not sorted."
860
772
  def users(user, *users)
861
773
  users.unshift(user)
@@ -872,6 +784,7 @@ module T
872
784
 
873
785
  desc "version", "Show version."
874
786
  def version
787
+ require 't/version'
875
788
  say T::Version
876
789
  end
877
790
  map %w(-v --version) => :version
@@ -930,6 +843,40 @@ module T
930
843
 
931
844
  private
932
845
 
846
+ def extract_mentioned_screen_names(text)
847
+ valid_mention_preceding_chars = /(?:[^a-zA-Z0-9_!#\$%&*@@]|^|RT:?)/o
848
+ at_signs = /[@@]/
849
+ valid_mentions = /
850
+ (#{valid_mention_preceding_chars}) # $1: Preceeding character
851
+ (#{at_signs}) # $2: At mark
852
+ ([a-zA-Z0-9_]{1,20}) # $3: Screen name
853
+ /ox
854
+
855
+ return [] if text !~ at_signs
856
+
857
+ text.to_s.scan(valid_mentions).map do |before, at, screen_name|
858
+ screen_name
859
+ end
860
+ end
861
+
862
+ def base_url
863
+ "#{protocol}://#{host}"
864
+ end
865
+
866
+ def generate_authorize_url(consumer, request_token)
867
+ request = consumer.create_signed_request(:get, consumer.authorize_path, request_token, pin_auth_parameters)
868
+ params = request['Authorization'].sub(/^OAuth\s+/, '').split(/,\s+/).map do |param|
869
+ key, value = param.split('=')
870
+ value =~ /"(.*?)"/
871
+ "#{key}=#{CGI::escape($1)}"
872
+ end.join('&')
873
+ "#{base_url}#{request.path}?#{params}"
874
+ end
875
+
876
+ def pin_auth_parameters
877
+ {:oauth_callback => 'oob'}
878
+ end
879
+
933
880
  def location
934
881
  return @location if @location
935
882
  require 'geokit'