t 0.1.0 → 0.2.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.
@@ -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