mournmail 0.3.2 → 1.0.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 55cea609c299af267b279b8434b661ec471776fe26c5175fdc363a527cc3cc7e
4
- data.tar.gz: b77b32b8fb3923a8e545cd1cd5084dab346974a4f9aa210ae353f76e17839370
3
+ metadata.gz: 140daea6492012b5093229b435fdbe2c98548430b8b8a74a0714483dd039c085
4
+ data.tar.gz: cb3fb9f8bd0a43fce73f3bd2d3e1d9515acaf74c1a3246c429c027ea7f9db140
5
5
  SHA512:
6
- metadata.gz: a891f54dc0e39e09d72a5a8eb9e0272560226fe417eb809462f245bf77e535a9dcaacdf34600e45933723dd0b81378d64e73f1538dcb8c5f2e7c4ffea370078a
7
- data.tar.gz: f0bee693b1b02a51449e38ca5d001a085f999040be22dcb5d29e1782dffd3dd31698312b9abde9f1fbc57528a72e031df05fa75ed4a6ee2c7535cd318063991e
6
+ metadata.gz: dbfe86922c8b715349fd6d22443e0222b58b6b4c6ea64e78a3cfc01b1caa8f9b4852796a381556f536a9cc8608db8203e69de17c7ce47d4172e03392c34a0847
7
+ data.tar.gz: 4437171b2b8baa965d829c30f1807d0c23b459dd0b5c5756b54fd728177d0e3af2df6702939b155f80913fb254d49303aee6123047509a0f44643e471feb03d0
data/README.md CHANGED
@@ -33,7 +33,11 @@ CONFIG[:mournmail_accounts] = {
33
33
  },
34
34
  spam_mailbox: "spam",
35
35
  outbox_mailbox: "outbox",
36
- archive_mailbox_format: "archive/%Y"
36
+ archive_mailbox_format: "archive/%Y",
37
+ signature: <<~EOF
38
+ --
39
+ Shugo Maeda <shugo@example.com>
40
+ EOF
37
41
  },
38
42
  "gmail.com" => {
39
43
  from: "Example <example@gmail.com>",
@@ -54,7 +58,7 @@ CONFIG[:mournmail_accounts] = {
54
58
  password: File.read(File.expand_path("~/.textbringer/gmail_passwd")).chomp,
55
59
  ssl: true
56
60
  },
57
- spam_mailbox: "迷惑メール",
61
+ spam_mailbox: "[Gmail]/迷惑メール",
58
62
  archive_mailbox_format: false
59
63
  },
60
64
  }
@@ -11,6 +11,14 @@ define_command(:mournmail_visit_mailbox, doc: "Visit mailbox") do
11
11
  end
12
12
  end
13
13
 
14
+ define_command(:mournmail_visit_spam_mailbox, doc: "Visit spam mailbox") do
15
+ mailbox = Mournmail.account_config[:spam_mailbox]
16
+ if mailbox.nil?
17
+ raise EditorError, "spam_mailbox is not specified"
18
+ end
19
+ mournmail_visit_mailbox(Net::IMAP.encode_utf7(mailbox))
20
+ end
21
+
14
22
  define_command(:mournmail_summary_sync, doc: "Sync summary.") do
15
23
  |mailbox = (Mournmail.current_mailbox || "INBOX"),
16
24
  all = current_prefix_arg|
@@ -61,9 +69,10 @@ define_command(:mail, doc: "Write a new mail.") do
61
69
  User-Agent: Mournmail/#{Mournmail::VERSION} Textbringer/#{Textbringer::VERSION} Ruby/#{RUBY_VERSION}
62
70
  --text follows this line--
63
71
  EOF
64
- re_search_backward(/^To:/)
65
- end_of_line
72
+ beginning_of_buffer
73
+ re_search_forward(/^To: */)
66
74
  if run_hooks
75
+ Mournmail.insert_signature
67
76
  run_hooks(:mournmail_draft_setup_hook)
68
77
  end
69
78
  end
@@ -14,6 +14,13 @@ module Textbringer
14
14
  "X-Mailer",
15
15
  "Content-Type"
16
16
  ]
17
+ CONFIG[:mournmail_quote_header_fields] = [
18
+ "Subject",
19
+ "Date",
20
+ "From",
21
+ "To",
22
+ "Cc"
23
+ ]
17
24
  CONFIG[:mournmail_imap_connect_timeout] = 10
