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
@@ -0,0 +1,54 @@
1
+ module Twterm
2
+ module Tab
3
+ module Statuses
4
+ class Conversation
5
+ include Base
6
+ include Dumpable
7
+
8
+ attr_reader :status
9
+
10
+ def ==(other)
11
+ other.is_a?(self.class) && status == other.status
12
+ end
13
+
14
+ def fetch_in_reply_to_status(status)
15
+ status.in_reply_to_status.then do |in_reply_to|
16
+ return if in_reply_to.nil?
17
+ append(in_reply_to)
18
+ sort
19
+ Thread.new { fetch_in_reply_to_status(in_reply_to) }
20
+ end
21
+ end
22
+
23
+ def fetch_replies(status)
24
+ status.replies.each do |reply|
25
+ prepend(reply)
26
+ sort
27
+ Thread.new { fetch_replies(reply) }
28
+ end
29
+ end
30
+
31
+ def dump
32
+ @status.id
33
+ end
34
+
35
+ def initialize(status_id)
36
+ super()
37
+
38
+ Status.find_or_fetch(status_id).then do |status|
39
+ @status = status
40
+
41
+ append(status)
42
+ scroller.move_to_top
43
+ Thread.new { fetch_in_reply_to_status(status) }
44
+ Thread.new { fetch_replies(status) }
45
+ end
46
+ end
47
+
48
+ def title
49
+ 'Conversation'.freeze
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,45 @@
1
+ module Twterm
2
+ module Tab
3
+ module Statuses
4
+ class Favorites
5
+ include Base
6
+ include Dumpable
7
+
8
+ attr_reader :user, :user_id
9
+
10
+ def ==(other)
11
+ other.is_a?(self.class) && user_id == other.user_id
12
+ end
13
+
14
+ def dump
15
+ @user.id
16
+ end
17
+
18
+ def fetch
19
+ Client.current.favorites(@user.id).then do |statuses|
20
+ statuses.reverse.each(&method(:prepend))
21
+ sort
22
+ yield if block_given?
23
+ end
24
+ end
25
+
26
+ def initialize(user_id)
27
+ super()
28
+
29
+ @user_id = user_id
30
+
31
+ User.find_or_fetch(user_id).then do |user|
32
+ @user = user
33
+ TabManager.instance.refresh_window
34
+
35
+ fetch { scroller.move_to_top }
36
+ end
37
+ end
38
+
39
+ def title
40
+ @user.nil? ? 'Loading...' : "@#{@user.screen_name} favorites"
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,36 @@
1
+ module Twterm
2
+ module Tab
3
+ module Statuses
4
+ class Home
5
+ include Base
6
+
7
+ def close
8
+ fail NotClosableError
9
+ end
10
+
11
+ def fetch
12
+ @client.home_timeline.then do |statuses|
13
+ statuses.each(&method(:prepend))
14
+ sort
15
+ yield if block_given?
16
+ end
17
+ end
18
+
19
+ def initialize(client)
20
+ fail ArgumentError, 'argument must be an instance of Client class' unless client.is_a? Client
21
+
22
+ super()
23
+ @client = client
24
+ @client.on_timeline_status(&method(:prepend))
25
+
26
+ fetch { scroller.move_to_top }
27
+ @auto_reloader = Scheduler.new(180) { fetch }
28
+ end
29
+
30
+ def title
31
+ 'Home'.freeze
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,47 @@
1
+ module Twterm
2
+ module Tab
3
+ module Statuses
4
+ class ListTimeline
5
+ include Base
6
+ include Dumpable
7
+
8
+ attr_reader :list
9
+
10
+ def initialize(list_id)
11
+ super()
12
+
13
+ self.title = 'Loading...'.freeze
14
+
15
+ List.find_or_fetch(list_id).then do |list|
16
+ @list = list
17
+ self.title = @list.full_name
18
+ TabManager.instance.refresh_window
19
+ fetch { scroller.move_to_top }
20
+ @auto_reloader = Scheduler.new(300) { fetch }
21
+ end
22
+ end
23
+
24
+ def fetch
25
+ Client.current.list_timeline(@list).then do |statuses|
26
+ statuses.reverse.each(&method(:prepend))
27
+ sort
28
+ yield if block_given?
29
+ end
30
+ end
31
+
32
+ def close
33
+ @auto_reloader.kill if @auto_reloader
34
+ super
35
+ end
36
+
37
+ def ==(other)
38
+ other.is_a?(self.class) && list == other.list
39
+ end
40
+
41
+ def dump
42
+ @list.id
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,40 @@
1
+ module Twterm
2
+ module Tab
3
+ module Statuses
4
+ class Mentions
5
+ include Base
6
+
7
+ def close
8
+ fail NotClosableError
9
+ end
10
+
11
+ def fetch
12
+ @client.mentions.then do |statuses|
13
+ statuses.reverse.each(&method(:prepend))
14
+ sort
15
+ yield if block_given?
16
+ end
17
+ end
18
+
19
+ def initialize(client)
20
+ fail ArgumentError, 'argument must be an instance of Client class' unless client.is_a? Client
21
+
22
+ super()
23
+
24
+ @client = client
25
+ @client.on_mention do |status|
26
+ prepend(status)
27
+ Notifier.instance.show_message "Mentioned by @#{status.user.screen_name}: #{status.text}"
28
+ end
29
+
30
+ fetch { scroller.move_to_top }
31
+ @auto_reloader = Scheduler.new(300) { fetch }
32
+ end
33
+
34
+ def title
35
+ 'Mentions'.freeze
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,42 @@
1
+ module Twterm
2
+ module Tab
3
+ module Statuses
4
+ class Search
5
+ include Base
6
+ include Dumpable
7
+
8
+ attr_reader :query
9
+
10
+ def ==(other)
11
+ other.is_a?(self.class) && query == other.query
12
+ end
13
+
14
+ def close
15
+ @auto_reloader.kill if @auto_reloader
16
+ super
17
+ end
18
+
19
+ def dump
20
+ @query
21
+ end
22
+
23
+ def fetch
24
+ Client.current.search(@query).then do |statuses|
25
+ statuses.reverse.each(&method(:prepend))
26
+ yield if block_given?
27
+ end
28
+ end
29
+
30
+ def initialize(query)
31
+ super()
32
+
33
+ @query = query
34
+ @title = "\"#{@query}\""
35
+
36
+ fetch { scroller.move_to_top }
37
+ @auto_reloader = Scheduler.new(300) { fetch }
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,51 @@
1
+ module Twterm
2
+ module Tab
3
+ module Statuses
4
+ class UserTimeline
5
+ include Base
6
+ include Dumpable
7
+
8
+ attr_reader :user, :user_id
9
+
10
+ def ==(other)
11
+ other.is_a?(self.class) && user_id == other.user_id
12
+ end
13
+
14
+ def close
15
+ @auto_reloader.kill if @auto_reloader
16
+ super
17
+ end
18
+
19
+ def dump
20
+ @user.id
21
+ end
22
+
23
+ def fetch
24
+ Client.current.user_timeline(@user.id).then do |statuses|
25
+ statuses.reverse.each(&method(:prepend))
26
+ sort
27
+ yield if block_given?
28
+ end
29
+ end
30
+
31
+ def initialize(user_id)
32
+ super()
33
+
34
+ @user_id = user_id
35
+
36
+ User.find_or_fetch(user_id).then do |user|
37
+ @user = user
38
+ TabManager.instance.refresh_window
39
+
40
+ fetch { scroller.move_to_top }
41
+ @auto_reloader = Scheduler.new(120) { fetch }
42
+ end
43
+ end
44
+
45
+ def title
46
+ @user.nil? ? 'Loading...' : "@#{@user.screen_name} timeline"
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -1,44 +1,313 @@
1
1
  module Twterm
