twterm 1.1.3 → 1.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.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/bin/twterm +4 -4
  3. data/lib/twterm/app.rb +19 -4
  4. data/lib/twterm/client.rb +8 -470
  5. data/lib/twterm/direct_message.rb +82 -0
  6. data/lib/twterm/direct_message_composer.rb +74 -0
  7. data/lib/twterm/direct_message_manager.rb +52 -0
  8. data/lib/twterm/event/base.rb +22 -0
  9. data/lib/twterm/event/direct_message/fetched.rb +10 -0
  10. data/lib/twterm/event/favorite.rb +18 -0
  11. data/lib/twterm/event/follow.rb +17 -0
  12. data/lib/twterm/event/notification.rb +33 -0
  13. data/lib/twterm/event/open_uri.rb +11 -0
  14. data/lib/twterm/event/screen/resize.rb +13 -0
  15. data/lib/twterm/event/status/base.rb +14 -0
  16. data/lib/twterm/event/status/delete.rb +13 -0
  17. data/lib/twterm/event/status/mention.rb +10 -0
  18. data/lib/twterm/event/status/timeline.rb +10 -0
  19. data/lib/twterm/event_dispatcher.rb +59 -0
  20. data/lib/twterm/filter_query_window.rb +11 -5
  21. data/lib/twterm/filterable_list.rb +6 -1
  22. data/lib/twterm/notifier.rb +39 -15
  23. data/lib/twterm/promise.rb +2 -2
  24. data/lib/twterm/publisher.rb +16 -0
  25. data/lib/twterm/rest_client.rb +401 -0
  26. data/lib/twterm/screen.rb +16 -13
  27. data/lib/twterm/status.rb +12 -1
  28. data/lib/twterm/streaming_client.rb +103 -0
  29. data/lib/twterm/subscriber.rb +33 -0
  30. data/lib/twterm/tab/base.rb +13 -6
  31. data/lib/twterm/tab/direct_message/conversation.rb +103 -0
  32. data/lib/twterm/tab/direct_message/conversation_list.rb +99 -0
  33. data/lib/twterm/tab/key_assignments_cheatsheet.rb +3 -2
  34. data/lib/twterm/tab/new/list.rb +5 -3
  35. data/lib/twterm/tab/new/search.rb +3 -2
  36. data/lib/twterm/tab/new/start.rb +17 -2
  37. data/lib/twterm/tab/new/user.rb +6 -3
  38. data/lib/twterm/tab/statuses/base.rb +18 -11
  39. data/lib/twterm/tab/statuses/conversation.rb +3 -2
  40. data/lib/twterm/tab/statuses/favorites.rb +3 -2
  41. data/lib/twterm/tab/statuses/home.rb +10 -4
  42. data/lib/twterm/tab/statuses/list_timeline.rb +3 -2
  43. data/lib/twterm/tab/statuses/mentions.rb +6 -6
  44. data/lib/twterm/tab/statuses/search.rb +4 -3
  45. data/lib/twterm/tab/statuses/user_timeline.rb +3 -2
  46. data/lib/twterm/tab/user_tab.rb +26 -16
  47. data/lib/twterm/tab/users/base.rb +3 -2
  48. data/lib/twterm/tab/users/followers.rb +3 -2
  49. data/lib/twterm/tab/users/friends.rb +3 -2
  50. data/lib/twterm/tab_manager.rb +20 -8
  51. data/lib/twterm/tweetbox.rb +5 -2
  52. data/lib/twterm/uri_opener.rb +25 -0
  53. data/lib/twterm/user.rb +11 -1
  54. data/lib/twterm/utils.rb +13 -0
  55. data/lib/twterm/version.rb +1 -1
  56. data/lib/twterm.rb +0 -3
  57. data/spec/twterm/event/screen/resize_spec.rb +11 -0
  58. data/spec/twterm/event_dispatcher_spec.rb +19 -0
  59. data/twterm.gemspec +1 -1
  60. metadata +29 -7
  61. data/lib/twterm/notification/base.rb +0 -24
  62. data/lib/twterm/notification/error.rb +0 -19
  63. data/lib/twterm/notification/message.rb +0 -19
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f098da25f566b0f23f9749d9f406b743b767c316
4
- data.tar.gz: 729b1e86520c873898705530f1da7ee92c700497
3
+ metadata.gz: 4b97eeb5e737a698dcfde20b7e1f9284fa9d60c7
4
+ data.tar.gz: 8cd8793ff080f41519449fac75e9a88cd9e84deb
5
5
  SHA512:
