t 0.6.4 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -66,6 +66,14 @@ interpreted by your shell.
66
66
  ### <a name="follow"></a>Follow users
67
67
  t follow @sferik @gem
68
68
 
69
+ ### <a name="does-follow"></a>Does one user follow another user?
70
+ t does_follow @ev @sferik
71
+
72
+ **Note**: When this command returns false, `t` exits with a non-zero exit code,
73
+ which which allows you to execute conditional commands, for example:
74
+
75
+ t does_follow @ev @sferik && t dm @ev "What's up, bro?"
76
+
69
77
  ### <a name="friends"></a>List your friends (ordered by number of followers)
70
78
  t friends -lf
71
79
 
@@ -75,11 +83,14 @@ interpreted by your shell.
75
83
  ### <a name="unfollow"></a>Unfollow everyone you follow who doesn't follow you back
76
84
  t leaders | xargs t unfollow
77
85
 
78
- ### Follow back everyone who follows you
86
+ ### <a name="follow"></a>Follow back everyone who follows you
79
87
  t disciples | xargs t follow
80
88
 
81
- ### Follow roulette: randomly follow someone who follows you
82
- t disciples | gshuf | head -1 | xargs t follow
89
+ ### <a name="follow-roulette"></a>Follow roulette: randomly follow someone who follows you
90
+ t disciples | shuf | head -1 | xargs t follow
91
+
92
+ ### <a name="favorite"></a>Favorite the last 10 tweets that mention you
93
+ t mentions -l -n 10 | awk '{print $1}' | xargs t favorite
83
94
 
84
95
  ### <a name="list-create"></a>Create a list
85
96
  t list create presidents
@@ -87,16 +98,16 @@ interpreted by your shell.
87
98
  ### <a name="list-add"></a>Add users to a list
88
99
  t list add presidents @BarackObama @Jasonfinn
89
100
 
90
- ### <a name="following"></a>Create a list that contains today's date in the name
101
+ ### Create a list that contains today's date in the name
91
102
  t list create following-`date "+%Y-%m-%d"`
92
103
 
93
- ### Add everyone you're following to a list
104
+ ### <a name="followings"></a>Add everyone you're following to a list
94
105
  t followings | xargs t list add following-`date "+%Y-%m-%d"`
95
106
 
96
107
  ### <a name="list-members"></a>Display members of a list
97
108
  t list members following-`date "+%Y-%m-%d"`
98
109
 
99
- ### Count the number of Twitter employees
110
+ ### <a name="list-members-count"></a>Count the number of Twitter employees
100
111
  t list members twitter team | wc -l
101
112
 
102
113
  ### <a name="search-all"></a>Search Twitter for the 20 most recent Tweets that match a specified query
@@ -123,6 +134,8 @@ interpreted by your shell.
123
134
  * Designed for Unix: All output is designed to be piped to other Unix
124
135
  utilities, like grep, cut, awk, bc, wc, and xargs for advanced processing.
125
136
  * 99% C0 Code Coverage: Extremely well tested, with a 3:1 test-to-code ratio.
137
+ * Generate spreadsheets: Output any command in CSV format simply by adding the
138
+ `--csv` flag.
126
139
 
127
140
  ## <a name="terminology"></a>Relationship Terminology
128
141
 
data/lib/t/cli.rb CHANGED
@@ -3,6 +3,9 @@ require 'active_support/core_ext/array/grouping'
3
3
  require 'active_support/core_ext/date/calculations'
4
4
  require 'active_support/core_ext/integer/time'
5
5
  require 'active_support/core_ext/numeric/time'
6
+ require 'csv'
7
+ # 'fastercsv' required on Ruby versions < 1.9
8
+ require 'fastercsv' unless Array.new.respond_to?(:to_csv)
6
9
  require 'geokit'
7
10
  require 'launchy'
8
11
  require 'oauth'
@@ -22,6 +25,11 @@ require 'time'
22
25
  require 'twitter'
23
26
  require 'yaml'
24
27
 
28
+ # twitter-text requires $KCODE to be set to UTF8 on Ruby versions < 1.8
29
+ major, minor, patch = RUBY_VERSION.split('.')
30
+ $KCODE='u' if major.to_i == 1 && minor.to_i < 9
31
+ require 'twitter-text'
32
+
25
33
  module T
26
34
  class CLI < Thor
27
35
  include ActionView::Helpers::DateHelper
@@ -30,6 +38,7 @@ module T
30
38
  include T::Collectable
31
39
  include T::Printable
32
40
  include T::Requestable
41
+ include Twitter::Extractor
33
42
 
34
43
  DEFAULT_NUM_RESULTS = 20
35
44
  MAX_SCREEN_NAME_SIZE = 20
@@ -58,15 +67,15 @@ module T
58
67
  end
59
68
 
60
69
  desc "authorize", "Allows an application to request user authorization"
61
- method_option :consumer_key, :aliases => "-c", :required => true
62
- method_option :consumer_secret, :aliases => "-s", :required => true
70
+ method_option :consumer_key, :aliases => "-c", :required => true, :desc => "This can be found at https://dev.twitter.com/apps"
71
+ method_option :consumer_secret, :aliases => "-s", :required => true, :desc => "This can be found at https://dev.twitter.com/apps"
72
+ method_option :display_url, :aliases => "-d", :type => :boolean, :default => false, :desc => "Display the authorization URL instead of attempting to open it."
63
73
  method_option :prompt, :aliases => "-p", :type => :boolean, :default => true
64
- method_option :dry_run, :type => :boolean
65
74
  def authorize
66
75
  request_token = consumer.get_request_token
67
76
  url = generate_authorize_url(request_token)
68
77
  if options['prompt']
69
- say "In a moment, your web browser will open to the Twitter app authorization page."
78
+ say "In a moment, you will be directed to the Twitter app authorization page."
70
79
  say "Perform the following steps to complete the authorization process:"
71
80
  say " 1. Sign in to Twitter"
72
81
  say " 2. Press \"Authorize app\""
@@ -76,7 +85,7 @@ module T
76
85
  ask "Press [Enter] to open the Twitter app authorization page."
77
86
  say
78
87
  end
79
- Launchy.open(url, :dry_run => options.fetch('dry_run', false))
88
+ Launchy.open(url, :dry_run => options['display_url'])
80
89
  pin = ask "Paste in the supplied PIN:"
81
90
  access_token = request_token.get_access_token(:oauth_verifier => pin.chomp)
82
91
  oauth_response = access_token.get('/1/account/verify_credentials.json')
@@ -95,31 +104,41 @@ module T
95
104
  say "Authorization successful."
96
105
  end
97
106
 
98
- desc "block SCREEN_NAME [SCREEN_NAME...]", "Block users."
99
- def block(screen_name, *screen_names)
100
- screen_names.unshift(screen_name)
101
- screen_names.map!(&:strip_ats)
102
- screen_names.threaded_each do |screen_name|
103
- screen_name.strip_ats
107
+ desc "block USER [USER...]", "Block users."
108
+ method_option :id, :aliases => "-i", :type => "boolean", :default => false, :desc => "Specify input as Twitter user IDs instead of screen names."
109
+ def block(user, *users)
110
+ users.unshift(user)
111
+ if options['id']
112
+ users.map!(&:to_i)
113
+ else
114
+ users.map!(&:strip_ats)
115
+ end
116
+ users = users.threaded_map do |user|
104
117
  retryable(:tries => 3, :on => Twitter::Error::ServerError, :sleep => 0) do
105
- client.block(screen_name, :include_entities => false)
118
+ client.block(user, :include_entities => false)
106
119
  end
107
120
  end
108
- number = screen_names.length
121
+ number = users.length
109
122
  say "@#{@rcfile.default_profile[0]} blocked #{number} #{number == 1 ? 'user' : 'users'}."
110
123
  say
111
- say "Run `#{File.basename($0)} delete block #{screen_names.map{|screen_name| "@#{screen_name}"}.join(' ')}` to unblock."
124
+ say "Run `#{File.basename($0)} delete block #{users.map{|user| "@#{user.screen_name}"}.join(' ')}` to unblock."
112
125
  end
