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 +4 -4
- data/README.md +6 -2
- data/lib/mournmail/commands.rb +11 -2
- data/lib/mournmail/config.rb +25 -0
- data/lib/mournmail/draft_mode.rb +23 -2
- data/lib/mournmail/message_mode.rb +24 -10
- data/lib/mournmail/message_rendering.rb +78 -18
- data/lib/mournmail/summary.rb +2 -1
- data/lib/mournmail/summary_mode.rb +19 -12
- data/lib/mournmail/utils.rb +68 -17
- data/lib/mournmail/version.rb +1 -1
- data/mournmail.gemspec +3 -1
- metadata +33 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 140daea6492012b5093229b435fdbe2c98548430b8b8a74a0714483dd039c085
|
4
|
+
data.tar.gz: cb3fb9f8bd0a43fce73f3bd2d3e1d9515acaf74c1a3246c429c027ea7f9db140
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
}
|
data/lib/mournmail/commands.rb
CHANGED
@@ -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
|
-
|
65
|
-
|
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
|
data/lib/mournmail/config.rb
CHANGED
@@ -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
|
data/lib/mournmail/draft_mode.rb
CHANGED
@@ -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
|
-
|
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:
|
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>)"]*[
|
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
|
-
|
145
|
+
@attached_file = Tempfile.open(file_name, binmode: true)
|
134
146
|
s = part.decoded
|
135
|
-
if part.
|
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
|
-
|
139
|
-
|
140
|
-
if
|
141
|
-
find_file(
|
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],
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
117
|
-
|
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
|
-
|
126
|
-
|
127
|
-
|
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
|
-
|
191
|
+
""
|
130
192
|
end
|
131
|
-
else
|
132
|
-
""
|
133
193
|
end
|
134
194
|
end
|
135
195
|
rescue => e
|
data/lib/mournmail/summary.rb
CHANGED
@@ -141,7 +141,8 @@ module Mournmail
|
|
141
141
|
end
|
142
142
|
s = data[0].attr["BODY[]"]
|
143
143
|
mail = Mournmail.parse_mail(s)
|
144
|
-
|
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.
|
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
|
-
|
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)
|
data/lib/mournmail/utils.rb
CHANGED
@@ -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
|
-
|
229
|
-
|
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
|
-
|
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 =>
|
279
|
+
:redirect_uri => "http://127.0.0.1:#{callback_server.port}/"
|
242
280
|
)
|
243
281
|
auth_uri = auth_client.authorization_uri.to_s
|
244
|
-
|
282
|
+
foreground! do
|
245
283
|
begin
|
246
284
|
Launchy.open(auth_uri)
|
247
285
|
rescue Launchy::CommandNotFoundError
|
248
|
-
|
286
|
+
show_google_auth_uri(auth_uri)
|
249
287
|
end
|
250
|
-
|
251
|
-
|
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
|
data/lib/mournmail/version.rb
CHANGED
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", "
|
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.
|
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:
|
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.
|
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.
|