griddler-acd 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/LICENSE +19 -0
- data/README.md +212 -0
- data/Rakefile +14 -0
- data/app/controllers/griddler/emails_controller.rb +25 -0
- data/config/initializers/griddler.rb +1 -0
- data/lib/griddler.rb +9 -0
- data/lib/griddler/adapter_registry.rb +28 -0
- data/lib/griddler/configuration.rb +52 -0
- data/lib/griddler/email.rb +84 -0
- data/lib/griddler/email_parser.rb +91 -0
- data/lib/griddler/engine.rb +9 -0
- data/lib/griddler/errors.rb +9 -0
- data/lib/griddler/route_extensions.rb +7 -0
- data/lib/griddler/testing.rb +89 -0
- data/lib/griddler/version.rb +3 -0
- metadata +138 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 0bbff6edeef08baf0c4cfc81727aef71984e8064
|
4
|
+
data.tar.gz: e622fa41d89c7ca9513c0a21752b51d05a5f2b30
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 20ee0b0ce115d5a9bb41e0f4e6084d0a187ca9249e4edf4f1713ccc1f5f2377479da328367249ce7cef6f1879e6ca8501f8e4e40e61944225ae06e766b007237
|
7
|
+
data.tar.gz: 3cf2ede21b125530f4bd26d95b953842d02d2dc3428e21122d04b650a60399f050f149be7793a302d46b3b23d76d026a65f87e4f3b86b5744cc5237cdbd5d2ef
|
data/LICENSE
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) 2014 Caleb Thompson, Joel Oliveira and thoughtbot, inc.
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,212 @@
|
|
1
|
+
Griddler
|
2
|
+
========
|
3
|
+
|
4
|
+
[![Build Status](https://travis-ci.org/thoughtbot/griddler.png?branch=master)](https://travis-ci.org/thoughtbot/griddler)
|
5
|
+
[![Code Climate](https://codeclimate.com/github/thoughtbot/griddler.png)](https://codeclimate.com/github/thoughtbot/griddler)
|
6
|
+
|
7
|
+
### Receive emails in your Rails app
|
8
|
+
|
9
|
+
Griddler is a Rails engine that provides an endpoint for services that convert
|
10
|
+
incoming emails to HTTP POST requests. It parses these POSTs and hands off a
|
11
|
+
built email object to a class implemented by you.
|
12
|
+
|
13
|
+
Tutorials
|
14
|
+
---------
|
15
|
+
|
16
|
+
* SendGrid wrote a
|
17
|
+
[great tutorial](http://blog.sendgrid.com/receiving-email-in-your-rails-app-with-griddler/)
|
18
|
+
on integrating Griddler with your application.
|
19
|
+
* We have our own blog post on the subject over at
|
20
|
+
[Giant Robots](http://robots.thoughtbot.com/handle-incoming-email-with-griddler).
|
21
|
+
|
22
|
+
Installation
|
23
|
+
------------
|
24
|
+
|
25
|
+
1. Add `griddler` and an [adapter] gem to your application's Gemfile
|
26
|
+
and run `bundle install`.
|
27
|
+
|
28
|
+
[adapter]: #adapters
|
29
|
+
|
30
|
+
2. A route is needed for the endpoint which receives `POST` messages. To add the
|
31
|
+
route, in `config/routes.rb` you may either use the provided routing method
|
32
|
+
`mount_griddler` or set the route explicitly. Examples:
|
33
|
+
|
34
|
+
```ruby
|
35
|
+
# config/routes.rb
|
36
|
+
|
37
|
+
# mount using default path: /email_processor
|
38
|
+
mount_griddler
|
39
|
+
|
40
|
+
# mount using a custom path
|
41
|
+
mount_griddler('/email/incoming')
|
42
|
+
|
43
|
+
# the DIY approach:
|
44
|
+
post '/email_processor' => 'griddler/emails#create'
|
45
|
+
```
|
46
|
+
|
47
|
+
### Configuration Options
|
48
|
+
|
49
|
+
An initializer can be created to control some of the options in Griddler.
|
50
|
+
Defaults are shown below with sample overrides following. In
|
51
|
+
`config/initializers/griddler.rb`:
|
52
|
+
|
53
|
+
```ruby
|
54
|
+
Griddler.configure do |config|
|
55
|
+
config.processor_class = EmailProcessor # CommentViaEmail
|
56
|
+
config.processor_method = :process # :create_comment (A method on CommentViaEmail)
|
57
|
+
config.reply_delimiter = '-- REPLY ABOVE THIS LINE --'
|
58
|
+
config.email_service = :sendgrid # :cloudmailin, :postmark, :mandrill, :mailgun
|
59
|
+
end
|
60
|
+
```
|
61
|
+
|
62
|
+
| Option | Meaning
|
63
|
+
| ------ | -------
|
64
|
+
| `processor_class` | The class Griddler will use to handle your incoming emails.
|
65
|
+
| `processor_method` | The method Griddler will call on the processor class when handling your incoming emails.
|
66
|
+
| `reply_delimiter` | The string searched for that will split your body.
|
67
|
+
| `email_service` | Tells Griddler which email service you are using. The supported email service options are `:sendgrid` (the default), `:cloudmailin` (expects multipart format), `:postmark`, `:mandrill` and `:mailgun`. You will also need to have an appropriate [adapter] gem included in your Gemfile.
|
68
|
+
|
69
|
+
By default Griddler will look for a class named `EmailProcessor` with a method
|
70
|
+
named `process`, taking in one argument, a `Griddler::Email` instance
|
71
|
+
representing the incoming email. For example, in `./lib/email_processor.rb`:
|
72
|
+
|
73
|
+
```ruby
|
74
|
+
class EmailProcessor
|
75
|
+
def initialize(email)
|
76
|
+
@email = email
|
77
|
+
end
|
78
|
+
|
79
|
+
def process
|
80
|
+
# all of your application-specific code here - creating models,
|
81
|
+
# processing reports, etc
|
82
|
+
|
83
|
+
# here's an example of model creation
|
84
|
+
user = User.find_by_email(@email.from[:email])
|
85
|
+
user.posts.create!(
|
86
|
+
subject: @email.subject,
|
87
|
+
body: @email.body
|
88
|
+
)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
```
|
92
|
+
|
93
|
+
Griddler::Email attributes
|
94
|
+
--------------------------
|
95
|
+
|
96
|
+
| Attribute | Description
|
97
|
+
| -------------- | -----------
|
98
|
+
| `#to` | An array of hashes containing recipient address information. See [Email Addresses](#email-addresses) for more information.
|
99
|
+
| `#from` | A hash containing the sender address information.
|
100
|
+
| `#cc` | An array of hashes containing cc email address information.
|
101
|
+
| `#subject` | The subject of the email message.
|
102
|
+
| `#body` | The full contents of the email body **unless** there is a line in the email containing the string `-- Reply ABOVE THIS LINE --`. In that case `.body` will contain everything before that line.
|
103
|
+
| `#raw_text` | The raw text part of the body.
|
104
|
+
| `#raw_html` | The raw html part of the body.
|
105
|
+
| `#raw_body` | The raw body information provided by the email service.
|
106
|
+
| `#attachments` | An array of `File` objects containing any attachments.
|
107
|
+
| `#headers` | A hash of headers parsed by `Mail::Header`.
|
108
|
+
| `#raw_headers` | The raw headers included in the message.
|
109
|
+
|
110
|
+
### Email Addresses
|
111
|
+
|
112
|
+
Gridder::Email provides email addresses as hashes. Each hash will have the following
|
113
|
+
information of each recipient:
|
114
|
+
|
115
|
+
| Key | Value
|
116
|
+
| --- | -----
|
117
|
+
| `:token` | All the text before the email's "@". We've found that this is the most often used portion of the email address and consider it to be the token we'll key off of for interaction with our application.
|
118
|
+
| `:host` | All the text after the email's "@". This is important to filter the recipients sent to the application vs emails to other domains. More info below on the Upgrading to 0.5.0 section.
|
119
|
+
| `:email` | The email address of the recipient.
|
120
|
+
| `:full` | The whole recipient field (e.g., `Some User <hello@example.com>`).
|
121
|
+
| `:name` | The name of the recipient (e.g., `Some User`).
|
122
|
+
|
123
|
+
Testing In Your App
|
124
|
+
-------------------
|
125
|
+
|
126
|
+
You may want to create a factory for when testing the integration of Griddler
|
127
|
+
into your application. If you're using factory\_girl this can be accomplished
|
128
|
+
with the following sample factory:
|
129
|
+
|
130
|
+
```ruby
|
131
|
+
factory :email, class: OpenStruct do
|
132
|
+
# Assumes Griddler.configure.to is :hash (default)
|
133
|
+
to [{ full: 'to_user@email.com', email: 'to_user@email.com', token: 'to_user', host: 'email.com', name: nil }]
|
134
|
+
from 'user@email.com'
|
135
|
+
subject 'email subject'
|
136
|
+
body 'Hello!'
|
137
|
+
attachments {[]}
|
138
|
+
|
139
|
+
trait :with_attachment do
|
140
|
+
attachments {[
|
141
|
+
ActionDispatch::Http::UploadedFile.new({
|
142
|
+
filename: 'img.png',
|
143
|
+
type: 'image/png',
|
144
|
+
tempfile: File.new("#{File.expand_path(File.dirname(__FILE__))}/fixtures/img.png")
|
145
|
+
})
|
146
|
+
]}
|
147
|
+
end
|
148
|
+
end
|
149
|
+
```
|
150
|
+
|
151
|
+
Bear in mind, if you plan on using the `:with_attachment` trait, that this
|
152
|
+
example assumes your factories are in `spec/factories.rb` and you have
|
153
|
+
an image file in `spec/fixtures/`.
|
154
|
+
|
155
|
+
To use it in your tests, build with `email = build(:email)`
|
156
|
+
or `email = build(:email, :with_attachment)`.
|
157
|
+
|
158
|
+
Adapters
|
159
|
+
--------
|
160
|
+
|
161
|
+
Depending on the service you want to use Griddler with, you'll need to add an
|
162
|
+
adapter gem in addition to `griddler`.
|
163
|
+
|
164
|
+
| Service | Adapter
|
165
|
+
| ------- | -------
|
166
|
+
| sendgrid | [griddler-sendgrid]
|
167
|
+
| mandrill | [griddler-mandrill]
|
168
|
+
| mailgun | [griddler-mailgun]
|
169
|
+
| postmark | [griddler-postmark]
|
170
|
+
|
171
|
+
[griddler-sendgrid]: https://github.com/thoughtbot/griddler-sendgrid
|
172
|
+
[griddler-mandrill]: https://github.com/wingrunr21/griddler-mandrill
|
173
|
+
[griddler-mailgun]: https://github.com/bradpauly/griddler-mailgun
|
174
|
+
[griddler-postmark]: https://github.com/r38y/griddler-postmark
|
175
|
+
|
176
|
+
Writing an Adapter
|
177
|
+
------------------
|
178
|
+
|
179
|
+
Griddler can theoretically work with any email => POST service. In order to work
|
180
|
+
correctly, adapters need to have their POST parameters restructured.
|
181
|
+
|
182
|
+
`Griddler::Email` expects certain parameters to be in place for proper parsing
|
183
|
+
to occur. When writing an adapter, ensure that the `normalized_params` method of
|
184
|
+
your adapter returns a hash with these keys:
|
185
|
+
|
186
|
+
| Parameter | Contents
|
187
|
+
| --------- | --------
|
188
|
+
| `:to` | The recipient field
|
189
|
+
| `:from` | The sender field
|
190
|
+
| `:subject` | Email subject
|
191
|
+
| `:text` | The text body of the email
|
192
|
+
| `:html` | The html body of the email, nil or empty string if not present
|
193
|
+
| `:attachments` | Array of attachments to the email. Can be an empty array.
|
194
|
+
| `:headers` | The raw headers of the email. **Optional**.
|
195
|
+
| `:charsets` | A JSON string containing the character sets of the fields extracted from the message. **Optional**.
|
196
|
+
|
197
|
+
All keys are required unless otherwise stated.
|
198
|
+
|
199
|
+
Adapters should be provided as gems. If you write an adapter, let us know and we
|
200
|
+
will add it to this README. See [griddler-sendgrid] for an example
|
201
|
+
implementation.
|
202
|
+
|
203
|
+
Credits
|
204
|
+
-------
|
205
|
+
|
206
|
+
Griddler was written by Caleb Thompson and Joel Oliveira.
|
207
|
+
|
208
|
+
Thanks to our [contributors](https://github.com/thoughtbot/griddler/contributors)!
|
209
|
+
|
210
|
+
![thoughtbot](http://thoughtbot.com/images/tm/logo.png)
|
211
|
+
|
212
|
+
The names and logos for thoughtbot are trademarks of thoughtbot, inc.
|
data/Rakefile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
begin
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'bundler/gem_tasks'
|
5
|
+
rescue LoadError
|
6
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
7
|
+
exit 1
|
8
|
+
end
|
9
|
+
|
10
|
+
Bundler::GemHelper.install_tasks
|
11
|
+
|
12
|
+
require 'rspec/core/rake_task'
|
13
|
+
RSpec::Core::RakeTask.new(:spec)
|
14
|
+
task default: :spec
|
@@ -0,0 +1,25 @@
|
|
1
|
+
class Griddler::EmailsController < ActionController::Base
|
2
|
+
def create
|
3
|
+
normalized_params.each do |p|
|
4
|
+
process_email Griddler::Email.new(p)
|
5
|
+
end
|
6
|
+
|
7
|
+
head :ok
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
delegate :processor_class, :processor_method, :email_service, to: :griddler_configuration
|
13
|
+
|
14
|
+
def normalized_params
|
15
|
+
Array.wrap(email_service.normalize_params(params))
|
16
|
+
end
|
17
|
+
|
18
|
+
def process_email(email)
|
19
|
+
processor_class.new(email).public_send(processor_method)
|
20
|
+
end
|
21
|
+
|
22
|
+
def griddler_configuration
|
23
|
+
Griddler.configuration
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'griddler'
|
data/lib/griddler.rb
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
require 'rails/engine' if defined?(::Rails::Engine)
|
2
|
+
require 'action_view'
|
3
|
+
require 'griddler/errors'
|
4
|
+
require 'griddler/engine'
|
5
|
+
require 'griddler/email'
|
6
|
+
require 'griddler/email_parser'
|
7
|
+
require 'griddler/configuration'
|
8
|
+
require 'griddler/route_extensions'
|
9
|
+
require 'griddler/adapter_registry'
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Griddler
|
2
|
+
class AdapterRegistry
|
3
|
+
DEFAULT_ADAPTER = :sendgrid
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@registry = {}
|
7
|
+
end
|
8
|
+
|
9
|
+
def register(adapter_name, adapter_class)
|
10
|
+
if adapter_name == DEFAULT_ADAPTER
|
11
|
+
@registry[:default] = adapter_class
|
12
|
+
end
|
13
|
+
@registry[adapter_name] = adapter_class
|
14
|
+
end
|
15
|
+
|
16
|
+
def [](adapter_name)
|
17
|
+
@registry[adapter_name]
|
18
|
+
end
|
19
|
+
|
20
|
+
def fetch(key, &block)
|
21
|
+
@registry.fetch(key, &block)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.adapter_registry
|
26
|
+
@adapter_registry ||= AdapterRegistry.new
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module Griddler
|
2
|
+
@@configuration = nil
|
3
|
+
|
4
|
+
def self.configure
|
5
|
+
@@configuration = Configuration.new
|
6
|
+
|
7
|
+
if block_given?
|
8
|
+
yield configuration
|
9
|
+
end
|
10
|
+
|
11
|
+
configuration
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.configuration
|
15
|
+
@@configuration || configure
|
16
|
+
end
|
17
|
+
|
18
|
+
class Configuration
|
19
|
+
attr_accessor :processor_class, :processor_method, :reply_delimiter
|
20
|
+
|
21
|
+
def processor_class
|
22
|
+
@processor_class ||=
|
23
|
+
begin
|
24
|
+
if Kernel.const_defined?(:EmailProcessor)
|
25
|
+
EmailProcessor
|
26
|
+
else
|
27
|
+
raise NameError.new(<<-ERROR.strip_heredoc, 'EmailProcessor')
|
28
|
+
To use Griddler, you must either define `EmailProcessor` or configure a
|
29
|
+
different processor. See https://github.com/thoughtbot/griddler#defaults for
|
30
|
+
more information.
|
31
|
+
ERROR
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def processor_method
|
37
|
+
@processor_method ||= :process
|
38
|
+
end
|
39
|
+
|
40
|
+
def reply_delimiter
|
41
|
+
@reply_delimiter ||= 'Reply ABOVE THIS LINE'
|
42
|
+
end
|
43
|
+
|
44
|
+
def email_service
|
45
|
+
@email_service_adapter ||= Griddler.adapter_registry[:default]
|
46
|
+
end
|
47
|
+
|
48
|
+
def email_service=(new_email_service)
|
49
|
+
@email_service_adapter = Griddler.adapter_registry.fetch(new_email_service) { raise Griddler::Errors::EmailServiceAdapterNotFound }
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
require 'htmlentities'
|
2
|
+
|
3
|
+
module Griddler
|
4
|
+
class Email
|
5
|
+
include ActionView::Helpers::SanitizeHelper
|
6
|
+
attr_reader :to, :from, :cc, :subject, :body, :raw_body, :raw_text, :raw_html,
|
7
|
+
:headers, :raw_headers, :attachments
|
8
|
+
|
9
|
+
def initialize(params)
|
10
|
+
@params = params
|
11
|
+
|
12
|
+
@to = recipients(:to)
|
13
|
+
@from = extract_address(params[:from])
|
14
|
+
@subject = params[:subject]
|
15
|
+
|
16
|
+
@body = extract_body
|
17
|
+
@raw_text = params[:text]
|
18
|
+
@raw_html = params[:html]
|
19
|
+
@raw_body = @raw_text.presence || @raw_html
|
20
|
+
|
21
|
+
@headers = extract_headers
|
22
|
+
|
23
|
+
@cc = recipients(:cc)
|
24
|
+
|
25
|
+
@raw_headers = params[:headers]
|
26
|
+
|
27
|
+
@attachments = params[:attachments]
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
attr_reader :params
|
33
|
+
|
34
|
+
def config
|
35
|
+
@config ||= Griddler.configuration
|
36
|
+
end
|
37
|
+
|
38
|
+
def recipients(type)
|
39
|
+
params[type].to_a.map { |recipient| extract_address(recipient) }
|
40
|
+
end
|
41
|
+
|
42
|
+
def extract_address(address)
|
43
|
+
EmailParser.parse_address(address)
|
44
|
+
end
|
45
|
+
|
46
|
+
def extract_body
|
47
|
+
EmailParser.extract_reply_body(text_or_sanitized_html)
|
48
|
+
end
|
49
|
+
|
50
|
+
def extract_headers
|
51
|
+
EmailParser.extract_headers(params[:headers])
|
52
|
+
end
|
53
|
+
|
54
|
+
def extract_cc_from_headers(headers)
|
55
|
+
EmailParser.extract_cc(headers)
|
56
|
+
end
|
57
|
+
|
58
|
+
def text_or_sanitized_html
|
59
|
+
text = clean_text(params.fetch(:text, ''))
|
60
|
+
text.presence || clean_html(params.fetch(:html, '')).presence
|
61
|
+
end
|
62
|
+
|
63
|
+
def clean_text(text)
|
64
|
+
clean_invalid_utf8_bytes(text)
|
65
|
+
end
|
66
|
+
|
67
|
+
def clean_html(html)
|
68
|
+
cleaned_html = clean_invalid_utf8_bytes(html)
|
69
|
+
cleaned_html = strip_tags(cleaned_html)
|
70
|
+
cleaned_html = HTMLEntities.new.decode(cleaned_html)
|
71
|
+
cleaned_html
|
72
|
+
end
|
73
|
+
|
74
|
+
def clean_invalid_utf8_bytes(text)
|
75
|
+
if !text.valid_encoding?
|
76
|
+
text = text
|
77
|
+
.force_encoding('ISO-8859-1')
|
78
|
+
.encode('UTF-8')
|
79
|
+
end
|
80
|
+
|
81
|
+
text
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
# Parse emails from their full format into a hash containing full email, host,
|
2
|
+
# local token, and the raw argument.
|
3
|
+
#
|
4
|
+
# Some Body <somebody@example.com>
|
5
|
+
# # => {
|
6
|
+
# token: 'somebody',
|
7
|
+
# host: 'example.com',
|
8
|
+
# email: 'somebody@example.com',
|
9
|
+
# full: 'Some Body <somebody@example.com>',
|
10
|
+
# }
|
11
|
+
require 'mail'
|
12
|
+
|
13
|
+
module Griddler::EmailParser
|
14
|
+
def self.parse_address(full_address)
|
15
|
+
email_address = extract_email_address(full_address)
|
16
|
+
name = extract_name(full_address)
|
17
|
+
token, host = split_address(email_address)
|
18
|
+
{
|
19
|
+
token: token,
|
20
|
+
host: host,
|
21
|
+
email: email_address,
|
22
|
+
full: full_address,
|
23
|
+
name: name,
|
24
|
+
}
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.extract_reply_body(body)
|
28
|
+
if body.blank?
|
29
|
+
""
|
30
|
+
else
|
31
|
+
remove_reply_portion(body)
|
32
|
+
.split(/[\r]*\n/)
|
33
|
+
.reject do |line|
|
34
|
+
line =~ /^\s+>/ ||
|
35
|
+
line =~ /^\s*Sent from my /
|
36
|
+
end.
|
37
|
+
join("\n").
|
38
|
+
strip
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.extract_headers(raw_headers)
|
43
|
+
header_fields = Mail::Header.new(raw_headers).fields
|
44
|
+
|
45
|
+
header_fields.inject({}) do |header_hash, header_field|
|
46
|
+
header_hash[header_field.name.to_s] = header_field.value.to_s
|
47
|
+
header_hash
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def self.reply_delimeter_regex
|
54
|
+
delimiter = Array(Griddler.configuration.reply_delimiter).join('|')
|
55
|
+
%r{#{delimiter}}
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.extract_email_address(full_address)
|
59
|
+
full_address.split('<').last.delete('>').strip
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.extract_name(full_address)
|
63
|
+
full_address = full_address.strip
|
64
|
+
name = full_address.split('<').first.strip
|
65
|
+
if name.present? && name != full_address
|
66
|
+
name
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.split_address(email_address)
|
71
|
+
email_address.try :split, '@'
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.regex_split_points
|
75
|
+
[
|
76
|
+
reply_delimeter_regex,
|
77
|
+
/^\s*[-]+\s*Original Message\s*[-]+\s*$/i,
|
78
|
+
/^\s*--\s*$/,
|
79
|
+
/^\s*\>?\s*On.*\r?\n?\s*.*\s*wrote:\r?\n?$/,
|
80
|
+
/On.*wrote:/,
|
81
|
+
/From:.*$/i,
|
82
|
+
/^\s*\d{4}\/\d{1,2}\/\d{1,2}\s.*\s<.*>?$/i
|
83
|
+
]
|
84
|
+
end
|
85
|
+
|
86
|
+
def self.remove_reply_portion(body)
|
87
|
+
regex_split_points.inject(body) do |result, split_point|
|
88
|
+
result.split(split_point).first || ""
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
require 'action_dispatch'
|
2
|
+
|
3
|
+
module Griddler::Testing
|
4
|
+
def upload_1
|
5
|
+
@upload_1 ||= UploadedImage.new('photo1.jpg').file
|
6
|
+
end
|
7
|
+
|
8
|
+
def upload_2
|
9
|
+
@upload_2 ||= UploadedImage.new('photo2.jpg').file
|
10
|
+
end
|
11
|
+
|
12
|
+
def normalize_params(params)
|
13
|
+
Griddler::Sendgrid::Adapter.normalize_params(params)
|
14
|
+
end
|
15
|
+
|
16
|
+
class UploadedImage
|
17
|
+
def initialize(name)
|
18
|
+
@name = name
|
19
|
+
end
|
20
|
+
|
21
|
+
def file
|
22
|
+
ActionDispatch::Http::UploadedFile.new({
|
23
|
+
filename: @name,
|
24
|
+
type: 'image/jpeg',
|
25
|
+
tempfile: fixture_file
|
26
|
+
})
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def fixture_file
|
32
|
+
cwd = File.expand_path File.dirname(__FILE__)
|
33
|
+
File.new(File.join(cwd, '..', '..', 'spec', 'fixtures', @name))
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
shared_examples_for 'Griddler adapter' do |adapter, service_params|
|
39
|
+
it 'adapts params to expected values' do
|
40
|
+
Griddler.configuration.email_service = adapter
|
41
|
+
|
42
|
+
normalized_params = Griddler.configuration.email_service.normalize_params(service_params)
|
43
|
+
|
44
|
+
Array.wrap(normalized_params).each do |params|
|
45
|
+
email = Griddler::Email.new(params)
|
46
|
+
|
47
|
+
expect(email.to).to eq([{
|
48
|
+
token: 'hi',
|
49
|
+
host: 'example.com',
|
50
|
+
full: 'Hello World <hi@example.com>',
|
51
|
+
email: 'hi@example.com',
|
52
|
+
name: 'Hello World',
|
53
|
+
}])
|
54
|
+
expect(email.cc).to eq [{
|
55
|
+
token: 'emily',
|
56
|
+
host: 'example.com',
|
57
|
+
email: 'emily@example.com',
|
58
|
+
full: 'emily@example.com',
|
59
|
+
name: nil
|
60
|
+
}]
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
RSpec::Matchers.define :be_normalized_to do |expected|
|
66
|
+
failure_message do |actual|
|
67
|
+
message = ""
|
68
|
+
expected.each do |k, v|
|
69
|
+
if actual[k] != expected[k]
|
70
|
+
message << "expected :#{k} to be normalized to #{expected[k].inspect}, "\
|
71
|
+
"but received #{actual[k].inspect}\n"
|
72
|
+
end
|
73
|
+
end
|
74
|
+
message
|
75
|
+
end
|
76
|
+
|
77
|
+
description do
|
78
|
+
"be normalized to #{expected}"
|
79
|
+
end
|
80
|
+
|
81
|
+
match do |actual|
|
82
|
+
expected.each do |k, v|
|
83
|
+
case v
|
84
|
+
when Regexp then expect(actual[k]).to =~ v
|
85
|
+
else expect(actual[k]).to === v
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
metadata
ADDED
@@ -0,0 +1,138 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: griddler-acd
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Caleb Thompson
|
8
|
+
- Joel Oliveira
|
9
|
+
- thoughtbot
|
10
|
+
- Swift
|
11
|
+
autorequire:
|
12
|
+
bindir: bin
|
13
|
+
cert_chain: []
|
14
|
+
date: 2014-08-20 00:00:00.000000000 Z
|
15
|
+
dependencies:
|
16
|
+
- !ruby/object:Gem::Dependency
|
17
|
+
name: htmlentities
|
18
|
+
requirement: !ruby/object:Gem::Requirement
|
19
|
+
requirements:
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '0'
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rspec-rails
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
requirements:
|
34
|
+
- - ">="
|
35
|
+
- !ruby/object:Gem::Version
|
36
|
+
version: '0'
|
37
|
+
type: :development
|
38
|
+
prerelease: false
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
- !ruby/object:Gem::Dependency
|
45
|
+
name: sqlite3
|
46
|
+
requirement: !ruby/object:Gem::Requirement
|
47
|
+
requirements:
|
48
|
+
- - ">="
|
49
|
+
- !ruby/object:Gem::Version
|
50
|
+
version: '0'
|
51
|
+
type: :development
|
52
|
+
prerelease: false
|
53
|
+
version_requirements: !ruby/object:Gem::Requirement
|
54
|
+
requirements:
|
55
|
+
- - ">="
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: '0'
|
58
|
+
- !ruby/object:Gem::Dependency
|
59
|
+
name: pry
|
60
|
+
requirement: !ruby/object:Gem::Requirement
|
61
|
+
requirements:
|
62
|
+
- - ">="
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
version: '0'
|
65
|
+
type: :development
|
66
|
+
prerelease: false
|
67
|
+
version_requirements: !ruby/object:Gem::Requirement
|
68
|
+
requirements:
|
69
|
+
- - ">="
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
version: '0'
|
72
|
+
- !ruby/object:Gem::Dependency
|
73
|
+
name: jquery-rails
|
74
|
+
requirement: !ruby/object:Gem::Requirement
|
75
|
+
requirements:
|
76
|
+
- - ">="
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
version: '0'
|
79
|
+
type: :development
|
80
|
+
prerelease: false
|
81
|
+
version_requirements: !ruby/object:Gem::Requirement
|
82
|
+
requirements:
|
83
|
+
- - ">="
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
description:
|
87
|
+
email:
|
88
|
+
- cjaysson@gmail.com
|
89
|
+
- joel@thoughtbot.com
|
90
|
+
- theycallmeswift@gmail.com
|
91
|
+
executables: []
|
92
|
+
extensions: []
|
93
|
+
extra_rdoc_files: []
|
94
|
+
files:
|
95
|
+
- LICENSE
|
96
|
+
- README.md
|
97
|
+
- Rakefile
|
98
|
+
- app/controllers/griddler/emails_controller.rb
|
99
|
+
- config/initializers/griddler.rb
|
100
|
+
- lib/griddler.rb
|
101
|
+
- lib/griddler/adapter_registry.rb
|
102
|
+
- lib/griddler/configuration.rb
|
103
|
+
- lib/griddler/email.rb
|
104
|
+
- lib/griddler/email_parser.rb
|
105
|
+
- lib/griddler/engine.rb
|
106
|
+
- lib/griddler/errors.rb
|
107
|
+
- lib/griddler/route_extensions.rb
|
108
|
+
- lib/griddler/testing.rb
|
109
|
+
- lib/griddler/version.rb
|
110
|
+
homepage: http://thoughtbot.com
|
111
|
+
licenses: []
|
112
|
+
metadata: {}
|
113
|
+
post_install_message: |
|
114
|
+
When upgrading from a Griddler version previous to 0.5.0, it is important that
|
115
|
+
you view https://github.com/thoughtbot/griddler/#upgrading-to-griddler-050 for
|
116
|
+
upgrade information.
|
117
|
+
rdoc_options: []
|
118
|
+
require_paths:
|
119
|
+
- app
|
120
|
+
- lib
|
121
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
122
|
+
requirements:
|
123
|
+
- - ">="
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: 1.9.2
|
126
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
127
|
+
requirements:
|
128
|
+
- - ">="
|
129
|
+
- !ruby/object:Gem::Version
|
130
|
+
version: '0'
|
131
|
+
requirements: []
|
132
|
+
rubyforge_project:
|
133
|
+
rubygems_version: 2.3.0
|
134
|
+
signing_key:
|
135
|
+
specification_version: 4
|
136
|
+
summary: SendGrid Parse API client Rails Engine
|
137
|
+
test_files: []
|
138
|
+
has_rdoc:
|