113
126
 
114
127
  desc "direct_messages", "Returns the #{DEFAULT_NUM_RESULTS} most recent Direct Messages sent to you."
115
- method_option :long, :aliases => "-l", :type => :boolean, :default => false, :desc => "List in long format."
128
+ method_option :csv, :aliases => "-c", :type => :boolean, :default => false, :desc => "Output in CSV format."
129
+ method_option :long, :aliases => "-l", :type => :boolean, :default => false, :desc => "Output in long format."
116
130
  method_option :number, :aliases => "-n", :type => :numeric, :default => DEFAULT_NUM_RESULTS, :desc => "Limit the number of results."
117
131
  method_option :reverse, :aliases => "-r", :type => :boolean, :default => false, :desc => "Reverse the order of the sort."
118
132
  def direct_messages
119
133
  count = options['number'] || DEFAULT_NUM_RESULTS
120
134
  direct_messages = client.direct_messages(:count => count, :include_entities => false)
121
135
  direct_messages.reverse! if options['reverse']
122
- if options['long']
136
+ if options['csv']
137
+ say ["ID", "Posted at", "Screen name", "Text"].to_csv unless direct_messages.empty?
138
+ direct_messages.each do |direct_message|
139
+ say [direct_message.id, direct_message.created_at.utc.strftime("%Y-%m-%d %H:%M:%S %z"), direct_message.sender.screen_name, direct_message.text].to_csv
140
+ end
141
+ elsif options['long']
123
142
  array = direct_messages.map do |direct_message|
124
143
  created_at = direct_message.created_at > 6.months.ago ? direct_message.created_at.strftime("%b %e %H:%M") : direct_message.created_at.strftime("%b %e %Y")
125
144
  [direct_message.id.to_s, created_at, "@#{direct_message.sender.screen_name}", direct_message.text.gsub(/\n+/, ' ')]
@@ -135,17 +154,23 @@ module T
135
154
  end
136
155
  end
137
156
  end
138
- map %w(dms) => :direct_messages
157
+ map %w(directmessages dms) => :direct_messages
139
158
 
140
159
  desc "direct_messages_sent", "Returns the #{DEFAULT_NUM_RESULTS} most recent Direct Messages sent to you."
141
- method_option :long, :aliases => "-l", :type => :boolean, :default => false, :desc => "List in long format."
160
+ method_option :csv, :aliases => "-c", :type => :boolean, :default => false, :desc => "Output in CSV format."
161
+ method_option :long, :aliases => "-l", :type => :boolean, :default => false, :desc => "Output in long format."
142
162
  method_option :number, :aliases => "-n", :type => :numeric, :default => DEFAULT_NUM_RESULTS, :desc => "Limit the number of results."
143
163
  method_option :reverse, :aliases => "-r", :type => :boolean, :default => false, :desc => "Reverse the order of the sort."
144
164
  def direct_messages_sent
145
165
  count = options['number'] || DEFAULT_NUM_RESULTS
146
166
  direct_messages = client.direct_messages_sent(:count => count, :include_entities => false)
147
167
  direct_messages.reverse! if options['reverse']
148
- if options['long']
168
+ if options['csv']
169
+ say ["ID", "Posted at", "Screen name", "Text"].to_csv unless direct_messages.empty?
170
+ direct_messages.each do |direct_message|
171
+ say [direct_message.id, direct_message.created_at.utc.strftime("%Y-%m-%d %H:%M:%S %z"), direct_message.recipient.screen_name, direct_message.text].to_csv
172
+ end
173
+ elsif options['long']
149
174
  array = direct_messages.map do |direct_message|
150
175
  created_at = direct_message.created_at > 6.months.ago ? direct_message.created_at.strftime("%b %e %H:%M") : direct_message.created_at.strftime("%b %e %Y")
151
176
  [direct_message.id.to_s, created_at, "@#{direct_message.recipient.screen_name}", direct_message.text.gsub(/\n+/, ' ')]
@@ -161,24 +186,33 @@ module T
161
186
  end
162
187
  end
163
188
  end
164
- map %w(sent_messages sms) => :direct_messages_sent
189
+ map %w(directmessagessent sent_messages sentmessages sms) => :direct_messages_sent
165
190
 
166
- desc "disciples", "Returns the list of people who follow you but you don't follow back."
167
- method_option :created, :aliases => "-c", :type => :boolean, :default => false, :desc => "Sort by the time when Twitter acount was created."
191
+ desc "disciples [USER]", "Returns the list of people who follow you but you don't follow back."
192
+ method_option :csv, :aliases => "-c", :type => :boolean, :default => false, :desc => "Output in CSV format."
168
193
  method_option :favorites, :aliases => "-v", :type => :boolean, :default => false, :desc => "Sort by number of favorites."
169
194
  method_option :followers, :aliases => "-f", :type => :boolean, :default => false, :desc => "Sort by number of followers."
170
- method_option :friends, :aliases => "-d", :type => :boolean, :default => false, :desc => "Sort by number of friends."
171
- method_option :listed, :aliases => "-i", :type => :boolean, :default => false, :desc => "Sort by number of list memberships."
172
- method_option :long, :aliases => "-l", :type => :boolean, :default => false, :desc => "List in long format."
195
+ method_option :friends, :aliases => "-e", :type => :boolean, :default => false, :desc => "Sort by number of friends."
196
+ method_option :id, :aliases => "-i", :type => "boolean", :default => false, :desc => "Specify user via ID instead of screen name."
197
+ method_option :listed, :aliases => "-s", :type => :boolean, :default => false, :desc => "Sort by number of list memberships."
198
+ method_option :long, :aliases => "-l", :type => :boolean, :default => false, :desc => "Output in long format."
199
+ method_option :posted, :aliases => "-p", :type => :boolean, :default => false, :desc => "Sort by the time when Twitter account was posted."
173
200
  method_option :reverse, :aliases => "-r", :type => :boolean, :default => false, :desc => "Reverse the order of the sort."
174
201
  method_option :tweets, :aliases => "-t", :type => :boolean, :default => false, :desc => "Sort by number of Tweets."
175
202
  method_option :unsorted, :aliases => "-u", :type => :boolean, :default => false, :desc => "Output is not sorted."
176
- def disciples
203
+ def disciples(user=nil)
204
+ if user
205
+ user = if options['id']
206
+ user.to_i
207
+ else
208
+ user.strip_ats
209
+ end
210
+ end
177
211
  follower_ids = collect_with_cursor do |cursor|
178
- client.follower_ids(:cursor => cursor)
212
+ client.follower_ids(user, :cursor => cursor)
179
213
  end
180
214
  following_ids = collect_with_cursor do |cursor|
181
- client.friend_ids(:cursor => cursor)
215
+ client.friend_ids(user, :cursor => cursor)
182
216
  end
183
217
  disciple_ids = (follower_ids - following_ids)
184
218
  users = disciple_ids.in_groups_of(MAX_USERS_PER_REQUEST, false).threaded_map do |disciple_id_group|
@@ -186,24 +220,87 @@ module T
186
220
  client.users(disciple_id_group, :include_entities => false)
187
221
  end
188
222
  end.flatten
189
- print_user_list(users)
223
+ print_users(users)
190
224
  end
191
225
 
192
- desc "dm SCREEN_NAME MESSAGE", "Sends that person a Direct Message."
193
- def dm(screen_name, message)
194
- screen_name = screen_name.strip_ats
195
- direct_message = client.direct_message_create(screen_name, message, :include_entities => false)
226
+ desc "dm USER MESSAGE", "Sends that person a Direct Message."
227
+ method_option :id, :aliases => "-i", :type => "boolean", :default => false, :desc => "Specify user via ID instead of screen name."
228
+ def dm(user, message)
229
+ user = if options['id']
230
+ user.to_i
231
+ else
232
+ user.strip_ats
233
+ end
234
+ direct_message = client.direct_message_create(user, message, :include_entities => false)
196
235
  say "Direct Message sent from @#{@rcfile.default_profile[0]} to @#{direct_message.recipient.screen_name} (#{time_ago_in_words(direct_message.created_at)} ago)."
