t 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,66 @@
1
+ require 't/core_ext/string'
2
+ require 't/rcfile'
3
+ require 'thor'
4
+ require 'twitter'
5
+
6
+ module T
7
+ class CLI
8
+ class Unfollow < Thor
9
+ DEFAULT_HOST = 'api.twitter.com'
10
+ DEFAULT_PROTOCOL = 'https'
11
+
12
+ check_unknown_options!
13
+
14
+ def initialize(*)
15
+ super
16
+ @rcfile = RCFile.instance
17
+ end
18
+
19
+ desc "users SCREEN_NAME [SCREEN_NAME...]", "Allows you to stop following users."
20
+ def users(screen_name, *screen_names)
21
+ screen_names.unshift(screen_name)
22
+ users = screen_names.map do |screen_name|
23
+ screen_name = screen_name.strip_at
24
+ user = client.unfollow(screen_name, :include_entities => false)
25
+ say "@#{@rcfile.default_profile[0]} is no longer following @#{user.screen_name}."
26
+ user
27
+ end
28
+ number = users.length
29
+ say "@#{@rcfile.default_profile[0]} is no longer following #{number} #{number == 1 ? 'user' : 'users'}."
30
+ say
31
+ say "Run `#{$0} follow users #{screen_names.join(' ')}` to follow again."
32
+ end
33
+
34
+ desc "all SUBCOMMAND ...ARGS", "Follow all users."
35
+ require 't/cli/unfollow/all'
36
+ subcommand 'all', CLI::Unfollow::All
37
+
38
+ private
39
+
40
+ def base_url
41
+ "#{protocol}://#{host}"
42
+ end
43
+
44
+ def client
45
+ return @client if @client
46
+ @rcfile.path = parent_options['profile'] if parent_options['profile']
47
+ @client = Twitter::Client.new(
48
+ :endpoint => base_url,
49
+ :consumer_key => @rcfile.default_consumer_key,
50
+ :consumer_secret => @rcfile.default_consumer_secret,
51
+ :oauth_token => @rcfile.default_token,
52
+ :oauth_token_secret => @rcfile.default_secret
53
+ )
54
+ end
55
+
56
+ def host
57
+ parent_options['host'] || DEFAULT_HOST
58
+ end
59
+
60
+ def protocol
61
+ parent_options['no_ssl'] ? 'http' : DEFAULT_PROTOCOL
62
+ end
63
+
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,122 @@
1
+ require 't/rcfile'
2
+ require 'thor'
3
+ require 'twitter'
4
+
5
+ module T
6
+ class CLI
7
+ class Unfollow
8
+ class All < Thor
9
+ DEFAULT_HOST = 'api.twitter.com'
10
+ DEFAULT_PROTOCOL = 'https'
11
+
12
+ check_unknown_options!
13
+
14
+ def initialize(*)
15
+ super
16
+ @rcfile = RCFile.instance
17
+ end
18
+
19
+ desc "listed LIST_NAME", "Unfollow all members of a list."
20
+ def listed(list_name)
21
+ list_member_collection = []
22
+ cursor = -1
23
+ until cursor == 0
24
+ list_members = client.list_members(list_name, :cursor => cursor, :include_entities => false, :skip_status => true)
25
+ list_member_collection += list_members.users
26
+ cursor = list_members.next_cursor
27
+ end
28
+ number = list_member_collection.length
29
+ return say "@#{@rcfile.default_profile[0]} is already not following any list members." if number.zero?
30
+ return unless yes? "Are you sure you want to unfollow #{number} #{number == 1 ? 'user' : 'users'}?"
31
+ list_member_collection.each do |list_member|
32
+ user = client.unfollow(list_member.id, :include_entities => false)
33
+ say "@#{@rcfile.default_profile[0]} is no longer following @#{user.screen_name}."
34
+ end
35
+ say "@#{@rcfile.default_profile[0]} is no longer following #{number} #{number == 1 ? 'user' : 'users'}."
36
+ say
37
+ say "Run `#{$0} follow all listed #{list_name}` to follow again."
38
+ end
39
+
40
+ desc "nonfollowers", "Unfollow all non-followers."
41
+ def nonfollowers
42
+ friend_ids = []
43
+ cursor = -1
44
+ until cursor == 0
45
+ friends = client.friend_ids(:cursor => cursor)
46
+ friend_ids += friends.ids
47
+ cursor = friends.next_cursor
48
+ end
49
+ follower_ids = []
50
+ cursor = -1
51
+ until cursor == 0
52
+ followers = client.follower_ids(:cursor => cursor)
53
+ follower_ids += followers.ids
54
+ cursor = followers.next_cursor
55
+ end
56
+ unfollow_ids = (friend_ids - follower_ids)
57
+ number = unfollow_ids.length
58
+ return say "@#{@rcfile.default_profile[0]} is already not following any non-followers." if number.zero?
59
+ return unless yes? "Are you sure you want to unfollow #{number} #{number == 1 ? 'user' : 'users'}?"
60
+ screen_names = unfollow_ids.map do |unfollow_id|
61
+ user = client.unfollow(unfollow_id, :include_entities => false)
62
+ say "@#{@rcfile.default_profile[0]} is no longer following @#{user.screen_name}."
63
+ user.screen_name
64
+ end
65
+ say "@#{@rcfile.default_profile[0]} is no longer following #{number} #{number == 1 ? 'user' : 'users'}."
66
+ say
67
+ say "Run `#{$0} follow users #{screen_names.join(' ')}` to follow again."
68
+ end
69
+
70
+ desc "users", "Unfollow all users."
71
+ def users
72
+ friend_ids = []
73
+ cursor = -1
74
+ until cursor == 0
75
+ friends = client.friend_ids(:cursor => cursor)
76
+ friend_ids += friends.ids
77
+ cursor = friends.next_cursor
78
+ end
79
+ number = friend_ids.length
80
+ return say "@#{@rcfile.default_profile[0]} is already not following anyone." if number.zero?
81
+ return unless yes? "Are you sure you want to unfollow #{number} #{number == 1 ? 'user' : 'users'}?"
82
+ screen_names = friend_ids.map do |friend_id|
83
+ user = client.unfollow(friend_id, :include_entities => false)
84
+ say "@#{@rcfile.default_profile[0]} is no longer following @#{user.screen_name}."
85
+ user.screen_name
86
+ end
87
+ say "@#{@rcfile.default_profile[0]} is no longer following #{number} #{number == 1 ? 'user' : 'users'}."
88
+ say
89
+ say "Run `#{$0} follow users #{screen_names.join(' ')}` to follow again."
90
+ end
91
+ map %w(friends) => :users
92
+
93
+ private
94
+
95
+ def base_url
96
+ "#{protocol}://#{host}"
97
+ end
98
+
99
+ def client
100
+ return @client if @client
101
+ @rcfile.path = parent_options['profile'] if parent_options['profile']
102
+ @client = Twitter::Client.new(
103
+ :endpoint => base_url,
104
+ :consumer_key => @rcfile.default_consumer_key,
105
+ :consumer_secret => @rcfile.default_consumer_secret,
106
+ :oauth_token => @rcfile.default_token,
107
+ :oauth_token_secret => @rcfile.default_secret
108
+ )
109
+ end
110
+
111
+ def host
112
+ parent_options['host'] || DEFAULT_HOST
113
+ end
114
+
115
+ def protocol
116
+ parent_options['no_ssl'] ? 'http' : DEFAULT_PROTOCOL
117
+ end
118
+
119
+ end
120
+ end
121
+ end
122
+ end
@@ -8,7 +8,7 @@ module T
8
8
 
