griddler-acd 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/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
|
+
[](https://travis-ci.org/thoughtbot/griddler)
|
|
5
|
+
[](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
|
+

|
|
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:
|