197
236
  end
198
237
  map %w(d m) => :dm
199
238
 
239
+ desc "does_contain [USER/]LIST USER", "Find out whether a list contains a user."
240
+ method_option :id, :aliases => "-i", :type => "boolean", :default => false, :desc => "Specify user via ID instead of screen name."
241
+ def does_contain(list, user=nil)
242
+ owner, list = list.split('/')
243
+ if list.nil?
244
+ list = owner
245
+ owner = @rcfile.default_profile[0]
246
+ else
247
+ owner = if options['id']
248
+ client.user(owner.to_i, :include_entities => false).screen_name
249
+ else
250
+ owner.strip_ats
251
+ end
252
+ end
253
+ if user.nil?
254
+ user = @rcfile.default_profile[0]
255
+ else
256
+ user = if options['id']
257
+ user = client.user(user.to_i, :include_entities => false).screen_name
258
+ else
259
+ user.strip_ats
260
+ end
261
+ end
262
+ if client.list_member?(owner, list, user)
263
+ say "Yes, @#{owner}/#{list} contains @#{user}."
264
+ else
265
+ say "No, @#{owner}/#{list} does not contain @#{user}."
266
+ exit 1
267
+ end
268
+ end
269
+ map %w(dc doescontain) => :does_contain
270
+
271
+ desc "does_follow USER [USER]", "Find out whether one user follows another."
272
+ method_option :id, :aliases => "-i", :type => "boolean", :default => false, :desc => "Specify user via ID instead of screen name."
273
+ def does_follow(user1, user2=nil)
274
+ user1 = if options['id']
275
+ client.user(user1.to_i, :include_entities => false).screen_name
276
+ else
277
+ user1.strip_ats
278
+ end
279
+ if user2.nil?
280
+ user2 = @rcfile.default_profile[0]
281
+ else
282
+ user2 = if options['id']
283
+ client.user(user2.to_i, :include_entities => false).screen_name
284
+ else
285
+ user2.strip_ats
286
+ end
287
+ end
288
+ if client.friendship?(user1, user2)
289
+ say "Yes, @#{user1} follows @#{user2}."
290
+ else
291
+ say "No, @#{user1} does not follow @#{user2}."
292
+ exit 1
293
+ end
294
+ end
295
+ map %w(df doesfollow) => :does_follow
296
+
200
297
  desc "favorite STATUS_ID [STATUS_ID...]", "Marks Tweets as favorites."
201
298
  def favorite(status_id, *status_ids)
202
299
  status_ids.unshift(status_id)
203
- status_ids.map!(&:strip_commas).map!(&:to_i)
300
+ status_ids.map!(&:strip_commas)
204
301
  favorites = status_ids.threaded_map do |status_id|
205
302
  retryable(:tries => 3, :on => Twitter::Error::ServerError, :sleep => 0) do
206
- client.favorite(status_id, :include_entities => false)
303
+ client.favorite(status_id.to_i, :include_entities => false)
207
304
  end
208
305
  end
209
306
  number = favorites.length
@@ -211,94 +308,135 @@ module T
211
308
  say
212
309
  say "Run `#{File.basename($0)} delete favorite #{status_ids.join(' ')}` to unfavorite."
213
310
  end
214
- map %w(fave) => :favorite
311
+ map %w(fave favourite) => :favorite
215
312
 
216
- desc "favorites", "Returns the #{DEFAULT_NUM_RESULTS} most recent Tweets you favorited."
217
- method_option :long, :aliases => "-l", :type => :boolean, :default => false, :desc => "List in long format."
313
+ desc "favorites [USER]", "Returns the #{DEFAULT_NUM_RESULTS} most recent Tweets you favorited."
314
+ method_option :csv, :aliases => "-c", :type => :boolean, :default => false, :desc => "Output in CSV format."
315
+ method_option :id, :aliases => "-i", :type => "boolean", :default => false, :desc => "Specify user via ID instead of screen name."
316
+ method_option :long, :aliases => "-l", :type => :boolean, :default => false, :desc => "Output in long format."
218
317
  method_option :number, :aliases => "-n", :type => :numeric, :default => DEFAULT_NUM_RESULTS, :desc => "Limit the number of results."
219
318
  method_option :reverse, :aliases => "-r", :type => :boolean, :default => false, :desc => "Reverse the order of the sort."
220
- def favorites
319
+ def favorites(user=nil)
320
+ if user
321
+ user = if options['id']
322
+ user.to_i
323
+ else
324
+ user.strip_ats
325
+ end
326
+ end
221
327
  count = options['number'] || DEFAULT_NUM_RESULTS
222
- statuses = client.favorites(:count => count, :include_entities => false)
223
- print_status_list(statuses)
328
+ statuses = client.favorites(user, :count => count, :include_entities => false)
329
+ print_statuses(statuses)
224
330
  end
225
- map %w(faves) => :favorites
331
+ map %w(faves favourites) => :favorites
226
332
 
227
- desc "follow SCREEN_NAME [SCREEN_NAME...]", "Allows you to start following users."
228
- def follow(screen_name, *screen_names)
229
- screen_names.unshift(screen_name)
230
- screen_names.map!(&:strip_ats)
231
- screen_names.threaded_each do |screen_name|
333
+ desc "follow USER [USER...]", "Allows you to start following users."
334
+ method_option :id, :aliases => "-i", :type => "boolean", :default => false, :desc => "Specify input as Twitter user IDs instead of screen names."
335
+ def follow(user, *users)
336
+ users.unshift(user)
337
+ if options['id']
338
+ users.map!(&:to_i)
339
+ else
340
+ users.map!(&:strip_ats)
341
+ end
342
+ users = users.threaded_map do |user|
232
343
  retryable(:tries => 3, :on => Twitter::Error::ServerError, :sleep => 0) do
233
- client.follow(screen_name, :include_entities => false)
344
+ client.follow(user, :include_entities => false)
234
345
  end
235
346
  end
236
- number = screen_names.length
347
+ number = users.length
237
348
  say "@#{@rcfile.default_profile[0]} is now following #{number} more #{number == 1 ? 'user' : 'users'}."
238
349
  say
239
- say "Run `#{File.basename($0)} unfollow #{screen_names.map{|screen_name| "@#{screen_name}"}.join(' ')}` to stop."
350
+ say "Run `#{File.basename($0)} unfollow #{users.map{|user| "@#{user.screen_name}"}.join(' ')}` to stop."
240
351
  end
241
352
 
242
- desc "followings", "Returns a list of the people you follow on Twitter."
243
- method_option :created, :aliases => "-c", :type => :boolean, :default => false, :desc => "Sort by the time when Twitter acount was created."
353
+ desc "followings [USER]", "Returns a list of the people you follow on Twitter."
354
+ method_option :csv, :aliases => "-c", :type => :boolean, :default => false, :desc => "Output in CSV format."
244
355
  method_option :favorites, :aliases => "-v", :type => :boolean, :default => false, :desc => "Sort by number of favorites."
245
356
  method_option :followers, :aliases => "-f", :type => :boolean, :default => false, :desc => "Sort by number of followers."
246
- method_option :friends, :aliases => "-d", :type => :boolean, :default => false, :desc => "Sort by number of friends."
247
- method_option :listed, :aliases => "-i", :type => :boolean, :default => false, :desc => "Sort by number of list memberships."
248
- method_option :long, :aliases => "-l", :type => :boolean, :default => false, :desc => "List in long format."
357
+ method_option :friends, :aliases => "-e", :type => :boolean, :default => false, :desc => "Sort by number of friends."
358
+ method_option :id, :aliases => "-i", :type => "boolean", :default => false, :desc => "Specify user via ID instead of screen name."
359
+ method_option :listed, :aliases => "-s", :type => :boolean, :default => false, :desc => "Sort by number of list memberships."
360
+ method_option :long, :aliases => "-l", :type => :boolean, :default => false, :desc => "Output in long format."
361
+ method_option :posted, :aliases => "-p", :type => :boolean, :default => false, :desc => "Sort by the time when Twitter account was posted."
249
362
  method_option :reverse, :aliases => "-r", :type => :boolean, :default => false, :desc => "Reverse the order of the sort."