9
9
  # @return [Integer]
10
10
  def self.minor
11
- 1
11
+ 2
12
12
  end
13
13
 
14
14
  # @return [Integer]
@@ -0,0 +1,332 @@
1
+ # encoding: utf-8
2
+ require 'helper'
3
+
4
+ describe T::CLI::Delete do
5
+
6
+ before do
7
+ @t = T::CLI.new
8
+ Timecop.freeze(Time.local(2011, 11, 24, 16, 20, 0))
9
+ @old_stderr = $stderr
10
+ $stderr = StringIO.new
11
+ @old_stdout = $stdout
12
+ $stdout = StringIO.new
13
+ end
14
+
15
+ after do
16
+ $stderr = @old_stderr
17
+ $stdout = @old_stdout
18
+ end
19
+
20
+ describe "#block" do
21
+ before do
22
+ @t.options = @t.options.merge(:profile => fixture_path + "/.trc")
23
+ stub_delete("/1/blocks/destroy.json").
24
+ with(:query => {:screen_name => "sferik", :include_entities => "false"}).
25
+ to_return(:body => fixture("sferik.json"), :headers => {:content_type => "application/json; charset=utf-8"})
26
+ end
27
+ it "should request the correct resource" do
28
+ @t.delete("block", "sferik")
29
+ a_delete("/1/blocks/destroy.json").
30
+ with(:query => {:screen_name => "sferik", :include_entities => "false"}).
31
+ should have_been_made
32
+ end
33
+ it "should have the correct output" do
34
+ @t.delete("block", "sferik")
35
+ $stdout.string.should =~ /^@testcli unblocked @sferik\.$/
36
+ end
37
+ end
38
+
39
+ describe "#dm" do
40
+ before do
41
+ @t.options = @t.options.merge(:profile => fixture_path + "/.trc")
42
+ end
43
+ context "not found" do
44
+ before do
45
+ stub_get("/1/direct_messages/sent.json").
46
+ with(:query => {:count => "1", :include_entities => "false"}).
47
+ to_return(:body => "[]", :headers => {:content_type => "application/json; charset=utf-8"})
48
+ end
49
+ it "should exit" do
50
+ lambda do
51
+ @t.delete("dm")
52
+ end.should raise_error(Thor::Error, "Direct Message not found")
53
+ end
54
+ end
55
+ context "found" do
56
+ before do
57
+ stub_get("/1/direct_messages/sent.json").
58
+ with(:query => {:count => "1", :include_entities => "false"}).
59
+ to_return(:body => fixture("direct_messages.json"), :headers => {:content_type => "application/json; charset=utf-8"})
60
+ stub_delete("/1/direct_messages/destroy/1773478249.json").
61
+ with(:query => {:include_entities => "false"}).
62
+ to_return(:body => fixture("direct_message.json"), :headers => {:content_type => "application/json; charset=utf-8"})
63
+ end
64
+ context ":force => true" do
65
+ before do
66
+ @t.options = @t.options.merge(:force => true)
67
+ end
68
+ it "should request the correct resource" do
69
+ @t.delete("dm")
70
+ a_get("/1/direct_messages/sent.json").
71
+ with(:query => {:count => "1", :include_entities => "false"}).
72
+ should have_been_made
73
+ a_delete("/1/direct_messages/destroy/1773478249.json").
74
+ with(:query => {:include_entities => "false"}).
75
+ should have_been_made
76
+ end
77
+ it "should have the correct output" do
78
+ @t.delete("dm")
79
+ $stdout.string.chomp.should == "@sferik deleted the direct message sent to @pengwynn: \"Creating a fixture for the Twitter gem\""
80
+ end
81
+ end
82
+ context ":force => false" do
83
+ before do
84
+ @t.options = @t.options.merge(:force => false)
85
+ end
86
+ it "should request the correct resource" do
87
+ $stdout.should_receive(:print).with("Are you sure you want to permanently delete the direct message to @hurrycane: \"Sounds good. Meeting Tuesday is fine.\"? ")
88
+ $stdin.should_receive(:gets).and_return("yes")
89
+ @t.delete("dm")
90
+ a_get("/1/direct_messages/sent.json").
91
+ with(:query => {:count => "1", :include_entities => "false"}).
92
+ should have_been_made
93
+ a_delete("/1/direct_messages/destroy/1773478249.json").
94
+ with(:query => {:include_entities => "false"}).
95
+ should have_been_made
96
+ end
97
+ context "yes" do
98
+ it "should have the correct output" do
99
+ $stdout.should_receive(:print).with("Are you sure you want to permanently delete the direct message to @hurrycane: \"Sounds good. Meeting Tuesday is fine.\"? ")
100
+ $stdin.should_receive(:gets).and_return("yes")
101
+ @t.delete("dm")
102
+ $stdout.string.chomp.should == "@sferik deleted the direct message sent to @pengwynn: \"Creating a fixture for the Twitter gem\""
103
+ end
104
+ end
105
+ context "no" do
106
+ it "should have the correct output" do
107
+ $stdout.should_receive(:print).with("Are you sure you want to permanently delete the direct message to @hurrycane: \"Sounds good. Meeting Tuesday is fine.\"? ")
108
+ $stdin.should_receive(:gets).and_return("no")
109
+ @t.delete("dm")
110
+ $stdout.string.chomp.should == ""
111
+ end
112
+ end
113
+ end
114
+ end
115
+ end
116
+
117
+ describe "#favorite" do
118
+ before do
119
+ @t.options = @t.options.merge(:profile => fixture_path + "/.trc")
120
+ end
121
+ context "not found" do
122
+ before do
123
+ stub_get("/1/favorites.json").
124
+ with(:query => {:count => "1", :include_entities => "false"}).
125
+ to_return(:body => "[]", :headers => {:content_type => "application/json; charset=utf-8"})
126
+ end
127
+ it "should exit" do
128
+ lambda do
129
+ @t.delete("favorite")
130
+ end.should raise_error(Thor::Error, "Tweet not found")
131
+ end
132
+ end
133
+ context "found" do
134
+ before do
135
+ stub_get("/1/favorites.json").
136
+ with(:query => {:count => "1", :include_entities => "false"}).
137
+ to_return(:body => fixture("favorites.json"), :headers => {:content_type => "application/json; charset=utf-8"})
138
+ stub_delete("/1/favorites/destroy/28439861609.json").
139
+ with(:query => {:include_entities => "false"}).
140
+ to_return(:body => fixture("status.json"), :headers => {:content_type => "application/json; charset=utf-8"})
141
+ end
142
+ context ":force => true" do
143
+ before do
144
+ @t.options = @t.options.merge(:force => true)
145
+ end
146
+ it "should request the correct resource" do
147
+ @t.delete("favorite")
148
+ a_get("/1/favorites.json").
149
+ with(:query => {:count => "1", :include_entities => "false"}).
150
+ should have_been_made
151
+ a_delete("/1/favorites/destroy/28439861609.json").
152
+ with(:query => {:include_entities => "false"}).
153
+ should have_been_made
154
+ end
155
+ it "should have the correct output" do
156
+ @t.delete("favorite")
157
+ $stdout.string.should =~ /^@testcli unfavorited @z's latest status: \"Spilled grilled onions on myself\. I smell delicious!\"$/
158
+ end
159
+ end
160
+ context ":force => false" do
161
+ before do
162
+ @t.options = @t.options.merge(:force => false)
163
+ end
164
+ it "should request the correct resource" do
165
+ $stdout.should_receive(:print).with("Are you sure you want to delete the favorite of @z's latest status: \"Spilled grilled onions on myself. I smell delicious!\"? ")
166
+ $stdin.should_receive(:gets).and_return("yes")
167
+ @t.delete("favorite")
168
+ a_get("/1/favorites.json").
169
+ with(:query => {:count => "1", :include_entities => "false"}).
170
+ should have_been_made
171
+ a_delete("/1/favorites/destroy/28439861609.json").
172
+ with(:query => {:include_entities => "false"}).
173
+ should have_been_made
174
+ end
175
+ context "yes" do
176
+ it "should have the correct output" do
177
+ $stdout.should_receive(:print).with("Are you sure you want to delete the favorite of @z's latest status: \"Spilled grilled onions on myself. I smell delicious!\"? ")
178
+ $stdin.should_receive(:gets).and_return("yes")
179
+ @t.delete("favorite")
180
+ $stdout.string.should =~ /^@testcli unfavorited @z's latest status: \"Spilled grilled onions on myself\. I smell delicious!\"$/
181
+ end
182
+ end
183
+ context "no" do
184
+ it "should have the correct output" do
185
+ $stdout.should_receive(:print).with("Are you sure you want to delete the favorite of @z's latest status: \"Spilled grilled onions on myself. I smell delicious!\"? ")
186
+ $stdin.should_receive(:gets).and_return("no")
187
+ @t.delete("favorite")
188
+ $stdout.string.chomp.should == ""
189
+ end
190
+ end
191
+ end
192
+ end
193
+ end
194
+
195
+ describe "#list" do
196
+ before do
197
+ @t.options = @t.options.merge(:profile => fixture_path + "/.trc")
198
+ stub_get("/1/account/verify_credentials.json").
199
+ to_return(:body => fixture("sferik.json"), :headers => {:content_type => "application/json; charset=utf-8"})
200
+ stub_delete("/1/lists/destroy.json").
201
+ with(:query => {:owner_screen_name => "sferik", :slug => "presidents"}).
202
+ to_return(:body => fixture("list.json"), :headers => {:content_type => "application/json; charset=utf-8"})
203
+ end
204
+ context ":force => true" do
205
+ before do
206
+ @t.options = @t.options.merge(:force => true)
207
+ end
208
+ it "should request the correct resource" do
209
+ @t.delete("list", "presidents")
210
+ a_get("/1/account/verify_credentials.json").
211
+ should have_been_made
212
+ a_delete("/1/lists/destroy.json").
213
+ with(:query => {:owner_screen_name => "sferik", :slug => "presidents"}).
214
+ should have_been_made
215
+ end
216
+ it "should have the correct output" do
217
+ @t.delete("list", "presidents")
218
+ $stdout.string.chomp.should == "@testcli deleted the list \"presidents\"."
219
+ end
220
+ end
221
+ context ":force => false" do
222
+ before do
223
+ @t.options = @t.options.merge(:force => false)
224
+ end
225
+ it "should request the correct resource" do
226
+ $stdout.should_receive(:print).with("Are you sure you want to permanently delete the list \"presidents\"? ")
227
+ $stdin.should_receive(:gets).and_return("yes")
228
+ @t.delete("list", "presidents")
229
+ a_get("/1/account/verify_credentials.json").
230
+ should have_been_made
231
+ a_delete("/1/lists/destroy.json").
232
+ with(:query => {:owner_screen_name => "sferik", :slug => "presidents"}).
233
+ should have_been_made
234
+ end
235
+ context "yes" do
236
+ it "should have the correct output" do
237
+ $stdout.should_receive(:print).with("Are you sure you want to permanently delete the list \"presidents\"? ")
238
+ $stdin.should_receive(:gets).and_return("yes")
239
+ @t.delete("list", "presidents")
240
+ $stdout.string.chomp.should == "@testcli deleted the list \"presidents\"."
241
+ end
242
+ end
243
+ context "no" do
244
+ it "should have the correct output" do
245
+ $stdout.should_receive(:print).with("Are you sure you want to permanently delete the list \"presidents\"? ")
246
+ $stdin.should_receive(:gets).and_return("no")
247
+ @t.delete("list", "presidents")
248
+ $stdout.string.chomp.should == ""
249
+ end
250
+ end
251
+ end
252
+ end
253
+
254
+ describe "#status" do
255
+ before do
256
+ @t.options = @t.options.merge(:profile => fixture_path + "/.trc")
257
+ end
258
+ context "not found" do
259
+ before do
260
+ stub_get("/1/account/verify_credentials.json").
261
+ with(:query => {:include_entities => "false"}).
262
+ to_return(:body => "{}", :headers => {:content_type => "application/json; charset=utf-8"})
263
+ end
264
+ it "should exit" do
265
+ lambda do
266
+ @t.delete("status")
267
+ end.should raise_error(Thor::Error, "Tweet not found")
268
+ end
269
+ end
270
+ context "found" do
271
+ before do
272
+ stub_get("/1/account/verify_credentials.json").
273
+ with(:query => {:include_entities => "false"}).
274
+ to_return(:body => fixture("sferik.json"), :headers => {:content_type => "application/json; charset=utf-8"})
275
+ stub_delete("/1/statuses/destroy/26755176471724032.json").
276
+ with(:query => {:include_entities => "false", :trim_user => "true"}).
277
+ to_return(:body => fixture("status.json"), :headers => {:content_type => "application/json; charset=utf-8"})
278
+ end
279
+ context ":force => true" do
280
+ before do
281
+ @t.options = @t.options.merge(:force => true)
282
+ end
283
+ it "should request the correct resource" do
284
+ @t.delete("status")
285
+ a_get("/1/account/verify_credentials.json").
286
+ with(:query => {:include_entities => "false"}).
287
+ should have_been_made
288
+ a_delete("/1/statuses/destroy/26755176471724032.json").
289
+ with(:query => {:include_entities => "false", :trim_user => "true"}).
290
+ should have_been_made
291
+ end
292
+ it "should have the correct output" do
293
+ @t.delete("status")
294
+ $stdout.string.chomp.should == "@testcli deleted the status: \"@noradio working on implementing #NewTwitter API methods in the twitter gem. Twurl is making it easy. Thank you!\""
295
+ end
296
+ end
297
+ context ":force => false" do
298
+ before do
299
+ @t.options = @t.options.merge(:force => false)
300
+ end
301
+ it "should request the correct resource" do
302
+ $stdout.should_receive(:print).with("Are you sure you want to permanently delete @testcli's latest status: \"RT @tenderlove: [ANN] sqlite3-ruby =&gt; sqlite3\"? ")
303
+ $stdin.should_receive(:gets).and_return("yes")
304
+ @t.delete("status")
305
+ a_get("/1/account/verify_credentials.json").
306
+ with(:query => {:include_entities => "false"}).
307
+ should have_been_made
308
+ a_delete("/1/statuses/destroy/26755176471724032.json").
309
+ with(:query => {:include_entities => "false", :trim_user => "true"}).
310
+ should have_been_made
311
+ end
312
+ context "yes" do
313
+ it "should have the correct output" do
314
+ $stdout.should_receive(:print).with("Are you sure you want to permanently delete @testcli's latest status: \"RT @tenderlove: [ANN] sqlite3-ruby =&gt; sqlite3\"? ")
315
+ $stdin.should_receive(:gets).and_return("yes")
316
+ @t.delete("status")
317
+ $stdout.string.chomp.should == "@testcli deleted the status: \"@noradio working on implementing #NewTwitter API methods in the twitter gem. Twurl is making it easy. Thank you!\""
318
+ end
319
+ end
320
+ context "no" do
321
+ it "should have the correct output" do
322
+ $stdout.should_receive(:print).with("Are you sure you want to permanently delete @testcli's latest status: \"RT @tenderlove: [ANN] sqlite3-ruby =&gt; sqlite3\"? ")
323
+ $stdin.should_receive(:gets).and_return("no")
324
+ @t.delete("status")
325
+ $stdout.string.chomp.should == ""
326
+ end
327
+ end
328
+ end
329
+ end
330
+ end
331
+
332
+ end