mournmail 0.1.1 → 1.0.0

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
- SHA1:
3
- metadata.gz: 624dd4f9dacde981ede7170c87da6c8ddb3a6aae
4
- data.tar.gz: 9b1890ed2a07cdd551e4688bb88d7a80b1ff2c51
2
+ SHA256:
3
+ metadata.gz: 8cbf82ccf54ab48f7596d24aaacb4673ca8f8271f304264df7aa96a932dac2fb
4
+ data.tar.gz: facbe695e21c68a1b5c50a9bb9d50894f948e09bab2a436b2174656953b809bf
5
5
  SHA512:
6
- metadata.gz: ea1353b03b02800c953d1df9c088400ec1d5698953105f9a0b8c55f1b387d61367061e83f873a891bfbcd73c3bf7a6f17e5a0b539d1cbe16b052b104cb88b18e
7
- data.tar.gz: 97a308e32677cc6a51490d1be5f4528159760e22866ceef39d33f977ee94fadd3fd95369ab72199baf8d0f5a47cb9daad435b5d0d971e20b2013a82bcc41e2f5
6
+ metadata.gz: 49c6ba544f435ea5eb150b510e26d18ab0bcd0fddd1a1a147dc0aee84bde05656bb153f44628ee0828b2e02c8f6ccc27b75c7e61b34ca0511c0eb2f074240e1e
7
+ data.tar.gz: 43accf4207ec48914c79940d2c45ce931beab826759b3e64174257891bf70d741913800aa976b41c19a2b47d1a0c8e32a00215995889058fdee5c82b4bc6e5e0
data/README.md CHANGED
@@ -10,34 +10,57 @@ Mournmail is a message user agent for
10
10
  ## Configuration
11
11
 
12
12
  ```ruby
13
- # The default value of From:
14
- CONFIG[:mournmail_from] = "Shugo Maeda <shugo@example.com>"
15
- # The default charset
16
- CONFIG[:mournmail_charset] = "ISO-2022-JP"
17
- # The delivery method for Mail#delivery_method
18
- CONFIG[:mournmail_delivery_method] = :smtp
19
- # The options for Mail#delivery_method
20
- CONFIG[:mournmail_delivery_options] = {
21
- :address => "smtp.example.com",
22
- :port => 465,
23
- :domain => Socket.gethostname,
24
- :user_name => "shugo",
25
- :password => File.read("/path/to/smtp_passwd").chomp,
26
- :authentication => "login",
27
- :tls => true,
28
- :ca_file => "/path/to/cacert.pem"
29
- }
30
- # The host for Net::IMAP#new
31
- CONFIG[:mournmail_imap_host] = "imap.example.com"
32
- # The options for Net::IMAP.new and
33
- # Net::IMAP#authenticate (auth_type, user_name, and password)
34
- CONFIG[:mournmail_imap_options] = {
35
- ssl: {
36
- :ca_file => File.expand_path("/path/to/cacert.pem")
13
+ CONFIG[:mournmail_accounts] = {
14
+ "example.com" => {
15
+ from: "Shugo Maeda <shugo@example.com>",
16
+ delivery_method: :smtp,
17
+ delivery_options: {
18
+ address: "smtp.example.com",
19
+ port: 465,
20
+ domain: Socket.gethostname,
21
+ user_name: "shugo",
22
+ password: File.read("/path/to/smtp_passwd").chomp,
23
+ authentication: "login",
24
+ tls: true,
25
+ ca_file: "/path/to/ca.pem"
26
+ },
27
+ imap_host: "imap.example.com",
28
+ imap_options: {
29
+ auth_type: "PLAIN",
30
+ user_name: "shugo",
31
+ password: File.read("/path/to/imap_passwd").chomp,
32
+ ssl: { ca_file: "/path/to/ca.pem" }
33
+ },
34
+ spam_mailbox: "spam",
35
+ outbox_mailbox: "outbox",
36
+ archive_mailbox_format: "archive/%Y",
37
+ signature: <<~EOF
38
+ --
39
+ Shugo Maeda <shugo@example.com>
40
+ EOF
41
+ },
42
+ "gmail.com" => {
43
+ from: "Example <example@gmail.com>",
44
+ delivery_method: :smtp,
45
+ delivery_options: {
46
+ address: "smtp.gmail.com",
47
+ port: 587,
48
+ domain: Socket.gethostname,
49
+ user_name: "example@gmail.com",
50
+ password: File.read("/path/to/gmail_passwd").chomp,
51
+ authentication: "login",
52
+ enable_starttls_auto: true
53
+ },
54
+ imap_host: "imap.gmail.com",
55
+ imap_options: {
56
+ auth_type: "PLAIN",
57
+ user_name: "example@gmail.com",
58
+ password: File.read(File.expand_path("~/.textbringer/gmail_passwd")).chomp,
59
+ ssl: true
60
+ },
61
+ spam_mailbox: "[Gmail]/迷惑メール",
62
+ archive_mailbox_format: false
37
63
  },
38
- auth_type: "PLAIN",
39
- user_name: "shugo",
40
- password: File.read("/path/to/imap_passwd").chomp
41
64
  }
42
65
  ```