2
2
  module Tab
3
3
  class UserTab
4
- include StatusesTab
4
+ include Base
5
5
  include Dumpable
6
+ include Scrollable
6
7
 
7
- attr_reader :user
8
+ attr_reader :user_id
8
9
 
9
10
  def ==(other)
10
- other.is_a?(self.class) && user == other.user
11
+ other.is_a?(self.class) && user_id == other.user_id
11
12
  end
12
13
 
13
- def close
14
- @auto_reloader.kill if @auto_reloader
15
- super
14
+ def dump
15
+ user_id
16
16
  end
17
17
 
18
- def dump
19
- @user.id
18
+ def drawable_item_count
19
+ (window.maxy - 11 - bio_height).div(2)
20
20
  end
21
21
 
22
22
  def fetch
23
- Client.current.user_timeline(@user.id) do |statuses|
24
- statuses.reverse.each(&method(:prepend))
25
- sort
26
- yield if block_given?
27
- end
23
+ update
28
24
  end
29
25
 
30
26
  def initialize(user_id)
31
27
  super()
32
28
 
33
- User.find_or_fetch(user_id) do |user|
34
- @user = user
35
- @title = "@#{@user.screen_name}"
36
- TabManager.instance.refresh_window
29
+ self.title = 'Loading...'.freeze
30
+ @user_id = user_id
31
+
32
+ User.find_or_fetch(user_id).then do |user|
33
+ refresh
34
+
35
+ Client.current.lookup_friendships
36
+ self.title = "@#{user.screen_name}"
37
+ end
38
+ end
39
+
40
+ def items
41
+ items = %i(
42
+ open_timeline_tab
43
+ show_friends
44
+ show_followers
45
+ show_favorites
46
+ )
47
+ items << :open_website unless user.website.nil?
48
+ items << :toggle_follow unless myself?
49
+ items << :toggle_mute unless myself?
50
+ items << :toggle_block unless myself?
51
+
52
+ items
53
+ end
54
+
55
+ def respond_to_key(key)
56
+ return true if scroller.respond_to_key(key)
57
+
58
+ case key
59
+ when ?F
60
+ follow
61
+ when 10
62
+ perform_selected_action
63
+ when ?t
64
+ open_timeline_tab
65
+ when ?W
66
+ open_website
67
+ else
68
+ return false
69
+ end
70
+
71
+ true
72
+ end
73
+
74
+ private
75
+
76
+ def bio_height
77
+ 320.div(window.maxx - 6) + 1
78
+ end
79
+
80
+ def block
81
+ Client.current.block(user_id).then do |users|
82
+ refresh
83
+
84
+ user = users.first
85
+ msg = "Blocked @#{user.screen_name}"
86
+ Notifier.instance.show_message msg
87
+ end
88
+ end
89
+
90
+ def blocking?
91
+ user.blocked_by?(Client.current.user_id)
92
+ end
93
+
94
+ def follow
95
+ Client.current.follow(user_id).then do |users|
96
+ refresh
97
+
98
+ user = users.first
99
+ if user.protected?
100
+ msg = "Sent following request to @#{user.screen_name}"
101
+ else
102
+ msg = "Followed @#{user.screen_name}"
103
+ end
104
+ Notifier.instance.show_message msg
105
+ end
106
+ end
107
+
108
+ def followed?
109
+ user.followed_by?(Client.current.user_id)
110
+ end
111
+
112
+ def following?
113
+ user.followed_by?(Client.current.user_id)
114
+ end
115
+
116
+ def following_requested?
117
+ user.following_requested_by?(Client.current.user_id)
118
+ end
119
+
120
+ def mute
121
+ Client.current.mute(user_id).then do |users|
122
+ refresh
123
+
124
+ user = users.first
125
+ msg = "Muted @#{user.screen_name}"
126
+ Notifier.instance.show_message msg
127
+ end
128
+ end
129
+
130
+ def muting?
131
+ user.muted_by?(Client.current.user_id)
132
+ end
133
+
134
+ def myself?
135
+ user_id == Client.current.user_id
136
+ end
137
+
138
+ def open_timeline_tab
139
+ tab = Tab::Statuses::UserTimeline.new(user_id)
140
+ TabManager.instance.add_and_show(tab)
141
+ end
142
+
143
+ def open_website
144
+ return if user.website.nil?
145
+
146
+ Launchy.open(user.website)
147
+ rescue Launchy::CommandNotFoundError
148
+ Notifier.instance.show_error 'Browser not found'
149
+ end
37
150
 
