6 # Copies mail from an IMAP account
9 # http://ruby-doc.org/stdlib/libdoc/net/imap/rdoc/index.html
20 opts.mailboxes = Array.new
23 # Fill in $opts values
24 parser = OptionParser.new do |myparser|
25 myparser.banner = "Usage: #{ File.basename($0) } [opts]"
26 myparser.separator("Specific options:")
27 myparser.on("--delete POLICY", "-d",
28 "Set delete policy to 'none' or 'old'. Default is 'none'.") do |d|
31 myparser.on("--username USERNAME", "-u",
32 "Email account to fetch. (example: \
33 RareCactus@gmail.com)") do |u|
36 myparser.on("--list-folders", "-l",
37 "List the IMAP folders that are present.") do |a|
38 raise "can only specify one action" if (opts.action)
41 myparser.on("--snarf", "-S",
42 "Copy mail to the current directory.") do |a|
43 raise "can only specify one action" if (opts.action)
46 myparser.on("--box [MAILBOX]", "-b",
47 "Act on a given mailbox. You may specify -b more than once for \
48 multiple mailboxes.") do |a|
51 myparser.on("--server [SERVER]", "-s",
52 "Email server to use") do |u|
58 raise "must specify an action" unless opts.action
59 raise "must give a username" unless opts.username
60 raise "must give a server" unless opts.server
65 # Get a password from STDIN without echoing it.
66 # This is kind of ugly, but it does work.
67 def get_password(prompt)
68 shell_cmds = 'stty -echo && read password && echo ${password}'
72 pipe = IO.popen(shell_cmds, "r") do |pipe|
75 echo_status = $?.exitstatus
78 if (echo_status != 0) then
79 raise "get_password: error executing: #{shell_cmds}"
85 # We don't know how to deal with non-numeric UIDs. Best just to leave them
87 return uid if (uid =~ /[^0123456789]/)
89 # Pad numeric uids out to 6 digits
90 return sprintf("%006d", uid)
97 def get_sanitized_email_name(mailbox, arr)
101 return "#{msn}_#{format_date(arr["INTERNALDATE"])}_#{format_uid(arr["UID"])}"
104 def write_email_to_disk(mailbox, data)
106 filename = get_sanitized_email_name(mailbox, arr)
107 fp = File.open(filename, 'w')
108 fp.write(arr["RFC822.HEADER"])
109 fp.write(arr["RFC822.TEXT"])
113 def snarf_mailbox(imap, mailbox)
117 searchterms = [ "NOT", "DELETED" ]
118 if $opts.delete == "old"
119 t = Date.today() - 365
120 time_str = t.strftime("%e-%b-%Y")
121 searchterms << "BEFORE" << time_str
122 prequel = "fetched and deleted: "
123 elsif $opts.delete == "none"
124 prequel = "fetched: "
126 raise "expected one of 'old', 'none' for delete argument."
131 msg_seqnos = Array.new
134 imap.search(searchterms).each do |message_id|
135 if (first_time == true) then
136 # Print a dot immediately after making first contact with the server.
137 # It is reassuring to the user.
142 data = imap.fetch(message_id,
143 [ "INTERNALDATE", "UID", "RFC822.HEADER", "RFC822.TEXT" ])
144 write_email_to_disk(mailbox, data)
146 full_count = full_count + 1
147 msg_seqnos << data[0].seqno.to_i
148 #break if (count > 20)
151 puts "#{prequel} #{full_count} messages from #{mailbox}"
155 # Print out a dot to signify progress
159 if ($opts.delete != "none"):
161 imap.store(msg_seqnos, "+FLAGS", [:Deleted])
169 $opts = MyOptions.parse(ARGV)
170 rescue Exception => msg
171 $stderr.print("#{msg}.\nType --help to see usage information.\n")
175 password = get_password("Please enter the password for #{$opts.username}:")
176 imap = Net::IMAP.new($opts.server, 993, true)
177 imap.login($opts.username, password)
180 imap.list("", "*").each do |mbl|
184 $opts.mailboxes.each do |mailbox|
185 snarf_mailbox(imap, mailbox)
188 raise "unknown action #{$opts.action}"