=begin Feed2Imap - RSS/Atom Aggregator uploading to an IMAP Server Copyright (c) 2005 Lucas Nussbaum This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA =end # Imap connection handling require 'net/imap' begin require 'openssl' rescue LoadError end require 'uri' # This class is a container of IMAP accounts. # Thanks to it, accounts are re-used : several feeds # using the same IMAP account will create only one # IMAP connection. class ImapAccounts < Hash def add_account(uri) u = URI::Generic::build({ :scheme => uri.scheme, :userinfo => uri.userinfo, :host => uri.host, :port => uri.port }) if not include?(u) ac = ImapAccount::new(u) self[u] = ac end return self[u] end end # This class is an IMAP account, with the given fd # once the connection has been established class ImapAccount attr_reader :uri @@no_ssl_verify = false def ImapAccount::no_ssl_verify=(v) @@no_ssl_verify = v end def initialize(uri) @uri = uri @existing_folders = [] self end # connects to the IMAP server # raises an exception if it fails def connect port = 143 usessl = false if uri.scheme == 'imap' port = 143 usessl = false elsif uri.scheme == 'imaps' port = 993 usessl = true else raise "Unknown scheme: #{uri.scheme}" end # use given port if port given port = uri.port if uri.port @connection = Net::IMAP::new(uri.host, port, usessl, nil, !@@no_ssl_verify) user, password = URI::unescape(uri.userinfo).split(':',2) @connection.login(user, password) self end # disconnect from the IMAP server def disconnect if @connection @connection.logout @connection.disconnect end end # tests if the folder exists and create it if not def create_folder_if_not_exists(folder) return if @existing_folders.include?(folder) if @connection.list('', folder).nil? @connection.create(folder) @connection.subscribe(folder) end @existing_folders << folder end # Put the mail in the given folder # You should check whether the folder exist first. def putmail(folder, mail, date = Time::now) create_folder_if_not_exists(folder) @connection.append(folder, mail.gsub(/\n/, "\r\n"), nil, date) end # update a mail def updatemail(folder, mail, id, date = Time::now, reupload_if_updated = true) create_folder_if_not_exists(folder) @connection.select(folder) searchres = @connection.search(['HEADER', 'Message-Id', id]) flags = nil if searchres.length > 0 # we get the flags from the first result and delete everything flags = @connection.fetch(searchres[0], 'FLAGS')[0].attr['FLAGS'] searchres.each { |m| @connection.store(m, "+FLAGS", [:Deleted]) } @connection.expunge flags -= [ :Recent ] # avoids errors with dovecot elsif not reupload_if_updated # mail not present, and we don't want to re-upload it return end @connection.append(folder, mail.gsub(/\n/, "\r\n"), flags, date) end # convert to string def to_s u2 = uri.clone u2.password = 'PASSWORD' u2.to_s end # remove mails in a folder according to a criteria def cleanup(folder, dryrun = false) puts "-- Considering #{folder}:" @connection.select(folder) a = ['SEEN', 'NOT', 'FLAGGED', 'BEFORE', (Date::today - 3).strftime('%d-%b-%Y')] todel = @connection.search(a) todel.each do |m| f = @connection.fetch(m, "FULL") d = f[0].attr['INTERNALDATE'] s = f[0].attr['ENVELOPE'].subject if s =~ /^=\?utf-8\?b\?/ s = Base64::decode64(s.gsub(/^=\?utf-8\?b\?(.*)\?=$/, '\1')).force_encoding('utf-8') elsif s =~ /^=\?iso-8859-1\?b\?/ s = Base64::decode64(s.gsub(/^=\?iso-8859-1\?b\?(.*)\?=$/, '\1')).force_encoding('iso-8859-1').encode('utf-8') end if dryrun puts "To remove: #{s} (#{d})" else puts "Removing: #{s} (#{d})" @connection.store(m, "+FLAGS", [:Deleted]) end end puts "-- Deleted #{todel.length} messages." if not dryrun @connection.expunge end return todel.length end end