43
66
 
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "groonga"
4
+
5
+ if ARGV.size < 1
6
+ STDERR.puts "Usage: mournmail_reindex <path to messages.db>"
7
+ exit 1
8
+ end
9
+
10
+ Groonga::Database.open(ARGV[0])
11
+ Groonga["Messages"].columns.each do |c|
12
+ print "Reindexing #{c.name}... "
13
+ STDOUT.flush
14
+ c.reindex
15
+ puts "done"
16
+ end
data/lib/mournmail.rb CHANGED
@@ -1,5 +1,3 @@
1
- # frozen_string_literal: true
2
-
3
1
  require_relative "mournmail/version"
4
2
  require_relative "mournmail/config"
5
3
  require_relative "mournmail/faces"
@@ -9,4 +7,6 @@ require_relative "mournmail/summary"
9
7
  require_relative "mournmail/draft_mode"
10
8
  require_relative "mournmail/summary_mode"
11
9
  require_relative "mournmail/message_mode"
10
+ require_relative "mournmail/search_result_mode"
12
11
  require_relative "mournmail/commands"
12
+ require_relative "mournmail/mail_encoded_word_patch"
@@ -1,12 +1,22 @@
1
- # frozen_string_literal: true
2
-
3
1
  define_command(:mournmail, doc: "Start mournmail.") do
2
+ Mournmail.open_groonga_db
4
3
  mournmail_visit_mailbox("INBOX")
5
4
  end
6
5
 
7
- define_command(:mournmail_visit_mailbox, doc: "Start mournmail.") do
8
- |mailbox = read_from_minibuffer("Visit mailbox: ", default: "INBOX")|
9
- mournmail_summary_sync(mailbox)
6
+ define_command(:mournmail_visit_mailbox, doc: "Visit mailbox") do
7
+ |mailbox = Mournmail.read_mailbox_name("Visit mailbox: ", default: "INBOX")|
8
+ summary = Mournmail::Summary.load_or_new(mailbox)
9
+ foreground do
10
+ Mournmail.show_summary(summary)
11
+ end
12
+ end
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))
10
20
  end
11
21
 
12
22
  define_command(:mournmail_summary_sync, doc: "Sync summary.") do
@@ -15,30 +25,10 @@ define_command(:mournmail_summary_sync, doc: "Sync summary.") do
15
25
  message("Syncing #{mailbox} in background...")
16
26
  Mournmail.background do
17
27
  summary = Mournmail.fetch_summary(mailbox, all: all)
18
- summary_text = summary.to_s
19
28
  summary.save
20
- next_tick do
21
- buffer = Buffer.find_or_new("*summary*", undo_limit: 0,
22
- read_only: true)
23
- buffer.apply_mode(Mournmail::SummaryMode)
24
- buffer.read_only_edit do
25
- buffer.clear
26
- buffer.insert(summary_text)
27
- end
28
- switch_to_buffer(buffer)
29
- Mournmail.current_mailbox = mailbox
30
- Mournmail.current_summary = summary
31
- Mournmail.current_mail = nil
32
- Mournmail.current_uid = nil
29
+ foreground do
30
+ Mournmail.show_summary(summary)
33
31
  message("Syncing #{mailbox} in background... Done")
