t 0.9.6 → 0.9.7
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 +29 -10
- data/bin/t +4 -25
- data/lib/t/cli.rb +24 -23
- data/lib/t/core_ext/kernel.rb +15 -0
- data/lib/t/core_ext/string.rb +4 -2
- data/lib/t/delete.rb +13 -13
- data/lib/t/format_helpers.rb +62 -0
- data/lib/t/list.rb +4 -4
- data/lib/t/printable.rb +27 -24
- data/lib/t/rcfile.rb +1 -1
- data/lib/t/requestable.rb +0 -4
- data/lib/t/search.rb +10 -11
- data/lib/t/stream.rb +57 -21
- data/lib/t/version.rb +1 -1
- data/lib/t.rb +22 -3
- data/spec/cli_spec.rb +53 -45
- data/spec/fixtures/501_ids.json +509 -1
- data/spec/fixtures/501_users_list.json +17543 -1
- data/spec/fixtures/direct_message.json +80 -1
- data/spec/fixtures/direct_messages.json +802 -1
- data/spec/fixtures/empty_cursor.json +7 -1
- data/spec/fixtures/favorites.json +1099 -1
- data/spec/fixtures/followers_ids.json +10 -1
- data/spec/fixtures/friends_ids.json +9 -1
- data/spec/fixtures/gem.json +61 -1
- data/spec/fixtures/list.json +54 -1
- data/spec/fixtures/lists.json +116 -1
- data/spec/fixtures/locations.json +57 -1
- data/spec/fixtures/not_found.json +4 -1
- data/spec/fixtures/rate_limit_status.json +6 -1
- data/spec/fixtures/recommendations.json +364 -1
- data/spec/fixtures/retweet.json +112 -1
- data/spec/fixtures/search.json +346 -1
- data/spec/fixtures/settings.json +30 -1
- data/spec/fixtures/sferik.json +76 -1
- data/spec/fixtures/status.json +109 -1
- data/spec/fixtures/status_no_attributes.json +104 -1
- data/spec/fixtures/status_no_country.json +102 -1
- data/spec/fixtures/status_no_full_name.json +101 -1
- data/spec/fixtures/status_no_locality.json +107 -1
- data/spec/fixtures/status_no_street_address.json +108 -1
- data/spec/fixtures/statuses.json +1105 -1
- data/spec/fixtures/trends.json +35 -1
- data/spec/fixtures/users.json +92 -1
- data/spec/fixtures/users_list.json +98 -1
- data/spec/list_spec.rb +13 -5
- data/spec/search_spec.rb +110 -102
- data/t.gemspec +1 -2
- metadata +6 -32
data/README.md
CHANGED
@@ -1,4 +1,9 @@
|
|
1
|
+
# [][icon]
|
2
|
+
|
3
|
+
[icon]: https://github.com/sferik/t/raw/master/icon/t.png
|
4
|
+
|
1
5
|
# Twitter CLI [][travis] [][gemnasium] [][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
|
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
|
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 |
|
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
|

|
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
|
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
|
24
|
-
rescue Twitter::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
|
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
|
-
|
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
|
-
|
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 "
|
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
|
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!(&:
|
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
|
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.
|
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!(&:
|
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
|
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 #{
|
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.
|
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.
|
670
|
+
array << ["Text", HTMLEntities.new.decode(status.full_text).gsub(/\n+/, ' ')]
|
670
671
|
array << ["Screen name", "@#{status.from_user}"]
|
671
|
-
|
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
|
-
|
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)]
|
data/lib/t/core_ext/string.rb
CHANGED
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!(&:
|
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
|
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
|
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!(&:
|
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
|
64
|
-
return unless yes? "Are you sure you want to remove @#{status.from_user}'s status: \"#{status.
|
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
|
67
|
-
say "@#{@rcfile.active_profile[0]} unfavorited @#{status.from_user}'s status: \"#{status.
|
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!(&:
|
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
|
93
|
-
return unless yes? "Are you sure you want to permanently delete @#{status.from_user}'s status: \"#{status.
|
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
|
96
|
-
say "@#{@rcfile.active_profile[0]} deleted the status: \"#{status.
|
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(
|
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
|
-
|
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,
|
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.
|
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),
|
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
|
-
|
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
|
-
|
42
|
-
|
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
|
-
|
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.
|
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
|
-
|
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)
|
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.
|
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
|
-
|
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
|
-
|
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
data/lib/t/requestable.rb
CHANGED