diff --git a/spec/imap_spec.cr b/spec/imap_spec.cr index cd7ffb9..4cd35b4 100644 --- a/spec/imap_spec.cr +++ b/spec/imap_spec.cr @@ -5,11 +5,10 @@ describe Imap do it "should count emails in mailbox" do imap = Imap::Client.new(host: "imap.gmail.com", port: 993, username: "***", password: "***") - mailboxes = imap.get_mailboxes + mailboxes = imap.list if mailboxes.size > 0 - mailbox = mailboxes[0] - imap.set_mailbox(mailbox) - message_count = imap.get_message_count + mailbox = mailboxes.first + message_count = imap.status(mailbox, ["MESSAGES"])["MESSAGES"] puts "There are #{message_count} message in #{mailbox}" end imap.close diff --git a/src/imap.cr b/src/imap.cr index 7515676..726982b 100644 --- a/src/imap.cr +++ b/src/imap.cr @@ -1,12 +1,16 @@ -require "./imap/*" require "openssl" require "logger" +require "./imap/*" + module Imap class Client + CAPS_UTF8 = ImmutableSet.new("UTF8=ALL", "UTF8=ONLY", "UTF8=ACCEPT") + @caps : Set(String) @socket : TCPSocket | OpenSSL::SSL::Socket::Client | Nil = nil @logger : Logger + @idling = false def initialize(host = "imap.gmail.com", port = 993, username = "", password = "", loglevel = Logger::ERROR) @logger = Logger.new(STDERR) @@ -16,11 +20,13 @@ module Imap tls_socket = OpenSSL::SSL::Socket::Client.new(@socket.as(TCPSocket), sync_close: true, hostname: host) tls_socket.sync = false @socket = tls_socket + login(username, password) - # list headers - # process_mail_headers(command("tag FETCH 1:#{count} (BODY[HEADER])")) @caps = capability + if CAPS_UTF8.intersects? @caps + command("ENABLE", "UTF8=ACCEPT") + end end private def socket @@ -31,22 +37,20 @@ module Imap end end - private def command(command : String, *parameters, &block : String -> Bool) - command_and_parameter = case command - when "DONE" - command - else - "tag #{command}" - end + private def send(data : String) + socket << data << "\r\n" + socket.flush + @logger.debug "Sent: #{data}" + end + private def command(command : String, *parameters, &block : String -> Bool) + command_and_parameter = "tag #{command}" if parameters.size > 0 params = parameters.join(" ") command_and_parameter += " #{params}" end - socket << command_and_parameter << "\r\n" - socket.flush - @logger.debug "Sent: #{command_and_parameter}" + send command_and_parameter while (line = socket.gets) @logger.debug " Got: #{line}" @@ -64,7 +68,7 @@ module Imap res << line next false elsif line =~ /^tag (BAD|NO)/ - raise "Invalid responce \"#{line}\" received." + raise "Invalid response \"#{line}\" received." else res << line end @@ -75,7 +79,7 @@ module Imap end private def login(username, password) - command("login", username, password) + command("LOGIN", username, password) end # Sends a CAPABILITY command @@ -92,20 +96,24 @@ module Imap spawn do command("IDLE") do |line| if line =~ /\+ idling/ - # nothing to do + @idling = true elsif line =~ /\* (\d+) ([A-Z]+)/ block.call($2, $1.to_u32) else + @idling = false next false end true end end + + Fiber.yield end # Sends a DONE command - def done - command("DONE") + def idle_done + raise "IDLE not started" unless @idling + send("DONE") end # Sends a SELECT command to select a +mailbox+ so that messages @@ -134,7 +142,7 @@ module Imap # Returns an array of mailbox names - def list : Set(String) + def list : Array(String) mailboxes = [] of String res = command(%{LIST "" "*"}) res.each do |line| @@ -143,8 +151,7 @@ module Imap mailboxes << name[1].to_s if name end end - # TODO: decode MIME encoded UTF-8 strings - return Set.new(mailboxes) + return mailboxes end # Sends a STATUS command, and returns the status of the indicated @@ -198,7 +205,7 @@ module Imap # Closes the imap connection def close command("LOGOUT") rescue nil - @socket.close + @socket.not_nil!.close if @socket end end end diff --git a/src/imap/immutable_set.cr b/src/imap/immutable_set.cr new file mode 100644 index 0000000..c823831 --- /dev/null +++ b/src/imap/immutable_set.cr @@ -0,0 +1,45 @@ +class ImmutableSet(T) + @set : Set(T) + + def initialize(*params : T) + @set = Set.new(params) + end + + def each + @set.each + end + + def size + @set.size + end + + def to_s(io) + io << "ImmutableSet{" + join ", ", io, &.inspect(io) + io << '}' + end + + def empty? + @set.empty? + end + + def inspect(io) + to_s(io) + end + + def object_id + @set.object_id + end + + def includes?(obj) + @set.includes?(obj) + end + + def intersects?(other) + @set.intersects?(other) + end + + def pretty_print(pp) : Nil + pp.list("ImmutableSet{", @set, "}") + end +end diff --git a/test.cr b/test.cr index 3bbed9d..d31929e 100644 --- a/test.cr +++ b/test.cr @@ -10,13 +10,14 @@ end imap = Imap::Client.new(host: "imap.gmail.com", port: 993, username: ARGV[0], password: ARGV[1], loglevel: Logger::DEBUG) mailboxes = imap.list if mailboxes.includes?("INBOX") + pp imap.status("INBOX", ["MESSAGES", "RECENT"]) + imap.select("INBOX") imap.idle do |name, value| puts "#{name} => #{value}" end end -while true - sleep 5 - imap.done -end +sleep 3 +imap.idle_done +imap.close