twterm 2.1.0 → 2.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 (59) hide show
  1. checksums.yaml +5 -5
  2. data/.travis.yml +4 -1
  3. data/lib/twterm.rb +1 -1
  4. data/lib/twterm/abstract_persistable_configuration.rb +118 -0
  5. data/lib/twterm/app.rb +21 -1
  6. data/lib/twterm/environment.rb +38 -0
  7. data/lib/twterm/event/message/abstract_message.rb +23 -0
  8. data/lib/twterm/event/message/error.rb +10 -0
  9. data/lib/twterm/event/message/info.rb +10 -0
  10. data/lib/twterm/event/message/success.rb +10 -0
  11. data/lib/twterm/event/message/warning.rb +10 -0
  12. data/lib/twterm/event/notification/abstract_notification.rb +17 -12
  13. data/lib/twterm/event/notification/direct_message.rb +30 -0
  14. data/lib/twterm/event/notification/favorite.rb +35 -0
  15. data/lib/twterm/event/notification/follow.rb +33 -0
  16. data/lib/twterm/event/notification/list_member_added.rb +33 -0
  17. data/lib/twterm/event/notification/mention.rb +35 -0
  18. data/lib/twterm/event/notification/quote.rb +35 -0
  19. data/lib/twterm/event/notification/retweet.rb +35 -0
  20. data/lib/twterm/key_mapper/abstract_key_mapper.rb +4 -4
  21. data/lib/twterm/list.rb +2 -1
  22. data/lib/twterm/message_window.rb +81 -0
  23. data/lib/twterm/notification_backend/abstract_notification_backend.rb +16 -0
  24. data/lib/twterm/notification_backend/inline_backend.rb +20 -0
  25. data/lib/twterm/notification_backend/terminal_notifier_backend.rb +23 -0
  26. data/lib/twterm/notification_backend/tmux_backend.rb +17 -0
  27. data/lib/twterm/notification_dispatcher.rb +36 -0
  28. data/lib/twterm/persistable_configuration_proxy.rb +82 -0
  29. data/lib/twterm/preferences.rb +66 -0
  30. data/lib/twterm/repository/abstract_expirable_entity_repository.rb +1 -1
  31. data/lib/twterm/rest_client.rb +12 -14
  32. data/lib/twterm/scheduler.rb +1 -1
  33. data/lib/twterm/screen.rb +1 -1
  34. data/lib/twterm/search_query_window.rb +2 -2
  35. data/lib/twterm/streaming_client.rb +54 -17
  36. data/lib/twterm/tab/base.rb +2 -3
  37. data/lib/twterm/tab/new/list.rb +2 -4
  38. data/lib/twterm/tab/new/start.rb +10 -0
  39. data/lib/twterm/tab/new/user.rb +2 -2
  40. data/lib/twterm/tab/preferences/index.rb +74 -0
  41. data/lib/twterm/tab/preferences/notification_backend.rb +85 -0
  42. data/lib/twterm/tab/scrollable.rb +0 -1
  43. data/lib/twterm/tab/searchable.rb +7 -7
  44. data/lib/twterm/tab/user_list_management.rb +3 -3
  45. data/lib/twterm/tab/user_tab.rb +6 -8
  46. data/lib/twterm/tab_manager.rb +4 -4
  47. data/lib/twterm/tweetbox.rb +1 -1
  48. data/lib/twterm/uri_opener.rb +2 -2
  49. data/lib/twterm/user.rb +2 -1
  50. data/lib/twterm/version.rb +1 -1
  51. data/twterm.gemspec +4 -2
  52. metadata +59 -14
  53. data/lib/twterm/event/favorite.rb +0 -18
  54. data/lib/twterm/event/follow.rb +0 -17
  55. data/lib/twterm/event/notification/error.rb +0 -13
  56. data/lib/twterm/event/notification/info.rb +0 -13
  57. data/lib/twterm/event/notification/success.rb +0 -13
  58. data/lib/twterm/event/notification/warning.rb +0 -13
  59. data/lib/twterm/notifier.rb +0 -82
