luggage 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.document +4 -0
- data/.gitignore +4 -0
- data/.rspec +1 -0
- data/ChangeLog.rdoc +4 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +20 -0
- data/README.md +221 -0
- data/Rakefile +35 -0
- data/lib/luggage/factory.rb +63 -0
- data/lib/luggage/mailbox.rb +101 -0
- data/lib/luggage/mailbox_array.rb +58 -0
- data/lib/luggage/mailbox_query_builder.rb +77 -0
- data/lib/luggage/message.rb +151 -0
- data/lib/luggage/version.rb +3 -0
- data/lib/luggage.rb +16 -0
- data/luggage.gemspec +30 -0
- data/spec/luggage/factory_spec.rb +83 -0
- data/spec/luggage/mailbox_query_builder_spec.rb +48 -0
- data/spec/luggage/mailbox_spec.rb +135 -0
- data/spec/luggage/message_spec.rb +181 -0
- data/spec/luggage_spec.rb +16 -0
- data/spec/net_imap.rb +15 -0
- data/spec/spec_helper.rb +36 -0
- metadata +201 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: fbf6c643e587287b8d3df07c913163c0c9b56c89
|
4
|
+
data.tar.gz: d7bd6e2bf725860cf8392469f2ca1195358f9863
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: c6e39c9d15b0980de7e56c3df266bde5180388b114d145b01b26d66de2b129cde2e019e88dd0bbf3e0557a6b4fb8dce83ddd7fc0759ba88d2733785f68978264
|
7
|
+
data.tar.gz: c070bbb000e6ed0654240cc40c41e015317d6625a07e1c89515ed932770b46b3f74f94c0f29e007087385f940a1d204ceeb5e816a3a2d792c12a726fbf385397
|
data/.document
ADDED
data/.gitignore
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--colour --format documentation
|
data/ChangeLog.rdoc
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2012 Otherinbox
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,221 @@
|
|
1
|
+
# Luggage
|
2
|
+
|
3
|
+
* [Homepage](https://github.com/otherinbox/luggage#readme)
|
4
|
+
* [Issues](https://github.com/otherinbox/luggage/issues)
|
5
|
+
|
6
|
+
DSL for interacting with Imap accounts
|
7
|
+
|
8
|
+
## Install
|
9
|
+
|
10
|
+
Nothing fancy - in you Gemfile;
|
11
|
+
|
12
|
+
``` ruby
|
13
|
+
gem 'luggage'
|
14
|
+
```
|
15
|
+
|
16
|
+
That was easy, right?
|
17
|
+
|
18
|
+
|
19
|
+
## Before we get started...
|
20
|
+
|
21
|
+
Many of the following examples use a DSL/block style syntax. Most of the object
|
22
|
+
initializers accept a block and do an `instance_eval` on the new object
|
23
|
+
if a block is passed. These three examples are equivalent:
|
24
|
+
|
25
|
+
``` ruby
|
26
|
+
Luggage.new :connection => c do
|
27
|
+
mailboxes "INBOX" do
|
28
|
+
message do
|
29
|
+
template "path/to/foo.eml"
|
30
|
+
end.save!
|
31
|
+
end
|
32
|
+
end
|
33
|
+
```
|
34
|
+
|
35
|
+
``` ruby
|
36
|
+
f = Luggage.new(:connection => c)
|
37
|
+
mb = f.maiboxes["INBOX"]
|
38
|
+
m = mb.message(:template => "path/to/foo.eml")
|
39
|
+
m.save!
|
40
|
+
```
|
41
|
+
|
42
|
+
``` ruby
|
43
|
+
Luggage.new(:connection => c).mailboxes["INBOX"].message(:template => "path/to/foo.eml").save!
|
44
|
+
```
|
45
|
+
|
46
|
+
|
47
|
+
## Creating a factory
|
48
|
+
|
49
|
+
Factories provide the top-level interface for interacting with IMAP servers.
|
50
|
+
All factories require an authenticated `Net::IMAP` instance, however there
|
51
|
+
are multiple ways to get there:
|
52
|
+
|
53
|
+
### Using an existing Connection
|
54
|
+
|
55
|
+
If you have an instance of `Net::IMAP` you can pass it to the constructor as
|
56
|
+
`:connection` and the factory will use it.
|
57
|
+
|
58
|
+
``` ruby
|
59
|
+
f = Luggage.new(:connection => connection)
|
60
|
+
```
|
61
|
+
|
62
|
+
Keep in mind that the connection needs to be authenticated
|
63
|
+
|
64
|
+
### Using an authentication string
|
65
|
+
|
66
|
+
`Net::IMAP` natively supports `LOGIN` and `CRAM-MD5` authentication schemes,
|
67
|
+
if you want to use either of these you can pass in `:server` and `:authenticate`,
|
68
|
+
the contents of `:authenticate` will be passed to `Net::IMAP#authenticate`.
|
69
|
+
|
70
|
+
``` ruby
|
71
|
+
f = Luggage.new(:server => 'imap.aol.com', :authenticate => 'LOGIN user password')
|
72
|
+
f = Luggage.new(:server => ['imap.aol.com' 993, true], :authenticate => 'LOGIN user password')
|
73
|
+
```
|
74
|
+
|
75
|
+
Notice that the value of `:server` will be passed to `Net::IMAP#new`, so the full
|
76
|
+
syntax of the initializer is available. See [the Ruby docs](http://rubydoc.info/stdlib/net/Net/IMAP)
|
77
|
+
for more details on auth and intialization
|
78
|
+
|
79
|
+
### Using XOauth
|
80
|
+
|
81
|
+
Google has implemented XOauth for their IMAP connections. To use this pass in
|
82
|
+
`:server` as before and a token as `:xoauth`
|
83
|
+
|
84
|
+
``` ruby
|
85
|
+
f = Luggage.new(:server => ['imap.gmail.com', 993, true], :xoauth => token)
|
86
|
+
```
|
87
|
+
|
88
|
+
See the documentation for you service provider for details on generating that token.
|
89
|
+
|
90
|
+
|
91
|
+
## Working with mailboxes
|
92
|
+
|
93
|
+
`Luggage#mailboxes` provides an interface to the different mailboxes on your
|
94
|
+
remote server. To access existing mailboxes you can use a couple syntaxes:
|
95
|
+
|
96
|
+
``` ruby
|
97
|
+
Luggage.new(:connection => c) do
|
98
|
+
mailboxes["SPAM"] # => #<Luggage::Mailbox server: "imap.gmail.com", name: "SPAM">
|
99
|
+
mailboxes("SPAM") # => #<Luggage::Mailbox server: "imap.gmail.com", name: "SPAM">
|
100
|
+
mailboxes[:inbox] # => #<Luggage::Mailbox server: "imap.gmail.com", name: "INBOX">
|
101
|
+
mailboxes[:g_all] # => #<Luggage::Mailbox server: "imap.gmail.com", name: "[Gmail]/All Mail">
|
102
|
+
mailboxes[0] # => #<Luggage::Mailbox server: "imap.gmail.com", name: "INBOX">
|
103
|
+
mailboxes(0) # => #<Luggage::Mailbox server: "imap.gmail.com", name: "INBOX">
|
104
|
+
mailboxes.first # => #<Luggage::Mailbox server: "imap.gmail.com", name: "INBOX">
|
105
|
+
mailboxes # => [<Luggage::Mailbox server: "imap.gmail.com", name: "SPAM">...]
|
106
|
+
mailboxes[0..10] # => [<Luggage::Mailbox server: "imap.gmail.com", name: "INBOX">...]
|
107
|
+
end
|
108
|
+
```
|
109
|
+
|
110
|
+
In most cases you can use method call and array/hash index syntax interchangeably.
|
111
|
+
Mailboxes come with a couple useful helper methods:
|
112
|
+
|
113
|
+
``` ruby
|
114
|
+
Luggage.new(:connection => c) do
|
115
|
+
mailboxes["New mailbox"].save! # Creates the remote mailbox
|
116
|
+
mailboxes["Old and busted"].delete! # Deletes the remote mailbox
|
117
|
+
mailboxes["Cheshire"].exists? # Tells you if it exists remotely
|
118
|
+
mailboxes["INBOX"].expunge! # Permanently deletes any messages marked for deletion
|
119
|
+
end
|
120
|
+
```
|
121
|
+
|
122
|
+
## Querying messages
|
123
|
+
|
124
|
+
You can access the messages in a given mailbox through a couple helpers
|
125
|
+
|
126
|
+
``` ruby
|
127
|
+
Luggage.new(:connection => c) do
|
128
|
+
mailboxes "INBOX" do
|
129
|
+
all # Returns an array of all the messages in the mailbox
|
130
|
+
first # Returns the first message (sorted by oldest first)
|
131
|
+
where("SINCE", 5.days.ago) # Executes Net::IMAP#search - see Ruby docs for mor info on search params
|
132
|
+
where(:subject => "FI!") # Shortcut for 'SUBJECT'
|
133
|
+
end
|
134
|
+
end
|
135
|
+
```
|
136
|
+
|
137
|
+
Querying works somewhat like ActiveRecord scopes, in that you can chain calls to `where`
|
138
|
+
to build up a compound query, which is only executed once you attempt to inspect the results.
|
139
|
+
Keep in mind that compound queries are generated by appending each key/value pair into
|
140
|
+
big string and sending it to the IMAP server - this isn't SQL
|
141
|
+
|
142
|
+
Messages are retrieved somewhat lazily. The `Message-ID` and `uid` fields are always fetched,
|
143
|
+
but the full body isn't fetched until you try to access a field like `subject` or `body`.
|
144
|
+
You can inspect retrieved messages using the same syntax as the [Mail](https://github.com/mikel/mail)
|
145
|
+
gem, for instance:
|
146
|
+
|
147
|
+
``` ruby
|
148
|
+
Luggage.new(:connection => c) do
|
149
|
+
mailboxes "INBOX" do
|
150
|
+
first.subject # The email subject
|
151
|
+
first.to # TO: field
|
152
|
+
first.headers['Return-Path'] # Random headers
|
153
|
+
first.body # Decoded body
|
154
|
+
first.multipart? # Is this a multi-part email?
|
155
|
+
|
156
|
+
first.flags # The flags set on the remote message
|
157
|
+
end
|
158
|
+
end
|
159
|
+
```
|
160
|
+
|
161
|
+
Messages are uniquely identified by their `Message-ID` header, so if you want to access a
|
162
|
+
remote email you can fetch its data by creating a new messge instance and passing in the
|
163
|
+
message id:
|
164
|
+
|
165
|
+
``` ruby
|
166
|
+
Luggage.new(:connection => c) do
|
167
|
+
message(:message_id => message_id).reload.flags
|
168
|
+
end
|
169
|
+
```
|
170
|
+
|
171
|
+
See the next section for more details on that `Luggage#message` method...
|
172
|
+
|
173
|
+
## Working with messages
|
174
|
+
|
175
|
+
`Luggage#message` and `Mailbox#message` provide interfaces for creating messages.
|
176
|
+
|
177
|
+
* If you pass in a `:template` argument, the message will be created using the contents
|
178
|
+
of the file at that path.
|
179
|
+
* If you pass an array as `:flags`, the passed flags will be set for the message when
|
180
|
+
it's uploaded to the remote server.
|
181
|
+
* A date can be passed as `:date` and will be used as the recieved-at date when the message
|
182
|
+
is uploaded.
|
183
|
+
* Any other arguments will be interpreted as properties to be set on the new message.
|
184
|
+
These will be set after the template is read (if provided), allowing
|
185
|
+
you to tweak the templates if needed.
|
186
|
+
|
187
|
+
If using the `Luggage` version, a mailbox must be specified as the first argument.
|
188
|
+
|
189
|
+
``` ruby
|
190
|
+
Luggage.new(:connection => c) do
|
191
|
+
message("INBOX", :template => 'path/to/email.eml').save!
|
192
|
+
|
193
|
+
mailboxes "INBOX" do
|
194
|
+
message(:template => 'path/to/email.eml').save!
|
195
|
+
|
196
|
+
message(:template => 'path/to/email.eml', :subject => "Custom subject").save!
|
197
|
+
message do
|
198
|
+
subject "Howdy"
|
199
|
+
body "Partner"
|
200
|
+
from "me@gmail.com"
|
201
|
+
to "you@gmail.com"
|
202
|
+
headers "DKIM-Signature" => "FAIL"
|
203
|
+
end.save!
|
204
|
+
end
|
205
|
+
end
|
206
|
+
```
|
207
|
+
|
208
|
+
_Don't forget to save_. Cause otherwise it won't get uploaded.
|
209
|
+
|
210
|
+
You can also work with existing messages on the server, either by querying for
|
211
|
+
them or by instantiating them directly by their message id
|
212
|
+
|
213
|
+
``` ruby
|
214
|
+
Luggage.new(:connection => c) do
|
215
|
+
mailboxes :g_all do
|
216
|
+
message(:message_id => "<foo@example.com>").exists? # Does a message with this Message-ID exist in this mailbox?
|
217
|
+
message(:message_id => "<foo@example.com>").reload # Re-download the content and flags of this message
|
218
|
+
first.delete! # Delete this message (add the 'Deleted' flag)
|
219
|
+
end
|
220
|
+
end
|
221
|
+
```
|
data/Rakefile
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
|
5
|
+
begin
|
6
|
+
require 'bundler'
|
7
|
+
rescue LoadError => e
|
8
|
+
warn e.message
|
9
|
+
warn "Run `gem install bundler` to install Bundler."
|
10
|
+
exit -1
|
11
|
+
end
|
12
|
+
|
13
|
+
begin
|
14
|
+
Bundler.setup(:development)
|
15
|
+
rescue Bundler::BundlerError => e
|
16
|
+
warn e.message
|
17
|
+
warn "Run `bundle install` to install missing gems."
|
18
|
+
exit e.status_code
|
19
|
+
end
|
20
|
+
|
21
|
+
require 'rake'
|
22
|
+
|
23
|
+
require 'rdoc/task'
|
24
|
+
RDoc::Task.new do |rdoc|
|
25
|
+
rdoc.title = "luggage"
|
26
|
+
end
|
27
|
+
task :doc => :rdoc
|
28
|
+
|
29
|
+
require 'rspec/core/rake_task'
|
30
|
+
RSpec::Core::RakeTask.new
|
31
|
+
|
32
|
+
task :test => :spec
|
33
|
+
task :default => :spec
|
34
|
+
|
35
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module Luggage
|
2
|
+
class Factory
|
3
|
+
attr_reader :connection
|
4
|
+
|
5
|
+
# Factory
|
6
|
+
#
|
7
|
+
# Factories require an instance of Net::IMAP. Serveral methods are supported:
|
8
|
+
#
|
9
|
+
# Factory.new(:connection => connection)
|
10
|
+
# In this case, `connection` should be an authorized Net::IMAP instance
|
11
|
+
#
|
12
|
+
# Factory.new(:server => "imap.example.com", :authentication => "LOGIN username password")
|
13
|
+
# In this case, we'll build a Net::IMAP instance and attempt to authenticate with the
|
14
|
+
# value of `authentication`. Net::IMAP supports LOGIN and CRAM-MD5 natively - see below
|
15
|
+
# for xoauth
|
16
|
+
#
|
17
|
+
# Factory.new(:server => "imap.gmail.com", :xoauth => "xoauth token string")
|
18
|
+
# In this case we'll build a Net::IMAP instance and attempt to send a raw XOAUTH authentication
|
19
|
+
# request using the supplied token.
|
20
|
+
#
|
21
|
+
def initialize(args = {}, &block)
|
22
|
+
if args.has_key?(:connection)
|
23
|
+
@connection = args[:connection]
|
24
|
+
elsif args.has_key?(:server) && args.has_key?(:authenticate)
|
25
|
+
@connection = Net::IMAP.new(*Array(args[:server]))
|
26
|
+
@connection.authenticate(*args[:authenticate])
|
27
|
+
elsif args.has_key?(:server) && args.has_key?(:xoauth)
|
28
|
+
@connection = Net::IMAP.new(*Array(args[:server]))
|
29
|
+
@connection.send(:send_command, "AUTHENTICATE XOAUTH #{args[:xoauth]}")
|
30
|
+
else
|
31
|
+
raise ArgumentError, "Imap Connection required."
|
32
|
+
end
|
33
|
+
|
34
|
+
instance_eval &block if block_given?
|
35
|
+
end
|
36
|
+
|
37
|
+
# Factory#message
|
38
|
+
#
|
39
|
+
# Constructs an Message
|
40
|
+
#
|
41
|
+
# `mailbox` can be either a string describing the Imap mailbox the message belongs to
|
42
|
+
# or an instance of Mailbox.
|
43
|
+
#
|
44
|
+
# `args` will be passed to ImapFactorY::Message#new_local - see that method for details
|
45
|
+
#
|
46
|
+
def message(mailbox, args = {}, &block)
|
47
|
+
Message.new_local(connection, mailbox, args, &block)
|
48
|
+
end
|
49
|
+
|
50
|
+
def mailboxes(*args)
|
51
|
+
array = MailboxArray.new(connection)
|
52
|
+
args.empty? ? array : array[*args]
|
53
|
+
end
|
54
|
+
|
55
|
+
def inspect
|
56
|
+
"#<Luggage::Factory server: \"#{host}\">"
|
57
|
+
end
|
58
|
+
|
59
|
+
def host
|
60
|
+
connection.instance_variable_get(:@host)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
module Luggage
|
2
|
+
class Mailbox
|
3
|
+
include Enumerable
|
4
|
+
|
5
|
+
attr_reader :connection, :name
|
6
|
+
|
7
|
+
# This provides an interface to a remote Imap mailbox
|
8
|
+
#
|
9
|
+
# `connection` should be an authenticated Net::IMAP
|
10
|
+
#
|
11
|
+
# `name` is the name of the remote mailbox
|
12
|
+
#
|
13
|
+
def initialize(connection, name, &block)
|
14
|
+
raise ArgumentError, "Net::IMAP connection required" unless connection.kind_of?(Net::IMAP)
|
15
|
+
raise ArgumentError, "name required" unless name.present?
|
16
|
+
|
17
|
+
@connection = connection
|
18
|
+
@name = name
|
19
|
+
|
20
|
+
instance_eval &block if block_given?
|
21
|
+
end
|
22
|
+
|
23
|
+
# Constructs an Message whose mailbox will be set to this instance
|
24
|
+
#
|
25
|
+
# `args` will be passed to ImapFactorY::Message#new_local - see that method for details
|
26
|
+
#
|
27
|
+
def message(args = {}, &block)
|
28
|
+
Message.new_local(connection, self, args, &block)
|
29
|
+
end
|
30
|
+
|
31
|
+
# Returns true if this mailbox exists on the remote server, false otherwise
|
32
|
+
#
|
33
|
+
def exists?
|
34
|
+
@exists ||= connection.list("", name).present?
|
35
|
+
end
|
36
|
+
|
37
|
+
# Deletes this mailbox on the remote server
|
38
|
+
#
|
39
|
+
def delete!
|
40
|
+
connection.delete(name)
|
41
|
+
@exists = false
|
42
|
+
end
|
43
|
+
|
44
|
+
# Selects this mailbox for future Imap commands.
|
45
|
+
#
|
46
|
+
def select!
|
47
|
+
connection.select(name)
|
48
|
+
end
|
49
|
+
|
50
|
+
# Creates the mailbox on the remote server if it doesn't exist already
|
51
|
+
#
|
52
|
+
def save!
|
53
|
+
unless exists?
|
54
|
+
connection.create(name)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# Removes 'deleted' messages on the remote server. Message#delete! marks
|
59
|
+
# messages with the 'Deleted' flag, but leaves them on the server. This removes
|
60
|
+
# them entirely
|
61
|
+
#
|
62
|
+
def expunge!
|
63
|
+
select!
|
64
|
+
connection.expunge()
|
65
|
+
end
|
66
|
+
|
67
|
+
# Returns an array of Message instances describing all emails in the remote mailbox
|
68
|
+
#
|
69
|
+
def all
|
70
|
+
MailboxQueryBuilder.new(self)
|
71
|
+
end
|
72
|
+
|
73
|
+
# Returns a Message instance describing the first email on the remote mailbox
|
74
|
+
#
|
75
|
+
def first
|
76
|
+
all.first
|
77
|
+
end
|
78
|
+
|
79
|
+
# Iterates over Mailbox#each
|
80
|
+
#
|
81
|
+
def each(&block)
|
82
|
+
all.each(&block)
|
83
|
+
end
|
84
|
+
|
85
|
+
# Filters emails on the remote server
|
86
|
+
#
|
87
|
+
# Returns a MailboxQueryBuilder - see MailboxQueryBuilder#where for usage details
|
88
|
+
#
|
89
|
+
def where(*args)
|
90
|
+
all.where(*args)
|
91
|
+
end
|
92
|
+
|
93
|
+
def inspect
|
94
|
+
"#<Luggage::Mailbox server: \"#{host}\", name: \"#{name}\">"
|
95
|
+
end
|
96
|
+
|
97
|
+
def host
|
98
|
+
connection.instance_variable_get(:@host)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module Luggage
|
2
|
+
class MailboxArray
|
3
|
+
attr_reader :connection
|
4
|
+
|
5
|
+
def initialize(connection)
|
6
|
+
@connection = connection
|
7
|
+
end
|
8
|
+
|
9
|
+
def [](*args, &block)
|
10
|
+
case args.first
|
11
|
+
when String
|
12
|
+
mailbox(args.first, &block)
|
13
|
+
when :inbox, :spam, :sent, :trash
|
14
|
+
mailbox(args.first.to_s.upcase, &block)
|
15
|
+
when :g_all
|
16
|
+
mailbox("[Gmail]/All Mail", &block)
|
17
|
+
when :g_sent
|
18
|
+
mailbox("[Gmail]/Sent", &block)
|
19
|
+
when :g_trash
|
20
|
+
mailbox("[Gmail]/Trash", &block)
|
21
|
+
when Symbol
|
22
|
+
mailbox(args.first, &block)
|
23
|
+
when nil
|
24
|
+
mailboxes
|
25
|
+
else
|
26
|
+
super
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def method_missing(meth, *args, &block)
|
31
|
+
mailboxes.send(meth, *args, &block)
|
32
|
+
end
|
33
|
+
|
34
|
+
def inspect
|
35
|
+
mailboxes.inspect
|
36
|
+
end
|
37
|
+
|
38
|
+
def host
|
39
|
+
connection.instance_variable_get(:@host)
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
# Cosntructs a Mailbox
|
45
|
+
#
|
46
|
+
# `name` should be a string describing the Imap mailbox's name
|
47
|
+
#
|
48
|
+
def mailbox(name, &block)
|
49
|
+
Mailbox.new(connection, name, &block)
|
50
|
+
end
|
51
|
+
|
52
|
+
def mailboxes
|
53
|
+
connection.list("", "*").map do |result|
|
54
|
+
Mailbox.new(connection, result.name)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
module Luggage
|
2
|
+
class MailboxQueryBuilder
|
3
|
+
include Enumerable
|
4
|
+
|
5
|
+
attr_reader :connection, :mailbox, :query
|
6
|
+
|
7
|
+
# Provides an ActiveRecord-style query interface to emails on the remote server
|
8
|
+
#
|
9
|
+
# `mailbox` should be a Mailbox instance describing the remote mailbox to be queried
|
10
|
+
#
|
11
|
+
def initialize(mailbox)
|
12
|
+
raise ArgumentError, "Luggage::Mailbox required" unless mailbox.kind_of?(Mailbox)
|
13
|
+
|
14
|
+
@mailbox = mailbox
|
15
|
+
@connection = mailbox.connection
|
16
|
+
@query = []
|
17
|
+
end
|
18
|
+
|
19
|
+
# Executes the query and yields to each returned result
|
20
|
+
#
|
21
|
+
def each(&block)
|
22
|
+
messages.each(&block)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Builds an Imap search query from the passed `args` hash. Each key is treated as
|
26
|
+
# a search key, each value is treated as a search value. Key/value pairs are appended
|
27
|
+
# to an array which will be passed to Net::IMAP#search. For more details on search
|
28
|
+
# syntax see Ruby std lib docs for Net::IMAP
|
29
|
+
#
|
30
|
+
def where(args = {})
|
31
|
+
@message_ids = nil
|
32
|
+
@messages = nil
|
33
|
+
|
34
|
+
args.each do |key, value|
|
35
|
+
case key.to_sym
|
36
|
+
when :body, :subject, :to, :cc, :from
|
37
|
+
@query += [key.to_s.upcase, value]
|
38
|
+
else
|
39
|
+
@query += [key, value]
|
40
|
+
end
|
41
|
+
end
|
42
|
+
self
|
43
|
+
end
|
44
|
+
|
45
|
+
# Executes the query and returns a slice of the resulting array of messages
|
46
|
+
#
|
47
|
+
def [](*args)
|
48
|
+
messages.[](*args)
|
49
|
+
end
|
50
|
+
|
51
|
+
def messages
|
52
|
+
@messages ||= message_ids.map {|message_id| Message.new(connection, mailbox, :message_id => message_id)}
|
53
|
+
end
|
54
|
+
|
55
|
+
def inspect
|
56
|
+
"#<Luggage::MailboxQueryBuilder server: \"#{host}\", mailbox: \"#{mailbox.name}\", query: #{query}>"
|
57
|
+
end
|
58
|
+
|
59
|
+
def host
|
60
|
+
connection.instance_variable_get(:@host)
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
def uids
|
66
|
+
mailbox.select!
|
67
|
+
connection.uid_search(@query.empty? ? "ALL" : @query)
|
68
|
+
end
|
69
|
+
|
70
|
+
def message_ids
|
71
|
+
field = "BODY[HEADER.FIELDS (Message-ID)]"
|
72
|
+
@message_ids ||= connection.uid_fetch(uids, field).map do |resp|
|
73
|
+
$1 if resp[:attr][field] =~ /(<\S*@\S*>)/
|
74
|
+
end.compact
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|