twterm 2.0.1 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -245,17 +245,15 @@ module Twterm
245
245
  end
246
246
  end
247
247
 
248
- def post(text, in_reply_to = nil)
248
+ def post(text, options = {})
249
249
  send_request do
250
- if in_reply_to.is_a? Status
251
- user = user_repository.find(in_reply_to.user_id)
252
- text = "@#{user.screen_name} #{text}"
253
- rest_client.update(text, in_reply_to_status_id: in_reply_to.id)
254
- else
255
- rest_client.update(text)
256
- end
257
- publish(Event::Notification::Success.new('Your tweet has been posted'))
250
+ rest_client.update(text, options)
258
251
  end
252
+ .then do |status|
253
+ publish(Event::Notification::Success.new('Your tweet has been posted'))
254
+
255
+ status
256
+ end
259
257
  end
260
258
 
261
259
  def rate_limit_status
@@ -25,5 +25,6 @@ class Scheduler
25
25
 
26
26
  def run
27
27
  @block.call unless @paused
28
+ rescue
28
29
  end
29
30
  end
data/lib/twterm/status.rb CHANGED
@@ -1,10 +1,19 @@
1
1
  require 'concurrent'
2
2
 
3
+ class Twitter::Tweet
4
+ attr_reader :quoted_status_id
5
+
6
+ def initialize(*args)
7
+ @quoted_status_id = args[0][:quoted_status_id]
8
+ super
9
+ end
10
+ end
11
+
3
12
  module Twterm
4
13
  class Status
5
14
  attr_reader :created_at, :favorite_count, :favorited, :id,
6
15
  :in_reply_to_status_id, :media, :retweet_count, :retweeted,
7
- :retweeted_status_id, :text, :urls, :user_id
16
+ :quoted_status_id, :retweeted_status_id, :text, :url, :urls, :user_id
8
17
  alias_method :favorited?, :favorited
9
18
  alias_method :retweeted?, :retweeted
10
19
 
@@ -35,6 +44,8 @@ module Twterm
35
44
  @text = CGI.unescapeHTML(tweet.full_text.dup)
36
45
  @created_at = tweet.created_at.dup.localtime
37
46
  @in_reply_to_status_id = tweet.in_reply_to_status_id
47
+ @quoted_status_id = tweet.quoted_status_id
48
+ @url = tweet.url
38
49
 
39
50
  update!(tweet, is_retweeted_status)
40
51
 
@@ -48,6 +59,10 @@ module Twterm
48
59
  expand_url!
49
60
  end
50
61
 
62
+ def quote?
63
+ !quoted_status_id.nil?
64
+ end
65
+
51
66
  def retweet?
52
67
  !retweeted_status_id.nil?
53
68
  end
@@ -36,9 +36,9 @@ module Twterm
36
36
 
37
37
  input_thread = Thread.new do
38
38
  close_screen
39
- app.completion_manager.set_default_mode!
39
+ app.completion_manager.set_search_mode!
40
40
  puts "\ninput search query"
41
- query = (readline('> ') || '').strip
41
+ query = (readline('> ', true) || '').strip
42
42
  resetter.call
43
43
 
44
44
  tab = query.nil? || query.empty? ? Tab::New::Search.new(app, client) : Tab::Statuses::Search.new(app, client, query)
@@ -25,7 +25,7 @@ module Twterm
25
25
  input_thread = Thread.new do
26
26
  close_screen
27
27
  puts "\nSearch user"
28
- screen_name = (readline('> @') || '').strip
28
+ screen_name = (readline('> @', true) || '').strip
29
29
  resetter.call
30
30
 
31
31
  if screen_name.nil? || screen_name.empty?
@@ -116,10 +116,16 @@ module Twterm
116
116
  render
117
117
  end
118
118
 
119
+ def quote
120
+ return if highlighted_status.nil?
121
+
122
+ app.tweetbox.quote(highlighted_original_status)
123
+ end
124
+
119
125
  def reply
