mournmail 0.3.2 → 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
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.