X-Git-Url: http://club.cc.cmu.edu/~cmccabe/cgi-bin/gitweb.cgi?a=blobdiff_plain;f=snarf_mail.rb;h=76c831ce8c0d2b521e1334d0b18cb3b9f0a19fc0;hb=6110c295719a4aa2809aa74f5037880a0b1f3db8;hp=bfc0873eaa7d227ca6e5810f09572e8a08962234;hpb=bd4968397296402e5eff92615de39f60c695167e;p=cmccabe-bin diff --git a/snarf_mail.rb b/snarf_mail.rb index bfc0873..76c831c 100755 --- a/snarf_mail.rb +++ b/snarf_mail.rb @@ -9,6 +9,7 @@ # http://ruby-doc.org/stdlib/libdoc/net/imap/rdoc/index.html # +require 'date' require 'net/imap' require 'optparse' require 'ostruct' @@ -16,36 +17,41 @@ require 'ostruct' class MyOptions def self.parse(args) opts = OpenStruct.new - opts.mailboxes = Array.new + opts.mailboxes = Array.new + opts.delete = "none" # Fill in $opts values parser = OptionParser.new do |myparser| myparser.banner = "Usage: #{ File.basename($0) } [opts]" myparser.separator("Specific options:") + myparser.on("--delete POLICY", "-d", + "Set delete policy to 'none' or 'old'. Default is 'none'.") do |d| + opts.delete = d + end myparser.on("--username USERNAME", "-u", "Email account to fetch. (example: \ RareCactus@gmail.com)") do |u| - opts.username = u + opts.username = u end myparser.on("--list-folders", "-l", "List the IMAP folders that are present.") do |a| - raise "can only specify one action" if (opts.action) - opts.action = :list + raise "can only specify one action" if (opts.action) + opts.action = :list end myparser.on("--snarf", "-S", - "Copy mail to the current directory.") do |a| - raise "can only specify one action" if (opts.action) - opts.action = :snarf - end + "Copy mail to the current directory.") do |a| + raise "can only specify one action" if (opts.action) + opts.action = :snarf + end myparser.on("--box [MAILBOX]", "-b", "Act on a given mailbox. You may specify -b more than once for \ multiple mailboxes.") do |a| - opts.mailboxes << a + opts.mailboxes << a end myparser.on("--server [SERVER]", "-s", - "Email server to use") do |u| - opts.server = u - end + "Email server to use") do |u| + opts.server = u + end end parser.parse!(args) @@ -59,50 +65,103 @@ end # Get a password from STDIN without echoing it. # This is kind of ugly, but it does work. def get_password(prompt) - shell_cmds = 'stty -echo && read password && echo ${password}' - printf "#{prompt}" - STDOUT.flush - pass = "" - pipe = IO.popen(shell_cmds, "r") do |pipe| - pass = pipe.read - end - echo_status = $?.exitstatus - system("stty sane") - puts - if (echo_status != 0) then - raise "get_password: error executing: #{shell_cmds}" - end - return pass.chomp + shell_cmds = 'stty -echo && read password && echo ${password}' + printf "#{prompt}" + STDOUT.flush + pass = "" + pipe = IO.popen(shell_cmds, "r") do |pipe| + pass = pipe.read + end + echo_status = $?.exitstatus + system("stty sane") + puts + if (echo_status != 0) then + raise "get_password: error executing: #{shell_cmds}" + end + return pass.chomp end def format_uid(uid) - # We don't know how to deal with non-numeric UIDs. Best just to leave them - # alone. - return uid if (uid =~ /[^0123456789]/) + # We don't know how to deal with non-numeric UIDs. Best just to leave them + # alone. + return uid if (uid =~ /[^0123456789]/) + + # Pad numeric uids out to 6 digits + return sprintf("%006d", uid) +end + +def format_date(date) + date.gsub!(' ', '_') +end - # Pad numeric uids out to 6 digits - return sprintf("%006d", uid) +def get_sanitized_email_name(mailbox, arr) + msn = mailbox.dup + msn.gsub!(' ', '_') + msn.gsub!('/', '.') + return "#{msn}_#{format_date(arr["INTERNALDATE"])}_#{format_uid(arr["UID"])}" +end + +def write_email_to_disk(mailbox, data) + arr = data[0].attr + filename = get_sanitized_email_name(mailbox, arr) + fp = File.open(filename, 'w') + fp.write(arr["RFC822.HEADER"]) + fp.write(arr["RFC822.TEXT"]) + fp.close end def snarf_mailbox(imap, mailbox) - imap.select(mailbox) - count = 0 - imap.search(["NOT", "DELETED"]).each do |message_id| - data = imap.fetch(message_id, [ "UID", "RFC822.HEADER", "RFC822.TEXT" ]) - a = data[0].attr - filename = "#{mailbox}#{format_uid(a["UID"])}" - fp = File.open(filename, 'w') - fp.write(a["RFC822.HEADER"]) - fp.write(a["RFC822.TEXT"]) - fp.close - count = count + 1 - if (count > 10) then - count = 0 - printf(".") - STDOUT.flush() - end - end - puts "fetched #{count} messages from #{mailbox}" + full_count = 0 + first_time = true + + searchterms = [ "NOT", "DELETED" ] + if $opts.delete == "old" + t = Date.today() - 365 + time_str = t.strftime("%e-%b-%Y") + searchterms << "BEFORE" << time_str + prequel = "fetched and deleted: " + elsif $opts.delete == "none" + prequel = "fetched: " + else + raise "expected one of 'old', 'none' for delete argument." + end + + while true + count = 0 + msg_seqnos = Array.new + + imap.select(mailbox) + imap.search(searchterms).each do |message_id| + if (first_time == true) then + # Print a dot immediately after making first contact with the server. + # It is reassuring to the user. + printf(".") + STDOUT.flush() + first_time = false + end + data = imap.fetch(message_id, + [ "INTERNALDATE", "UID", "RFC822.HEADER", "RFC822.TEXT" ]) + write_email_to_disk(mailbox, data) + count = count + 1 + full_count = full_count + 1 + msg_seqnos << data[0].seqno.to_i + #break if (count > 20) + end + if (count == 0) then + puts "#{prequel} #{full_count} messages from #{mailbox}" + return + end + + # Print out a dot to signify progress + printf(".") + STDOUT.flush() + + if ($opts.delete != "none"): + # Delete messages + imap.store(msg_seqnos, "+FLAGS", [:Deleted]) + imap.expunge + end + end end # MAIN @@ -118,15 +177,15 @@ imap = Net::IMAP.new($opts.server, 993, true) imap.login($opts.username, password) case ($opts.action) when :list - imap.list("", "*").each do |mbl| - puts "#{mbl.name}" - end + imap.list("", "*").each do |mbl| + puts "#{mbl.name}" + end when :snarf - $opts.mailboxes.each do |mailbox| - snarf_mailbox(imap, mailbox) - end + $opts.mailboxes.each do |mailbox| + snarf_mailbox(imap, mailbox) + end else - raise "unknown action #{$opts.action}" + raise "unknown action #{$opts.action}" end imap.logout() imap.disconnect()