120
126
  return if highlighted_status.nil?
121
127
 
122
- app.tweetbox.compose(highlighted_original_status)
128
+ app.tweetbox.reply(highlighted_original_status)
123
129
  end
124
130
 
125
131
  def respond_to_key(key)
@@ -142,6 +148,8 @@ module Twterm
142
148
  retweet
143
149
  when k[:tab, :reload]
144
150
  reload
151
+ when k[:status, :quote]
152
+ quote
145
153
  when k[:status, :user]
146
154
  show_user
147
155
  else
@@ -0,0 +1,18 @@
1
+ module Twterm
2
+ module Tab
3
+ module Statuses
4
+ module Cacheable
5
+ def retrieve_from_cache!
6
+ statuses = cached_statuses
7
+ cached_statuses.each { |status| append(status) }
8
+
9
+ unless statuses.empty?
10
+ sort
11
+ scroller.move_to_top
12
+ initially_loaded!
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -1,11 +1,14 @@
1
1
  require 'concurrent'
2
2
 
3
+ require 'twterm/tab/dumpable'
3
4
  require 'twterm/tab/statuses/base'
5
+ require 'twterm/tab/statuses/cacheable'
4
6
 
5
7
  module Twterm
6
8
  module Tab
7
9
  module Statuses
8
10
  class Conversation < Base
11
+ include Cacheable
9
12
  include Dumpable
10
13
 
11
14
  attr_reader :status_id
@@ -18,7 +21,10 @@ module Twterm
18
21
  find_or_fetch_status(status_id).then do |status|
19
22
  append(status)
20
23
  fetch_ancestor(status)
24
+ fetch_quoted_status(status)
21
25
  find_descendants(status)
26
+ fetch_possible_quotes(status)
27
+ fetch_possible_replies(status)
22
28
  end
23
29
  end
24
30
 
@@ -40,11 +46,62 @@ module Twterm
40
46
  end
41
47
  end
42
48
 
49
+ def fetch_possible_quotes(status)
50
+ client.search(status.url).then do |statuses|
51
+ statuses
52
+ .select { |s| !s.retweet? && s.quoted_status_id == status.id }
53
+ .each { |s| prepend(s) }
54
+
55
+ sort
56
+ render
57
+ end
58
+ end
59
+
60
+ def fetch_possible_replies(status)
61
+ user = app.user_repository.find(status.user_id)
62
+
63
+ return if user.nil?
64
+
65
+ client.search("to:#{user.screen_name}").then do |statuses|
66
+ statuses
67
+ .select { |s| !s.retweet? && s.in_reply_to_status_id == status.id }
68
+ .each { |s| prepend(s) }
69
+
70
+ sort
71
+ render
72
+ end
73
+ end
74
+
75
+ def fetch_quoted_status(status)
76
+ quoted_status_id = status.quoted_status_id
77
+
78
+ if quoted_status_id.nil?
79
+ Concurrent::Promise.fulfill(nil)
80
+ elsif (instance = app.status_repository.find(quoted_status_id))
81
+ Concurrent::Promise.fulfill(instance)
82
+ else
83
+ client.show_status(quoted_status_id)
84
+ end
85
+ .then do |quoted_status|
86
+ next if quoted_status.nil?
87
+ append(quoted_status)
88
+ sort
89
+ fetch_ancestor(quoted_status)
90
+ fetch_quoted_status(quoted_status)
91
+ end
92
+ end
93
+
43
94
  def find_descendants(status)
44
- app.status_repository.find_replies_for(status.id).each do |reply|
95
+ app.status_repository.find_replies_for(status.id).reject { |s| s.retweet? }.each do |reply|
45
96
  prepend(reply)
46
97
  find_descendants(reply)
47
98
  end
99
+
100
+ app.status_repository.find_quotes_for(status.id).reject { |s| s.retweet? }.each do |quote|
101
+ prepend(quote)
102
+ find_descendants(quote)
103
+ end
104
+
48
105
  sort
