t 0.6.4 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/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."