t 0.2.1 → 0.3.0

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