snarf_mail: Fix total count of all messages
[cmccabe-bin] / snarf_mail.rb
1 #!/usr/bin/env ruby
2
3 #
4 # snarf_mail.rb
5 #
6 # Copies mail from an IMAP account
7 #
8 # Handy reference:
9 # http://ruby-doc.org/stdlib/libdoc/net/imap/rdoc/index.html
10 #
11
12 require 'net/imap'
13 require 'optparse'
14 require 'ostruct'
15
16 class MyOptions
17   def self.parse(args)
18     opts = OpenStruct.new
19                 opts.mailboxes = Array.new
20
21     # Fill in $opts values
22     parser = OptionParser.new do |myparser|
23       myparser.banner = "Usage: #{ File.basename($0) } [opts]"
24       myparser.separator("Specific options:")
25       myparser.on("--username USERNAME", "-u",
26               "Email account to fetch. (example: \
27 RareCactus@gmail.com)") do |u|
28                                 opts.username = u
29       end
30       myparser.on("--list-folders", "-l",
31               "List the IMAP folders that are present.") do |a|
32                                 raise "can only specify one action" if (opts.action)
33                                 opts.action = :list
34       end
35       myparser.on("--snarf", "-S",
36                                                         "Copy mail to the current directory.") do |a|
37                                 raise "can only specify one action" if (opts.action)
38                                 opts.action = :snarf
39                         end
40       myparser.on("--box [MAILBOX]", "-b",
41               "Act on a given mailbox. You may specify -b more than once for \
42 multiple mailboxes.") do |a|
43                                 opts.mailboxes << a
44       end
45       myparser.on("--server [SERVER]", "-s",
46                                                         "Email server to use") do |u|
47                                 opts.server = u
48                         end
49     end
50
51     parser.parse!(args)
52     raise "must specify an action" unless opts.action
53     raise "must give a username" unless opts.username
54     raise "must give a server" unless opts.server
55     return opts
56   end
57 end
58
59 # Get a password from STDIN without echoing it.
60 # This is kind of ugly, but it does work.
61 def get_password(prompt)
62         shell_cmds = 'stty -echo && read password && echo ${password}'
63         printf "#{prompt}"
64         STDOUT.flush
65         pass = ""
66         pipe = IO.popen(shell_cmds, "r") do |pipe|
67                 pass = pipe.read
68         end
69         echo_status = $?.exitstatus
70         system("stty sane")
71         puts
72         if (echo_status != 0) then
73                 raise "get_password: error executing: #{shell_cmds}"
74         end
75         return pass.chomp
76 end
77
78 def format_uid(uid)
79         # We don't know how to deal with non-numeric UIDs. Best just to leave them
80         # alone.
81         return uid if (uid =~ /[^0123456789]/)
82
83         # Pad numeric uids out to 6 digits
84         return sprintf("%006d", uid)
85 end
86
87 def snarf_mailbox(imap, mailbox)
88         imap.select(mailbox)
89         count = 0
90   total_count = 0
91         imap.search(["NOT", "DELETED"]).each do |message_id|
92                 data = imap.fetch(message_id, [ "UID", "RFC822.HEADER", "RFC822.TEXT" ])
93                 a = data[0].attr
94                 filename = "#{mailbox}#{format_uid(a["UID"])}"
95                 fp = File.open(filename, 'w')
96                 fp.write(a["RFC822.HEADER"])
97                 fp.write(a["RFC822.TEXT"])
98                 fp.close
99                 count = count + 1
100     total_count = total_count + 1
101                 if (count > 10) then
102                         count = 0
103                         printf(".")
104                         STDOUT.flush()
105                 end
106         end
107         puts "fetched #{total_count} messages from #{mailbox}"
108 end
109
110 # MAIN
111 begin
112   $opts = MyOptions.parse(ARGV)
113 rescue Exception => msg
114   $stderr.print("#{msg}.\nType --help to see usage information.\n")
115   exit 1
116 end
117
118 password = get_password("Please enter the password for #{$opts.username}:")
119 imap = Net::IMAP.new($opts.server, 993, true)
120 imap.login($opts.username, password)
121 case ($opts.action)
122 when :list
123         imap.list("", "*").each do |mbl|
124                 puts "#{mbl.name}"
125         end
126 when :snarf
127         $opts.mailboxes.each do |mailbox|
128                 snarf_mailbox(imap, mailbox)
129         end
130 else
131         raise "unknown action #{$opts.action}"
132 end
133 imap.logout()
134 imap.disconnect()
135 exit 0