first commit
This commit is contained in:
commit
65f87822fc
10 changed files with 240 additions and 0 deletions
7
.editorconfig
Normal file
7
.editorconfig
Normal file
|
@ -0,0 +1,7 @@
|
|||
[*.cr]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
trim_trailing_whitespace = true
|
8
.gitignore
vendored
Normal file
8
.gitignore
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
/doc/
|
||||
/lib/
|
||||
/bin/
|
||||
/.shards/
|
||||
|
||||
# Libraries don't need dependency lock
|
||||
# Dependencies will be locked in application that uses them
|
||||
/shard.lock
|
1
.travis.yml
Normal file
1
.travis.yml
Normal file
|
@ -0,0 +1 @@
|
|||
language: crystal
|
21
LICENSE
Normal file
21
LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2017 Cris Ward
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
41
README.md
Normal file
41
README.md
Normal file
|
@ -0,0 +1,41 @@
|
|||
# imap
|
||||
|
||||
A very much WIP imap library for crystal.
|
||||
|
||||
## Installation
|
||||
|
||||
Add this to your application's `shard.yml`:
|
||||
|
||||
```yaml
|
||||
dependencies:
|
||||
imap:
|
||||
github: crisward/imap
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```crystal
|
||||
require "imap"
|
||||
|
||||
imap = Imap::Client.new(host: "imap.gmail.com", port: 993, username: "email@gmail.com", password: "*******")
|
||||
mailboxes = imap.get_mailboxes
|
||||
if mailboxes.size > 0
|
||||
mailbox = mailboxes[0]
|
||||
imap.set_mailbox(mailbox)
|
||||
message_count = imap.get_message_count
|
||||
puts "There are #{message_count} message in #{mailbox}"
|
||||
end
|
||||
imap.close
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
1. Fork it ( https://github.com/crisward/imap/fork )
|
||||
2. Create your feature branch (git checkout -b my-new-feature)
|
||||
3. Commit your changes (git commit -am 'Add some feature')
|
||||
4. Push to the branch (git push origin my-new-feature)
|
||||
5. Create a new Pull Request
|
||||
|
||||
## Contributors
|
||||
|
||||
- [crisward](https://github.com/crisward) Cris Ward - creator, maintainer
|
9
shard.yml
Normal file
9
shard.yml
Normal file
|
@ -0,0 +1,9 @@
|
|||
name: imap
|
||||
version: 0.1.0
|
||||
|
||||
authors:
|
||||
- Cris Ward <cris@duodesign.co.uk>
|
||||
|
||||
crystal: 0.23.1
|
||||
|
||||
license: MIT
|
17
spec/imap_spec.cr
Normal file
17
spec/imap_spec.cr
Normal file
|
@ -0,0 +1,17 @@
|
|||
require "./spec_helper"
|
||||
|
||||
describe Imap do
|
||||
# TODO: Write tests
|
||||
|
||||
it "should count emails in mailbox" do
|
||||
imap = Imap::Client.new(host: "imap.gmail.com", port: 993, username: "***", password: "***")
|
||||
mailboxes = imap.get_mailboxes
|
||||
if mailboxes.size > 0
|
||||
mailbox = mailboxes[0]
|
||||
imap.set_mailbox(mailbox)
|
||||
message_count = imap.get_message_count
|
||||
puts "There are #{message_count} message in #{mailbox}"
|
||||
end
|
||||
imap.close
|
||||
end
|
||||
end
|
2
spec/spec_helper.cr
Normal file
2
spec/spec_helper.cr
Normal file
|
@ -0,0 +1,2 @@
|
|||
require "spec"
|
||||
require "../src/imap"
|
131
src/imap.cr
Normal file
131
src/imap.cr
Normal file
|
@ -0,0 +1,131 @@
|
|||
require "./imap/*"
|
||||
require "openssl"
|
||||
require "logger"
|
||||
|
||||
module Imap
|
||||
class Client
|
||||
@socket : TCPSocket | OpenSSL::SSL::Socket::Client | Nil = nil
|
||||
@logger : Logger
|
||||
@mailbox : String?
|
||||
|
||||
def initialize(host = "imap.gmail.com", port = 993, username = "", password = "", loglevel = Logger::ERROR)
|
||||
@logger = Logger.new(STDOUT)
|
||||
@logger.level = loglevel
|
||||
@mailboxes = [] of String
|
||||
@mailbox = nil
|
||||
|
||||
@command_history = [] of String
|
||||
@socket = TCPSocket.new(host, port)
|
||||
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])"))
|
||||
end
|
||||
|
||||
private def socket
|
||||
if _socket = @socket
|
||||
_socket
|
||||
else
|
||||
raise "Client socket not opened."
|
||||
end
|
||||
end
|
||||
|
||||
private def command(command : String, parameter : String? = nil)
|
||||
command_and_parameter = command
|
||||
command_and_parameter += " " + parameter if parameter
|
||||
@command_history << command_and_parameter
|
||||
@logger.info "=====> #{command_and_parameter}"
|
||||
socket << command_and_parameter << "\r\n"
|
||||
socket.flush
|
||||
response
|
||||
end
|
||||
|
||||
private def login(username, password)
|
||||
command("tag login #{username} #{password}")
|
||||
end
|
||||
|
||||
# sets the current mailbox
|
||||
def set_mailbox(mailbox)
|
||||
@mailbox = mailbox
|
||||
command("tag SELECT #{mailbox}")
|
||||
end
|
||||
|
||||
# Returns an array of mailbox names
|
||||
def get_mailboxes : Array(String)
|
||||
mailboxes = [] of String
|
||||
res = command(%{tag LIST "" "*"})
|
||||
res.each do |line|
|
||||
if line =~ /HasNoChildren/
|
||||
name = line.match(/"([^"]+)"$/)
|
||||
mailboxes << name[1].to_s if name
|
||||
end
|
||||
end
|
||||
return mailboxes
|
||||
end
|
||||
|
||||
# Returns the number of messages in the current mailbox
|
||||
def get_message_count
|
||||
mailbox = @mailbox
|
||||
if !mailbox
|
||||
raise "No Mailbox set"
|
||||
end
|
||||
res = command("tag STATUS #{mailbox} (MESSAGES)")
|
||||
# eg (MESSAGES 3)
|
||||
res.each do |line|
|
||||
if line =~ /MESSAGES/
|
||||
match = line.match(/MESSAGES ([0-9]+)/)
|
||||
if match
|
||||
return match[1].to_i
|
||||
end
|
||||
end
|
||||
end
|
||||
return 0
|
||||
end
|
||||
|
||||
private def process_mail_headers(res)
|
||||
ip = nil
|
||||
from = nil
|
||||
res.each do |line|
|
||||
if line =~ /^From:/
|
||||
from = line.sub(/^From: /, "")
|
||||
end
|
||||
if line =~ /^Received:/
|
||||
ips = line.match(/\[(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\]/)
|
||||
if ips
|
||||
ip = ips[1].to_s
|
||||
end
|
||||
end
|
||||
if ip && from
|
||||
@logger.info "from: #{from} ip: #{ip}"
|
||||
from = nil
|
||||
ip = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private def response
|
||||
status_messages = [] of String
|
||||
while (line = socket.gets)
|
||||
@command_history << line
|
||||
if line =~ /^\*/
|
||||
status_messages << line
|
||||
elsif line =~ /^tag OK/
|
||||
status_messages << line
|
||||
break
|
||||
elsif line =~ /^tag (BAD|NO)/
|
||||
raise "Invalid responce \"#{line}\" received."
|
||||
else
|
||||
status_messages << line
|
||||
end
|
||||
end
|
||||
status_messages
|
||||
end
|
||||
|
||||
# Closes the imap connection
|
||||
def close
|
||||
command("tag LOGOUT")
|
||||
end
|
||||
end
|
||||
end
|
3
src/imap/version.cr
Normal file
3
src/imap/version.cr
Normal file
|
@ -0,0 +1,3 @@
|
|||
module Imap
|
||||
VERSION = "0.1.0"
|
||||
end
|
Loading…
Reference in a new issue