texmailer 0.1 → 0.2.d
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/texmailer +29 -12
- data/lib/texmailer/adbook.rb +98 -0
- data/lib/texmailer/config.rb +53 -1
- data/lib/texmailer/drafts.rb +166 -3
- data/lib/texmailer/iview.rb +126 -0
- data/lib/texmailer/iworker +137 -0
- data/lib/texmailer/js/jquery.autocomplete.js +390 -0
- data/lib/texmailer/js/jquery.js +19 -0
- data/lib/texmailer/rhtml/addrbook.jsn +8 -0
- data/lib/texmailer/rhtml/header.html +1 -0
- data/lib/texmailer/rhtml/index.html +41 -15
- data/lib/texmailer/rhtml/reply.html +122 -0
- data/lib/texmailer/rhtml/settings.html +71 -5
- data/lib/texmailer/send.rb +1 -0
- data/lib/texmailer/wapp.rb +28 -20
- metadata +17 -7
data/bin/texmailer
CHANGED
@@ -1,4 +1,6 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
+
TMVERSION = '0.2.d'
|
3
|
+
|
2
4
|
require 'rubygems'
|
3
5
|
require 'ftools'
|
4
6
|
require 'erb'
|
@@ -14,6 +16,8 @@ require 'texmailer/drafts'
|
|
14
16
|
require 'texmailer/config'
|
15
17
|
require 'texmailer/send'
|
16
18
|
require 'texmailer/wapp'
|
19
|
+
require 'texmailer/iview'
|
20
|
+
require 'texmailer/adbook'
|
17
21
|
|
18
22
|
include Texmailer
|
19
23
|
|
@@ -22,12 +26,14 @@ $basedir = libdir + '/texmailer'
|
|
22
26
|
# Parse Options
|
23
27
|
theport = 6213
|
24
28
|
$userdir = '~/.texmailer'
|
29
|
+
daemonize = true
|
25
30
|
|
26
31
|
opt = OptionParser.new { |opts|
|
27
32
|
opts.banner = "Usage: texmailer [options]"
|
28
33
|
opts.on("-p PORT","--port PORT",Integer,"Port to listen on (default #{String(theport)})") { |p| theport = p }
|
29
34
|
opts.on("-u DIRECTORY","--userdir DIRECTORY","User directory (default #{$userdir})") { |p| $userdir = p }
|
30
|
-
|
35
|
+
opts.on("-n","--no-daemon","Do not send to background") {daemonize = false}
|
36
|
+
opts.version = TMVERSION
|
31
37
|
opts.on_tail("-h","--help","Displays this help message") {
|
32
38
|
puts opt
|
33
39
|
exit(1)
|
@@ -41,6 +47,11 @@ rescue
|
|
41
47
|
end
|
42
48
|
|
43
49
|
$userdir = File.expand_path($userdir)
|
50
|
+
|
51
|
+
$iv = Iview.new $userdir
|
52
|
+
$ab = Adbook.new $userdir
|
53
|
+
$iv.upabook
|
54
|
+
|
44
55
|
# Make user directories if they don't exist
|
45
56
|
begin
|
46
57
|
Dir.mkdir($userdir,0700) if ! File.directory? $userdir
|
@@ -52,18 +63,24 @@ rescue
|
|
52
63
|
end
|
53
64
|
|
54
65
|
# Run webserver, fork and die
|
55
|
-
begin
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
66
|
+
begin
|
67
|
+
if daemonize then
|
68
|
+
Rack::Handler::Mongrel.run(webapp, :Host=>"127.0.0.1", :Port=>theport) { |x|
|
69
|
+
if pid = Kernel.fork then
|
70
|
+
Process.detach(pid)
|
71
|
+
puts "Server running with PID=#{String(pid)}\n"
|
72
|
+
puts "Go to http://127.0.0.1:#{String(theport)}/ to send LaTeX-ed mail!\n"
|
73
|
+
exit(0)
|
74
|
+
end
|
63
75
|
|
64
|
-
|
65
|
-
|
66
|
-
|
76
|
+
$stderr.reopen('/dev/null','w')
|
77
|
+
$stdout.reopen('/dev/null','w')
|
78
|
+
}
|
79
|
+
else
|
80
|
+
Rack::Handler::Mongrel.run(webapp, :Host=>"127.0.0.1", :Port=>theport) { |x|
|
81
|
+
puts "Go to http://127.0.0.1:#{String(theport)}/ to send LaTeX-ed mail!\n"
|
82
|
+
}
|
83
|
+
end
|
67
84
|
rescue
|
68
85
|
STDERR.write "Could not listen on port #{String(theport)}. Make sure texmailer is not already running and that you have permissions.\n"
|
69
86
|
exit(1)
|
@@ -0,0 +1,98 @@
|
|
1
|
+
require 'texmailer/iview'
|
2
|
+
require 'net/imap'
|
3
|
+
|
4
|
+
module Texmailer
|
5
|
+
|
6
|
+
ABVERSION = '0.1'
|
7
|
+
|
8
|
+
class Adbook
|
9
|
+
|
10
|
+
def initialize userdir
|
11
|
+
@afile = userdir+'/abook.dat'
|
12
|
+
if File.readable? @afile
|
13
|
+
data = Marshal.load(File.read(@afile))
|
14
|
+
@ab = data[1] if data[0] == ABVERSION
|
15
|
+
end
|
16
|
+
@ab ||= {}
|
17
|
+
end
|
18
|
+
|
19
|
+
def save
|
20
|
+
s = Marshal.dump([ABVERSION,@ab])
|
21
|
+
File.open(@afile,'w',0600) {|f| f.write s}
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
def search q
|
26
|
+
subs = {}
|
27
|
+
lg = q.length-1
|
28
|
+
query= q.upcase
|
29
|
+
@ab.each_pair { |em,nm|
|
30
|
+
if em[0..lg].upcase == query
|
31
|
+
subs[em] = nm
|
32
|
+
else
|
33
|
+
nm.split(" ").each { |x|
|
34
|
+
if x[0..lg].upcase == query
|
35
|
+
subs[em] = nm
|
36
|
+
break
|
37
|
+
end
|
38
|
+
}
|
39
|
+
end
|
40
|
+
}
|
41
|
+
lst = subs.to_a.collect{ |x|
|
42
|
+
nstr = x[1].strip
|
43
|
+
nstr = '"' + nstr + '" ' if nstr.length > 0
|
44
|
+
'\''+ nstr + '<'+x[0]+'>\''
|
45
|
+
}
|
46
|
+
return lst.join(",")
|
47
|
+
end
|
48
|
+
|
49
|
+
def upemail name,email
|
50
|
+
name = '' if name[0..0] == '='
|
51
|
+
@ab[email] = name if !(ename = @ab[email]) || ename.length < name.length
|
52
|
+
end
|
53
|
+
|
54
|
+
def uptolist em
|
55
|
+
arr = []
|
56
|
+
pfx = ''
|
57
|
+
while m = /[\,\"]/.match(em)
|
58
|
+
if m[0] == ','
|
59
|
+
arr = arr + [(pfx + m.pre_match).strip]
|
60
|
+
pfx = ''
|
61
|
+
em = m.post_match
|
62
|
+
elsif m2 = /[^\\]\"/.match(m.post_match)
|
63
|
+
pfx = pfx + m.pre_match + m[0] + m2.pre_match + m2[0]
|
64
|
+
em = m2.post_match
|
65
|
+
else
|
66
|
+
pfx = pfx + em
|
67
|
+
em = ''
|
68
|
+
end
|
69
|
+
end
|
70
|
+
if (s = (pfx+em).strip).length > 0
|
71
|
+
arr = arr + [s]
|
72
|
+
end
|
73
|
+
arr.each { |x|
|
74
|
+
if m = /.*?\"(.*?[^\\])\"\s*<([^@\s]*?@[^@\s]*?)>/.match(x)
|
75
|
+
upemail m[1].strip, m[2].strip
|
76
|
+
elsif m = /(.*?)<([^@]*?@[^@]*?)>/.match(x)
|
77
|
+
upemail m[1].strip, m[2].strip
|
78
|
+
elsif m = /[^\s\"]*?@[^\s\"]*/.match(x)
|
79
|
+
upemail '', m[0]
|
80
|
+
end
|
81
|
+
}
|
82
|
+
end
|
83
|
+
|
84
|
+
def upenv evs
|
85
|
+
evs.each { |x|
|
86
|
+
[x.from, x.to, x.cc].each { |y|
|
87
|
+
y.each { |z|
|
88
|
+
upemail z.name||'', z.mailbox+"@"+z.host
|
89
|
+
} if y
|
90
|
+
}
|
91
|
+
}
|
92
|
+
save
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
96
|
+
|
97
|
+
|
98
|
+
end
|
data/lib/texmailer/config.rb
CHANGED
@@ -1,5 +1,53 @@
|
|
1
1
|
module Texmailer
|
2
2
|
|
3
|
+
# From list
|
4
|
+
def loadflist
|
5
|
+
f = $userdir + "/flist.cfg"
|
6
|
+
return '' if ! File.readable? f
|
7
|
+
return File.read(f)
|
8
|
+
end
|
9
|
+
|
10
|
+
def getlist flist
|
11
|
+
return [] if ! flist
|
12
|
+
flist.strip!
|
13
|
+
return [] if flist.length == 0
|
14
|
+
a = flist.split(",")
|
15
|
+
a.collect! {|x|
|
16
|
+
x.gsub(/\s/,'').gsub(/[\<\>]/,'')
|
17
|
+
}
|
18
|
+
end
|
19
|
+
|
20
|
+
def saveflist req
|
21
|
+
g = getlist req["flist"]
|
22
|
+
File.open($userdir + "/flist.cfg","w",0600) { |f| f.write g.join(', ')}
|
23
|
+
end
|
24
|
+
|
25
|
+
# IMAP Parameters
|
26
|
+
def loadiparams
|
27
|
+
p = $iv.getsettings
|
28
|
+
|
29
|
+
['host','port','ssl','igcert','user','password'].each { |x|
|
30
|
+
p[x] = String(p[x]) if p[x]
|
31
|
+
p[x] ||= 'false' if x == 'ssl' || x == 'igcert'
|
32
|
+
p[x] ||= ''
|
33
|
+
}
|
34
|
+
return p
|
35
|
+
end
|
36
|
+
|
37
|
+
def saveiparams req
|
38
|
+
p = Hash.new
|
39
|
+
['host','user','password'].each { |x|
|
40
|
+
p[x] = req[x] if req[x]
|
41
|
+
p[x] ||= ''
|
42
|
+
}
|
43
|
+
p['port'] = Integer(req['port']) rescue
|
44
|
+
p['port'] ||= 143
|
45
|
+
p['ssl'] = req['ssl'] ? true : false
|
46
|
+
p['igcert'] = req['igcert'] ? true : false
|
47
|
+
$iv.savesettings p
|
48
|
+
end
|
49
|
+
|
50
|
+
# SMTP Parameters
|
3
51
|
def loadparams
|
4
52
|
p = Hash.new
|
5
53
|
params = [['from',0],['mailfrom', 0],['host', 0],['port', 0],['ssl', 1],['igcert', 1],['auth', 1],['user', 0],['pass', 0]]
|
@@ -11,7 +59,7 @@ module Texmailer
|
|
11
59
|
p[params[i-1][0]] = lns[i-1].sub(/\n/m,'')
|
12
60
|
}
|
13
61
|
end
|
14
|
-
|
62
|
+
|
15
63
|
params.each { |x| p[x[0]] ||= '' }
|
16
64
|
params.each { |x| p[x[0]] == 'false' if x[1] == 1 && p[x[0]] != 'true' }
|
17
65
|
return p
|
@@ -57,12 +105,16 @@ module Texmailer
|
|
57
105
|
def msmtp(req,message)
|
58
106
|
fnm = makerc
|
59
107
|
pr = loadparams
|
108
|
+
svdir = $userdir+"/drafts/"+req["mailid"]
|
109
|
+
|
60
110
|
IO.popen("msmtp -C #{fnm} -t 2> #{$userdir}/msmtplog","w") { |p|
|
61
111
|
[ ["To:","mailto"],["CC:","mailcc"],["BCC:","mailbcc"],["Subject:","subject"] ].each { |x|
|
62
112
|
if req[x[1]] && req[x[1]].length > 0 then
|
63
113
|
p.write x[0] + " " + req[x[1]].gsub(/\n/m,'') + "\n"
|
64
114
|
end
|
65
115
|
}
|
116
|
+
p.write(File.read(svdir+"/exhdr") + "\n") if File.readable? svdir+"/exhdr"
|
117
|
+
|
66
118
|
if pr["from"].length > 0 then
|
67
119
|
p.write "From: #{pr["from"]} <#{pr["mailfrom"]}>\n"
|
68
120
|
end
|
data/lib/texmailer/drafts.rb
CHANGED
@@ -1,7 +1,19 @@
|
|
1
1
|
require 'cgi'
|
2
|
+
require 'time'
|
3
|
+
require 'net/imap'
|
4
|
+
require 'texmailer/config'
|
2
5
|
|
3
6
|
module Texmailer
|
4
7
|
|
8
|
+
def shortem(emlst)
|
9
|
+
emlst = emlst.gsub(/\s+/,' ')
|
10
|
+
r = /(\".*?\")\s*\<([^\>]*)\>/
|
11
|
+
a = emlst.gsub(r) {
|
12
|
+
Regexp.last_match[1].gsub('"','')
|
13
|
+
}
|
14
|
+
return a
|
15
|
+
end
|
16
|
+
|
5
17
|
def savedraft(mailid,req)
|
6
18
|
svdir = $userdir + '/drafts/'+mailid
|
7
19
|
if ! File.directory? svdir
|
@@ -10,8 +22,92 @@ module Texmailer
|
|
10
22
|
["mailto","mailcc","mailbcc","subject","body"].each { |s|
|
11
23
|
File.open(svdir+'/'+s,"w") { |f| f.write req[s] }
|
12
24
|
}
|
25
|
+
["mailto","mailcc","mailbcc"].each { |s|
|
26
|
+
$ab.uptolist req[s]
|
27
|
+
}
|
13
28
|
end
|
14
29
|
|
30
|
+
def makereply(cache,mailid)
|
31
|
+
p = loadparams
|
32
|
+
mfrom = p["mailfrom"]
|
33
|
+
mfrom = [mfrom] + getlist(loadflist)
|
34
|
+
|
35
|
+
evl = cache[0]
|
36
|
+
to = [evl.reply_to[0]] if evl.reply_to.length > 0
|
37
|
+
to ||= [evl.from[0]] if evl.from
|
38
|
+
to ||= []
|
39
|
+
cc = evl.cc
|
40
|
+
cc ||= []
|
41
|
+
|
42
|
+
if to.length == 0
|
43
|
+
to = to + evl.to if evl.to
|
44
|
+
else
|
45
|
+
cc = cc + evl.to if evl.to
|
46
|
+
end
|
47
|
+
|
48
|
+
[to,cc].each { |em|
|
49
|
+
em.collect! { |x|
|
50
|
+
addr = x.mailbox+"@"+x.host
|
51
|
+
mine = false
|
52
|
+
mfrom.each {|y| mine = true if y == addr}
|
53
|
+
if mine
|
54
|
+
nil
|
55
|
+
else
|
56
|
+
if x.name && x.name.length > 0 && x.name[0..0] != '='
|
57
|
+
"\"#{x.name}\" <#{addr}>"
|
58
|
+
else
|
59
|
+
addr
|
60
|
+
end
|
61
|
+
end
|
62
|
+
}
|
63
|
+
em.compact!
|
64
|
+
}
|
65
|
+
|
66
|
+
if evl.from
|
67
|
+
x = evl.from[0]
|
68
|
+
addr = x.mailbox+"@"+x.host
|
69
|
+
addr.gsub!('_','\\_')
|
70
|
+
sender = if x.name && x.name.length > 0 && x.name[0..0] != '='
|
71
|
+
"#{x.name} ``#{addr}\""
|
72
|
+
else
|
73
|
+
addr
|
74
|
+
end
|
75
|
+
end
|
76
|
+
sender ||= 'No Name'
|
77
|
+
|
78
|
+
if evl.subject && evl.subject.length > 0
|
79
|
+
subj = evl.subject
|
80
|
+
subj = "Re: "+subj if subj.length < 3 || subj[0..2].upcase != 'RE:'
|
81
|
+
else
|
82
|
+
subj = "Re: your mail"
|
83
|
+
end
|
84
|
+
|
85
|
+
exhdr = "In-Reply-To: " + evl.message_id if evl.message_id
|
86
|
+
|
87
|
+
svdir = $userdir + '/drafts/'+mailid
|
88
|
+
if ! File.directory? svdir
|
89
|
+
Dir.mkdir(svdir)
|
90
|
+
end
|
91
|
+
|
92
|
+
File.open(svdir+"/mailto","w") {|f| f.write to.join(",")} if to.length > 0
|
93
|
+
File.open(svdir+"/mailcc","w") {|f| f.write cc.join(",")} if cc.length > 0
|
94
|
+
File.open(svdir+"/subject","w") {|f| f.write subj}
|
95
|
+
File.open(svdir+"/exhdr","w") {|f| f.write exhdr} if evl.message_id
|
96
|
+
|
97
|
+
if cache[1].length > 0
|
98
|
+
dstr = 'On ' + evl.date.to_s + ' ' if evl.date
|
99
|
+
dstr ||= ''
|
100
|
+
qtext = "\\reply{\n" + dstr + sender + " wrote:\n\n"
|
101
|
+
if ! cache[1][0]
|
102
|
+
qtext = qtext + "\\begin{verbatim}\n"+cache[1][1]+"\n\\end{verbatim}\n}\n"
|
103
|
+
else
|
104
|
+
qtext = qtext + cache[1][1] + "\n}\n"
|
105
|
+
end
|
106
|
+
File.open(svdir+"/body","w") {|f| f.write "\n\n"+qtext}
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
|
15
111
|
def getpvloc(mailid)
|
16
112
|
pvlnk = '/preview/' + mailid + '/t.html'
|
17
113
|
return pvlnk if File.readable? $userdir+pvlnk
|
@@ -24,19 +120,49 @@ module Texmailer
|
|
24
120
|
Dir.mkdir(pvdir)
|
25
121
|
end
|
26
122
|
|
123
|
+
body = req["body"]
|
124
|
+
|
27
125
|
s = <<EOF
|
28
|
-
\\documentclass{article}
|
126
|
+
\\documentclass[10pt]{article}
|
29
127
|
\\usepackage{amsmath,amssymb}
|
30
128
|
\\setlength{\\parindent}{0in}
|
129
|
+
\\newcommand{\\reply}[1]{\\begin{quotation}
|
130
|
+
\\setlength{\\parindent}{0in}
|
131
|
+
#1
|
132
|
+
\\end{quotation}
|
133
|
+
}
|
31
134
|
\\begin{document}
|
32
|
-
#{
|
135
|
+
#{body}
|
33
136
|
\\end{document}
|
34
137
|
EOF
|
138
|
+
vblocks = []
|
139
|
+
s2 = ''
|
140
|
+
while m = /\\begin\{verbatim\}/.match(s)
|
141
|
+
rstring = (0...15).map{ ('A'..'Z').to_a[rand(26)] }.join + "\n";
|
142
|
+
s2 = s2 + m.pre_match + rstring
|
143
|
+
s = m.post_match
|
144
|
+
if m2 = /\\end\{verbatim\}/.match(s)
|
145
|
+
s = m2.post_match
|
146
|
+
vstring = m2.pre_match
|
147
|
+
else
|
148
|
+
vstring = s
|
149
|
+
s = ''
|
150
|
+
end
|
151
|
+
vstring = CGI.escapeHTML(vstring).gsub("\n","<br>\n")
|
152
|
+
vblocks = vblocks + [ [rstring,vstring]]
|
153
|
+
end
|
154
|
+
s = s2 + s
|
35
155
|
File.open(pvdir + '/t.tex','w') { |f| f.write s}
|
36
156
|
Dir.chdir(pvdir);
|
37
157
|
|
158
|
+
|
38
159
|
if system('latex -interaction=nonstopmode t > ltxrun.txt 2>/dev/null')
|
39
160
|
system('htlatex t > /dev/null 2>/dev/null')
|
161
|
+
html = File.read(pvdir+'/t.html')
|
162
|
+
html.gsub!(/\<body.*?>/m) { |m| m + '<div style="font-family: Helvetica, Arial, sans-serif; font-size: 14px">' }
|
163
|
+
html.gsub!('</body>','</body></div>')
|
164
|
+
vblocks.each { |vb| html.gsub!(vb[0],vb[1]) }
|
165
|
+
File.open(pvdir+'/t.html','w') { |f| f.write html}
|
40
166
|
return true
|
41
167
|
else
|
42
168
|
emsg = File.read(pvdir+'/ltxrun.txt');
|
@@ -69,6 +195,7 @@ EOF
|
|
69
195
|
lst.each {|f| File.delete(f)}
|
70
196
|
Dir.rmdir(svdir)
|
71
197
|
end
|
198
|
+
Dir.chdir($userdir)
|
72
199
|
|
73
200
|
end
|
74
201
|
|
@@ -90,13 +217,49 @@ EOF
|
|
90
217
|
subject ||= ''
|
91
218
|
subject = '(No Subject)' if subject.length == 0
|
92
219
|
|
93
|
-
[mid,CGI.escapeHTML(mailto),CGI.escapeHTML(subject),drtime]
|
220
|
+
[mid,CGI.escapeHTML(shortem(mailto)),CGI.escapeHTML(subject),drtime]
|
94
221
|
end
|
95
222
|
}
|
96
223
|
s.compact!
|
97
224
|
s.sort! {|a,b| b[3] <=> a[3]}
|
98
225
|
return s
|
99
226
|
end
|
227
|
+
|
228
|
+
|
229
|
+
def nicecache cache
|
230
|
+
msgs = cache.sort { |m,l| Time.parse(l[1][0].date) <=> Time.parse(m[1][0].date) }
|
231
|
+
msgs.collect! { |x|
|
232
|
+
uid = x[0]
|
233
|
+
evl = x[1][0]
|
234
|
+
bdy = x[1][1][1]
|
235
|
+
|
236
|
+
from = evl.from[0].name if evl.from[0].name && evl.from[0].name.length > 0 && evl.from[0].name[0..0] != '='
|
237
|
+
from ||= (evl.from[0].mailbox + "@" + evl.from[0].host) if evl.from[0].mailbox && evl.from[0].host
|
238
|
+
from ||= "Unknown Sender"
|
239
|
+
if from.length > 26
|
240
|
+
from = from.split(/[@ ]/)[0]
|
241
|
+
end
|
242
|
+
|
243
|
+
maxsb = 95
|
244
|
+
subject = evl.subject.gsub(/[\s]+/m, ' ').strip if evl.subject
|
245
|
+
subject ||= '(no subject)'
|
246
|
+
bdy.gsub!(/^\>.*/,'')
|
247
|
+
if bdy.length > 0 && subject.length < maxsb && m = /[A-Za-z]/.match(bdy)
|
248
|
+
bdy = m[0] + m.post_match
|
249
|
+
bdy = bdy.gsub!(/\s+/m,' ').strip
|
250
|
+
if bdy.length > maxsb-subject.length
|
251
|
+
bdy = bdy[0..(maxsb-subject.length-1)]
|
252
|
+
end
|
253
|
+
subject = CGI.escapeHTML(subject)+'<span style="color:#999999">: '+CGI.escapeHTML(bdy)+"</span>"
|
254
|
+
else
|
255
|
+
subject = CGI.escapeHTML(subject)
|
256
|
+
end
|
257
|
+
|
258
|
+
sdate = Time.parse(evl.date).to_nice_s
|
259
|
+
[uid,from,subject,sdate]
|
260
|
+
}
|
261
|
+
end
|
262
|
+
|
100
263
|
end
|
101
264
|
|
102
265
|
## Borrowed from sup
|