mailgun-ruby 1.1.8 → 1.2.3
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 +5 -5
- data/.travis.yml +6 -5
- data/Gemfile +1 -1
- data/README.md +24 -2
- data/docs/Domains.md +0 -0
- data/docs/Webhooks.md +1 -1
- data/docs/railgun/Overview.md +11 -0
- data/docs/railgun/Parameters.md +83 -0
- data/lib/mailgun/client.rb +10 -5
- data/lib/mailgun/events/events.rb +1 -1
- data/lib/mailgun/messages/batch_message.rb +1 -0
- data/lib/mailgun/messages/message_builder.rb +18 -4
- data/lib/mailgun/suppressions.rb +4 -1
- data/lib/mailgun/version.rb +1 -1
- data/lib/mailgun/webhooks/webhooks.rb +1 -1
- data/lib/railgun/mailer.rb +84 -8
- data/mailgun.gemspec +11 -11
- data/spec/integration/email_validation_spec.rb +8 -0
- data/spec/integration/events_spec.rb +1 -1
- data/spec/integration/suppressions_spec.rb +18 -2
- data/spec/spec_helper.rb +3 -1
- data/spec/unit/events/events_spec.rb +19 -0
- data/spec/unit/messages/batch_message_spec.rb +1 -0
- data/spec/unit/messages/message_builder_spec.rb +45 -10
- data/spec/unit/messages/sample_data/unknown.type +0 -0
- data/spec/unit/railgun/content_type_spec.rb +71 -0
- data/spec/unit/railgun/mailer_spec.rb +229 -0
- data/vcr_cassettes/suppressions.yml +66 -15
- metadata +49 -29
- data/.ruby-version +0 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 4fec3197ad0202b588d430e3bbbc96e822961e0213a9b45882e5f6490bd9b5bf
|
4
|
+
data.tar.gz: 8db91a5a020812f436900efb0d181baede311ed6a6d8390a28fa907a32099897
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 164c3e148cd16078ad96c2f60620720e754a412dc8ae69bb0248b1e0dc599e5f570314e5b668a356ff90e3e12e1e72879276c9ccf91901c9502ad98cf56a7781
|
7
|
+
data.tar.gz: be6d57fca9b0224ddc99a17831bef724e05a6e63dfef177c9d478bec8efefe943bf3f07886902da015474d0e5c2a33ddf797cf7ba11db1c7ce2eda8648b0ea35
|
data/.travis.yml
CHANGED
@@ -1,10 +1,11 @@
|
|
1
1
|
language: ruby
|
2
2
|
sudo: false
|
3
3
|
rvm:
|
4
|
-
- 2.
|
5
|
-
- 2.
|
6
|
-
- 2.
|
7
|
-
- 2.
|
4
|
+
- 2.2.2
|
5
|
+
- 2.2.10
|
6
|
+
- 2.3.7
|
7
|
+
- 2.4.4
|
8
|
+
- 2.5.1
|
8
9
|
script:
|
9
10
|
- bundle install
|
10
11
|
- bundle exec rake spec
|
@@ -17,7 +18,7 @@ deploy:
|
|
17
18
|
gemspec: mailgun.gemspec
|
18
19
|
on:
|
19
20
|
tags: true
|
20
|
-
condition: "$TRAVIS_RUBY_VERSION == 2.
|
21
|
+
condition: "$TRAVIS_RUBY_VERSION == 2.5.1"
|
21
22
|
notifications:
|
22
23
|
slack:
|
23
24
|
rooms:
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -19,7 +19,7 @@ gem install mailgun-ruby
|
|
19
19
|
Gemfile:
|
20
20
|
|
21
21
|
```ruby
|
22
|
-
gem 'mailgun-ruby', '~>1.
|
22
|
+
gem 'mailgun-ruby', '~>1.2.3'
|
23
23
|
```
|
24
24
|
|
25
25
|
Usage
|
@@ -27,7 +27,7 @@ Usage
|
|
27
27
|
Here's how to send a message using the library:
|
28
28
|
|
29
29
|
```ruby
|
30
|
-
require 'mailgun'
|
30
|
+
require 'mailgun-ruby'
|
31
31
|
|
32
32
|
# First, instantiate the Mailgun Client with your API key
|
33
33
|
mg_client = Mailgun::Client.new 'your-api-key'
|
@@ -56,6 +56,12 @@ domain = 'example.com'
|
|
56
56
|
result = mg_client.get("#{domain}/events", {:event => 'delivered'})
|
57
57
|
```
|
58
58
|
|
59
|
+
If you're using the EU domains, make sure you specify it when creating the client:
|
60
|
+
|
61
|
+
```
|
62
|
+
mg_client = Mailgun::Client.new 'your-api-key', 'api.eu.mailgun.net'
|
63
|
+
```
|
64
|
+
|
59
65
|
Rails
|
60
66
|
-----
|
61
67
|
|
@@ -74,9 +80,25 @@ and replace `api-myapikey` and `mydomain.com` with your secret API key and domai
|
|
74
80
|
config.action_mailer.mailgun_settings = {
|
75
81
|
api_key: 'api-myapikey',
|
76
82
|
domain: 'mydomain.com',
|
83
|
+
# api_host: 'api.eu.mailgun.net' # Uncomment this line for EU region domains
|
77
84
|
}
|
78
85
|
```
|
79
86
|
|
87
|
+
To specify Mailgun options such as campaign or tags:
|
88
|
+
```ruby
|
89
|
+
class UserMailer < ApplicationMailer
|
90
|
+
def welcome_email
|
91
|
+
mail(to: params[:to], subject: "Welcome!").tap do |message|
|
92
|
+
message.mailgun_options = {
|
93
|
+
"tag" => ["abtest-option-a", "beta-user"],
|
94
|
+
"tracking-opens" => true,
|
95
|
+
"tracking-clicks" => "htmlonly"
|
96
|
+
}
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
```
|
101
|
+
|
80
102
|
To get the Mailgun `message_id` after ActionMailer has successfully delivered the email:
|
81
103
|
|
82
104
|
```ruby
|
data/docs/Domains.md
CHANGED
File without changes
|
data/docs/Webhooks.md
CHANGED
@@ -31,7 +31,7 @@ hook.create 'my.perfect.domain', 'deliver', 'https://the.webhook.url/'
|
|
31
31
|
hook.remove 'my.perfect.domain', 'deliver'
|
32
32
|
|
33
33
|
# Remove all webhooks for a domain
|
34
|
-
hook.
|
34
|
+
hook.remove_all 'my.perfect.domain'
|
35
35
|
```
|
36
36
|
|
37
37
|
More Documentation
|
@@ -0,0 +1,11 @@
|
|
1
|
+
Overview
|
2
|
+
========
|
3
|
+
|
4
|
+
Railgun is a Rails add-on that allows ActionMailer to send via the Mailgun API.
|
5
|
+
|
6
|
+
See [railgun-sample](https://github.com/pirogoeth/railgun-sample/) for examples of integrating Railgun with your Rails app.
|
7
|
+
|
8
|
+
|
9
|
+
## Table of Contents
|
10
|
+
|
11
|
+
- [Parameters](/docs/railgun/Parameters.md)
|
@@ -0,0 +1,83 @@
|
|
1
|
+
Parameters
|
2
|
+
==========
|
3
|
+
|
4
|
+
When sending messages via Railgun, it is often useful to set options, headers, and variables
|
5
|
+
that should be added to the `POST` request against the messages endpoint.
|
6
|
+
|
7
|
+
|
8
|
+
## Options
|
9
|
+
|
10
|
+
See [Mailgun Docs | Sending](https://documentation.mailgun.com/en/latest/api-sending.html#sending) for available options.
|
11
|
+
|
12
|
+
---
|
13
|
+
|
14
|
+
To set options on a message:
|
15
|
+
|
16
|
+
```ruby
|
17
|
+
# app/controllers/some_controller.rb
|
18
|
+
|
19
|
+
message = YourMailer.your_message(@args)
|
20
|
+
|
21
|
+
message.mailgun_options ||= {
|
22
|
+
"tracking-opens" => "true",
|
23
|
+
"tracking-clicks" => "htmlonly",
|
24
|
+
"tag" => "some,tags",
|
25
|
+
}
|
26
|
+
```
|
27
|
+
|
28
|
+
|
29
|
+
## Variables
|
30
|
+
|
31
|
+
See [Mailgun Docs | Attaching Data to Messages](https://documentation.mailgun.com/en/latest/user_manual.html#attaching-data-to-messages) for more information.
|
32
|
+
|
33
|
+
---
|
34
|
+
|
35
|
+
To set variables on a message:
|
36
|
+
|
37
|
+
```ruby
|
38
|
+
# app/controllers/some_controller.rb
|
39
|
+
|
40
|
+
message = YourMailer.your_message(@args)
|
41
|
+
|
42
|
+
message.mailgun_variables ||= {
|
43
|
+
"user_info" => {"id" => "1", "name" => "tstark"},
|
44
|
+
}
|
45
|
+
```
|
46
|
+
|
47
|
+
|
48
|
+
## Headers
|
49
|
+
|
50
|
+
See [Mailgun Docs | Sending](https://documentation.mailgun.com/en/latest/api-sending.html#sending) for more information.
|
51
|
+
|
52
|
+
---
|
53
|
+
|
54
|
+
To set headers on a message *from a controller*:
|
55
|
+
|
56
|
+
```ruby
|
57
|
+
# app/controllers/some_controller.rb
|
58
|
+
|
59
|
+
message = YourMailer.your_message(@args)
|
60
|
+
|
61
|
+
message.mailgun_headers ||= {
|
62
|
+
"X-Sent-From-Rails" => "true",
|
63
|
+
}
|
64
|
+
```
|
65
|
+
|
66
|
+
To set headers on a message *from a mailer*:
|
67
|
+
|
68
|
+
```ruby
|
69
|
+
# app/mailers/your_mailer.rb
|
70
|
+
|
71
|
+
class YourMailer < ApplicationMailer
|
72
|
+
# ...
|
73
|
+
|
74
|
+
def your_message(args)
|
75
|
+
headers({
|
76
|
+
"X-Sent-From-Rails" => "true",
|
77
|
+
})
|
78
|
+
|
79
|
+
mail to: "some-address@example.org", ...
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
```
|
data/lib/mailgun/client.rb
CHANGED
@@ -14,13 +14,18 @@ module Mailgun
|
|
14
14
|
api_host = 'api.mailgun.net',
|
15
15
|
api_version = 'v3',
|
16
16
|
ssl = true,
|
17
|
-
test_mode = false
|
17
|
+
test_mode = false,
|
18
|
+
timeout = nil)
|
19
|
+
|
20
|
+
rest_client_params = {
|
21
|
+
user: 'api',
|
22
|
+
password: api_key,
|
23
|
+
user_agent: "mailgun-sdk-ruby/#{Mailgun::VERSION}"
|
24
|
+
}
|
25
|
+
rest_client_params[:timeout] = timeout if timeout
|
18
26
|
|
19
27
|
endpoint = endpoint_generator(api_host, api_version, ssl)
|
20
|
-
@http_client = RestClient::Resource.new(endpoint,
|
21
|
-
user: 'api',
|
22
|
-
password: api_key,
|
23
|
-
user_agent: "mailgun-sdk-ruby/#{Mailgun::VERSION}")
|
28
|
+
@http_client = RestClient::Resource.new(endpoint, rest_client_params)
|
24
29
|
@test_mode = test_mode
|
25
30
|
end
|
26
31
|
|
@@ -101,7 +101,7 @@ module Mailgun
|
|
101
101
|
# Returns a String of the partial URI if the given url follows the regular API format
|
102
102
|
# Returns nil in other cases (e.g. when given nil, or an irrelevant url)
|
103
103
|
def extract_endpoint_from(url = nil)
|
104
|
-
URI.parse(url).path[
|
104
|
+
URI.parse(url).path[/\/v[\d]\/#{@domain}\/events\/(.+)/,1]
|
105
105
|
rescue URI::InvalidURIError
|
106
106
|
nil
|
107
107
|
end
|
@@ -111,6 +111,7 @@ module Mailgun
|
|
111
111
|
# This method resets the message object to prepare for the next batch
|
112
112
|
# of recipients.
|
113
113
|
def reset_message
|
114
|
+
@recipient_variables = {}
|
114
115
|
@message.delete('recipient-variables')
|
115
116
|
@message.delete(:to)
|
116
117
|
@message.delete(:cc)
|
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'mime/types'
|
1
2
|
require 'time'
|
2
3
|
|
3
4
|
module Mailgun
|
@@ -197,7 +198,7 @@ module Mailgun
|
|
197
198
|
# @param [Boolean] tracking Boolean true or false.
|
198
199
|
# @return [void]
|
199
200
|
def track_opens(mode)
|
200
|
-
|
201
|
+
set_single('o:tracking-opens', bool_lookup(mode))
|
201
202
|
end
|
202
203
|
|
203
204
|
# Deprecated: 'set_open_tracking' is deprecated. Please use 'track_opens' instead.
|
@@ -211,7 +212,7 @@ module Mailgun
|
|
211
212
|
# @param [String] mode True, False, or HTML (for HTML only tracking)
|
212
213
|
# @return [void]
|
213
214
|
def track_clicks(mode)
|
214
|
-
|
215
|
+
set_single('o:tracking-clicks', bool_lookup(mode))
|
215
216
|
end
|
216
217
|
|
217
218
|
# Depreciated: 'set_click_tracking. is deprecated. Please use 'track_clicks' instead.
|
@@ -379,10 +380,17 @@ module Mailgun
|
|
379
380
|
def parse_address(address, vars)
|
380
381
|
return address unless vars.is_a? Hash
|
381
382
|
fail(Mailgun::ParameterError, 'Email address not specified') unless address.is_a? String
|
383
|
+
if vars['full_name'] != nil && (vars['first'] != nil || vars['last'] != nil)
|
384
|
+
fail(Mailgun::ParameterError, 'Must specify at most one of full_name or first/last. Vars passed: #{vars}')
|
385
|
+
end
|
382
386
|
|
383
|
-
|
387
|
+
if vars['full_name']
|
388
|
+
full_name = vars['full_name']
|
389
|
+
elsif vars['first'] || vars['last']
|
390
|
+
full_name = "#{vars['first']} #{vars['last']}".strip
|
391
|
+
end
|
384
392
|
|
385
|
-
return "'#{full_name}' <#{address}>" if
|
393
|
+
return "'#{full_name}' <#{address}>" if full_name
|
386
394
|
address
|
387
395
|
end
|
388
396
|
|
@@ -402,6 +410,12 @@ module Mailgun
|
|
402
410
|
'Unable to access attachment file object.'
|
403
411
|
) unless attachment.respond_to?(:read)
|
404
412
|
|
413
|
+
if attachment.respond_to?(:path) && !attachment.respond_to?(:content_type)
|
414
|
+
mime_types = MIME::Types.type_for(attachment.path)
|
415
|
+
content_type = mime_types.empty? ? 'application/octet-stream' : mime_types[0].content_type
|
416
|
+
attachment.instance_eval "def content_type; '#{content_type}'; end"
|
417
|
+
end
|
418
|
+
|
405
419
|
unless filename.nil?
|
406
420
|
attachment.instance_variable_set :@original_filename, filename
|
407
421
|
attachment.instance_eval 'def original_filename; @original_filename; end'
|
data/lib/mailgun/suppressions.rb
CHANGED
@@ -157,7 +157,10 @@ module Mailgun
|
|
157
157
|
|
158
158
|
unsubscribe.each do |k, v|
|
159
159
|
# Hash values MUST be strings.
|
160
|
-
|
160
|
+
# However, unsubscribes contain an array of tags
|
161
|
+
if v.is_a? Array
|
162
|
+
unsubscribe[k] = v.map(&:to_s)
|
163
|
+
elsif !v.is_a? String
|
161
164
|
unsubscribe[k] = v.to_s
|
162
165
|
end
|
163
166
|
end
|
data/lib/mailgun/version.rb
CHANGED
@@ -46,7 +46,7 @@ module Mailgun
|
|
46
46
|
# Returns a Boolean of whether the webhook was created
|
47
47
|
def create(domain, action, url = '')
|
48
48
|
res = @client.post("domains/#{domain}/webhooks", id: action, url: url)
|
49
|
-
res.to_h['webhook']['url'] == url && res.to_h[message] == 'Webhook has been created'
|
49
|
+
res.to_h['webhook']['url'] == url && res.to_h['message'] == 'Webhook has been created'
|
50
50
|
end
|
51
51
|
alias_method :add, :create
|
52
52
|
alias_method :add_webhook, :create
|
data/lib/railgun/mailer.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'action_mailer'
|
2
|
+
require 'json'
|
2
3
|
require 'mailgun'
|
3
4
|
require 'rails'
|
4
5
|
require 'railgun/errors'
|
@@ -9,6 +10,9 @@ module Railgun
|
|
9
10
|
# Mailgun.
|
10
11
|
class Mailer
|
11
12
|
|
13
|
+
# List of the headers that will be ignored when copying headers from `mail.header_fields`
|
14
|
+
IGNORED_HEADERS = %w[ to from subject reply-to ]
|
15
|
+
|
12
16
|
# [Hash] config ->
|
13
17
|
# Requires *at least* `api_key` and `domain` keys.
|
14
18
|
attr_accessor :config, :domain, :settings
|
@@ -23,7 +27,14 @@ module Railgun
|
|
23
27
|
raise Railgun::ConfigurationError.new("Config requires `#{k}` key", @config) unless @config.has_key?(k)
|
24
28
|
end
|
25
29
|
|
26
|
-
@mg_client = Mailgun::Client.new(
|
30
|
+
@mg_client = Mailgun::Client.new(
|
31
|
+
config[:api_key],
|
32
|
+
config[:api_host] || 'api.mailgun.net',
|
33
|
+
config[:api_version] || 'v3',
|
34
|
+
config[:api_ssl].nil? ? true : config[:api_ssl],
|
35
|
+
false,
|
36
|
+
config[:timeout],
|
37
|
+
)
|
27
38
|
@domain = @config[:domain]
|
28
39
|
|
29
40
|
# To avoid exception in mail gem v2.6
|
@@ -47,7 +58,7 @@ module Railgun
|
|
47
58
|
end
|
48
59
|
|
49
60
|
def mailgun_client
|
50
|
-
@
|
61
|
+
@mg_client
|
51
62
|
end
|
52
63
|
|
53
64
|
end
|
@@ -58,6 +69,9 @@ module Railgun
|
|
58
69
|
# After prefixing them with the proper option type, they are added to
|
59
70
|
# the message hash where they will then be sent to the API as JSON.
|
60
71
|
#
|
72
|
+
# It is important to note that headers set in `mailgun_headers` on the message
|
73
|
+
# WILL overwrite headers set via `mail.headers()`.
|
74
|
+
#
|
61
75
|
# @param [Mail::Message] mail message to transform
|
62
76
|
#
|
63
77
|
# @return [Hash] transformed message hash
|
@@ -66,16 +80,49 @@ module Railgun
|
|
66
80
|
|
67
81
|
# v:* attributes (variables)
|
68
82
|
mail.mailgun_variables.try(:each) do |k, v|
|
69
|
-
message["v:#{k}"] = v
|
83
|
+
message["v:#{k}"] = JSON.dump(v)
|
70
84
|
end
|
71
85
|
|
72
86
|
# o:* attributes (options)
|
73
87
|
mail.mailgun_options.try(:each) do |k, v|
|
74
|
-
message["o:#{k}"] = v
|
88
|
+
message["o:#{k}"] = v.dup
|
75
89
|
end
|
76
90
|
|
91
|
+
# support for using ActionMailer's `headers()` inside of the mailer
|
92
|
+
# note: this will filter out parameters such as `from`, `to`, and so forth
|
93
|
+
# as they are accepted as POST parameters on the message endpoint.
|
94
|
+
|
95
|
+
msg_headers = Hash.new
|
96
|
+
|
77
97
|
# h:* attributes (headers)
|
78
|
-
|
98
|
+
|
99
|
+
# Let's set all of these headers on the [Mail::Message] so that
|
100
|
+
# the are created inside of a [Mail::Header] instance and processed there.
|
101
|
+
mail.headers(mail.mailgun_headers || {})
|
102
|
+
mail.header_fields.each do |field|
|
103
|
+
header = field.name.downcase
|
104
|
+
if msg_headers.include? header
|
105
|
+
msg_headers[header] = [msg_headers[header], field.value].flatten
|
106
|
+
else
|
107
|
+
msg_headers[header] = field.value
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
msg_headers.each do |k, v|
|
112
|
+
if Railgun::Mailer::IGNORED_HEADERS.include? k.downcase
|
113
|
+
Rails.logger.debug("[railgun] ignoring header (using envelope instead): #{k}")
|
114
|
+
next
|
115
|
+
end
|
116
|
+
|
117
|
+
# Cover cases like `cc`, `bcc` where parameters are valid
|
118
|
+
# headers BUT they are submitted as separate POST params
|
119
|
+
# and already exist on the message because of the call to
|
120
|
+
# `build_message_object`.
|
121
|
+
if message.include? k.downcase
|
122
|
+
Rails.logger.debug("[railgun] ignoring header (already set): #{k}")
|
123
|
+
next
|
124
|
+
end
|
125
|
+
|
79
126
|
message["h:#{k}"] = v
|
80
127
|
end
|
81
128
|
|
@@ -84,7 +131,12 @@ module Railgun
|
|
84
131
|
|
85
132
|
# reject blank values
|
86
133
|
message.delete_if do |k, v|
|
87
|
-
|
134
|
+
return true if v.nil?
|
135
|
+
|
136
|
+
# if it's an array remove empty elements
|
137
|
+
v.delete_if { |i| i.respond_to?(:empty?) && i.empty? } if v.is_a?(Array)
|
138
|
+
|
139
|
+
v.respond_to?(:empty?) && v.empty?
|
88
140
|
end
|
89
141
|
|
90
142
|
return message
|
@@ -138,7 +190,7 @@ module Railgun
|
|
138
190
|
# @return [String]
|
139
191
|
def extract_body_html(mail)
|
140
192
|
begin
|
141
|
-
(mail
|
193
|
+
retrieve_html_part(mail).body.decoded || nil
|
142
194
|
rescue
|
143
195
|
nil
|
144
196
|
end
|
@@ -152,10 +204,34 @@ module Railgun
|
|
152
204
|
# @return [String]
|
153
205
|
def extract_body_text(mail)
|
154
206
|
begin
|
155
|
-
(mail
|
207
|
+
retrieve_text_part(mail).body.decoded || nil
|
156
208
|
rescue
|
157
209
|
nil
|
158
210
|
end
|
159
211
|
end
|
160
212
|
|
213
|
+
# Returns the mail object from the Mail::Message object if text part exists,
|
214
|
+
# (decomposing multipart into individual format if necessary)
|
215
|
+
# otherwise nil.
|
216
|
+
#
|
217
|
+
# @param [Mail::Message] mail message to transform
|
218
|
+
#
|
219
|
+
# @return [Mail::Message] mail message with its content-type = text/plain
|
220
|
+
def retrieve_text_part(mail)
|
221
|
+
return mail.text_part if mail.multipart?
|
222
|
+
(mail.mime_type =~ /^text\/plain$/i) && mail
|
223
|
+
end
|
224
|
+
|
225
|
+
# Returns the mail object from the Mail::Message object if html part exists,
|
226
|
+
# (decomposing multipart into individual format if necessary)
|
227
|
+
# otherwise nil.
|
228
|
+
#
|
229
|
+
# @param [Mail::Message] mail message to transform
|
230
|
+
#
|
231
|
+
# @return [Mail::Message] mail message with its content-type = text/html
|
232
|
+
def retrieve_html_part(mail)
|
233
|
+
return mail.html_part if mail.multipart?
|
234
|
+
(mail.mime_type =~ /^text\/html$/i) && mail
|
235
|
+
end
|
236
|
+
|
161
237
|
end
|