t 0.9.6 → 0.9.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. data/README.md +29 -10
  2. data/bin/t +4 -25
  3. data/lib/t/cli.rb +24 -23
  4. data/lib/t/core_ext/kernel.rb +15 -0
  5. data/lib/t/core_ext/string.rb +4 -2
  6. data/lib/t/delete.rb +13 -13
  7. data/lib/t/format_helpers.rb +62 -0
  8. data/lib/t/list.rb +4 -4
  9. data/lib/t/printable.rb +27 -24
  10. data/lib/t/rcfile.rb +1 -1
  11. data/lib/t/requestable.rb +0 -4
  12. data/lib/t/search.rb +10 -11
  13. data/lib/t/stream.rb +57 -21
  14. data/lib/t/version.rb +1 -1
  15. data/lib/t.rb +22 -3
  16. data/spec/cli_spec.rb +53 -45
  17. data/spec/fixtures/501_ids.json +509 -1
  18. data/spec/fixtures/501_users_list.json +17543 -1
  19. data/spec/fixtures/direct_message.json +80 -1
  20. data/spec/fixtures/direct_messages.json +802 -1
  21. data/spec/fixtures/empty_cursor.json +7 -1
  22. data/spec/fixtures/favorites.json +1099 -1
  23. data/spec/fixtures/followers_ids.json +10 -1
  24. data/spec/fixtures/friends_ids.json +9 -1
  25. data/spec/fixtures/gem.json +61 -1
  26. data/spec/fixtures/list.json +54 -1
  27. data/spec/fixtures/lists.json +116 -1
  28. data/spec/fixtures/locations.json +57 -1
  29. data/spec/fixtures/not_found.json +4 -1
  30. data/spec/fixtures/rate_limit_status.json +6 -1
  31. data/spec/fixtures/recommendations.json +364 -1
  32. data/spec/fixtures/retweet.json +112 -1
  33. data/spec/fixtures/search.json +346 -1
  34. data/spec/fixtures/settings.json +30 -1
  35. data/spec/fixtures/sferik.json +76 -1
  36. data/spec/fixtures/status.json +109 -1
  37. data/spec/fixtures/status_no_attributes.json +104 -1
  38. data/spec/fixtures/status_no_country.json +102 -1
  39. data/spec/fixtures/status_no_full_name.json +101 -1
  40. data/spec/fixtures/status_no_locality.json +107 -1
  41. data/spec/fixtures/status_no_street_address.json +108 -1
  42. data/spec/fixtures/statuses.json +1105 -1
  43. data/spec/fixtures/trends.json +35 -1
  44. data/spec/fixtures/users.json +92 -1
  45. data/spec/fixtures/users_list.json +98 -1
  46. data/spec/list_spec.rb +13 -5
  47. data/spec/search_spec.rb +110 -102
  48. data/t.gemspec +1 -2
  49. metadata +6 -32
data/README.md CHANGED
@@ -1,4 +1,9 @@
1
+ # [![Application icon](https://github.com/sferik/t/raw/master/icon/t.png)][icon]
2
+
3
+ [icon]: https://github.com/sferik/t/raw/master/icon/t.png
4
+
1
5
  # Twitter CLI [![Build Status](https://secure.travis-ci.org/sferik/t.png?branch=master)][travis] [![Dependency Status](https://gemnasium.com/sferik/t.png?travis)][gemnasium] [![Click here to make a donation to T](http://www.pledgie.com/campaigns/17330.png)][pledgie]
6
+
2
7
  ### A command-line power tool for Twitter.
3
8
 
4
9
  The CLI takes syntactic cues from the [Twitter SMS commands][sms], however it
@@ -7,12 +12,10 @@ offers vastly more commands and capabilities than are available via SMS.
7
12
  [travis]: http://travis-ci.org/sferik/t
8
13
  [gemnasium]: https://gemnasium.com/sferik/t
9
14
  [pledgie]: http://www.pledgie.com/campaigns/17330
10
- [gem]: https://rubygems.org/gems/twitter
11
15
  [sms]: https://support.twitter.com/articles/14020-twitter-sms-command
12
16
 
13
17
  ## Installation
14
- # Requires Ruby :)
15
- gem install t
18
+ gem install t # Requires Ruby :)
16
19
 
17
20
  ## Configuration
18
21
 
@@ -56,7 +59,7 @@ and consumer key pair, like this:
56
59
 
57
60
  Account information is stored in a YAML-formatted file located at `~/.trc`.
58
61
 
59
- **Note**: Anyone with access to this file can masquerade as you on Twitter, so
62
+ **Note**: Anyone with access to this file can impersonate you on Twitter, so
60
63
  it's important to keep it secure, just as you would treat your SSH private key.
61
64
  For this reason, the file is hidden and has the permission bits set to `0600`.
62
65
 
