Object
IMAPProcessor is a client for processing messages on an IMAP server.
Subclasses need to provide:
The version of IMAPProcessor you are using
Adds a —move option to the option parser which stores the destination mailbox in the MoveTo option. Call this from a subclass’ process_args method.
# File lib/imap_processor.rb, line 71 def self.add_move @@options[:MoveTo] = nil @@extra_options << proc do |opts, options| opts.on( "--move=MAILBOX", "Mailbox to move message to", "Default: #{options[:MoveTo].inspect}", "Options file name: :MoveTo") do |mailbox| options[:MoveTo] = mailbox end end end
Handles the basic settings from options including verbosity, mailboxes to process, and Net::IMAP::debug
# File lib/imap_processor.rb, line 309 def initialize(options) @options = options @verbose = options[:Verbose] @boxes = options[:Boxes] Net::IMAP.debug = options[:Debug] end
Handles processing of args loading defaults from a file in ~ based on processor_file. Extra option defaults can be specified by required_options. Yields an option parser instance to add new OptionParser options to:
class MyProcessor < IMAPProcessor
def self.process_args(args)
required_options = {
:MoveTo => [nil, "MoveTo not set"],
}
super __FILE__, args, required_options do |opts, options|
opts.banner << "Explain my_processor's executable"
opts.on( "--move=MAILBOX",
"Mailbox to move message to",
"Default: #{options[:MoveTo].inspect}",
"Options file name: :MoveTo") do |mailbox|
options[:MoveTo] = mailbox
end
end
end
NOTE: You can add a —move option using ::add_move
# File lib/imap_processor.rb, line 110 def self.process_args(processor_file, args, required_options = {}) # :yield: OptionParser opts_file_name = File.basename processor_file, '.rb' opts_file_name = "imap_#{opts_file_name}" unless opts_file_name =~ /^imap_/ opts_file = File.expand_path "~/.#{opts_file_name}" options = @@options.dup if required_options then required_options.each do |option, (default, message)| raise ArgumentError, "required_options message is missing for #{option}" if default.nil? and message.nil? end end if File.exist? opts_file then unless File.stat(opts_file).mode & 077 == 0 then $stderr.puts "WARNING! #{opts_file} is group/other readable or writable!" $stderr.puts "WARNING! I'm not doing a thing until you fix it!" exit 1 end options.merge! YAML.load_file(opts_file) end options[:SSL] = true unless options.key? :SSL options[:Username] ||= ENV['USER'] options[:Root] ||= nil options[:Verbose] ||= false options[:Debug] ||= false required_options.each do |k,(v,m)| options[k] ||= v end op = OptionParser.new do |opts| opts.program_name = File.basename $0 opts.banner = "Usage: #{opts.program_name} [options]\n\n" opts.separator '' opts.separator 'Connection options:' opts.on("-H", "--host HOST", "IMAP server host", "Default: #{options[:Host].inspect}", "Options file name: :Host") do |host| options[:Host] = host end opts.on("-P", "--port PORT", "IMAP server port", "Default: The correct port SSL/non-SSL mode", "Options file name: :Port") do |port| options[:Port] = port end opts.on("-s", "--[no-]ssl", "Use SSL for IMAP connection", "Default: #{options[:SSL].inspect}", "Options file name: :SSL") do |ssl| options[:SSL] = ssl end opts.on( "--[no-]debug", "Display Net::IMAP debugging info", "Default: #{options[:Debug].inspect}", "Options file name: :Debug") do |debug| options[:Debug] = debug end opts.separator '' opts.separator 'Login options:' opts.on("-u", "--username USERNAME", "IMAP username", "Default: #{options[:Username].inspect}", "Options file name: :Username") do |username| options[:Username] = username end opts.on("-p", "--password PASSWORD", "IMAP password", "Default: Read from ~/.#{opts_file_name}", "Options file name: :Password") do |password| options[:Password] = password end authenticators = Net::IMAP.send :class_variable_get, :@@authenticators auth_types = authenticators.keys.sort.join ', ' opts.on("-a", "--auth AUTH", auth_types, "IMAP authentication type override", "Authentication type will be auto-", "discovered", "Default: #{options[:Auth].inspect}", "Options file name: :Auth") do |auth| options[:Auth] = auth end opts.separator '' opts.separator "IMAP options:" opts.on("-r", "--root ROOT", "Root of mailbox hierarchy", "Default: #{options[:Root].inspect}", "Options file name: :Root") do |root| options[:Root] = root end opts.on("-b", "--boxes BOXES", Array, "Comma-separated list of mailbox names", "to search", "Default: #{options[:Boxes].inspect}", "Options file name: :Boxes") do |boxes| options[:Boxes] = boxes end opts.on("-v", "--[no-]verbose", "Be verbose", "Default: #{options[:Verbose].inspect}", "Options file name: :Verbose") do |verbose| options[:Verbose] = verbose end opts.on("-q", "--quiet", "Be quiet") do options[:Verbose] = false end if block_given? then opts.separator '' opts.separator "#{self} options:" yield opts, options if block_given? end @@extra_options.each do |block| block.call opts, options end opts.separator '' opts.banner << "\nOptions may also be set in the options file ~/.\#{opts_file_name}\n\nExample ~/.\#{opts_file_name}:\n\\tHost=mail.example.com\n\\tPassword=my password\n\n" end op.parse! args options[:Port] ||= options[:SSL] ? 993 : 143 if options[:Host].nil? or options[:Password].nil? or options[:Boxes].nil? or required_options.any? { |k,(v,m)| options[k].nil? } then $stderr.puts op $stderr.puts $stderr.puts "Host name not set" if options[:Host].nil? $stderr.puts "Password not set" if options[:Password].nil? $stderr.puts "Boxes not set" if options[:Boxes].nil? required_options.each do |option_name, (option_value, missing_message)| $stderr.puts missing_message if options[option_name].nil? end exit 1 end return options end
Sets up an IMAP processor’s options then calls its #run method.
# File lib/imap_processor.rb, line 288 def self.run(args = ARGV, &block) options = process_args args client = new(options, &block) client.run rescue Interrupt exit rescue SystemExit raise rescue Exception => e $stderr.puts "Failed to finish with exception: #{e.class}:#{e.message}" $stderr.puts "\t#{e.backtrace.join "\n\t"}" exit 1 ensure client.imap.logout if client and client.imap end
Connects to IMAP server host at port using ssl if ssl is true then logs in as username with password. IMAPProcessor is only known to work with PLAIN auth on SSL sockets.
Returns a Connection object.
# File lib/imap_processor.rb, line 323 def connect(host = @options[:Host], port = @options[:Port], ssl = @options[:SSL], username = @options[:Username], password = @options[:Password], auth = @options[:Auth]) # :yields: Connection imap = Net::IMAP.new host, port, ssl, nil, false log "Connected to imap://#{host}:#{port}/" capability = imap.capability log "Capabilities: #{capability.join ', '}" auth_caps = capability.select { |c| c =~ /^AUTH/ } if auth.nil? then raise "Couldn't find a supported auth type" if auth_caps.empty? auth = auth_caps.first.sub(/AUTH=/, '') end auth = auth.upcase log "Trying #{auth} authentication" imap.authenticate auth, username, password log "Logged in as #{username}" connection = Connection.new imap, capability if block_given? then begin yield connection ensure connection.imap.logout end else return connection end end
Create the mailbox name if it doesn’t exist. Note that this will SELECT the mailbox if it exists.
# File lib/imap_processor.rb, line 365 def create_mailbox name log "LIST #{name}" list = imap.list '', name return if list log "CREATE #{name}" imap.create name end
Delete and expunge the specified uids.
# File lib/imap_processor.rb, line 376 def delete_messages uids, expunge = true log "DELETING [...#{uids.size} uids]" imap.store uids, '+FLAGS.SILENT', [:Deleted] if expunge then log "EXPUNGE" imap.expunge end end
Yields each uid and message as a TMail::Message for uids of MIME type type.
If there’s an exception raised during handling a message the subject, message-id and inspected body are logged.
If the block returns nil or false, the message is considered skipped and its uid is not returned in the uid list. (Hint: next false unless …)
Returns the uids of successfully handled messages.
# File lib/imap_processor.rb, line 397 def each_message(uids, type) # :yields: TMail::Mail parts = mime_parts uids, type uids = [] each_part parts, true do |uid, message| skip = false mail = TMail::Mail.parse message begin success = yield uid, mail uids << uid if success rescue => e log e.message puts "\t#{e.backtrace.join "\n\t"}" unless $DEBUG # backtrace at bottom log "Subject: #{mail.subject}" log "Message-Id: #{mail.message_id}" p mail.body if verbose? raise if $DEBUG end end uids end
Yields each message part from parts. If header is true, a complete message is yielded, appropriately joined for use with TMail::Mail.
# File lib/imap_processor.rb, line 429 def each_part(parts, header = false) # :yields: uid, message parts.each do |uid, section| sequence = ["BODY[#{section}]"] sequence.unshift "BODY[#{section}.MIME]" unless section == 'TEXT' sequence.unshift 'BODY[HEADER]' if header body = imap.fetch(uid, sequence).first sequence = sequence.map { |item| body.attr[item] } unless section == 'TEXT' and header then sequence[0].sub!(/\r\n\z/, '') end yield uid, sequence.join end end
Logs message to $stderr if verbose
# File lib/imap_processor.rb, line 450 def log(message) return unless @verbose $stderr.puts "# #{message}" end
Retrieves the BODY data item name for the mime_type part from messages uids. Returns an array of uid/part pairs. If no matching part with mime_type is found the uid is omitted.
Returns an Array of uid, section pairs.
Use a subsequent Net::IMAP#fetch to retrieve the selected part.
# File lib/imap_processor.rb, line 464 def mime_parts(uids, mime_type) media_type, subtype = mime_type.upcase.split('/', 2) structures = imap.fetch uids, 'BODYSTRUCTURE' structures.zip(uids).map do |body, uid| section = nil structure = body.attr['BODYSTRUCTURE'] case structure when Net::IMAP::BodyTypeMultipart then parts = structure.parts section = parts.each_with_index do |part, index| break index if part.media_type == media_type and part.subtype == subtype end next unless Integer === section when Net::IMAP::BodyTypeText, Net::IMAP::BodyTypeBasic then section = 'TEXT' if structure.media_type == media_type and structure.subtype == subtype end [uid, section] end.compact end
Move the specified uids to a new destination then delete and expunge them. Creates the destination mailbox if it doesn’t exist.
# File lib/imap_processor.rb, line 496 def move_messages uids, destination, expunge = true return if uids.empty? log "COPY [...#{uids.size} uids]" begin imap.copy uids, destination rescue Net::IMAP::NoResponseError => e # ruby-lang bug #1713 #raise unless e.response.data.code.name == 'TRYCREATE' create_mailbox destination imap.copy uids, destination end delete_messages uids, expunge end
Displays Date, Subject and Message-Id from messages in uids
# File lib/imap_processor.rb, line 515 def show_messages(uids) return if uids.nil? or (Array === uids and uids.empty?) fetch_data = 'BODY.PEEK[HEADER.FIELDS (DATE SUBJECT MESSAGE-ID)]' messages = imap.fetch uids, fetch_data fetch_data.sub! '.PEEK', '' # stripped by server messages.each do |res| puts res.attr[fetch_data].delete("\r") end end
Disabled; run with --debug to generate this.
Generated with the Darkfish Rdoc Generator 1.1.6.