t 0.3.1 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
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