6
- metadata.gz: 2d2c9b570b9ab523e03f90fbc0ace1083f8ed747c4c2440b9637652fedaf8976d970836f2436288f509396f106ae8636e11f3fb7ead68e216bae0dc76a19ac86
7
- data.tar.gz: 23519d2aa0809a3eb05dc6b1efa20a78bbdc4698fb0df299ee1ee45132015b8073f394a0bd12e8eb8173c36e27020cba684a52c58cdbdc3fea7213553ed31c19
6
+ metadata.gz: 114e722d6092d263be12a5b1d4bd102011c47b8cf9ff0a1893fcd9f6b25b562735f19725b8958fbcdad5c5a29a64490c50fe71923357e17fed43764374b63551
7
+ data.tar.gz: f78c9f33143bf6a3b27f0adae41bb0983d187340fa021ce95a781948d1b3ec4dd4e81100b40619741974ed0e45d29a35e4a87340718b8895ff9ed9d9a0b7f73c
data/bin/twterm CHANGED
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require 'twterm'
4
-
5
3
  if ARGV.count == 1 && (%w(-v --version).include?(ARGV.first))
4
+ require 'twterm/version'
6
5
  puts 'twterm version %s' % Twterm::VERSION
7
6
  exit
7
+ else
8
+ require 'twterm'
9
+ Twterm::App.instance.run
8
10
  end
9
-
10
- Twterm::App.instance.run
data/lib/twterm/app.rb CHANGED
@@ -1,8 +1,13 @@
1
+ require 'curses'
2
+ require 'twterm/event/screen/resize'
3
+ require 'twterm/uri_opener'
4
+
1
5
  module Twterm
2
6
  class App
7
+ include Publisher
3
8
  include Singleton
4
9
 
5
- DATA_DIR = "#{ENV['HOME']}/.twterm"
10
+ DATA_DIR = "#{ENV['HOME']}/.twterm".freeze
6
11
 
7
12
  def initialize
8
13
  Dir.mkdir(DATA_DIR, 0700) unless File.directory?(DATA_DIR)
@@ -16,18 +21,28 @@ module Twterm
16
21
  TabManager.instance.add_and_show(timeline)
17
22
 
18
23
  mentions_tab = Tab::Statuses::Mentions.new(client)
24
+
19
25
  TabManager.instance.add(mentions_tab)
20
26
  TabManager.instance.recover_tabs
21
27
 
22
28
  Screen.instance.refresh
23
29
 
24
- client.user_stream
30
+ client.connect_user_stream
25
31
 
26
32
  reset_interruption_handler
27
33
 
28
- Signal.trap(:WINCH) { Screen.instance.resize }
34
+ URIOpener.instance
35
+
36
+ resize = proc do
37
+ next if Curses.closed?
38
+
39
+ lines = `tput lines`.to_i
40
+ cols = `tput cols`.to_i
41
+ publish(Event::Screen::Resize.new(lines, cols))
42
+ end
29
43
 
30
- Scheduler.new(60) { Screen.instance.resize }
44
+ Signal.trap(:WINCH, &resize)
45
+ Scheduler.new(60, &resize)
31
46
  end
32
47
 
33
48
  def run
data/lib/twterm/client.rb CHANGED
@@ -1,122 +1,15 @@
1
+ require 'twterm/rest_client'
2
+ require 'twterm/streaming_client'
3
+
1
4
  module Twterm
2
5
  class Client
3
- attr_reader :user_id, :screen_name
6
+ include RESTClient
7
+ include StreamingClient
4
8
 
5
- CREATE_STATUS_PROC = -> (s) { Status.new(s) }
6
- CONSUMER_KEY = 'vLNSVFgXclBJQJRZ7VLMxL9lA'.freeze
7
- CONSUMER_SECRET = 'OFLKzrepRG2p1hq0nUB9j2S9ndFQoNTPheTpmOY0GYw55jGgS5'.freeze
9
+ attr_reader :user_id, :screen_name
8
10
 