@@ -0,0 +1,66 @@
1
+ require 'toml'
2
+
3
+ require 'twterm/abstract_persistable_configuration'
4
+
5
+ module Twterm
6
+ class Preferences < AbstractPersistableConfiguration
7
+ def initialize(preferences)
8
+ super(preferences)
9
+ end
10
+
11
+ # @param [Symbol] cat
12
+ # @param [Symbol] key
13
+ def [](cat, key)
14
+ validate_key!(cat, key)
15
+
16
+ configuration[cat][key]
17
+ end
18
+
19
+ def []=(cat, key, value)
20
+ validate_key!(cat, key)
21
+
22
+ configuration[cat][key] = value
23
+ end
24
+
25
+ # Returns an instance having the default value
26
+ #
27
+ # @return [Twterm::Preferences] an instance having the default value
28
+ def self.default
29
+ new({
30
+ notification_backend: {
31
+ inline: true,
32
+ terminal_notifier: false,
33
+ tmux: false,
34
+ },
35
+ })
36
+ end
37
+
38
+ # @return [Hash]
39
+ def to_h
40
+ configuration
41
+ end
42
+
43
+ # @return [Hash]
44
+ def self.structure
45
+ bool = -> x { x == true || x == false }
46
+
47
+ {
48
+ notification_backend: {
49
+ inline: bool,
50
+ terminal_notifier: bool,
51
+ tmux: bool,
52
+ },
53
+ }
54
+ end
55
+
56
+ private
57
+
58
+ alias_method :preferences, :configuration
59
+
60
+ # @raise [ArgumentError]
61
+ def validate_key!(cat, key)
62
+ raise ArgumentError, "no such category: #{cat}" unless configuration.has_key?(cat)
63
+ raise ArgumentError, "no such key: #{cat}.#{key}" unless configuration[cat].has_key?(key)
64
+ end
65
+ end
66
+ end
@@ -40,7 +40,7 @@ module Twterm
40
40
  raise NotImplementedError, '`garbage_collection_event_class` must be implemented'
41
41
  end
42
42
 
43
- def should_keep?(instance)
43
+ def should_keep?(_instance)
44
44
  raise NotImplementedError, '`should_keep?` method must be implemented'
45
45
  end
46
46
 
@@ -2,7 +2,7 @@ require 'concurrent'
2
2
  require 'twterm/direct_message'
3
3
  require 'twterm/direct_message_manager'
4
4
  require 'twterm/publisher'
5
- require 'twterm/event/notification/success'
5
+ require 'twterm/event/message/success'
6
6
 
7
7
  module Twterm
8
8
  module RESTClient
@@ -32,7 +32,7 @@ module Twterm
32
32
  msg = direct_message_repository.create(message)
33
33
  direct_message_manager.add(recipient.id, msg)
34
34
  publish(Event::DirectMessage::Fetched.new)
35
- publish(Event::Notification::Success.new('Your message to @%s has been sent' % recipient.screen_name))
35
+ publish(Event::Message::Success.new('Your message to @%s has been sent' % recipient.screen_name))
36
36
  end
37
37
  end
38
38
 
@@ -56,11 +56,11 @@ module Twterm
56
56
  send_request_without_catch do
57
57
  rest_client.destroy_status(status.id)
58
58
  publish(Event::Status::Delete.new(status.id))
59
- publish(Event::Notification::Success.new('Your tweet has been deleted'))
59
+ publish(Event::Message::Success.new('Your tweet has been deleted'))
60
60
  end.catch do |reason|
61
61
  case reason
62
62
  when Twitter::Error::NotFound, Twitter::Error::Forbidden
63
- publish(Event::Notification::Error.new('You cannot destroy that status'))
63
+ publish(Event::Message::Error.new('You cannot destroy that status'))
64
64
  else
65
65
  raise reason
66
66
  end
@@ -75,7 +75,7 @@ module Twterm
75
75
  end.then do |tweet, *_|
76
76
  status_repository.create(tweet)
77
77
 
