twterm 1.0.12 → 1.1.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/lib/twterm.rb +15 -8
  3. data/lib/twterm/app.rb +2 -3
  4. data/lib/twterm/client.rb +218 -68
  5. data/lib/twterm/filterable_list.rb +2 -1
  6. data/lib/twterm/friendship.rb +124 -0
  7. data/lib/twterm/list.rb +5 -3
  8. data/lib/twterm/promise.rb +143 -0
  9. data/lib/twterm/screen.rb +0 -1
  10. data/lib/twterm/status.rb +15 -14
  11. data/lib/twterm/tab/base.rb +15 -1
  12. data/lib/twterm/tab/key_assignments_cheatsheet.rb +7 -19
  13. data/lib/twterm/tab/new/list.rb +9 -15
  14. data/lib/twterm/tab/new/search.rb +9 -17
  15. data/lib/twterm/tab/new/start.rb +90 -29
  16. data/lib/twterm/tab/new/user.rb +5 -7
  17. data/lib/twterm/tab/scrollable.rb +36 -4
  18. data/lib/twterm/tab/statuses/base.rb +240 -0
  19. data/lib/twterm/tab/statuses/conversation.rb +54 -0
  20. data/lib/twterm/tab/statuses/favorites.rb +45 -0
  21. data/lib/twterm/tab/statuses/home.rb +36 -0
  22. data/lib/twterm/tab/statuses/list_timeline.rb +47 -0
  23. data/lib/twterm/tab/statuses/mentions.rb +40 -0
  24. data/lib/twterm/tab/statuses/search.rb +42 -0
  25. data/lib/twterm/tab/statuses/user_timeline.rb +51 -0
  26. data/lib/twterm/tab/user_tab.rb +288 -19
  27. data/lib/twterm/tab/users/base.rb +90 -0
  28. data/lib/twterm/tab/users/followers.rb +41 -0
  29. data/lib/twterm/tab/users/friends.rb +41 -0
  30. data/lib/twterm/tab_manager.rb +13 -5
  31. data/lib/twterm/user.rb +67 -8
  32. data/lib/twterm/version.rb +1 -1
  33. data/spec/twterm/friendship_spec.rb +104 -0
  34. metadata +18 -11
  35. data/lib/twterm/tab/conversation_tab.rb +0 -49
  36. data/lib/twterm/tab/list_tab.rb +0 -44
  37. data/lib/twterm/tab/mentions_tab.rb +0 -36
  38. data/lib/twterm/tab/search_tab.rb +0 -40
  39. data/lib/twterm/tab/statuses_tab.rb +0 -251
  40. data/lib/twterm/tab/timeline_tab.rb +0 -31
  41. data/lib/twterm/user_window.rb +0 -71
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 01d68a787d82a916e9db6edf8801d3b774d70b62
4
- data.tar.gz: 769b6645edd66d51ff21ae54d58a66ae8412e1dd
3
+ metadata.gz: 21bc4a036d064d1a1a4755eb3655f187ebca6514
4
+ data.tar.gz: 03be117b59f4775d71aaad2fef2384f916eb4c92
5
5
  SHA512:
6
- metadata.gz: 668fd496ea6667035ba1a839ac308a6d92d3e76daf05b67ad5b3f685e25d7fcc52642a8a66588bc2ca8c310e9e597e82f37e81784bf492005019ebaed2435c49
7
- data.tar.gz: e17fbe8226c4aa0c351d6d5d11a5d4619d54a024383f217f7e46fac255b1aebc0f90a5f5854a06690e4ec1887f3cbd63c650533bdad28714ebca583781f07aaf
6
+ metadata.gz: 096afbe0caffeaa8d861e8119b8f35144c93e5ff116e996c4f93c67e8e06eff3a3b9bddf46575441c3f777b222760a4867f78d48f1832773886d4a48c94a429e
7
+ data.tar.gz: 29a3516e9f86141dea2b74c4b76ebafcd8f3b68cef94284809d3d17507462a6f60f4c7ba832f3df8bfc6b4129081ecd0eafcde46d26750eae493fda29613a45d
data/lib/twterm.rb CHANGED
@@ -8,6 +8,7 @@ require 'launchy'
8
8
  require 'oauth'
9
9
  require 'readline'
