names_to_numbers.rb: add --starting-number
[cmccabe-bin] / snarf_mail.rb
index bfc0873..76c831c 100755 (executable)
@@ -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()