t 0.2.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/README.md CHANGED
@@ -66,10 +66,6 @@ type `t help TASK` to get help for a specific command.
66
66
 
67
67
  t set location "San Francisco"
68
68
 
69
- ### <a name="get"></a>Get the latest Tweet posted by a user
70
-
71
- t get sferik
72
-
73
69
  ### <a name="whois"></a>Retrieve profile information for a user
74
70
 
75
71
  t whois sferik
@@ -86,17 +82,17 @@ type `t help TASK` to get help for a specific command.
86
82
 
87
83
  t follow users sferik gem
88
84
 
89
- ### <a name="follow-all-followers"></a>Follow all followers (i.e. follow back)
85
+ ### <a name="follow-followers"></a>Follow all followers (i.e. follow back)
90
86
 
91
- t follow all followers
87
+ t follow followers
92
88
 
93
89
  ### <a name="unfollow-users"></a>Stop following users
94
90
 
95
91
  t unfollow users sferik gem
96
92
 
97
- ### <a name="unfollow-all-nonfollowers"></a>Unfollow all non-followers
93
+ ### <a name="unfollow-nonfollowers"></a>Unfollow all non-followers
98
94
 
99
- t unfollow all nonfollowers
95
+ t unfollow nonfollowers
100
96
 
101
97
  ### <a name="list-create"></a>Create a list
102
98
 
@@ -108,28 +104,36 @@ type `t help TASK` to get help for a specific command.
108
104
 
109
105
  ### <a name="list-add-friends"></a>Add all friends to a list
110
106
 
111
- t list add all friends presidents
107
+ t list add friends presidents
112
108
 
113
109
  ### <a name="list-add-followers"></a>Add all followers to a list
114
110
 
115
- t list add all followers presidents
111
+ t list add followers presidents
116
112
 
117
113
  ### <a name="list-add-followers"></a>Add all members of one list to another
118
114
 
119
- t list add all listed democrats presidents
115
+ t list add listed democrats presidents
120
116
 
121
117
  ### <a name="follow-all-listed"></a>Follow all members of a list
122
118
 
123
- t follow all listed presidents
119
+ t follow listed presidents
124
120
 
125
121
  ### <a name="unfollow-all-listed"></a>Unfollow all members of a list
126
122
 
127
- t unfollow all listed presidents
123
+ t unfollow listed presidents
128
124
 
129
125
  ### <a name="list-timeline"></a>Retrieve the timeline of status updates from a list
130
126
 
131
127
  t list timeline presidents
132
128
 
129
+ ### <a name="timeline"></a>Retrieve the timeline of status updates posted by you and the users you follow
130
+
131
+ t timeline
132
+
133
+ ### <a name="timeline-user"></a>Retrieve the timeline of status updates posted by a user
134
+
135
+ t timeline sferik
136
+
133
137
  ### <a name="mentions"></a>Retrieve the timeline of status updates that mention you
134
138
 
135
139
  t mentions
@@ -150,6 +154,18 @@ type `t help TASK` to get help for a specific command.
150
154
 
151
155
  t favorite sferik
152
156
 
157
+ ### <a name="search-all"></a>Retrieve the 20 most recent Tweets that match a specified query
158
+
159
+ t search all twitter
160
+
161
+ ### <a name="search-timeline"></a>Retrieve Tweets in your timeline that match a specified query
162
+
163
+ t search timeline twitter
164
+
165
+ ### <a name="search-user"></a>Retrieve Tweets in a user's timeline that match a specified query
166
+
167
+ t search user sferik twitter
168
+
153
169
  ## <a name="history"></a>History