34
- begin
35
- buffer.beginning_of_buffer
36
- buffer.re_search_forward(/^\d+ u/)
37
- rescue SearchError
38
- buffer.end_of_buffer
39
- buffer.re_search_backward(/^\d+ /, raise_error: false)
40
- end
41
- summary_read_command
42
32
  end
43
33
  end
44
34
  end
@@ -63,6 +53,7 @@ define_command(:mournmail_quit, doc: "Quit mournmail.") do
63
53
  Mournmail.current_summary = nil
64
54
  Mournmail.current_mail = nil
65
55
  Mournmail.current_uid = nil
56
+ Mournmail.close_groonga_db
66
57
  end
67
58
 
68
59
  define_command(:mail, doc: "Write a new mail.") do
@@ -70,16 +61,18 @@ define_command(:mail, doc: "Write a new mail.") do
70
61
  buffer = Buffer.new_buffer("*draft*")
71
62
  switch_to_buffer(buffer)
72
63
  draft_mode
64
+ conf = Mournmail.account_config
73
65
  insert <<~EOF
74
- From: #{CONFIG[:mournmail_from]}
66
+ From: #{conf[:from]}
75
67
  To:
76
68
  Subject:
77
69
  User-Agent: Mournmail/#{Mournmail::VERSION} Textbringer/#{Textbringer::VERSION} Ruby/#{RUBY_VERSION}
78
70
  --text follows this line--
79
71
  EOF
80
- re_search_backward(/^To:/)
81
- end_of_line
72
+ beginning_of_buffer
73
+ re_search_forward(/^To: */)
82
74
  if run_hooks
75
+ Mournmail.insert_signature
83
76
  run_hooks(:mournmail_draft_setup_hook)
84
77
  end
85
78
  end
@@ -1,10 +1,6 @@
1
- # frozen_string_literal: true
2
-
3
1
  module Textbringer
4
2
  CONFIG[:mournmail_directory] = File.expand_path("~/.mournmail")
5
- CONFIG[:mournmail_from] = ""
6
- CONFIG[:mournmail_delivery_method] = :smtp
7
- CONFIG[:mournmail_delivery_options] = {}
3
+ CONFIG[:mournmail_accounts] = {}
8
4
  CONFIG[:mournmail_charset] = "utf-8"
9
5
  CONFIG[:mournmail_save_directory] = "/tmp"
10
6
  CONFIG[:mournmail_display_header_fields] = [
@@ -18,9 +14,26 @@ module Textbringer
18
14
  "X-Mailer",
19
15
  "Content-Type"
20
16
  ]
17
+ CONFIG[:mournmail_quote_header_fields] = [
18
+ "Subject",
19
+ "Date",
20
+ "From",
21
+ "To",
22
+ "Cc"
23
+ ]
21
24
  CONFIG[:mournmail_imap_connect_timeout] = 10
22
- CONFIG[:mournmail_file_open_comamnd] = "xdg-open"
23
- CONFIG[:mournmail_link_open_comamnd] = "xdg-open"
24
- CONFIG[:mournmail_outbox] = nil
25
+ CONFIG[:mournmail_keep_alive_interval] = 60
26
+ case RUBY_PLATFORM
27
+ when /mswin|mingw/
28
+ CONFIG[:mournmail_file_open_comamnd] = "start"
29
+ CONFIG[:mournmail_link_open_comamnd] = "start"
30
+ when /darwin/
31
+ CONFIG[:mournmail_file_open_comamnd] = "open"
32
+ CONFIG[:mournmail_link_open_comamnd] = "open"
33
+ else
34
+ CONFIG[:mournmail_file_open_comamnd] = "xdg-open"
35
+ CONFIG[:mournmail_link_open_comamnd] = "xdg-open"
36
+ end
25
37
  CONFIG[:mournmail_addresses_path] = File.expand_path("~/.addresses")
