twterm 2.5.0 → 2.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +62 -0
  3. data/.gitignore +1 -0
  4. data/Makefile +18 -0
  5. data/README.md +30 -4
  6. data/bin/twterm +1 -3
  7. data/default.nix +21 -0
  8. data/gemset.nix +5130 -0
  9. data/lib/twterm/app.rb +0 -18
  10. data/lib/twterm/client.rb +1 -6
  11. data/lib/twterm/color_manager.rb +8 -9
  12. data/lib/twterm/image.rb +26 -0
  13. data/lib/twterm/image/underlined.rb +31 -0
  14. data/lib/twterm/image_builder/user_name_image_builder.rb +1 -0
  15. data/lib/twterm/key_mapper.rb +6 -8
  16. data/lib/twterm/key_mapper/abstract_key_mapper.rb +2 -2
  17. data/lib/twterm/list.rb +36 -1
  18. data/lib/twterm/message_window.rb +4 -5
  19. data/lib/twterm/persistable_configuration_proxy.rb +6 -6
  20. data/lib/twterm/preferences.rb +8 -1
  21. data/lib/twterm/rest_client.rb +0 -33
  22. data/lib/twterm/screen.rb +54 -14
  23. data/lib/twterm/search_query_window.rb +4 -5
  24. data/lib/twterm/status.rb +10 -0
  25. data/lib/twterm/tab/abstract_tab.rb +33 -8
  26. data/lib/twterm/tab/new/index.rb +0 -10
  27. data/lib/twterm/tab/new/list.rb +3 -3
  28. data/lib/twterm/tab/new/search.rb +2 -2
  29. data/lib/twterm/tab/new/user.rb +2 -2
  30. data/lib/twterm/tab/preferences/control.rb +77 -0
  31. data/lib/twterm/tab/preferences/index.rb +6 -0
  32. data/lib/twterm/tab/status_tab.rb +10 -0
  33. data/lib/twterm/tab/statuses/abstract_statuses_tab.rb +4 -4
  34. data/lib/twterm/tab/statuses/home.rb +0 -3
  35. data/lib/twterm/tab/statuses/mentions.rb +1 -0
  36. data/lib/twterm/tab/user_tab.rb +0 -9
  37. data/lib/twterm/tab_manager.rb +77 -5
  38. data/lib/twterm/tweetbox.rb +2 -3
  39. data/lib/twterm/user.rb +1 -0
  40. data/lib/twterm/version.rb +1 -1
  41. data/nix/Gemfile +3 -0
  42. data/nix/Gemfile.lock +70 -0
  43. data/nix/gemset.nix +283 -0
  44. data/shell.nix +40 -0
  45. data/twterm.gemspec +13 -13
  46. metadata +42 -44
  47. data/.travis.yml +0 -12
  48. data/lib/twterm/direct_message.rb +0 -60
  49. data/lib/twterm/direct_message_composer.rb +0 -80
  50. data/lib/twterm/direct_message_manager.rb +0 -51
  51. data/lib/twterm/event/direct_message/fetched.rb +0 -10
  52. data/lib/twterm/event/notification/direct_message.rb +0 -30
  53. data/lib/twterm/event/status/timeline.rb +0 -10
  54. data/lib/twterm/repository/direct_message_repository.rb +0 -14
  55. data/lib/twterm/streaming_client.rb +0 -146
  56. data/lib/twterm/tab/direct_message/conversation.rb +0 -104
  57. data/lib/twterm/tab/direct_message/conversation_list.rb +0 -100