9
11
  @@instances = []
10
12
 
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
-
21
- def destroy_status(status)
22
- send_request_without_catch do
23
- rest_client.destroy_status(status.id)
24
- Notifier.instance.show_message('Your tweet has been deleted')
25
- end.catch do |reason|
26
- case reason
27
- when Twitter::Error::NotFound, Twitter::Error::Forbidden
28
- Notifier.instance.show_error 'You cannot destroy that status'
29
- else
30
- raise reason
31
- end
32
- end.catch(&show_error)
33
- end
34
-
35
- def favorite(status)
36
- return false unless status.is_a? Status
37
-
38
- send_request do
39
- rest_client.favorite(status.id)
40
- end.then do
41
- status.favorite!
42
- Notifier.instance.show_message('Successfully liked: @%s "%s"' % [
43
- status.user.screen_name, status.text
44
- ])
45
- end
46
- end
47
-
48
- def favorites(user_id = nil)
49
- user_id ||= self.user_id
50
-
51
- send_request do
52
- rest_client.favorites(user_id, count: 200)
53
- end.then do |tweets|
54
- tweets.map(&CREATE_STATUS_PROC)
55
- end
56
- end
57
-
58
- def fetch_muted_users
59
- send_request do
60
- @muted_user_ids = rest_client.muted_ids.to_a
61
- end
62
- end
63
-
64
- def follow(*user_ids)
65
- send_request do
66
- rest_client.follow(*user_ids)
67
- end.then do |users|
68
- users.each do |user|
69
- if user.protected?
70
- Friendship.following_requested(self.user_id, user.id)
71
- else
72
- Friendship.follow(self.user_id, user.id)
73
- end
74
- end
75
- end
76
- end
77
-
78
- def followers(user_id = nil)
79
- user_id ||= self.user_id
80
-
81
- m = Mutex.new
82
-
83
- send_request do
84
- rest_client.follower_ids(user_id).each_slice(100) do |user_ids|
85
- m.synchronize do
86
- users = rest_client.users(*user_ids).map(& -> u { User.new(u) })
87
- users.each do |user|
88
- Friendship.follow(user.id, self.user_id)
89
- end if user_id == self.user_id
90
- yield users
91
- end
92
- end
93
- end
94
- end
95
-
96
- def friends(user_id = nil)
97
- user_id ||= self.user_id
98
-
99
- m = Mutex.new
100
-
101
- send_request do
102
- rest_client.friend_ids(user_id).each_slice(100) do |user_ids|
103
- m.synchronize do
104
- yield rest_client.users(*user_ids).map(& -> u { User.new(u) })
105
- end
106
- end
107
- end
108
- end
109
-
110
- def home_timeline
111
- send_request do
112
- rest_client.home_timeline(count: 200)
113
- end.then do |statuses|
114
- statuses
115
- .select(&@mute_filter)
116
- .map(&CREATE_STATUS_PROC)
117
- end
118
- end
119
-
120
13
  def initialize(user_id, screen_name, access_token, access_token_secret)
121
14
  @user_id, @screen_name = user_id, screen_name
122
15
  @access_token, @access_token_secret = access_token, access_token_secret
@@ -134,324 +27,9 @@ module Twterm
134
27
 
135
28
  initialize_user_stream
136
29
 
137
- @@instances << self
138
- end
139
-
140
- def initialize_user_stream
141
- return if user_stream_initialized?
142
-
143
- streaming_client.on_friends do
144
- user_stream_connected!
145
- end
146
-
147
- streaming_client.on_timeline_status do |tweet|
148
- status = Status.new(tweet)
149
- invoke_callbacks(:timeline_status, status)
150
- invoke_callbacks(:mention, status) if status.text.include? "@#{@screen_name}"
151
- end
152
-
153
- streaming_client.on_delete do |status_id|
154
- timeline.delete_status(status_id)
155
- end
156
-
157
- streaming_client.on_event(:favorite) do |event|
158
- break if event[:source][:screen_name] == @screen_name
30
+ direct_message_manager
159
31
 