10
10
  require 'singleton'
11
+ require 'set'
11
12
  require 'tweetstream'
12
13
  require 'twitter'
13
14
  require 'twitter-text'
@@ -25,6 +26,7 @@ require 'twterm/extensions/integer'
25
26
  require 'twterm/extensions/string'
26
27
  require 'twterm/filter_query_window'
27
28
  require 'twterm/filterable_list'
29
+ require 'twterm/friendship'
28
30
  require 'twterm/history/base'
29
31
  require 'twterm/history/hashtag'
30
32
  require 'twterm/history/screen_name'
@@ -33,6 +35,7 @@ require 'twterm/notification/base'
33
35
  require 'twterm/notification/message'
34
36
  require 'twterm/notification/error'
35
37
  require 'twterm/notifier'
38
+ require 'twterm/promise'
36
39
  require 'twterm/screen'
37
40
  require 'twterm/scheduler'
38
41
  require 'twterm/status'
@@ -41,25 +44,29 @@ require 'twterm/tab/base'
41
44
  require 'twterm/tab/dumpable'
42
45
  require 'twterm/tab/exceptions'
43
46
  require 'twterm/tab/scrollable'
44
- require 'twterm/tab/statuses_tab'
45
- require 'twterm/tab/conversation_tab'
46
47
  require 'twterm/tab/key_assignments_cheatsheet'
47
- require 'twterm/tab/list_tab'
48
- require 'twterm/tab/mentions_tab'
49
48
  require 'twterm/tab/new/start'
50
49
  require 'twterm/tab/new/list'
51
50
  require 'twterm/tab/new/search'
52
51
  require 'twterm/tab/new/user'
53
- require 'twterm/tab/search_tab'
54
- require 'twterm/tab/timeline_tab'
52
+ require 'twterm/tab/statuses/base'
53
+ require 'twterm/tab/statuses/conversation'
54
+ require 'twterm/tab/statuses/favorites'
55
+ require 'twterm/tab/statuses/home'
56
+ require 'twterm/tab/statuses/list_timeline'
57
+ require 'twterm/tab/statuses/mentions'
58
+ require 'twterm/tab/statuses/search'
59
+ require 'twterm/tab/statuses/user_timeline'
55
60
  require 'twterm/tab/user_tab'
61
+ require 'twterm/tab/users/base'
62
+ require 'twterm/tab/users/followers'
63
+ require 'twterm/tab/users/friends'
56
64
  require 'twterm/tweetbox'
57
65
  require 'twterm/user'
58
- require 'twterm/user_window'
59
66
  require 'twterm/version'
60
67
 
61
68
  module Twterm
62
69
  class Conf
63
- REQUIRE_VERSION = '1.0.12'
70
+ REQUIRE_VERSION = '1.1.0.beta1'
64
71
  end
65
72
  end
data/lib/twterm/app.rb CHANGED
@@ -12,17 +12,16 @@ module Twterm
12
12
  Screen.instance
13
13
  FilterQueryWindow.instance
14
14
 
15
- timeline = Tab::TimelineTab.new(client)
15
+ timeline = Tab::Statuses::Home.new(client)
16
16
  TabManager.instance.add_and_show(timeline)
17
17
 
18
- mentions_tab = Tab::MentionsTab.new(client)
18
+ mentions_tab = Tab::Statuses::Mentions.new(client)
19
19
  TabManager.instance.add(mentions_tab)
20
20
  TabManager.instance.recover_tabs
21
21
 
22
22
  Screen.instance.refresh
23
23
 
24
24
  client.stream
25
- UserWindow.instance
26
25
 
27
26
  reset_interruption_handler
28
27
  end
data/lib/twterm/client.rb CHANGED
@@ -8,6 +8,16 @@ module Twterm
8
8
 
9
9
  @@instances = []
10
10
 
11
+ def block(*user_ids)
12
+ send_request do
13
+ rest_client.block(*user_ids)
14
+ end.then do |users|
15
+ users.each do |user|
16
+ Friendship.block(self.user_id, user.id)
17
+ end
18
+ end
19
+ end
20
+
11
21
  def connect_stream
12
22
  stream_client.stop_stream
13
23
 
@@ -24,14 +34,16 @@ module Twterm
24
34
  end
25
35
 