38
+ CONFIG[:mournmail_signature_regexp] = /^\n-- /
26
39
  end
@@ -1,5 +1,3 @@
1
- # frozen_string_literal: true
2
-
3
1
  module Mournmail
4
2
  class DraftMode < Textbringer::Mode
5
3
  MAIL_MODE_MAP = Keymap.new
@@ -7,6 +5,10 @@ module Mournmail
7
5
  MAIL_MODE_MAP.define_key("\C-c\C-k", :draft_kill_command)
8
6
  MAIL_MODE_MAP.define_key("\C-c\C-x\C-i", :draft_attach_file_command)
9
7
  MAIL_MODE_MAP.define_key("\t", :draft_complete_or_insert_tab_command)
8
+ MAIL_MODE_MAP.define_key("\C-c\C-xv", :draft_pgp_sign_command)
9
+ MAIL_MODE_MAP.define_key("\C-c\C-xe", :draft_pgp_encrypt_command)
10
+ MAIL_MODE_MAP.define_key("\C-c\t", :insert_signature_command)
11
+ MAIL_MODE_MAP.define_key("\C-c@", :draft_change_account_command)
10
12
 
11
13
  define_syntax :field_name, /^[A-Za-z\-]+: /
12
14
  define_syntax :quotation, /^>.*/
@@ -22,6 +24,7 @@ module Mournmail
22
24
  unless y_or_n?("Send this mail?")
23
25
  return
24
26
  end
27
+ run_hooks(:mournmail_pre_send_hook)
25
28
  s = @buffer.to_s
26
29
  charset = CONFIG[:mournmail_charset]
27
30
  begin
@@ -30,15 +33,22 @@ module Mournmail
30
33
  charset = "utf-8"
31
34
  end
32
35
  m = Mail.new(charset: charset)
36
+ m.transport_encoding = "8bit"
33
37
  header, body = s.split(/^--text follows this line--\n/, 2)
34
38
  attached_files = []
35
39
  attached_messages = []
40
+ pgp_sign = false
41
+ pgp_encrypt = false
36
42
  header.scan(/^([!-9;-~]+):[ \t]*(.*(?:\n[ \t].*)*)\n/) do |name, val|
37
43
  case name
38
44
  when "Attached-File"
39
45
  attached_files.push(val.strip)
40
46
  when "Attached-Message"
41
47
  attached_messages.push(val.strip)
48
+ when "PGP-Sign"
49
+ pgp_sign = val.strip == "yes"
50
+ when "PGP-Encrypt"
51
+ pgp_encrypt = val.strip == "yes"
42
52
  else
43
53
  m[name] = val
44
54
  end
@@ -55,35 +65,58 @@ module Mournmail
55
65
  end
56
66
  end
57
67
  attached_files.each do |file|
58
- m.add_file(file)
68
+ m.add_file(filename: File.basename(file),
69
+ content: File.read(file),
70
+ encoding: "binary")
71
+ end
72
+ account = @buffer[:mournmail_delivery_account] ||
73
+ Mournmail.current_account
74
+ conf = CONFIG[:mournmail_accounts][account]
75
+ options = @buffer[:mournmail_delivery_options] ||
76
+ conf[:delivery_options]
77
+ if options[:authentication] == "gmail"
78
+ token = Mournmail.google_access_token(account)
79
+ options = options.merge(authentication: "xoauth2",
80
+ password: token)
59
81
  end
60
- m.delivery_method(CONFIG[:mournmail_delivery_method],
61
- CONFIG[:mournmail_delivery_options])
82
+ m.delivery_method(@buffer[:mournmail_delivery_method] ||
83
+ conf[:delivery_method],
84
+ options)
62
85
  bury_buffer(@buffer)
63
- background do
86
+ Mournmail.background do
64
87
  begin
65
88
  if !attached_messages.empty?
66
89
  attached_messages.each do |attached_message|
67
- mailbox, uid = attached_message.strip.split("/")
68
- s, = Mournmail.read_mail(mailbox, uid.to_i)
90
+ cache_id = attached_message.strip
91
+ s = File.read(Mournmail.mail_cache_path(cache_id))
69
92
  part = Mail::Part.new(content_type: "message/rfc822", body: s)
