--- /dev/null
+#!/usr/bin/env ruby
+
+#
+# snarf_mail.rb
+#
+# Copies mail from an IMAP account
+#
+# Handy reference:
+# http://ruby-doc.org/stdlib/libdoc/net/imap/rdoc/index.html
+#
+
+require 'net/imap'
+require 'optparse'
+require 'ostruct'
+
+class MyOptions
+ def self.parse(args)
+ opts = OpenStruct.new
+ opts.mailboxes = Array.new
+
+ # Fill in $opts values
+ parser = OptionParser.new do |myparser|
+ myparser.banner = "Usage: #{ File.basename($0) } [opts]"
+ myparser.separator("Specific options:")
+ myparser.on("--username USERNAME", "-u",
+ "Email account to fetch. (example: \
+RareCactus@gmail.com)") do |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
+ 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
+ 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
+ end
+ myparser.on("--server [SERVER]", "-s",
+ "Email server to use") do |u|
+ opts.server = u
+ end
+ end
+
+ parser.parse!(args)
+ raise "must specify an action" unless opts.action
+ raise "must give a username" unless opts.username
+ raise "must give a server" unless opts.server
+ return opts
+ end
+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
+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]/)
+
+ # Pad numeric uids out to 6 digits
+ return sprintf("%006d", uid)
+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}"
+end
+
+# MAIN
+begin
+ $opts = MyOptions.parse(ARGV)
+rescue Exception => msg
+ $stderr.print("#{msg}.\nType --help to see usage information.\n")
+ exit 1
+end
+
+password = get_password("Please enter the password for #{$opts.username}:")
+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
+when :snarf
+ $opts.mailboxes.each do |mailbox|
+ snarf_mailbox(imap, mailbox)
+ end
+else
+ raise "unknown action #{$opts.action}"
+end
+imap.logout()
+imap.disconnect()
+exit 0