twterm 1.0.12 → 1.1.0.beta1

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.
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