26
36
  def destroy_status(status)
27
- send_request do
28
- begin
29
- rest_client.destroy_status(status.id)
30
- yield if block_given?
31
- rescue Twitter::Error::NotFound, Twitter::Error::Forbidden
37
+ send_request_without_catch do
38
+ rest_client.destroy_status(status.id)
39
+ end.catch do |reason|
40
+ case reason
41
+ when Twitter::Error::NotFound, Twitter::Error::Forbidden
32
42
  Notifier.instance.show_error 'You cannot destroy that status'
43
+ else
44
+ raise reason
33
45
  end
34
- end
46
+ end.catch(&show_error)
35
47
  end
36
48
 
37
49
  def favorite(status)
@@ -39,27 +51,80 @@ module Twterm
39
51
 
40
52
  send_request do
41
53
  rest_client.favorite(status.id)
54
+ end.then do
42
55
  status.favorite!
43
- yield status if block_given?
44
56
  end
57
+ end
45
58
 
46
- self
59
+ def favorites(user_id = nil)
60
+ user_id ||= self.user_id
61
+
62
+ send_request do
63
+ rest_client.favorites(user_id, count: 200)
64
+ end.then do |tweets|
65
+ tweets.map(&CREATE_STATUS_PROC)
66
+ end
47
67
  end
48
68
 
49
69
  def fetch_muted_users
50
70
  send_request do
51
71
  @muted_user_ids = rest_client.muted_ids.to_a
52
- yield @muted_user_ids if block_given?
72
+ end
73
+ end
74
+
75
+ def follow(*user_ids)
76
+ send_request do
77
+ rest_client.follow(*user_ids)
78
+ end.then do |users|
79
+ users.each do |user|
80
+ if user.protected?
81
+ Friendship.following_requested(self.user_id, user.id)
82
+ else
83
+ Friendship.follow(self.user_id, user.id)
84
+ end
85
+ end
86
+ end
87
+ end
88
+
89
+ def followers(user_id = nil)
90
+ user_id ||= self.user_id
91
+
92
+ m = Mutex.new
93
+
94
+ send_request do
95
+ rest_client.follower_ids(user_id).each_slice(100) do |user_ids|
96
+ m.synchronize do
97
+ users = rest_client.users(*user_ids).map(& -> u { User.new(u) })
98
+ users.each do |user|
99
+ Friendship.follow(user.id, self.user_id)
100
+ end if user_id == self.user_id
101
+ yield users
102
+ end
103
+ end
104
+ end
105
+ end
106
+
107
+ def friends(user_id = nil)
108
+ user_id ||= self.user_id
109
+
110
+ m = Mutex.new
111
+
112
+ send_request do
113
+ rest_client.friend_ids(user_id).each_slice(100) do |user_ids|
114
+ m.synchronize do
115
+ yield rest_client.users(*user_ids).map(& -> u { User.new(u) })
116
+ end
117
+ end
53
118
  end
54
119
  end
55
120
 
56
121
  def home_timeline
57
122
  send_request do
58
- statuses = rest_client
59
- .home_timeline(count: 100)
123
+ rest_client.home_timeline(count: 200)
124
+ end.then do |statuses|
125
+ statuses
60
126
  .select(&@mute_filter)
61
127
  .map(&CREATE_STATUS_PROC)
62
- yield statuses
63
128
  end
64
129
  end
65
130
 
@@ -83,7 +148,9 @@ module Twterm
83
148
 
84
149
  def list(list_id)
85
150
  send_request do
86
- yield List.new(rest_client.list(list_id))
151
+ rest_client.list(list_id)
152
+ end.then do |list|
153
+ List.new(list)
87
154
  end
88
155
  end
89
156
 
@@ -91,27 +158,66 @@ module Twterm
91
158
  fail ArgumentError,
92
159
  'argument must be an instance of List class' unless list.is_a? List
93
160
  send_request do
94
- statuses = rest_client
95
- .list_timeline(list.id, count: 100)
161
+ rest_client.list_timeline(list.id, count: 200)
162
+ end.then do |statuses|
163
+ statuses
96
164
  .select(&@mute_filter)
97
165
  .map(&CREATE_STATUS_PROC)
98
- yield statuses
99
166
  end
100
167
  end
101
168
 
102
169
  def lists