18
25
  CONFIG[:mournmail_keep_alive_interval] = 60
19
26
  case RUBY_PLATFORM
@@ -28,4 +35,22 @@ module Textbringer
28
35
  CONFIG[:mournmail_link_open_comamnd] = "xdg-open"
29
36
  end
30
37
  CONFIG[:mournmail_addresses_path] = File.expand_path("~/.addresses")
38
+ CONFIG[:mournmail_signature_regexp] = /^\n-- /
39
+ CONFIG[:mournmail_allowed_attachment_extensions] = [
40
+ "txt",
41
+ "md",
42
+ "htm",
43
+ "html",
44
+ "pdf",
45
+ "jpg",
46
+ "jpeg",
47
+ "png",
48
+ "gif",
49
+ "doc",
50
+ "docx",
51
+ "xls",
52
+ "xlsx",
53
+ "ppt",
54
+ "zip"
55
+ ]
31
56
  end
@@ -8,6 +8,7 @@ module Mournmail
8
8
  MAIL_MODE_MAP.define_key("\C-c\C-xv", :draft_pgp_sign_command)
9
9
  MAIL_MODE_MAP.define_key("\C-c\C-xe", :draft_pgp_encrypt_command)
10
10
  MAIL_MODE_MAP.define_key("\C-c\t", :insert_signature_command)
11
+ MAIL_MODE_MAP.define_key("\C-c@", :draft_change_account_command)
11
12
 
12
13
  define_syntax :field_name, /^[A-Za-z\-]+: /
13
14
  define_syntax :quotation, /^>.*/
@@ -68,12 +69,15 @@ module Mournmail
68
69
  content: File.read(file),
69
70
  encoding: "binary")
70
71
  end
71
- conf = Mournmail.account_config
72
+ account = @buffer[:mournmail_delivery_account] ||
73
+ Mournmail.current_account
74
+ conf = CONFIG[:mournmail_accounts][account]
72
75
  options = @buffer[:mournmail_delivery_options] ||
73
76
  conf[:delivery_options]
74
77
  if options[:authentication] == "gmail"
78
+ token = Mournmail.google_access_token(account)
75
79
  options = options.merge(authentication: "xoauth2",
76
- password: Mournmail.google_access_token)
80
+ password: token)
77
81
  end
78
82
  m.delivery_method(@buffer[:mournmail_delivery_method] ||
79
83
  conf[:delivery_method],
@@ -186,6 +190,23 @@ module Mournmail
186
190
  @buffer.insert(CONFIG[:signature])
187
191
  end
188
192
 
193
+ define_local_command(:draft_change_account, doc: "Change account.") do
194
+ |account = Mournmail.read_account_name("Change account: ")|
195
+ from = CONFIG[:mournmail_accounts][account][:from]
196
+ @buffer[:mournmail_delivery_account] = account
197
+ @buffer.save_excursion do
198
+ @buffer.beginning_of_buffer
199
+ @buffer.re_search_forward(/^From:.*/)
200
+ @buffer.replace_match("From: " + from)
201
+ @buffer.end_of_buffer
202
+ if @buffer.re_search_backward(CONFIG[:mournmail_signature_regexp],
203
+ raise_error: false)
204
+ @buffer.delete_region(@buffer.point, @buffer.point_max)
205
+ end
206
+ Mournmail.insert_signature
207
+ end
208
+ end
209
+
189
210
  private
190
211
 
191
212
  def end_of_header
@@ -12,7 +12,7 @@ module Mournmail
12
12
 
13
13
  # See http://nihongo.jp/support/mail_guide/dev_guide.txt
14
14
  MAILTO_REGEXP = URI.regexp("mailto")
15
- URI_REGEXP = /(https?|ftp):\/\/[^  \t\n>)"]*[^]  \t\n>.,:)"]+|#{MAILTO_REGEXP}/
15
+ URI_REGEXP = /(https?|ftp):\/\/[^  \t\n>)"]*[^\]  \t\n>.,:)"]+|#{MAILTO_REGEXP}/
16
16
  MIME_REGEXP = /^\[(([0-9.]+) [A-Za-z._\-]+\/[A-Za-z._\-]+.*|PGP\/MIME .*)\]$/