250
363
  method_option :tweets, :aliases => "-t", :type => :boolean, :default => false, :desc => "Sort by number of Tweets."
251
364
  method_option :unsorted, :aliases => "-u", :type => :boolean, :default => false, :desc => "Output is not sorted."
252
- def followings
365
+ def followings(user=nil)
366
+ if user
367
+ user = if options['id']
368
+ user.to_i
369
+ else
370
+ user.strip_ats
371
+ end
372
+ end
253
373
  following_ids = collect_with_cursor do |cursor|
254
- client.friend_ids(:cursor => cursor)
374
+ client.friend_ids(user, :cursor => cursor)
255
375
  end
256
376
  users = following_ids.in_groups_of(MAX_USERS_PER_REQUEST, false).threaded_map do |following_id_group|
257
377
  retryable(:tries => 3, :on => Twitter::Error::ServerError, :sleep => 0) do
258
378
  client.users(following_id_group, :include_entities => false)
259
379
  end
260
380
  end.flatten
261
- print_user_list(users)
381
+ print_users(users)
262
382
  end
263
383
 
264
- desc "followers", "Returns a list of the people who follow you on Twitter."
265
- method_option :created, :aliases => "-c", :type => :boolean, :default => false, :desc => "Sort by the time when Twitter acount was created."
384
+ desc "followers [USER]", "Returns a list of the people who follow you on Twitter."
385
+ method_option :csv, :aliases => "-c", :type => :boolean, :default => false, :desc => "Output in CSV format."
266
386
  method_option :favorites, :aliases => "-v", :type => :boolean, :default => false, :desc => "Sort by number of favorites."
267
387
  method_option :followers, :aliases => "-f", :type => :boolean, :default => false, :desc => "Sort by number of followers."
268
- method_option :friends, :aliases => "-d", :type => :boolean, :default => false, :desc => "Sort by number of friends."
269
- method_option :listed, :aliases => "-i", :type => :boolean, :default => false, :desc => "Sort by number of list memberships."
270
- method_option :long, :aliases => "-l", :type => :boolean, :default => false, :desc => "List in long format."
388
+ method_option :friends, :aliases => "-e", :type => :boolean, :default => false, :desc => "Sort by number of friends."
389
+ method_option :id, :aliases => "-i", :type => "boolean", :default => false, :desc => "Specify user via ID instead of screen name."
390
+ method_option :listed, :aliases => "-s", :type => :boolean, :default => false, :desc => "Sort by number of list memberships."
391
+ method_option :long, :aliases => "-l", :type => :boolean, :default => false, :desc => "Output in long format."
392
+ method_option :posted, :aliases => "-p", :type => :boolean, :default => false, :desc => "Sort by the time when Twitter account was posted."
271
393
  method_option :reverse, :aliases => "-r", :type => :boolean, :default => false, :desc => "Reverse the order of the sort."
272
394
  method_option :tweets, :aliases => "-t", :type => :boolean, :default => false, :desc => "Sort by number of Tweets."
273
395
  method_option :unsorted, :aliases => "-u", :type => :boolean, :default => false, :desc => "Output is not sorted."
274
- def followers
396
+ def followers(user=nil)
397
+ if user
398
+ user = if options['id']
399
+ user.to_i
400
+ else
401
+ user.strip_ats
402
+ end
403
+ end
275
404
  follower_ids = collect_with_cursor do |cursor|
276
- client.follower_ids(:cursor => cursor)
405
+ client.follower_ids(user, :cursor => cursor)
277
406
  end
278
407
  users = follower_ids.in_groups_of(MAX_USERS_PER_REQUEST, false).threaded_map do |follower_id_group|
279
408
  retryable(:tries => 3, :on => Twitter::Error::ServerError, :sleep => 0) do
280
409
  client.users(follower_id_group, :include_entities => false)
281
410
  end
282
411
  end.flatten
283
- print_user_list(users)
412
+ print_users(users)
284
413
  end
285
414
 
286
- desc "friends", "Returns the list of people who you follow and follow you back."
287
- method_option :created, :aliases => "-c", :type => :boolean, :default => false, :desc => "Sort by the time when Twitter acount was created."
415
+ desc "friends [USER]", "Returns the list of people who you follow and follow you back."
416
+ method_option :csv, :aliases => "-c", :type => :boolean, :default => false, :desc => "Output in CSV format."
288
417
  method_option :favorites, :aliases => "-v", :type => :boolean, :default => false, :desc => "Sort by number of favorites."
289
418
  method_option :followers, :aliases => "-f", :type => :boolean, :default => false, :desc => "Sort by number of followers."
290
- method_option :friends, :aliases => "-d", :type => :boolean, :default => false, :desc => "Sort by number of friends."
291
- method_option :listed, :aliases => "-i", :type => :boolean, :default => false, :desc => "Sort by number of list memberships."
292
- method_option :long, :aliases => "-l", :type => :boolean, :default => false, :desc => "List in long format."
419
+ method_option :friends, :aliases => "-e", :type => :boolean, :default => false, :desc => "Sort by number of friends."
420
+ method_option :id, :aliases => "-i", :type => "boolean", :default => false, :desc => "Specify user via ID instead of screen name."
421
+ method_option :listed, :aliases => "-s", :type => :boolean, :default => false, :desc => "Sort by number of list memberships."
422
+ method_option :long, :aliases => "-l", :type => :boolean, :default => false, :desc => "Output in long format."
423
+ method_option :posted, :aliases => "-p", :type => :boolean, :default => false, :desc => "Sort by the time when Twitter account was posted."
293
424
  method_option :reverse, :aliases => "-r", :type => :boolean, :default => false, :desc => "Reverse the order of the sort."
294
425
  method_option :tweets, :aliases => "-t", :type => :boolean, :default => false, :desc => "Sort by number of Tweets."
295
426
  method_option :unsorted, :aliases => "-u", :type => :boolean, :default => false, :desc => "Output is not sorted."
296
- def friends
427
+ def friends(user=nil)
428
+ if user
429
+ user = if options['id']
430
+ user.to_i
431
+ else
432
+ user.strip_ats
433
+ end
434
+ end
297
435
  following_ids = collect_with_cursor do |cursor|
298
- client.friend_ids(:cursor => cursor)
436
+ client.friend_ids(user, :cursor => cursor)
299
437
  end
300
438
  follower_ids = collect_with_cursor do |cursor|
301
- client.follower_ids(:cursor => cursor)
439
+ client.follower_ids(user, :cursor => cursor)
302
440
  end
303
441
  friend_ids = (following_ids & follower_ids)
304
442
  users = friend_ids.in_groups_of(MAX_USERS_PER_REQUEST, false).threaded_map do |friend_id_group|
@@ -306,25 +444,34 @@ module T
306
444
  client.users(friend_id_group, :include_entities => false)
307
445
  end
308
446
  end.flatten
309
- print_user_list(users)
447
+ print_users(users)
310
448
  end
311
449
 
312
- desc "leaders", "Returns the list of people who you follow but don't follow you back."
313
- method_option :created, :aliases => "-c", :type => :boolean, :default => false, :desc => "Sort by the time when Twitter acount was created."
450
+ desc "leaders [USER]", "Returns the list of people who you follow but don't follow you back."
451
+ method_option :csv, :aliases => "-c", :type => :boolean, :default => false, :desc => "Output in CSV format."
314
452
  method_option :favorites, :aliases => "-v", :type => :boolean, :default => false, :desc => "Sort by number of favorites."