160
- user = event[:source][:screen_name]
161
- text = event[:target_object][:text]
162
- message = "@#{user} has favorited your tweet: #{text}"
163
- Notifier.instance.show_message(message)
164
- end
165
-
166
- streaming_client.on_event(:follow) do |event|
167
- screen_name = event[:source][:screen_name]
168
- break if screen_name == @screen_name
169
-
170
- Notifier.instance.show_message('@%s has followed you' % screen_name)
171
- end
172
-
173
- streaming_client.on_no_data_received do
174
- user_stream_disconnected!
175
- user_stream
176
- end
177
-
178
- user_stream_initialized!
179
- end
180
-
181
- def list(list_id)
182
- send_request do
183
- rest_client.list(list_id)
184
- end.then do |list|
185
- List.new(list)
186
- end
187
- end
188
-
189
- def list_timeline(list)
190
- fail ArgumentError,
191
- 'argument must be an instance of List class' unless list.is_a? List
192
- send_request do
193
- rest_client.list_timeline(list.id, count: 200)
194
- end.then do |statuses|
195
- statuses
196
- .select(&@mute_filter)
197
- .map(&CREATE_STATUS_PROC)
198
- end
199
- end
200
-
201
- def lists
202
- send_request do
203
- rest_client.lists
204
- end.then do |lists|
205
- lists.map { |list| List.new(list) }
206
- end
207
- end
208
-
209
- def lookup_friendships
210
- user_ids = User.ids.reject { |id| Friendship.already_looked_up?(id) }
211
- send_request_without_catch do
212
- user_ids.each_slice(100) do |chunked_user_ids|
213
- friendships = rest_client.friendships(*chunked_user_ids)
214
- friendships.each do |friendship|
215
- id = friendship.id
216
- client_id = user_id
217
-
218
- conn = friendship.connections
219
- conn.include?('blocking') ? Friendship.block(client_id, id) : Friendship.unblock(client_id, id)
220
- conn.include?('following') ? Friendship.follow(client_id, id) : Friendship.unfollow(client_id, id)
221
- conn.include?('following_requested') ? Friendship.following_requested(client_id, id) : Friendship.following_not_requested(client_id, id)
222
- conn.include?('followed_by') ? Friendship.follow(id, client_id) : Friendship.unfollow(id, client_id)
223
- conn.include?('muting') ? Friendship.mute(client_id, id) : Friendship.unmute(client_id, id)
224
- end
225
- end
226
- end.catch do |e|
227
- case e
228
- when Twitter::Error::TooManyRequests
229
- # do nothing
230
- else
231
- raise e
232
- end
233
- end.catch(&show_error)
234
- end
235
-
236
- def mentions
237
- send_request do
238
- rest_client.mentions(count: 200)
239
- end.then do |statuses|
240
- statuses
241
- .select(&@mute_filter)
242
- .map(&CREATE_STATUS_PROC)
243
- end
244
- end
245
-
246
- def mute(user_ids)
247
- send_request do
248
- rest_client.mute(*user_ids)
249
- end.then do |users|
250
- users.each do |user|
251
- Friendship.mute(self.user_id, user.id)
252
- end
253
- end
254
- end
255
-
256
- def on_mention(&block)
257
- fail ArgumentError, 'no block given' unless block_given?
258
- on(:mention, &block)
259
- end
260
-
261
- def on_timeline_status(&block)
262
- fail ArgumentError, 'no block given' unless block_given?
263
- on(:timeline_status, &block)
264
- end
265
-
266
- def post(text, in_reply_to = nil)
267
- send_request do
268
- if in_reply_to.is_a? Status
269
- text = "@#{in_reply_to.user.screen_name} #{text}"
270
- rest_client.update(text, in_reply_to_status_id: in_reply_to.id)
271
- else
272
- rest_client.update(text)
273
- end
274
- Notifier.instance.show_message('Your tweet has been posted')
275
- end
276
- end
277
-
278
- def rest_client
279
- @rest_client ||= Twitter::REST::Client.new do |config|
280
- config.consumer_key = CONSUMER_KEY
281
- config.consumer_secret = CONSUMER_SECRET
282
- config.access_token = @access_token
283
- config.access_token_secret = @access_token_secret
284
- end
285
- end
286
-
287
- def retweet(status)
288
- fail ArgumentError,
289
- 'argument must be an instance of Status class' unless status.is_a? Status
290
-
291
- send_request_without_catch do
292
- rest_client.retweet!(status.id)
293
- end.then do
294
- status.retweet!
295
- Notifier.instance.show_message('Successfully retweeted: @%s "%s"' % [
296
- status.user.screen_name, status.text
297
- ])
298
- end.catch do |reason|
299
- message =
300
- case reason
301
- when Twitter::Error::AlreadyRetweeted
302
- 'The status is already retweeted'
303
- when Twitter::Error::NotFound
304
- 'The status is not found'
305
- when Twitter::Error::Forbidden
306
- if status.user.id == user_id # when the status is mine
307
- 'You cannot retweet your own status'
308
- else # when the status is not mine
309
- 'The status is protected'
310
- end
311
- else
312
- raise e
313
- end
314
- Notifier.instance.show_error "Retweet attempt failed: #{message}"
315
- end.catch(&show_error)
316
- end
317
-
318
- def saved_search
319
- send_request do
320
- rest_client.saved_searches
321
- end
322
- end
323
-
324
- def search(query)
325
- send_request do
326
- rest_client.search(query, count: 100).attrs[:statuses]
327
- end.then do |statuses|
328
- statuses
329
- .map(&Twitter::Tweet.method(:new))
330
- .map(&CREATE_STATUS_PROC)
331
- end
332
- end
333
-
334
- def show_status(status_id)
335
- send_request do
336
- rest_client.status(status_id)
337
- end.then do |status|
338
- Status.new(status)
339
- end
340
- end
341
-
342
- def show_user(query)
343
- send_request_without_catch do
344
- rest_client.user(query)
345
- end.catch do |reason|
346
- case reason
347
- when Twitter::Error::NotFound
348
- nil
349
- else
350
- raise reason
351
- end
352
- end.catch(&show_error).then do |user|
353
- user.nil? ? nil : User.new(user)
354
- end
355
- end
356
-
357
- def streaming_client
358
- @streaming_client ||= TweetStream::Client.new(
359
- consumer_key: CONSUMER_KEY,
360
- consumer_secret: CONSUMER_SECRET,
361
- oauth_token: @access_token,
362
- oauth_token_secret: @access_token_secret,
363
- auth_method: :oauth
364
- )
365
- end
366
-
367
- def unblock(*user_ids)
368
- send_request do
369
- rest_client.unblock(*user_ids)
370
- end.then do |users|
371
- users.each do |user|
372
- Friendship.unblock(self.user_id, user.id)
373
- end
374
- end
375
- end
376
-
377
- def unfavorite(status)
378
- fail ArgumentError,
379
- 'argument must be an instance of Status class' unless status.is_a? Status
380
-
381
- send_request do
382
- rest_client.unfavorite(status.id)
383
- end.then do
384
- status.unfavorite!
385
- Notifier.instance.show_message('Successfully unliked: @%s "%s"' % [
386
- status.user.screen_name, status.text
387
- ])
388
- end
389
- end
390
-
391
- def unfollow(*user_ids)
392
- send_request do
393
- rest_client.unfollow(*user_ids)
394
- end.then do |users|
395
- users.each do |user|
396
- Friendship.unfollow(self.user_id, user.id)
397
- end
398
- end
399
- end
400
-
401
- def unmute(user_ids)
402
- send_request do
403
- rest_client.unmute(*user_ids)
404
- end.then do |users|
405
- users.each do |user|
406
- Friendship.unmute(self.user_id, user.id)
407
- end
408
- end
409
- end
410
-
411
- def user_stream
412
- streaming_client.stop_stream
413
-
414
- @streaming_thread = Thread.new do
415
- begin
416
- Notifier.instance.show_message 'Trying to connect to Twitter...'
417
- streaming_client.userstream
418
- rescue EventMachine::ConnectionError
419
- Notifier.instance.show_error 'Connection failed'
420
- sleep 30
421
- retry
422
- end
423
- end
424
- end
425
-
426
- def user_stream_connected?
427
- @user_stream_connected || false
428
- end
429
-
430
- def user_stream_connected!
431
- Notifier.instance.show_message 'Connection established' unless user_stream_connected?
432
- @user_stream_connected = true
433
- end
434
-
435
- def user_stream_disconnected!
436
- @user_stream_connected = false
437
- end
438
-
439
- def user_stream_initialized?
440
- @user_stream_initialized || false
441
- end
442
-
443
- def user_stream_initialized!
444
- @user_stream_initialized = true
445
- end
446
-
447
- def user_timeline(user_id)
448
- send_request do
449
- rest_client.user_timeline(user_id, count: 200)
450
- end.then do |statuses|
451
- statuses
452
- .select(&@mute_filter)
453
- .map(&CREATE_STATUS_PROC)
454
- end
32
+ @@instances << self
455
33
  end
