twterm 2.5.1 → 2.10.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.
- checksums.yaml +4 -4
- data/.circleci/config.yml +62 -0
- data/.gitignore +1 -0
- data/Makefile +21 -0
- data/README.md +52 -6
- data/bin/twterm +1 -3
- data/default.nix +21 -0
- data/gemset.nix +4981 -0
- data/lib/twterm/app.rb +13 -26
- data/lib/twterm/client.rb +1 -6
- data/lib/twterm/color_manager.rb +8 -9
- data/lib/twterm/image.rb +31 -0
- data/lib/twterm/image/attr.rb +42 -0
- data/lib/twterm/image/bold.rb +7 -21
- data/lib/twterm/image/color.rb +9 -15
- data/lib/twterm/image/dim.rb +13 -0
- data/lib/twterm/image/underlined.rb +17 -0
- data/lib/twterm/image_builder/user_name_image_builder.rb +1 -0
- data/lib/twterm/key_mapper.rb +6 -8
- data/lib/twterm/key_mapper/abstract_key_mapper.rb +2 -2
- data/lib/twterm/list.rb +36 -1
- data/lib/twterm/message_window.rb +4 -13
- data/lib/twterm/persistable_configuration_proxy.rb +6 -6
- data/lib/twterm/preferences.rb +8 -1
- data/lib/twterm/rest_client.rb +0 -33
- data/lib/twterm/screen.rb +103 -25
- data/lib/twterm/search_query_window.rb +5 -13
- data/lib/twterm/status.rb +10 -0
- data/lib/twterm/tab/abstract_tab.rb +30 -16
- data/lib/twterm/tab/new/index.rb +0 -10
- data/lib/twterm/tab/new/search.rb +2 -2
- data/lib/twterm/tab/new/user.rb +2 -2
- data/lib/twterm/tab/preferences/control.rb +77 -0
- data/lib/twterm/tab/preferences/index.rb +6 -0
- data/lib/twterm/tab/scrollable.rb +1 -1
- data/lib/twterm/tab/searchable.rb +6 -4
- data/lib/twterm/tab/status_tab.rb +10 -0
- data/lib/twterm/tab/statuses/abstract_statuses_tab.rb +11 -6
- data/lib/twterm/tab/statuses/home.rb +0 -3
- data/lib/twterm/tab/statuses/mentions.rb +1 -0
- data/lib/twterm/tab/user_tab.rb +0 -9
- data/lib/twterm/tab_manager.rb +77 -10
- data/lib/twterm/tweetbox.rb +2 -3
- data/lib/twterm/user.rb +1 -0
- data/lib/twterm/version.rb +1 -1
- data/nix/Gemfile +3 -0
- data/nix/Gemfile.lock +77 -0
- data/nix/gemset.nix +325 -0
- data/shell.nix +40 -0
- data/spec/twterm/image/bold_spec.rb +30 -0
- data/spec/twterm/image/color_spec.rb +16 -0
- data/spec/twterm/image/dim_spec.rb +30 -0
- data/twterm.gemspec +13 -13
- metadata +46 -46
- data/.travis.yml +0 -12
- data/lib/twterm/direct_message.rb +0 -60
- data/lib/twterm/direct_message_composer.rb +0 -80
- data/lib/twterm/direct_message_manager.rb +0 -51
- data/lib/twterm/event/direct_message/fetched.rb +0 -10
- data/lib/twterm/event/notification/direct_message.rb +0 -30
- data/lib/twterm/event/screen/resize.rb +0 -13
- data/lib/twterm/event/status/timeline.rb +0 -10
- data/lib/twterm/repository/direct_message_repository.rb +0 -14
- data/lib/twterm/streaming_client.rb +0 -146
- data/lib/twterm/tab/direct_message/conversation.rb +0 -104
- data/lib/twterm/tab/direct_message/conversation_list.rb +0 -100
- data/spec/twterm/event/screen/resize_spec.rb +0 -11
data/lib/twterm/list.rb
CHANGED
@@ -1,7 +1,41 @@
|
|
1
1
|
module Twterm
|
2
|
+
# A Twitter list
|
2
3
|
class List
|
3
|
-
|
4
|
+
# Unique ID of the list
|
5
|
+
#
|
6
|
+
# @return [Integer]
|
7
|
+
attr_reader :id
|
4
8
|
|
9
|
+
# @return [String]
|
10
|
+
attr_reader :name
|
11
|
+
|
12
|
+
# @return [String]
|
13
|
+
attr_reader :slug
|
14
|
+
|
15
|
+
# @return [String]
|
16
|
+
attr_reader :full_name
|
17
|
+
|
18
|
+
attr_reader :mode
|
19
|
+
|
20
|
+
# @return [String]
|
21
|
+
attr_reader :description
|
22
|
+
|
23
|
+
# The number of users that are in this list
|
24
|
+
#
|
25
|
+
# @return [Integer]
|
26
|
+
attr_reader :member_count
|
27
|
+
|
28
|
+
# The number of users that subscribe this list
|
29
|
+
#
|
30
|
+
# @return [Integer]
|
31
|
+
attr_reader :subscriber_count
|
32
|
+
|
33
|
+
# @return [String]
|
34
|
+
attr_reader :url
|
35
|
+
|
36
|
+
# @param other [List]
|
37
|
+
#
|
38
|
+
# @return [Boolean]
|
5
39
|
def ==(other)
|
6
40
|
other.is_a?(self.class) && id == other.id
|
7
41
|
end
|
@@ -11,6 +45,7 @@ module Twterm
|
|
11
45
|
update!(list)
|
12
46
|
end
|
13
47
|
|
48
|
+
# @return [self]
|
14
49
|
def update!(list)
|
15
50
|
@name = list.name
|
16
51
|
@slug = list.slug
|
@@ -1,23 +1,19 @@
|
|
1
1
|
require 'twterm/subscriber'
|
2
2
|
require 'twterm/event/message/abstract_message'
|
3
|
-
require 'twterm/event/screen/resize'
|
4
3
|
|
5
4
|
module Twterm
|
6
5
|
class MessageWindow
|
7
|
-
include Singleton
|
8
|
-
include Curses
|
9
6
|
include Subscriber
|
10
7
|
|
11
|
-
|
12
|
-
|
8
|
+
# @param window [Curses::Window]
|
9
|
+
def initialize(window)
|
10
|
+
@window = window
|
13
11
|
@queue = Queue.new
|
14
12
|
|
15
13
|
subscribe(Event::Message::AbstractMessage) do |e|
|
16
14
|
queue(e)
|
17
15
|
end
|
18
16
|
|
19
|
-
subscribe(Event::Screen::Resize, :resize)
|
20
|
-
|
21
17
|
Thread.new do
|
22
18
|
while message = @queue.pop # rubocop:disable Lint/AssignmentInCondition:
|
23
19
|
show(message)
|
@@ -34,7 +30,7 @@ module Twterm
|
|
34
30
|
|
35
31
|
def show(message = nil)
|
36
32
|
loop do
|
37
|
-
break unless closed?
|
33
|
+
break unless Curses.closed?
|
38
34
|
sleep 0.5
|
39
35
|
end
|
40
36
|
|
@@ -72,10 +68,5 @@ module Twterm
|
|
72
68
|
@queue.push(message)
|
73
69
|
self
|
74
70
|
end
|
75
|
-
|
76
|
-
def resize(_event)
|
77
|
-
@window.resize(1, stdscr.maxx)
|
78
|
-
@window.move(stdscr.maxy - 2, 0)
|
79
|
-
end
|
80
71
|
end
|
81
72
|
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
require 'toml'
|
1
|
+
require 'toml-rb'
|
2
2
|
|
3
3
|
module Twterm
|
4
4
|
class PersistableConfigurationProxy
|
@@ -32,16 +32,16 @@ module Twterm
|
|
32
32
|
# @param [String] filepath File path to load configuration from
|
33
33
|
# @return [Twterm::PersistableConfigurationProxy] a configuration proxy
|
34
34
|
def self.load_from_file!(klass, filepath)
|
35
|
-
config =
|
35
|
+
config = TomlRB.load_file(filepath, symbolize_keys: true)
|
36
36
|
new(klass.new(config), filepath).migrate!
|
37
37
|
rescue Errno::ENOENT
|
38
38
|
new(klass.default, filepath)
|
39
|
-
rescue
|
39
|
+
rescue TomlRB::ParseError, TomlRB::ValueOverwriteError => e
|
40
40
|
msg =
|
41
41
|
case e
|
42
|
-
when
|
42
|
+
when TomlRB::ParseError
|
43
43
|
"Your configuration file could not be parsed"
|
44
|
-
when
|
44
|
+
when TomlRB::ValueOverwriteError
|
45
45
|
"`#{e.key}` is declared more than once"
|
46
46
|
end
|
47
47
|
|
@@ -75,7 +75,7 @@ Press any key to continue
|
|
75
75
|
attr_reader :filepath, :instance
|
76
76
|
|
77
77
|
def persist!
|
78
|
-
hash =
|
78
|
+
hash = TomlRB.dump(instance.to_h).gsub("\n[", "\n\n[")
|
79
79
|
File.open(filepath, 'w', 0644) { |f| f.write(hash) }
|
80
80
|
end
|
81
81
|
end
|
data/lib/twterm/preferences.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require 'toml'
|
1
|
+
require 'toml-rb'
|
2
2
|
|
3
3
|
require 'twterm/abstract_persistable_configuration'
|
4
4
|
|
@@ -27,6 +27,9 @@ module Twterm
|
|
27
27
|
# @return [Twterm::Preferences] an instance having the default value
|
28
28
|
def self.default
|
29
29
|
new({
|
30
|
+
control: {
|
31
|
+
scroll_direction: 'traditional',
|
32
|
+
},
|
30
33
|
photo_viewer_backend: {
|
31
34
|
browser: true,
|
32
35
|
imgcat: false,
|
@@ -48,8 +51,12 @@ module Twterm
|
|
48
51
|
# @return [Hash]
|
49
52
|
def self.structure
|
50
53
|
bool = -> x { x == true || x == false }
|
54
|
+
scroll_direction = -> x { x == 'natural' || x == 'traditional' }
|
51
55
|
|
52
56
|
{
|
57
|
+
control: {
|
58
|
+
scroll_direction: scroll_direction
|
59
|
+
},
|
53
60
|
photo_viewer_backend: {
|
54
61
|
browser: bool,
|
55
62
|
imgcat: bool,
|
data/lib/twterm/rest_client.rb
CHANGED
@@ -1,6 +1,4 @@
|
|
1
1
|
require 'concurrent'
|
2
|
-
require 'twterm/direct_message'
|
3
|
-
require 'twterm/direct_message_manager'
|
4
2
|
require 'twterm/publisher'
|
5
3
|
require 'twterm/event/message/success'
|
6
4
|
|
@@ -25,33 +23,6 @@ module Twterm
|
|
25
23
|
end
|
26
24
|
end
|
27
25
|
|
28
|
-
def create_direct_message(recipient, text)
|
29
|
-
send_request do
|
30
|
-
rest_client.create_direct_message(recipient.id, text)
|
31
|
-
end.then do |message|
|
32
|
-
msg = direct_message_repository.create(message)
|
33
|
-
direct_message_manager.add(recipient.id, msg)
|
34
|
-
publish(Event::DirectMessage::Fetched.new)
|
35
|
-
publish(Event::Message::Success.new('Your message to @%s has been sent' % recipient.screen_name))
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
def direct_message_conversations
|
40
|
-
direct_message_manager.conversations
|
41
|
-
end
|
42
|
-
|
43
|
-
def direct_messages_received
|
44
|
-
send_request do
|
45
|
-
rest_client.direct_messages(count: 200).map { |dm| direct_message_repository.create(dm) }
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
def direct_messages_sent
|
50
|
-
send_request do
|
51
|
-
rest_client.direct_messages_sent(count: 200).map { |dm| direct_message_repository.create(dm) }
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
26
|
def destroy_status(status)
|
56
27
|
send_request_without_catch do
|
57
28
|
rest_client.destroy_status(status.id)
|
@@ -424,10 +395,6 @@ module Twterm
|
|
424
395
|
|
425
396
|
private
|
426
397
|
|
427
|
-
def direct_message_manager
|
428
|
-
@direct_message_manager ||= DirectMessageManager.new(self)
|
429
|
-
end
|
430
|
-
|
431
398
|
def show_error
|
432
399
|
proc do |e|
|
433
400
|
case e
|
data/lib/twterm/screen.rb
CHANGED
@@ -1,26 +1,74 @@
|
|
1
1
|
require 'twterm/event/screen/refresh'
|
2
|
-
require 'twterm/event/screen/resize'
|
3
2
|
require 'twterm/key_mapper'
|
4
3
|
require 'twterm/subscriber'
|
5
4
|
|
6
5
|
module Twterm
|
7
6
|
class Screen
|
8
7
|
include Subscriber
|
9
|
-
|
8
|
+
|
9
|
+
# @todo Make private
|
10
|
+
# @return [Curses::Window]
|
11
|
+
attr_reader :tab_manager_window
|
12
|
+
|
13
|
+
# @todo Make private
|
14
|
+
# @return [Curses::Window]
|
15
|
+
attr_reader :tab_window
|
16
|
+
|
17
|
+
# @todo Make private
|
18
|
+
# @return [Curses::Window]
|
19
|
+
attr_reader :message_window_window
|
20
|
+
|
21
|
+
# @todo Make private
|
22
|
+
# @return [Curses::Window]
|
23
|
+
attr_reader :search_query_window_window
|
10
24
|
|
11
25
|
def initialize(app, client)
|
12
26
|
@app, @client = app, client
|
13
27
|
|
14
|
-
@
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
28
|
+
@stdscr = Curses.init_screen
|
29
|
+
|
30
|
+
width = @stdscr.maxx
|
31
|
+
height = @stdscr.maxy
|
32
|
+
|
33
|
+
@tab_manager_window = @stdscr.subwin(1, width, 0, 0)
|
34
|
+
@tab_window = @stdscr.subwin(height - 3, width, 2, 0)
|
35
|
+
@message_window_window = @stdscr.subwin(1, width, height - 1, 0)
|
36
|
+
@search_query_window_window = @stdscr.subwin(1, width, height - 1, 0)
|
37
|
+
|
38
|
+
Curses.noecho
|
39
|
+
Curses.raw
|
40
|
+
Curses.curs_set(0)
|
41
|
+
Curses.stdscr.keypad(true)
|
42
|
+
Curses.start_color
|
43
|
+
Curses.use_default_colors
|
44
|
+
Curses.mousemask(Curses::BUTTON1_CLICKED | 65536 | 2097152)
|
21
45
|
|
22
46
|
subscribe(Event::Screen::Refresh) { refresh }
|
23
|
-
|
47
|
+
end
|
48
|
+
|
49
|
+
def resize(lines, cols)
|
50
|
+
return if Curses.closed?
|
51
|
+
|
52
|
+
Curses.resizeterm(lines, cols)
|
53
|
+
@stdscr.resize(lines, cols)
|
54
|
+
|
55
|
+
tab_manager_window.move(0, 0)
|
56
|
+
tab_manager_window.resize(1, cols)
|
57
|
+
tab_manager_window.refresh
|
58
|
+
|
59
|
+
tab_window.move(2, 0)
|
60
|
+
tab_window.resize(lines - 3, cols)
|
61
|
+
tab_window.refresh
|
62
|
+
|
63
|
+
message_window_window.move(cols - 1, 0)
|
64
|
+
message_window_window.resize(1, cols)
|
65
|
+
message_window_window.refresh
|
66
|
+
|
67
|
+
search_query_window_window.move(cols - 1, 0)
|
68
|
+
search_query_window_window.resize(1, cols)
|
69
|
+
search_query_window_window.refresh
|
70
|
+
|
71
|
+
refresh
|
24
72
|
end
|
25
73
|
|
26
74
|
def respond_to_key(key)
|
@@ -53,30 +101,60 @@ module Twterm
|
|
53
101
|
|
54
102
|
attr_reader :app, :client
|
55
103
|
|
56
|
-
|
57
|
-
|
58
|
-
app.tab_manager.current_tab.
|
59
|
-
|
104
|
+
# @param [Integer, String] key
|
105
|
+
def handle_keyboard_event(key)
|
106
|
+
return if app.tab_manager.current_tab.respond_to_key(key)
|
107
|
+
return if app.tab_manager.respond_to_key(key)
|
108
|
+
respond_to_key(key)
|
60
109
|
end
|
61
110
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
111
|
+
# @param [Curses::MouseEvent] e
|
112
|
+
def handle_mouse_event(e)
|
113
|
+
x = e.x
|
114
|
+
y = e.y
|
115
|
+
|
116
|
+
case e.bstate
|
117
|
+
when Curses::BUTTON1_CLICKED
|
118
|
+
return app.tab_manager.handle_left_click(x, y) if app.tab_manager.enclose?(x, y)
|
119
|
+
when 65536
|
120
|
+
scroll_direction = app.preferences[:control, :scroll_direction]
|
121
|
+
|
122
|
+
case scroll_direction
|
123
|
+
when 'natural'
|
124
|
+
return app.tab_manager.handle_scroll_up(x, y) if app.tab_manager.enclose?(x, y)
|
125
|
+
when 'traditional'
|
126
|
+
return app.tab_manager.handle_scroll_down(x, y) if app.tab_manager.enclose?(x, y)
|
127
|
+
end
|
128
|
+
when 2097152
|
129
|
+
scroll_direction = app.preferences[:control, :scroll_direction]
|
130
|
+
|
131
|
+
case scroll_direction
|
132
|
+
when 'natural'
|
133
|
+
return app.tab_manager.handle_scroll_down(x, y) if app.tab_manager.enclose?(x, y)
|
134
|
+
when 'traditional'
|
135
|
+
return app.tab_manager.handle_scroll_up(x, y) if app.tab_manager.enclose?(x, y)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
68
139
|
|
69
|
-
|
140
|
+
def refresh
|
141
|
+
app.tab_manager.refresh_window
|
142
|
+
app.tab_manager.current_tab.render
|
143
|
+
app.message_window.show
|
70
144
|
end
|
71
145
|
|
72
146
|
def scan
|
73
147
|
app.reset_interruption_handler
|
74
148
|
|
75
|
-
key = getch
|
149
|
+
key = Curses.getch
|
76
150
|
|
77
|
-
|
78
|
-
|
79
|
-
|
151
|
+
if key == Curses::Key::MOUSE
|
152
|
+
e = Curses.getmouse
|
153
|
+
|
154
|
+
handle_mouse_event(e) unless e.nil?
|
155
|
+
else
|
156
|
+
handle_keyboard_event(key)
|
157
|
+
end
|
80
158
|
end
|
81
159
|
end
|
82
160
|
end
|
@@ -1,23 +1,19 @@
|
|
1
|
-
require 'twterm/event/screen/resize'
|
2
1
|
require 'twterm/subscriber'
|
3
2
|
|
4
3
|
module Twterm
|
5
4
|
class SearchQueryWindow
|
6
|
-
include Curses
|
7
|
-
include Singleton
|
8
5
|
include Subscriber
|
9
6
|
|
10
7
|
class CancelInput < StandardError; end
|
11
8
|
|
12
9
|
attr_reader :last_query
|
13
10
|
|
14
|
-
|
15
|
-
|
11
|
+
# @param window [Curses::Window]
|
12
|
+
def initialize(window)
|
13
|
+
@window = window
|
16
14
|
@searching_down = true
|
17
15
|
@str = ''
|
18
16
|
@last_query = ''
|
19
|
-
|
20
|
-
subscribe(Event::Screen::Resize, :resize)
|
21
17
|
end
|
22
18
|
|
23
19
|
def input
|
@@ -29,7 +25,7 @@ module Twterm
|
|
29
25
|
chars = []
|
30
26
|
|
31
27
|
loop do
|
32
|
-
char = getch
|
28
|
+
char = Curses.getch
|
33
29
|
|
34
30
|
if char.nil?
|
35
31
|
case chars.first
|
@@ -114,13 +110,9 @@ module Twterm
|
|
114
110
|
|
115
111
|
private
|
116
112
|
|
113
|
+
# @return [Curses::Window]
|
117
114
|
attr_reader :window
|
118
115
|
|
119
|
-
def resize(_event)
|
120
|
-
window.resize(1, stdscr.maxx)
|
121
|
-
window.move(stdscr.maxy - 1, 0)
|
122
|
-
end
|
123
|
-
|
124
116
|
def render(str)
|
125
117
|
window.clear
|
126
118
|
window.setpos(0, 0)
|
data/lib/twterm/status.rb
CHANGED
@@ -12,6 +12,7 @@ class Twitter::Tweet
|
|
12
12
|
end
|
13
13
|
|
14
14
|
module Twterm
|
15
|
+
# A tweet
|
15
16
|
class Status
|
16
17
|
attr_reader :created_at, :favorite_count, :favorited, :hashtags, :id,
|
17
18
|
:in_reply_to_status_id, :media, :retweet_count, :retweeted,
|
@@ -23,11 +24,13 @@ module Twterm
|
|
23
24
|
other.is_a?(self.class) && id == other.id
|
24
25
|
end
|
25
26
|
|
27
|
+
# @todo This should be done in a presenter
|
26
28
|
def date
|
27
29
|
format = Time.now - @created_at < 86_400 ? '%H:%M:%S' : '%Y-%m-%d %H:%M:%S'
|
28
30
|
@created_at.strftime(format)
|
29
31
|
end
|
30
32
|
|
33
|
+
# @todo This can be marked as private
|
31
34
|
def expand_url!
|
32
35
|
sub = -> (x) { @text.sub!(x.url, x.display_url) }
|
33
36
|
(@media + @urls).each(&sub)
|
@@ -69,10 +72,16 @@ module Twterm
|
|
69
72
|
expand_url!
|
70
73
|
end
|
71
74
|
|
75
|
+
# Is this status a quote?
|
76
|
+
#
|
77
|
+
# @return [Boolean]
|
72
78
|
def quote?
|
73
79
|
!quoted_status_id.nil?
|
74
80
|
end
|
75
81
|
|
82
|
+
# Is this status a retweet?
|
83
|
+
#
|
84
|
+
# @return [Boolean]
|
76
85
|
def retweet?
|
77
86
|
!retweeted_status_id.nil?
|
78
87
|
end
|
@@ -93,6 +102,7 @@ module Twterm
|
|
93
102
|
@retweeted = false
|
94
103
|
end
|
95
104
|
|
105
|
+
# @return [self]
|
96
106
|
def update!(tweet, is_retweeted_status = false)
|
97
107
|
@retweet_count = tweet.retweet_count
|
98
108
|
@favorite_count = tweet.favorite_count
|