49
106
  end
50
107
 
@@ -57,6 +114,8 @@ module Twterm
57
114
 
58
115
  @status_id = status_id
59
116
 
117
+ retrieve_from_cache!
118
+
60
119
  reload.then do
61
120
  scroller.move_to_top
62
121
  sort
@@ -66,6 +125,12 @@ module Twterm
66
125
  def title
67
126
  'Conversation'.freeze
68
127
  end
128
+
129
+ private
130
+
131
+ def cached_statuses
132
+ [app.status_repository.find(status_id)].compact
133
+ end
69
134
  end
70
135
  end
71
136
  end
@@ -15,6 +15,8 @@ module Twterm
15
15
 
16
16
  self.title = 'Loading...'.freeze
17
17
 
18
+ @auto_reloader = Scheduler.new(300) { reload }
19
+
18
20
  find_or_fetch_list(list_id).then do |list|
19
21
  self.title = list.full_name
20
22
  app.tab_manager.refresh_window
@@ -23,8 +25,6 @@ module Twterm
23
25
  initially_loaded!
24
26
  scroller.move_to_top
25
27
  end
26
-
27
- @auto_reloader = Scheduler.new(300) { reload }
28
28
  end
29
29
  end
30
30
 
@@ -1,9 +1,11 @@
1
1
  require 'twterm/tab/statuses/base'
2
+ require 'twterm/tab/statuses/cacheable'
2
3
 
3
4
  module Twterm
4
5
  module Tab
5
6
  module Statuses
6
7
  class UserTimeline < Base
8
+ include Cacheable
7
9
  include Dumpable
8
10
 
9
11
  attr_reader :user, :user_id
@@ -29,6 +31,9 @@ module Twterm
29
31
  super(app, client)
30
32
 
31
33
  @user_id = user_id
34
+ @auto_reloader = Scheduler.new(120) { reload }
35
+
36
+ retrieve_from_cache!
32
37
 
33
38
  find_or_fetch_user(user_id).then do |user|
34
39
  @user = user
@@ -38,14 +43,18 @@ module Twterm
38
43
  initially_loaded!
39
44
  scroller.move_to_top
40
45
  end
41
-
42
- @auto_reloader = Scheduler.new(120) { reload }
43
46
  end
44
47
  end
45
48
 
46
49
  def title
47
50
  @user.nil? ? 'Loading...' : "@#{@user.screen_name} timeline"
48
51
  end
52
+
53
+ private
54
+
55
+ def cached_statuses
56
+ app.status_repository.all.select { |status| status.user_id == user_id }
57
+ end
49
58
  end
50
59
  end
51
60
  end
@@ -15,109 +15,98 @@ module Twterm
15
15
  @app, @client = app, client
16
16
  end
17
17
 
18
- def compose(in_reply_to = nil)
19
- @text = ''
20
-
21
- @in_reply_to, screen_name =
22
- if in_reply_to.is_a?(Status)
23
- [in_reply_to, app.user_repository.find(in_reply_to.user_id).screen_name]
24
- else
25
- [nil, nil]
26
- end
18
+ def compose
19
+ ask_and_post("\e[1mCompose new Tweet\e[0m", '> ', -> body { body })
20
+ end
27
21
 
28
- resetter = proc do
29
- reset_prog_mode
30
- sleep 0.1
31
- app.screen.refresh
32
- end
22
+ def quote(status)
23
+ screen_name = app.user_repository.find(status.user_id).screen_name
24
+ leading_text = "\e[1mQuoting @#{screen_name}'s Tweet\e[0m\n\n#{status.text}"
25
+ prompt = '> '
33
26
 
34
- thread = Thread.new do
35
- close_screen
27
+ ask_and_post(leading_text, prompt, -> body { "#{body} #{status.url}" })
28
+ end
36
29
 