38
- fetch { scroller.move_to_top }
39
- @auto_reloader = Scheduler.new(120) { fetch }
151
+ def perform_selected_action
152
+ case scroller.current_item
153
+ when :open_timeline_tab
154
+ open_timeline_tab
155
+ when :open_website
156
+ open_website
157
+ when :show_favorites
158
+ show_favorites
159
+ when :show_followers
160
+ show_followers
161
+ when :show_friends
162
+ show_friends
163
+ when :toggle_block
164
+ blocking? ? unblock : block
165
+ when :toggle_follow
166
+ if following?
167
+ unfollow
168
+ elsif following_requested?
169
+ # do nothing
170
+ else
171
+ follow
172
+ end
173
+ when :toggle_mute
174
+ muting? ? unmute : mute
40
175
  end
41
176
  end
177
+
178
+ def show_favorites
179
+ tab = Tab::Statuses::Favorites.new(user_id)
180
+ TabManager.instance.add_and_show(tab)
181
+ end
182
+
183
+ def show_followers
184
+ tab = Tab::Users::Followers.new(user_id)
185
+ TabManager.instance.add_and_show(tab)
186
+ end
187
+
188
+ def show_friends
189
+ tab = Tab::Users::Friends.new(user_id)
190
+ TabManager.instance.add_and_show(tab)
191
+ end
192
+
193
+ def unblock
194
+ Client.current.unblock(user_id).then do |users|
195
+ refresh
196
+
197
+ user = users.first
198
+ msg = "Unblocked @#{user.screen_name}"
199
+ Notifier.instance.show_message msg
200
+ end
201
+ end
202
+
203
+ def unfollow
204
+ Client.current.unfollow(user_id).then do |users|
205
+ refresh
206
+
207
+ user = users.first
208
+ msg = "Unfollowed @#{user.screen_name}"
209
+ Notifier.instance.show_message msg
210
+ end
211
+ end
212
+
213
+ def unmute
214
+ Client.current.unmute(user_id).then do |users|
215
+ refresh
216
+
217
+ user = users.first
218
+ msg = "Unmuted @#{user.screen_name}"
219
+ Notifier.instance.show_message msg
220
+ end
221
+ end
222
+
223
+ def update
224
+ if user.nil?
225
+ User.find_or_fetch(user_id).then { update }
226
+ return
227
+ end
228
+
229
+ window.setpos(2, 3)
230
+ window.bold { window.addstr(user.name) }
231
+ window.addstr(" (@#{user.screen_name})")
232
+
233
+ window.with_color(:yellow) { window.addstr(' [protected]') } if user.protected?
234
+ window.with_color(:cyan) { window.addstr(' [verified]') } if user.verified?
235
+
236
+ window.setpos(5, 4)
237
+ if myself?
238
+ window.with_color(:yellow) { window.addstr(' [your account]') }
239
+ else
240
+ window.with_color(:green) { window.addstr(' [following]') } if following?
241
+ window.with_color(:white) { window.addstr(' [not following]') } if !following? && !blocking? && !following_requested?
242
+ window.with_color(:yellow) { window.addstr(' [following requested]') } if following_requested?
243
+ window.with_color(:cyan) { window.addstr(' [follows you]') } if followed?
244
+ window.with_color(:red) { window.addstr(' [muting]') } if muting?
245
+ window.with_color(:red) { window.addstr(' [blocking]') } if blocking?
246
+ end
247
+
248
+ user.description.split_by_width(window.maxx - 6).each.with_index(7) do |line, i|
249
+ window.setpos(i, 5)
250
+ window.addstr(line)
251
+ end
252
+
253
+ window.setpos(8 + bio_height, 5)
254
+ window.addstr("Location: #{user.location}") unless user.location.nil?
255
+
256
+ current_line = 11 + bio_height
257
+
258
+ drawable_items.each.with_index(0) do |item, i|
259
+ if scroller.current_item? i
260
+ window.setpos(current_line, 3)
261
+ window.with_color(:black, :magenta) { window.addch(' ') }
262
+ end
263
+
264
+ window.setpos(current_line, 5)
265
+ case item
266
+ when :toggle_block
267
+ if blocking?
268
+ window.addstr(' Unblock this user')
269
+ else
270
+ window.addstr(' Block this user')
271
+ end
272
+ when :toggle_follow
273
+ if following?
274
+ window.addstr(' Unfollow this user')
275
+ elsif following_requested?
276
+ window.addstr(' Following request sent')
277
+ else
278
+ window.addstr('[ ] Follow this user')
279
+ window.setpos(current_line, 6)
280
+ window.bold { window.addch(?F) }
281
+ end
282
+ when :toggle_mute
283
+ if muting?
284
+ window.addstr(' Unmute this user')
285
+ else
286
+ window.addstr(' Mute this user')
287
+ end
288
+ when :open_timeline_tab
289
+ window.addstr("[ ] #{user.statuses_count.format} tweets")
290
+ window.setpos(current_line, 6)
291
+ window.bold { window.addch(?t) }
292
+ when :open_website
293
+ window.addstr("[ ] Open website (#{user.website})")
294
+ window.setpos(current_line, 6)
295
+ window.bold { window.addch(?W) }
296
+ when :show_favorites
297
+ window.addstr(" #{user.favorites_count.format} favorites")
298
+ when :show_followers
299
+ window.addstr(" #{user.followers_count.format} followers")
300
+ when :show_friends
301
+ window.addstr(" #{user.friends_count.format} following")
302
+ end
303
+
304
+ current_line += 2
305
+ end
306
+ end
307
+
308
+ def user
309
+ User.find(user_id)
310
+ end
42
311
  end
43
312
  end
44
313
  end