315
453
  method_option :followers, :aliases => "-f", :type => :boolean, :default => false, :desc => "Sort by number of followers."
316
- method_option :friends, :aliases => "-d", :type => :boolean, :default => false, :desc => "Sort by number of friends."
317
- method_option :listed, :aliases => "-i", :type => :boolean, :default => false, :desc => "Sort by number of list memberships."
318
- method_option :long, :aliases => "-l", :type => :boolean, :default => false, :desc => "List in long format."
454
+ method_option :friends, :aliases => "-e", :type => :boolean, :default => false, :desc => "Sort by number of friends."
455
+ method_option :id, :aliases => "-i", :type => "boolean", :default => false, :desc => "Specify user via ID instead of screen name."
456
+ method_option :listed, :aliases => "-s", :type => :boolean, :default => false, :desc => "Sort by number of list memberships."
457
+ method_option :long, :aliases => "-l", :type => :boolean, :default => false, :desc => "Output in long format."
458
+ method_option :posted, :aliases => "-p", :type => :boolean, :default => false, :desc => "Sort by the time when Twitter account was posted."
319
459
  method_option :reverse, :aliases => "-r", :type => :boolean, :default => false, :desc => "Reverse the order of the sort."
320
460
  method_option :tweets, :aliases => "-t", :type => :boolean, :default => false, :desc => "Sort by number of Tweets."
321
461
  method_option :unsorted, :aliases => "-u", :type => :boolean, :default => false, :desc => "Output is not sorted."
322
- def leaders
462
+ def leaders(user=nil)
463
+ if user
464
+ user = if options['id']
465
+ user.to_i
466
+ else
467
+ user.strip_ats
468
+ end
469
+ end
323
470
  following_ids = collect_with_cursor do |cursor|
324
- client.friend_ids(:cursor => cursor)
471
+ client.friend_ids(user, :cursor => cursor)
325
472
  end
326
473
  follower_ids = collect_with_cursor do |cursor|
327
- client.follower_ids(:cursor => cursor)
474
+ client.follower_ids(user, :cursor => cursor)
328
475
  end
329
476
  leader_ids = (following_ids - follower_ids)
330
477
  users = leader_ids.in_groups_of(MAX_USERS_PER_REQUEST, false).threaded_map do |leader_id_group|
@@ -332,62 +479,105 @@ module T
332
479
  client.users(leader_id_group, :include_entities => false)
333
480
  end
334
481
  end.flatten
335
- print_user_list(users)
482
+ print_users(users)
483
+ end
484
+
485
+ desc "lists [USER]", "Returns the lists created by a user."
486
+ method_option :csv, :aliases => "-c", :type => :boolean, :default => false, :desc => "Output in CSV format."
487
+ method_option :id, :aliases => "-i", :type => "boolean", :default => false, :desc => "Specify user via ID instead of screen name."
488
+ method_option :long, :aliases => "-l", :type => :boolean, :default => false, :desc => "Output in long format."
489
+ method_option :members, :aliases => "-m", :type => :boolean, :default => false, :desc => "Sort by number of members."
490
+ method_option :mode, :aliases => "-o", :type => :boolean, :default => false, :desc => "Sort by mode."
491
+ method_option :posted, :aliases => "-p", :type => :boolean, :default => false, :desc => "Sort by the time when Twitter list was posted."
492
+ method_option :reverse, :aliases => "-r", :type => :boolean, :default => false, :desc => "Reverse the order of the sort."
493
+ method_option :subscribers, :aliases => "-s", :type => :boolean, :default => false, :desc => "Sort by number of subscribers."
494
+ method_option :unsorted, :aliases => "-u", :type => :boolean, :default => false, :desc => "Output is not sorted."
495
+ def lists(user=nil)
496
+ if user
497
+ user = if options['id']
498
+ user.to_i
499
+ else
500
+ user.strip_ats
501
+ end
502
+ end
503
+ lists = collect_with_cursor do |cursor|
504
+ client.lists(user, :cursor => cursor)
505
+ end
506
+ print_lists(lists)
336
507
  end
337
508
 
338
509
  desc "mentions", "Returns the #{DEFAULT_NUM_RESULTS} most recent Tweets mentioning you."
339
- method_option :long, :aliases => "-l", :type => :boolean, :default => false, :desc => "List in long format."
510
+ method_option :csv, :aliases => "-c", :type => :boolean, :default => false, :desc => "Output in CSV format."
511
+ method_option :long, :aliases => "-l", :type => :boolean, :default => false, :desc => "Output in long format."
340
512
  method_option :number, :aliases => "-n", :type => :numeric, :default => DEFAULT_NUM_RESULTS, :desc => "Limit the number of results."
341
513
  method_option :reverse, :aliases => "-r", :type => :boolean, :default => false, :desc => "Reverse the order of the sort."
342
514
  def mentions
343
515
  count = options['number'] || DEFAULT_NUM_RESULTS
344
516
  statuses = client.mentions(:count => count, :include_entities => false)
345
- print_status_list(statuses)
517
+ print_statuses(statuses)
346
518
  end
347
519
  map %w(replies) => :mentions
348
520
 
349
- desc "open SCREEN_NAME", "Opens that user's profile in a web browser."
350
- method_option :dry_run, :type => :boolean
351
- def open(screen_name)
352
- screen_name = screen_name.strip_ats
353
- Launchy.open("https://twitter.com/#{screen_name}", :dry_run => options.fetch('dry_run', false))
521
+ desc "open USER", "Opens that user's profile in a web browser."
522
+ method_option :display_url, :aliases => "-d", :type => :boolean, :default => false, :desc => "Display the requested URL instead of attempting to open it."
523
+ method_option :id, :aliases => "-i", :type => "boolean", :default => false, :desc => "Specify user via ID instead of screen name."
524
+ method_option :status, :aliases => "-s", :type => :boolean, :default => false, :desc => "Specify input as a Twitter status ID instead of a screen name."
525
+ def open(user)
526
+ if options['id']
527
+ user = client.user(user.to_i, :include_entities => false)
528
+ Launchy.open("https://twitter.com/#{user.screen_name}", :dry_run => options['display_url'])
529
+ elsif options['status']
530
+ status = client.status(user.to_i, :include_entities => false, :include_my_retweet => false)
531
+ Launchy.open("https://twitter.com/#{status.user.screen_name}/status/#{status.id}", :dry_run => options['display_url'])
532
+ else
533
+ Launchy.open("https://twitter.com/#{user.strip_ats}", :dry_run => options['display_url'])
534
+ end
354
535
  end
355
536
 
356
537
  desc "reply STATUS_ID MESSAGE", "Post your Tweet as a reply directed at another person."
538
+ method_option :all, :aliases => "-a", :type => "boolean", :default => false, :desc => "Reply to all users mentioned in the Tweet."
357
539
  method_option :location, :aliases => "-l", :type => :boolean, :default => false
358
540
  def reply(status_id, message)
359
541
  status_id = status_id.strip_commas
360
- status = client.status(status_id, :include_entities => false, :include_my_retweet => false)
542
+ status = client.status(status_id.to_i, :include_entities => false, :include_my_retweet => false)
543
+ users = Array(status.user.screen_name)
544
+ users += extract_mentioned_screen_names(status.text) if options['all']
545
+ users.uniq!
546
+ users.map!(&:prepend_at)
361
547
  opts = {:in_reply_to_status_id => status.id, :include_entities => false, :trim_user => true}
362
548
  opts.merge!(:lat => location.lat, :long => location.lng) if options['location']
363
- reply = client.update("@#{status.user.screen_name} #{message}", opts)
364
- say "Reply created by @#{@rcfile.default_profile[0]} to @#{status.user.screen_name} (#{time_ago_in_words(reply.created_at)} ago)."
549
+ reply = client.update("#{users.join(' ')} #{message}", opts)
550
+ say "Reply created by @#{@rcfile.default_profile[0]} to #{users.join(' ')} (#{time_ago_in_words(reply.created_at)} ago)."
365
551
  say