103
170
  send_request do
104
- yield rest_client.lists.map { |list| List.new(list) }
171
+ rest_client.lists
172
+ end.then do |lists|
173
+ lists.map { |list| List.new(list) }
105
174
  end
106
175
  end
107
176
 
177
+ def lookup_friendships
178
+ user_ids = User.ids.reject { |id| Friendship.already_looked_up?(id) }
179
+ send_request_without_catch do
180
+ user_ids.each_slice(100) do |chunked_user_ids|
181
+ friendships = rest_client.friendships(*chunked_user_ids)
182
+ friendships.each do |friendship|
183
+ id = friendship.id
184
+ client_id = user_id
185
+
186
+ conn = friendship.connections
187
+ conn.include?('blocking') ? Friendship.block(client_id, id) : Friendship.unblock(client_id, id)
188
+ conn.include?('following') ? Friendship.follow(client_id, id) : Friendship.unfollow(client_id, id)
189
+ conn.include?('following_requested') ? Friendship.following_requested(client_id, id) : Friendship.following_not_requested(client_id, id)
190
+ conn.include?('followed_by') ? Friendship.follow(id, client_id) : Friendship.unfollow(id, client_id)
191
+ conn.include?('muting') ? Friendship.mute(client_id, id) : Friendship.unmute(client_id, id)
192
+ end
193
+ end
194
+ end.catch do |e|
195
+ case e
196
+ when Twitter::Error::TooManyRequests
197
+ # do nothing
198
+ else
199
+ raise e
200
+ end
201
+ end.catch(&show_error)
202
+ end
203
+
108
204
  def mentions
109
205
  send_request do
110
- statuses = rest_client
111
- .mentions(count: 100)
206
+ rest_client.mentions(count: 200)
207
+ end.then do |statuses|
208
+ statuses
112
209
  .select(&@mute_filter)
113
210
  .map(&CREATE_STATUS_PROC)
114
- yield statuses
211
+ end
212
+ end
213
+
214
+ def mute(user_ids)
215
+ send_request do
216
+ rest_client.mute(*user_ids)
217
+ end.then do |users|
218
+ users.each do |user|
219
+ Friendship.mute(self.user_id, user.id)
220
+ end
115
221
  end
116
222
  end
117
223
 
@@ -149,63 +255,66 @@ module Twterm
149
255
  fail ArgumentError,
150
256
  'argument must be an instance of Status class' unless status.is_a? Status
151
257
 
152
- send_request do
153
- begin
154
- rest_client.retweet!(status.id)
155
- status.retweet!
156
- yield status if block_given?
157
- rescue => e
158
- message =
159
- case e
160
- when Twitter::Error::AlreadyRetweeted
161
- 'The status is already retweeted'
162
- when Twitter::Error::NotFound
163
- 'The status is not found'
164
- when Twitter::Error::Forbidden
165
- if status.user.id == user_id # when the status is mine
166
- 'You cannot retweet your own status'
167
- else # when the status is not mine
168
- 'The status is protected'
169
- end
170
- else
171
- raise e
258
+ send_request_without_catch do
259
+ rest_client.retweet!(status.id)
260
+ end.then do
261
+ status.retweet!
262
+ end.catch do |reason|
263
+ message =
264
+ case reason
265
+ when Twitter::Error::AlreadyRetweeted
266
+ 'The status is already retweeted'
267
+ when Twitter::Error::NotFound
268
+ 'The status is not found'
269
+ when Twitter::Error::Forbidden
270
+ if status.user.id == user_id # when the status is mine
271
+ 'You cannot retweet your own status'
272
+ else # when the status is not mine
273
+ 'The status is protected'
172
274
  end
173
- Notifier.instance.show_error "Retweet attempt failed: #{message}"
174
- end
175
- end
275
+ else
276
+ raise e
277
+ end
278
+ Notifier.instance.show_error "Retweet attempt failed: #{message}"
279
+ end.catch(&show_error)
176
280
  end
177
281
 
178
282
  def saved_search
179
283
  send_request do
180
- yield rest_client.saved_searches
284
+ rest_client.saved_searches
181
285
  end
182
286
  end
183
287
 
184
288
  def search(query)
185
289
  send_request do