70
93
  m.body << part
71
94
  end
72
95
  end
73
- m.deliver!
74
- Mournmail.imap_connect do |imap|
75
- outbox = CONFIG[:mournmail_outbox]
76
- if outbox
96
+ if pgp_sign || pgp_encrypt
97
+ m.gpg(sign: pgp_sign, encrypt: pgp_encrypt)
98
+ end
99
+ m.deliver
100
+ foreground do
101
+ message("Mail sent.")
102
+ end
103
+ cache_id = Mournmail.write_mail_cache(m.encoded)
104
+ Mournmail.index_mail(cache_id, m)
105
+ outbox = Mournmail.account_config[:outbox_mailbox]
106
+ if outbox
107
+ Mournmail.imap_connect do |imap|
108
+ unless imap.list("", outbox)
109
+ imap.create(outbox)
110
+ end
77
111
  imap.append(outbox, m.to_s, [:Seen])
78
112
  end
79
113
  end
80
- next_tick do
114
+ foreground do
81
115
  kill_buffer(@buffer, force: true)
82
116
  Mournmail.back_to_summary
83
- message("Mail sent.")
84
117
  end
85
118
  rescue Exception
86
- next_tick do
119
+ foreground do
87
120
  switch_to_buffer(@buffer)
88
121
  end
89
122
  raise
@@ -101,9 +134,7 @@ module Mournmail
101
134
  define_local_command(:draft_attach_file, doc: "Attach a file.") do
102
135
  |file_name = read_file_name("Attach file: ")|
103
136
  @buffer.save_excursion do
104
- @buffer.beginning_of_buffer
105
- @buffer.re_search_forward(/^--text follows this line--$/)
106
- @buffer.beginning_of_line
137
+ end_of_header
107
138
  @buffer.insert("Attached-File: #{file_name}\n")
108
139
  end
109
140
  end
@@ -120,10 +151,15 @@ module Mournmail
120
151
  start_pos = @buffer.point
121
152
  s = @buffer.substring(start_pos, end_pos)
122
153
  if !s.empty?
123
- re = /^(.*")?#{Regexp.quote(s)}.*/
124
- line = File.read(CONFIG[:mournmail_addresses_path]).slice(re)
125
- if line
126
- addr = line.slice(/^\S+/)
154
+ re = /^(?:.*")?#{Regexp.quote(s)}.*/
155
+ addrs = File.read(CONFIG[:mournmail_addresses_path])
156
+ .scan(re).map { |line| line.slice(/^\S+/) }
157
+ if !addrs.empty?
158
+ addr = addrs.inject { |x, y|
159
+ x.chars.zip(y.chars).take_while { |i, j|
160
+ i == j
161
+ }.map { |i,| i }.join
162
+ }
127
163
  @buffer.delete_region(start_pos, end_pos)
128
164
  @buffer.insert(addr)
129
165
  else
@@ -135,5 +171,48 @@ module Mournmail
135
171
  @buffer.insert("\t")
136
172
  end
137
173
  end
174
+
175
+ define_local_command(:draft_pgp_sign, doc: "PGP sign.") do
176
+ @buffer.save_excursion do
177
+ end_of_header
178
+ @buffer.insert("PGP-Sign: yes\n")
179
+ end
180
+ end
181
+
182
+ define_local_command(:draft_pgp_encrypt, doc: "PGP encrypt.") do
183
+ @buffer.save_excursion do
184
+ end_of_header
185
+ @buffer.insert("PGP-Encrypt: yes\n")
186
+ end
187
+ end
188
+
189
+ define_local_command(:insert_signature, doc: "Insert signature.") do
190
+ @buffer.insert(CONFIG[:signature])
191
+ end
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
+
210
+ private
211
+
212
+ def end_of_header
213
+ @buffer.beginning_of_buffer
214
+ @buffer.re_search_forward(/^--text follows this line--$/)
215
+ @buffer.beginning_of_line
216
+ end
138
217
  end
139
218
  end