366
552
  say "Run `#{File.basename($0)} delete status #{reply.id}` to delete."
367
553
  end
368
554
 
369
- desc "report_spam SCREEN_NAME [SCREEN_NAME...]", "Report users for spam."
370
- def report_spam(screen_name, *screen_names)
371
- screen_names.unshift(screen_name)
372
- screen_names.map!(&:strip_ats)
373
- screen_names.threaded_each do |screen_name|
374
- screen_name.strip_ats
555
+ desc "report_spam USER [USER...]", "Report users for spam."
556
+ method_option :id, :aliases => "-i", :type => "boolean", :default => false, :desc => "Specify input as Twitter user IDs instead of screen names."
557
+ def report_spam(user, *users)
558
+ users.unshift(user)
559
+ if options['id']
560
+ users.map!(&:to_i)
561
+ else
562
+ users.map!(&:strip_ats)
563
+ end
564
+ users = users.threaded_map do |user|
375
565
  retryable(:tries => 3, :on => Twitter::Error::ServerError, :sleep => 0) do
376
- client.report_spam(screen_name, :include_entities => false)
566
+ client.report_spam(user, :include_entities => false)
377
567
  end
378
568
  end
379
- number = screen_names.length
569
+ number = users.length
380
570
  say "@#{@rcfile.default_profile[0]} reported #{number} #{number == 1 ? 'user' : 'users'}."
381
571
  end
382
- map %w(report spam) => :report_spam
572
+ map %w(report reportspam spam) => :report_spam
383
573
 
384
574
  desc "retweet STATUS_ID [STATUS_ID...]", "Sends Tweets to your followers."
385
575
  def retweet(status_id, *status_ids)
386
576
  status_ids.unshift(status_id)
387
- status_ids.map!(&:strip_commas).map!(&:to_i)
577
+ status_ids.map!(&:strip_commas)
388
578
  retweets = status_ids.threaded_map do |status_id|
389
579
  retryable(:tries => 3, :on => Twitter::Error::ServerError, :sleep => 0) do
390
- client.retweet(status_id, :include_entities => false, :trim_user => true)
580
+ client.retweet(status_id.to_i, :include_entities => false, :trim_user => true)
391
581
  end
392
582
  end
393
583
  number = retweets.length
@@ -397,90 +587,182 @@ module T
397
587
  end
398
588
  map %w(rt) => :retweet
399
589
 
400
- desc "retweets [SCREEN_NAME]", "Returns the #{DEFAULT_NUM_RESULTS} most recent Retweets by a user."
401
- method_option :long, :aliases => "-l", :type => :boolean, :default => false, :desc => "List in long format."
590
+ desc "retweets [USER]", "Returns the #{DEFAULT_NUM_RESULTS} most recent Retweets by a user."
591
+ method_option :csv, :aliases => "-c", :type => :boolean, :default => false, :desc => "Output in CSV format."
592
+ method_option :id, :aliases => "-i", :type => "boolean", :default => false, :desc => "Specify user via ID instead of screen name."
593
+ method_option :long, :aliases => "-l", :type => :boolean, :default => false, :desc => "Output in long format."
402
594
  method_option :number, :aliases => "-n", :type => :numeric, :default => DEFAULT_NUM_RESULTS, :desc => "Limit the number of results."
403
595
  method_option :reverse, :aliases => "-r", :type => :boolean, :default => false, :desc => "Reverse the order of the sort."
404
- def retweets(screen_name=nil)
405
- screen_name = screen_name.strip_ats if screen_name
596
+ def retweets(user=nil)
597
+ if user
598
+ user = if options['id']
599
+ user.to_i
600
+ else
601
+ user.strip_ats
602
+ end
603
+ end
406
604
  count = options['number'] || DEFAULT_NUM_RESULTS
407
- statuses = client.retweeted_by(screen_name, :count => count, :include_entities => false)
408
- print_status_list(statuses)
605
+ statuses = client.retweeted_by(user, :count => count, :include_entities => false)
606
+ print_statuses(statuses)
409
607
  end
410
608
  map %w(rts) => :retweets
411
609
 
610
+ desc "ruler", "Prints a 140-character ruler"
611
+ def ruler
612
+ say "----|----|----|----|----|----|----|----|----|----|----|----|----|----|----|----|----|----|----|----|----|----|----|----|----|----|----|----|"
613
+ end
614
+
412
615
  desc "status STATUS_ID", "Retrieves detailed information about a Tweet."
616
+ method_option :csv, :aliases => "-c", :type => :boolean, :default => false, :desc => "Output in CSV format."
413
617
  def status(status_id)
414
618
  status_id = status_id.strip_commas
415
- status = client.status(status_id, :include_entities => false, :include_my_retweet => false)
416
- created_at = status.created_at > 6.months.ago ? status.created_at.strftime("%b %e %H:%M") : status.created_at.strftime("%b %e %Y")
417
- array = []
418
- array << ["ID", status.id.to_s]
419
- array << ["Text", status.text.gsub(/\n+/, ' ')]
420
- array << ["Screen name", "@#{status.user.screen_name}"]
421
- array << ["Posted at", created_at]
422
- unless status.geo.nil?
423
- location = Geokit::Geocoders::MultiGeocoder.reverse_geocode(status.geo.coordinates)
424
- if location.city && location.state && location.country
425
- array << ["Location", [location.city, location.state, location.country].join(", ")]
426
- elsif location.state && location.country
427
- array << ["Location", [location.state, location.country].join(", ")]
428
- elsif location.country
429
- array << ["Location", location.country]
430
- end
431
- end
432
- array << ["Retweets", number_with_delimiter(status.retweet_count)] unless status.retweet_count.zero?
433
- array << ["Source", strip_tags(status.source)]
434
- array << ["URL", "https://twitter.com/#{status.user.screen_name}/status/#{status.id}"]
435
- print_table(array)
436
- end
437
-
438
- desc "suggest", "This command returns a listing of Twitter users' accounts we think you might enjoy following."
439
- method_option :created, :aliases => "-c", :type => :boolean, :default => false, :desc => "Sort by the time when Twitter acount was created."
619
+ status = client.status(status_id.to_i, :include_entities => false, :include_my_retweet => false)
620
+ if status.geo
621
+ geoloc = Geokit::Geocoders::MultiGeocoder.reverse_geocode(status.geo.coordinates)
622
+ location = if geoloc.city && geoloc.state && geoloc.country
623
+ [geoloc.city, geoloc.state, geoloc.country].join(", ")
624
+ elsif geoloc.state && geoloc.country
625
+ [geoloc.state, geoloc.country].join(", ")
626
+ else
627
+ geoloc.country
628
+ end
629
+ else
630
+ location = nil
631
+ end
632
+ if options['csv']
633
+ say ["ID", "Text", "Screen name", "Posted at", "Location", "Retweets", "Source", "URL"].to_csv
634
+ say [status.id, status.text, status.user.screen_name, status.created_at.utc.strftime("%Y-%m-%d %H:%M:%S %z"), location, status.retweet_count, strip_tags(status.source), "https://twitter.com/#{status.user.screen_name}/status/#{status.id}"].to_csv
635
+ else
636
+ array = []
637
+ array << ["ID", status.id.to_s]
638
+ array << ["Text", status.text.gsub(/\n+/, ' ')]
639
+ array << ["Screen name", "@#{status.user.screen_name}"]
640
+ posted_at = status.created_at > 6.months.ago ? status.created_at.strftime("%b %e %H:%M") : status.created_at.strftime("%b %e %Y")
641
+ array << ["Posted at", posted_at]
642
+ array << ["Location", location] unless location.nil?
643
+ array << ["Retweets", number_with_delimiter(status.retweet_count)]
644
+ array << ["Source", strip_tags(status.source)]
645
+ array << ["URL", "https://twitter.com/#{status.user.screen_name}/status/#{status.id}"]
646
+ print_table(array)
647
+ end
648
+ end
649
+
650
+ desc "suggest [USER]", "Returns a listing of Twitter users' accounts you might enjoy following."
651
+ method_option :csv, :aliases => "-c", :type => :boolean, :default => false, :desc => "Output in CSV format."
440
652
  method_option :favorites, :aliases => "-v", :type => :boolean, :default => false, :desc => "Sort by number of favorites."