@@ -1,80 +0,0 @@
1
- require 'twterm/event/screen/refresh'
2
- require 'twterm/publisher'
3
- require 'twterm/user'
4
- require 'twterm/utils'
5
-
6
- module Twterm
7
- class DirectMessageComposer
8
- include Readline
9
- include Curses
10
- include Publisher
11
- include Utils
12
-
13
- def initialize(app, client)
14
- @app, @client = app, client
15
- end
16
-
17
- def compose(recipient)
18
- check_type User, recipient
19
-
20
- clear
21
-
22
- resetter = proc do
23
- reset_prog_mode
24
- sleep 0.1
25
- publish(Event::Screen::Refresh.new)
26
- end
27
-
28
- thread = Thread.new do
29
- close_screen
30
-
31
- puts "\nCompose new message to @%s:" % recipient.screen_name
32
-
33
- app.completion_manager.set_default_mode!
34
-
35
- loop do
36
- line = (readline('> ', true) || '').strip
37
- break if line.empty?
38
-
39
- if line.end_with?('\\')
40
- @text << line.chop.rstrip + "\n"
41
- else
42
- @text << line
43
- break
44
- end
45
- end
46
-
47
- puts "\n"
48
-
49
- resetter.call
50
- send(recipient) unless text.empty?
51
- end
52
-
53
- app.register_interruption_handler do
54
- thread.kill
55
- clear
56
- puts "\nCanceled"
57
- resetter.call
58
- end
59
-
60
- thread.join
61
- end
62
-
63
- private
64
-
65
- attr_reader :app, :client
66
-
67
- def clear
68
- @text = ''
69
- end
70
-
71
- def send(recipient)
72
- client.create_direct_message(recipient, text)
73
- clear
74
- end
75
-
76
- def text
77
- @text || ''
78
- end
79
- end
80
- end
@@ -1,51 +0,0 @@
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_id, message)
25
- check_type DirectMessage, message
26
-
27
- @conversations[collocutor_id] ||= DirectMessage::Conversation.new(collocutor_id)
28
- @conversations[collocutor_id] << message
29
- end
30
-
31
- def fetch
32
- client.direct_messages_received.then do |messages|
33
- messages.each { |m| add(m.sender_id, m) }
34
- publish(Event::DirectMessage::Fetched.new)
35
- end
36
-
37
- client.direct_messages_sent.then do |messages|
38
- messages.each { |m| add(m.recipient_id, m) }
39
- publish(Event::DirectMessage::Fetched.new)
40
- end
41
- end
42
-
43
- def conversations
44
- @conversations.values
45
- end
46
-
47
- private
48
-
49
- attr_reader :client
50
- end
51
- end
@@ -1,10 +0,0 @@
1
- require 'twterm/event/abstract_event'
2
-
3
- module Twterm
4
- module Event
5
- module DirectMessage
6
- class Fetched < AbstractEvent
7
- end
8
- end
9
- end
10
- end
@@ -1,30 +0,0 @@
1
- require 'twterm/event/notification/abstract_notification'
2
-
3
- module Twterm
4
- module Event
5
- module Notification
6
- class DirectMessage < AbstractNotification
7
- # @param [Twterm::DirectMessage] message
8
- # @param [Twterm::User] user
9
- def initialize(message, user)
10
- @message = message
11
- @user = user
12
- end
13
-
14
- # @return [String] notification body
15
- def body
16
- message.text
17
- end
18
-
19
- # @return [String] notification title
20
- def title
21
- "@#{user.screen_name} has sent you a message"
22
- end
23
-
24
- private
25
-
26
- attr_reader :message, :user
27
- end
28
- end
29
- end
30
- end
@@ -1,10 +0,0 @@
1
- require 'twterm/event/status/abstract_status_event'
2
-
3
- module Twterm
4
- module Event
5
- module Status
6
- class Timeline < AbstractStatusEvent
7
- end
8
- end
9
- end
10
- end
@@ -1,14 +0,0 @@
1
- require 'twterm/direct_message'
2
- require 'twterm/repository/abstract_entity_repository'
3
-
4
- module Twterm
5
- module Repository
6
- class DirectMessageRepository < AbstractEntityRepository
7
- private
8
-
9
- def type
10
- DirectMessage
11
- end
12
- end
13
- end
14
- end
@@ -1,146 +0,0 @@
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'
10
- require 'twterm/event/status/mention'
11
- require 'twterm/event/status/timeline'
12
- require 'twterm/publisher'
13
-
14
- module Twterm
15
- module StreamingClient
16
- include Publisher
17
-
18
- CONSUMER_KEY = 'vLNSVFgXclBJQJRZ7VLMxL9lA'.freeze
19
- CONSUMER_SECRET = 'OFLKzrepRG2p1hq0nUB9j2S9ndFQoNTPheTpmOY0GYw55jGgS5'.freeze
20
-
21
- def connect_user_stream
22
- return if user_stream_connected?
23
-
24
- @streaming_thread = Thread.new do
25
- begin
26
- publish(Event::Message::Info.new('Trying to connect to Twitter...'))
27
- streaming_client.user do |event|
28
- keep_alive!
29
-
30
- case event
31
- when Twitter::Tweet
32
- status = status_repository.create(event)
33
- user = user_repository.create(event.user)
34
-
35
- publish(Event::Status::Timeline.new(status))
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
49
- when Twitter::Streaming::Event
50
- case event.name
51
- when :favorite
52
- next if event.source.id == user_id
53
- user = user_repository.create(event.source)
54
- status = status_repository.create(event.target_object)
55
- notification = Event::Notification::Like.new(status, user)
56
- publish(notification)
57
- when :follow
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
65
-
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
71
-
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)
76
- end
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)
84
- when Twitter::Streaming::FriendList
85
- user_stream_connected!
86
- when Twitter::Streaming::DeletedTweet
87
- publish(Event::StatusDeleted.new(event.id))
88
- end
89
- end
90
- rescue Twitter::Error::TooManyRequests
91
- publish(Event::Message::Error.new('Rate limit exceeded'))
92
- sleep 120
93
- retry
94
- rescue Errno::ENETUNREACH, Errno::ETIMEDOUT, Resolv::ResolvError
95
- publish(Event::Message::Error.new('Network is unavailable'))
96
- sleep 30
97
- retry
98
- rescue Twitter::Error => e
99
- publish(Event::Message::Error.new(e.message))
100
- end
101
- end
102
- end
103
-
104
- private
105
-
106
- def keep_alive!
107
- @keep_alive_timer.kill if @keep_alive_timer.is_a?(Thread)
108
- @keep_alive_timer = Thread.new do
109
- sleep(120)
110
- @user_stream_connected = false
111
- @streaming_thread.kill
112
- connect_user_stream
113
- end
114
- end
115
-
116
- def streaming_client
117
- @streaming_client ||= Twitter::Streaming::Client.new do |config|
118
- config.consumer_key = CONSUMER_KEY
119
- config.consumer_secret = CONSUMER_SECRET
120
- config.access_token = @access_token
121
- config.access_token_secret = @access_token_secret
122
- end
123
- end
124
-
125
- def user_stream_connected?
126
- @user_stream_connected || false
127
- end
128
-
129
- def user_stream_connected!
130
- publish(Event::Message::Info.new('Connection established')) unless user_stream_connected?
131
- @user_stream_connected = true
132
- end
133
-
134
- def user_stream_disconnected!
135
- @user_stream_connected = false
136
- end
137
-
138
- def user_stream_initialized?
139
- @user_stream_initialized || false
140
- end
141
-
142
- def user_stream_initialized!
143
- @user_stream_initialized = true
144
- end
145
- end
146
- end
@@ -1,104 +0,0 @@
1
- require 'twterm/direct_message_composer'
2
- require 'twterm/event/direct_message/fetched'
3
- require 'twterm/image_builder/user_name_image_builder'
4
- require 'twterm/subscriber'
5
- require 'twterm/tab/abstract_tab'
6
- require 'twterm/tab/loadable'
7
- require 'twterm/tab/searchable'
8
-
9
- module Twterm
10
- module Tab
11
- module DirectMessage
12
- class Conversation < AbstractTab
13
- include Searchable
14
- include Subscriber
15
- include Loadable
16
-
17
- def drawable_item_count
18
- messages.drop(scroller.offset).lazy
19
- .map { |m| m.text.split_by_width(window.maxx - 4).count + 2 }
20
- .scan(0, :+)
21
- .each_cons(2)
22
- .select { |_, l| l < window.maxy }
23
- .count
24
- end
25
-
26
- def image
27
- return Image.string(initially_loaded? ? 'No results found' : 'Loading...') if items.empty?
28
-
29
- scroller.drawable_items.map.with_index(0) do |message, i|
30
- sender = app.user_repository.find(message.sender_id)
31
-
32
- header = [
33
- ImageBuilder::UserNameImageBuilder.new(sender).build,
34
- Image.string(message.date.to_s).brackets,
35
- ].intersperse(Image.whitespace).reduce(Image.empty, :-)
36
-
37
- body = message.text.split_by_width(window.maxx - 4)
38
- .map { |x| Image.string(x) }
39
- .reduce(Image.empty, :|)
40
-
41
- m = header | body
42
-
43
- cursor = Image.cursor(m.height, scroller.current_index?(i))
44
-
45
- cursor - Image.whitespace - m
46
- end
47
- .intersperse(Image.blank_line)
48
- .reduce(Image.empty, :|)
49
- end
50
-
51
- def initialize(app, client, conversation)
52
- super(app, client)
53
-
54
- @conversation = conversation
55
-
56
- subscribe(Event::DirectMessage::Fetched) { initially_loaded! }
57
- end
58
-
59
- def items
60
- messages
61
- end
62
-
63
- def matches?(message, query)
64
- sender = app.user_repository.find(message.sender_id)
65
-
66
- [
67
- message.text,
68
- sender.screen_name,
69
- sender.name
70
- ].any? { |x| x.downcase.include?(query.downcase) }
71
- end
72
-
73
- def respond_to_key(key)
74
- return true if scroller.respond_to_key(key)
75
-
76
- k = KeyMapper.instance
77
-
78
- case key
79
- when k[:status, :compose], k[:status, :reply]
80
- collocutor = app.user_repository.find(conversation.collocutor_id)
81
- app.direct_message_composer.compose(collocutor)
82
- else
83
- return false
84
- end
85
-
86
- true
87
- end
88
-
89
- def title
90
- collocutor = app.user_repository.find(conversation.collocutor_id)
91
- '@%s messages' % collocutor.screen_name
92
- end
93
-
94
- private
95
-
96
- attr_reader :conversation
97
-
98
- def messages
99
- @conversation.messages
100
- end
101
- end
102
- end
103
- end
104
- end