twterm 2.1.0 → 2.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.travis.yml +4 -1
- data/lib/twterm.rb +1 -1
- data/lib/twterm/abstract_persistable_configuration.rb +118 -0
- data/lib/twterm/app.rb +21 -1
- data/lib/twterm/environment.rb +38 -0
- data/lib/twterm/event/message/abstract_message.rb +23 -0
- data/lib/twterm/event/message/error.rb +10 -0
- data/lib/twterm/event/message/info.rb +10 -0
- data/lib/twterm/event/message/success.rb +10 -0
- data/lib/twterm/event/message/warning.rb +10 -0
- data/lib/twterm/event/notification/abstract_notification.rb +17 -12
- data/lib/twterm/event/notification/direct_message.rb +30 -0
- data/lib/twterm/event/notification/favorite.rb +35 -0
- data/lib/twterm/event/notification/follow.rb +33 -0
- data/lib/twterm/event/notification/list_member_added.rb +33 -0
- data/lib/twterm/event/notification/mention.rb +35 -0
- data/lib/twterm/event/notification/quote.rb +35 -0
- data/lib/twterm/event/notification/retweet.rb +35 -0
- data/lib/twterm/key_mapper/abstract_key_mapper.rb +4 -4
- data/lib/twterm/list.rb +2 -1
- data/lib/twterm/message_window.rb +81 -0
- data/lib/twterm/notification_backend/abstract_notification_backend.rb +16 -0
- data/lib/twterm/notification_backend/inline_backend.rb +20 -0
- data/lib/twterm/notification_backend/terminal_notifier_backend.rb +23 -0
- data/lib/twterm/notification_backend/tmux_backend.rb +17 -0
- data/lib/twterm/notification_dispatcher.rb +36 -0
- data/lib/twterm/persistable_configuration_proxy.rb +82 -0
- data/lib/twterm/preferences.rb +66 -0
- data/lib/twterm/repository/abstract_expirable_entity_repository.rb +1 -1
- data/lib/twterm/rest_client.rb +12 -14
- data/lib/twterm/scheduler.rb +1 -1
- data/lib/twterm/screen.rb +1 -1
- data/lib/twterm/search_query_window.rb +2 -2
- data/lib/twterm/streaming_client.rb +54 -17
- data/lib/twterm/tab/base.rb +2 -3
- data/lib/twterm/tab/new/list.rb +2 -4
- data/lib/twterm/tab/new/start.rb +10 -0
- data/lib/twterm/tab/new/user.rb +2 -2
- data/lib/twterm/tab/preferences/index.rb +74 -0
- data/lib/twterm/tab/preferences/notification_backend.rb +85 -0
- data/lib/twterm/tab/scrollable.rb +0 -1
- data/lib/twterm/tab/searchable.rb +7 -7
- data/lib/twterm/tab/user_list_management.rb +3 -3
- data/lib/twterm/tab/user_tab.rb +6 -8
- data/lib/twterm/tab_manager.rb +4 -4
- data/lib/twterm/tweetbox.rb +1 -1
- data/lib/twterm/uri_opener.rb +2 -2
- data/lib/twterm/user.rb +2 -1
- data/lib/twterm/version.rb +1 -1
- data/twterm.gemspec +4 -2
- metadata +59 -14
- data/lib/twterm/event/favorite.rb +0 -18
- data/lib/twterm/event/follow.rb +0 -17
- data/lib/twterm/event/notification/error.rb +0 -13
- data/lib/twterm/event/notification/info.rb +0 -13
- data/lib/twterm/event/notification/success.rb +0 -13
- data/lib/twterm/event/notification/warning.rb +0 -13
- data/lib/twterm/notifier.rb +0 -82
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'twterm/event/notification/abstract_notification'
|
2
|
+
|
3
|
+
module Twterm
|
4
|
+
module Event
|
5
|
+
module Notification
|
6
|
+
class ListMemberAdded < AbstractNotification
|
7
|
+
# @param [Twterm::List] list
|
8
|
+
def initialize(list)
|
9
|
+
@list = list
|
10
|
+
end
|
11
|
+
|
12
|
+
# @return [String] notification body
|
13
|
+
def body
|
14
|
+
list.description
|
15
|
+
end
|
16
|
+
|
17
|
+
# @return [String] notification title
|
18
|
+
def title
|
19
|
+
"You've been added to #{list.full_name}"
|
20
|
+
end
|
21
|
+
|
22
|
+
# @return [String] notification url
|
23
|
+
def url
|
24
|
+
list.url
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
attr_reader :list
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'twterm/event/notification/abstract_notification'
|
2
|
+
|
3
|
+
module Twterm
|
4
|
+
module Event
|
5
|
+
module Notification
|
6
|
+
class Mention < AbstractNotification
|
7
|
+
# @param [Twterm::Status] status
|
8
|
+
# @param [Twterm::User] user
|
9
|
+
def initialize(status, user)
|
10
|
+
@status = status
|
11
|
+
@user = user
|
12
|
+
end
|
13
|
+
|
14
|
+
# @return [String] notification body
|
15
|
+
def body
|
16
|
+
status.text
|
17
|
+
end
|
18
|
+
|
19
|
+
# @return [String] notification title
|
20
|
+
def title
|
21
|
+
"@#{user.screen_name} has replied to your tweet"
|
22
|
+
end
|
23
|
+
|
24
|
+
# @return [String] notification url
|
25
|
+
def url
|
26
|
+
status.url
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
attr_reader :status, :user
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'twterm/event/notification/abstract_notification'
|
2
|
+
|
3
|
+
module Twterm
|
4
|
+
module Event
|
5
|
+
module Notification
|
6
|
+
class Quote < AbstractNotification
|
7
|
+
# @param [Twterm::Status] status
|
8
|
+
# @param [Twterm::User] user
|
9
|
+
def initialize(status, user)
|
10
|
+
@status = status
|
11
|
+
@user = user
|
12
|
+
end
|
13
|
+
|
14
|
+
# @return [String] notification body
|
15
|
+
def body
|
16
|
+
status.text
|
17
|
+
end
|
18
|
+
|
19
|
+
# @return [String] notification title
|
20
|
+
def title
|
21
|
+
"@#{user.screen_name} has quoted your tweet"
|
22
|
+
end
|
23
|
+
|
24
|
+
# @return [String] notification url
|
25
|
+
def url
|
26
|
+
status.url
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
attr_reader :status, :user
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'twterm/event/notification/abstract_notification'
|
2
|
+
|
3
|
+
module Twterm
|
4
|
+
module Event
|
5
|
+
module Notification
|
6
|
+
class Retweet < AbstractNotification
|
7
|
+
# @param [Twterm::Status] status
|
8
|
+
# @param [Twterm::User] user
|
9
|
+
def initialize(status, user)
|
10
|
+
@status = status
|
11
|
+
@user = user
|
12
|
+
end
|
13
|
+
|
14
|
+
# @return [String] notification body
|
15
|
+
def body
|
16
|
+
status.text
|
17
|
+
end
|
18
|
+
|
19
|
+
# @return [String] notification title
|
20
|
+
def title
|
21
|
+
"@#{user.screen_name} has retweeted your tweet"
|
22
|
+
end
|
23
|
+
|
24
|
+
# @return [String] notification url
|
25
|
+
def url
|
26
|
+
status.url
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
attr_reader :status, :user
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -23,6 +23,10 @@ module Twterm
|
|
23
23
|
@mappings[key]
|
24
24
|
end
|
25
25
|
|
26
|
+
def self.commands
|
27
|
+
self::DEFAULT_MAPPINGS.keys
|
28
|
+
end
|
29
|
+
|
26
30
|
def to_h
|
27
31
|
@dict
|
28
32
|
end
|
@@ -49,10 +53,6 @@ module Twterm
|
|
49
53
|
raise NoSuchKey.new(key)
|
50
54
|
end
|
51
55
|
end
|
52
|
-
|
53
|
-
def self.commands
|
54
|
-
self::DEFAULT_MAPPINGS.keys
|
55
|
-
end
|
56
56
|
end
|
57
57
|
end
|
58
58
|
end
|
data/lib/twterm/list.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
module Twterm
|
2
2
|
class List
|
3
|
-
attr_reader :id, :name, :slug, :full_name, :mode, :description, :member_count, :subscriber_count
|
3
|
+
attr_reader :id, :name, :slug, :full_name, :mode, :description, :member_count, :subscriber_count, :url
|
4
4
|
|
5
5
|
def ==(other)
|
6
6
|
other.is_a?(self.class) && id == other.id
|
@@ -19,6 +19,7 @@ module Twterm
|
|
19
19
|
@description = list.description.is_a?(Twitter::NullObject) ? '' : list.description
|
20
20
|
@member_count = list.member_count
|
21
21
|
@subscriber_count = list.subscriber_count
|
22
|
+
@url = list.url
|
22
23
|
|
23
24
|
self
|
24
25
|
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'twterm/subscriber'
|
2
|
+
require 'twterm/event/message/abstract_message'
|
3
|
+
require 'twterm/event/screen/resize'
|
4
|
+
|
5
|
+
module Twterm
|
6
|
+
class MessageWindow
|
7
|
+
include Singleton
|
8
|
+
include Curses
|
9
|
+
include Subscriber
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
@window = stdscr.subwin(1, stdscr.maxx, stdscr.maxy - 2, 0)
|
13
|
+
@queue = Queue.new
|
14
|
+
|
15
|
+
subscribe(Event::Message::AbstractMessage) do |e|
|
16
|
+
queue(e)
|
17
|
+
end
|
18
|
+
|
19
|
+
subscribe(Event::Screen::Resize, :resize)
|
20
|
+
|
21
|
+
Thread.new do
|
22
|
+
while message = @queue.pop # rubocop:disable Lint/AssignmentInCondition:
|
23
|
+
show(message)
|
24
|
+
sleep 3
|
25
|
+
show
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def show_info(message)
|
31
|
+
@queue.push(Event::Message::Info.new(message))
|
32
|
+
self
|
33
|
+
end
|
34
|
+
|
35
|
+
def show(message = nil)
|
36
|
+
loop do
|
37
|
+
break unless closed?
|
38
|
+
sleep 0.5
|
39
|
+
end
|
40
|
+
|
41
|
+
@window.clear
|
42
|
+
|
43
|
+
if message.is_a?(Event::Message::AbstractMessage)
|
44
|
+
fg_color, bg_color =
|
45
|
+
case message
|
46
|
+
when Event::Message::Error
|
47
|
+
[:white, :red]
|
48
|
+
when Event::Message::Info
|
49
|
+
[:black, :cyan]
|
50
|
+
when Event::Message::Success
|
51
|
+
[:black, :green]
|
52
|
+
when Event::Message::Warning
|
53
|
+
[:black, :yellow]
|
54
|
+
end
|
55
|
+
|
56
|
+
@window.with_color(fg_color, bg_color) do
|
57
|
+
@window.setpos(0, 0)
|
58
|
+
@window.addstr(' ' * @window.maxx)
|
59
|
+
@window.setpos(0, 1)
|
60
|
+
time = message.time.strftime('[%H:%M:%S]')
|
61
|
+
body = message.body.gsub("\n", ' ')
|
62
|
+
@window.addstr("#{time} #{body}")
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
@window.refresh
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
def queue(message)
|
72
|
+
@queue.push(message)
|
73
|
+
self
|
74
|
+
end
|
75
|
+
|
76
|
+
def resize(_event)
|
77
|
+
@window.resize(1, stdscr.maxx)
|
78
|
+
@window.move(stdscr.maxy - 2, 0)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Twterm
|
2
|
+
module NotificationBackend
|
3
|
+
# @abstract
|
4
|
+
class AbstractNotificationBackend
|
5
|
+
# @abstract
|
6
|
+
# @param [Twterm::Event::Notification::AbstractNotification] _notification a notification to show
|
7
|
+
def notify(_notification)
|
8
|
+
raise NotImplementedError, '`notify` method must be implemented'
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
attr_reader :app
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'curses'
|
2
|
+
|
3
|
+
require 'twterm/event/message/info'
|
4
|
+
require 'twterm/notification_backend/abstract_notification_backend'
|
5
|
+
require 'twterm/publisher'
|
6
|
+
|
7
|
+
module Twterm
|
8
|
+
module NotificationBackend
|
9
|
+
class InlineBackend < AbstractNotificationBackend
|
10
|
+
include Publisher
|
11
|
+
|
12
|
+
# @param [Twterm::Event::Notification::AbstractNotification] notification
|
13
|
+
# @return [void]
|
14
|
+
def notify(notification)
|
15
|
+
message = Event::Message::Info.new(notification.fallback)
|
16
|
+
publish(message)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'terminal-notifier'
|
2
|
+
|
3
|
+
require 'twterm/notification_backend/abstract_notification_backend'
|
4
|
+
|
5
|
+
module Twterm
|
6
|
+
module NotificationBackend
|
7
|
+
class TerminalNotifierBackend < AbstractNotificationBackend
|
8
|
+
# @param [Twterm::Event::Notification::AbstractNotification] notification notification to display via terminal-notifier
|
9
|
+
def notify(notification)
|
10
|
+
opts = {
|
11
|
+
title: 'twterm',
|
12
|
+
subtitle: notification.title,
|
13
|
+
group: Process.pid,
|
14
|
+
sound: 'Purr',
|
15
|
+
}
|
16
|
+
|
17
|
+
opts[:open] = notification.url unless notification.url.nil?
|
18
|
+
|
19
|
+
TerminalNotifier.notify(notification.body, opts)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'shellwords'
|
2
|
+
|
3
|
+
require 'twterm/notification_backend/abstract_notification_backend'
|
4
|
+
|
5
|
+
module Twterm
|
6
|
+
module NotificationBackend
|
7
|
+
class TmuxBackend < AbstractNotificationBackend
|
8
|
+
# @param [Twterm::Event::Notification::AbstractNotification] notification notification to display in tmux status line
|
9
|
+
# @return [void]
|
10
|
+
def notify(notification)
|
11
|
+
`tmux set-option display-time 3000`
|
12
|
+
`tmux set-option message-style fg=black,bg=green`
|
13
|
+
`tmux display #{Shellwords.escape(" [twterm] #{notification.fallback.gsub("\n", ' ')}")}`
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'twterm/event/notification/abstract_notification'
|
2
|
+
require 'twterm/notification_backend/inline_backend'
|
3
|
+
require 'twterm/notification_backend/terminal_notifier_backend'
|
4
|
+
require 'twterm/notification_backend/tmux_backend'
|
5
|
+
require 'twterm/subscriber'
|
6
|
+
|
7
|
+
module Twterm
|
8
|
+
class NotificationDispatcher
|
9
|
+
include Subscriber
|
10
|
+
|
11
|
+
# @param [Twterm::Preferences] preferences
|
12
|
+
def initialize(preferences)
|
13
|
+
@preferences = preferences
|
14
|
+
|
15
|
+
@backends = {
|
16
|
+
inline: NotificationBackend::InlineBackend.new,
|
17
|
+
terminal_notifier: NotificationBackend::TerminalNotifierBackend.new,
|
18
|
+
tmux: NotificationBackend::TmuxBackend.new,
|
19
|
+
}
|
20
|
+
|
21
|
+
subscribe(Event::Notification::AbstractNotification) { |n| dispatch(n) }
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
attr_reader :backends, :preferences
|
27
|
+
|
28
|
+
# @param [Twterm::Notification::AbstractNotification] notification notification to dispatch to backends
|
29
|
+
# @return [void]
|
30
|
+
def dispatch(notification)
|
31
|
+
backends.keys.each do |key|
|
32
|
+
backends[key].notify(notification) if preferences[:notification_backend, key]
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'toml'
|
2
|
+
|
3
|
+
module Twterm
|
4
|
+
class PersistableConfigurationProxy
|
5
|
+
# @param [Twterm::AbstractPersistableConfiguration] instance A configuration to persist
|
6
|
+
# @param [String] filepath File path to persist the given configuration
|
7
|
+
def initialize(instance, filepath)
|
8
|
+
@instance = instance
|
9
|
+
@filepath = filepath
|
10
|
+
end
|
11
|
+
|
12
|
+
def [](*args)
|
13
|
+
instance.[](*args)
|
14
|
+
end
|
15
|
+
|
16
|
+
def []=(*args)
|
17
|
+
instance.[]=(*args)
|
18
|
+
persist!
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.getc
|
22
|
+
system('stty raw -echo')
|
23
|
+
STDIN.getc
|
24
|
+
ensure
|
25
|
+
system('stty -raw echo')
|
26
|
+
end
|
27
|
+
|
28
|
+
# Loads a configuration file from and returns a proxy.
|
29
|
+
# Falls back to the default value when the specified file does not exist.
|
30
|
+
#
|
31
|
+
# @param [Class] klass Must be a subclass of {Twterm::AbstractPersistableConfiguration}
|
32
|
+
# @param [String] filepath File path to load configuration from
|
33
|
+
# @return [Twterm::PersistableConfigurationProxy] a configuration proxy
|
34
|
+
def self.load_from_file!(klass, filepath)
|
35
|
+
config = TOML.load_file(filepath, symbolize_keys: true)
|
36
|
+
new(klass.new(config), filepath).migrate!
|
37
|
+
rescue Errno::ENOENT
|
38
|
+
new(klass.default, filepath)
|
39
|
+
rescue TOML::ParseError, TOML::ValueOverwriteError => e
|
40
|
+
msg =
|
41
|
+
case e
|
42
|
+
when TOML::ParseError
|
43
|
+
"Your configuration file could not be parsed"
|
44
|
+
when TOML::ValueOverwriteError
|
45
|
+
"`#{e.key}` is declared more than once"
|
46
|
+
end
|
47
|
+
|
48
|
+
warn <<-EOS
|
49
|
+
\e[1mCould not load the configuration file: #{filepath}\e[0m
|
50
|
+
(#{msg})
|
51
|
+
|
52
|
+
Falling back to the default key assignments
|
53
|
+
|
54
|
+
Check the syntax and edit the file manually,
|
55
|
+
or remove it and launch twterm again to restore
|
56
|
+
|
57
|
+
Press any key to continue
|
58
|
+
EOS
|
59
|
+
|
60
|
+
getc
|
61
|
+
|
62
|
+
new(klass.default, filepath)
|
63
|
+
end
|
64
|
+
|
65
|
+
# @return [self]
|
66
|
+
def migrate!
|
67
|
+
instance.complete_missing_items!
|
68
|
+
persist!
|
69
|
+
|
70
|
+
self
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
attr_reader :filepath, :instance
|
76
|
+
|
77
|
+
def persist!
|
78
|
+
hash = TOML.dump(instance.to_h).gsub("\n[", "\n\n[")
|
79
|
+
File.open(filepath, 'w', 0644) { |f| f.write(hash) }
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|