37
- if in_reply_to.nil?
38
- puts "\nCompose new tweet:"
39
- else
40
- puts "\nReply to @#{screen_name}'s tweet: \"#{in_reply_to.text}\""
41
- end
30
+ def reply(status)
31
+ screen_name = app.user_repository.find(status.user_id).screen_name
32
+ leading_text = "\e[1mReplying to @#{screen_name}\e[0m\n\n#{status.text}"
33
+ prompt = "> @#{screen_name} "
42
34
 
43
- app.completion_manager.set_default_mode!
35
+ ask_and_post(leading_text, prompt, -> body { "@#{screen_name} #{body}" }, { in_reply_to_status_id: status.id })
36
+ end
37
+
38
+ private
39
+
40
+ attr_reader :app, :client, :in_reply_to
41
+
42
+ def ask(prompt, postprocessor, &cont)
43
+ app.completion_manager.set_default_mode!
44
+
45
+ thread = Thread.new do
46
+ raw_text = ''
44
47
 
45
48
  loop do
46
49
  loop do
47
- msg = in_reply_to.nil? || !text.empty? ? '> ' : "> @#{screen_name} "
48
- line = (readline(msg, true) || '').strip
50
+ line = (readline(prompt, true) || '').strip
49
51
  break if line.empty?
50
52
 
51
53
  if line.end_with?('\\')
52
- @text << line.chop.rstrip + "\n"
54
+ raw_text << line.chop.rstrip + "\n"
53
55
  else
54
- @text << line
56
+ raw_text << line
55
57
  break
56
58
  end
57
59
  end
58
60
 
59
61
  puts "\n"
60
62
 
63
+ text = postprocessor.call(raw_text)
64
+
61
65
  begin
62
- validate_text!
66
+ validate!(text)
63
67
  break
64
68
  rescue EmptyTextError
65
69
  break
66
70
  rescue InvalidCharactersError
67
71
  puts 'Text contains invalid characters'
68
72
  rescue TextTooLongError
69
- puts "Text is too long (#{text_length} / 140 characters)"
73
+ puts "Text is too long (#{text_length(text)} / 140 characters)"
70
74
  end
71
75
 
72
76
  puts "\n"
73
- clear
77
+ raw_text = ''
74
78
  end
75
79
 
76
- resetter.call
77
- post
80
+ reset
81
+ cont.call(raw_text) unless raw_text.empty?
78
82
  end
79
83
 
80
84
  app.register_interruption_handler do
81
85
  thread.kill
82
- clear
83
86
  puts "\nCanceled"
84
- resetter.call
87
+ reset
85
88
  end
86
89
 
87
90
  thread.join
88
91
  end
89
92
 
90
- private
91
-
92
- attr_reader :app, :client, :in_reply_to
93
-
94
- def clear
95
- @text = ''
96
- @in_reply_to = nil
97
- end
98
-
99
- def post
100
- validate_text!
101
- client.post(text, in_reply_to)
102
- rescue EmptyTextError
103
- # do nothing
104
- rescue InvalidCharactersError
105
- publish(Event::Notification::Error.new('Text contains invalid characters'))
106
- rescue TextTooLongError
107
- publish(Event::Notification::Error.new("Text is too long (#{text_length} / 140 characters)"))
108
- ensure
109
- clear
93
+ def ask_and_post(leading_text, prompt, postprocessor, options = {})
94
+ close_screen
95
+ puts "\e[H\e[2J#{leading_text}\n\n"
96
+ ask(prompt, postprocessor) { |text| client.post(postprocessor.call(text), options) }
110
97
  end
111
98
 
112
- def text
113
- @text || ''
99
+ def reset
100
+ reset_prog_mode
101
+ sleep 0.1
102
+ app.screen.refresh
114
103
  end
115
104
 
116
- def text_length
105
+ def text_length(text)
117
106
  Twitter::Validation.tweet_length(text)
118
107
  end
119
108
 
120
- def validate_text!
109
+ def validate!(text)
121
110
  case Twitter::Validation.tweet_invalid?(text)
122
111
  when :empty
123
112
  fail EmptyTextError