vmail 0.4.5 → 0.4.6
Sign up to get free protection for your applications and to get access to all the features.
- data/README.markdown +21 -0
- data/Rakefile +6 -0
- data/bin/vmail +0 -0
- data/bin/vmail_client +0 -0
- data/lib/vmail.rb +2 -0
- data/lib/vmail.vim +48 -75
- data/lib/vmail/imap_client.rb +258 -161
- data/lib/vmail/reply_template.rb +51 -0
- data/lib/vmail/version.rb +1 -1
- data/test/fixtures/reply_all.eml +58 -0
- data/test/reply_template_test.rb +32 -0
- data/website/vmail-template.html +4 -4
- metadata +7 -2
data/README.markdown
CHANGED
@@ -63,6 +63,9 @@ You can omit the password key-value pair if you'd rather not have the password
|
|
63
63
|
saved in the file. In that case, you'll prompted for the password each time you
|
64
64
|
start vmail.
|
65
65
|
|
66
|
+
If you are behind a firewall that blocks IMAP, there are additional
|
67
|
+
configuration options that you can use. See below.
|
68
|
+
|
66
69
|
## Contacts autocompletion
|
67
70
|
|
68
71
|
vmail uses Vim autocompletion to help you auto-complete email addresses.
|
@@ -398,6 +401,24 @@ vmail gem is downloaded from).
|
|
398
401
|
[github]:https://github.com/danchoi/vmail
|
399
402
|
[rubygems]:https://rubygems.org/gems/vmail
|
400
403
|
|
404
|
+
## Additional configuration options
|
405
|
+
|
406
|
+
The default IMAP server vmail uses is `imap.gmail.com` and the default port is
|
407
|
+
`993`. If you want to change these values, e.g, because you are behind a
|
408
|
+
firewall which blocks IMAP, you can change these values by adding two lines in
|
409
|
+
your .vmailrc, like so:
|
410
|
+
|
411
|
+
server: localhost
|
412
|
+
port: 2999
|
413
|
+
|
414
|
+
Then you can create an SSH tunnel, e.g.
|
415
|
+
|
416
|
+
ssh -f user@example.com -L 2999:imap.gmail.com:993 -N
|
417
|
+
|
418
|
+
(Thanks to [Dave Bolton][davebolton] for this patch.)
|
419
|
+
|
420
|
+
[davebolton]:https://github.com/lightningdb
|
421
|
+
|
401
422
|
## Bug reports, feature requests
|
402
423
|
|
403
424
|
Please file bug reports and feature requests in the [vmail github issue tracker][tracker].
|
data/Rakefile
CHANGED
@@ -12,12 +12,18 @@ task :web do
|
|
12
12
|
end
|
13
13
|
end
|
14
14
|
|
15
|
+
desc "git push and rake release bumped version"
|
16
|
+
task :bumped do
|
17
|
+
puts `git commit -a -m'bump' && git push && rake release`
|
18
|
+
end
|
19
|
+
|
15
20
|
desc "Run tests"
|
16
21
|
task :test do
|
17
22
|
$:.unshift File.expand_path("test")
|
18
23
|
require 'test_helper'
|
19
24
|
require 'time_format_test'
|
20
25
|
require 'message_formatter_test'
|
26
|
+
require 'reply_template_test'
|
21
27
|
require 'base64_test'
|
22
28
|
MiniTest::Unit.autorun
|
23
29
|
end
|
data/bin/vmail
CHANGED
File without changes
|
data/bin/vmail_client
CHANGED
File without changes
|
data/lib/vmail.rb
CHANGED
data/lib/vmail.vim
CHANGED
@@ -83,9 +83,9 @@ function! s:show_message()
|
|
83
83
|
" moving up when the next echo statement executes:
|
84
84
|
call feedkeys(":\<cr>")
|
85
85
|
redraw
|
86
|
-
|
87
|
-
let s:
|
88
|
-
let command = s:show_message_command . s:
|
86
|
+
" substract 2: because lines numbers start at 1 & messages start at line 2
|
87
|
+
let s:current_message_index = line('.') - 2
|
88
|
+
let command = s:show_message_command . s:current_message_index
|
89
89
|
echom "Loading message. Please wait..."
|
90
90
|
redrawstatus
|
91
91
|
let res = system(command)
|
@@ -133,7 +133,7 @@ endfunction
|
|
133
133
|
|
134
134
|
" invoked from withint message window
|
135
135
|
function! s:show_raw()
|
136
|
-
let command = s:show_message_command . s:
|
136
|
+
let command = s:show_message_command . s:current_message_index . ' raw'
|
137
137
|
echo command
|
138
138
|
setlocal modifiable
|
139
139
|
1,$delete
|
@@ -198,29 +198,21 @@ function! s:update()
|
|
198
198
|
endfunction
|
199
199
|
|
200
200
|
function! s:toggle_star() range
|
201
|
-
let
|
202
|
-
let n = 0
|
203
|
-
let uids = []
|
204
|
-
while lnum <= a:lastline
|
205
|
-
let line = getline(lnum)
|
206
|
-
let message_uid = matchstr(line, '^\d\+')
|
207
|
-
call add(uids, message_uid)
|
208
|
-
let lnum = lnum + 1
|
209
|
-
endwhile
|
210
|
-
let uid_set = join(uids, ",")
|
201
|
+
let uid_set = (a:firstline - 2) . '..' . (a:lastline - 2)
|
211
202
|
let flag_symbol = "[*]"
|
212
203
|
" check if starred already
|
213
204
|
let action = " +FLAGS"
|
214
|
-
if (match(
|
205
|
+
if (match(getline(a:firstline), flag_symbol) != -1)
|
215
206
|
let action = " -FLAGS"
|
216
207
|
endif
|
217
208
|
let command = s:flag_command . uid_set . action . " Flagged"
|
218
|
-
if
|
219
|
-
echom "toggling flag on message
|
209
|
+
if (a:lastline - a:firstline + 1) == 1
|
210
|
+
echom "toggling flag on message"
|
220
211
|
else
|
221
|
-
echom "toggling flags on
|
212
|
+
echom "toggling flags on " . (a:lastline - a:firstline + 1) . " messages"
|
222
213
|
endif
|
223
|
-
" toggle [*] on lines
|
214
|
+
" toggle [*] on lines; TODO: do this all in vimscript and do the starring
|
215
|
+
" in a thread in imap_client
|
224
216
|
let res = system(command)
|
225
217
|
setlocal modifiable
|
226
218
|
exec a:firstline . "," . a:lastline . "delete"
|
@@ -232,22 +224,19 @@ function! s:toggle_star() range
|
|
232
224
|
if len(split(res, "\n")) > 2
|
233
225
|
call feedkeys("\<cr>")
|
234
226
|
endif
|
227
|
+
echom "done"
|
235
228
|
endfunction
|
236
229
|
|
237
230
|
" flag can be Deleted or [Gmail]/Spam
|
238
231
|
func! s:delete_messages(flag) range
|
239
|
-
let
|
240
|
-
let n = 0
|
241
|
-
let uids = []
|
242
|
-
while lnum <= a:lastline
|
243
|
-
let line = getline(lnum)
|
244
|
-
let message_uid = matchstr(line, '^\d\+')
|
245
|
-
call add(uids, message_uid)
|
246
|
-
let lnum = lnum + 1
|
247
|
-
endwhile
|
248
|
-
let uid_set = join(uids, ",")
|
232
|
+
let uid_set = (a:firstline - 2) . '..' . (a:lastline - 2)
|
249
233
|
let command = s:flag_command . uid_set . " +FLAGS " . a:flag
|
250
|
-
|
234
|
+
let nummsgs = (a:lastline - a:firstline + 1)
|
235
|
+
if nummsgs == 1
|
236
|
+
echom "deleting message"
|
237
|
+
else
|
238
|
+
echom "deleting " . (a:lastline - a:firstline + 1) . " messages"
|
239
|
+
endif
|
251
240
|
let res = system(command)
|
252
241
|
setlocal modifiable
|
253
242
|
exec a:firstline . "," . a:lastline . "delete"
|
@@ -255,53 +244,39 @@ func! s:delete_messages(flag) range
|
|
255
244
|
write
|
256
245
|
" if more than 2 lines change, vim forces us to look at a message.
|
257
246
|
" dismiss it.
|
258
|
-
if
|
247
|
+
if nummsgs > 2
|
259
248
|
call feedkeys("\<cr>")
|
260
249
|
endif
|
261
250
|
redraw
|
262
|
-
echo
|
251
|
+
echo nummsgs . " message" . (nummsgs == 1 ? '' : 's') . " marked " . a:flag
|
263
252
|
endfunc
|
264
253
|
|
265
254
|
func! s:archive_messages() range
|
266
|
-
let
|
267
|
-
let n = 0
|
268
|
-
let uids = []
|
269
|
-
while lnum <= a:lastline
|
270
|
-
let line = getline(lnum)
|
271
|
-
let message_uid = matchstr(line, '^\d\+')
|
272
|
-
call add(uids, message_uid)
|
273
|
-
let lnum = lnum + 1
|
274
|
-
endwhile
|
275
|
-
let uid_set = join(uids, ",")
|
255
|
+
let uid_set = (a:firstline - 2) . '..' . (a:lastline - 2)
|
276
256
|
let command = s:move_to_command . uid_set . ' ' . "all"
|
277
|
-
|
257
|
+
let nummsgs = (a:lastline - a:firstline + 1)
|
258
|
+
echo "archiving message" . (nummsgs == 1 ? '' : 's')
|
278
259
|
let res = system(command)
|
279
260
|
setlocal modifiable
|
280
261
|
exec a:firstline . "," . a:lastline . "delete"
|
281
262
|
setlocal nomodifiable
|
282
263
|
write
|
283
|
-
|
284
|
-
|
264
|
+
if nummsgs > 2
|
265
|
+
call feedkeys("\<cr>")
|
266
|
+
endif
|
285
267
|
redraw
|
286
|
-
echo
|
268
|
+
echo nummsgs . " message" . (nummsgs == 1 ? '' : 's') . " archived"
|
287
269
|
endfunc
|
288
270
|
|
289
271
|
" --------------------------------------------------------------------------------
|
272
|
+
|
290
273
|
" append text bodies of a set of messages to a file
|
291
274
|
func! s:append_messages_to_file() range
|
292
|
-
let
|
293
|
-
let
|
294
|
-
let uids = []
|
295
|
-
while lnum <= a:lastline
|
296
|
-
let line = getline(lnum)
|
297
|
-
let message_uid = matchstr(line, '^\d\+')
|
298
|
-
call add(uids, message_uid)
|
299
|
-
let lnum = lnum + 1
|
300
|
-
endwhile
|
301
|
-
let uid_set = join(uids, ",")
|
275
|
+
let uid_set = (a:firstline - 2) . '..' . (a:lastline - 2)
|
276
|
+
let nummsgs = (a:lastline - a:firstline + 1)
|
302
277
|
let s:append_file = input("print messages to file: ", s:append_file)
|
303
278
|
let command = s:append_to_file_command . s:append_file . ' ' . uid_set
|
304
|
-
echo "appending " .
|
279
|
+
echo "appending " . nummsgs . " message" . (nummsgs == 1 ? '' : 's') . " to " . s:append_file . ". please wait..."
|
305
280
|
let res = system(command)
|
306
281
|
echo res
|
307
282
|
redraw
|
@@ -311,16 +286,8 @@ endfunc
|
|
311
286
|
" move to another mailbox
|
312
287
|
function! s:move_to_mailbox(copy) range
|
313
288
|
let s:copy_to_mailbox = a:copy
|
314
|
-
let
|
315
|
-
let
|
316
|
-
let uids = []
|
317
|
-
while lnum <= a:lastline
|
318
|
-
let line = getline(lnum)
|
319
|
-
let message_uid = matchstr(line, '^\d\+')
|
320
|
-
call add(uids, message_uid)
|
321
|
-
let lnum = lnum + 1
|
322
|
-
endwhile
|
323
|
-
let s:uid_set = join(uids, ",")
|
289
|
+
let s:uid_set = (a:firstline - 2) . '..' . (a:lastline - 2)
|
290
|
+
let s:nummsgs = (a:lastline - a:firstline + 1)
|
324
291
|
" now prompt use to select mailbox
|
325
292
|
if !exists("s:mailboxes")
|
326
293
|
call s:get_mailbox_list()
|
@@ -360,8 +327,11 @@ function! s:complete_move_to_mailbox()
|
|
360
327
|
end
|
361
328
|
setlocal nomodifiable
|
362
329
|
write
|
330
|
+
if s:nummsgs > 2
|
331
|
+
call feedkeys("\<cr>")
|
332
|
+
endif
|
363
333
|
redraw
|
364
|
-
echo "
|
334
|
+
echo s:nummsgs . " message" . (s:nummsgs == 1 ? '' : 's') . ' ' . (s:copy_to_mailbox ? 'copied' : 'moved') . ' to ' . mailbox
|
365
335
|
endfunction
|
366
336
|
|
367
337
|
function! CompleteMoveMailbox(findstart, base)
|
@@ -453,14 +423,17 @@ function! s:select_mailbox()
|
|
453
423
|
return
|
454
424
|
endif
|
455
425
|
let s:mailbox = mailbox
|
426
|
+
let s:query = "100 all"
|
456
427
|
let command = s:select_mailbox_command . shellescape(s:mailbox)
|
428
|
+
echo "selecting mailbox ". s:mailbox ". please wait..."
|
429
|
+
|
457
430
|
call system(command)
|
458
431
|
redraw
|
459
432
|
" now get latest 100 messages
|
460
433
|
call s:focus_list_window()
|
461
434
|
setlocal modifiable
|
462
435
|
let command = s:search_command . "100 all"
|
463
|
-
echo "
|
436
|
+
echo "loading messages..."
|
464
437
|
let res = system(command)
|
465
438
|
1,$delete
|
466
439
|
put! =res
|
@@ -514,7 +487,7 @@ function! s:more_messages()
|
|
514
487
|
let line = getline(line('.'))
|
515
488
|
let uid = matchstr(line, '^\d\+')
|
516
489
|
let command = s:more_messages_command . uid
|
517
|
-
echo
|
490
|
+
echo "fetching more messages. please wait..."
|
518
491
|
let res = system(command)
|
519
492
|
setlocal modifiable
|
520
493
|
let lines = split(res, "\n")
|
@@ -528,7 +501,7 @@ endfunction
|
|
528
501
|
" compose reply, compose, forward, save draft
|
529
502
|
|
530
503
|
function! s:compose_reply(all)
|
531
|
-
let command = s:reply_template_command
|
504
|
+
let command = s:reply_template_command
|
532
505
|
if a:all
|
533
506
|
let command = command . ' 1'
|
534
507
|
endif
|
@@ -546,7 +519,7 @@ function! s:compose_message()
|
|
546
519
|
endfunction
|
547
520
|
|
548
521
|
function! s:compose_forward()
|
549
|
-
let command = s:forward_template_command
|
522
|
+
let command = s:forward_template_command
|
550
523
|
call s:open_compose_window(command)
|
551
524
|
" call search("^to:")
|
552
525
|
" normal A
|
@@ -628,7 +601,7 @@ endfunc
|
|
628
601
|
|
629
602
|
" call from inside message window with <Leader>h
|
630
603
|
func! s:open_html_part()
|
631
|
-
let command = s:open_html_part_command
|
604
|
+
let command = s:open_html_part_command
|
632
605
|
" the command saves the html part to a local file
|
633
606
|
let outfile = system(command)
|
634
607
|
" todo: allow user to change open in browser command?
|
@@ -690,9 +663,9 @@ func! s:message_window_mappings()
|
|
690
663
|
nnoremap <silent> <buffer> q :close<cr>
|
691
664
|
|
692
665
|
nnoremap <silent> <buffer> <leader># :close<cr>:call <SID>focus_list_window()<cr>:call <SID>delete_messages("Deleted")<cr>
|
693
|
-
nmap <silent> <buffer> <leader>d <leader>#
|
694
666
|
nnoremap <silent> <buffer> <leader>* :call <SID>focus_list_window()<cr>:call <SID>toggle_star()<cr>
|
695
|
-
|
667
|
+
noremap <silent> <buffer> <leader>! :call <SID>focus_list_window()<cr>:call <SID>delete_messages("[Gmail]/Spam")<CR>
|
668
|
+
noremap <silent> <buffer> <leader>e :call <SID>focus_list_window()<cr>:call <SID>archive_messages()<CR>
|
696
669
|
|
697
670
|
nnoremap <silent> <buffer> <Leader>b :call <SID>focus_list_window()<cr>call <SID>move_to_mailbox(0)<CR>
|
698
671
|
nnoremap <silent> <buffer> <Leader>B :call <SID>focus_list_window()<cr>call <SID>move_to_mailbox(1)<CR>
|
data/lib/vmail/imap_client.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'drb'
|
2
2
|
require 'vmail/message_formatter'
|
3
|
+
require 'vmail/reply_template'
|
3
4
|
require 'vmail/string_ext'
|
4
5
|
require 'yaml'
|
5
6
|
require 'mail'
|
@@ -27,11 +28,13 @@ module Vmail
|
|
27
28
|
@logger = Logger.new(config['logfile'] || STDERR)
|
28
29
|
@logger.level = Logger::DEBUG
|
29
30
|
@current_mail = nil
|
30
|
-
@
|
31
|
+
@current_message_index = nil
|
32
|
+
@imap_server = config['server'] || 'imap.gmail.com'
|
33
|
+
@imap_port = config['port'] || 993
|
31
34
|
end
|
32
35
|
|
33
36
|
def open
|
34
|
-
@imap = Net::IMAP.new(
|
37
|
+
@imap = Net::IMAP.new(@imap_server, @imap_port, true, nil, false)
|
35
38
|
@imap.login(@username, @password)
|
36
39
|
end
|
37
40
|
|
@@ -41,11 +44,11 @@ module Vmail
|
|
41
44
|
@imap.disconnect
|
42
45
|
end
|
43
46
|
|
44
|
-
def select_mailbox(mailbox)
|
47
|
+
def select_mailbox(mailbox, force=false)
|
45
48
|
if MailboxAliases[mailbox]
|
46
49
|
mailbox = MailboxAliases[mailbox]
|
47
50
|
end
|
48
|
-
if mailbox == @mailbox
|
51
|
+
if mailbox == @mailbox && !force
|
49
52
|
return
|
50
53
|
end
|
51
54
|
log "selecting mailbox #{mailbox.inspect}"
|
@@ -53,11 +56,27 @@ module Vmail
|
|
53
56
|
log @imap.select(mailbox)
|
54
57
|
end
|
55
58
|
@mailbox = mailbox
|
56
|
-
|
57
|
-
|
59
|
+
get_mailbox_status
|
60
|
+
get_highest_message_id
|
58
61
|
return "OK"
|
59
62
|
end
|
60
63
|
|
64
|
+
def reload_mailbox
|
65
|
+
select_mailbox(@mailbox, true)
|
66
|
+
end
|
67
|
+
|
68
|
+
def get_highest_message_id
|
69
|
+
# get highest message ID
|
70
|
+
res = @imap.fetch([1,"*"], ["ENVELOPE"])
|
71
|
+
@num_messages = res[-1].seqno
|
72
|
+
log "HIGHEST ID: #@num_messages"
|
73
|
+
end
|
74
|
+
|
75
|
+
def get_mailbox_status
|
76
|
+
@status = @imap.status(@mailbox, ["MESSAGES", "RECENT", "UNSEEN"])
|
77
|
+
log "mailbox status: #{@status.inspect}"
|
78
|
+
end
|
79
|
+
|
61
80
|
def revive_connection
|
62
81
|
log "reviving connection"
|
63
82
|
open
|
@@ -65,6 +84,18 @@ module Vmail
|
|
65
84
|
@imap.select(@mailbox)
|
66
85
|
end
|
67
86
|
|
87
|
+
def prime_connection
|
88
|
+
reconnect_if_necessary(4) do
|
89
|
+
# this is just to prime the IMAP connection
|
90
|
+
# It's necessary for some reason before update and deliver.
|
91
|
+
log "priming connection for delivering"
|
92
|
+
res = @imap.fetch(@ids[-1], ["ENVELOPE"])
|
93
|
+
if res.nil?
|
94
|
+
raise IOError, "IMAP connection seems broken"
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
68
99
|
def list_mailboxes
|
69
100
|
@mailboxes ||= (@imap.list("[Gmail]/", "%") + @imap.list("", "%")).
|
70
101
|
select {|struct| struct.attr.none? {|a| a == :Noselect} }.
|
@@ -83,37 +114,52 @@ module Vmail
|
|
83
114
|
@mailboxes
|
84
115
|
end
|
85
116
|
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
117
|
+
# id_set may be a range, array, or string
|
118
|
+
def fetch_envelopes(id_set, are_uids=false)
|
119
|
+
log "fetch_envelopes: #{id_set.inspect}"
|
120
|
+
if id_set.is_a?(String)
|
121
|
+
id_set = id_set.split(',')
|
91
122
|
end
|
92
|
-
|
93
|
-
|
94
|
-
if uid_set.empty?
|
123
|
+
max_id = id_set.to_a[-1]
|
124
|
+
if id_set.to_a.empty?
|
95
125
|
log "empty set"
|
96
126
|
return ""
|
97
127
|
end
|
98
128
|
results = reconnect_if_necessary do
|
99
|
-
|
129
|
+
if are_uids
|
130
|
+
@imap.uid_fetch(id_set, ["FLAGS", "ENVELOPE", "RFC822.SIZE", "UID" ])
|
131
|
+
else
|
132
|
+
@imap.fetch(id_set, ["FLAGS", "ENVELOPE", "RFC822.SIZE", "UID" ])
|
133
|
+
end
|
134
|
+
end
|
135
|
+
if results.nil?
|
136
|
+
error = "expected fetch results but got nil"
|
137
|
+
log(error) && raise(error)
|
100
138
|
end
|
101
139
|
log "extracting headers"
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
}
|
110
|
-
|
111
|
-
|
112
|
-
|
140
|
+
new_message_rows = results.map {|x| extract_row_data(x, max_id) }
|
141
|
+
if are_uids
|
142
|
+
# replace old row_text values
|
143
|
+
new_message_rows.each {|new_row_data|
|
144
|
+
@message_list.
|
145
|
+
select {|old_row_data| old_row_data[:uid] == new_row_data[:uid]}.
|
146
|
+
each {|old_row_data| old_row_data[:row_text] = new_row_data[:row_text]}
|
147
|
+
}
|
148
|
+
else
|
149
|
+
# put new rows before the current ones
|
150
|
+
@message_list.unshift(*new_message_rows)
|
151
|
+
end
|
152
|
+
log "returning #{new_message_rows.size} new rows"
|
153
|
+
return new_message_rows.
|
154
|
+
map {|x| x[:row_text]}.
|
155
|
+
join("\n")
|
113
156
|
end
|
114
157
|
|
115
|
-
|
116
|
-
|
158
|
+
# TODO extract this to another class or module and write unit tests
|
159
|
+
def extract_row_data(fetch_data, max_id=nil)
|
160
|
+
seqno = fetch_data.seqno
|
161
|
+
uid = fetch_data.attr['UID']
|
162
|
+
# log "fetched seqno #{seqno} uid #{uid}"
|
117
163
|
envelope = fetch_data.attr["ENVELOPE"]
|
118
164
|
size = fetch_data.attr["RFC822.SIZE"]
|
119
165
|
flags = fetch_data.attr["FLAGS"]
|
@@ -148,18 +194,22 @@ module Vmail
|
|
148
194
|
subject = envelope.subject || ''
|
149
195
|
subject = Mail::Encodings.unquote_and_convert_to(subject, 'UTF-8')
|
150
196
|
flags = format_flags(flags)
|
151
|
-
first_col_width =
|
197
|
+
first_col_width = max_id.to_s.length
|
152
198
|
mid_width = @width - (first_col_width + 33)
|
153
199
|
address_col_width = (mid_width * 0.3).ceil
|
154
200
|
subject_col_width = (mid_width * 0.7).floor
|
155
|
-
[
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
201
|
+
row_text = [ seqno.to_s.col(first_col_width),
|
202
|
+
(date_formatted || '').col(14),
|
203
|
+
address.col(address_col_width),
|
204
|
+
subject.col(subject_col_width),
|
205
|
+
number_to_human_size(size).rcol(6),
|
206
|
+
flags.rcol(7)
|
207
|
+
].join(' ')
|
208
|
+
{:uid => uid, :seqno => seqno, :row_text => row_text}
|
161
209
|
rescue
|
162
|
-
"#{uid
|
210
|
+
log "error extracting header for uid #{uid} seqno #{seqno}: #$!"
|
211
|
+
row_text =i "#{seqno.to_s} : error extracting this header"
|
212
|
+
{:uid => uid, :seqno => seqno, :row_text => row_text}
|
163
213
|
end
|
164
214
|
|
165
215
|
UNITS = [:b, :kb, :mb, :gb].freeze
|
@@ -189,83 +239,120 @@ module Vmail
|
|
189
239
|
end
|
190
240
|
|
191
241
|
def search(limit, *query)
|
192
|
-
|
193
|
-
limit =
|
242
|
+
limit = limit.to_i
|
243
|
+
limit = 100 if limit.to_s !~ /^\d+$/
|
194
244
|
query = ['ALL'] if query.empty?
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
@
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
245
|
+
if query.size == 1 && query[0].downcase == 'all'
|
246
|
+
# form a sequence range
|
247
|
+
query.unshift [[@num_messages - limit.to_i + 1 , 1].max, @num_messages].join(':')
|
248
|
+
@all_search = true
|
249
|
+
else
|
250
|
+
# this is a special query search
|
251
|
+
# set the target range to the whole set
|
252
|
+
query.unshift "1:#@num_messages"
|
253
|
+
@all_search = false
|
254
|
+
end
|
255
|
+
log "@all_search #{@all_search}"
|
256
|
+
@query = query
|
257
|
+
log "search query: #@query.inspect"
|
258
|
+
@ids = reconnect_if_necessary do
|
259
|
+
@imap.search(@query.join(' '))
|
260
|
+
end
|
261
|
+
# save ids in @ids, because filtered search relies on it
|
262
|
+
fetch_ids = if @all_search
|
263
|
+
@ids
|
264
|
+
else #filtered search
|
265
|
+
@start_index = [@ids.length - limit, 0].max
|
266
|
+
@ids[@start_index..-1]
|
267
|
+
end
|
268
|
+
log "search query got #{@ids.size} results"
|
269
|
+
@message_list = [] # this will hold all the data extracted from these message envelopes
|
270
|
+
res = fetch_envelopes(fetch_ids)
|
271
|
+
add_more_message_line(res, fetch_ids[0])
|
203
272
|
end
|
204
273
|
|
205
274
|
def update
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
log "uid_search #@query"
|
217
|
-
@imap.uid_search(@query)
|
275
|
+
prime_connection
|
276
|
+
old_num_messages = @num_messages
|
277
|
+
# we need to re-select the mailbox to get the new highest id
|
278
|
+
reload_mailbox
|
279
|
+
update_query = @query
|
280
|
+
# set a new range filter
|
281
|
+
update_query[0] = "#{old_num_messages}:#{@num_messages}"
|
282
|
+
ids = reconnect_if_necessary {
|
283
|
+
log "search #update_query"
|
284
|
+
@imap.search(update_query.join(' '))
|
218
285
|
}
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
286
|
+
# TODO change this. will throw error now
|
287
|
+
new_ids = ids.select {|x| x > @ids.max}
|
288
|
+
@ids = @ids + new_ids
|
289
|
+
log "UPDATE: NEW UIDS: #{new_ids.inspect}"
|
290
|
+
if !new_ids.empty?
|
291
|
+
res = fetch_envelopes(new_ids)
|
224
292
|
res
|
225
293
|
end
|
226
294
|
end
|
227
295
|
|
228
|
-
# gets 100 messages prior to
|
229
|
-
def more_messages(
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
res =
|
296
|
+
# gets 100 messages prior to id
|
297
|
+
def more_messages(message_id, limit=100)
|
298
|
+
log "more_messages: message_id #{message_id}"
|
299
|
+
message_id = message_id.to_i
|
300
|
+
if @all_search
|
301
|
+
x = [(message_id - limit), 0].max
|
302
|
+
y = [message_id - 1, 0].max
|
303
|
+
res = fetch_envelopes((x..y))
|
304
|
+
add_more_message_line(res, x)
|
305
|
+
else
|
306
|
+
# filter search query
|
307
|
+
log "@start_index #@start_index"
|
308
|
+
x = [(@start_index - limit), 0].max
|
309
|
+
y = [@start_index - 1, 0].max
|
310
|
+
@start_index = x
|
311
|
+
res = fetch_envelopes(@ids[x..y])
|
312
|
+
add_more_message_line(res, @ids[x])
|
313
|
+
end
|
314
|
+
end
|
315
|
+
|
316
|
+
def add_more_message_line(res, start_id)
|
317
|
+
log "add_more_message_line for start_id #{start_id}"
|
318
|
+
if @all_search
|
319
|
+
return res if start_id.nil?
|
320
|
+
if start_id <= 1
|
321
|
+
return res
|
322
|
+
end
|
323
|
+
remaining = start_id - 1
|
324
|
+
else # filter search
|
325
|
+
remaining = @ids.index(start_id) - 1
|
326
|
+
end
|
327
|
+
if remaining < 1
|
328
|
+
log "none remaining"
|
329
|
+
return res
|
244
330
|
end
|
245
|
-
|
331
|
+
log "remaining messages: #{remaining}"
|
332
|
+
"> Load #{[100, remaining].min} more messages. #{remaining} remaining.\n" + res
|
246
333
|
end
|
247
334
|
|
248
|
-
def show_message(
|
249
|
-
|
250
|
-
if forwarded
|
251
|
-
return @current_message.split(/\n-{20,}\n/, 2)[1]
|
252
|
-
end
|
335
|
+
def show_message(index, raw=false)
|
336
|
+
log "showing message at #{index}"
|
253
337
|
return @current_mail.to_s if raw
|
254
|
-
|
255
|
-
|
338
|
+
index = index.to_i
|
339
|
+
return @current_message if index == @current_message_index
|
340
|
+
envelope_data = @message_list[index]
|
341
|
+
uid = envelope_data[:uid]
|
342
|
+
log "fetching uid #{uid}"
|
256
343
|
fetch_data = reconnect_if_necessary do
|
257
344
|
@imap.uid_fetch(uid, ["FLAGS", "RFC822", "RFC822.SIZE"])[0]
|
258
345
|
end
|
259
346
|
res = fetch_data.attr["RFC822"]
|
260
347
|
mail = Mail.new(res)
|
261
|
-
@
|
348
|
+
@current_message_index = index
|
262
349
|
@current_mail = mail # used later to show raw message or extract attachments if any
|
263
350
|
log "saving current mail with parts: #{@current_mail.parts.inspect}"
|
264
351
|
formatter = Vmail::MessageFormatter.new(mail)
|
265
352
|
out = formatter.process_body
|
266
353
|
size = fetch_data.attr["RFC822.SIZE"]
|
267
354
|
@current_message = <<-EOF
|
268
|
-
#{@mailbox} #{
|
355
|
+
#{@mailbox} #{index} #{number_to_human_size size} #{format_parts_info(formatter.list_parts)}
|
269
356
|
---------------------------------------
|
270
357
|
#{format_headers(formatter.extract_headers)}
|
271
358
|
|
@@ -283,36 +370,66 @@ EOF
|
|
283
370
|
end
|
284
371
|
end
|
285
372
|
|
286
|
-
#
|
373
|
+
# id_set is a string comming from the vim client
|
287
374
|
# action is -FLAGS or +FLAGS
|
288
|
-
def flag(
|
289
|
-
|
290
|
-
|
291
|
-
end
|
292
|
-
# #<struct Net::IMAP::FetchData seqno=17423, attr={"FLAGS"=>[:Seen, "Flagged"], "UID"=>83113}>
|
293
|
-
log "flag #{uid_set} #{flg} #{action}"
|
375
|
+
def flag(index_range, action, flg)
|
376
|
+
uid_set = uids_from_index_range(index_range)
|
377
|
+
log "flag #{uid_set.inspect} #{flg} #{action}"
|
294
378
|
if flg == 'Deleted'
|
379
|
+
log "Deleting index_range: #{index_range.inspect}; uid_set: #{uid_set.inspect}"
|
295
380
|
# for delete, do in a separate thread because deletions are slow
|
296
381
|
Thread.new do
|
297
382
|
unless @mailbox == '[Gmail]/Trash'
|
298
383
|
@imap.uid_copy(uid_set, "[Gmail]/Trash")
|
299
384
|
end
|
300
385
|
res = @imap.uid_store(uid_set, action, [flg.to_sym])
|
386
|
+
remove_uid_set_from_cached_lists(uid_set)
|
387
|
+
reload_mailbox
|
301
388
|
end
|
302
|
-
uid_set.each { |uid| @all_uids.delete(uid) }
|
303
389
|
elsif flg == '[Gmail]/Spam'
|
304
|
-
|
305
|
-
|
306
|
-
|
390
|
+
log "Marking as spam index_range: #{index_range.inspect}; uid_set: #{uid_set.inspect}"
|
391
|
+
Thread.new do
|
392
|
+
@imap.uid_copy(uid_set, "[Gmail]/Spam")
|
393
|
+
res = @imap.uid_store(uid_set, action, [:Deleted])
|
394
|
+
remove_uid_set_from_cached_lists(uid_set)
|
395
|
+
reload_mailbox
|
396
|
+
end
|
397
|
+
"#{id} deleted"
|
307
398
|
else
|
308
|
-
log "Flagging"
|
399
|
+
log "Flagging index_range: #{index_range.inspect}; uid_set: #{uid_set.inspect}"
|
309
400
|
res = @imap.uid_store(uid_set, action, [flg.to_sym])
|
310
|
-
|
311
|
-
|
312
|
-
end
|
401
|
+
log res.inspect
|
402
|
+
fetch_envelopes(uid_set, true).tap {|x| log x}
|
403
|
+
end
|
404
|
+
end
|
405
|
+
|
406
|
+
def uids_from_index_range(index_range_as_string)
|
407
|
+
raise "expecting String" unless index_range_as_string.is_a?(String)
|
408
|
+
raise "expecting a range as string" unless index_range_as_string =~ /^\d+\.\.\d+$/
|
409
|
+
log "converting index_range #{index_range_as_string} to uids"
|
410
|
+
uids = @message_list[eval(index_range_as_string)].map {|row| row[:uid]}
|
411
|
+
log "converted index_range #{index_range_as_string} to uids #{uids.inspect}"
|
412
|
+
uids
|
413
|
+
end
|
414
|
+
|
415
|
+
def remove_uid_set_from_cached_lists(uid_set)
|
416
|
+
# delete from cached @ids and @message_list
|
417
|
+
uid_set.each {|uid|
|
418
|
+
@message_list.
|
419
|
+
select {|row| row[:uid] == uid}.
|
420
|
+
each {|row|
|
421
|
+
seqno = row[:seqno]
|
422
|
+
log "deleting seqno #{seqno} from @ids"
|
423
|
+
@ids.delete seqno
|
424
|
+
log "deleting msg uid #{row[:uid]} from @message_list"
|
425
|
+
@message_list.delete(row)
|
426
|
+
}
|
427
|
+
}
|
428
|
+
|
313
429
|
end
|
314
430
|
|
315
|
-
def move_to(
|
431
|
+
def move_to(id_set, mailbox)
|
432
|
+
log "move #{id_set.inspect} to #{mailbox}"
|
316
433
|
if mailbox == 'all'
|
317
434
|
log "archiving messages"
|
318
435
|
end
|
@@ -320,24 +437,28 @@ EOF
|
|
320
437
|
mailbox = MailboxAliases[mailbox]
|
321
438
|
end
|
322
439
|
create_if_necessary mailbox
|
323
|
-
|
324
|
-
|
440
|
+
log "getting uids form index range #{id_set}"
|
441
|
+
uid_set = uids_from_index_range(id_set)
|
442
|
+
log "moving uid_set: #{uid_set.inspect} to #{mailbox}"
|
443
|
+
Thread.new do
|
444
|
+
log @imap.uid_copy(uid_set, mailbox)
|
445
|
+
log @imap.uid_store(uid_set, '+FLAGS', [:Deleted])
|
446
|
+
reload_mailbox
|
447
|
+
log "moved uid_set #{uid_set.inspect} to #{mailbox}"
|
325
448
|
end
|
326
|
-
log "move_to #{uid_set.inspect} #{mailbox}"
|
327
|
-
log @imap.uid_copy(uid_set, mailbox)
|
328
|
-
log @imap.uid_store(uid_set, '+FLAGS', [:Deleted])
|
329
449
|
end
|
330
450
|
|
331
|
-
def copy_to(
|
451
|
+
def copy_to(id_set, mailbox)
|
332
452
|
if MailboxAliases[mailbox]
|
333
453
|
mailbox = MailboxAliases[mailbox]
|
334
454
|
end
|
335
455
|
create_if_necessary mailbox
|
336
|
-
|
337
|
-
|
338
|
-
|
456
|
+
uid_set = uids_from_index_range(id_set)
|
457
|
+
log "copying #{uid_set.inspect} to #{mailbox}"
|
458
|
+
Thread.new do
|
459
|
+
log @imap.uid_copy(uid_set, mailbox)
|
460
|
+
log "copied uid_set #{uid_set.inspect} to #{mailbox}"
|
339
461
|
end
|
340
|
-
log @imap.uid_copy(uid_set, mailbox)
|
341
462
|
end
|
342
463
|
|
343
464
|
def create_if_necessary(mailbox)
|
@@ -350,18 +471,18 @@ EOF
|
|
350
471
|
end
|
351
472
|
end
|
352
473
|
|
353
|
-
def append_to_file(file,
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
message = show_message(uid)
|
474
|
+
def append_to_file(file, index_range_as_string)
|
475
|
+
raise "expecting a range as string" unless index_range_as_string =~ /^\d+\.\.\d+$/
|
476
|
+
index_range = eval(index_range_as_string)
|
477
|
+
log "append to file range #{index_range.inspect} to file: #{file}"
|
478
|
+
index_range.each do |idx|
|
479
|
+
message = show_message(idx)
|
360
480
|
divider = "#{'=' * 39}\n"
|
361
481
|
File.open(file, 'a') {|f| f.puts(divider + message + "\n\n")}
|
362
|
-
|
482
|
+
subject = (message[/^subject:(.*)/,1] || '').strip
|
483
|
+
log "appended message '#{subject}'"
|
363
484
|
end
|
364
|
-
"printed #{
|
485
|
+
"printed #{index_range.to_a.size} message#{index_range.to_a.size == 1 ? '' : 's'} to #{file.strip}"
|
365
486
|
end
|
366
487
|
|
367
488
|
|
@@ -384,41 +505,22 @@ EOF
|
|
384
505
|
lines.join("\n")
|
385
506
|
end
|
386
507
|
|
387
|
-
def reply_template(
|
388
|
-
log "sending reply template
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
cc = [envelope.to, envelope.cc]
|
393
|
-
cc = cc.flatten.compact.
|
394
|
-
select {|x| @username !~ /#{x.mailbox}@#{x.host}/}.
|
395
|
-
map {|x| address_to_string(x)}.join(", ")
|
396
|
-
mail = Mail.new fetch_data.attr['RFC822']
|
397
|
-
formatter = Vmail::MessageFormatter.new(mail)
|
398
|
-
headers = formatter.extract_headers
|
399
|
-
subject = headers['subject']
|
400
|
-
if subject !~ /Re: /
|
401
|
-
subject = "Re: #{subject}"
|
402
|
-
end
|
403
|
-
cc = replyall ? cc : nil
|
404
|
-
date = headers['date'].is_a?(String) ? Time.parse(headers['date']) : headers['date']
|
405
|
-
quote_header = "On #{date.strftime('%a, %b %d, %Y at %I:%M %p')}, #{address_to_string(envelope.from[0])} wrote:\n\n"
|
406
|
-
body = quote_header + formatter.process_body.gsub(/^(?=>)/, ">").gsub(/^(?!>)/, "> ")
|
407
|
-
reply_headers = { 'from' => "#@name <#@username>", 'to' => recipient, 'cc' => cc, 'subject' => subject}
|
508
|
+
def reply_template(replyall=false)
|
509
|
+
log "sending reply template"
|
510
|
+
# user reply_template class
|
511
|
+
reply_headers = Vmail::ReplyTemplate.new(@current_mail, @username, @name, replyall).reply_headers
|
512
|
+
body = reply_headers.delete(:body)
|
408
513
|
format_headers(reply_headers) + "\n\n\n" + body + signature
|
409
514
|
end
|
410
515
|
|
411
|
-
def address_to_string(x)
|
412
|
-
x.name ? "#{x.name} <#{x.mailbox}@#{x.host}>" : "#{x.mailbox}@#{x.host}"
|
413
|
-
end
|
414
516
|
|
415
517
|
def signature
|
416
518
|
return '' unless @signature
|
417
519
|
"\n\n#@signature"
|
418
520
|
end
|
419
521
|
|
420
|
-
def forward_template
|
421
|
-
original_body =
|
522
|
+
def forward_template
|
523
|
+
original_body = @current_message.split(/\n-{20,}\n/, 2)[1]
|
422
524
|
new_message_template +
|
423
525
|
"\n---------- Forwarded message ----------\n" +
|
424
526
|
original_body + signature
|
@@ -427,15 +529,7 @@ EOF
|
|
427
529
|
def deliver(text)
|
428
530
|
# parse the text. The headers are yaml. The rest is text body.
|
429
531
|
require 'net/smtp'
|
430
|
-
|
431
|
-
# this is just to prime the IMAP connection
|
432
|
-
# It's necessary for some reason.
|
433
|
-
log "priming connection for delivering"
|
434
|
-
res = @imap.uid_fetch(@all_uids[-1], ["ENVELOPE"])
|
435
|
-
if res.nil?
|
436
|
-
raise IOError, "IMAP connection seems broken"
|
437
|
-
end
|
438
|
-
end
|
532
|
+
prime_connection
|
439
533
|
mail = new_mail_from_input(text)
|
440
534
|
mail.delivery_method(*smtp_settings)
|
441
535
|
log mail.deliver!
|
@@ -518,8 +612,8 @@ EOF
|
|
518
612
|
"saved:\n" + saved.map {|x| "- #{x}"}.join("\n")
|
519
613
|
end
|
520
614
|
|
521
|
-
def open_html_part
|
522
|
-
log "open_html_part
|
615
|
+
def open_html_part
|
616
|
+
log "open_html_part"
|
523
617
|
log @current_mail.parts.inspect
|
524
618
|
multipart = @current_mail.parts.detect {|part| part.multipart?}
|
525
619
|
html_part = if multipart
|
@@ -571,6 +665,9 @@ EOF
|
|
571
665
|
log(revive_connection)
|
572
666
|
# try just once
|
573
667
|
block.call
|
668
|
+
rescue
|
669
|
+
log "error: #{$!}"
|
670
|
+
raise
|
574
671
|
end
|
575
672
|
|
576
673
|
def self.start(config)
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'vmail/message_formatter'
|
2
|
+
require 'mail'
|
3
|
+
require 'time'
|
4
|
+
|
5
|
+
module Vmail
|
6
|
+
class ReplyTemplate
|
7
|
+
|
8
|
+
def initialize(mail, username, name, replyall)
|
9
|
+
@username, @name, @replyall = username, name, replyall
|
10
|
+
@mail = Mail.new(mail)
|
11
|
+
end
|
12
|
+
|
13
|
+
def reply_headers
|
14
|
+
formatter = Vmail::MessageFormatter.new(@mail)
|
15
|
+
headers = formatter.extract_headers
|
16
|
+
subject = headers['subject']
|
17
|
+
if subject !~ /Re: /
|
18
|
+
subject = "Re: #{subject}"
|
19
|
+
end
|
20
|
+
date = headers['date'].is_a?(String) ? Time.parse(headers['date']) : headers['date']
|
21
|
+
quote_header = "On #{date.strftime('%a, %b %d, %Y at %I:%M %p')}, #{sender} wrote:\n\n"
|
22
|
+
body = quote_header + formatter.process_body.gsub(/^(?=>)/, ">").gsub(/^(?!>)/, "> ")
|
23
|
+
{'from' => "#@name <#@username>", 'to' => primary_recipient, 'cc' => cc, 'subject' => subject, :body => body}
|
24
|
+
end
|
25
|
+
|
26
|
+
def primary_recipient
|
27
|
+
from = @mail.header['from']
|
28
|
+
reply_to = @mail.header['reply-to']
|
29
|
+
[ reply_to, from ].flatten.compact.map(&:to_s)[0]
|
30
|
+
end
|
31
|
+
|
32
|
+
def cc
|
33
|
+
return nil unless @replyall
|
34
|
+
cc = @mail.header['to'].value.split(/,\s*/) + @mail.header['cc'].value.split(/,\s*/)
|
35
|
+
cc = cc.flatten.compact.
|
36
|
+
select {|x|
|
37
|
+
x.to_s[/<([^>]+)>/, 1] !~ /#{@username}/ && x.to_s[/^[^<]+/, 1] !~ /#{@name}/
|
38
|
+
}.join(', ')
|
39
|
+
end
|
40
|
+
|
41
|
+
def sender
|
42
|
+
@mail.header['from'].value
|
43
|
+
end
|
44
|
+
|
45
|
+
# deprecated
|
46
|
+
def address_to_string(x)
|
47
|
+
x.name ? "#{x.name} <#{x.mailbox}@#{x.host}>" : "#{x.mailbox}@#{x.host}"
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
end
|
data/lib/vmail/version.rb
CHANGED
@@ -0,0 +1,58 @@
|
|
1
|
+
Return-Path: <chappy1@gmail.com>
|
2
|
+
Received: by 10.213.2.204 with SMTP id 12cs233347ebk; Sun, 12 Dec 2010 10:13:13 -0800
|
3
|
+
Received: by 10.91.51.19 with SMTP id d19mr3869811agk.183.1292177592502; Sun, 12 Dec 2010 10:13:12 -0800
|
4
|
+
Received: from mail-yx0-f171.google.com (mail-yx0-f171.google.com [209.85.213.171]) by mx.google.com with ESMTP id d7si4681466and.167.2010.12.12.10.13.10; Sun, 12 Dec 2010 10:13:11 -0800
|
5
|
+
Received: by mail-yx0-f171.google.com with SMTP id 11so3634988yxi.2 for
|
6
|
+
<multiple recipients>; Sun, 12 Dec 2010 10:13:10 -0800 (PST)
|
7
|
+
Received: by 10.90.24.10 with SMTP id 10mr3890562agx.179.1292177590623; Sun, 12 Dec 2010 10:13:10 -0800
|
8
|
+
Received: from [26.144.14.48] (m482436d0.tmodns.net [208.54.36.72]) by mx.google.com with ESMTPS id c34sm4564739anc.30.2010.12.12.10.13.08 (version=TLSv1/SSLv3 cipher=RC4-MD5); Sun, 12 Dec 2010 10:13:09 -0800
|
9
|
+
Date: Sun, 12 Dec 2010 13:13:01 -0500
|
10
|
+
From: Chappy Youn <chappy1@gmail.com>
|
11
|
+
To: Daniel Choi <dhchoi@gmail.com>,
|
12
|
+
Draculette Ko <violinist.ko@gmail.com>,
|
13
|
+
Cookiemonster Youn <cookiemonster@gmail.com>
|
14
|
+
Cc: Racoon <raycoon@gmail.com>
|
15
|
+
Message-ID: <73734601-F016-4F3B-A0C8-0FF48011292F@gmail.com>
|
16
|
+
Subject: Holiday potluck at Ray's
|
17
|
+
Mime-Version: 1.0
|
18
|
+
Content-Type: text/plain;
|
19
|
+
charset=us-ascii;
|
20
|
+
delsp=yes;
|
21
|
+
format=flowed
|
22
|
+
Content-Transfer-Encoding: 7bit
|
23
|
+
Delivered-To: dhchoi@gmail.com
|
24
|
+
Received-SPF: pass (google.com: domain of chappy1@gmail.com designates
|
25
|
+
209.85.213.171 as permitted sender) client-ip=209.85.213.171;
|
26
|
+
Authentication-Results: mx.google.com; spf=pass (google.com: domain of
|
27
|
+
chappy1@gmail.com designates 209.85.213.171 as permitted sender)
|
28
|
+
smtp.mail=chappy1@gmail.com; dkim=pass (test mode) header.i=@gmail.com
|
29
|
+
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=gamma;
|
30
|
+
h=domainkey-signature:received:received:message-id:from:to
|
31
|
+
:content-type:content-transfer-encoding:x-mailer:mime-version
|
32
|
+
:subject:date:cc; bh=2E6xsnWGlAai1MjVchauqPtC2J0dmGnscU+hCRvcScY=;
|
33
|
+
b=joH+NKL712kaFO0ThbHEF/7q9qSf1O6Xi9u2TBCp3uQl0aEVwuqGFHcg+id0/Am5qM
|
34
|
+
IoxNBItJn9zMUgmzzZcJSl5/LaxKwrj8NOn/eYF8fkKRecCbNUoCYV4Z0T7BkuPwpkWA
|
35
|
+
PYPun1PZT8ArAM2aXoMzApEnzOCvf/ZkM78Q4=
|
36
|
+
DomainKey-Signature: a=rsa-sha1; c=nofws; d=gmail.com; s=gamma;
|
37
|
+
h=message-id:from:to:content-type:content-transfer-encoding:x-mailer
|
38
|
+
:mime-version:subject:date:cc;
|
39
|
+
b=fCuCk6O03S/wqKttwtK0ePIAy7MFja+qOyIkt9X/ToJg8MiPoHh6UH5o0UaAlAEyE7
|
40
|
+
7C8iRyxjV4EC3vfQhzHUzZHFuXAOtPOF6ZSidHKbXVad3Wvv0zie9dV3pFXCbg0fyC8M
|
41
|
+
c1nHTFM+So9DDIulICizW196SVSh65Ds+NVK4=
|
42
|
+
X-Mailer: iPhone Mail (7C144)
|
43
|
+
|
44
|
+
Guys,
|
45
|
+
Tonight we will have a potluck at Ray's at 7. Pls bring food for 1.5
|
46
|
+
ppl.
|
47
|
+
|
48
|
+
Ray will provide wine and dessert.
|
49
|
+
|
50
|
+
Also, we will be having a poor man's Yankee swap. Pls bring something
|
51
|
+
gift wrapped from home. Nothing fancy, but something halfway decent or
|
52
|
+
funny.
|
53
|
+
|
54
|
+
El, make sure it's worth more than 50 cents.
|
55
|
+
|
56
|
+
Chappy
|
57
|
+
|
58
|
+
Sent from my iPhone
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'vmail/reply_template'
|
3
|
+
|
4
|
+
describe Vmail::ReplyTemplate do
|
5
|
+
before do
|
6
|
+
@raw = read_fixture('reply_all.eml')
|
7
|
+
@mail = Mail.new(@raw)
|
8
|
+
@rt = Vmail::ReplyTemplate.new(@mail, 'dhchoi@gmail.com', 'Daniel Choi', true)
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_detect_primary_recipient
|
12
|
+
assert_equal "Chappy Youn <chappy1@gmail.com>", @rt.primary_recipient
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_detect_cc
|
16
|
+
expected = "Draculette Ko <violinist.ko@gmail.com>, Cookiemonster Youn <cookiemonster@gmail.com>, Racoon <raycoon@gmail.com>"
|
17
|
+
assert_equal expected, @rt.cc
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_sender
|
21
|
+
assert_equal "Chappy Youn <chappy1@gmail.com>", @rt.sender
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_template
|
25
|
+
expected = {"from"=>"Daniel Choi <dhchoi@gmail.com>", "to"=>"Chappy Youn <chappy1@gmail.com>", "cc"=>"Draculette Ko <violinist.ko@gmail.com>, Cookiemonster Youn <cookiemonster@gmail.com>, Racoon <raycoon@gmail.com>", "subject"=>"Re: Holiday potluck at Ray's", :body=>"On Sun, Dec 12, 2010 at 01:13 PM, Chappy Youn <chappy1@gmail.com> wrote:\n\n> Guys,\n> Tonight we will have a potluck at Ray's at 7. Pls bring food for 1.5 \n> ppl.\n> \n> Ray will provide wine and dessert.\n> \n> Also, we will be having a poor man's Yankee swap. Pls bring something \n> gift wrapped from home. Nothing fancy, but something halfway decent or \n> funny.\n> \n> El, make sure it's worth more than 50 cents.\n> \n> Chappy\n> \n> Sent from my iPhone"}
|
26
|
+
|
27
|
+
assert_equal expected, @rt.reply_headers
|
28
|
+
end
|
29
|
+
|
30
|
+
|
31
|
+
end
|
32
|
+
|
data/website/vmail-template.html
CHANGED
@@ -23,10 +23,6 @@
|
|
23
23
|
|
24
24
|
<div class="sidebar">
|
25
25
|
|
26
|
-
<h4>share this</h4>
|
27
|
-
|
28
|
-
<span class="st_twitter_large" displayText="Tweet"></span><span class="st_facebook_large" displayText="Facebook"></span><span class="st_ybuzz_large" displayText="Yahoo! Buzz"></span><span class="st_gbuzz_large" displayText="Google Buzz"></span><span class="st_email_large" displayText="Email"></span><span class="st_sharethis_large" displayText="ShareThis"></span>
|
29
|
-
|
30
26
|
<h4>links</h4>
|
31
27
|
<ul>
|
32
28
|
<li><a href="https://github.com/danchoi/vmail">github repo</a></li>
|
@@ -34,6 +30,10 @@
|
|
34
30
|
<li><a href="https://github.com/danchoi/vmail/issues">issue tracker</a></li>
|
35
31
|
<li><a href="http://www.google.com/search?sourceid=chrome&ie=UTF-8&q=vmail+vim#hl=en&tbo=1&tbs=mbl:1,mbl_sv:0&q=link:http://danielchoi.com/software/vmail.html&sa=X&ei=iw0JTYnsPMT_lgfgvMC1AQ&ved=0CAUQ6QcwCg&fp=1&cad=b">web reactions</a></li>
|
36
32
|
</ul>
|
33
|
+
<h4>share this</h4>
|
34
|
+
|
35
|
+
<span class="st_twitter_large" displayText="Tweet"></span><span class="st_facebook_large" displayText="Facebook"></span><span class="st_ybuzz_large" displayText="Yahoo! Buzz"></span><span class="st_gbuzz_large" displayText="Google Buzz"></span><span class="st_email_large" displayText="Email"></span><span class="st_sharethis_large" displayText="ShareThis"></span>
|
36
|
+
|
37
37
|
</div>
|
38
38
|
|
39
39
|
</div>
|
metadata
CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
segments:
|
6
6
|
- 0
|
7
7
|
- 4
|
8
|
-
-
|
9
|
-
version: 0.4.
|
8
|
+
- 6
|
9
|
+
version: 0.4.6
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Daniel Choi
|
@@ -71,6 +71,7 @@ files:
|
|
71
71
|
- lib/vmail/imap_client.rb
|
72
72
|
- lib/vmail/message_formatter.rb
|
73
73
|
- lib/vmail/options.rb
|
74
|
+
- lib/vmail/reply_template.rb
|
74
75
|
- lib/vmail/string_ext.rb
|
75
76
|
- lib/vmail/version.rb
|
76
77
|
- test/base64_test.rb
|
@@ -79,10 +80,12 @@ files:
|
|
79
80
|
- test/fixtures/google-affiliate.eml
|
80
81
|
- test/fixtures/htmlbody.eml
|
81
82
|
- test/fixtures/moleskine-html.eml
|
83
|
+
- test/fixtures/reply_all.eml
|
82
84
|
- test/fixtures/rfc_part.eml
|
83
85
|
- test/fixtures/textbody-nocontenttype.eml
|
84
86
|
- test/fixtures/with-attachments.eml
|
85
87
|
- test/message_formatter_test.rb
|
88
|
+
- test/reply_template_test.rb
|
86
89
|
- test/test_helper.rb
|
87
90
|
- test/time_format_test.rb
|
88
91
|
- vmail.gemspec
|
@@ -158,9 +161,11 @@ test_files:
|
|
158
161
|
- test/fixtures/google-affiliate.eml
|
159
162
|
- test/fixtures/htmlbody.eml
|
160
163
|
- test/fixtures/moleskine-html.eml
|
164
|
+
- test/fixtures/reply_all.eml
|
161
165
|
- test/fixtures/rfc_part.eml
|
162
166
|
- test/fixtures/textbody-nocontenttype.eml
|
163
167
|
- test/fixtures/with-attachments.eml
|
164
168
|
- test/message_formatter_test.rb
|
169
|
+
- test/reply_template_test.rb
|
165
170
|
- test/test_helper.rb
|
166
171
|
- test/time_format_test.rb
|