17
17
  URI_OR_MIME_REGEXP = /#{URI_REGEXP}|#{MIME_REGEXP}/
18
18
 
@@ -24,6 +24,7 @@ module Mournmail
24
24
  def initialize(buffer)
25
25
  super(buffer)
26
26
  buffer.keymap = MESSAGE_MODE_MAP
27
+ @attached_file = nil
27
28
  end
28
29
 
29
30
  define_local_command(:message_open_link_or_part,
@@ -52,6 +53,9 @@ module Mournmail
52
53
 
53
54
  define_local_command(:message_next_link_or_part,
54
55
  doc: "Go to the next link or MIME part.") do
56
+ if @buffer.looking_at?(URI_OR_MIME_REGEXP)
57
+ @buffer.forward_char
58
+ end
55
59
  if @buffer.re_search_forward(URI_OR_MIME_REGEXP, raise_error: false)
56
60
  goto_char(@buffer.match_beginning(0))
57
61
  else
@@ -76,8 +80,8 @@ module Mournmail
76
80
 
77
81
  def part_file_name(part)
78
82
  file_name =
79
- part["content-disposition"]&.parameters&.[]("filename") ||
80
- part["content-type"]&.parameters&.[]("name") ||
83
+ (part["content-disposition"]&.parameters&.[]("filename") rescue nil) ||
84
+ (part["content-type"]&.parameters&.[]("name") rescue nil) ||
81
85
  part_default_file_name(part)
82
86
  decoded_file_name = Mail::Encodings.decode_encode(file_name, :decode)
83
87
  if /\A([A-Za-z0-9_\-]+)'(?:[A-Za-z0-9_\-])*'(.*)/ =~ decoded_file_name
@@ -125,23 +129,33 @@ module Mournmail
125
129
  raise EditorError, "Can't open a multipart entity."
126
130
  end
127
131
  ext = part_file_name(part).slice(/\.([^.]+)\z/, 1)
132
+ if part.main_type != "text" || part.sub_type == "html"
133
+ if ext.nil?
134
+ raise EditorError, "The extension of the filename is not specified"
135
+ end
136
+ if !CONFIG[:mournmail_allowed_attachment_extensions].include?(ext)
137
+ raise EditorError, ".#{ext} is not allowed"
138
+ end
139
+ end
128
140
  if ext
129
141
  file_name = ["mournmail", "." + ext]
130
142
  else
131
143
  file_name = "mournmail"
132
144
  end
133
- f = Tempfile.open(file_name, binmode: true)
145
+ @attached_file = Tempfile.open(file_name, binmode: true)
134
146
  s = part.decoded
135
- if part.charset
147
+ if part.content_type == "text/html"
148
+ s = s.sub(/<meta http-equiv="content-type".*?>/i, "")
149
+ elsif part.charset
136
150
  s = s.encode(part.charset)
137
151
  end
138
- f.write(s)
139
- f.close
140
- if ext == "txt"
141
- find_file(f.path)
152
+ @attached_file.write(s)
153
+ @attached_file.close
154
+ if part.main_type == "text" && part.sub_type != "html"
155
+ find_file(@attached_file.path)
142
156
  else
143
157
  background do
144
- system(*CONFIG[:mournmail_file_open_comamnd], f.path,
158
+ system(*CONFIG[:mournmail_file_open_comamnd], @attached_file.path,
145
159
  out: File::NULL, err: File::NULL)
146
160
  end
147
161
  end
@@ -8,8 +8,8 @@ module Mournmail
8
8
  render_header + "\n" + render_body(indices)
9
9
  end
10
10
 
11
- def render_header
12
- CONFIG[:mournmail_display_header_fields].map { |name|
11
+ def render_header(fields = CONFIG[:mournmail_display_header_fields])
12
+ fields.map { |name|
13
13
  val = self[name]&.to_s&.gsub(/\t/, " ")
14
14
  val ? "#{name}: #{val}\n" : ""
15
15
  }.join
@@ -41,11 +41,36 @@ module Mournmail
41
41
  else
42
42
  type = Mail::Encodings.decode_encode(self["content-type"].to_s,
43
43
  :decode) rescue
44
- "broken/type; error=\"#{$!} (#{$!.class})\""
44
+ "broken/type; error=\"#{$!} (#{$!.class})\""
45
45
  "[0 #{type}]\n"
46
46
  end + pgp_signature
47
47
  end
48
48
 
49
+ def render_text(indices = [])
50
+ if HAVE_MAIL_GPG && encrypted?
51
+ mail = decrypt(verify: true)
52
+ return mail.render_text(indices)
53
+ end
54
+ if multipart?
55
+ parts.each_with_index.map { |part, i|
56
+ if sub_type == "alternative" && i > 0
57
+ ""
58
+ else
59
+ part.render_text([*indices, i])
60
+ end
61
+ }.join("\n")
62
+ elsif main_type.nil? || main_type == "text"
63
+ s = Mournmail.to_utf8(body.decoded, charset)
64
+ if sub_type == "html"
65
+ Html2Text.convert(s)
66
+ else
67
+ s
68
+ end
69
+ else
70
+ ""
71
+ end
72
+ end
73
+
49
74
  def dig_part(i, *rest_indices)
50
75
  if HAVE_MAIL_GPG && encrypted?
51
76
  mail = decrypt(verify: true)
@@ -91,7 +116,39 @@ module Mournmail
91
116
  :decode) rescue
92
117
  "broken/type; error=\"#{$!} (#{$!.class})\""
93
118
  "[#{index} #{type}]\n" +
94
- (no_content ? "" : render_content(indices))
119
+ render_content(indices, no_content)
120
+ end
121
+
122
+ def render_text(indices)
123
+ if multipart?
124
+ parts.each_with_index.map { |part, i|
125
+ if sub_type == "alternative" && i > 0
126
+ ""
127
+ else
128
+ part.render_text([*indices, i])
129
+ end
130
+ }.join("\n")
131
+ else
132
+ if main_type == "message" && sub_type == "rfc822"
133
+ mail = Mail.new(body.raw_source)
134
+ mail.render_header(CONFIG[:mournmail_quote_header_fields]) +
135
+ "\n" + mail.render_text(indices)
136
+ elsif attachment?
137
+ ""
138
+ else
139
+ if main_type == "text"
140
+ if sub_type == "html"
141
+ Html2Text.convert(decoded).sub(/(?<!\n)\z/, "\n")
142
+ else
143
+ decoded.sub(/(?<!\n)\z/, "\n").gsub(/\r\n/, "\n")
144
+ end
145
+ else
146
+ ""
147
+ end
148
+ end
149
+ end
150
+ rescue => e
151
+ ""
95
152
  end
96
153
 
97
154
  def dig_part(i, *rest_indices)
@@ -110,26 +167,29 @@ module Mournmail
110
167
 
111
168
  private
112
169
 
113
- def render_content(indices)
170
+ def render_content(indices, no_content)
114
171
  if multipart?
115
172
  parts.each_with_index.map { |part, i|
116
- no_content = sub_type == "alternative" && i > 0
117
- part.render([*indices, i], no_content)
173
+ part.render([*indices, i],
174
+ no_content || sub_type == "alternative" && i > 0)
118
175
  }.join
119
- elsif main_type == "message" && sub_type == "rfc822"
120
- mail = Mail.new(body.raw_source)
121
- mail.render(indices)
122
- elsif attachment?
123
- ""
124
176
  else
125
- if main_type == "text"
126
- if sub_type == "html"
127
- Html2Text.convert(decoded).sub(/(?<!\n)\z/, "\n")
177
+ return "" if no_content
178
+ if main_type == "message" && sub_type == "rfc822"
179
+ mail = Mail.new(body.raw_source)
180
+ mail.render(indices)
181
+ elsif attachment?
182
+ ""
183
+ else
184
+ if main_type == "text"
185
+ if sub_type == "html"
186
+ Html2Text.convert(decoded).sub(/(?<!\n)\z/, "\n")
187
+ else
188
+ decoded.sub(/(?<!\n)\z/, "\n").gsub(/\r\n/, "\n")
189
+ end
128
190
  else
129
- decoded.sub(/(?<!\n)\z/, "\n").gsub(/\r\n/, "\n")
191
+ ""
130
192
  end
131
- else
132
- ""
133
193
  end
134
194
  end
135
195
  rescue => e
@@ -141,7 +141,8 @@ module Mournmail
141
141
  end
142
142
  s = data[0].attr["BODY[]"]
143
143
  mail = Mournmail.parse_mail(s)
144
- if @mailbox != Mournmail.account_config[:spam_mailbox]
144
+ spam_mailbox = Mournmail.account_config[:spam_mailbox]
145
+ if @mailbox != Net::IMAP.encode_utf7(spam_mailbox)
145
146
  item.cache_id = Mournmail.write_mail_cache(s)
146
147
  Mournmail.index_mail(item.cache_id, mail)
147
148
  end
@@ -25,6 +25,7 @@ module Mournmail
25
25
  SUMMARY_MODE_MAP.define_key("*t", :summary_mark_unflagged_command)
26
26
  SUMMARY_MODE_MAP.define_key("y", :summary_archive_command)
27
27
  SUMMARY_MODE_MAP.define_key("o", :summary_refile_command)
28
+ SUMMARY_MODE_MAP.define_key("!", :summary_refile_spam_command)
28
29
  SUMMARY_MODE_MAP.define_key("p", :summary_prefetch_command)
29
30
  SUMMARY_MODE_MAP.define_key("X", :summary_expunge_command)
30
31
  SUMMARY_MODE_MAP.define_key("v", :summary_view_source_command)
@@ -33,6 +34,7 @@ module Mournmail
33
34
  SUMMARY_MODE_MAP.define_key("k", :previous_line)
34
35
  SUMMARY_MODE_MAP.define_key("j", :next_line)
35
36
  SUMMARY_MODE_MAP.define_key("m", :mournmail_visit_mailbox)
37
+ SUMMARY_MODE_MAP.define_key("S", :mournmail_visit_spam_mailbox)
36
38
  SUMMARY_MODE_MAP.define_key("/", :summary_search_command)
37
39
  SUMMARY_MODE_MAP.define_key("t", :summary_show_thread_command)
38
40
  SUMMARY_MODE_MAP.define_key("@", :summary_change_account_command)
@@ -98,7 +100,7 @@ module Mournmail
98
100
  |reply_all = current_prefix_arg|
99
101
  Mournmail.background do
100
102
  mail = read_current_mail[0]
101
- body = mail.render_body
103
+ body = mail.render_text
102
104
  foreground do
103
105
  Window.current = Mournmail.message_window
104
106
  Commands.mail(run_hooks: false)
@@ -137,6 +139,7 @@ module Mournmail
137
139
  On #{mail['date']}
138
140
  #{mail['from']} wrote:
139
141
  EOF
142
+ Mournmail.insert_signature
140
143
  exchange_point_and_mark
141
144
  run_hooks(:mournmail_draft_setup_hook)
142
145
  end
@@ -373,10 +376,23 @@ module Mournmail
373
376
  end
374
377
  end
375
378
 
379
+ define_local_command(:summary_refile_spam,
380
+ doc: "Refile marked mails as spam.") do
381
+ mailbox = Mournmail.account_config[:spam_mailbox]
382
+ if mailbox.nil?
383
+ raise EditorError, "spam_mailbox is not specified"
384
+ end
385
+ summary_refile(Net::IMAP.encode_utf7(mailbox))
386
+ end
387
+
376
388
  define_local_command(:summary_prefetch,
377
389
  doc: "Prefetch mails.") do
378
390
  summary = Mournmail.current_summary
379
391
  mailbox = Mournmail.current_mailbox
392
+ spam_mailbox = Mournmail.account_config[:spam_mailbox]
393
+ if mailbox == Net::IMAP.encode_utf7(spam_mailbox)
394
+ raise EditorError, "Can't prefetch spam"
395
+ end
380
396
  target_uids = @buffer.to_s.scan(/^ *\d+/).map { |s|
381
397
  s.to_i
382
398
  }.select { |uid|
@@ -394,9 +410,7 @@ module Mournmail
394
410
  s = i.attr["BODY[]"]
395
411
  if s
396
412
  cache_id = Mournmail.write_mail_cache(s)
397
- if mailbox != Mournmail.account_config[:spam_mailbox]
398
- Mournmail.index_mail(cache_id, Mail.new(s))
399
- end
413
+ Mournmail.index_mail(cache_id, Mail.new(s))
400
414
  summary[uid].cache_id = cache_id
401
415
  end
402
416
  end
@@ -450,7 +464,7 @@ module Mournmail
450
464
 
451
465
  define_local_command(:summary_change_account,
452
466
  doc: "Change the current account.") do
453
- |account = read_account_name("Change account: ")|
467
+ |account = Mournmail.read_account_name("Change account: ")|
454
468
  unless CONFIG[:mournmail_accounts].key?(account)
455
469
  raise EditorError, "No such account: #{account}"
456
470
  end
@@ -651,13 +665,6 @@ module Mournmail
651
665
  message
652
666
  end
653
667
 
654
- def read_account_name(prompt, **opts)
655
- f = ->(s) {
656
- complete_for_minibuffer(s, CONFIG[:mournmail_accounts].keys)
657
- }
658
- read_from_minibuffer(prompt, completion_proc: f, **opts)
659
- end
660
-
661
668
  def delete_from_summary(summary, uids, msg)
662
669
  summary.delete_item_if do |item|
663
670
  uids.include?(item.uid)
@@ -11,6 +11,7 @@ require 'google/api_client/client_secrets'
11
11
  require 'google/api_client/auth/storage'
12
12
  require 'google/api_client/auth/storages/file_store'
13
13
  require 'launchy'
14
+ require "socket"
14
15
 
15
16
  class Net::SMTP
16
17
  def auth_xoauth2(user, secret)
@@ -225,38 +226,67 @@ module Mournmail
225
226
  end
226
227
  end
227
228
 
228
- def self.google_access_token
229
- auth_path = File.expand_path("cache/#{current_account}/google_auth.json",
229
+ class GoogleAuthCallbackServer
230
+ def initialize
231
+ @servers = Socket.tcp_server_sockets("127.0.0.1", 0)
232
+ end
233
+
234
+ def port
235
+ @servers.first.local_address.ip_port
236
+ end
237
+
238
+ def receive_code
239
+ Socket.accept_loop(@servers) do |sock, addr|
240
+ line = sock.gets
241
+ query_string = line.slice(%r'\AGET [^?]*\?(.*) HTTP/1.1\r\n', 1)
242
+ params = CGI.parse(query_string)
243
+ code = params["code"][0]
244
+ while line = sock.gets
245
+ break if line == "\r\n"
246
+ end
247
+ sock.print("HTTP/1.1 200 OK\r\n")
248
+ sock.print("Content-Type: text/plain\r\n")
249
+ sock.print("\r\n")
250
+ if code
251
+ sock.print("Authenticated!")
252
+ else
253
+ sock.print("Authentication failed!")
254
+ end
255
+ return code
256
+ ensure
257
+ sock.close
258
+ end
259
+ ensure
260
+ @servers.each(&:close)
261
+ end
262
+ end
263
+
264
+ def self.google_access_token(account = current_account)
265
+ auth_path = File.expand_path("cache/#{account}/google_auth.json",
230
266
  CONFIG[:mournmail_directory])
231
267
  FileUtils.mkdir_p(File.dirname(auth_path))
232
268
  store = Google::APIClient::FileStore.new(auth_path)
233
269
  storage = Google::APIClient::Storage.new(store)
234
270
  storage.authorize
235
271
  if storage.authorization.nil?
236
- path = File.expand_path(account_config[:client_secret_path])
272
+ conf = CONFIG[:mournmail_accounts][account]
273
+ path = File.expand_path(conf[:client_secret_path])
237
274
  client_secrets = Google::APIClient::ClientSecrets.load(path)
275
+ callback_server = GoogleAuthCallbackServer.new
238
276
  auth_client = client_secrets.to_authorization
239
277
  auth_client.update!(
240
278
  :scope => 'https://mail.google.com/',
241
- :redirect_uri => 'urn:ietf:wg:oauth:2.0:oob'
279
+ :redirect_uri => "http://127.0.0.1:#{callback_server.port}/"
242
280
  )
243
281
  auth_uri = auth_client.authorization_uri.to_s
244
- auth_client.code = foreground! {
282
+ foreground! do
245
283
  begin
246
284
  Launchy.open(auth_uri)
247
285
  rescue Launchy::CommandNotFoundError
248
- buffer = show_google_auth_uri(auth_uri)
286
+ show_google_auth_uri(auth_uri)
249
287
  end
250
- begin
251
- Window.echo_area.clear_message
252
- Window.redisplay
253
- read_from_minibuffer("Code: ").chomp
254
- ensure
255
- if buffer
256
- kill_buffer(buffer, force: true)
257
- end
258
- end
259
- }
288
+ end
289
+ auth_client.code = callback_server.receive_code
260
290
  auth_client.fetch_access_token!
261
291
  old_umask = File.umask(077)
262
292
  begin
@@ -382,7 +412,8 @@ module Mournmail
382
412
  dir = File.dirname(path)
383
413
  base = File.basename(path)
384
414
  begin
385
- f = Tempfile.create(["#{base}-", ".tmp"], dir)
415
+ f = Tempfile.create(["#{base}-", ".tmp"], dir,
416
+ external_encoding: "ASCII-8BIT", binmode: true)
386
417
  begin
387
418
  f.write(s)
388
419
  ensure
@@ -554,4 +585,24 @@ module Mournmail
554
585
  def self.parse_mail(s)
555
586
  Mail.new(s.scrub("??"))
556
587
  end
588
+
589
+ def self.read_account_name(prompt, **opts)
590
+ f = ->(s) {
591
+ complete_for_minibuffer(s, CONFIG[:mournmail_accounts].keys)
592
+ }
593
+ read_from_minibuffer(prompt, completion_proc: f, **opts)
594
+ end
595
+
596
+ def self.insert_signature
597
+ account = Buffer.current[:mournmail_delivery_account] ||
598
+ Mournmail.current_account
599
+ signature = CONFIG[:mournmail_accounts][account][:signature]
600
+ if signature
601
+ Buffer.current.save_excursion do
602
+ end_of_buffer
603
+ insert("\n")
604
+ insert(signature)
605
+ end
606
+ end
607
+ end
557
608
  end
@@ -1,3 +1,3 @@
1
1
  module Mournmail
2
- VERSION = "0.3.2"
2
+ VERSION = "1.0.2"
3
3
  end
data/mournmail.gemspec CHANGED
@@ -22,6 +22,8 @@ Gem::Specification.new do |spec|
22
22
  spec.require_paths = ["lib"]
23
23
 
24
24
  spec.add_runtime_dependency "textbringer"
25
+ spec.add_runtime_dependency "net-smtp"
26
+ spec.add_runtime_dependency "net-imap"
25
27
  spec.add_runtime_dependency "mail"
26
28
  spec.add_runtime_dependency "mime-types"
27
29
  spec.add_runtime_dependency "rroonga"
@@ -30,5 +32,5 @@ Gem::Specification.new do |spec|
30
32
  spec.add_runtime_dependency "html2text"
31
33
 
32
34
  spec.add_development_dependency "bundler"
33
- spec.add_development_dependency "rake", "~> 12.0"
35
+ spec.add_development_dependency "rake", ">= 12.0"
34
36
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mournmail
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.2
4
+ version: 1.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shugo Maeda
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-02-19 00:00:00.000000000 Z
11
+ date: 2022-09-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: textbringer
@@ -24,6 +24,34 @@ dependencies:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: net-smtp
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: net-imap
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
27
55
  - !ruby/object:Gem::Dependency
28
56
  name: mail
29
57
  requirement: !ruby/object:Gem::Requirement
@@ -126,14 +154,14 @@ dependencies:
126
154
  name: rake
127
155
  requirement: !ruby/object:Gem::Requirement
128
156
  requirements:
129
- - - "~>"
157
+ - - ">="
130
158
  - !ruby/object:Gem::Version
131
159
  version: '12.0'
132
160
  type: :development
133
161
  prerelease: false
134
162
  version_requirements: !ruby/object:Gem::Requirement
135
163
  requirements:
136
- - - "~>"
164
+ - - ">="
137
165
  - !ruby/object:Gem::Version
138
166
  version: '12.0'
139
167
  description: A message user agent for Textbringer.
@@ -186,7 +214,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
186
214
  - !ruby/object:Gem::Version
187
215
  version: '0'
188
216
  requirements: []
189
- rubygems_version: 3.3.0.dev
217
+ rubygems_version: 3.4.0.dev
190
218
  signing_key:
191
219
  specification_version: 4
192
220
  summary: A message user agent for Textbringer.