456
34
 
457
35
  def self.new(user_id, screen_name, token, secret)
@@ -463,45 +41,5 @@ module Twterm
463
41
  def self.current
464
42
  @@instances[0]
465
43
  end
466
-
467
- private
468
-
469
- def show_error
470
- proc do |e|
471
- case e
472
- when Twitter::Error
473
- Notifier.instance.show_error "Failed to send request: #{e.message}"
474
- else
475
- raise e
476
- end
477
- end.freeze
478
- end
479
-
480
- def invoke_callbacks(event, data = nil)
481
- return if @callbacks[event].nil?
482
-
483
- @callbacks[event].each { |cb| cb.call(data) }
484
- self
485
- end
486
-
487
- def on(event, &block)
488
- @callbacks[event] ||= []
489
- @callbacks[event] << block
490
- self
491
- end
492
-
493
- def send_request(&block)
494
- send_request_without_catch(&block).catch(&show_error)
495
- end
496
-
497
- def send_request_without_catch(&block)
498
- Promise.new do |resolve, reject|
499
- begin
500
- resolve.(block.call)
501
- rescue Twitter::Error => reason
502
- reject.(reason)
503
- end
504
- end
505
- end
506
44
  end
507
45
  end
@@ -0,0 +1,82 @@
1
+ require 'twterm/user'
2
+ require 'twterm/utils'
3
+
4
+ module Twterm
5
+ class DirectMessage
6
+ attr_reader :id, :created_at, :recipient, :sender, :text
7
+
8
+ @@instances = {}
9
+
10
+ def initialize(message)
11
+ @id = message.id
12
+ update!(message)
13
+
14
+ @@instances[id] = self
15
+ end
16
+
17
+ def ==(other)
18
+ other.is_a?(self.class) && id == other.id
19
+ end
20
+
21
+ def date
22
+ format = Time.now - @created_at < 86_400 ? '%H:%M:%S' : '%Y-%m-%d %H:%M:%S'
23
+ @created_at.strftime(format)
24
+ end
25
+
26
+ def matches?(q)
27
+ [
28
+ sender.name,
29
+ sender.screen_name,
30
+ text
31
+ ].map(&:downcase).any? { |x| x.include?(q) }
32
+ end
33
+
34
+ def update!(message)
35
+ @created_at = message.created_at.dup.localtime
36
+ @recipient = User.new(message.recipient)
37
+ @sender = User.new(message.sender)
38
+ @text = message.text
39
+
40
+ self
41
+ end
42
+
43
+ class Conversation
44
+ include Utils
45
+
46
+ attr_reader :collocutor, :messages
47
+
48
+ def initialize(collocutor)
49
+ check_type User, collocutor
50
+
51
+ @collocutor = collocutor
52
+ @messages = []
53
+ end
54
+
55
+ def <<(message)
56
+ @messages << message if messages.find { |m| m == message }.nil?
57
+ @messages.sort_by!(&:created_at).reverse!
58
+
59
+ self
60
+ end
61
+
62
+ def matches?(q)
63
+ [
64
+ collocutor.screen_name,
65
+ collocutor.name,
66
+ preview
67
+ ].map(&:downcase).any? { |x| x.include?(q.downcase) }
68
+ end
69
+
70
+ def preview
71
+ messages.sort_by(&:created_at).last.text.gsub("\n", ' ')
72
+ end
73
+
74
+ def updated_at
75
+ updated_at = @messages.map(&:created_at).max
76
+
77
+ format = Time.now - updated_at < 86_400 ? '%H:%M:%S' : '%Y-%m-%d %H:%M:%S'
78
+ updated_at.strftime(format)
79
+ end
80
+ end
81
+ end
82
+ end