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.
Files changed (67) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +62 -0
  3. data/.gitignore +1 -0
  4. data/Makefile +21 -0
  5. data/README.md +52 -6
  6. data/bin/twterm +1 -3
  7. data/default.nix +21 -0
  8. data/gemset.nix +4981 -0
  9. data/lib/twterm/app.rb +13 -26
  10. data/lib/twterm/client.rb +1 -6
  11. data/lib/twterm/color_manager.rb +8 -9
  12. data/lib/twterm/image.rb +31 -0
  13. data/lib/twterm/image/attr.rb +42 -0
  14. data/lib/twterm/image/bold.rb +7 -21
  15. data/lib/twterm/image/color.rb +9 -15
  16. data/lib/twterm/image/dim.rb +13 -0
  17. data/lib/twterm/image/underlined.rb +17 -0
  18. data/lib/twterm/image_builder/user_name_image_builder.rb +1 -0
  19. data/lib/twterm/key_mapper.rb +6 -8
  20. data/lib/twterm/key_mapper/abstract_key_mapper.rb +2 -2
  21. data/lib/twterm/list.rb +36 -1
  22. data/lib/twterm/message_window.rb +4 -13
  23. data/lib/twterm/persistable_configuration_proxy.rb +6 -6
  24. data/lib/twterm/preferences.rb +8 -1
  25. data/lib/twterm/rest_client.rb +0 -33
  26. data/lib/twterm/screen.rb +103 -25
  27. data/lib/twterm/search_query_window.rb +5 -13
  28. data/lib/twterm/status.rb +10 -0
  29. data/lib/twterm/tab/abstract_tab.rb +30 -16
  30. data/lib/twterm/tab/new/index.rb +0 -10
  31. data/lib/twterm/tab/new/search.rb +2 -2
  32. data/lib/twterm/tab/new/user.rb +2 -2
  33. data/lib/twterm/tab/preferences/control.rb +77 -0
  34. data/lib/twterm/tab/preferences/index.rb +6 -0
  35. data/lib/twterm/tab/scrollable.rb +1 -1
  36. data/lib/twterm/tab/searchable.rb +6 -4
  37. data/lib/twterm/tab/status_tab.rb +10 -0
  38. data/lib/twterm/tab/statuses/abstract_statuses_tab.rb +11 -6
  39. data/lib/twterm/tab/statuses/home.rb +0 -3
  40. data/lib/twterm/tab/statuses/mentions.rb +1 -0
  41. data/lib/twterm/tab/user_tab.rb +0 -9
  42. data/lib/twterm/tab_manager.rb +77 -10
  43. data/lib/twterm/tweetbox.rb +2 -3
  44. data/lib/twterm/user.rb +1 -0
  45. data/lib/twterm/version.rb +1 -1
  46. data/nix/Gemfile +3 -0
  47. data/nix/Gemfile.lock +77 -0
  48. data/nix/gemset.nix +325 -0
  49. data/shell.nix +40 -0
  50. data/spec/twterm/image/bold_spec.rb +30 -0
  51. data/spec/twterm/image/color_spec.rb +16 -0
  52. data/spec/twterm/image/dim_spec.rb +30 -0
  53. data/twterm.gemspec +13 -13
  54. metadata +46 -46
  55. data/.travis.yml +0 -12
  56. data/lib/twterm/direct_message.rb +0 -60
  57. data/lib/twterm/direct_message_composer.rb +0 -80
  58. data/lib/twterm/direct_message_manager.rb +0 -51
  59. data/lib/twterm/event/direct_message/fetched.rb +0 -10
  60. data/lib/twterm/event/notification/direct_message.rb +0 -30
  61. data/lib/twterm/event/screen/resize.rb +0 -13
  62. data/lib/twterm/event/status/timeline.rb +0 -10
  63. data/lib/twterm/repository/direct_message_repository.rb +0 -14
  64. data/lib/twterm/streaming_client.rb +0 -146
  65. data/lib/twterm/tab/direct_message/conversation.rb +0 -104
  66. data/lib/twterm/tab/direct_message/conversation_list.rb +0 -100
  67. data/spec/twterm/event/screen/resize_spec.rb +0 -11
data/.travis.yml DELETED
@@ -1,12 +0,0 @@
1
- language: ruby
2
-
3
- rvm:
4
- - 2.1
5
- - 2.2
6
- - 2.3.0
7
- - 2.4.0
8
- - 2.5.0
9
-
10
- before_install: gem update --system
11
-
12
- script: bundle exec rubocop --parallel --fail-level warning && bundle exec rspec
@@ -1,60 +0,0 @@
1
- require 'twterm/user'
2
- require 'twterm/utils'
3
-
4
- module Twterm
5
- class DirectMessage
6
- attr_reader :id, :created_at, :recipient_id, :sender_id, :text
7
-
8
- def initialize(message)
9
- @id = message.id
10
- update!(message)
11
- end
12
-
13
- def ==(other)
14
- other.is_a?(self.class) && id == other.id
15
- end
16
-
17
- def date
18
- format = Time.now - @created_at < 86_400 ? '%H:%M:%S' : '%Y-%m-%d %H:%M:%S'
19
- @created_at.strftime(format)
20
- end
21
-
22
- def update!(message)
23
- @created_at = message.created_at.dup.localtime
24
- @recipient_id = message.recipient.id
25
- @sender_id = message.sender.id
26
- @text = message.text
27
-
28
- self
29
- end
30
-
31
- class Conversation
32
- include Utils
33
-
34
- attr_reader :collocutor_id, :messages
35
-
36
- def initialize(collocutor_id)
37
- @collocutor_id = collocutor_id
38
- @messages = []
39
- end
40
-
41
- def <<(message)
42
- @messages << message if messages.find { |m| m == message }.nil?
43
- @messages.sort_by!(&:created_at).reverse!
44
-
45
- self
46
- end
47
-
48
- def preview
49
- messages.sort_by(&:created_at).last.text.gsub("\n", ' ')
50
- end
51
-
52
- def updated_at
53
- updated_at = @messages.map(&:created_at).max
54
-
55
- format = Time.now - updated_at < 86_400 ? '%H:%M:%S' : '%Y-%m-%d %H:%M:%S'
56
- updated_at.strftime(format)
57
- end
58
- end
59
- end
60
- end
@@ -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,13 +0,0 @@
1
- require 'twterm/event/abstract_event'
2
-
3
- module Twterm
4
- module Event
5
- module Screen
6
- class Resize < AbstractEvent
7
- def fields
8
- { lines: Integer, cols: Integer }
9
- end
10
- end
11
- end
12
- end
13
- 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