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.
- checksums.yaml +4 -4
- data/bin/twterm +4 -4
- data/lib/twterm/app.rb +19 -4
- data/lib/twterm/client.rb +8 -470
- data/lib/twterm/direct_message.rb +82 -0
- data/lib/twterm/direct_message_composer.rb +74 -0
- data/lib/twterm/direct_message_manager.rb +52 -0
- data/lib/twterm/event/base.rb +22 -0
- data/lib/twterm/event/direct_message/fetched.rb +10 -0
- data/lib/twterm/event/favorite.rb +18 -0
- data/lib/twterm/event/follow.rb +17 -0
- data/lib/twterm/event/notification.rb +33 -0
- data/lib/twterm/event/open_uri.rb +11 -0
- data/lib/twterm/event/screen/resize.rb +13 -0
- data/lib/twterm/event/status/base.rb +14 -0
- data/lib/twterm/event/status/delete.rb +13 -0
- data/lib/twterm/event/status/mention.rb +10 -0
- data/lib/twterm/event/status/timeline.rb +10 -0
- data/lib/twterm/event_dispatcher.rb +59 -0
- data/lib/twterm/filter_query_window.rb +11 -5
- data/lib/twterm/filterable_list.rb +6 -1
- data/lib/twterm/notifier.rb +39 -15
- data/lib/twterm/promise.rb +2 -2
- data/lib/twterm/publisher.rb +16 -0
- data/lib/twterm/rest_client.rb +401 -0
- data/lib/twterm/screen.rb +16 -13
- data/lib/twterm/status.rb +12 -1
- data/lib/twterm/streaming_client.rb +103 -0
- data/lib/twterm/subscriber.rb +33 -0
- data/lib/twterm/tab/base.rb +13 -6
- data/lib/twterm/tab/direct_message/conversation.rb +103 -0
- data/lib/twterm/tab/direct_message/conversation_list.rb +99 -0
- data/lib/twterm/tab/key_assignments_cheatsheet.rb +3 -2
- data/lib/twterm/tab/new/list.rb +5 -3
- data/lib/twterm/tab/new/search.rb +3 -2
- data/lib/twterm/tab/new/start.rb +17 -2
- data/lib/twterm/tab/new/user.rb +6 -3
- data/lib/twterm/tab/statuses/base.rb +18 -11
- data/lib/twterm/tab/statuses/conversation.rb +3 -2
- data/lib/twterm/tab/statuses/favorites.rb +3 -2
- data/lib/twterm/tab/statuses/home.rb +10 -4
- data/lib/twterm/tab/statuses/list_timeline.rb +3 -2
- data/lib/twterm/tab/statuses/mentions.rb +6 -6
- data/lib/twterm/tab/statuses/search.rb +4 -3
- data/lib/twterm/tab/statuses/user_timeline.rb +3 -2
- data/lib/twterm/tab/user_tab.rb +26 -16
- data/lib/twterm/tab/users/base.rb +3 -2
- data/lib/twterm/tab/users/followers.rb +3 -2
- data/lib/twterm/tab/users/friends.rb +3 -2
- data/lib/twterm/tab_manager.rb +20 -8
- data/lib/twterm/tweetbox.rb +5 -2
- data/lib/twterm/uri_opener.rb +25 -0
- data/lib/twterm/user.rb +11 -1
- data/lib/twterm/utils.rb +13 -0
- data/lib/twterm/version.rb +1 -1
- data/lib/twterm.rb +0 -3
- data/spec/twterm/event/screen/resize_spec.rb +11 -0
- data/spec/twterm/event_dispatcher_spec.rb +19 -0
- data/twterm.gemspec +1 -1
- metadata +29 -7
- data/lib/twterm/notification/base.rb +0 -24
- data/lib/twterm/notification/error.rb +0 -19
- data/lib/twterm/notification/message.rb +0 -19
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'twterm/publisher'
|
2
|
+
require 'twterm/user'
|
3
|
+
require 'twterm/utils'
|
4
|
+
|
5
|
+
module Twterm
|
6
|
+
class DirectMessageComposer
|
7
|
+
include Singleton
|
8
|
+
include Readline
|
9
|
+
include Curses
|
10
|
+
include Publisher
|
11
|
+
include Utils
|
12
|
+
|
13
|
+
def compose(recipient)
|
14
|
+
check_type User, recipient
|
15
|
+
|
16
|
+
clear
|
17
|
+
|
18
|
+
resetter = proc do
|
19
|
+
reset_prog_mode
|
20
|
+
sleep 0.1
|
21
|
+
Screen.instance.refresh
|
22
|
+
end
|
23
|
+
|
24
|
+
thread = Thread.new do
|
25
|
+
close_screen
|
26
|
+
|
27
|
+
puts "\nCompose new message to @%s:" % recipient.screen_name
|
28
|
+
|
29
|
+
CompletionManager.instance.set_default_mode!
|
30
|
+
|
31
|
+
loop do
|
32
|
+
line = (readline('> ', true) || '').strip
|
33
|
+
break if line.empty?
|
34
|
+
|
35
|
+
if line.end_with?('\\')
|
36
|
+
@text << line.chop.rstrip + "\n"
|
37
|
+
else
|
38
|
+
@text << line
|
39
|
+
break
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
puts "\n"
|
44
|
+
|
45
|
+
resetter.call
|
46
|
+
send(recipient) unless text.empty?
|
47
|
+
end
|
48
|
+
|
49
|
+
App.instance.register_interruption_handler do
|
50
|
+
thread.kill
|
51
|
+
clear
|
52
|
+
puts "\nCanceled"
|
53
|
+
resetter.call
|
54
|
+
end
|
55
|
+
|
56
|
+
thread.join
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def clear
|
62
|
+
@text = ''
|
63
|
+
end
|
64
|
+
|
65
|
+
def send(recipient)
|
66
|
+
Client.current.create_direct_message(recipient, text)
|
67
|
+
clear
|
68
|
+
end
|
69
|
+
|
70
|
+
def text
|
71
|
+
@text || ''
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'twterm/client'
|
2
|
+
require 'twterm/direct_message'
|
3
|
+
require 'twterm/event/direct_message/fetched'
|
4
|
+
require 'twterm/publisher'
|
5
|
+
require 'twterm/scheduler'
|
6
|
+
require 'twterm/user'
|
7
|
+
require 'twterm/utils'
|
8
|
+
|
9
|
+
module Twterm
|
10
|
+
class DirectMessageManager
|
11
|
+
include Publisher, Utils
|
12
|
+
|
13
|
+
def initialize(client)
|
14
|
+
check_type Client, client
|
15
|
+
|
16
|
+
@client = client
|
17
|
+
@conversations = {}
|
18
|
+
|
19
|
+
fetch
|
20
|
+
|
21
|
+
Scheduler.new(300) { fetch }
|
22
|
+
end
|
23
|
+
|
24
|
+
def add(collocutor, message)
|
25
|
+
check_type User, collocutor
|
26
|
+
check_type DirectMessage, message
|
27
|
+
|
28
|
+
@conversations[collocutor.id] ||= DirectMessage::Conversation.new(collocutor)
|
29
|
+
@conversations[collocutor.id] << message
|
30
|
+
end
|
31
|
+
|
32
|
+
def fetch
|
33
|
+
client.direct_messages_received.then do |messages|
|
34
|
+
messages.each { |m| add(m.sender, m) }
|
35
|
+
publish(Event::DirectMessage::Fetched.new)
|
36
|
+
end
|
37
|
+
|
38
|
+
client.direct_messages_sent.then do |messages|
|
39
|
+
messages.each { |m| add(m.recipient, m) }
|
40
|
+
publish(Event::DirectMessage::Fetched.new)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def conversations
|
45
|
+
@conversations.values
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
attr_reader :client
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'twterm/utils'
|
2
|
+
|
3
|
+
module Twterm
|
4
|
+
module Event
|
5
|
+
class Base
|
6
|
+
include Utils
|
7
|
+
|
8
|
+
def initialize(*args)
|
9
|
+
fields.zip(args).map(&:flatten).each do |name, type, value|
|
10
|
+
check_type type, value
|
11
|
+
|
12
|
+
instance_variable_set('@%s' % name, value)
|
13
|
+
self.class.send(:attr_reader, name)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def fields
|
18
|
+
[]
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'twterm/client'
|
2
|
+
require 'twterm/event/base'
|
3
|
+
require 'twterm/user'
|
4
|
+
require 'twterm/status'
|
5
|
+
|
6
|
+
module Twterm
|
7
|
+
module Event
|
8
|
+
class Favorite < Base
|
9
|
+
def fields
|
10
|
+
{
|
11
|
+
source: ::Twterm::User,
|
12
|
+
target: ::Twterm::Status,
|
13
|
+
authenticating_user: ::Twterm::Client
|
14
|
+
}
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'twterm/client'
|
2
|
+
require 'twterm/event/base'
|
3
|
+
require 'twterm/user'
|
4
|
+
|
5
|
+
module Twterm
|
6
|
+
module Event
|
7
|
+
class Follow < Base
|
8
|
+
def fields
|
9
|
+
{
|
10
|
+
source: ::Twterm::User,
|
11
|
+
target: ::Twterm::User,
|
12
|
+
authenticating_user: ::Twterm::Client
|
13
|
+
}
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'twterm/event/base'
|
2
|
+
|
3
|
+
module Twterm
|
4
|
+
module Event
|
5
|
+
class Notification < Base
|
6
|
+
attr_reader :time
|
7
|
+
|
8
|
+
def initialize(type, message)
|
9
|
+
super(type, CGI.unescapeHTML(message))
|
10
|
+
|
11
|
+
@time = Time.now
|
12
|
+
end
|
13
|
+
|
14
|
+
def fields
|
15
|
+
{
|
16
|
+
type: Symbol,
|
17
|
+
message: String
|
18
|
+
}
|
19
|
+
end
|
20
|
+
|
21
|
+
def color
|
22
|
+
case type
|
23
|
+
when :error
|
24
|
+
[:white, :red]
|
25
|
+
when :message
|
26
|
+
[:white, :blue]
|
27
|
+
else
|
28
|
+
[:white, :black]
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'singleton'
|
2
|
+
require 'twterm/utils'
|
3
|
+
|
4
|
+
module Twterm
|
5
|
+
class EventDispatcher
|
6
|
+
include Singleton
|
7
|
+
include Utils
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@subscriptions = []
|
11
|
+
end
|
12
|
+
|
13
|
+
def dispatch(event)
|
14
|
+
@subscriptions
|
15
|
+
.select { |s| s.event == event.class }
|
16
|
+
.map(&:callback)
|
17
|
+
.each { |cb| cb.call(event) }
|
18
|
+
|
19
|
+
self
|
20
|
+
end
|
21
|
+
|
22
|
+
def register_subscription(subscriber_id, event, callback)
|
23
|
+
check_type Class, event
|
24
|
+
unless event <= Event::Base
|
25
|
+
raise TypeError, 'the second argument must be a subclass of Twterm::Event::Base'
|
26
|
+
end
|
27
|
+
|
28
|
+
@subscriptions << Subscription.new(subscriber_id, event, callback)
|
29
|
+
|
30
|
+
self
|
31
|
+
end
|
32
|
+
|
33
|
+
def unregister_subscription(subscriber_id, event)
|
34
|
+
cond = if event.nil? # remove all subscriptions from the subscriber
|
35
|
+
-> s { s.subscriber_id == subscriber_id }
|
36
|
+
else # remove only specified event
|
37
|
+
-> s { s.subscriber_id == subscriber_id && s.event == event }
|
38
|
+
end
|
39
|
+
|
40
|
+
@subscriptions.reject!(&cond)
|
41
|
+
|
42
|
+
self
|
43
|
+
end
|
44
|
+
|
45
|
+
class Subscription
|
46
|
+
attr_reader :subscriber_id, :event, :callback
|
47
|
+
|
48
|
+
def initialize(subscriber_id, event, block)
|
49
|
+
@subscriber_id, @event, @callback = subscriber_id, event, block
|
50
|
+
end
|
51
|
+
|
52
|
+
def ==(other)
|
53
|
+
self.class == other.class &&
|
54
|
+
subscriber_id == other.subscriber_id &&
|
55
|
+
event == other.event
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -1,10 +1,16 @@
|
|
1
|
+
require 'twterm/event/screen/resize'
|
2
|
+
require 'twterm/subscriber'
|
3
|
+
|
1
4
|
module Twterm
|
2
5
|
class FilterQueryWindow
|
3
6
|
include Curses
|
4
7
|
include Singleton
|
8
|
+
include Subscriber
|
5
9
|
|
6
10
|
def initialize
|
7
11
|
@window = stdscr.subwin(1, stdscr.maxx, stdscr.maxy - 1, 0)
|
12
|
+
|
13
|
+
subscribe(Event::Screen::Resize, :resize)
|
8
14
|
end
|
9
15
|
|
10
16
|
def input
|
@@ -73,13 +79,13 @@ module Twterm
|
|
73
79
|
stdscr.addstr(' ' * window.maxx)
|
74
80
|
end
|
75
81
|
|
76
|
-
def resize
|
77
|
-
@window.resize(1, stdscr.maxx)
|
78
|
-
@window.move(stdscr.maxy - 1, 0)
|
79
|
-
end
|
80
|
-
|
81
82
|
private
|
82
83
|
|
83
84
|
attr_reader :window
|
85
|
+
|
86
|
+
def resize(event)
|
87
|
+
@window.resize(1, stdscr.maxx)
|
88
|
+
@window.move(stdscr.maxy - 1, 0)
|
89
|
+
end
|
84
90
|
end
|
85
91
|
end
|
@@ -1,7 +1,12 @@
|
|
1
|
+
require 'twterm/event/notification'
|
2
|
+
require 'twterm/publisher'
|
3
|
+
|
1
4
|
module Twterm
|
2
5
|
module FilterableList
|
3
6
|
extend Forwardable
|
4
7
|
|
8
|
+
include Publisher
|
9
|
+
|
5
10
|
def filter
|
6
11
|
@filter_query = FilterQueryWindow.instance.input
|
7
12
|
|
@@ -10,7 +15,7 @@ module Twterm
|
|
10
15
|
elsif items.count == 0
|
11
16
|
query = filter_query
|
12
17
|
reset_filter
|
13
|
-
|
18
|
+
publish(Event::Notification.new(:error, "No matches found: \"#{query}\""))
|
14
19
|
else
|
15
20
|
Notifier.instance.show_message "#{total_item_count} items found: \"#{filter_query}\""
|
16
21
|
scroller.move_to_top
|
data/lib/twterm/notifier.rb
CHANGED
@@ -1,12 +1,33 @@
|
|
1
|
+
require 'twterm/subscriber'
|
2
|
+
require 'twterm/event/favorite'
|
3
|
+
require 'twterm/event/notification'
|
4
|
+
require 'twterm/event/screen/resize'
|
5
|
+
|
1
6
|
module Twterm
|
2
7
|
class Notifier
|
3
8
|
include Singleton
|
4
9
|
include Curses
|
10
|
+
include Subscriber
|
5
11
|
|
6
12
|
def initialize
|
7
13
|
@window = stdscr.subwin(1, stdscr.maxx, stdscr.maxy - 2, 0)
|
8
14
|
@queue = Queue.new
|
9
15
|
|
16
|
+
subscribe(Event::Favorite) do |e|
|
17
|
+
break if e.source.id == e.authenticating_user.user_id
|
18
|
+
|
19
|
+
msg = '@%s has favorited your tweet: %s' % [
|
20
|
+
e.source.screen_name, e.target.text
|
21
|
+
]
|
22
|
+
show_message(msg)
|
23
|
+
end
|
24
|
+
|
25
|
+
subscribe(Event::Notification) do |e|
|
26
|
+
queue(e)
|
27
|
+
end
|
28
|
+
|
29
|
+
subscribe(Event::Screen::Resize, :resize)
|
30
|
+
|
10
31
|
Thread.new do
|
11
32
|
while notification = @queue.pop
|
12
33
|
show(notification)
|
@@ -16,19 +37,8 @@ module Twterm
|
|
16
37
|
end
|
17
38
|
end
|
18
39
|
|
19
|
-
def resize
|
20
|
-
@window.resize(1, stdscr.maxx)
|
21
|
-
@window.move(stdscr.maxy - 2, 0)
|
22
|
-
end
|
23
|
-
|
24
|
-
def show_error(message)
|
25
|
-
notification = Notification::Error.new(message)
|
26
|
-
@queue.push(notification)
|
27
|
-
self
|
28
|
-
end
|
29
|
-
|
30
40
|
def show_message(message)
|
31
|
-
notification = Notification
|
41
|
+
notification = Event::Notification.new(:message, message)
|
32
42
|
@queue.push(notification)
|
33
43
|
self
|
34
44
|
end
|
@@ -41,18 +51,32 @@ module Twterm
|
|
41
51
|
|
42
52
|
@window.clear
|
43
53
|
|
44
|
-
if notification.is_a?
|
45
|
-
|
54
|
+
if notification.is_a?(Event::Notification)
|
55
|
+
fg_color, bg_color = notification.color
|
56
|
+
|
57
|
+
@window.with_color(fg_color, bg_color) do
|
46
58
|
@window.setpos(0, 0)
|
47
59
|
@window.addstr(' ' * @window.maxx)
|
48
60
|
@window.setpos(0, 1)
|
49
61
|
time = notification.time.strftime('[%H:%M:%S]')
|
50
|
-
message = notification.
|
62
|
+
message = notification.message.gsub("\n", ' ')
|
51
63
|
@window.addstr("#{time} #{message}")
|
52
64
|
end
|
53
65
|
end
|
54
66
|
|
55
67
|
@window.refresh
|
56
68
|
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
def queue(notification)
|
73
|
+
@queue.push(notification)
|
74
|
+
self
|
75
|
+
end
|
76
|
+
|
77
|
+
def resize(event)
|
78
|
+
@window.resize(1, stdscr.maxx)
|
79
|
+
@window.move(stdscr.maxy - 2, 0)
|
80
|
+
end
|
57
81
|
end
|
58
82
|
end
|
data/lib/twterm/promise.rb
CHANGED
@@ -11,7 +11,7 @@ module Twterm
|
|
11
11
|
|
12
12
|
def catch(on_rejected = nil, &block)
|
13
13
|
if on_rejected.is_a?(Proc) && block_given?
|
14
|
-
fail
|
14
|
+
fail SyntaxError, 'both block arg and actual block given'
|
15
15
|
end
|
16
16
|
|
17
17
|
on_rejected ||= block
|
@@ -48,7 +48,7 @@ module Twterm
|
|
48
48
|
|
49
49
|
def then(on_fulfilled = nil, on_rejected = nil, &block)
|
50
50
|
if (on_fulfilled.is_a?(Proc) || on_rejected.is_a?(Proc)) && block_given?
|
51
|
-
fail
|
51
|
+
fail SyntaxError, 'both block arg and actual block given'
|
52
52
|
end
|
53
53
|
|
54
54
|
on_fulfilled ||= block
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'twterm/event_dispatcher'
|
2
|
+
require 'twterm/event/base'
|
3
|
+
require 'twterm/utils'
|
4
|
+
|
5
|
+
module Twterm
|
6
|
+
module Publisher
|
7
|
+
include Utils
|
8
|
+
|
9
|
+
def publish(event)
|
10
|
+
check_type Event::Base, event
|
11
|
+
|
12
|
+
EventDispatcher.instance.dispatch(event)
|
13
|
+
event
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|