t 0.3.1 → 0.4.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/.rspec CHANGED
@@ -1,2 +1,2 @@
1
- --format=documentation
2
1
  --color
2
+ --order random
@@ -1,8 +1,8 @@
1
- before_install: "gem update --system"
1
+ language: ruby
2
2
  rvm:
3
3
  - 1.8.7
4
4
  - 1.9.2
5
5
  - 1.9.3
6
- - jruby
7
- - rbx
8
- - ree
6
+ - jruby-18mode
7
+ - rbx-18mode
8
+ - rbx-19mode
data/README.md CHANGED
@@ -1,7 +1,8 @@
1
1
  # 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]
2
- A command-line interface for Twitter, powered by the [twitter gem][gem]. The
3
- CLI attempts to mimic the [Twitter SMS commands][sms] wherever possible,
4
- however it offers more commands than are available via SMS.
2
+ ### A command-line power tool for Twitter.
3
+
4
+ The CLI attempts to mimic the [Twitter SMS commands][sms] wherever possible,
5
+ however it offers many more commands than are available via SMS.
5
6
 
6
7
  [travis]: http://travis-ci.org/sferik/t
7
8
  [gemnasium]: https://gemnasium.com/sferik/t
@@ -156,15 +157,27 @@ type `t help TASK` to get help for a specific command.
156
157
 
157
158
  ### <a name="search-all"></a>Retrieve the 20 most recent Tweets that match a specified query
158
159
 
159
- t search all twitter
160
+ t search all "query"
161
+
162
+ ### <a name="search-retweets"></a>Returns Tweets you've favorited that mach a specified query
163
+
164
+ t search favorites "query"
165
+
166
+ ### <a name="search-mentions"></a>Returns Tweets mentioning you that mach a specified query
167
+
168
+ t search mentions "query"
169
+
170
+ ### <a name="search-retweets"></a>Returns Tweets you've retweeted that mach a specified query
171
+
172
+ t search retweets "query"
160
173
 
161
174
  ### <a name="search-timeline"></a>Retrieve Tweets in your timeline that match a specified query
162
175
 
163
- t search timeline twitter
176
+ t search timeline "query"
164
177
 
165
178
  ### <a name="search-user"></a>Retrieve Tweets in a user's timeline that match a specified query
166
179
 
167
- t search user sferik twitter
180
+ t search user sferik "query"
168
181
 
169
182
  ## <a name="history"></a>History
