luggage 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|