186
- statuses = rest_client
187
- .search(query, count: 100)
290
+ rest_client.search(query, count: 200)
291
+ end.then do |statuses|
292
+ statuses
188
293
  .select(&@mute_filter)
189
294
  .map(&CREATE_STATUS_PROC)
190
- yield statuses
191
295
  end
192
296
  end
193
297
 
194
298
  def show_status(status_id)
195
299
  send_request do
196
- yield Status.new(rest_client.status(status_id))
300
+ rest_client.status(status_id)
301
+ end.then do |status|
302
+ Status.new(status)
197
303
  end
198
304
  end
199
305
 
200
306
  def show_user(query)
201
- send_request do
202
- user =
203
- begin
204
- User.new(rest_client.user(query))
205
- rescue Twitter::Error::NotFound
206
- nil
207
- end
208
- yield user
307
+ send_request_without_catch do
308
+ rest_client.user(query)
309
+ end.catch do |reason|
310
+ case reason
311
+ when Twitter::Error::NotFound
312
+ nil
313
+ else
314
+ raise reason
315
+ end
316
+ end.catch(&show_error).then do |user|
317
+ user.nil? ? nil : User.new(user)
209
318
  end
210
319
  end
211
320
 
@@ -252,24 +361,54 @@ module Twterm
252
361
  )
253
362
  end
254
363
 
364
+ def unblock(*user_ids)
365
+ send_request do
366
+ rest_client.unblock(*user_ids)
367
+ end.then do |users|
368
+ users.each do |user|
369
+ Friendship.unblock(self.user_id, user.id)
370
+ end
371
+ end
372
+ end
373
+
255
374
  def unfavorite(status)
256
375
  fail ArgumentError,
257
376
  'argument must be an instance of Status class' unless status.is_a? Status
258
377
 
259
378
  send_request do
260
379
  rest_client.unfavorite(status.id)
380
+ end.then do
261
381
  status.unfavorite!
262
- yield status if block_given?
382
+ end
383
+ end
384
+
385
+ def unfollow(*user_ids)
386
+ send_request do
387
+ rest_client.unfollow(*user_ids)
388
+ end.then do |users|
389
+ users.each do |user|
390
+ Friendship.unfollow(self.user_id, user.id)
391
+ end
392
+ end
393
+ end
394
+
395
+ def unmute(user_ids)
396
+ send_request do
397
+ rest_client.unmute(*user_ids)
398
+ end.then do |users|
399
+ users.each do |user|
400
+ Friendship.unmute(self.user_id, user.id)
401
+ end
263
402
  end
264
403
  end
265
404
 
266
405
  def user_timeline(user_id)
267
406
  send_request do
268
- statuses = rest_client
269
- .user_timeline(user_id, count: 100)
407
+ rest_client.user_timeline(user_id, count: 200)
408
+ end.then do |statuses|
409
+ statuses
270
410
  .select(&@mute_filter)
271
411
  .map(&CREATE_STATUS_PROC)
272
- yield statuses
273
412
  end
274
413
  end
275
414
 
@@ -285,6 +424,17 @@ module Twterm
285
424
 
286
425
  private
287
426
 
427
+ def show_error
428
+ proc do |e|
429
+ case e
430
+ when Twitter::Error
431
+ Notifier.instance.show_error "Failed to send request: #{e.message}"
432
+ else
433
+ raise e
434
+ end
435
+ end.freeze
436
+ end
437
+
288
438
  def invoke_callbacks(event, data = nil)
289
439
  return if @callbacks[event].nil?
290
440
 
@@ -299,15 +449,15 @@ module Twterm
299
449
  end
300
450
 
301
451
  def send_request(&block)
302
- Thread.new do
452
+ send_request_without_catch(&block).catch(&show_error)
453
+ end
454
+
455
+ def send_request_without_catch(&block)
456
+ Promise.new do |resolve, reject|
303
457
  begin
304
- block.call
305
- rescue Twitter::Error => e
306
- Notifier.instance.show_error "Failed to send request: #{e.message}"
307
- if e.message == 'getaddrinfo: nodename nor servname provided, or not known'
308
- sleep 10
309
- retry
310
- end
458
+ resolve.(block.call)
459
+ rescue Twitter::Error => reason
460
+ reject.(reason)
311
461
  end
312
462
  end
313
463
  end