@@ -114,7 +117,7 @@ example, send a user a direct message only if he already follows you:
114
117
  t leaders | xargs t unfollow
115
118
 
116
119
  ### Twitter roulette: randomly follow someone who follows you (who you don't already follow)
117
- t disciples | shuf | head -1 | xargs t follow
120
+ t groupies | shuf | head -1 | xargs t follow
118
121
 
119
122
  ### Favorite the last 10 tweets that mention you
120
123
  t mentions -n 10 -l | awk '{print $1}' | xargs t favorite
@@ -163,6 +166,23 @@ example, send a user a direct message only if he already follows you:
163
166
 
164
167
  [search]: https://dev.twitter.com/docs/using-search
165
168
 
169
+ # Using T for Backup
170
+
171
+ [@jphpsf][jphpsf] wrote a [blog post][blog] explaining how to use `t` to backup
172
+ your Twitter account.
173
+
174
+ [jphpsf]: https://github.com/jphpsf
175
+ [blog]: http://blog.jphpsf.com/2012/05/07/backing-up-your-twitter-account-with-t/
176
+
177
+ `t` was also mentioned on [an episode of the Ruby 5 podcast][ruby5].
178
+
179
+ [ruby5]: http://ruby5.envylabs.com/episodes/273-episode-269-may-4th-2012/stories/2400-t-command-line-power-tool-for-twitter
180
+
181
+ If you discuss `t` in a blog post or podcast, [let me know][email] and I'll
182
+ link it here.
183
+
184
+ [email]: mailto:sferik@gmail.com
185
+
166
186
  ## Relationship Terminology
167
187
 
168
188
  There is some ambiguity in the terminology used to describe relationships on
@@ -176,7 +196,7 @@ used by `t`:
176
196
  | YOU FOLLOW THEM | YOU DON'T FOLLOW THEM |
177
197
  _________________________|_________________________|_________________________|_________________________
178
198
  | | | | |
179
- | THEY FOLLOW YOU | friends | disciples | followers |
199
+ | THEY FOLLOW YOU | friends | groupies | followers |
180
200
  |_________________________|_________________________|_________________________|_________________________|
181
201
  | | |
182
202
  | THEY DON'T FOLLOW YOU | leaders |
@@ -195,6 +215,7 @@ version 0.5.0, when it was [removed][]. This project is offered as a sucessor
195
215
  to that effort, however it is a clean room implementation that contains none of
196
216
  the original code.
197
217
 
218
+ [gem]: https://rubygems.org/gems/twitter
198
219
  [removed]: https://github.com/jnunemaker/twitter/commit/dd2445e3e2c97f38b28a3f32ea902536b3897adf