78
- publish(Event::Notification::Success.new('Successfully liked: @%s "%s"' % [
78
+ publish(Event::Message::Success.new('Successfully liked: @%s "%s"' % [
79
79
  tweet.user.screen_name, status.text
80
80
  ]))
81
81
  end
@@ -201,7 +201,7 @@ module Twterm
201
201
  end
202
202
  end.catch do |e|
203
203
  case e
204
- when Twitter::Error::TooManyRequests
204
+ when Twitter::Error::TooManyRequests # rubocop:disable Lint/EmptyWhen
205
205
  # do nothing
206
206
  else
207
207
  raise e
@@ -250,7 +250,7 @@ module Twterm
250
250
  rest_client.update(text, options)
251
251
  end
252
252
  .then do |status|
253
- publish(Event::Notification::Success.new('Your tweet has been posted'))
253
+ publish(Event::Message::Success.new('Your tweet has been posted'))
254
254
 
255
255
  status
256
256
  end
@@ -273,7 +273,7 @@ module Twterm
273
273
  end.then do |tweet, *_|
274
274
  status_repository.create(tweet)
275
275
 
276
- publish(Event::Notification::Success.new('Successfully retweeted: @%s "%s"' % [
276
+ publish(Event::Message::Success.new('Successfully retweeted: @%s "%s"' % [
277
277
  tweet.user.screen_name, status.text
278
278
  ]))
279
279
  end.catch do |reason|
@@ -288,7 +288,7 @@ module Twterm
288
288
  else
289
289
  raise e
290
290
  end
291
- publish(Event::Notification::Error.new("Retweet attempt failed: #{message}"))
291
+ publish(Event::Message::Error.new("Retweet attempt failed: #{message}"))
292
292
  end.catch(&show_error)
293
293
  end
294
294
 
@@ -350,9 +350,7 @@ module Twterm
350
350
  end.then do |tweet, *_|
351
351
  status_repository.create(tweet)
352
352
 
353
- publish(Event::Notification::Success.new('Successfully unliked: @%s "%s"' % [
354
- tweet.user.screen_name, status.text
355
- ]))
353
+ publish(Event::Message::Success.new("Successfully unliked @#{tweet.user.screen_name}'s tweet", sutatus.text))
356
354
  end
357
355
  end
358
356
 
@@ -389,7 +387,7 @@ module Twterm
389
387
  .then do |tweet|
390
388
  status = status_repository.create(tweet)
391
389
 
392
- publish(Event::Notification::Success.new('Successfully unretweeted: @%s "%s"' % [
390
+ publish(Event::Message::Success.new('Successfully unretweeted: @%s "%s"' % [
393
391
  tweet.user.screen_name, status.text
394
392
  ]))
395
393
 
@@ -434,7 +432,7 @@ module Twterm
434
432
  proc do |e|
435
433
  case e
436
434
  when Twitter::Error
437
- publish(Event::Notification::Error.new("Failed to send request: #{e.message}"))
435
+ publish(Event::Message::Error.new("Failed to send request: #{e.message}"))
438
436
  else
439
437
  raise e
440
438
  end
@@ -25,6 +25,6 @@ class Scheduler
25
25
 
26
26
  def run
27
27
  @block.call unless @paused
28
- rescue
28
+ rescue StandardError # rubocop:disable Lint/HandleExceptions
29
29
  end
30
30
  end
data/lib/twterm/screen.rb CHANGED
@@ -24,7 +24,7 @@ module Twterm
24
24
  def refresh
25
25
  app.tab_manager.refresh_window
26
26
  app.tab_manager.current_tab.render
27
- Notifier.instance.show
27
+ MessageWindow.instance.show
28
28
  end
29
29
 
30
30
  def respond_to_key(key)
@@ -45,7 +45,7 @@ module Twterm
45
45
 
46
46
  @str.chop!
47
47
  render_current_string
48
- when 0..31
48
+ when 0..31 # rubocop:disable Lint/EmptyWhen
49
49
  # ignore control codes (\x00 - \x1f)
50
50
  else
51
51
  next if chars.empty?
@@ -116,7 +116,7 @@ module Twterm
116
116
 
117
117
  attr_reader :window
118
118
 
119
- def resize(event)
119
+ def resize(_event)
120
120
  window.resize(1, stdscr.maxx)
121
121
  window.move(stdscr.maxy - 1, 0)
122
122
  end
@@ -1,7 +1,12 @@
1
- require 'twterm/event/favorite'
2
- require 'twterm/event/follow'
3
- require 'twterm/event/notification/error'
4
- require 'twterm/event/notification/info'
1
+ require 'twterm/event/message/error'
2
+ require 'twterm/event/message/info'
3
+ require 'twterm/event/notification/direct_message'
4
+ require 'twterm/event/notification/favorite'
5
+ require 'twterm/event/notification/follow'
6
+ require 'twterm/event/notification/list_member_added'
7
+ require 'twterm/event/notification/mention'
8
+ require 'twterm/event/notification/quote'
9
+ require 'twterm/event/notification/retweet'
5
10
  require 'twterm/event/status/mention'
6
11
  require 'twterm/event/status/timeline'
7
12
  require 'twterm/publisher'
@@ -18,32 +23,64 @@ module Twterm
18
23
 
19
24
  @streaming_thread = Thread.new do
20
25
  begin
21
- publish(Event::Notification::Info.new('Trying to connect to Twitter...'))
26
+ publish(Event::Message::Info.new('Trying to connect to Twitter...'))
22
27
  streaming_client.user do |event|
23
28
  keep_alive!
24
29
 
25
30
  case event
26
31
  when Twitter::Tweet
27
32
  status = status_repository.create(event)
33
+ user = user_repository.create(event.user)
34
+
28
35
  publish(Event::Status::Timeline.new(status))
29
- publish(Event::Status::Mention.new(status)) if status.text.include?('@%s' % screen_name)
36
+
37
+ if !status.retweet? && status.text.include?('@%s' % screen_name)
38
+ publish(Event::Status::Mention.new(status))
39
+
40
+ notification = Event::Notification::Mention.new(status, user)
41
+ publish(notification)
42
+ end
43
+
44
+ if status.retweet? && event.retweeted_status.user.id == user_id
45
+ retweeted_status = status_repository.create(event.retweeted_status)
46
+ notification = Event::Notification::Retweet.new(retweeted_status, user)
47
+ publish(notification)
48
+ end
30
49
  when Twitter::Streaming::Event
31
50
  case event.name
32
51
  when :favorite
52
+ next if event.source.id == user_id
33
53
  user = user_repository.create(event.source)
34
54
  status = status_repository.create(event.target_object)
35
-
36
- event = Event::Favorite.new(user, status, self)
37
- publish(event)
55
+ notification = Event::Notification::Like.new(status, user)
56
+ publish(notification)
38
57
  when :follow
39
- source = user_repository.create(event.source)
40
- target = user_repository.create(event.target)
58
+ next if event.source.id == user_id
59
+
60
+ user = user_repository.create(event.source)
61
+ notification = Event::Notification::Follow.new(user)
62
+ publish(notification)
63
+ when :list_member_added
64
+ next if event.source.id == user_id
41
65
 
42
- event = Event::Follow.new(source, target, self)
66
+ list = list_repository.create(event.target_object)
67
+ notification = Event::Notification::ListMemberAdded.new(list)
68
+ publish(notification)
69
+ when :quoted_tweet
70
+ next if event.source.id == user_id
43
71
 
44
- publish(event)
72
+ status = status_repository.create(event.target_object)
73
+ user = user_repository.create(event.source)
74
+ notification = Event::Notification::Quote.new(status, user)
75
+ publish(notification)
45
76
  end
46
77
  when Twitter::DirectMessage
78
+ next if event.sender.id == user_id
79
+
80
+ user = user_repository.create(event.sender)
81
+ message = DirectMessage.new(event)
82
+ notification = Event::Notification::DirectMessage.new(message, user)
83
+ publish(notification)
47
84
  when Twitter::Streaming::FriendList
48
85
  user_stream_connected!
49
86
  when Twitter::Streaming::DeletedTweet
@@ -51,15 +88,15 @@ module Twterm
51
88
  end
52
89
  end
53
90
  rescue Twitter::Error::TooManyRequests
54
- publish(Event::Notification::Error.new('Rate limit exceeded'))
91
+ publish(Event::Message::Error.new('Rate limit exceeded'))
55
92
  sleep 120
56
93
  retry
57
94
  rescue Errno::ENETUNREACH, Errno::ETIMEDOUT, Resolv::ResolvError
58
- publish(Event::Notification::Error.new('Network is unavailable'))
95
+ publish(Event::Message::Error.new('Network is unavailable'))
59
96
  sleep 30
60
97
  retry
61
98
  rescue Twitter::Error => e
62
- publish(Event::Notification::Error.new(e.message))
99
+ publish(Event::Message::Error.new(e.message))
63
100
  end
64
101
  end
65
102
  end
@@ -90,7 +127,7 @@ module Twterm
90
127
  end
91
128
 
92
129
  def user_stream_connected!
93
- publish(Event::Notification::Info.new('Connection established')) unless user_stream_connected?
130
+ publish(Event::Message::Info.new('Connection established')) unless user_stream_connected?
94
131
  @user_stream_connected = true
95
132
  end
96
133
 
@@ -10,8 +10,7 @@ module Twterm
10
10
  include Curses
11
11
  include Subscriber
12
12
 
13
- attr_reader :window
14
- attr_accessor :title
13
+ attr_reader :window, :title
15
14
 
16
15
  def ==(other)
17
16
  self.equal?(other)
@@ -107,7 +106,7 @@ module Twterm
107
106
  )
108
107
  end
109
108
 
110
- def resize(event)
109
+ def resize(_event)
111
110
  window.resize(stdscr.maxy - 5, stdscr.maxx)
112
111
  window.move(3, 0)
113
112
  end
@@ -1,4 +1,4 @@
1
- require 'twterm/event/notification/info'
1
+ require 'twterm/event/message/info'
2
2
  require 'twterm/tab/base'
3
3
  require 'twterm/tab/loadable'
4
4
 
@@ -33,7 +33,7 @@ module Twterm
33
33
  @@lists || []
34
34
  end
35
35
 
36
- def matches?(list, query)
36
+ def matches?(_list, query)
37
37
  [
38
38
  other.description,
39
39
  other.full_name,
@@ -43,8 +43,6 @@ module Twterm
43
43
  def respond_to_key(key)
44
44
  return true if scroller.respond_to_key(key)
45
45
 
46
- k = KeyMapper.instance
47
-
48
46
  case key
49
47
  when 10
50
48
  return true if current_list.nil?
@@ -1,6 +1,7 @@
1
1
  require 'twterm/tab/base'
2
2
  require 'twterm/tab/direct_message/conversation_list'
3
3
  require 'twterm/tab/rate_limit_status'
4
+ require 'twterm/tab/preferences/index'
4
5
 
5
6
  module Twterm
6
7
  module Tab
@@ -24,6 +25,7 @@ module Twterm
24
25
  user_tab
25
26
  key_assignments_cheatsheet
26
27
  rate_limit_status
28
+ preferences
27
29
  ).freeze
28
30
  end
29
31
 
@@ -69,6 +71,8 @@ module Twterm
69
71
  'Key assignments cheatsheet'
70
72
  when :rate_limit_status
71
73
  'Rate limit status'
74
+ when :preferences
75
+ 'Preferences'
72
76
  end
73
77
 
74
78
  cursor - Image.whitespace - Image.string(desc)
@@ -103,6 +107,10 @@ module Twterm
103
107
  switch(Tab::KeyAssignmentsCheatsheet.new(app, client))
104
108
  end
105
109
 
110
+ def open_preferences_tab
111
+ switch(Tab::Preferences::Index.new(app, client))
112
+ end
113
+
106
114
  def perform_selected_action
107
115
  case current_item
108
116
  when :direct_messages
@@ -117,6 +125,8 @@ module Twterm
117
125
  open_key_assignments_cheatsheet
118
126
  when :rate_limit_status
119
127
  open_rate_limit_status
128
+ when :preferences
129
+ open_preferences_tab
120
130
  end
121
131
  end
122
132
 
@@ -1,6 +1,6 @@
1
1
  require 'twterm/publisher'
2
2
  require 'twterm/tab/base'
3
- require 'twterm/event/notification/error'
3
+ require 'twterm/event/message/error'
4
4
 
5
5
  module Twterm
6
6
  module Tab
@@ -33,7 +33,7 @@ module Twterm
33
33
  else
34
34
  client.show_user(screen_name).then do |user|
35
35
  if user.nil?
36
- publish(Event::Notification::Error.new('User not found'))
36
+ publish(Event::Message::Error.new('User not found'))
37
37
  tab = Tab::New::Start.new(app, client)
38
38
  else
39
39
  tab = Tab::UserTab.new(app, client, user.id)
@@ -0,0 +1,74 @@
1
+ require 'twterm/image'
2
+ require 'twterm/tab/preferences/notification_backend'
3
+ require 'twterm/preferences'
4
+ require 'twterm/publisher'
5
+ require 'twterm/tab/base'
6
+ require 'twterm/tab/scrollable'
7
+
8
+ module Twterm
9
+ module Tab
10
+ module Preferences
11
+ class Index < Tab::Base
12
+ include Scrollable
13
+
14
+ def initialize(app, client)
15
+ super(app, client)
16
+ end
17
+
18
+ def drawable_item_count
19
+ (window.maxy - 1) / 2
20
+ end
21
+
22
+ def image
23
+ drawable_items.map.with_index do |item, i|
24
+ cursor = Image.cursor(1, scroller.current_index?(i))
25
+ desc =
26
+ case item
27
+ when :notification_backend
28
+ 'Notification backend preferences'
29
+ end
30
+
31
+ cursor - Image.whitespace - Image.string(desc)
32
+ end
33
+ .intersperse(Image.blank_line)
34
+ .reduce(Image.empty) { |acc, x| acc | x }
35
+ end
36
+
37
+ def items
38
+ [
39
+ :notification_backend
40
+ ]
41
+ end
42
+
43
+ def respond_to_key(key)
44
+ return true if scroller.respond_to_key(key)
45
+
46
+ case key
47
+ when 10
48
+ open
49
+ else
50
+ return false
51
+ end
52
+
53
+ true
54
+ end
55
+
56
+ def title
57
+ 'Preferences'
58
+ end
59
+
60
+ private
61
+
62
+ def open
63
+ tab =
64
+ case scroller.current_item
65
+ when :notification_backend
66
+ Tab::Preferences::NotificationBackend.new(app, client)
67
+ end
68
+
69
+ app.tab_manager.add_and_show(tab)
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end