441
653
  method_option :followers, :aliases => "-f", :type => :boolean, :default => false, :desc => "Sort by number of followers."
442
- method_option :friends, :aliases => "-d", :type => :boolean, :default => false, :desc => "Sort by number of friends."
443
- method_option :listed, :aliases => "-i", :type => :boolean, :default => false, :desc => "Sort by number of list memberships."
444
- method_option :long, :aliases => "-l", :type => :boolean, :default => false, :desc => "List in long format."
654
+ method_option :friends, :aliases => "-e", :type => :boolean, :default => false, :desc => "Sort by number of friends."
655
+ method_option :id, :aliases => "-i", :type => "boolean", :default => false, :desc => "Specify user via ID instead of screen name."
656
+ method_option :listed, :aliases => "-s", :type => :boolean, :default => false, :desc => "Sort by number of list memberships."
657
+ method_option :long, :aliases => "-l", :type => :boolean, :default => false, :desc => "Output in long format."
445
658
  method_option :number, :aliases => "-n", :type => :numeric, :default => DEFAULT_NUM_RESULTS, :desc => "Limit the number of results."
659
+ method_option :posted, :aliases => "-p", :type => :boolean, :default => false, :desc => "Sort by the time when Twitter account was posted."
446
660
  method_option :reverse, :aliases => "-r", :type => :boolean, :default => false, :desc => "Reverse the order of the sort."
447
661
  method_option :tweets, :aliases => "-t", :type => :boolean, :default => false, :desc => "Sort by number of Tweets."
448
662
  method_option :unsorted, :aliases => "-u", :type => :boolean, :default => false, :desc => "Output is not sorted."
449
- def suggest
663
+ def suggest(user=nil)
664
+ if user
665
+ user = if options['id']
666
+ user.to_i
667
+ else
668
+ user.strip_ats
669
+ end
670
+ end
450
671
  limit = options['number'] || DEFAULT_NUM_RESULTS
451
- users = client.recommendations(:limit => limit, :include_entities => false)
452
- print_user_list(users)
672
+ users = client.recommendations(user, :limit => limit, :include_entities => false)
673
+ print_users(users)
453
674
  end
454
675
 
455
- desc "timeline [SCREEN_NAME]", "Returns the #{DEFAULT_NUM_RESULTS} most recent Tweets posted by a user."
456
- method_option :long, :aliases => "-l", :type => :boolean, :default => false, :desc => "List in long format."
676
+ desc "timeline [USER]", "Returns the #{DEFAULT_NUM_RESULTS} most recent Tweets posted by a user."
677
+ method_option :csv, :aliases => "-c", :type => :boolean, :default => false, :desc => "Output in CSV format."
678
+ method_option :id, :aliases => "-i", :type => "boolean", :default => false, :desc => "Specify user via ID instead of screen name."
679
+ method_option :long, :aliases => "-l", :type => :boolean, :default => false, :desc => "Output in long format."
457
680
  method_option :number, :aliases => "-n", :type => :numeric, :default => DEFAULT_NUM_RESULTS, :desc => "Limit the number of results."
458
681
  method_option :reverse, :aliases => "-r", :type => :boolean, :default => false, :desc => "Reverse the order of the sort."
459
- def timeline(screen_name=nil)
682
+ def timeline(user=nil)
460
683
  count = options['number'] || DEFAULT_NUM_RESULTS
461
- if screen_name
462
- screen_name = screen_name.strip_ats
463
- statuses = client.user_timeline(screen_name, :count => count, :include_entities => false)
684
+ if user
685
+ user = if options['id']
686
+ user.to_i
687
+ else
688
+ user.strip_ats
689
+ end
690
+ statuses = client.user_timeline(user, :count => count, :include_entities => false)
464
691
  else
465
692
  statuses = client.home_timeline(:count => count, :include_entities => false)
466
693
  end
467
- print_status_list(statuses)
694
+ print_statuses(statuses)
468
695
  end
469
696
  map %w(tl) => :timeline
470
697
 
471
- desc "unfollow SCREEN_NAME [SCREEN_NAME...]", "Allows you to stop following users."
472
- def unfollow(screen_name, *screen_names)
473
- screen_names.unshift(screen_name)
474
- screen_names.map!(&:strip_ats)
475
- screen_names.threaded_each do |screen_name|
698
+ desc "trends [WOEID]", "Returns the top 10 trending topics."
699
+ method_option :exclude_hashtags, :aliases => "-x", :type => "boolean", :default => false, :desc => "Remove all hashtags from the trends list."
700
+ def trends(woe_id=1)
701
+ opts = {}
702
+ opts.merge!(:exclude => "hashtags") if options['exclude_hashtags']
703
+ trends = client.trends(woe_id, opts)
704
+ if STDOUT.tty?
705
+ print_in_columns(trends.map(&:name))
706
+ else
707
+ trends.each do |trend|
708
+ say trend.name
709
+ end
710
+ end
711
+ end
712
+
713
+ desc "trends_locations", "Returns the locations for which Twitter has trending topic information."
714
+ method_option :csv, :aliases => "-c", :type => :boolean, :default => false, :desc => "Output in CSV format."
715
+ method_option :long, :aliases => "-l", :type => :boolean, :default => false, :desc => "Output in long format."
716
+ method_option :reverse, :aliases => "-r", :type => :boolean, :default => false, :desc => "Reverse the order of the sort."
717
+ method_option :unsorted, :aliases => "-u", :type => :boolean, :default => false, :desc => "Output is not sorted."
718
+ def trend_locations
719
+ places = client.trend_locations
720
+ places = places.sort_by{|places| places.name.downcase} unless options['unsorted']
721
+ places.reverse! if options['reverse']
722
+ if options['csv']
723
+ say ["WOEID", "Parent ID", "Type", "Name", "Country"].to_csv unless places.empty?
724
+ places.each do |place|
725
+ say [place.woeid, place.parent_id, place.place_type, place.name, place.country].to_csv
726
+ end
727
+ elsif options['long']
728
+ array = places.map do |place|
729
+ [place.woeid.to_s, place.parent_id.to_s, place.place_type, place.name, place.country]
730
+ end
731
+ if STDOUT.tty?
732
+ headings = ["WOEID", "Parent ID", "Type", "Name", "Country"]
733
+ array.unshift(headings) unless places.empty?
734
+ end
735
+ print_table(array)
736
+ else
737
+ if STDOUT.tty?
738
+ print_in_columns(places.map(&:name))
739
+ else
740
+ places.each do |place|
741
+ say place.name
742
+ end
743
+ end
744
+ end
745
+ end
746
+ map %w(locations trendlocations) => :trend_locations
747
+
748
+ desc "unfollow USER [USER...]", "Allows you to stop following users."
749
+ method_option :id, :aliases => "-i", :type => "boolean", :default => false, :desc => "Specify input as Twitter user IDs instead of screen names."
750
+ def unfollow(user, *users)
751
+ users.unshift(user)
752
+ if options['id']
753
+ users.map!(&:to_i)
754
+ else
755
+ users.map!(&:strip_ats)
756
+ end
757
+ users = users.threaded_map do |user|
476
758
  retryable(:tries => 3, :on => Twitter::Error::ServerError, :sleep => 0) do
477
- client.unfollow(screen_name, :include_entities => false)
759
+ client.unfollow(user, :include_entities => false)
478
760
  end
479
761
  end
480
- number = screen_names.length
762
+ number = users.length
481
763
  say "@#{@rcfile.default_profile[0]} is no longer following #{number} #{number == 1 ? 'user' : 'users'}."
482
764
  say