170
183
  ![History](http://twitter.rubyforge.org/images/terminal_output.png "History")
@@ -229,11 +242,9 @@ implementations:
229
242
  * Ruby 1.9.3
230
243
  * [JRuby][]
231
244
  * [Rubinius][]
232
- * [Ruby Enterprise Edition][ree]
233
245
 
234
246
  [jruby]: http://www.jruby.org/
235
247
  [rubinius]: http://rubini.us/
236
- [ree]: http://www.rubyenterpriseedition.com/
237
248
 
238
249
  If something doesn't work on one of these interpreters, it should be considered
239
250
  a bug.
@@ -86,10 +86,10 @@ module T
86
86
  user = client.block(screen_name, :include_entities => false)
87
87
  say "@#{@rcfile.default_profile[0]} blocked @#{user.screen_name}"
88
88
  say
89
- say "Run `#{$0} delete block #{user.screen_name}` to unblock."
89
+ say "Run `#{File.basename($0)} delete block #{user.screen_name}` to unblock."
90
90
  end
91
91
 
92
- desc "direct_messages", "Returns the 20 most recent Direct Messages sent to you."
92
+ desc "direct_messages", "Returns the #{DEFAULT_NUM_RESULTS} most recent Direct Messages sent to you."
93
93
  method_option :number, :aliases => "-n", :type => :numeric, :default => DEFAULT_NUM_RESULTS
94
94
  def direct_messages
95
95
  defaults = {:include_entities => false}
@@ -117,7 +117,7 @@ module T
117
117
  client.favorite(user.status.id, :include_entities => false)
118
118
  say "@#{@rcfile.default_profile[0]} favorited @#{user.screen_name}'s latest status: \"#{user.status.text}\""
119
119
  say
120
- say "Run `#{$0} delete favorite` to unfavorite."
120
+ say "Run `#{File.basename($0)} delete favorite` to unfavorite."
121
121
  else
122
122
  raise Thor::Error, "Tweet not found"
123
123
  end
@@ -178,7 +178,7 @@ module T
178
178
  status = client.update("@#{user.screen_name} #{message}", defaults)
179
179
  say "Reply created by @#{@rcfile.default_profile[0]} to @#{user.screen_name} (#{time_ago_in_words(status.created_at)} ago)"
180
180
  say
181
- say "Run `#{$0} delete status` to delete."
181
+ say "Run `#{File.basename($0)} delete status` to delete."
182
182
  end
183
183
 
184
184
  desc "retweet SCREEN_NAME", "Sends that user's latest Tweet to your followers."
@@ -189,7 +189,7 @@ module T
189
189
  client.retweet(user.status.id, :include_entities => false, :trim_user => true)
190
190
  say "@#{@rcfile.default_profile[0]} retweeted @#{user.screen_name}'s latest status: \"#{user.status.text}\""
191
191
  say
192
- say "Run `#{$0} delete status` to undo."
192
+ say "Run `#{File.basename($0)} delete status` to undo."
193
193
  else
194
194
  raise Thor::Error, "Tweet not found"
195
195
  end
@@ -202,7 +202,23 @@ module T
202
202
  end
203
203
  map %w(rt) => :retweet
204
204
 
205
- desc "sent_messages", "Returns the 20 most recent Direct Messages sent to you."
205
+ desc "retweets [SCREEN_NAME]", "Returns the #{DEFAULT_NUM_RESULTS} most recent Retweets by a user."
206
+ method_option :number, :aliases => "-n", :type => :numeric, :default => DEFAULT_NUM_RESULTS
207
+ method_option :reverse, :aliases => "-r", :type => :boolean, :default => false
208
+ def retweets(screen_name=nil)
209
+ screen_name = screen_name.strip_at if screen_name
210
+ defaults = {:include_entities => false}
211
+ defaults.merge!(:count => options['number']) if options['number']
212
+ timeline = client.retweeted_by(screen_name, defaults)
213
+ timeline.reverse! if options['reverse']
214
+ page unless T.env.test?
215
+ timeline.each do |status|
216
+ say "#{status.user.screen_name.rjust(MAX_SCREEN_NAME_SIZE)}: #{status.text} (#{time_ago_in_words(status.created_at)} ago)"
217
+ end
218
+ end
219
+ map %w(rts) => :retweets
220
+
221
+ desc "sent_messages", "Returns the #{DEFAULT_NUM_RESULTS} most recent Direct Messages sent to you."
206
222
  method_option :number, :aliases => "-n", :type => :numeric, :default => DEFAULT_NUM_RESULTS
207
223
  def sent_messages
208
224
  defaults = {:include_entities => false}
@@ -224,7 +240,7 @@ module T
224
240
  say "Favorites: #{number_with_delimiter(user.favorites_count)}"
225
241
  say "Listed: #{number_with_delimiter(user.listed_count)}"
226
242
  say
227
- say "Run `#{$0} whois #{user.screen_name}` to view profile."
243
+ say "Run `#{File.basename($0)} whois #{user.screen_name}` to view profile."
228
244
  end
229
245
 
230
246
  desc "status MESSAGE", "Post a Tweet."
@@ -235,7 +251,7 @@ module T
235
251
  status = client.update(message, defaults)
236
252
  say "Tweet created by @#{@rcfile.default_profile[0]} (#{time_ago_in_words(status.created_at)} ago)"
237
253
  say
238
- say "Run `#{$0} delete status` to delete."
254
+ say "Run `#{File.basename($0)} delete status` to delete."
239
255
  end
240
256
  map %w(post tweet update) => :status
241
257
 
@@ -245,9 +261,9 @@ module T
245
261
  if recommendation
246
262
  say "Try following @#{recommendation.screen_name}."
247
263
  say
248
- say "Run `#{$0} follow #{recommendation.screen_name}` to follow."
249
- say "Run `#{$0} whois #{recommendation.screen_name}` for profile."
250
- say "Run `#{$0} suggest` for another recommendation."
264
+ say "Run `#{File.basename($0)} follow #{recommendation.screen_name}` to follow."
265
+ say "Run `#{File.basename($0)} whois #{recommendation.screen_name}` for profile."
266
+ say "Run `#{File.basename($0)} suggest` for another recommendation."
251
267
  end
252
268
  end
253
269
 
@@ -288,29 +304,29 @@ module T
288
304
  say "web: #{user.url}"
289
305
  end
290
306
 
307
+ require 't/cli/delete'
291
308
  desc "delete SUBCOMMAND ...ARGS", "Delete Tweets, Direct Messages, etc."
292
309
  method_option :force, :aliases => "-f", :type => :boolean
293
- require 't/cli/delete'
294
310
  subcommand 'delete', CLI::Delete
295
311
 
296
- desc "follow SUBCOMMAND ...ARGS", "Follow users."
297
312
  require 't/cli/follow'
313
+ desc "follow SUBCOMMAND ...ARGS", "Follow users."
298
314
  subcommand 'follow', CLI::Follow
299
315
 
300
- desc "list SUBCOMMAND ...ARGS", "Do various things with lists."
301
316
  require 't/cli/list'
317
+ desc "list SUBCOMMAND ...ARGS", "Do various things with lists."
302
318
  subcommand 'list', CLI::List
303
319
 
304
- desc "search SUBCOMMAND ...ARGS", "Search through Tweets."
305
320
  require 't/cli/search'
321
+ desc "search SUBCOMMAND ...ARGS", "Search through Tweets."
306
322
  subcommand 'search', CLI::Search
307
323
 
308
- desc "set SUBCOMMAND ...ARGS", "Change various account settings."
309
324
  require 't/cli/set'
325
+ desc "set SUBCOMMAND ...ARGS", "Change various account settings."
310
326
  subcommand 'set', CLI::Set
311
327
 
312
- desc "unfollow SUBCOMMAND ...ARGS", "Unfollow users."
313
328
  require 't/cli/unfollow'
329
+ desc "unfollow SUBCOMMAND ...ARGS", "Unfollow users."
314
330
  subcommand 'unfollow', CLI::Unfollow
315
331
 
316
332
  private
@@ -1,13 +1,12 @@
1
1
  require 't/core_ext/string'
2
2
  require 't/rcfile'
3
+ require 't/requestable'
3
4
  require 'thor'
4
- require 'twitter'
5
5
 
6
6
  module T
7
7
  class CLI
8
8
  class Delete < Thor
9
- DEFAULT_HOST = 'api.twitter.com'
10
- DEFAULT_PROTOCOL = 'https'
9
+ include T::Requestable
11
10
 
12
11
  check_unknown_options!
13
12
 
@@ -22,7 +21,7 @@ module T
22
21
  user = client.unblock(screen_name, :include_entities => false)
23
22
  say "@#{@rcfile.default_profile[0]} unblocked @#{user.screen_name}."
24
23
  say
25
- say "Run `#{$0} block #{user.screen_name}` to block."
24
+ say "Run `#{File.basename($0)} block #{user.screen_name}` to block."
26
25
  end
27
26
 
28
27
  desc "dm", "Delete the last Direct Message sent."
@@ -50,7 +49,7 @@ module T
50
49
  client.unfavorite(status.id, :include_entities => false)
51
50
  say "@#{@rcfile.default_profile[0]} unfavorited @#{status.user.screen_name}'s latest status: \"#{status.text}\""
52
51
  say
53
- say "Run `#{$0} favorite #{status.user.screen_name}` to favorite."
52
+ say "Run `#{File.basename($0)} favorite #{status.user.screen_name}` to favorite."
54
53
  else
55
54
  raise Thor::Error, "Tweet not found"
56
55
  end
@@ -81,32 +80,6 @@ module T
81
80
  end
82
81
  map %w(post tweet update) => :status
83
82
 
84
- private
85
-
86
- def base_url
87
- "#{protocol}://#{host}"
88
- end
89
-
90
- def client
91
- return @client if @client
92
- @rcfile.path = parent_options['profile'] if parent_options['profile']
93
- @client = Twitter::Client.new(
94
- :endpoint => base_url,
95
- :consumer_key => @rcfile.default_consumer_key,
96
- :consumer_secret => @rcfile.default_consumer_secret,
97
- :oauth_token => @rcfile.default_token,
98
- :oauth_token_secret => @rcfile.default_secret
99
- )
100
- end
101
-
102
- def host
103
- parent_options['host'] || DEFAULT_HOST
104
- end
105
-
106
- def protocol
107
- parent_options['no_ssl'] ? 'http' : DEFAULT_PROTOCOL
108
- end
109
-
110
83
  end
111
84
  end
112
85
  end
@@ -3,16 +3,14 @@ require 't/core_ext/enumerable'
3
3
  require 't/core_ext/string'
4
4
  require 't/collectable'
5
5
  require 't/rcfile'
6
+ require 't/requestable'
6
7
  require 'thor'
7
- require 'twitter'
8
8
 
9
9
  module T
10
10
  class CLI
11
11
  class Follow < Thor
12
12
  include T::Collectable
13
-
14
- DEFAULT_HOST = 'api.twitter.com'
15
- DEFAULT_PROTOCOL = 'https'
13
+ include T::Requestable
16
14
 
17
15
  check_unknown_options!
18
16
 
@@ -40,7 +38,7 @@ module T
40
38
  end
41
39
  say "@#{@rcfile.default_profile[0]} is now following #{number} more #{number == 1 ? 'user' : 'users'}."
42
40
  say
43
- say "Run `#{$0} unfollow all followers` to stop."
41
+ say "Run `#{File.basename($0)} unfollow all followers` to stop."
44
42
  end
45
43
 
46
44
  desc "listed LIST_NAME", "Follow all members of a list."
@@ -58,7 +56,7 @@ module T
58
56
  end
59
57
  say "@#{@rcfile.default_profile[0]} is now following #{number} more #{number == 1 ? 'user' : 'users'}."
60
58
  say
61
- say "Run `#{$0} unfollow all listed #{list_name}` to stop."
59
+ say "Run `#{File.basename($0)} unfollow all listed #{list_name}` to stop."
62
60
  end
63
61
 
64
62
  desc "users SCREEN_NAME [SCREEN_NAME...]", "Allows you to start following users."
@@ -72,33 +70,7 @@ module T
72
70
  number = screen_names.length
73
71
  say "@#{@rcfile.default_profile[0]} is now following #{number} more #{number == 1 ? 'user' : 'users'}."
74
72
  say
75
- say "Run `#{$0} unfollow users #{screen_names.join(' ')}` to stop."
76
- end
77
-
78
- private
79
-
80
- def base_url
81
- "#{protocol}://#{host}"
82
- end
83
-
84
- def client
85
- return @client if @client
86
- @rcfile.path = parent_options['profile'] if parent_options['profile']
87
- @client = Twitter::Client.new(
88
- :endpoint => base_url,
89
- :consumer_key => @rcfile.default_consumer_key,
90
- :consumer_secret => @rcfile.default_consumer_secret,
91
- :oauth_token => @rcfile.default_token,
92
- :oauth_token_secret => @rcfile.default_secret
93
- )
94
- end
95
-
96
- def host
97
- parent_options['host'] || DEFAULT_HOST
98
- end
99
-
100
- def protocol
101
- parent_options['no_ssl'] ? 'http' : DEFAULT_PROTOCOL
73
+ say "Run `#{File.basename($0)} unfollow users #{screen_names.join(' ')}` to stop."
102
74
  end
103
75
 
104
76
  end
@@ -1,17 +1,16 @@
1
1
  require 'action_view'
2
2
  require 'pager'
3
3
  require 't/rcfile'
4
+ require 't/requestable'
4
5
  require 'thor'
5
- require 'twitter'
6
6
 
7
7
  module T
8
8
  class CLI
9
9
  class List < Thor
10
10
  include ActionView::Helpers::DateHelper
11
11
  include Pager
12
+ include T::Requestable
12
13
 
13
- DEFAULT_HOST = 'api.twitter.com'
14
- DEFAULT_PROTOCOL = 'https'
15
14
  DEFAULT_NUM_RESULTS = 20
16
15
  MAX_SCREEN_NAME_SIZE = 20
17
16
 
@@ -54,32 +53,6 @@ module T
54
53
  require 't/cli/list/remove'
55
54
  subcommand 'remove', CLI::List::Remove
56
55
 
57
- private
58
-
59
- def base_url
60
- "#{protocol}://#{host}"
61
- end
62
-
63
- def client
64
- return @client if @client
65
- @rcfile.path = parent_options['profile'] if parent_options['profile']
66
- @client = Twitter::Client.new(
67
- :endpoint => base_url,
68
- :consumer_key => @rcfile.default_consumer_key,
69
- :consumer_secret => @rcfile.default_consumer_secret,
70
- :oauth_token => @rcfile.default_token,
71
- :oauth_token_secret => @rcfile.default_secret
72
- )
73
- end
74
-
75
- def host
76
- parent_options['host'] || DEFAULT_HOST
77
- end
78
-
79
- def protocol
80
- parent_options['no_ssl'] ? 'http' : DEFAULT_PROTOCOL
81
- end
82
-
83
56
  end
84
57
  end
85
58
  end
@@ -1,19 +1,21 @@
1
1
  require 'active_support/core_ext/array/grouping'
2
+ require 'retryable'
3
+ require 't/core_ext/enumerable'
2
4
  require 't/core_ext/string'
3
5
  require 't/collectable'
4
6
  require 't/rcfile'
7
+ require 't/requestable'
5
8
  require 'thor'
6
- require 'twitter'
7
9
 
8
10
  module T
9
11
  class CLI
10
12
  class List
11
13
  class Add < Thor
12
14
  include T::Collectable
15
+ include T::Requestable
13
16
 
14
- DEFAULT_HOST = 'api.twitter.com'
15
- DEFAULT_PROTOCOL = 'https'
16
17
  MAX_USERS_PER_LIST = 500
18
+ MAX_USERS_PER_REQUEST = 100
17
19
 
18
20
  check_unknown_options!
19
21
 
@@ -44,13 +46,15 @@ module T
44
46
  return unless yes? "Are you sure you want to add #{number} #{number == 1 ? 'friend' : 'friends'} to the list \"#{list_name}\"?"
45
47
  end
46
48
  max_members_to_add = MAX_USERS_PER_LIST - existing_list_members
47
- list_member_ids_to_add[0...max_members_to_add].in_groups_of(100, false) do |user_id_group|
48
- client.list_add_members(list_name, user_id_group)
49
+ list_member_ids_to_add[0...max_members_to_add].in_groups_of(MAX_USERS_PER_REQUEST, false).threaded_each do |user_id_group|
50
+ retryable(:tries => 3, :on => Twitter::Error::ServerError, :sleep => 0) do
51
+ client.list_add_members(list_name, user_id_group)
52
+ end
49
53
  end
50
54
  number_added = [number, max_members_to_add].min
51
55
  say "@#{@rcfile.default_profile[0]} added #{number_added} #{number_added == 1 ? 'friend' : 'friends'} to the list \"#{list_name}\"."
52
56
  say
53
- say "Run `#{$0} list remove all friends #{list_name}` to undo."
57
+ say "Run `#{File.basename($0)} list remove all friends #{list_name}` to undo."
54
58
  end
55
59
 
56
60
  desc "followers LIST_NAME", "Add all followers to a list."
@@ -75,13 +79,15 @@ module T
75
79
  return unless yes? "Are you sure you want to add #{number} #{number == 1 ? 'follower' : 'followers'} to the list \"#{list_name}\"?"
76
80
  end
77
81
  max_members_to_add = MAX_USERS_PER_LIST - existing_list_members
78
- list_member_ids_to_add[0...max_members_to_add].in_groups_of(100, false) do |user_id_group|
79
- client.list_add_members(list_name, user_id_group)
82
+ list_member_ids_to_add[0...max_members_to_add].in_groups_of(MAX_USERS_PER_REQUEST, false).threaded_each do |user_id_group|
83
+ retryable(:tries => 3, :on => Twitter::Error::ServerError, :sleep => 0) do
84
+ client.list_add_members(list_name, user_id_group)
85
+ end
80
86
  end
81
87
  number_added = [number, max_members_to_add].min
82
88
  say "@#{@rcfile.default_profile[0]} added #{number_added} #{number_added == 1 ? 'follower' : 'followers'} to the list \"#{list_name}\"."
83
89
  say
84
- say "Run `#{$0} list remove all followers #{list_name}` to undo."
90
+ say "Run `#{File.basename($0)} list remove all followers #{list_name}` to undo."
85
91
  end
86
92
 
87
93
  desc "listed FROM_LIST_NAME TO_LIST_NAME", "Add all list memebers to a list."
@@ -106,50 +112,30 @@ module T
106
112
  return unless yes? "Are you sure you want to add #{number} #{number == 1 ? 'member' : 'members'} to the list \"#{to_list_name}\"?"
107
113
  end
108
114
  max_members_to_add = MAX_USERS_PER_LIST - existing_list_members
109
- list_member_ids_to_add[0...max_members_to_add].in_groups_of(100, false) do |user_id_group|
110
- client.list_add_members(to_list_name, user_id_group)
115
+ list_member_ids_to_add[0...max_members_to_add].in_groups_of(MAX_USERS_PER_REQUEST, false).threaded_each do |user_id_group|
116
+ retryable(:tries => 3, :on => Twitter::Error::ServerError, :sleep => 0) do
117
+ client.list_add_members(to_list_name, user_id_group)
118
+ end
111
119
  end
112
120
  number_added = [number, max_members_to_add].min
113
121
  say "@#{@rcfile.default_profile[0]} added #{number_added} #{number_added == 1 ? 'member' : 'members'} to the list \"#{to_list_name}\"."
114
122
  say
115
- say "Run `#{$0} list remove all listed #{from_list_name} #{to_list_name}` to undo."
123
+ say "Run `#{File.basename($0)} list remove all listed #{from_list_name} #{to_list_name}` to undo."
116
124
  end
117
125
 
118
126
  desc "users LIST_NAME SCREEN_NAME [SCREEN_NAME...]", "Add users to a list."
119
127
  def users(list_name, screen_name, *screen_names)
120
128
  screen_names.unshift(screen_name)
121
- screen_names.map!{|screen_name| screen_name.strip_at}
122
- client.list_add_members(list_name, screen_names)
129
+ screen_names.map!(&:strip_at)
130
+ screen_names.in_groups_of(MAX_USERS_PER_REQUEST, false).threaded_each do |user_id_group|
131
+ retryable(:tries => 3, :on => Twitter::Error::ServerError, :sleep => 0) do
132
+ client.list_add_members(list_name, user_id_group)
133
+ end
134
+ end
123
135
  number = screen_names.length
124
136
  say "@#{@rcfile.default_profile[0]} added #{number} #{number == 1 ? 'user' : 'users'} to the list \"#{list_name}\"."
125
137
  say
126
- say "Run `#{$0} list remove users #{list_name} #{screen_names.join(' ')}` to undo."
127
- end
128
-
129
- private
130
-
131
- def base_url
132
- "#{protocol}://#{host}"
133
- end
134
-
135
- def client
136
- return @client if @client
137
- @rcfile.path = parent_options['profile'] if parent_options['profile']
138
- @client = Twitter::Client.new(
139
- :endpoint => base_url,
140
- :consumer_key => @rcfile.default_consumer_key,
141
- :consumer_secret => @rcfile.default_consumer_secret,
142
- :oauth_token => @rcfile.default_token,
143
- :oauth_token_secret => @rcfile.default_secret
144
- )
145
- end
146
-
147
- def host
148
- parent_options['host'] || DEFAULT_HOST
149
- end
150
-
151
- def protocol
152
- parent_options['no_ssl'] ? 'http' : DEFAULT_PROTOCOL
138
+ say "Run `#{File.basename($0)} list remove users #{list_name} #{screen_names.join(' ')}` to undo."
153
139
  end
154
140
 
155
141
  end