vmail 0.4.5 → 0.4.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|