483
- say "Run `#{File.basename($0)} follow #{screen_names.map{|screen_name| "@#{screen_name}"}.join(' ')}` to follow again."
765
+ say "Run `#{File.basename($0)} follow #{users.map{|user| "@#{user.screen_name}"}.join(' ')}` to follow again."
484
766
  end
485
767
 
486
768
  desc "update MESSAGE", "Post a Tweet."
@@ -495,21 +777,27 @@ module T
495
777
  end
496
778
  map %w(post tweet) => :update
497
779
 
498
- desc "users SCREEN_NAME [SCREEN_NAME...]", "Returns a list of users you specify."
499
- method_option :created, :aliases => "-c", :type => :boolean, :default => false, :desc => "Sort by the time when Twitter acount was created."
780
+ desc "users USER [USER...]", "Returns a list of users you specify."
781
+ method_option :csv, :aliases => "-c", :type => :boolean, :default => false, :desc => "Output in CSV format."
500
782
  method_option :favorites, :aliases => "-v", :type => :boolean, :default => false, :desc => "Sort by number of favorites."
501
783
  method_option :followers, :aliases => "-f", :type => :boolean, :default => false, :desc => "Sort by number of followers."
502
- method_option :friends, :aliases => "-d", :type => :boolean, :default => false, :desc => "Sort by number of friends."
503
- method_option :listed, :aliases => "-i", :type => :boolean, :default => false, :desc => "Sort by number of list memberships."
504
- method_option :long, :aliases => "-l", :type => :boolean, :default => false, :desc => "List in long format."
784
+ method_option :friends, :aliases => "-e", :type => :boolean, :default => false, :desc => "Sort by number of friends."
785
+ method_option :id, :aliases => "-i", :type => "boolean", :default => false, :desc => "Specify input as Twitter user IDs instead of screen names."
786
+ method_option :listed, :aliases => "-s", :type => :boolean, :default => false, :desc => "Sort by number of list memberships."
787
+ method_option :long, :aliases => "-l", :type => :boolean, :default => false, :desc => "Output in long format."
788
+ method_option :posted, :aliases => "-p", :type => :boolean, :default => false, :desc => "Sort by the time when Twitter account was posted."
505
789
  method_option :reverse, :aliases => "-r", :type => :boolean, :default => false, :desc => "Reverse the order of the sort."
506
790
  method_option :tweets, :aliases => "-t", :type => :boolean, :default => false, :desc => "Sort by number of Tweets."
507
791
  method_option :unsorted, :aliases => "-u", :type => :boolean, :default => false, :desc => "Output is not sorted."
508
- def users(screen_name, *screen_names)
509
- screen_names.unshift(screen_name)
510
- screen_names.map!(&:strip_ats)
511
- users = client.users(screen_names, :include_entities => false)
512
- print_user_list(users)
792
+ def users(user, *users)
793
+ users.unshift(user)
794
+ if options['id']
795
+ users.map!(&:to_i)
796
+ else
797
+ users.map!(&:strip_ats)
798
+ end
799
+ users = client.users(users, :include_entities => false)
800
+ print_users(users)
513
801
  end
514
802
  map %w(stats) => :users
515
803
 
@@ -519,39 +807,49 @@ module T
519
807
  end
520
808
  map %w(-v --version) => :version
521
809
 
522
- desc "whois SCREEN_NAME", "Retrieves profile information for the user."
523
- def whois(screen_name)
524
- screen_name = screen_name.strip_ats
525
- user = client.user(screen_name, :include_entities => false)
526
- array = []
527
- name_label = user.verified ? "Name (Verified)" : "Name"
528
- array << ["ID", user.id.to_s]
529
- array << [name_label, user.name] unless user.name.nil?
530
- array << ["Bio", user.description.gsub(/\n+/, ' ')] unless user.description.nil?
531
- array << ["Location", user.location] unless user.location.nil?
532
- array << ["URL", user.url] unless user.url.nil?
533
- following = user.following ? "Following" : "Not following"
534
- array << ["Status", following] unless user.following.nil?
535
- array << ["Last update", "#{user.status.text.gsub(/\n+/, ' ')} (#{time_ago_in_words(user.status.created_at)} ago)"] unless user.status.nil?
536
- created_at = user.created_at > 6.months.ago ? user.created_at.strftime("%b %e %H:%M") : user.created_at.strftime("%b %e %Y")
537
- array << ["Since", created_at]
538
- array << ["Tweets", number_with_delimiter(user.statuses_count)]
539
- array << ["Favorites", number_with_delimiter(user.favourites_count)]
540
- array << ["Listed", number_with_delimiter(user.listed_count)]
541
- array << ["Following", number_with_delimiter(user.friends_count)]
542
- array << ["Followers", number_with_delimiter(user.followers_count)]
543
- print_table(array)
810
+ desc "whois USER", "Retrieves profile information for the user."
811
+ method_option :csv, :aliases => "-c", :type => :boolean, :default => false, :desc => "Output in CSV format."
812
+ method_option :id, :aliases => "-i", :type => "boolean", :default => false, :desc => "Specify user via ID instead of screen name."
813
+ def whois(user)
814
+ user = if options['id']
815
+ user.to_i
816
+ else
817
+ user.strip_ats
818
+ end
819
+ user = client.user(user, :include_entities => false)
820
+ if options['csv']
821
+ say ["ID", "Verified", "Name", "Screen name", "Bio", "Location", "Following", "Last update", "Lasted updated at", "Since", "Tweets", "Favorites", "Listed", "Following", "Followers", "URL"].to_csv
822
+ say [user.id, user.verified?, user.name, user.screen_name, user.description, user.location, user.following?, user.status.text, user.status.created_at.utc.strftime("%Y-%m-%d %H:%M:%S %z"), user.created_at.utc.strftime("%Y-%m-%d %H:%M:%S %z"), user.statuses_count, user.favourites_count, user.listed_count, user.friends_count, user.followers_count, user.url].to_csv
823
+ else
824
+ array = []
825
+ name_label = user.verified ? "Name (Verified)" : "Name"
826
+ array << ["ID", user.id.to_s]
827
+ array << [name_label, user.name] unless user.name.nil?
828
+ array << ["Bio", user.description.gsub(/\n+/, ' ')] unless user.description.nil?
829
+ array << ["Location", user.location] unless user.location.nil?
830
+ status = user.following ? "Following" : "Not following"
831
+ array << ["Status", status]
832
+ array << ["Last update", "#{user.status.text.gsub(/\n+/, ' ')} (#{time_ago_in_words(user.status.created_at)} ago)"] unless user.status.nil?
833
+ created_at = user.created_at > 6.months.ago ? user.created_at.strftime("%b %e %H:%M") : user.created_at.strftime("%b %e %Y")
834
+ array << ["Since", created_at]
835
+ array << ["Tweets", number_with_delimiter(user.statuses_count)]
836
+ array << ["Favorites", number_with_delimiter(user.favourites_count)]
837
+ array << ["Listed", number_with_delimiter(user.listed_count)]
838
+ array << ["Following", number_with_delimiter(user.friends_count)]
839
+ array << ["Followers", number_with_delimiter(user.followers_count)]
840
+ array << ["URL", user.url] unless user.url.nil?
841
+ print_table(array)
842
+ end
544
843
  end
844
+ map %w(user) => :whois
545
845
 
546
846
  desc "delete SUBCOMMAND ...ARGS", "Delete Tweets, Direct Messages, etc."
547
- method_option :force, :aliases => "-f", :type => :boolean, :default => false
548
847
  subcommand 'delete', T::Delete
549
848
 
550
849
  desc "list SUBCOMMAND ...ARGS", "Do various things with lists."
551
850
  subcommand 'list', T::List
552
851
 
553
852
  desc "search SUBCOMMAND ...ARGS", "Search through Tweets."
554
- method_option :long, :aliases => "-l", :type => :boolean, :default => false, :desc => "List in long format."
555
853
  subcommand 'search', T::Search
556
854
 
557
855
  desc "set SUBCOMMAND ...ARGS", "Change various account settings."