199
220
  ![History](https://github.com/sferik/t/raw/master/screenshots/history.png)
200
221
 
@@ -253,9 +274,6 @@ implementations:
253
274
  * Ruby 1.8.7
254
275
  * Ruby 1.9.2
255
276
  * Ruby 1.9.3
256
- * [Rubinius][]
257
-
258
- [rubinius]: http://rubini.us/
259
277
 
260
278
  If something doesn't work on one of these Ruby versions, it's a bug.
261
279
 
@@ -272,5 +290,6 @@ of a major release, support for that Ruby version may be dropped.
272
290
 
273
291
  ## Copyright
274
292
  Copyright (c) 2011 Erik Michaels-Ober. See [LICENSE][] for details.
275
-
293
+ Application icon by [@nvk][nvk].
276
294
  [license]: https://github.com/sferik/t/blob/master/LICENSE.md
295
+ [nvk]: http://rodolfonovak.com
data/bin/t CHANGED
@@ -12,34 +12,13 @@ begin
12
12
  rescue Interrupt
13
13
  pute "Quitting..."
14
14
  exit 1
15
- rescue Twitter::Error::BadRequest => error
16
- pute error.message
17
- exit 400
18
15
  rescue OAuth::Unauthorized
19
16
  pute "Authorization failed"
20
- exit 401
17
+ exit 1
21
18
  rescue Twitter::Error::Unauthorized => error
22
19
  pute "#{error.message} Run `#{$0} authorize --consumer-key=CONSUMER_KEY --consumer-secret=CONSUMER_SECRET` to authorize."
23
- exit 401
24
- rescue Twitter::Error::Forbidden => error
25
- pute error.message
26
- exit 403
27
- rescue Twitter::Error::NotFound => error
28
- pute error.message
29
- exit 404
30
- rescue Twitter::Error::NotAcceptable => error
31
- pute error.message
32
- exit 406
33
- rescue Twitter::Error::EnhanceYourCalm => error
34
- pute error.message
35
- exit 420
36
- rescue Twitter::Error::InternalServerError => error
37
- pute error.message
38
- exit 500
39
- rescue Twitter::Error::BadGateway => error
40
- pute error.message
41
- exit 502
42
- rescue Twitter::Error::ServiceUnavailable => error
20
+ exit 1
21
+ rescue Twitter::Error => error
43
22
  pute error.message
44
- exit 503
23
+ exit 1
45
24
  end
data/lib/t/cli.rb CHANGED
@@ -1,8 +1,8 @@
1
- require 'action_view'
2
1
  require 'active_support/core_ext/array/grouping'
3
2
  require 'active_support/core_ext/date/calculations'
4
3
  require 'active_support/core_ext/integer/time'
5
4
  require 'active_support/core_ext/numeric/time'
5
+ require 't/format_helpers'
6
6
  require 'csv'
7
7
  # 'fastercsv' required on Ruby versions < 1.9
8
8
  require 'fastercsv' unless Array.new.respond_to?(:to_csv)
@@ -27,17 +27,17 @@ require 'yaml'
27
27
 
28
28
  module T
29
29
  class CLI < Thor
30
- include ActionView::Helpers::DateHelper
31
- include ActionView::Helpers::NumberHelper
32
- include ActionView::Helpers::TextHelper
33
30
  include T::Authorizable
34
31
  include T::Collectable
35
32
  include T::Printable
36
33
  include T::Requestable
34
+ include T::FormatHelpers
37
35
 
38
36
  DEFAULT_NUM_RESULTS = 20
39
37
  MAX_SCREEN_NAME_SIZE = 20
40
38
  MAX_USERS_PER_REQUEST = 100
39
+ DIRECT_MESSAGE_HEADINGS = ["ID", "Posted at", "Screen name", "Text"]
40
+ TREND_HEADINGS = ["WOEID", "Parent ID", "Type", "Name", "Country"]
41
41
 
42
42
  check_unknown_options!
43
43
 
@@ -141,7 +141,8 @@ module T
141
141
  array = direct_messages.map do |direct_message|
142
142
  [direct_message.id, ls_formatted_time(direct_message), "@#{direct_message.sender.screen_name}", HTMLEntities.new.decode(direct_message.text).gsub(/\n+/, ' ')]
143
143
  end
144
- print_table_with_headings(array, DIRECT_MESSAGE_HEADINGS)
144
+ format = options['format'] || DIRECT_MESSAGE_HEADINGS.size.times.map{"%s"}
145
+ print_table_with_headings(array, DIRECT_MESSAGE_HEADINGS, format)
145
146
  else
146
147
  direct_messages.each do |direct_message|
147
148
  say "#{direct_message.sender.screen_name.rjust(MAX_SCREEN_NAME_SIZE)}: #{direct_message.text.gsub(/\n+/, ' ')} (#{time_ago_in_words(direct_message.created_at)} ago)"
@@ -170,7 +171,8 @@ module T
170
171
  array = direct_messages.map do |direct_message|
171
172
  [direct_message.id, ls_formatted_time(direct_message), "@#{direct_message.recipient.screen_name}", HTMLEntities.new.decode(direct_message.text).gsub(/\n+/, ' ')]
172
173
  end
173
- print_table_with_headings(array, DIRECT_MESSAGE_HEADINGS)
174
+ format = options['format'] || DIRECT_MESSAGE_HEADINGS.size.times.map{"%s"}
175
+ print_table_with_headings(array, DIRECT_MESSAGE_HEADINGS, format)
174
176
  else
175
177
  direct_messages.each do |direct_message|
176
178
  say "#{direct_message.recipient.screen_name.rjust(MAX_SCREEN_NAME_SIZE)}: #{direct_message.text.gsub(/\n+/, ' ')} (#{time_ago_in_words(direct_message.created_at)} ago)"
@@ -179,7 +181,7 @@ module T
179
181
  end
180
182
  map %w(directmessagessent sent_messages sentmessages sms) => :direct_messages_sent
181
183
 
182
- desc "disciples [USER]", "Returns the list of people who follow you but you don't follow back."
184
+ desc "groupies [USER]", "Returns the list of people who follow you but you don't follow back."
183
185
  method_option "csv", :aliases => "-c", :type => :boolean, :default => false, :desc => "Output in CSV format."
184
186
  method_option "favorites", :aliases => "-v", :type => :boolean, :default => false, :desc => "Sort by number of favorites."
185
187
  method_option "followers", :aliases => "-f", :type => :boolean, :default => false, :desc => "Sort by number of followers."
@@ -191,7 +193,7 @@ module T
191
193
  method_option "reverse", :aliases => "-r", :type => :boolean, :default => false, :desc => "Reverse the order of the sort."
192
194
  method_option "tweets", :aliases => "-t", :type => :boolean, :default => false, :desc => "Sort by number of Tweets."
193
195
  method_option "unsorted", :aliases => "-u", :type => :boolean, :default => false, :desc => "Output is not sorted."
194
- def disciples(user=nil)
196
+ def groupies(user=nil)
195
197
  if user
196
198
  user = if options['id']
197
199
  user.to_i
@@ -213,6 +215,7 @@ module T
213
215
  end.flatten
214
216
  print_users(users)
215
217
  end
218
+ map %w(disciples) => :groupies
216
219
 
217
220
  desc "dm USER MESSAGE", "Sends that person a Direct Message."
218
221
  method_option "id", :aliases => "-i", :type => "boolean", :default => false, :desc => "Specify user via ID instead of screen name."
@@ -288,10 +291,10 @@ module T
288
291
  desc "favorite STATUS_ID [STATUS_ID...]", "Marks Tweets as favorites."
289
292
  def favorite(status_id, *status_ids)
290
293
  status_ids.unshift(status_id)
291
- status_ids.map!(&:strip_commas)
294
+ status_ids.map!(&:to_i)
292
295
  favorites = status_ids.threaded_map do |status_id|
293
296
  retryable(:tries => 3, :on => Twitter::Error::ServerError, :sleep => 0) do
294
- client.favorite(status_id.to_i)
297
+ client.favorite(status_id)
295
298
  end
296
299
  end
297
300
  number = favorites.length
@@ -546,7 +549,7 @@ module T
546
549
  array = []
547
550
  array << ["Hourly limit", number_with_delimiter(rate_limit_status.hourly_limit)]
548
551
  array << ["Remaining hits", number_with_delimiter(rate_limit_status.remaining_hits)]
549
- array << ["Reset time", ls_formatted_time(rate_limit_status, :reset_time)]
552
+ array << ["Reset time", "#{ls_formatted_time(rate_limit_status, :reset_time)} (#{distance_of_time_in_words_to_now(rate_limit_status.reset_time)} from now)"]
550
553
  print_table(array)
551
554
  end
552
555
  end
@@ -556,7 +559,6 @@ module T
556
559
  method_option "all", :aliases => "-a", :type => "boolean", :default => false, :desc => "Reply to all users mentioned in the Tweet."
557
560
  method_option "location", :aliases => "-l", :type => :boolean, :default => false
558
561
  def reply(status_id, message)
559
- status_id = status_id.strip_commas
560
562
  status = client.status(status_id.to_i, :include_my_retweet => false)
561
563
  users = Array(status.from_user)
562
564
  if options['all']
@@ -564,7 +566,7 @@ module T
564
566
  major, minor, patch = RUBY_VERSION.split('.')
565
567
  $KCODE='u' if major.to_i == 1 && minor.to_i < 9
566
568
  require 'twitter-text'
567
- users += Twitter::Extractor.extract_mentioned_screen_names(status.text)
569
+ users += Twitter::Extractor.extract_mentioned_screen_names(status.full_text)
568
570
  users.uniq!
569
571
  end
570
572
  users.map!(&:prepend_at)
@@ -598,16 +600,16 @@ module T
598
600
  desc "retweet STATUS_ID [STATUS_ID...]", "Sends Tweets to your followers."
599
601
  def retweet(status_id, *status_ids)
600
602
  status_ids.unshift(status_id)
601
- status_ids.map!(&:strip_commas)
603
+ status_ids.map!(&:to_i)
602
604
  retweets = status_ids.threaded_map do |status_id|
603
605
  retryable(:tries => 3, :on => Twitter::Error::ServerError, :sleep => 0) do
604
- client.retweet(status_id.to_i, :trim_user => true)
606
+ client.retweet(status_id, :trim_user => true)
605
607
  end
606
608
  end
607
609
  number = retweets.length
608
610
  say "@#{@rcfile.active_profile[0]} retweeted #{number} #{number == 1 ? 'tweet' : 'tweets'}."
609
611
  say
610
- say "Run `#{File.basename($0)} delete status #{status_ids.join(' ')}` to undo."
612
+ say "Run `#{File.basename($0)} delete status #{retweets.map(&:id).join(' ')}` to undo."
611
613
  end
612
614
  map %w(rt) => :retweet
613
615
 
@@ -641,7 +643,6 @@ module T
641
643
  desc "status STATUS_ID", "Retrieves detailed information about a Tweet."
642
644
  method_option "csv", :aliases => "-c", :type => :boolean, :default => false, :desc => "Output in CSV format."
643
645
  def status(status_id)
644
- status_id = status_id.strip_commas
645
646
  status = client.status(status_id.to_i, :include_my_retweet => false)
646
647
  location = if status.place
647
648
  if status.place.name && status.place.attributes && status.place.attributes['street_address'] && status.place.attributes['locality'] && status.place.attributes['region'] && status.place.country
@@ -662,14 +663,13 @@ module T
662
663
  end
663
664
  if options['csv']
664
665
  say ["ID", "Text", "Screen name", "Posted at", "Location", "Retweets", "Source", "URL"].to_csv
665
- say [status.id, HTMLEntities.new.decode(status.text), status.from_user, csv_formatted_time(status), location, status.retweet_count, strip_tags(status.source), "https://twitter.com/#{status.from_user}/status/#{status.id}"].to_csv
666
+ say [status.id, HTMLEntities.new.decode(status.full_text), status.from_user, csv_formatted_time(status), location, status.retweet_count, strip_tags(status.source), "https://twitter.com/#{status.from_user}/status/#{status.id}"].to_csv
666
667
  else
667
668
  array = []
668
669
  array << ["ID", status.id.to_s]
669
- array << ["Text", HTMLEntities.new.decode(status.text).gsub(/\n+/, ' ')]
670
+ array << ["Text", HTMLEntities.new.decode(status.full_text).gsub(/\n+/, ' ')]
670
671
  array << ["Screen name", "@#{status.from_user}"]
671
- posted_at = status.created_at > 6.months.ago ? status.created_at.strftime("%b %e %H:%M") : status.created_at.strftime("%b %e %Y")
672
- array << ["Posted at", posted_at]
672
+ array << ["Posted at", "#{ls_formatted_time(status)} (#{time_ago_in_words(status.created_at)} ago)"]
673
673
  array << ["Location", location] unless location.nil?
674
674
  array << ["Retweets", number_with_delimiter(status.retweet_count)]
675
675
  array << ["Source", strip_tags(status.source)]
@@ -757,7 +757,8 @@ module T
757
757
  array = places.map do |place|
758
758
  [place.woeid, place.parent_id, place.place_type, place.name, place.country]
759
759
  end
760
- print_table_with_headings(array, TREND_HEADINGS)
760
+ format = options['format'] || TREND_HEADINGS.size.times.map{"%s"}
761
+ print_table_with_headings(array, TREND_HEADINGS, format)
761
762
  else
762
763
  print_attribute(places, :name)
763
764
  end
@@ -847,7 +848,7 @@ module T
847
848
  array << ["Location", user.location] unless user.location.nil?
848
849
  array << ["Status", user.following ? "Following" : "Not following"]
849
850
  array << ["Last update", "#{HTMLEntities.new.decode(user.status.text).gsub(/\n+/, ' ')} (#{time_ago_in_words(user.status.created_at)} ago)"] unless user.status.nil?
850
- array << ["Since", ls_formatted_time(user)]
851
+ array << ["Since", "#{ls_formatted_time(user)} (#{time_ago_in_words(user.created_at)} ago)"]
851
852
  array << ["Tweets", number_with_delimiter(user.statuses_count)]
852
853
  array << ["Favorites", number_with_delimiter(user.favourites_count)]
853
854
  array << ["Listed", number_with_delimiter(user.listed_count)]
@@ -0,0 +1,15 @@
1
+ module Kernel
2
+
3
+ def Bignum(arg)
4
+ Integer(arg)
5
+ end
6
+
7
+ def Fixnum(arg)
8
+ Integer(arg)
9
+ end
10
+
11
+ def NilClass(arg)
12
+ nil
13
+ end
14
+
15
+ end
@@ -8,8 +8,10 @@ class String
8
8
  self.tr('@', '')
9
9
  end
10
10
 
11
- def strip_commas
12
- self.tr(',', '')
11
+ alias_method :old_to_i, :to_i
12
+
13
+ def to_i(base=10)
14
+ self.tr(',', '').old_to_i(base)
13
15
  end
14
16
 
15
17
  end
data/lib/t/delete.rb CHANGED
@@ -41,13 +41,13 @@ module T
41
41
  method_option "force", :aliases => "-f", :type => :boolean, :default => false
42
42
  def dm(direct_message_id, *direct_message_ids)
43
43
  direct_message_ids.unshift(direct_message_id)
44
- direct_message_ids.map!(&:strip_commas)
44
+ direct_message_ids.map!(&:to_i)
45
45
  direct_message_ids.each do |direct_message_id|
46
46
  unless options['force']
47
- direct_message = client.direct_message(direct_message_id.to_i)
47
+ direct_message = client.direct_message(direct_message_id)
48
48
  return unless yes? "Are you sure you want to permanently delete the direct message to @#{direct_message.recipient.screen_name}: \"#{direct_message.text}\"? [y/N]"
49
49
  end
50
- direct_message = client.direct_message_destroy(direct_message_id.to_i)
50
+ direct_message = client.direct_message_destroy(direct_message_id)
51
51
  say "@#{@rcfile.active_profile[0]} deleted the direct message sent to @#{direct_message.recipient.screen_name}: \"#{direct_message.text}\""
52
52
  end
53
53
  end
@@ -57,14 +57,14 @@ module T
57
57
  method_option "force", :aliases => "-f", :type => :boolean, :default => false
58
58
  def favorite(status_id, *status_ids)
59
59
  status_ids.unshift(status_id)
60
- status_ids.map!(&:strip_commas)
60
+ status_ids.map!(&:to_i)
61
61
  status_ids.each do |status_id|
62
62
  unless options['force']
63
- status = client.status(status_id.to_i, :include_my_retweet => false, :trim_user => true)
64
- return unless yes? "Are you sure you want to remove @#{status.from_user}'s status: \"#{status.text}\" from your favorites? [y/N]"
63
+ status = client.status(status_id, :include_my_retweet => false, :trim_user => true)
64
+ return unless yes? "Are you sure you want to remove @#{status.from_user}'s status: \"#{status.full_text}\" from your favorites? [y/N]"
65
65
  end
66
- status = client.unfavorite(status_id.to_i)
67
- say "@#{@rcfile.active_profile[0]} unfavorited @#{status.from_user}'s status: \"#{status.text}\""
66
+ status = client.unfavorite(status_id)
67
+ say "@#{@rcfile.active_profile[0]} unfavorited @#{status.from_user}'s status: \"#{status.full_text}\""
68
68
  end
69
69
  end
70
70
  map %w(fave favourite) => :favorite
@@ -86,14 +86,14 @@ module T
86
86
  method_option "force", :aliases => "-f", :type => :boolean, :default => false
87
87
  def status(status_id, *status_ids)
88
88
  status_ids.unshift(status_id)
89
- status_ids.map!(&:strip_commas)
89
+ status_ids.map!(&:to_i)
90
90
  status_ids.each do |status_id|
91
91
  unless options['force']
92
- status = client.status(status_id.to_i, :include_my_retweet => false, :trim_user => true)
93
- return unless yes? "Are you sure you want to permanently delete @#{status.from_user}'s status: \"#{status.text}\"? [y/N]"
92
+ status = client.status(status_id, :include_my_retweet => false, :trim_user => true)
93
+ return unless yes? "Are you sure you want to permanently delete @#{status.from_user}'s status: \"#{status.full_text}\"? [y/N]"
94
94
  end
95
- status = client.status_destroy(status_id.to_i, :trim_user => true)
96
- say "@#{@rcfile.active_profile[0]} deleted the status: \"#{status.text}\""
95
+ status = client.status_destroy(status_id, :trim_user => true)
96
+ say "@#{@rcfile.active_profile[0]} deleted the status: \"#{status.full_text}\""
97
97
  end
98
98
  end
99
99
  map %w(post tweet update) => :status
@@ -0,0 +1,62 @@
1
+ require 'date'
2
+
3
+ module T
4
+ module FormatHelpers
5
+ private
6
+
7
+ # https://github.com/rails/rails/blob/bd8a970/actionpack/lib/action_view/helpers/date_helper.rb
8
+ def distance_of_time_in_words_to_now(from_time)
9
+ to_time = Time.now
10
+ seconds = (to_time - from_time).abs
11
+ case (minutes = seconds / 60)
12
+ when 0 then 'less than a minute'
13
+ when 1 then '1 minute'
14
+ when 2..44 then '%d minutes' % minutes
15
+ when 45..89 then 'about 1 hour'
16
+ when 90..1439 then 'about %d hours' % (minutes.to_f / 60.0).round
17
+ when 1440..2519 then '1 day'
18
+ when 2520..43199 then '%d days' % (minutes.to_f / 1440.0).round
19
+ when 43200..86399 then 'about 1 month'
20
+ when 86400..525599 then '%d months' % (minutes.to_f / 43200.0).round
21
+ else
22
+ fyear = from_time.year
23
+ fyear += 1 if from_time.month >= 3
24
+ tyear = to_time.year
25
+ tyear -= 1 if to_time.month < 3
26
+ leap_years = (fyear > tyear) ? 0 : (fyear..tyear).count{|x| Date.leap?(x)}
27
+ minute_offset_for_leap_year = leap_years * 1440
28
+ minutes_with_offset = minutes - minute_offset_for_leap_year
29
+ remainder = (minutes_with_offset % 525600)
30
+ distance_in_years = (minutes_with_offset / 525600)
31
+ if remainder < 131400
32
+ pluralize distance_in_years, 'about %d year'
33
+ elsif remainder < 394200
34
+ pluralize distance_in_years, 'over %d year'
35
+ else
36
+ pluralize distance_in_years + 1, 'almost %d year'
37
+ end
38
+ end
39
+ end
40
+ alias time_ago_in_words distance_of_time_in_words_to_now
41
+
42
+ def pluralize(count, word)
43
+ word += 's' if count.to_i > 1
44
+ if word.include? '%'
45
+ word % count
46
+ else
47
+ "%d #{word}" % count
48
+ end
49
+ end
50
+
51
+ def strip_tags(html)
52
+ html.gsub(/<.+?>/, '')
53
+ end
54
+
55
+ def number_with_delimiter(num)
56
+ digits = num.to_s.split(//)
57
+ groups = digits.reverse.in_groups_of(3).map {|g| g.join('') }
58
+ groups.join(',').reverse
59
+ end
60
+
61
+ end
62
+ end
data/lib/t/list.rb CHANGED
@@ -1,5 +1,5 @@
1
- require 'action_view'
2
1
  require 'active_support/core_ext/array/grouping'
2
+ require 't/format_helpers'
3
3
  require 'csv'
4
4
  # 'fastercsv' required on Ruby versions < 1.9
5
5
  require 'fastercsv' unless Array.new.respond_to?(:to_csv)
@@ -14,10 +14,10 @@ require 'thor'
14
14
 
15
15
  module T
16
16
  class List < Thor
17
- include ActionView::Helpers::DateHelper
18
17
  include T::Collectable
19
18
  include T::Printable
20
19
  include T::Requestable
20
+ include T::FormatHelpers
21
21
 
22
22
  DEFAULT_NUM_RESULTS = 20
23
23
  MAX_SCREEN_NAME_SIZE = 20
@@ -88,7 +88,7 @@ module T
88
88
  array << ["Description", list.description] unless list.description.nil?
89
89
  array << ["Slug", list.slug]
90
90
  array << ["Screen name", "@#{list.user.screen_name}"]
91
- array << ["Created at", ls_formatted_time(list)]
91
+ array << ["Created at", "#{ls_formatted_time(list)} (#{time_ago_in_words(list.created_at)} ago)"]
92
92
  array << ["Members", number_with_delimiter(list.member_count)]
93
93
  array << ["Subscribers", number_with_delimiter(list.subscriber_count)]
94
94
  array << ["Status", list.following ? "Following" : "Not following"]
@@ -97,7 +97,7 @@ module T
97
97
  print_table(array)
98
98
  end
99
99
  end
100
- map %w(detail) => :information
100
+ map %w(details) => :information
101
101
 
102
102
  desc "members [USER/]LIST", "Returns the members of a Twitter list."
103
103
  method_option "csv", :aliases => "-c", :type => :boolean, :default => false, :desc => "Output in CSV format."
data/lib/t/printable.rb CHANGED
@@ -1,47 +1,43 @@
1
- require 'action_view'
2
1
  require 'csv'
3
2
  # 'fastercsv' required on Ruby versions < 1.9
4
3
  require 'fastercsv' unless Array.new.respond_to?(:to_csv)
5
4
  require 'htmlentities'
5
+ require 't/core_ext/kernel'
6
6
  require 'thor/shell/color'
7
7
  require 'time'
8
8
 
9
9
  module T
10
10
  module Printable
11
11
  MAX_SCREEN_NAME_SIZE = 20
12
- DIRECT_MESSAGE_HEADINGS = ["ID", "Posted at", "Screen name", "Text"]
13
- LIST_HEADINGS =["ID", "Created at", "Screen name", "Slug", "Members", "Subscribers", "Mode", "Description"]
12
+ LIST_HEADINGS = ["ID", "Created at", "Screen name", "Slug", "Members", "Subscribers", "Mode", "Description"]
14
13
  STATUS_HEADINGS = ["ID", "Posted at", "Screen name", "Text"]
15
- TREND_HEADINGS = ["WOEID", "Parent ID", "Type", "Name", "Country"]
16
14
  USER_HEADINGS = ["ID", "Since", "Tweets", "Favorites", "Listed", "Following", "Followers", "Screen name", "Name"]
17
15
 
18
- include ActionView::Helpers::NumberHelper
19
-
20
- def self.included(base)
21
-
22
16
  private
23
17
 
24
18
  def build_long_list(list)
25
- [list.id, ls_formatted_time(list), "@#{list.user.screen_name}", list.slug, number_with_delimiter(list.member_count), number_with_delimiter(list.subscriber_count), list.mode, list.description]
19
+ [list.id, ls_formatted_time(list), "@#{list.user.screen_name}", list.slug, list.member_count, list.subscriber_count, list.mode, list.description]
26
20
  end
27
21
 
28
22
  def build_long_status(status)
29
- [status.id, ls_formatted_time(status), "@#{status.from_user}", HTMLEntities.new.decode(status.text).gsub(/\n+/, ' ')]
23
+ [status.id, ls_formatted_time(status), "@#{status.from_user}", HTMLEntities.new.decode(status.full_text).gsub(/\n+/, ' ')]
30
24
  end
31
25
 
32
26
  def build_long_user(user)
33
- [user.id, ls_formatted_time(user), number_with_delimiter(user.statuses_count), number_with_delimiter(user.favourites_count), number_with_delimiter(user.listed_count), number_with_delimiter(user.friends_count), number_with_delimiter(user.followers_count), "@#{user.screen_name}", user.name]
27
+ [user.id, ls_formatted_time(user), user.statuses_count, user.favourites_count, user.listed_count, user.friends_count, user.followers_count, "@#{user.screen_name}", user.name]
34
28
  end
35
29
 
36
30
  def csv_formatted_time(object, key=:created_at)
37
- Time.parse(object.send(key.to_sym).to_s).utc.strftime("%Y-%m-%d %H:%M:%S %z")
31
+ time = object.send(key.to_sym)
32
+ time.utc.strftime("%Y-%m-%d %H:%M:%S %z")
38
33
  end
39
34
 
40
35
  def ls_formatted_time(object, key=:created_at)
41
- if object.send(key.to_sym) > 6.months.ago
42
- Time.parse(object.send(key.to_sym).to_s).strftime("%b %e %H:%M")
36
+ time = T.local_time object.send(key.to_sym)
37
+ if time > 6.months.ago
38
+ time.strftime("%b %e %H:%M")
43
39
  else
44
- Time.parse(object.send(key.to_sym).to_s).strftime("%b %e %Y")
40
+ time.strftime("%b %e %Y")
45
41
  end
46
42
  end
47
43
 
@@ -50,7 +46,7 @@ module T
50
46
  end
51
47
 
52
48
  def print_csv_status(status)
53
- say [status.id, csv_formatted_time(status), status.from_user, HTMLEntities.new.decode(status.text)].to_csv
49
+ say [status.id, csv_formatted_time(status), status.from_user, HTMLEntities.new.decode(status.full_text)].to_csv
54
50
  end
55
51
 
56
52
  def print_csv_user(user)
@@ -78,7 +74,8 @@ module T
78
74
  array = lists.map do |list|
79
75
  build_long_list(list)
80
76
  end
81
- print_table_with_headings(array, LIST_HEADINGS)
77
+ format = options['format'] || LIST_HEADINGS.size.times.map{"%s"}
78
+ print_table_with_headings(array, LIST_HEADINGS, format)
82
79
  else
83
80
  print_attribute(lists, :full_name)
84
81
  end
@@ -94,9 +91,15 @@ module T
94
91
  end
95
92
  end
96
93
 
97
- def print_table_with_headings(array, headings)
94
+ def print_table_with_headings(array, headings, format)
95
+ return if array.flatten.empty?
98
96
  if STDOUT.tty?
99
- array.unshift(headings) unless array.flatten.empty?
97
+ array.unshift(headings)
98
+ array.map! do |row|
99
+ row.each_with_index.map do |element, index|
100
+ Kernel.send(element.class.name.to_sym, format[index] % element)
101
+ end
102
+ end
100
103
  print_table(array, :truncate => true)
101
104
  else
102
105
  print_table(array)
@@ -109,7 +112,7 @@ module T
109
112
  else
110
113
  say(" @#{status.from_user}")
111
114
  end
112
- print_wrapped(HTMLEntities.new.decode(status.text), :indent => 3)
115
+ print_wrapped(HTMLEntities.new.decode(status.full_text), :indent => 3)
113
116
  say
114
117
  end
115
118
 
@@ -124,7 +127,8 @@ module T
124
127
  array = statuses.map do |status|
125
128
  build_long_status(status)
126
129
  end
127
- print_table_with_headings(array, STATUS_HEADINGS)
130
+ format = options['format'] || STATUS_HEADINGS.size.times.map{"%s"}
131
+ print_table_with_headings(array, STATUS_HEADINGS, format)
128
132
  else
129
133
  statuses.each do |status|
130
134
  print_status(status)
@@ -157,13 +161,12 @@ module T
157
161
  array = users.map do |user|
158
162
  build_long_user(user)
159
163
  end
160
- print_table_with_headings(array, USER_HEADINGS)
164
+ format = options['format'] || USER_HEADINGS.size.times.map{"%s"}
165
+ print_table_with_headings(array, USER_HEADINGS, format)
161
166
  else
162
167
  print_attribute(users, :screen_name)
163
168
  end
164
169
  end
165
170
 
166
- end
167
-
168
171
  end
169
172
  end
data/lib/t/rcfile.rb CHANGED
@@ -86,7 +86,7 @@ private
86
86
  end
87
87
 
88
88
  def write
89
- File.open(@path, File::RDWR|File::CREAT, 0600) do |rcfile|
89
+ File.open(@path, File::RDWR|File::TRUNC|File::CREAT, 0600) do |rcfile|
90
90
  rcfile.write @data.to_yaml
91
91
  end
92
92
  end
data/lib/t/requestable.rb CHANGED
@@ -5,8 +5,6 @@ module T
5
5
  DEFAULT_HOST = 'api.twitter.com'
6
6
  DEFAULT_PROTOCOL = 'https'
7
7
 
8
- def self.included(base)
9
-
10
8
  private
11
9
 
12
10
  def base_url
@@ -33,7 +31,5 @@ module T
33
31
  options['no-ssl'] ? 'http' : DEFAULT_PROTOCOL
34
32
  end
35
33
 
36
- end
37
-
38
34
  end
39
35
  end