154
170
  ![History](http://twitter.rubyforge.org/images/terminal_output.png "History")
155
171
 
data/lib/t/cli.rb CHANGED
@@ -15,13 +15,15 @@ module T
15
15
 
16
16
  DEFAULT_HOST = 'api.twitter.com'
17
17
  DEFAULT_PROTOCOL = 'https'
18
-
19
- class_option :host, :aliases => "-H", :type => :string, :default => DEFAULT_HOST, :desc => "Twitter API server"
20
- class_option :no_ssl, :aliases => "-U", :type => :boolean, :default => false, :desc => "Disable SSL"
21
- class_option :profile, :aliases => "-P", :type => :string, :default => File.join(File.expand_path("~"), RCFile::FILE_NAME), :desc => "Path to RC file", :banner => "FILE"
18
+ DEFAULT_NUM_RESULTS = 20
19
+ MAX_SCREEN_NAME_SIZE = 20
22
20
 
23
21
  check_unknown_options!
24
22
 
23
+ option :host, :aliases => "-H", :type => :string, :default => DEFAULT_HOST, :desc => "Twitter API server"
24
+ option :no_ssl, :aliases => "-U", :type => :boolean, :default => false, :desc => "Disable SSL"
25
+ option :profile, :aliases => "-P", :type => :string, :default => File.join(File.expand_path("~"), RCFile::FILE_NAME), :desc => "Path to RC file", :banner => "FILE"
26
+
25
27
  def initialize(*)
26
28
  super
27
29
  @rcfile = RCFile.instance
@@ -86,10 +88,13 @@ module T
86
88
  end
87
89
 
88
90
  desc "direct_messages", "Returns the 20 most recent Direct Messages sent to you."
91
+ method_option :number, :aliases => "-n", :type => :numeric, :default => DEFAULT_NUM_RESULTS
89
92
  def direct_messages
93
+ defaults = {:include_entities => false}
94
+ defaults.merge!(:count => options['number']) if options['number']
90
95
  run_pager
91
- client.direct_messages(:include_entities => false).each do |direct_message|
92
- say "#{direct_message.sender.screen_name.rjust(20)}: #{direct_message.text} (#{time_ago_in_words(direct_message.created_at)} ago)"
96
+ client.direct_messages(defaults).each do |direct_message|
97
+ say "#{direct_message.sender.screen_name.rjust(MAX_SCREEN_NAME_SIZE)}: #{direct_message.text} (#{time_ago_in_words(direct_message.created_at)} ago)"
93
98
  end
94
99
  end
95
100
  map %w(dms) => :direct_messages
@@ -123,43 +128,32 @@ module T
123
128
  end
124
129
  map %w(fave) => :favorite
125
130
 
126
- desc "favorites", "Returns the 20 most recent Tweets you favorited."
127
- method_option :number, :aliases => "-n", :type => :numeric, :default => 20
131
+ desc "favorites", "Returns the #{DEFAULT_NUM_RESULTS} most recent Tweets you favorited."
132
+ method_option :number, :aliases => "-n", :type => :numeric, :default => DEFAULT_NUM_RESULTS
128
133
  method_option :reverse, :aliases => "-r", :type => :boolean, :default => false
129
134
  def favorites
130
- hash = {:include_entities => false}
131
- hash.merge!(:count => options['number']) if options['number']
132
- timeline = client.favorites(hash)
135
+ defaults = {:include_entities => false}
136
+ defaults.merge!(:count => options['number']) if options['number']
137
+ timeline = client.favorites(defaults)
133
138
  timeline.reverse! if options['reverse']
134
139
  run_pager
135
140
  timeline.each do |status|
136
- say "#{status.user.screen_name.rjust(20)}: #{status.text} (#{time_ago_in_words(status.created_at)} ago)"
141
+ say "#{status.user.screen_name.rjust(MAX_SCREEN_NAME_SIZE)}: #{status.text} (#{time_ago_in_words(status.created_at)} ago)"
137
142
  end
138
143
  end
139
144
  map %w(faves) => :favorites
140
145
 
141
- desc "get SCREEN_NAME", "Retrieves the latest update posted by the user."
142
- def get(screen_name)
143
- screen_name = screen_name.strip_at
144
- user = client.user(screen_name, :include_entities => false)
145
- if user.status
146
- say "#{user.status.text} (#{time_ago_in_words(user.status.created_at)} ago)"
147
- else
148
- raise Thor::Error, "Tweet not found"
149
- end
150
- end
151
-
152
- desc "mentions", "Returns the 20 most recent Tweets mentioning you."
153
- method_option :number, :aliases => "-n", :type => :numeric, :default => 20
146
+ desc "mentions", "Returns the #{DEFAULT_NUM_RESULTS} most recent Tweets mentioning you."
147
+ method_option :number, :aliases => "-n", :type => :numeric, :default => DEFAULT_NUM_RESULTS
154
148
  method_option :reverse, :aliases => "-r", :type => :boolean, :default => false
155
149
  def mentions
156
- hash = {:include_entities => false}
157
- hash.merge!(:count => options['number']) if options['number']
158
- timeline = client.mentions(hash)
150
+ defaults = {:include_entities => false}
151
+ defaults.merge!(:count => options['number']) if options['number']
152
+ timeline = client.mentions(defaults)
159
153
  timeline.reverse! if options['reverse']
160
154
  run_pager
161
155
  timeline.each do |status|
162
- say "#{status.user.screen_name.rjust(20)}: #{status.text} (#{time_ago_in_words(status.created_at)} ago)"
156
+ say "#{status.user.screen_name.rjust(MAX_SCREEN_NAME_SIZE)}: #{status.text} (#{time_ago_in_words(status.created_at)} ago)"
163
157
  end
164
158
  end
165
159
  map %w(replies) => :mentions
@@ -175,11 +169,11 @@ module T
175
169
  method_option :location, :aliases => "-l", :type => :boolean, :default => true
176
170
  def reply(screen_name, message)
177
171
  screen_name = screen_name.strip_at
178
- hash = {:include_entities => false, :trim_user => true}
179
- hash.merge!(:lat => location.lat, :long => location.lng) if options['location']
172
+ defaults = {:include_entities => false, :trim_user => true}
173
+ defaults.merge!(:lat => location.lat, :long => location.lng) if options['location']
180
174
  user = client.user(screen_name, :include_entities => false)
181
- hash.merge!(:in_reply_to_status_id => user.status.id) if user.status
182
- status = client.update("@#{user.screen_name} #{message}", hash)
175
+ defaults.merge!(:in_reply_to_status_id => user.status.id) if user.status
176
+ status = client.update("@#{user.screen_name} #{message}", defaults)
183
177
  say "Reply created by @#{@rcfile.default_profile[0]} to @#{user.screen_name} (#{time_ago_in_words(status.created_at)} ago)"
184
178
  say
185
179
  say "Run `#{$0} delete status` to delete."
@@ -206,25 +200,14 @@ module T
206
200
  end
207
201
  map %w(rt) => :retweet
208
202
 
209
- desc "search QUERY", "Returns the 20 most recent Tweets that match a specified query."
210
- method_option :number, :aliases => "-n", :type => :numeric, :default => 20
211
- method_option :reverse, :aliases => "-r", :type => :boolean, :default => false
212
- def search(query)
213
- hash = {:include_entities => false}
214
- hash.merge!(:rpp => options['number']) if options['number']
215
- timeline = client.search(query, hash)
216
- timeline.reverse! if options['reverse']
217
- run_pager
218
- timeline.each do |status|
219
- say "#{status.from_user.rjust(20)}: #{status.text} (#{time_ago_in_words(status.created_at)} ago)"
220
- end
221
- end
222
-
223
203
  desc "sent_messages", "Returns the 20 most recent Direct Messages sent to you."
204
+ method_option :number, :aliases => "-n", :type => :numeric, :default => DEFAULT_NUM_RESULTS
224
205
  def sent_messages
206
+ defaults = {:include_entities => false}
207
+ defaults.merge!(:count => options['number']) if options['number']
225
208
  run_pager
226
- client.direct_messages_sent(:include_entities => false).each do |direct_message|
227
- say "#{direct_message.recipient.screen_name.rjust(20)}: #{direct_message.text} (#{time_ago_in_words(direct_message.created_at)} ago)"
209
+ client.direct_messages_sent(defaults).each do |direct_message|
210
+ say "#{direct_message.recipient.screen_name.rjust(MAX_SCREEN_NAME_SIZE)}: #{direct_message.text} (#{time_ago_in_words(direct_message.created_at)} ago)"
228
211
  end
229
212
  end
230
213
  map %w(sms) => :sent_messages
@@ -245,9 +228,9 @@ module T
245
228
  desc "status MESSAGE", "Post a Tweet."
246
229
  method_option :location, :aliases => "-l", :type => :boolean, :default => true
247
230
  def status(message)
248
- hash = {:include_entities => false, :trim_user => true}
249
- hash.merge!(:lat => location.lat, :long => location.lng) if options['location']
250
- status = client.update(message, hash)
231
+ defaults = {:include_entities => false, :trim_user => true}
232
+ defaults.merge!(:lat => location.lat, :long => location.lng) if options['location']
233
+ status = client.update(message, defaults)
251
234
  say "Tweet created by @#{@rcfile.default_profile[0]} (#{time_ago_in_words(status.created_at)} ago)"
252
235
  say
253
236
  say "Run `#{$0} delete status` to delete."
@@ -266,17 +249,22 @@ module T
266
249
  end
267
250
  end
268
251
 
269
- desc "timeline", "Returns the 20 most recent Tweets posted by you and the users you follow."
270
- method_option :number, :aliases => "-n", :type => :numeric, :default => 20
252
+ desc "timeline [SCREEN_NAME]", "Returns the #{DEFAULT_NUM_RESULTS} most recent Tweets posted by a user."
253
+ method_option :number, :aliases => "-n", :type => :numeric, :default => DEFAULT_NUM_RESULTS
271
254
  method_option :reverse, :aliases => "-r", :type => :boolean, :default => false
272
- def timeline
273
- hash = {:include_entities => false}
274
- hash.merge!(:count => options['number']) if options['number']
275
- timeline = client.home_timeline(hash)
255
+ def timeline(screen_name=nil)
256
+ defaults = {:include_entities => false}
257
+ defaults.merge!(:count => options['number']) if options['number']
258
+ if screen_name
259
+ screen_name = screen_name.strip_at
260
+ timeline = client.user_timeline(screen_name, defaults)
261
+ else
262
+ timeline = client.home_timeline(defaults)
263
+ end
276
264
  timeline.reverse! if options['reverse']
277
265
  run_pager
278
266
  timeline.each do |status|
279
- say "#{status.user.screen_name.rjust(20)}: #{status.text} (#{time_ago_in_words(status.created_at)} ago)"
267
+ say "#{status.user.screen_name.rjust(MAX_SCREEN_NAME_SIZE)}: #{status.text} (#{time_ago_in_words(status.created_at)} ago)"
280
268
  end
281
269
  end
282
270
  map %w(tl) => :timeline
@@ -310,6 +298,10 @@ module T
310
298
  require 't/cli/list'
311
299
  subcommand 'list', CLI::List
312
300
 
301
+ desc "search SUBCOMMAND ...ARGS", "Search through Tweets."
302
+ require 't/cli/search'
303
+ subcommand 'search', CLI::Search
304
+
313
305
  desc "set SUBCOMMAND ...ARGS", "Change various account settings."
314
306
  require 't/cli/set'
315
307
  subcommand 'set', CLI::Set
data/lib/t/cli/follow.rb CHANGED
@@ -1,5 +1,7 @@
1
+ require 'retryable'
1
2
  require 't/core_ext/enumerable'
2
3
  require 't/core_ext/string'
4
+ require 't/collectable'
3
5
  require 't/rcfile'
4
6
  require 'thor'
5
7
  require 'twitter'
@@ -7,6 +9,8 @@ require 'twitter'
7
9
  module T
8
10
  class CLI
9
11
  class Follow < Thor
12
+ include T::Collectable
13
+
10
14
  DEFAULT_HOST = 'api.twitter.com'
11
15
  DEFAULT_PROTOCOL = 'https'
12
16
 
@@ -17,12 +21,53 @@ module T
17
21
  @rcfile = RCFile.instance
18
22
  end
19
23
 
24
+ desc "followers", "Follow all followers."
25
+ def followers
26
+ follower_ids = collect_with_cursor do |cursor|
27
+ client.follower_ids(:cursor => cursor)
28
+ end
29
+ friend_ids = collect_with_cursor do |cursor|
30
+ client.friend_ids(:cursor => cursor)
31
+ end
32
+ follow_ids = (follower_ids - friend_ids)
33
+ number = follow_ids.length
34
+ return say "@#{@rcfile.default_profile[0]} is already following all followers." if number.zero?
35
+ return unless yes? "Are you sure you want to follow #{number} #{number == 1 ? 'user' : 'users'}?"
36
+ screen_names = follow_ids.threaded_map do |follow_id|
37
+ retryable(:tries => 3, :on => Twitter::Error::ServerError, :sleep => 0) do
38
+ client.follow(follow_id, :include_entities => false)
39
+ end
40
+ end
41
+ say "@#{@rcfile.default_profile[0]} is now following #{number} more #{number == 1 ? 'user' : 'users'}."
42
+ say
43
+ say "Run `#{$0} unfollow all followers` to stop."
44
+ end
45
+
46
+ desc "listed LIST_NAME", "Follow all members of a list."
47
+ def listed(list_name)
48
+ list_member_collection = collect_with_cursor do |cursor|
49
+ client.list_members(list_name, :cursor => cursor, :skip_status => true, :include_entities => false)
50
+ end
51
+ number = list_member_collection.length
52
+ return say "@#{@rcfile.default_profile[0]} is already following all list members." if number.zero?
53
+ return unless yes? "Are you sure you want to follow #{number} #{number == 1 ? 'user' : 'users'}?"
54
+ list_member_collection.threaded_map do |list_member|
55
+ retryable(:tries => 3, :on => Twitter::Error::ServerError, :sleep => 0) do
56
+ client.follow(list_member.id, :include_entities => false)
57
+ end
58
+ end
59
+ say "@#{@rcfile.default_profile[0]} is now following #{number} more #{number == 1 ? 'user' : 'users'}."
60
+ say
61
+ say "Run `#{$0} unfollow all listed #{list_name}` to stop."
62
+ end
63
+
20
64
  desc "users SCREEN_NAME [SCREEN_NAME...]", "Allows you to start following users."
21
65
  def users(screen_name, *screen_names)
22
66
  screen_names.unshift(screen_name)
23
67
  screen_names.threaded_map do |screen_name|
24
- screen_name = screen_name.strip_at
25
- client.follow(screen_name, :include_entities => false)
68
+ retryable(:tries => 3, :on => Twitter::Error::ServerError, :sleep => 0) do
69
+ client.follow(screen_name, :include_entities => false)
70
+ end
26
71
  end
27
72
  number = screen_names.length
28
73
  say "@#{@rcfile.default_profile[0]} is now following #{number} more #{number == 1 ? 'user' : 'users'}."
@@ -30,10 +75,6 @@ module T
30
75
  say "Run `#{$0} unfollow users #{screen_names.join(' ')}` to stop."
31
76
  end
32
77
 
33
- desc "all SUBCOMMAND ...ARGS", "Follow all users."
34
- require 't/cli/follow/all'
35
- subcommand 'all', CLI::Follow::All
36
-
37
78
  private
38
79
 
39
80
  def base_url
data/lib/t/cli/list.rb CHANGED
@@ -10,6 +10,8 @@ module T
10
10
 
11
11
  DEFAULT_HOST = 'api.twitter.com'
12
12
  DEFAULT_PROTOCOL = 'https'
13
+ DEFAULT_NUM_RESULTS = 20
14
+ MAX_SCREEN_NAME_SIZE = 20
13
15
 
14
16
  check_unknown_options!
15
17
 
@@ -21,23 +23,23 @@ module T
21
23
  desc "create LIST_NAME [DESCRIPTION]", "Create a new list."
22
24
  method_option :private, :aliases => "-p", :type => :boolean
23
25
  def create(list_name, description="")
24
- hash = description.blank? ? {} : {:description => description}
25
- hash.merge!(:mode => 'private') if options['private']
26
- client.list_create(list_name, hash)
26
+ defaults = description.blank? ? {} : {:description => description}
27
+ defaults.merge!(:mode => 'private') if options['private']
28
+ client.list_create(list_name, defaults)
27
29
  say "@#{@rcfile.default_profile[0]} created the list \"#{list_name}\"."
28
30
  end
29
31
 
30
32
  desc "timeline LIST_NAME", "Show tweet timeline for members of the specified list."
31
- method_option :number, :aliases => "-n", :type => :numeric, :default => 20
33
+ method_option :number, :aliases => "-n", :type => :numeric, :default => DEFAULT_NUM_RESULTS
32
34
  method_option :reverse, :aliases => "-r", :type => :boolean, :default => false
33
35
  def timeline(list_name)
34
- hash = {:include_entities => false}
35
- hash.merge!(:per_page => options['number']) if options['number']
36
- timeline = client.list_timeline(list_name, hash)
36
+ defaults = {:include_entities => false}
37
+ defaults.merge!(:per_page => options['number']) if options['number']
38
+ timeline = client.list_timeline(list_name, defaults)
37
39
  timeline.reverse! if options['reverse']
38
40
  run_pager
39
41
  timeline.each do |status|
40
- say "#{status.user.screen_name.rjust(20)}: #{status.text} (#{time_ago_in_words(status.created_at)} ago)"
42
+ say "#{status.user.screen_name.rjust(MAX_SCREEN_NAME_SIZE)}: #{status.text} (#{time_ago_in_words(status.created_at)} ago)"
41
43
  end
42
44
  end
43
45
  map %w(tl) => :timeline
@@ -1,4 +1,6 @@
1
+ require 'active_support/core_ext/array/grouping'
1
2
  require 't/core_ext/string'
3
+ require 't/collectable'
2
4
  require 't/rcfile'
3
5
  require 'thor'
4
6
  require 'twitter'
@@ -7,8 +9,11 @@ module T
7
9
  class CLI
8
10
  class List
9
11
  class Add < Thor
12
+ include T::Collectable
13
+
10
14
  DEFAULT_HOST = 'api.twitter.com'
11
15
  DEFAULT_PROTOCOL = 'https'
16
+ MAX_USERS_PER_LIST = 500
12
17
 
13
18
  check_unknown_options!
14
19
 
@@ -17,6 +22,99 @@ module T
17
22
  @rcfile = RCFile.instance
18
23
  end
19
24
 
25
+ desc "friends LIST_NAME", "Add all friends to a list."
26
+ def friends(list_name)
27
+ list_member_ids = collect_with_cursor do |cursor|
28
+ client.list_members(list_name, :cursor => cursor, :skip_status => true, :include_entities => false)
29
+ end
30
+ existing_list_members = list_member_ids.length
31
+ if existing_list_members >= MAX_USERS_PER_LIST
32
+ return say "The list \"#{list_name}\" are already contains the maximum of #{MAX_USERS_PER_LIST} members."
33
+ end
34
+ friend_ids = collect_with_cursor do |cursor|
35
+ client.friend_ids(:cursor => cursor)
36
+ end
37
+ list_member_ids_to_add = (friend_ids - list_member_ids)
38
+ number = list_member_ids_to_add.length
39
+ if number.zero?
40
+ return say "All of @#{@rcfile.default_profile[0]}'s friends are already members of the list \"#{list_name}\"."
41
+ elsif existing_list_members + number > MAX_USERS_PER_LIST
42
+ return unless yes? "Lists can't have more than #{MAX_USERS_PER_LIST} members. Do you want to add up to #{MAX_USERS_PER_LIST} friends to the list \"#{list_name}\"?"
43
+ else
44
+ return unless yes? "Are you sure you want to add #{number} #{number == 1 ? 'friend' : 'friends'} to the list \"#{list_name}\"?"
45
+ end
46
+ 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
+ end
50
+ number_added = [number, max_members_to_add].min
51
+ say "@#{@rcfile.default_profile[0]} added #{number_added} #{number_added == 1 ? 'friend' : 'friends'} to the list \"#{list_name}\"."
52
+ say
53
+ say "Run `#{$0} list remove all friends #{list_name}` to undo."
54
+ end
55
+
56
+ desc "followers LIST_NAME", "Add all followers to a list."
57
+ def followers(list_name)
58
+ list_member_ids = collect_with_cursor do |cursor|
59
+ client.list_members(list_name, :cursor => cursor, :skip_status => true, :include_entities => false)
60
+ end
61
+ existing_list_members = list_member_ids.length
62
+ if existing_list_members >= MAX_USERS_PER_LIST
63
+ return say "The list \"#{list_name}\" are already contains the maximum of #{MAX_USERS_PER_LIST} members."
64
+ end
65
+ follower_ids = collect_with_cursor do |cursor|
66
+ followers = client.follower_ids(:cursor => cursor)
67
+ end
68
+ list_member_ids_to_add = (follower_ids - list_member_ids)
69
+ number = list_member_ids_to_add.length
70
+ if number.zero?
71
+ return say "All of @#{@rcfile.default_profile[0]}'s followers are already members of the list \"#{list_name}\"."
72
+ elsif existing_list_members + number > MAX_USERS_PER_LIST
73
+ return unless yes? "Lists can't have more than #{MAX_USERS_PER_LIST} members. Do you want to add up to #{MAX_USERS_PER_LIST} followers to the list \"#{list_name}\"?"
74
+ else
75
+ return unless yes? "Are you sure you want to add #{number} #{number == 1 ? 'follower' : 'followers'} to the list \"#{list_name}\"?"
76
+ end
77
+ 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)
80
+ end
81
+ number_added = [number, max_members_to_add].min
82
+ say "@#{@rcfile.default_profile[0]} added #{number_added} #{number_added == 1 ? 'follower' : 'followers'} to the list \"#{list_name}\"."
83
+ say
84
+ say "Run `#{$0} list remove all followers #{list_name}` to undo."
85
+ end
86
+
87
+ desc "listed FROM_LIST_NAME TO_LIST_NAME", "Add all list memebers to a list."
88
+ def listed(from_list_name, to_list_name)
89
+ to_list_members = collect_with_cursor do |cursor|
90
+ client.list_members(to_list_name, :cursor => cursor, :skip_status => true, :include_entities => false)
91
+ end
92
+ existing_list_members = to_list_members.length
93
+ if existing_list_members >= MAX_USERS_PER_LIST
94
+ return say "The list \"#{to_list_name}\" are already contains the maximum of #{MAX_USERS_PER_LIST} members."
95
+ end
96
+ from_list_members = collect_with_cursor do |cursor|
97
+ client.list_members(from_list_name, :cursor => cursor, :skip_status => true, :include_entities => false)
98
+ end
99
+ list_member_ids_to_add = (from_list_members.collect(&:id) - to_list_members.collect(&:id))
100
+ number = list_member_ids_to_add.length
101
+ if number.zero?
102
+ return say "All of the members of the list \"#{from_list_name}\" are already members of the list \"#{to_list_name}\"."
103
+ elsif existing_list_members + number > MAX_USERS_PER_LIST
104
+ return unless yes? "Lists can't have more than #{MAX_USERS_PER_LIST} members. Do you want to add up to #{MAX_USERS_PER_LIST} members to the list \"#{to_list_name}\"?"
105
+ else
106
+ return unless yes? "Are you sure you want to add #{number} #{number == 1 ? 'member' : 'members'} to the list \"#{to_list_name}\"?"
107
+ end
108
+ 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)
111
+ end
112
+ number_added = [number, max_members_to_add].min
113
+ say "@#{@rcfile.default_profile[0]} added #{number_added} #{number_added == 1 ? 'member' : 'members'} to the list \"#{to_list_name}\"."
114
+ say
115
+ say "Run `#{$0} list remove all listed #{from_list_name} #{to_list_name}` to undo."
116
+ end
117
+
20
118
  desc "users LIST_NAME SCREEN_NAME [SCREEN_NAME...]", "Add users to a list."
21
119
  def users(list_name, screen_name, *screen_names)
22
120
  screen_names.unshift(screen_name)
@@ -28,10 +126,6 @@ module T
28
126
  say "Run `#{$0} list remove users #{list_name} #{screen_names.join(' ')}` to undo."
29
127
  end
30
128
 
31
- desc "all SUBCOMMAND ...ARGS", "Add all users to a list."
32
- require 't/cli/list/add/all'
33
- subcommand 'all', CLI::List::Add::All
34
-
35
129
  private
36
130
 
37
131
  def base_url